preface

Those of you who write custom views often know that when a View changes, there are two methods to refresh it: the main thread calls invalidate() and the child thread calls postInvalidate(). After calling the above two methods, the system will refresh our View at an appropriate time, calling back the onDraw method. With this knowledge in mind, the default is to assume that our custom View will remain as it was when it was last refreshed, barring other distractions, as long as we don’t call the Invaidate () series of methods. But Android always surprises us, and the next quiz will refresh your knowledge.

Show begins

Let’s start by creating two custom views:

/** * View */
class MyView : View {

    constructor(context: Context?) : super(context)
    constructor(context: Context? , attrs: AttributeSet?) :super(context, attrs)
    constructor(context: Context? , attrs: AttributeSet? , defStyleAttr:Int) : super(
        context,
        attrs,
        defStyleAttr
    )

    private var mColor: Int = Color.BLUE
    private var mPaint: Paint = Paint()
    private var mTextPaint: Paint = Paint()

    init {
        mPaint.color = mColor
        mPaint.strokeWidth = 10f
        mPaint.style = Paint.Style.STROKE

        mTextPaint.color = Color.BLACK
        mTextPaint.textSize = 50f
    }

    override fun onDraw(canvas: Canvas?). {
        super.onDraw(canvas)
        Log.e("MyView"."onDraw") canvas? .drawRect(100f.100f.300f.300f, mPaint) canvas? .drawText("MyView".50f.50f, mTextPaint)
    }

    open fun setColor(@ColorInt color: Int) {
        this.mColor = color
        mPaint.color = mColor
    }
}
Copy the code
/** * Custom View uses double buffering refresh */
class MyViewUseBitmap : View {

    constructor(context: Context?) : super(context)
    constructor(context: Context? , attrs: AttributeSet?) :super(context, attrs)
    constructor(context: Context? , attrs: AttributeSet? , defStyleAttr:Int) : super(
        context,
        attrs,
        defStyleAttr
    )

    private var mCanvas: Canvas? = null
    private var mBitmap: Bitmap? = null
    private var mColor: Int = Color.BLUE
    private var mPaint: Paint = Paint()
    private var mTextPaint: Paint = Paint()

    init {
        mPaint.color = mColor
        mPaint.strokeWidth = 10f
        mPaint.style = Paint.Style.STROKE

        mTextPaint.color = Color.BLACK
        mTextPaint.textSize = 50f
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        if (mBitmap == null) { mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) mCanvas = Canvas(mBitmap!!) mCanvas? .drawRect(100f.100f.300f.300f, mPaint)
        }
    }

    override fun onDraw(canvas: Canvas?). {
        super.onDraw(canvas)
        Log.e("MyViewUseBitmap"."onDraw") mBitmap? .apply { canvas? .drawBitmap(this.0f.0f.null) } canvas? .drawText("MyViewUseBitmap".50f.50f, mTextPaint)
    }

    open fun setColor(@ColorInt color: Int) {
        this.mColor = color mPaint.color = mColor mBitmap? .eraseColor(Color.TRANSPARENT) mCanvas? .drawRect(100f.100f.300f.300f, mPaint)
    }
}
Copy the code

The biggest difference between the above two custom views is that one uses double-buffered refresh and the other does not.

Next create our Activity:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val myView = findViewById<MyView>(R.id.myView)
        valmyViewUseBitmap = findViewById<MyViewUseBitmap>(R.id.myViewUseBitmap) findViewById<View>(R.id.changeColor).setOnClickListener { myView.setColor(Color.RED) myViewUseBitmap.setColor(Color.RED)  } findViewById<View>(R.id.invalidate).setOnClickListener { myView.invalidate() myViewUseBitmap.invalidate() } } }Copy the code

As well as the layout

<? xml version="1.0" encoding="utf-8"? > <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.hardwareandinvalidate.MyView
        android:id="@+id/myView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@id/myViewUseBitmap"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.example.hardwareandinvalidate.MyViewUseBitmap
        android:id="@+id/myViewUseBitmap"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@id/changeColor"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/myView" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/changeColor"
        android:layout_width="150dp"
        android:layout_height="60dp"
        android:layout_margin="20dp"
        android:background="@color/black"
        android:text="Change Color"
        android:gravity="center"
        android:textColor="@color/white"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/invalidate"
        android:layout_width="150dp"
        android:layout_height="60dp"
        android:layout_margin="20dp"
        android:background="@color/black"
        android:text="Invalidate"
        android:gravity="center"
        android:textColor="@color/white"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
