hello! I'm coming!又有好長時間沒更新了撑螺。今天我們來實(shí)現(xiàn)一個基于Kotlin的萬能自定義ImageView(在Google的大力推動下,Kotlin已經(jīng)成為android開發(fā)的主流語言了)崎弃。
作為移動端開發(fā)來說甘晤,最郁悶的莫過于UI設(shè)計(jì)師的天馬行空的想象了含潘,一會出個圓形的頭像,一會出個圓角的頭像线婚,又會出一些不規(guī)則的頭像...但對于我們開發(fā)來說好不容易開發(fā)好一個滿足要求的自定義View遏弱,但下個版本有可能就變了,這時候心頭真是萬馬奔騰塞弊。沒辦法漱逸,誰叫我們是搬磚的呢,繼續(xù)苦命的開發(fā)游沿。這時候我就想饰抒,能不能寫一個萬能的CustomImageView呢,這樣就不用糾結(jié)UI的各種變動了诀黍。不知道大家了解PorterDuffXfermode不袋坑?如果不了解的也可以去官網(wǎng)瞅瞅,我們可以基于它來實(shí)現(xiàn)圖片的混合渲染繪制以達(dá)到我們想要顯示的形狀眯勾,哪怕是不規(guī)則的枣宫,也不需要各種跪求UI設(shè)計(jì)師給我們提供對應(yīng)的path來進(jìn)行圖片裁剪。想想就很激動ψ(`?′)ψ咒精。先來目睹一下我們的效果圖
感覺效果還可以吧镶柱,嘿嘿!
那就開擼吧DP稹P稹!
1.首先當(dāng)然是定義自定義屬性
<declare-styleable name="CustomImageView">
<!--目標(biāo)圖片資源范咨,即想要截取形狀的樣圖-->
<attr name="dsc" format="reference"/>
<!--邊框圖片資源-->
<attr name="bord" format="reference"/>
<!--是否顯示邊框-->
<attr name="hasBord" format="boolean"/>
</declare-styleable>
2.下面便是正式自定義ImageView設(shè)計(jì)代碼了
/**
* 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()
}
//目標(biāo)資源
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
//目標(biāo)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 ->
//背景色設(shè)為白色故觅,方便比較效果
canvas?.drawColor(Color.TRANSPARENT)
//將繪制操作保存到新的圖層,因?yàn)閳D像合成是很昂貴的操作渠啊,將用到硬件加速输吏,這里將圖像合成的處理放到離屏緩存中進(jìn)行
val saveCount = canvas?.saveLayer(drawableRect, bitmapPaint, ALL_SAVE_FLAG) ?: 0
//繪制目標(biāo)圖
mDstBitmap?.also {
canvas?.drawBitmap(it, null, drawableRect, bitmapPaint)
}
//設(shè)置混合模式
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設(shè)計(jì)師想要什么樣的頭像效果,我們都可以很方便的實(shí)現(xiàn)替蛉。這時候我們只需要讓UI設(shè)計(jì)師提供一張默認(rèn)的效果圖贯溅,然后我們設(shè)置通過app:dsc="@drawable/icon_dsc"
, 如果要帶邊框app:hasBord="true" app:bord="@drawable/icon_bord"
躲查,就可以輕松實(shí)現(xiàn)它浅。so easy,從此再也不怕各種形狀了镣煮。