Android 自定義View系列文章
Android 自定義View練手Demo(一)實作圓角遮罩效果
Android 自定義View練手Demo(二)實作圓形頭像效果
Android 自定義View練手Demo(三)實作微信拍一拍的動畫效果
Android自定義View實作圓角遮罩效果
一圖勝千言,有一個遮罩就會凸顯出重點區域
本文通過兩種方式來實作這種效果,來達到自定義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()是直接把整個
都繪制在離屏緩沖中,Google建議這樣做。View
是使用 GPU 來緩沖,setLayerType(LAYER_TYPE_HARDWARE)
是直接直接用一個setLayerType(LAYER_TYPE_SOFTWARE)
來緩沖。Bitmap
- PorterDuff.Mode的類型,已經使用,需要有透明的背景色
- 自定義屬性的方式
這是一個簡單且使用的Demo,很适合練手!
6.源碼位址
RoundRectCoverView.kt
7.原文位址
Android自定義View實作圓角遮罩效果
8.參考文章
hencoder
PorterDuff.Mode
推薦一下我開源的項目 WanAndroid 用戶端
WanAndroidJetpack 架構圖
- 一個純 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 整體概覽
喜歡的點個 Stars,有問題的請提 Issues。