Android from scratch with a label written TagTextView

Do not forget to always come to my GitHub see what fun ~

Recently upgraded the company's project to 9.x, and was followed by a big wave of updates, which have a more obvious change is that many sectors have a design with a label, as follows:

How to achieve

See this, most small partners can think this is a simple photo-text, graphics could not help but think of Yang Che-hung side by side controls Gangster MixtureTextView , or write a no trouble, only you need to use shape background documents binding SpannableStringIt can be.

Indeed, use SpannableStringreally is the most convenient way, but little attention here might step on pit.

private fun convertViewToBitmap(view: View): Bitmap {
    view.isDrawingCacheEnabled = true
    view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
    view.layout(0, 0, view.measuredWidth, view.measuredHeight)
    view.buildDrawingCache()
    val bitmap = view.drawingCache
    view.isDrawingCacheEnabled = false
    view.destroyDrawingCache()
    return bitmap
}

fun setTagText(style: Int, content: String) {
    val view = LayoutInflater.from(context).inflate(R.layout.layout_codoon_tag_textview, null)
    val tagView = view.findViewById<CommonShapeButton>(R.id.tvName)
    val tag = when (style) {
        STYLE_NONE -> {
            ""
        }
        STYLE_CODOON -> {
            tagView.setStrokeColor(R.color.tag_color_codoon.toColorRes())
            tagView.setTextColor(R.color.tag_color_codoon.toColorRes())
            "自营"
        }
        STYLE_JD -> {
            tagView.setStrokeColor(R.color.tag_color_jd.toColorRes())
            tagView.setTextColor(R.color.tag_color_jd.toColorRes())
            "京东"
        }
        STYLE_TM -> {
            tagView.setStrokeColor(R.color.tag_color_tm.toColorRes())
            tagView.setTextColor(R.color.tag_color_tm.toColorRes())
            "天猫"
        }
        STYLE_PDD -> {
            tagView.setStrokeColor(R.color.tag_color_pdd.toColorRes())
            tagView.setTextColor(R.color.tag_color_pdd.toColorRes())
            "拼多多"
        }
        STYLE_TB -> {
            tagView.setStrokeColor(R.color.tag_color_tb.toColorRes())
            tagView.setTextColor(R.color.tag_color_tb.toColorRes())
            "淘宝"
        }
        else -> {
            ""
        }
    }
    val spannableString = SpannableString("$tag$content")
    val bitmap = convertViewToBitmap(view)
    val drawable = BitmapDrawable(resources, bitmap)
    drawable.setBounds(0, 0, tagView.width, tagView.height)
    spannableString.setSpan(CenterImageSpan(drawable), 0, tag.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
    text = spannableString
    gravity = Gravity.CENTER_VERTICAL
}

companion object {
    const val STYLE_NONE = 0
    const val STYLE_JD = 1
    const val STYLE_TB = 2
    const val STYLE_CODOON = 3
    const val STYLE_PDD = 4
    const val STYLE_TM = 5
}
复制代码

Style xml file does not have to put up here, very simple, with a shape background TextView, but because of the shape files is extremely difficult to maintain, unified in our project uses a custom View to achieve these fillets effect.

Detailed reference of the blog: Finishing Android project shape labels and reflection

What effects such as rounded shape is not our main discussion here, we look at this code, the idea is very clear and simple: Firstly LayoutInflaterreturn a View, then this Viewthrough a series of logical judgment confirming the inside of the display copy and stroke color processing . Then Viewthe buildDrawingCache()method for generating a Bitmap for SpannableStringuse, and then spannableStringset to the textViewcan.

Few notes

There should be noted that the details, using LayoutInflaterthe generated Viewnot through measure()and layout()baptism methods, it must not have its widthand heightother property assignment.

So we buildDrawingCache()do before a crucial two-step process:

view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
view.layout(0, 0, view.measuredWidth, view.measuredHeight)
复制代码

From buildDrawingCache()that we can see the source code, this method will not return to the right Bitmap, in our View's CacheSizesize exceeds a certain defaults write device, it may return null.

Our system gives me the default maximum of DrawingCacheSize4 times the screen width and height of the product.

Because View here is minimal, it does not return null case appears.

Although the above code has been tested, you can basically meet the demand on most models. But the spirit is marked @Deprecatedobsolete methods, we resolutely do not mind, we need to generate Bitmapthe method of small-scale transformation.

In the latest SDK, we found Viewa buildDrawingCache()series of methods have been marked @Deprecated.

/**
 * <p>Calling this method is equivalent to calling <code>buildDrawingCache(false)</code>.</p>
 *
 * @see #buildDrawingCache(boolean)
 *
 * @deprecated The view drawing cache was largely made obsolete with the introduction of
 * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
 * layers are largely unnecessary and can easily result in a net loss in performance due to the
 * cost of creating and updating the layer. In the rare cases where caching layers are useful,
 * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
 * rendering. For software-rendered snapshots of a small part of the View hierarchy or
 * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
 * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
 * software-rendered usages are discouraged and have compatibility issues with hardware-only
 * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
 * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
 * reports or unit testing the {@link PixelCopy} API is recommended.
 */
@Deprecated	
public void buildDrawingCache() {
    buildDrawingCache(false);
}		
复制代码

From the official comments, we found that the use of outdated view rendering, hardware acceleration intermediate buffer after many degrees are unnecessary and can easily lead to a net loss of performance.

So we use Canvasa simple modified look:

private fun convertViewToBitmap(view: View): Bitmap? {
    view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
    view.layout(0, 0, view.measuredWidth, view.measuredHeight)
    val bitmap = Bitmap.createBitmap(view.measuredWidth, view.measuredHeight, Bitmap.Config.ARGB_4444)
    val canvas = Canvas(bitmap)
    canvas.drawColor(Color.WHITE)
    view.draw(canvas)
    return bitmap
}
复制代码

The sudden collapse

perfect, but unfortunately, when the 4.x on a mobile phone testing, the occurrence of a null pointer crash.

Saw logs, we found that performing view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)) thrown layer source system when the bug of this code.

