天天看點

Android 自定義View練手Demo(一)實作圓角遮罩效果Android自定義View實作圓角遮罩效果推薦一下我開源的項目 WanAndroid 用戶端

Android 自定義View系列文章

Android 自定義View練手Demo(一)實作圓角遮罩效果

Android 自定義View練手Demo(二)實作圓形頭像效果

Android 自定義View練手Demo(三)實作微信拍一拍的動畫效果

Android自定義View實作圓角遮罩效果

一圖勝千言,有一個遮罩就會凸顯出重點區域

Android 自定義View練手Demo(一)實作圓角遮罩效果Android自定義View實作圓角遮罩效果推薦一下我開源的項目 WanAndroid 用戶端

本文通過兩種方式來實作這種效果,來達到自定義View練手的效果

此效果的用途

  • 在裁剪圖檔,确定裁剪範圍
  • 在APP中引導使用者,突顯某個區域

這是一個麻雀雖小五髒俱全的小Demo了,非常适合練手。

1.引言

通過本文可以學習到

  • Canvas和Paint 的常用且實用的 API
  • Xfermode的使用
  • View級别的離屏緩沖的開啟方式
  • Canvas的離屏緩沖和View的離屏緩沖的差別
  • 如何給自定義View設定自定義屬性的使用

2.第一種實作方式

class RoundRectCoverView(context: Context, attrs: AttributeSet) : View(context, attrs) {
  
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
  	private var mPadding = 40.dp //間距
    private var mRoundCorner = 10.dp //圓角矩形的角度
    private var mCoverColor = "#99000000".toColorInt()//遮罩的顔色
    private val porterDuffXfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)
  
   	init {
        //開啟View級别的離屏緩沖,并關閉硬體加速,使用軟體繪制
        setLayerType(LAYER_TYPE_SOFTWARE, null)
    }
    override fun onDraw(canvas: Canvas) {
        //先畫一個圓角矩形,也就是透明區域(Destination image)
        canvas.drawRoundRect(mPadding, mPadding, width - mPadding, height - mPadding, mRoundCorner, mRoundCorner, paint)
        //設定遮罩的顔色
        paint.color = mCoverColor
        //設定paint的 xfermode 為PorterDuff.Mode.SRC_OUT
        paint.xfermode = porterDuffXfermode
        //畫遮罩的矩形(Source image)
        canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
        //清空paint 的 xfermode
        paint.xfermode = null
    }
           

上面代碼中的注釋已經寫的很清楚了,這裡說一下

setLayerType(LAYER_TYPE_SOFTWARE, null)

是開啟View級别的離屏緩沖,就是拿出整個View大小的一塊區域,這塊區域是透明的。那麼你就可能會好奇,為啥要拿出這麼一塊透明的區域呢?因為使用PorterDuff.Mode時候需要用到。

什麼是PorterDuff.Mode

這裡面我們用到的是PorterDuff.Mode.SRC_OUT,正好對應的是Source Out這種模式是我們想要的

  • 注意Destination image是第一次畫的圓角矩形,需要畫在一個透明的View中,這就需要離屏緩沖
  • Source image是畫的帶顔色的遮罩也就是整個View
  • PorterDuff.Mode.SRC_OUT,類似于把後畫的和先前畫的重疊的部分,摳出去扔掉,結果就是我們要的效果

3.第二種實作方式

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

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)

    private var mPadding = 40.dp //間距
    private var mRoundCorner = 10.dp //圓角矩形的角度
    private var mCoverColor = "#99000000".toColorInt()//遮罩的顔色

    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) {
        //設定離屏緩沖的範圍
        bounds.set(0f, 0f, width.toFloat(), height.toFloat())
        //設定Clip Path的矩形區域
        clipPath.addRoundRect(mPadding, mPadding, width - mPadding, height - mPadding, mRoundCorner, mRoundCorner, Path.Direction.CW)
    }

    override fun onDraw(canvas: Canvas) {
         //Canvas的離屏緩沖
          val count = canvas.saveLayer(bounds, paint)
          //KTX的擴充函數相當于對Canvas的 save 和 restore 操作
          canvas.withSave {
              //畫遮罩的顔色
              canvas.drawColor(mCoverColor)
              //按Path來裁切
              canvas.clipPath(clipPath)
              //畫镂空的範圍
              canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.SRC)
          }
          //把離屏緩沖的内容,繪制到View上去
          canvas.restoreToCount(count)
    }
  
}
           

解釋一下,在onSizeChanged方法中,我們設定了離屏緩沖的範圍,注意這個範圍夠用就行,太大了耗費性能。同時設定了Clip Path裁剪的矩形區域。

onDraw方法中

  • 首先開啟離屏緩沖**(注意這裡開啟的是Canvas的離屏緩沖)**這範圍就是bounds的範圍
  • 畫出完整的遮罩的顔色
  • 然後把Canvas裁切出一個圓角矩形
  • 然後在話一個透明的顔色,模式指定為PorterDuff.Mode.SRC
  • 把離屏緩沖的内容,繪制到View上去

注意:記得開離屏緩沖,否則結果可能不是你想要的

4.設定自定義屬性

4.1首先在values檔案夾下,建立一個attrs.xml的檔案(當然你也可以叫别的名字,但這個是規範)
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="RoundRectCoverView">
        <attr name="roundCorner" format="dimension" />
        <attr name="roundPadding" format="dimension" />
        <attr name="roundCoverColor" format="color" />
    </declare-styleable>
</resources>
           

聲明你想再xml中能設定的屬性,比如我這裡聲明了,圓角的大小,圓角矩形的padding值,以及遮罩的顔色

4.2在代碼中動态擷取xml中設定的值
init {

    //通過TypeArray 擷取 xml 配置的屬性
    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()

}
           
  • 通過TypeArray擷取,聲明的屬性值
  • 指派給成員變量,下面使用的時候就直接使用了
  • 回收TypeArray
4.3在xml代碼直接使用
<?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"
    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"//角度
        app:roundCoverColor="#aa000000"//遮罩顔色
        app:roundPadding="30dp" />

</androidx.constraintlayout.widget.ConstraintLayout>
           

5.總結

  • 離屏緩沖的兩種方式差別以及開啟方式
    • Canvas.saveLayer() 可以做短時的離屏緩沖,Google不建議使用,因為每次繪制都需要拿出一塊來,耗費性能
    • View.setLayerType()是直接把整個

      View

      都繪制在離屏緩沖中,Google建議這樣做。

      setLayerType(LAYER_TYPE_HARDWARE)

      是使用 GPU 來緩沖,

      setLayerType(LAYER_TYPE_SOFTWARE)

      是直接直接用一個

      Bitmap

      來緩沖。
  • PorterDuff.Mode的類型,已經使用,需要有透明的背景色
  • 自定義屬性的方式

這是一個簡單且使用的Demo,很适合練手!

6.源碼位址

RoundRectCoverView.kt

7.原文位址

Android自定義View實作圓角遮罩效果

8.參考文章

hencoder

PorterDuff.Mode

推薦一下我開源的項目 WanAndroid 用戶端

WanAndroidJetpack 架構圖

Android 自定義View練手Demo(一)實作圓角遮罩效果Android自定義View實作圓角遮罩效果推薦一下我開源的項目 WanAndroid 用戶端
  • 一個純 Android 學習項目,WanAndroid 用戶端。
  • 項目采用

    MVVM

    架構,用

    Kotlin

    語音編寫。
  • Android Jetpack 的大量使用包括但不限于

    Lifecycle

    LiveData

    ViewModel

    Databinding

    Room

    ConstraintLayout

    等,未來可能會更多。
  • 采用

    Retrofit

    Kotlin-Coroutine

    協程進行網絡互動。
  • 加載圖檔

    Glide

    主流加載圖檔架構。
  • 資料存儲主要用到了

    Room

    和騰訊的

    MMKV

Kotlin + MVVM + Jetpack + Retrofit + Glide 的綜合運用,是學習 MMVM 架構的不錯的項目。

此項目本身也是一個專門學習 Android 相關知識的 APP,歡迎下載下傳體驗!

源碼位址(附帶下載下傳連結)

WanAndroidJetpack

APP 整體概覽

Android 自定義View練手Demo(一)實作圓角遮罩效果Android自定義View實作圓角遮罩效果推薦一下我開源的項目 WanAndroid 用戶端

喜歡的點個 Stars,有問題的請提 Issues。