IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android:手把手教你自定义头像View,可根据名字自动生成背景色+文字的显示效果,含动画效果。 -> 正文阅读

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

首先看需要做成的效果,如下所示😻:

请添加图片描述
请添加图片描述

如上所示:我们需要做到如下效果,在图片加载出来之前或者加载失败需要展示一个自定义的背景色+文字的样式,加载完成之后显示对应的图片,且包含动画效果。
由于:需要项目本身包含有:com.github.open-android:RoundedImageView:v1.0.0 库。 所以直接基于RoundedImageView来自定义HeadImageView,这样就不需要处理圆形了,只需要关注背景色、文字、动画以及可配置性就可以了。

现在就可以简单的,我们可以暂时写这么多:

class HeadImageView
@JvmOverloads
constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
    RoundedImageView(context, attrs, defStyle) {
}

分析:应该怎么做🧐?

  1. 定义可配置的属性:我们生成的背景图由背景色和文字组成,另外是否显示自定义绘制的样式需要有一个开关,所以我们这里就定义:文字颜色、文字大小、背景色以及是否显示自定义的样式。
  2. 绘制背景色
  3. 绘制文字
  4. 增加动画效果
  5. 考虑全局可配置性
  6. 扫尾处理(recyclerView图片错乱、glide加载动画去除等)

下面,就按照以上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中,对属性进行解析,核心代码如下

    @ColorInt
    var fontColor: Int = -1
        set(value) {
            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.考虑全局可配置性👀

上面的颜色以及对应的显示字体都是根据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.length
    return 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

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-07-31 16:45:30  更:2021-07-31 16:47:46 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/7 0:46:33-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码