Into the source code found in RelativeLayoutthe onMeasure()in such a code.

if (isWrapContentWidth) {
    // Width already has left padding in it since it was calculated by looking at
    // the right of each child view
    width += mPaddingRight;

    if (mLayoutParams != null && mLayoutParams.width >= 0) {
        width = Math.max(width, mLayoutParams.width);
    }

    width = Math.max(width, getSuggestedMinimumWidth());
    width = resolveSize(width, widthMeasureSpec);
    // ...
    }
}
复制代码

Look no problems, but the contrast 4.3 source code, and found a little clue.

if (mLayoutParams.width >= 0) {
      width = Math.max(width, mLayoutParams.width);
}
复制代码

The original report is the null pointer layoutParams. Look at our inflate()code.

val view = LayoutInflater.from(context).inflate(R.layout.layout_codoon_tag_textview, null)
复制代码

Any one of Android development is concerned, are the most familiar with the code, the meaning is very simple, xml instantiated from Viewview, but the parent view is null, so from xml file instantiated Viewno way to view attachthe Viewhierarchy tree, so led to layoutParamsthis parameter is null. Now we find the cause, then the solution will very simple. Only you need to inflate()post, and then set about paramsit.

view.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
复制代码

Thus, the basic has been achieved, the main logic code:

/**
 * 电商专用的 TagTextView
 * 后面可以拓展直接设置颜色和样式的其他风格
 *
 * Author: nanchen
 * Email: liusl@codoon.com
 * Date: 2019/5/7 10:43
 */
class CodoonTagTextView @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr) {

    private var tagTvSize: Float = 0f

    init {
        val array = context.obtainStyledAttributes(attrs, R.styleable.CodoonTagTextView)
        val style = array.getInt(R.styleable.CodoonTagTextView_codoon_tag_style, 0)
        val content = array.getString(R.styleable.CodoonTagTextView_codoon_tag_content)
        tagTvSize = array.getDimension(R.styleable.CodoonTagTextView_codoon_tag_tv_size, 0f)
        content?.apply {
            setTagText(style, this)
        }
        array.recycle()
    }

    private fun convertViewToBitmap(view: View): Bitmap? {
//        view.isDrawingCacheEnabled = true
        view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
        view.layout(0, 0, view.measuredWidth, view.measuredHeight)
//        view.buildDrawingCache()
//        val bitmap = view.drawingCache
//        view.isDrawingCacheEnabled = false
//        view.destroyDrawingCache()
        val bitmap = Bitmap.createBitmap(view.measuredWidth, view.measuredHeight, Bitmap.Config.ARGB_4444)
        val canvas = Canvas(bitmap)
        canvas.drawColor(Color.WHITE)
        view.draw(canvas)
        return bitmap
    }

    fun setTagText(style: Int, content: String) {
        val view = LayoutInflater.from(context).inflate(R.layout.layout_codoon_tag_textview, null)
        view.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
        val tagView = view.findViewById<CommonShapeButton>(R.id.tvName)
        val tag = when (style) {
            STYLE_NONE -> {
                ""
            }
            STYLE_CODOON -> {
                tagView.setStrokeColor(R.color.tag_color_codoon.toColorRes())
                tagView.setTextColor(R.color.tag_color_codoon.toColorRes())
                "自营"
            }
            STYLE_JD -> {
                tagView.setStrokeColor(R.color.tag_color_jd.toColorRes())
                tagView.setTextColor(R.color.tag_color_jd.toColorRes())
                "京东"
            }
            STYLE_TM -> {
                tagView.setStrokeColor(R.color.tag_color_tm.toColorRes())
                tagView.setTextColor(R.color.tag_color_tm.toColorRes())
                "天猫"
            }
            STYLE_PDD -> {
                tagView.setStrokeColor(R.color.tag_color_pdd.toColorRes())
                tagView.setTextColor(R.color.tag_color_pdd.toColorRes())
                "拼多多"
            }
            STYLE_TB -> {
                tagView.setStrokeColor(R.color.tag_color_tb.toColorRes())
                tagView.setTextColor(R.color.tag_color_tb.toColorRes())
                "淘宝"
            }
            else -> {
                ""
            }
        }
        if (tag.isNotEmpty()) {
            tagView.text = tag
            if (tagTvSize != 0f) {
                tagView.textSize = tagTvSize.toDpF()
            }
//            if (tagHeight != 0f) {
//                val params = tagView.layoutParams
//                params.height = tagHeight.toInt()
//                tagView.layoutParams = params
//            }
        }
        val spannableString = SpannableString("$tag$content")
        val bitmap = convertViewToBitmap(view)
        bitmap?.apply {
            val drawable = BitmapDrawable(resources, bitmap)
            drawable.setBounds(0, 0, tagView.width, tagView.height)
            spannableString.setSpan(CenterImageSpan(drawable), 0, tag.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
        }
        text = spannableString
        gravity = Gravity.CENTER_VERTICAL
    }

    companion object {
        const val STYLE_NONE = 0    // 不加
        const val STYLE_JD = 1      // 京东
        const val STYLE_TB = 2      // 淘宝
        const val STYLE_CODOON = 3  // 自营
        const val STYLE_PDD = 4     // 拼多多
        const val STYLE_TM = 5      // 天猫
    }
}

复制代码

I am a south dust, only numbers than the heart of the public are welcome to follow me.

South dust, GitHub 7k Star, the major technical Blog Forum regulars came Android, but not just Android. Write about technology, but also spit some emotional. Endless open source, could not finish the hypocritical, you hear me blowing force can not be wrong ~

Guess you like

Origin juejin.im/post/5ceb5928e51d45778f076ca3