Guest User

Untitled

a guest
Dec 9th, 2019
88
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