hello! I'm coming!又有好长时间没更新了。今天我们来实现一个基于Kotlin的万能自定义ImageView(在Google的大力推动下,Kotlin已经成为android开发的主流语言了)。
作为移动端开发来说,最郁闷的莫过于UI设计师的天马行空的想象了,一会出个圆形的头像,一会出个圆角的头像,又会出一些不规则的头像...但对于我们开发来说好不容易开发好一个满足要求的自定义View,但下个版本有可能就变了,这时候心头真是万马奔腾。没办法,谁叫我们是搬砖的呢,继续苦命的开发。这时候我就想,能不能写一个万能的CustomImageView呢,这样就不用纠结UI的各种变动了。不知道大家了解PorterDuffXfermode不?如果不了解的也可以去官网瞅瞅,我们可以基于它来实现图片的混合渲染绘制以达到我们想要显示的形状,哪怕是不规则的,也不需要各种跪求UI设计师给我们提供对应的path来进行图片裁剪。想想就很激动ψ(`∇´)ψ。先来目睹一下我们的效果图
CustomImageView.png
感觉效果还可以吧,嘿嘿!
那就开撸吧!!!
1.首先当然是定义自定义属性
<declare-styleable name="CustomImageView">
<!--目标图片资源,即想要截取形状的样图-->
<attr name="dsc" format="reference"/>
<!--边框图片资源-->
<attr name="bord" format="reference"/>
<!--是否显示边框-->
<attr name="hasBord" format="boolean"/>
</declare-styleable>
2.下面便是正式自定义ImageView设计代码了
/**
* Created by coud on 2018/10/30.
* 万能自定义imageview
*/
class CustomImageView(context: Context, attrs: AttributeSet? = null) : AppCompatImageView(context, attrs) {
//是否有边框
var isBord = false
set(value) {
field = value
setUp()
}
//目标资源
var dscRes = R.mipmap.ic_default_custom_dsc
set(value) {
field = value
setUp()
}
//边框资源
var bordRes = R.mipmap.ic_default_custom_bord
set(value) {
field = value
isBord = true
setUp()
}
private var mReady = true
private var mSetupPending = false
//源bitmap
private var mSrcBitmap: Bitmap? = null
//目标bitmap
private var mDstBitmap: Bitmap? = null
//边框bitmap
private var mBordBitmap: Bitmap? = null
private val bitmapConfig by lazy { Bitmap.Config.ARGB_8888 }
private val colorDrawableDimension by lazy { 2 }
private val drawableRect by lazy { RectF() }
private val borderRect by lazy { RectF() }
private val shaderMatrix by lazy { Matrix() }
private val bitmapPaint by lazy { Paint(Paint.ANTI_ALIAS_FLAG) }
init {
attrs?.also {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomImageView)
isBord = typedArray.getBoolean(R.styleable.CustomImageView_hasBord, false)
dscRes = typedArray.getResourceId(R.styleable.CustomImageView_dsc, R.mipmap.ic_default_custom_dsc)
bordRes = typedArray.getResourceId(R.styleable.CustomImageView_bord, R.mipmap.ic_default_custom_bord)
typedArray.recycle()
}
init()
}
private fun init() {
if (mSetupPending) {
setUp()
mSetupPending = false
}
}
override fun setAdjustViewBounds(adjustViewBounds: Boolean) {
if (adjustViewBounds) {
throw IllegalArgumentException("adjustViewBounds not supported.")
}
}
override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {
super.setPadding(left, top, right, bottom)
setUp()
}
override fun setPaddingRelative(start: Int, top: Int, end: Int, bottom: Int) {
super.setPaddingRelative(start, top, end, bottom)
setUp()
}
override fun setImageBitmap(bm: Bitmap?) {
super.setImageBitmap(bm)
initializeBitmap()
}
override fun setImageDrawable(drawable: Drawable?) {
super.setImageDrawable(drawable)
initializeBitmap()
}
override fun setImageResource(resId: Int) {
super.setImageResource(resId)
initializeBitmap()
}
override fun setImageURI(uri: Uri?) {
super.setImageURI(uri)
initializeBitmap()
}
override fun setColorFilter(cf: ColorFilter?) {
super.setColorFilter(cf)
cf?.also {
applyColorFilter()
invalidate()
}
}
@SuppressLint("DrawAllocation")
override fun onDraw(canvas: Canvas?) {
mSrcBitmap?.also { it ->
//背景色设为白色,方便比较效果
canvas?.drawColor(Color.TRANSPARENT)
//将绘制操作保存到新的图层,因为图像合成是很昂贵的操作,将用到硬件加速,这里将图像合成的处理放到离屏缓存中进行
val saveCount = canvas?.saveLayer(drawableRect, bitmapPaint, ALL_SAVE_FLAG) ?: 0
//绘制目标图
mDstBitmap?.also {
canvas?.drawBitmap(it, null, drawableRect, bitmapPaint)
}
//设置混合模式
bitmapPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
//绘制源图
canvas?.drawBitmap(it, null, drawableRect, bitmapPaint)
//清除混合模式
bitmapPaint.xfermode = null
// 还原画布
canvas?.restoreToCount(saveCount)
if (isBord) {
mBordBitmap?.also {
canvas?.drawBitmap(it, null, drawableRect, bitmapPaint)
}
}
}
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
setUp()
}
private fun setUp() {
if (!mReady) {
mSetupPending = true
return
}
if (0 == width || 0 == height) {
return
}
mSrcBitmap?.also { it ->
borderRect.set(calculateBounds())
drawableRect.set(borderRect)
val srcShader = BitmapShader(it, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
mDstBitmap = mDstBitmap ?: getBitmapFromRes(dscRes)
if (isBord) {
mBordBitmap = getBitmapFromRes(bordRes)
mBordBitmap?.also {
val bordShader = BitmapShader(it, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
updateShaderMatrix(it, bordShader)
}
}
applyColorFilter()
updateShaderMatrix(it, srcShader)
mDstBitmap?.also {
val dstShader = BitmapShader(it, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
updateShaderMatrix(it, dstShader)
}
invalidate()
} ?: also {
invalidate()
}
}
private fun initializeBitmap() {
mSrcBitmap = getBitmapFromDrawable(drawable)
setUp()
}
private fun getBitmapFromDrawable(drawable: Drawable): Bitmap {
return when (drawable) {
is BitmapDrawable -> drawable.bitmap
is ColorDrawable -> creatBitmap(Bitmap.createBitmap(colorDrawableDimension, colorDrawableDimension, bitmapConfig), drawable)
else -> creatBitmap(Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, bitmapConfig), drawable)
}
}
private fun creatBitmap(bitmap: Bitmap, drawable: Drawable): Bitmap {
return bitmap.also {
val canvas = Canvas(it)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
}
}
@SuppressLint("NewApi")
private fun applyColorFilter() {
colorFilter?.also {
bitmapPaint.colorFilter = it
}
}
private fun getBitmapFromRes(@DrawableRes resId: Int): Bitmap {
return resId.let {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
creatBitmap(Bitmap.createBitmap(drawableRect.width().toInt(),
drawableRect.height().toInt(), bitmapConfig),
resources.getDrawable(it, null))
} else {
BitmapFactory.decodeResource(resources, it)
}
}
}
private fun calculateBounds(): RectF {
val availableWidth = width - paddingLeft - paddingRight
val availableHeight = height - paddingTop - paddingBottom
val sideLength = Math.min(availableWidth, availableHeight)
val left = paddingLeft - (availableWidth - sideLength) / 2.0f
val top = paddingTop - (availableHeight - sideLength) / 2.0f
return RectF(left, top, left + sideLength, top + sideLength)
}
private fun updateShaderMatrix(bitmap: Bitmap, shader: Shader) {
shaderMatrix.set(null)
val scaleX = bitmap.width / drawableRect.width()
val scaleY = bitmap.height / drawableRect.height()
shaderMatrix.setScale(scaleX, scaleY)
shader.setLocalMatrix(shaderMatrix)
}
}
以后不管UI设计师想要什么样的头像效果,我们都可以很方便的实现。这时候我们只需要让UI设计师提供一张默认的效果图,然后我们设置通过app:dsc="@drawable/icon_dsc"
, 如果要带边框app:hasBord="true" app:bord="@drawable/icon_bord"
,就可以轻松实现。so easy,从此再也不怕各种形状了。
网友评论