Android Custom View series

  • (1) To achieve the effect of rounded corner mask
  • Android custom View Demo (2) to achieve the circular avatar effect
  • Android custom View Demo (3) to achieve the micro channel shot one shot of the animation effect

The Android custom View implements the rounded corner mask effect

A picture is worth a thousand words, and a mask will highlight key areas

This article through two ways to achieve this effect, to achieve the effect of custom View practice

The use of this effect

  • In cropping the picture, determine the cropping range
  • Guide the user in the APP and highlight an area

This is a sparrow although small viscera complete small Demo, very suitable for practice.

1. The introduction

Learn from this article

  • Common and useful APIS for Canvas and Paint
  • The use of Xfermode
  • How to enable off-screen buffering at the View level
  • The difference between Canvas’s off-screen buffer and View’s off-screen buffer
  • How to set the use of custom properties for a custom View

2. The first implementation

class RoundRectCoverView(context: Context, attrs: AttributeSet) : View(context, attrs) {
  
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
  	private var mPadding = 40.dp / / spacing
    private var mRoundCorner = 10.dp // The Angle of the rounded rectangle
    private var mCoverColor = "# 99000000".toColorInt()// The color of the mask
    private val porterDuffXfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)
  
   	init {
        // Turn on off-screen buffering at the View level, turn off hardware acceleration, and use software to draw
        setLayerType(LAYER_TYPE_SOFTWARE, null)}override fun onDraw(canvas: Canvas) {
        // Draw a rounded rectangle, which is the transparent area (Destination image)
        canvas.drawRoundRect(mPadding, mPadding, width - mPadding, height - mPadding, mRoundCorner, mRoundCorner, paint)
        // Set the color of the mask
        paint.color = mCoverColor
        // Set paint's xferMode to porterduff.mode.src_out
        paint.xfermode = porterDuffXfermode
        // Draw the mask's rectangle (Source image)
        canvas.drawRect(0f.0f, width.toFloat(), height.toFloat(), paint)
        // Clear paint's xfermode
        paint.xfermode = null
    }
Copy the code

SetLayerType (LAYER_TYPE_SOFTWARE, null) is used to enable off-screen buffering at the View level. It is used to take out the entire area of the View, which is transparent. So you might be wondering, why take out such a transparent area? This is required when using porterduff.mode.

What is PorterDuff Mode

In this case, we use porterduff.mode.src_out, which corresponds exactly to Source Out, which is the Mode we want

  • Note that the Destination image is the first rounded rectangle to be drawn ina transparent View, which requires off-screen buffering
  • The Source image is the painted mask which is the entire View
  • Porterduff.mode.src_out, similar to the overlap between the last and the previous drawing, cut out and throw away, the result is what we want

3. The second way of implementation

class RoundRectCoverView(context: Context, attrs: AttributeSet) : View(context, attrs) {

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)

    private var mPadding = 40.dp / / spacing
    private var mRoundCorner = 10.dp // The Angle of the rounded rectangle
    private var mCoverColor = "# 99000000".toColorInt()// The color of the mask

    private val bounds = RectF()
    private val porterDuffXfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)
    private val clipPath = Path()

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        // Set the range of off-screen buffering
        bounds.set(0f.0f, width.toFloat(), height.toFloat())
        // Set the rectangle of the Clip Path
        clipPath.addRoundRect(mPadding, mPadding, width - mPadding, height - mPadding, mRoundCorner, mRoundCorner, Path.Direction.CW)
    }

    override fun onDraw(canvas: Canvas) {
         //Canvas off-screen buffering
          val count = canvas.saveLayer(bounds, paint)
          // The KTX extension functions are equivalent to the save and restore operations on Canvas
          canvas.withSave {
              // Paint the color of the mask
              canvas.drawColor(mCoverColor)
              // Cut by Path
              canvas.clipPath(clipPath)
              // Draw the scope of the hollow out
              canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.SRC)
          }
          // Draw the off-screen cached content to the View
          canvas.restoreToCount(count)
    }
  
}
Copy the code

