Android:手把手教你自定义头像View,可根据名字自动生成背景色+文字的显示效果,含动画效果。

field = value

invalidate()

}

@Px

var fontSize: Float = 0.0F

set(value) {

field = value

invalidate()

}

var isDefaultPic: Boolean = false

set(value) {

field = value

invalidate()

}

@ColorInt

private var backgroundFontColorInt: Int = -1

init {

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()来进行判断

override fun onDraw(canvas: Canvas?) {

super.onDraw(canvas)

drawDefaultPic(canvas!!)

}

private fun drawDefaultPic(canvas: Canvas) {

mPaint.alpha = 255

mPaint.color = backgroundFontColorInt

if (isOval) {

canvas.drawCircle(width / 2F, height / 2F, width / 2F, mPaint)

} else {

canvas.drawRect(0F, 0F, width.toFloat(), height.toFloat(), mPaint)

}

}

3.绘制文字👀

这里我需要关注的是,怎么样让文字居中显示绘制

先看一张图🤩。

在这里插入图片描述

上面的参数很多是吧,是不是感觉有点懵😫,不慌,只需要关注这几个参数:

base:字符基线,这个就比如我们规定,在一个100*100的空间里,在点(50,50)的位置绘制abcdefg,那么abcdef这几个字母全在横坐标50的上方,而g则上半部分在上方,下半部分在下方。因为绘制坐标是基于base来的。

descent:字符最低点到base的推荐距离

ascent:字符最高点到base的推荐距离

bottom:字符最低点到base的最大距离

以上便是需要知道的参数,所以怎么保证,字体绘制在最中间呢?

水平居中

定义一个矩形,设置文字对其方式设置为居中对齐,然后设置x轴坐标为矩形的中心,那水平方向就居中了。

垂直居中

垂直的需要计算出从base到垂直水平点的偏移量,那么偏移量怎么计算呢?

descent - ascent 就是字体整体推荐的高度,除以2就是推荐高度的一半。考虑还有字体最低点,可以减去bottom

核心实现代码如下😋

private fun onDrawFont(canvas: Canvas) {

val rect = Rect(0, 0, width, height)

mPaint.color = fontColor

mPaint.textSize = fontSize

mPaint.textAlign = Paint.Align.CENTER

val baseLine = mPaint.fontMetrics.let {

val distance = (it.descent - it.ascent) / 2 - it.bottom

rect.centerY() + distance

}

canvas.drawText(displayText, rect.centerX().toFloat(), baseLine, mPaint)

}

if (isDefaultPic) {

drawDefaultPic(canvas)

}

em,这样的话基本就可以使用了,如果不需要动画等其他的效果,上面的代码粘贴粘贴基本就可以了。

4.增加动画效果👀

这怎么做到呢?

思考一下,到可以使用属性动画+离屏绘制+图层混合完成。

属性动画:可以简单的理解为,动画间隔时间内不断的更改对应属性的值,重绘。实现动画效果。

离屏绘制:简单理解使用全新的canvas,绘制完成后,盖在了原来的canvas上面。

图层混合:将所绘制的像素与canvas中对应位置的像素按照一定规则进行混合,形成新的像素值,最终更新canvas中最终显示的像素值。

这几个概念呢?如果有不清楚的,可以简单Google了解一下哈,这里不做引申了,直接看怎么使用吧😋。

大致的实现原理呢😃?

首先背景色加文字是通过canvas直接绘制出来,而图层混合有一个模式可以让绘制的交际变透明。就需要在绘制一块,而这一块和上面的绘制变成交际,就会透明了,实际的图像就会显示。自定义一个属性通过属性动画不断变化,然后达到控制第二块绘制区域的效果,实现透明区域的缩放。以上操作均在离屏绘制中完成。考虑到圆形和正方形两种情况,所以针对这两种情况分别做了不同的绘制。

下面看看绘制的代码核心实现吧😍。

//离屏绘制

val saveLayerId = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), mPaint)

//过渡渐变

//dst

mPaint.color = backgroundFontColorInt

if (isOval) {

canvas.drawCircle(width / 2F, height / 2F, width / 2F, mPaint)

} else {

canvas.drawRect(0F, 0F, width.toFloat(), height.toFloat(), mPaint)

}

//绘制字体

onDrawFont(canvas)

//设置图层混合模式 DST_OUT 绘制交集部分变透明

mPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT)

//src

mPaint.color = ContextCompat.getColor(AppUtil.application, R.color.white)

if (isOval) {

canvas.drawCircle(width / 2F, height / 2F, (width / 2) * (1 - animationControl), mPaint)

} else {

canvas.drawRect(

(width / 2F) * animationControl,

(height / 2F) * animationControl,

(width / 2F) + ((1 - animationControl) * (width / 2F)),

(height / 2F) + ((1 - animationControl) * (height / 2F)),

mPaint

)

}

//图层混合取消

mPaint.xfermode = null

canvas.restoreToCount(saveLayerId)

下面在看看动画的代码核心实现吧😍。因为把动画放到了,view里面所以使用软引用持有(内存不够,大不了就被回收了嘛,非必须)。

/**

  • 配合属性动画处理image默认的转场动画 值在 0-1 之间

*/

var animationControl: Float = 1F

set(value) {

field = if (animationControl < 0) 0F else if (animationControl > 1) 1F else value

invalidate()

}

private var objectAnimatorSoft: SoftReference<ObjectAnimator?>? = null

if (objectAnimatorSoft == null) {

objectAnimatorSoft = SoftReference(ObjectAnimator.ofFloat(this, “animationControl”, 1F, 0F))

}

objectAnimatorSoft?.get()?.let {

it.duration = animatorTime

it.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 = false

return

}

if (enableAnim) {

//启用动画

objectAnimatorSoft?.get()?.start()

} else {

//不启用动画

isDefaultPic = false

}

}

}

好像就,差不多了。。。 下面进行其他的扫尾工作

5.考虑全局可配置性👀

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.length

return if (length > 2) {

name.substring(length - 2, length)

} else {

name

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**

[外链图片转存中…(img-xoQrDVPp-1715498229303)]

[外链图片转存中…(img-rEzKLvRU-1715498229306)]

[外链图片转存中…(img-IlRs5EHd-1715498229307)]

[外链图片转存中…(img-RZqFdpjh-1715498229307)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值