SHARE
TWEET

Untitled

a guest Dec 9th, 2019 79 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. class AvatarImageViewMask @JvmOverloads constructor(
  2.     context: Context,
  3.     attrs: AttributeSet? = null,
  4.     defStyleAttr: Int = 0
  5. ) : ImageView(context, attrs, defStyleAttr) {
  6.  
  7.     companion object {
  8.         private const val DEFAULT_SIZE = 40
  9.         private const val DEFAULT_BORDER_WIDTH = 2
  10.         private const val DEFAULT_BORDER_COLOR = Color.WHITE
  11.     }
  12.  
  13.     private var initials: String = "??"
  14.     @Px
  15.     var borderWidth: Float = context.dpToPx(DEFAULT_BORDER_WIDTH)
  16.     @ColorInt
  17.     private var borderColor: Int = Color.WHITE
  18.  
  19.     // сглаживание диагональных линий, рисуемых объектом
  20.     private val maskPaint = Paint(Paint.ANTI_ALIAS_FLAG)
  21.     // наш бордер
  22.     private val borderPaint = Paint(Paint.ANTI_ALIAS_FLAG)
  23.     // размер нашей View
  24.     private val viewRect = Rect()
  25.  
  26.     private lateinit var resultBm: Bitmap
  27.     private lateinit var maskBm: Bitmap
  28.     private lateinit var srcBm: Bitmap
  29.  
  30.     init {
  31.         val ta = context.obtainStyledAttributes(attrs, R.styleable.AvatarImageViewMask)
  32.         try {
  33.             initials = ta.getString(R.styleable.AvatarImageViewMask_aivm_initials) ?: "??"
  34.             borderWidth = ta.getDimension(
  35.                 R.styleable.AvatarImageViewMask_aivm_borderWidth,
  36.                 context.dpToPx(DEFAULT_BORDER_WIDTH)
  37.             )
  38.             borderColor = ta.getColor(R.styleable.AvatarImageViewMask_aivm_borderColor, DEFAULT_BORDER_COLOR)
  39.  
  40.             scaleType = ScaleType.CENTER_CROP
  41.             setup()
  42.         } finally {
  43.             ta.recycle()
  44.         }
  45.     }
  46.  
  47.     private fun setup() {
  48.         with(maskPaint) {
  49.             color = Color.RED
  50.             // Чтобы занимал полностью всё при отрисовке
  51.             style = Paint.Style.FILL
  52.         }
  53.         with(borderPaint) {
  54.             // чисто обводка
  55.             style = Paint.Style.STROKE
  56.             // то, что определили в init()
  57.             color = borderColor
  58.             strokeWidth = borderWidth
  59.         }
  60.     }
  61.  
  62.     private fun prepareBitmaps(w: Int, h: Int) {
  63.         // ARGB_888 - Способна принимать RGB + Alpha-канал (все цвета + прозрачность). Но т.к. хранит много,
  64.         // места в памяти занимает тоже много
  65.         // ALPHA_8 - занимаем меньше места в памяти => производительность лучше. Принимает только альфа-канал,
  66.         // поверх маски мы будем накладывать изображение, и нам нет необходимости учитывать все остальные цвета,
  67.         // которые есть в этой BitMap
  68.         maskBm = Bitmap.createBitmap(w, h, Bitmap.Config.ALPHA_8)
  69.         resultBm = maskBm.copy(Bitmap.Config.ARGB_8888, true)
  70.  
  71.         val maskCanvas = Canvas(maskBm)
  72.         // Отрисуем наш круг
  73.         maskCanvas.drawOval(viewRect.toRectF(), maskPaint)
  74.  
  75.         // Наложение одной битмапы на другую. Берём пересечение
  76.         maskPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
  77.         // Получаем исходный BitMap, корвертим нашу картинку в BitMap
  78.         srcBm = drawable.toBitmap(w, h, Bitmap.Config.ARGB_8888)
  79.  
  80.         // Штука, которая будет складывать наши BitMap
  81.         val resultCanvas = Canvas(resultBm)
  82.  
  83.         // На результирующей отрисуем сначала Bitmap маски, затем Bitmap источника
  84.         resultCanvas.drawBitmap(maskBm, viewRect, viewRect, null)
  85.         // Нужно быть внимательным и следить, что maskPaint стоит вместо null, иначе получим исходную картинку
  86.         resultCanvas.drawBitmap(srcBm, viewRect, viewRect, maskPaint)
  87.     }
  88.  
  89.     // Не стоит производить всякие вычисления, особенно, свзяанные с размерами в callbackах onMeasure(),
  90.     // так как измерение View происходит в несколько подходов, и будут вызываться в несколько раз
  91.     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
  92. //        Log.d("AvatarImageViewMask", """
  93. //          onMeasure()
  94. //          width: ${MeasureSpec.toString(widthMeasureSpec)}
  95. //          height: ${MeasureSpec.toString(heightMeasureSpec)}
  96. //        """.trimIndent())
  97.  
  98.         val initSize = resolveDefaultSize(widthMeasureSpec)
  99.         setMeasuredDimension(initSize, initSize)
  100. //        Log.d("AvatarImageViewMask", "onMeasure() after set size: $measuredWidth $measuredHeight")
  101.     }
  102.  
  103.     // Все изменения, связанные с размерами View, все вычисления, мы можем прекрасно производить в этом методе,
  104.     // потому что в нём мы уже точно знаем, какие размеры имеет наше View
  105.     override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
  106.         super.onSizeChanged(w, h, oldw, oldh)
  107.         Log.d("AvatarImageViewMask", "onSizeChanged()")
  108.  
  109.         if (w == 0) {
  110.             return
  111.         }
  112.         // Когда уже знаем ширину и высоту BitMap, инициализируем размеры нашего примоугольника,
  113.         // которые ограничивает нашу View
  114.         with (viewRect) {
  115.             top = 0
  116.             left = 0
  117.             right = w
  118.             bottom = h
  119.         }
  120.         prepareBitmaps(w, h)
  121.     }
  122.  
  123.     // Не нужен, т.к. у нашей вьюшки не будет детей
  124.     override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
  125.         super.onLayout(changed, left, top, right, bottom)
  126.         Log.d("AvatarImageViewMask", "onLayout()")
  127.     }
  128.  
  129.     override fun onDraw(canvas: Canvas) {
  130. //        super.onDraw(canvas)
  131.         Log.d("AvatarImageViewMask", "onDraw()")
  132.         // В onDraw() не надо создавать никаких объектов. Это надо делать на более ранних этапах. Например, в setup()
  133.         // Тут только отрисовываем объекты. Иначе будем наблюдать вибрации при изменении размеров и цвеирв.
  134.         // Это связано с тем, что GC будет вычищать объекты, которые были аллоцированы, и это будет
  135.         // снижать производительность нашей View
  136.  
  137.         // 2 аргумент - исходный размер
  138.         // 3 аргумент - конечный размер
  139.         canvas.drawBitmap(resultBm, viewRect, viewRect, null)
  140.         // уменьшим размер border, а то он чёт вылазит за границы :((
  141.         val half = (borderWidth / 2).toInt()
  142.         viewRect.inset(half, half)
  143.         // нарисуем овал исходной ширины поверх нашего исображения
  144.         canvas.drawOval(viewRect.toRectF(), borderPaint)
  145.     }
  146.  
  147.     private fun resolveDefaultSize(spec: Int): Int {
  148.         return when (MeasureSpec.getMode(spec)) {
  149.             MeasureSpec.UNSPECIFIED -> {
  150.                 context.dpToPx(DEFAULT_SIZE).toInt()
  151.             }
  152.             else -> MeasureSpec.getSize(spec)
  153.         }
  154.     }
  155. }
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top