Copy the code

As you can see, the current interface displays the above two custom views. The “Change Color” button changes the Color, and the “Invalidate” button calls the View’s Invalidate () method.

Let’s run and see what it looks like:

It can be seen that I have clicked the “Change Color” button for several times, but the two custom views did not Change with it. It was not until I clicked the “Invalidate” button that the two views changed, so the result is the same as our cognition.

Only Change the click style of the 【Change Color】 button:

. <androidx.appcompat.widget.AppCompatTextView android:id="@+id/changeColor"
    android:layout_width="150dp"
    android:layout_height="60dp"
    android:layout_margin="20dp"
    android:background="@drawable/selector_press"/ / modify points
    android:text="Change Color"
    android:gravity="center"
    android:textColor="@drawable/selector_text_color"/ / modify points
    app:layout_constraintBottom_toBottomOf="parent app:layout_constraintLeft_toLeftOf="parent"/ >... //selector_press.xml 
      1.0" encoding="utf-8"? > http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/shape_press" android:state_pressed="true"/>
    <item android:drawable="@drawable/shape_normal" android:state_pressed="false"/>
</selector>

//shape_press.xml<? xml version="1.0" encoding="utf-8"? > <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
    <solid android:color="# 000000" />
    <stroke android:width="1dip" android:color="#3F3F3F"/ > <! --> <corners android:radius="10dp"/ > <! <padding android:bottom="5dp"
        android:left="5dp"
        android:right="5dp"
        android:top="5dp" />
</shape>

//shape_normal.xml<? xml version="1.0" encoding="utf-8"? > <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
    <solid android:color="#FFFFFF" />
    <stroke android:width="2dp" android:color="# 000000"/ > <! --> <corners android:radius="10dp"/ > <! <padding android:bottom="5dp"
        android:left="5dp"
        android:right="5dp"
        android:top="5dp" />
</shape>

// selector_text_color.xml<? xml version="1.0" encoding="utf-8"? > <selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="@color/white" android:state_pressed="true"/>
    <item android:color="@color/black" android:state_pressed="false"/>
</selector>
Copy the code

Let’s take a look at this in action:

When I click the “Change Color” button, MyView does not Change, but MyViewUseBitmap automatically refreshes. When I click the “Invalidate” button, MyView refreshes automatically. MyView performed as expected. That is, MyViewUseBitmap automatically flushes itself when we don’t actively call invalidate(). For those of you who are as bold as I am, you might think that the click style refresh of the button causes the View Tree to refresh, which in turn causes the MyViewUseBitmap to refresh. However, I can reliably tell you that the onDraw method of MyViewUseBitmap is not called after clicking the “Change Color” button through debugging and logging. That is, without onDraw() being called back, our View is refreshed 😱.

In some cases, I need my View to refresh according to my refresh timing, so the above phenomenon becomes uncontrollable. “I, too, was stunned at first and for the NTH time thought I would not be” Android world “developers. Fortunately, I finally found a solution that put me off the idea of a career change. That is:

// Enable hardware acceleration for View
setLayerType(LAYER_TYPE_HARDWARE, null) or// Enable software acceleration for View
setLayerType(LAYER_TYPE_SOFTWARE, nullLAYER_TYPE_SOFTWARE can be used for compatibility and LAYER_TYPE_HARDWARE for performance.Copy the code

Now that the problem is perfectly solved, let’s run again:

😊 happy, and can be happy to write Android.

conclusion

In the case that the custom View does not use the double buffering mechanism, the View refresh is controllable; But once we use double buffering, changes to other parts of the interface may cause our View to refresh. The solution is to call view.setlayerType (), which can be LAYER_TYPE_HARDWARE or LAYER_TYPE_SOFTWARE, but not LAYER_TYPE_NONE.

LAYER_TYPE_NONE: this is the default behavior of a View, which renders normally and does not support off-screen buffers. LAYER_TYPE_HARDWARE: Hardware acceleration, which renders the View as a hardware texture. LAYER_TYPE_SOFTWARE: Software acceleration, the View is rendered as a bitmap by softwareCopy the code

Welcome to discuss what is wrong with the above.

Reprint please indicate the source https://juejin.cn/post/6951298126418444324