preface

With long commutes, there’s always time to brush up on articles on the way. Rare-earth Nuggets is one of the most commonly used apps (not ads, hahaha). In a recent post # CollapsingToolbarLayout personal center page is also related to CollapsingToolbarLayout. Today again, is not a big technology, but we commonly used custom view that set of things, just feel beautiful, want to achieve their own ~ first picture:

implementation

First analyze the effect:

  • Part of the text is highlighted
  • The highlighted parts are parallelograms, not rectangles

How to do it: First draw a light font, then draw a dark font, but the dark font only shows part of the parallelogram. Let’s go straight to the code:

Custom attributes

In the values directory, create the attrs.xml file to define the attributes

<? The XML version = "1.0" encoding = "utf-8"? > <resources> <declare-styleable name="FlickerText"> <attr name="text" format="string" /> <attr name="text_size" format="dimension" /> <attr name="flick_precent" format="float" /> <attr name="text_normal_color" format="color|reference" /> <attr name="text_flick_color" format="color|reference" /> </declare-styleable> </resources>Copy the code

There are several attributes:

  • The text content displayed
  • The font size of the displayed text
  • The proportion of the width of the highlighted quadrilateral
  • Default font color
  • Highlight font color

Custom FlickerView, inherits from View

class FlickerText : View {

    private var minWidth = 0
    private var minHeight = 0

    private lateinit var paint: Paint
    private var textSize = 120
    private var showText: String = ""
    private var normalColor = Color.parseColor("#F0F0F2")
    private var flickColor = Color.parseColor("#DCDCDC")
    private var flickPercent = 0.16f
    private var clipLeft = -VERTICALOFFSET
    private var path: Path = Path()

    constructor(context: Context) : super(context) {
        init(null, 0, 0)
    }

    constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
        init(attributeSet, 0, 0)
    }

    constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int) : super(
        context,
        attributeSet,
        defStyleAttr
    ) {
        init(attributeSet, defStyleAttr, 0)
    }
Copy the code

Default values for custom attributes are given here

Gets the configuration property value

private fun init(attrs: AttributeSet? , defStyleAttr: Int, defStyleRes: Int) {/ / access to configuration properties context. Theme. ObtainStyledAttributes (attrs, R.s tyleable. FlickerText, 0, 0 ).apply { try { showText = getString(R.styleable.FlickerText_text).toString() textSize = getDimensionPixelSize(R.styleable.FlickerText_text_size, textSize) normalColor = getColor(R.styleable.FlickerText_text_normal_color, normalColor) flickColor = getColor(R.styleable.FlickerText_text_flick_color, flickColor) flickPercent = getFloat(R.styleable.FlickerText_flick_precent, FlickPercent)} finally {recycle()}} // Initialize paintbrushes related paint = paint () paint.isantiAlias = true paint.textSize = textSize.toFloat() val textBound = Rect() paint.getTextBounds(showText, 0, showText.length, textBound) minWidth = textBound.width() minHeight = textBound.height() }Copy the code

The custom attribute values configured in XML are obtained via obtainStyledAttributes. It also calculates the width and height needed to display full text properly, based on setting the font size of the brush

Calculate View height

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    val widthMode = MeasureSpec.getMode(widthMeasureSpec)
    val widthSize = MeasureSpec.getSize(widthMeasureSpec)
    val heightMode = MeasureSpec.getMode(heightMeasureSpec)
    val heightSize = MeasureSpec.getSize(heightMeasureSpec)
    var width = 0
    var height = 0
    if (widthMode == MeasureSpec.EXACTLY) {
        width = widthSize
    } else {
        width = min(widthSize, minWidth)
    }
    if (heightMode == MeasureSpec.EXACTLY) {
        height = heightSize
    } else {
        height = min(heightSize, minHeight)
    }
    setMeasuredDimension(width, height)
}
Copy the code

There are two main judgments made here:

  • If mode is EXACTLY, the exact value is specified
  • If mode is AT_MOST or UNSPECIFIED, the size provided by the parent layout is determined as UNSPECIFIED and the size required to display the complete text is calculated above. The minimum value is selected to ensure that the size does not exceed the size provided by the parent layout

draw

This is the point of showing the effect

override fun onDraw(canvas: Canvas?) { super.onDraw(canvas) paint.color = normalColor canvas? . DrawText (showText, 0f, height * 0.5f, paint) path.reset() path.moveto (clipLeft, 0f) path.lineTo(clipLeft + width * flickPercent, 0f) path.lineTo(clipLeft + width * flickPercent + VERTICALOFFSET, height.toFloat()) path.lineTo(clipLeft + VERTICALOFFSET, height.toFloat()) paint.color = flickColor canvas? .clipPath(path) canvas? . DrawText (showText, 0f, height * 0.5f, paint) clipLeft += 5f if (clipLeft > width) { clipLeft = -VERTICALOFFSET } invalidate() }Copy the code
  1. The canvas clipPath function is mainly used here, which will clip the canvas and display specific effects according to the set mode (the first drawing is described as A, and the second drawing is described as B) :
  • DIFFERENCE: Parts of A that differ from B are displayed
  • REPLACE: Displays the part of B
  • REVERSE_DIFFERENCE: Parts of B that differ from A are displayed
  • INTERSECT: Intersection of A and B
  • UNION: the complete set of A and B
  • XOR: Part of the set shape minus the intersection shape
// Check the API, INTERSECT public Boolean clipPath(@nonnull Path Path) {return clipPath(Path, region.op. INTERSECT); }Copy the code
  1. The definition of Path is to assemble a parallelogram. After each redraw, you need to call path.reset() to clear the previous path.
  2. The clipLeft increment allows the highlights to gradually scroll to the right

Summary and Expansion

conclusion

In fact, the effect is very simple, mainly using canvas clipPath function. But probably because usually use less, so did not notice. So free or read the source code, you can find some interesting things.

expand

  1. Here mainly involves some knowledge of custom view, the official has some related introduction: official custom view tutorial
  2. Paint has a similar API: setXfermode
Set or clear the transfer mode object. A transfer mode defines how source pixels (generate by a drawing command) are composited with the destination pixels (content of the render target). Pass null to clear any previous transfer mode. As  a convenience, the parameter passed is also returned. public Xfermode setXfermode(Xfermode xfermode) { return installXfermode(xfermode); }Copy the code

Image blending mode can be realized through this API. PorterDuffXfermode mainly includes the following modes:

Some of the eraser features of the previous lottery can be implemented in this way. Specific will not say ~

Finally, as usual, attach the demo address: gitee-demo