Android:手把手教你自定义头像View,可根据名字自动生成背景色+文字的显示效果,含动画效果。
首先看需要做成的效果,如下所示😻:
如上所示:我們需要做到如下效果,在圖片加載出來之前或者加載失敗需要展示一個自定義的背景色+文字的樣式,加載完成之后顯示對應的圖片,且包含動畫效果。
由于:需要項目本身包含有:com.github.open-android:RoundedImageView:v1.0.0 庫。 所以直接基于RoundedImageView來自定義HeadImageView,這樣就不需要處理圓形了,只需要關注背景色、文字、動畫以及可配置性就可以了。
現在就可以簡單的,我們可以暫時寫這么多:
分析:應該怎么做🧐?
下面,就按照以上6步,簡簡單單的擼起來吧 😁
開動(為了看起來舒服一點,這里就不放注釋了。最后會提供git源碼demo)😉
1.定義可配置的屬性👀
新建attrs.xml資源文件,增加對應的屬性
<resources><declare-styleable name="HeadImageView"><!--文字的顏色--><attr name="font_color" format="color" /><!--文字的大小--><attr name="font_size" format="dimension" /><!--默認文字對應的背景色--><attr name="background_font_color" format="color"/><!--是否使用默認的圖片 即背景色加上漢字--><attr name="is_default_pic" format="boolean"/></declare-styleable> </resources>在HeadImageView中,對屬性進行解析,核心代碼如下
@ColorIntvar fontColor: Int = -1set(value) {field = valueinvalidate()}@Pxvar fontSize: Float = 0.0Fset(value) {field = valueinvalidate()}var isDefaultPic: Boolean = falseset(value) {field = valueinvalidate()}@ColorIntprivate var backgroundFontColorInt: Int = -1init {val attributeSet = context.obtainStyledAttributes(attrs, R.styleable.HeadImageView)fontColor = attributeSet.getColor(R.styleable.HeadImageView_font_color,ContextCompat.getColor(context, R.color.white))fontSize =attributeSet.getDimension(R.styleable.HeadImageView_font_size, 16F.spToPx)backgroundFontColorInt = attributeSet.getColor(R.styleable.HeadImageView_background_font_color,Color.parseColor("#2E6BE5"))isDefaultPic = attributeSet.getBoolean(R.styleable.HeadImageView_is_default_pic, false)attributeSet.recycle()} 其他的初始化操作可自行查看源碼噢,比如paint、動畫控制相關等,這里就不一一列舉了🐷。2.繪制背景色👀
我們需要考慮圓形以及正方形不同的繪制
需要判斷當前是否是圓形,根據RoundedImageView已經提供的方法isOval()來進行判斷
3.繪制文字👀
這里我需要關注的是,怎么樣讓文字居中顯示繪制
先看一張圖🤩。
上面的參數很多是吧,是不是感覺有點懵😫,不慌,只需要關注這幾個參數:
base:字符基線,這個就比如我們規定,在一個100*100的空間里,在點(50,50)的位置繪制abcdefg,那么abcdef這幾個字母全在橫坐標50的上方,而g則上半部分在上方,下半部分在下方。因為繪制坐標是基于base來的。
descent:字符最低點到base的推薦距離
ascent:字符最高點到base的推薦距離
bottom:字符最低點到base的最大距離
以上便是需要知道的參數,所以怎么保證,字體繪制在最中間呢?
水平居中
定義一個矩形,設置文字對其方式設置為居中對齊,然后設置x軸坐標為矩形的中心,那水平方向就居中了。
垂直居中
垂直的需要計算出從base到垂直水平點的偏移量,那么偏移量怎么計算呢?
descent - ascent 就是字體整體推薦的高度,除以2就是推薦高度的一半。考慮還有字體最低點,可以減去bottom
核心實現代碼如下😋
4.增加動畫效果👀
這怎么做到呢?思考一下,到可以使用屬性動畫+離屏繪制+圖層混合完成。
屬性動畫:可以簡單的理解為,動畫間隔時間內不斷的更改對應屬性的值,重繪。實現動畫效果。
離屏繪制:簡單理解使用全新的canvas,繪制完成后,蓋在了原來的canvas上面。
圖層混合:將所繪制的像素與canvas中對應位置的像素按照一定規則進行混合,形成新的像素值,最終更新canvas中最終顯示的像素值。
這幾個概念呢?如果有不清楚的,可以簡單Google了解一下哈,這里不做引申了,直接看怎么使用吧😋。
大致的實現原理呢😃?
首先背景色加文字是通過canvas直接繪制出來,而圖層混合有一個模式可以讓繪制的交際變透明。就需要在繪制一塊,而這一塊和上面的繪制變成交際,就會透明了,實際的圖像就會顯示。自定義一個屬性通過屬性動畫不斷變化,然后達到控制第二塊繪制區域的效果,實現透明區域的縮放。以上操作均在離屏繪制中完成。考慮到圓形和正方形兩種情況,所以針對這兩種情況分別做了不同的繪制。
下面看看繪制的代碼核心實現吧😍。
下面在看看動畫的代碼核心實現吧😍。因為把動畫放到了,view里面所以使用軟引用持有(內存不夠,大不了就被回收了嘛,非必須)。
/*** 配合屬性動畫處理image默認的轉場動畫 值在 0-1 之間*/ var animationControl: Float = 1Fset(value) {field = if (animationControl < 0) 0F else if (animationControl > 1) 1F else valueinvalidate()} private var objectAnimatorSoft: SoftReference<ObjectAnimator?>? = null if (objectAnimatorSoft == null) {objectAnimatorSoft = SoftReference(ObjectAnimator.ofFloat(this, "animationControl", 1F, 0F)) } objectAnimatorSoft?.get()?.let {it.duration = animatorTimeit.addListener(object : Animator.AnimatorListener {override fun onAnimationStart(animation: Animator?) {}override fun onAnimationEnd(animation: Animator?) {this@HeadImageView?.isDefaultPic = false}override fun onAnimationCancel(animation: Animator?) {this@HeadImageView?.isDefaultPic = false}override fun onAnimationRepeat(animation: Animator?) {}}) }提供一個方法等圖片加載出來后,取消默認圖的顯示。isDefaultPic 的set里面調用了 invalidate() 所以會直接重繪了。
fun cancelDefaultPic() {if (isDefaultPic) {//如果繪制的是drawable則沒有動畫效果,直接設置isDefaultPic重新繪制即可if (mBackgroundDrawable != null) {isDefaultPic = falsereturn}if (enableAnim) {//啟用動畫objectAnimatorSoft?.get()?.start()} else {//不啟用動畫isDefaultPic = false}} } 好像就,差不多了。。。 下面進行其他的掃尾工作5.考慮全局可配置性👀
上面的顏色以及對應的顯示字體都是根據content自動生成的,所以需要一個默認的算法,同時也允許提供定制算法。 上面的默認圖是背景色+文字。也有可能是圖片呀 , 怎么破😃 可自定設置動畫時常、判斷啟用動畫 綜上:我們需要額外提供一個object類,用來注冊這些東西。太細節就不說了,簡單展示一下默認提供的計算字體和背景色的方法以及默認圖是圖片的問題。 背景色計算😃 private fun defaultObtainImageFontBackgroundColorInt(name: String): Int {val bgColors = intArrayOf(Color.parseColor("#FA7976"),Color.parseColor("#B7A0F1"),Color.parseColor("#6890F3"),Color.parseColor("#57BAB3"),Color.parseColor("#61C7F1"),Color.parseColor("#FAA77D"))return name.toByteArray().fold(0) { acc: Int, byte: Byte ->acc + byte}.let {bgColors[it.absoluteValue % bgColors.size]} } 取內容的后兩個字顯示😃 private fun defaultObtainImageFontText(name: String): String {val length = name.lengthreturn if (length > 2) {name.substring(length - 2, length)} else {name} } 至于顯示默認背景為圖片就更簡單了,直接在object中防止一個Drawable,繪制的實現先從object中取,取不到的則繼續正常的背景繪制,不然就直接繪制drawable就好了🙂。 //如果drawable不為null直接繪制drawable否則繪制默認背景圖if (mBackgroundDrawable != null) {mBackgroundDrawable?.draw(canvas)return}drawDefaultPic(canvas)6.掃尾處理(recyclerView圖片錯亂、glide加載動畫去除等)👀
寫工具方法加載圖片了,這里使用glide進行網絡圖片的加載。 設置tag防止圖片錯亂 使用自定義ViewTarget,手動設置圖片,去除動畫 /*** 加載圖片*/ fun HeadImageView.loadImage(url: String, name: String) {val text = HeadImageViewHelp.obtainImageFontText(name)val colorInt = HeadImageViewHelp.obtainImageFontBackgroundColorInt(name)val tag = getTag(R.id.imageload_tag)if (tag == null || !TextUtils.equals(tag.toString(), url)) {//顯示默認圖片setBackgroundFontColorAndText(colorInt, text)}Glide.with(this).load(url).into(ImageViewTarget(this)) }/*** 自定義ImageViewTarget設置圖片*/ class ImageViewTarget(ivPic: HeadImageView) : CustomViewTarget<HeadImageView, Drawable>(ivPic) {override fun onLoadFailed(errorDrawable: Drawable?) {}override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {this.view.setImageDrawable(resource)//默認取消文字顯示this.view.cancelDefaultPic()}override fun onResourceCleared(placeholder: Drawable?) {}} 至此:基本就完成了,使用的話只需要調用loadImage方法就可以了。源碼地址:覺得不錯給個star吧!🧐
https://github.com/zhangnangua/grocery-store/tree/master/HeadImageViewDemo
總結
以上是生活随笔為你收集整理的Android:手把手教你自定义头像View,可根据名字自动生成背景色+文字的显示效果,含动画效果。的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为什么百度快照没有样式
- 下一篇: 今天的骑行路线。。。