android 清空canvas部分内容_Android自定义View实现圆形头像效果
在我們的APP中通常會遇到,展示圓形頭像的需求,一般通過Glide就能實現,但是讓我們做一個圓形頭像,如果讓我們自定義實現這種效果,該怎樣做呢?
好,接下來本文通過三種方式來實現這種效果!
注意:這是一個練手的Demo
1.通過本文可以學到的知識點
- canvas.clipPath API的使用
- Xfermode的使用
- Paint的Xfermode和ShaderAPI
- Matrix的平移和Canvans的平移(源碼中,為了在一個View同時展示三種效果,所以對Canvas坐標進行了平移)
- 總結三種實現方式的優缺點
2.通過自定義View制作圓形頭像
通過三種方式來實現這種效果,是哪三種方式呢?
- 通過canvas.clipPath()
- 通過paint.xfermode = porterDuffXfermode
- 通過paint.shader = bitmapShader
3.第一種實現方式
利用 canvas的 clip方法
private val AVATAR_SIZE = 240.dp //圖片的大小 private val RADIUS = AVATAR_SIZE / 2 //裁剪圓形的半徑class CircleAvatarView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : View(context, attrs, defStyleAttr) {private val paint = Paint()private var avatar = getAvatar(R.drawable.my_avatar, AVATAR_SIZE.toInt())//圖片的bitmapprivate val circlePath = Path()//圓形的路徑override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {/*** 在圓心 x:View寬度的一半 y:View高度的一半 半徑為圖片尺寸的一半 的位置上畫圓*/circlePath.addCircle(width / 2f, height / 2f, RADIUS, Path.Direction.CW)}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)//ktx的擴展方法,會自動保存恢復Canvascanvas.withSave {//重置畫筆paint.reset()//利用Canvas來裁切去要畫的范圍,也就是那個圓形canvas.clipPath(circlePath)//在裁切后畫上聲明的Bitmapcanvas.drawBitmap(avatar, width / 2 - RADIUS, height / 2 - RADIUS, paint)}}}解釋一下代碼,當然看代碼的注釋也是一樣的
- 定義包級別的圖片的寬度為240dp和圓形的半徑
- 獲取一個要展示的圖片的Bitmap
- 聲明一個要裁剪的圓形的Path
- onSizeChanged方法中添加一個圓形
- 利用Canvas來裁切去要畫的范圍,也就是那個圓形
- 在裁切后畫上聲明的Bitmap
以上就是繪制圓形頭像的第一種方法,主要使用的是canvas.clipPath(circlePath)方法,注意Canvas的保存和恢復
4.第二種實現方式
通過paint.xfermode = porterDuffXfermode
PorterDuff.Mode
private val AVATAR_SIZE = 240.dp //圖片的大小 private val RADIUS = AVATAR_SIZE / 2 //裁剪圓形的半徑class CircleAvatarView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : View(context, attrs, defStyleAttr) {private val paint = Paint()private var avatar = getAvatar(R.drawable.my_avatar, AVATAR_SIZE.toInt())//圖片的bitmapprivate val circlePath = Path()//圓形的路徑private val bounds = RectF()//離屏緩沖的 boundsprivate val porterDuffXfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {/*** 在圓心 x:View寬度的一半 y:View高度的一半 半徑為圖片尺寸的一半 的位置上畫圓*/circlePath.addCircle(width / 2f, height / 2f, RADIUS, Path.Direction.CW)/*** 設置離屏緩沖的 bounds最好不要太大,影響性能*/bounds.set(width / 2f - RADIUS, height / 2f - RADIUS,width / 2f + RADIUS, height / 2f + RADIUS)}override fun onDraw(canvas: Canvas) {super.onDraw(canvas) //開啟離屏緩沖val count = canvas.saveLayer(bounds, null)paint.reset()canvas.drawPath(circlePath, paint)//設置paint的 xfermode 為PorterDuff.Mode.SRC_INpaint.xfermode = porterDuffXfermode//以當前的Paint來畫Bitmapcanvas.drawBitmap(avatar, width / 2 - RADIUS, height / 2 - RADIUS, paint)//清空paint 的 xfermodepaint.xfermode = null//把離屏緩沖的內容,繪制到View上去canvas.restoreToCount(count)} }解釋一下代碼,當然看代碼的注釋也是一樣的
- 在onSizeChanged方法中設置離屏緩沖的范圍,是圓形頭像的外切矩形的范圍
- onDraw方法中開啟離屏緩沖
- Canvas先畫了一個圓形(相當于PorterDuff.Mode中的Destination image)
- 設置paint的 xfermode 為PorterDuff.Mode.SRC_IN
- 以當前的Paint來畫Bitmap(注意此時相當于PorterDuff.Mode中的Source image),因為我們選擇的模式為PorterDuff.Mode.SRC_IN所以就畫出我們想要的效果
- 把離屏緩沖的內容,繪制到View上去
注意:一定要開啟離屏緩沖,不然結果可能不是你所預期的,離屏緩沖相當于拿出一塊透明的View來繪制,Canvas要繪制的圖形
5.第三種實現方式
通過paint.shader = bitmapShader
private val AVATAR_SIZE = 240.dp //圖片的大小 private val RADIUS = AVATAR_SIZE / 2 //裁剪圓形的半徑class CircleAvatarView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : View(context, attrs, defStyleAttr) {private val paint = Paint()private var avatar = getAvatar(R.drawable.my_avatar, AVATAR_SIZE.toInt())//圖片的bitmapprivate val circlePath = Path()//圓形的路徑private val bitmapShader = BitmapShader(avatar, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {/*** 在圓心 x:View寬度的一半 y:View高度的一半 半徑為圖片尺寸的一半 的位置上畫圓*/circlePath.addCircle(width / 2f, height / 2f, RADIUS, Path.Direction.CW)}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)paint.reset()//給paint設置Shaderpaint.shader = bitmapShader//以Shader的形式來畫圓形canvas.drawPath(circlePath, paint)} }解釋一下代碼,當然看代碼的注釋也是一樣的
這種方式就比較簡單了
- 聲明一個BitmapShader,把頭像的bitmap填入BitmapShader的構造中,并填入參數Shader.TileMode的值,這個具體看這篇文章對Paint做了詳細解釋
- 在onDraw方法中給paint設置Shader
- 以Shader的形式來畫圓形,結果就是一個圓角頭像了
6.總結
總結一下三種方式的優缺點
1.canvas.clipPath():
利用 canvas的 clip方法 沒有抗鋸齒效果,會有毛邊,因為是精確的切像素點2.paint.xfermode = porterDuffXfermode
利用 paint的 xfermode 方法 有抗鋸齒效果,沒有毛邊,效果好,做了抗鋸齒的處理,填充和周邊類似的半透明色等3.paint.shader = bitmapShader
利用 設置 paint 的 shader 方法 有抗鋸齒效果,沒有毛邊,效果好 但是圖片結果會受到Shader.TileMode影響,可能結果不是你所預期的綜上總結:最好使用paint.xfermode = porterDuffXfermode這種方式,因為我們需要的是一個顯示完美的頭像
此外還要注意使用離屏緩沖
7.源碼地址
CircleAvatarView.kt
8.原文地址
注意:我的源碼是把三種方式統一畫在了一個View中,并通過Matrix的平移或者Canvans的平移來實現向下排列的效果,順便先練了Matrix的平移和Canvans的平移
Android自定義View實現圓形頭像效果
9.參考文章
hencoder
PorterDuff.Mode
推薦一下我開源的項目 WanAndroid 客戶端
WanAndroidJetpack 架構圖
- 一個純 Android 學習項目,WanAndroid 客戶端。
- 項目采用 MVVM 架構,用 Kotlin 語音編寫。
- Android Jetpack 的大量使用包括但不限于Lifecycle、LiveData、ViewModel、Databinding、Room、ConstraintLayout等,未來可能會更多。
- 采用 Retrofit 和 Kotlin-Coroutine 協程進行網絡交互。
- 加載圖片 Glide 主流加載圖片框架。
- 數據存儲主要用到了 Room 和騰訊的 MMKV。
此項目本身也是一個專門學習 Android 相關知識的 APP,歡迎下載體驗!
源碼地址(附帶下載鏈接)
WanAndroidJetpack
APP 整體概覽
喜歡的點個 Stars,有問題的請提 Issues。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的android 清空canvas部分内容_Android自定义View实现圆形头像效果的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 阴阳师管狐哪里打 悬赏封印管狐哪里刷的多
- 下一篇: mysql delete 表关联删除数据