To explain, in the onSizeChanged method, we set the range of the off-screen buffer, note that this range is sufficient, too much performance. At the same time set the Clip Path clipping rectangle area.

In the ontouch method

  • First turn on the off-screen buffering **(notice that this is the Canvas off-screen buffering)** This is the bounds of the bounds

  • Paint the full mask color

  • Then cut the Canvas into a rounded rectangle

  • Then in the words of a transparent color, the Mode is specified as porterduff.mode.src

  • I’m going to draw the off-screen buffer to the View

Note: Remember to turn off the screen buffer, otherwise the results may not be what you want

4. Set custom attributes

4.1 In the values folder, create a file attrs.xml (you can call it anything, but this is the specification).

      
<resources>
    <declare-styleable name="RoundRectCoverView">
        <attr name="roundCorner" format="dimension" />
        <attr name="roundPadding" format="dimension" />
        <attr name="roundCoverColor" format="color" />
    </declare-styleable>
</resources>
Copy the code

Declare the properties that you want to set in the XML, for example, I’ve declared the size of the rounded corners, the padding of the rounded rectangle, and the color of the mask

4.2 Dynamically obtaining values set in XML in code
init {

    // Get the attributes of the XML configuration through TypeArray
    val ta = context.obtainStyledAttributes(attrs, R.styleable.RoundRectCoverView)
    mPadding = ta.getDimension(R.styleable.RoundRectCoverView_roundPadding, 40.dp)
    mRoundCorner = ta.getDimension(R.styleable.RoundRectCoverView_roundCorner, 10.dp)
    mCoverColor = ta.getColor(R.styleable.RoundRectCoverView_roundCoverColor, "# 99000000".toColorInt())

    ta.recycle()

}
Copy the code
  • Obtained through TypeArray, the value of the declared property
  • Assign a value to a member variable, which is used directly in the following example
  • Recycling TypeArray
4.3 used directly in XML code

      
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:src="@drawable/captain_america"
        android:scaleType="centerCrop"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.jhb.customviewcollection.RoundRectCoverView.RoundRectCoverView
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:roundCorner="10dp"/ / Angleapp:roundCoverColor="#aa000000"// Mask colorapp:roundPadding="30dp" />

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

5. To summarize

  • The difference between the two ways of off-screen buffer and the way to open
    • Canvas.savelayer () can be used for short off-screen buffering. Google doesn’t recommend using it because it takes performance to draw a piece each time
    • View.setlayertype () is directly put the wholeViewAre drawn in an off-screen buffer, as Google recommends.setLayerType(LAYER_TYPE_HARDWARE)Is using the GPU to buffer,setLayerType(LAYER_TYPE_SOFTWARE)I’m just going to use oneBitmapTo buffer.
  • The type of PorterDuff.Mode that has been used requires a transparent background color
  • How to customize attributes

This is a simple and use of the Demo, very suitable for practice!

6. Source address

RoundRectCoverView.kt

7. The original address

The Android custom View implements the rounded corner mask effect

8. Refer to articles

hencoder

PorterDuff.Mode

I recommend my open source project WanAndroid client

WanAndroidJetpack architecture diagram

  • A pure Android learning project, WanAndroid client.
  • Project useMVVMArchitecture,KotlinVoice writing.
  • Extensive use of Android Jetpack includes, but is not limited toLifecycle,LiveData,ViewModel,Databinding,Room,ConstraintLayoutAnd more in the future.
  • usingRetrofitKotlin-Coroutine coroutinesNetwork interaction.
  • Loading picturesGlideMainstream load picture frame.
  • Data storage is mainly usedRoomAnd tencentMMKV.

Kotlin + MVVM + Jetpack + Retrofit + Glide is a good project to learn MMVM architecture.

This project itself is also a special learning Android related knowledge of the APP, welcome to download the experience!

Source code address (with download link)

WanAndroidJetpack

Overview of the APP

Order if you likeStarsIf you have questions, please refer to the following questions: