Advertisement
Guest User

Untitled

a guest
Dec 8th, 2019
123
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Kotlin 9.14 KB | None | 0 0
  1. package com.nowiwr01.avatarimageview.ui
  2.  
  3. import android.content.Context
  4. import android.graphics.*
  5. import android.graphics.drawable.Drawable
  6. import android.util.AttributeSet
  7. import android.util.Log
  8. import android.widget.ImageView
  9. import androidx.annotation.ColorInt
  10. import androidx.annotation.Px
  11. import androidx.core.graphics.drawable.toBitmap
  12. import androidx.core.graphics.toRectF
  13. import com.nowiwr01.avatarimageview.R
  14. import com.nowiwr01.avatarimageview.extensions.dpToPx
  15. import kotlin.math.truncate
  16.  
  17. class AvatarImageView @JvmOverloads constructor(
  18.     context: Context,
  19.     attrs: AttributeSet? = null,
  20.     defStyleAttr: Int = 0
  21. ) : ImageView(context, attrs, defStyleAttr) {
  22.  
  23.     companion object {
  24.         private const val DEFAULT_SIZE = 40
  25.         private const val DEFAULT_BORDER_WIDTH = 2
  26.         private const val DEFAULT_BORDER_COLOR = Color.WHITE
  27.  
  28.         // background в режими инициалов
  29.         private val bgColor = arrayOf(
  30.             Color.parseColor("#7BC862"),
  31.             Color.parseColor("#E17076"),
  32.             Color.parseColor("#FAA774"),
  33.             Color.parseColor("#6EC9CB"),
  34.             Color.parseColor("#65AADD"),
  35.             Color.parseColor("#A695E7"),
  36.             Color.parseColor("#EE7AAE"),
  37.             Color.parseColor("#2196F3")
  38.         )
  39.     }
  40.  
  41.     private var initials: String = "??"
  42.     @Px
  43.     var borderWidth: Float = context.dpToPx(DEFAULT_BORDER_WIDTH)
  44.     @ColorInt
  45.     private var borderColor: Int = Color.WHITE
  46.  
  47.     // наш бордер
  48.     private val borderPaint = Paint(Paint.ANTI_ALIAS_FLAG)
  49.     private val avatarPaint = Paint(Paint.ANTI_ALIAS_FLAG)
  50.     private val initialsPaint = Paint(Paint.ANTI_ALIAS_FLAG)
  51.     // размер нашей View
  52.     private val viewRect = Rect()
  53.     private val borderRect = Rect()
  54.  
  55.     // аватар или инициалы
  56.     private var isAvatarMode = true
  57.  
  58.     init {
  59.         val ta = context.obtainStyledAttributes(attrs, R.styleable.AvatarImageView)
  60.         try {
  61.             initials = ta.getString(R.styleable.AvatarImageView_aiv_initials) ?: "??"
  62.             borderWidth = ta.getDimension(
  63.                 R.styleable.AvatarImageView_aiv_borderWidth,
  64.                 context.dpToPx(DEFAULT_BORDER_WIDTH)
  65.             )
  66.             borderColor =
  67.                 ta.getColor(R.styleable.AvatarImageView_aiv_borderColor, DEFAULT_BORDER_COLOR)
  68.  
  69.             scaleType = ScaleType.CENTER_CROP
  70.             setup()
  71.         } finally {
  72.             ta.recycle()
  73.         }
  74.  
  75.         setOnLongClickListener {
  76.             handleLongClick()
  77.         }
  78.     }
  79.  
  80.     private fun setup() {
  81.         with(borderPaint) {
  82.             // чисто обводка
  83.             style = Paint.Style.STROKE
  84.             // то, что определили в init()
  85.             color = borderColor
  86.             strokeWidth = borderWidth
  87.         }
  88.     }
  89.  
  90.     // Будем вызывать этот метод, если находимся в avatar-моде
  91.     private fun prepareShader(w: Int, h: Int) {
  92.         if (w == 0 || drawable == null) {
  93.             return
  94.         }
  95.         val srcBm = drawable.toBitmap(w, h, Bitmap.Config.ARGB_8888)
  96.         avatarPaint.shader = BitmapShader(srcBm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
  97.     }
  98.  
  99.     // Не стоит производить всякие вычисления, особенно, свзяанные с размерами в callbackах onMeasure(),
  100.     // так как измерение View происходит в несколько подходов, и будут вызываться в несколько раз
  101.     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
  102. //        Log.d("AvatarImageView", """
  103. //          onMeasure()
  104. //          width: ${MeasureSpec.toString(widthMeasureSpec)}
  105. //          height: ${MeasureSpec.toString(heightMeasureSpec)}
  106. //        """.trimIndent())
  107.  
  108.         val initSize = resolveDefultSize(widthMeasureSpec)
  109.         setMeasuredDimension(initSize, initSize)
  110. //        Log.d("AvatarImageView", "onMeasure() after set size: $measuredWidth $measuredHeight")
  111.     }
  112.  
  113.     // Все изменения, связанные с размерами View, все вычисления, мы можем прекрасно производить в этом методе,
  114.     // потому что в нём мы уже точно знаем, какие размеры имеет наше View
  115.     override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
  116.         super.onSizeChanged(w, h, oldw, oldh)
  117.         Log.d("AvatarImageView", "onSizeChanged()")
  118.  
  119.         if (w == 0) {
  120.             return
  121.         }
  122.         // Когда уже знаем ширину и высоту BitMap, инициализируем размеры нашего примоугольника,
  123.         // которые ограничивает нашу View
  124.         with(viewRect) {
  125.             top = 0
  126.             left = 0
  127.             right = w
  128.             bottom = h
  129.         }
  130.         prepareShader(w, h)
  131.     }
  132.  
  133.     // Не нужен, т.к. у нашей вьюшки не будет детей
  134.     override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
  135.         super.onLayout(changed, left, top, right, bottom)
  136.         Log.d("AvatarImageView", "onLayout()")
  137.     }
  138.  
  139.     override fun onDraw(canvas: Canvas) {
  140. //        super.onDraw(canvas)
  141.         Log.d("AvatarImageView", "onDraw()")
  142.         // В onDraw() не надо создавать никаких объектов. Это надо делать на более ранних этапах. Например, в setup()
  143.         // Тут только отрисовываем объекты. Иначе будем наблюдать вибрации при изменении размеров и цвеирв.
  144.         // Это связано с тем, что GC будет вычищать объекты, которые были аллоцированы, и это будет
  145.         // снижать производительность нашей View
  146.  
  147.         if (drawable != null && isAvatarMode) {
  148.             drawAvatar(canvas)
  149.         } else {
  150.             drawInitials(canvas)
  151.         }
  152.  
  153.         // уменьшим размер border, а то он чёт вылазит за границы :((
  154.         // но при лонг клике тогда будет уменьшаться каждый раз. Чтобы исправить это, созданим новый квадрат
  155.         val half = (borderWidth / 2).toInt()
  156.         borderRect.set(viewRect)
  157.         borderRect.inset(half, half)
  158.         // нарисуем овал исходной ширины поверх нашего исображения
  159.         canvas.drawOval(borderRect.toRectF(), borderPaint)
  160.     }
  161.  
  162.     override fun setImageBitmap(bm: Bitmap?) {
  163.         super.setImageBitmap(bm)
  164.         if (isAvatarMode) {
  165.             prepareShader(width, height)
  166.         }
  167.         Log.d("AvatarImageView", "setImageBitmap()")
  168.     }
  169.  
  170.     override fun setImageDrawable(drawable: Drawable?) {
  171.         super.setImageDrawable(drawable)
  172.         if (isAvatarMode) {
  173.             prepareShader(width, height)
  174.         }
  175.         Log.d("AvatarImageView", "setImageDrawable()")
  176.     }
  177.  
  178.     override fun setImageResource(resId: Int) {
  179.         super.setImageResource(resId)
  180.         if (isAvatarMode) {
  181.             prepareShader(width, height)
  182.         }
  183.         Log.d("AvatarImageView", "setImageResource()")
  184.     }
  185.  
  186.     private fun resolveDefultSize(spec: Int): Int {
  187.         return when (MeasureSpec.getMode(spec)) {
  188.             MeasureSpec.UNSPECIFIED -> {
  189.                 context.dpToPx(DEFAULT_SIZE).toInt()
  190.             }
  191.             else -> MeasureSpec.getSize(spec)
  192.         }
  193.     }
  194.  
  195.     private fun drawAvatar(canvas: Canvas) {
  196.         canvas.drawOval(viewRect.toRectF(), avatarPaint)
  197.     }
  198.  
  199.     private fun initialsToColor(letters: String) : Int{
  200.         val b = letters[0].toByte()
  201.         val len = bgColor.size
  202.         val d = b / len.toDouble()
  203.         val index = ((d - truncate(d)) * len).toInt()
  204.         return bgColor[index]
  205.     }
  206.  
  207.     private fun drawInitials(canvas: Canvas) {
  208.         initialsPaint.color = initialsToColor(initials)
  209.         canvas.drawOval(viewRect.toRectF(), initialsPaint)
  210.         with(initialsPaint) {
  211.             color = Color.WHITE
  212.             textAlign = Paint.Align.CENTER
  213.             textSize = height * 0.33f
  214.         }
  215.         val offsetY = (initialsPaint.descent() + initialsPaint.ascent()) / 2
  216.         // Сейчас отрисовка инициалов начинается с верхней части центра. Надо исправить.
  217.         // Исправим путём опускания на половину текущего размера шрифта
  218.         canvas.drawText(initials, viewRect.exactCenterX(), viewRect.exactCenterY() - offsetY, initialsPaint)
  219.     }
  220.  
  221.     // При долгом клике на аватарку она должна превратиться в инициалы
  222.     private fun handleLongClick() : Boolean {
  223.         isAvatarMode = !isAvatarMode
  224.         invalidate()
  225.         return true
  226.     }
  227. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement