Android自定义view实现两张图片的分割线滑动对比

140 阅读2分钟
首先对自定义view拆分一下流程,要实现两张图片的分割线滑动对比视图,首先肯定需要两个摆放图片 的imageview,再通过分割线的位置来对图片控件的大小进行绘制,绘制分割线,以及其他控件上的内容。
众所周知,先绘制的控件在底部,类似xml文件里如果不设置elevation,也是默认后面的view盖住前面的view,所以先绘制右边的图片,名字叫imageviewB,后通过分割线的定位再绘制imageviewA的位置,从左盖住B的部分内容。
接下来在绘制分割线,以及其他元素。下面是全部代码

import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.text.TextPaint
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.animation.LinearInterpolator
import android.widget.ImageView
import androidx.core.content.ContextCompat
import com.blankj.utilcode.util.LogUtils
import com.blankj.utilcode.util.SizeUtils

class ComparisonImageView : View {
    private val mImageViewA by lazy {
        ImageView(context).apply {
            setImageResource(R.mipmap.img_take_photo_default)
            scaleType = ImageView.ScaleType.CENTER_CROP
        }
    }
    private val mImageViewB by lazy {
        ImageView(context).apply {
            setImageResource(R.mipmap.img_take_photo_default)
            scaleType = ImageView.ScaleType.CENTER_CROP
        }
    }
    private val imgViewBefore by lazy {
        ContextCompat.getDrawable(
            context,
            R.mipmap.img_before
        )
    }
    private val imgViewAfter by lazy {
        ContextCompat.getDrawable(
            context,
            R.mipmap.img_after
        )
    }

    private val mDrawableBar by lazy { ContextCompat.getDrawable(context, R.mipmap.img_score_bar) }
    private val mDrawableLine by lazy {
        ContextCompat.getDrawable(
            context,
            R.mipmap.img_score_line
        )
    }
    private val lineHalfWidth by lazy { SizeUtils.dp2px(1f).toFloat() }
    private val dp12 by lazy { SizeUtils.dp2px(12f).toFloat() }
    private val dp34 by lazy { SizeUtils.dp2px(26f).toFloat() }
    private val dp64 by lazy { SizeUtils.dp2px(45f).toFloat() }
    private val dp74 by lazy { SizeUtils.dp2px(114f).toFloat() }

    var drawMode = 0

    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )

    constructor(
        context: Context?,
        attrs: AttributeSet?,
        defStyleAttr: Int,
        defStyleRes: Int
    ) : super(context, attrs, defStyleAttr, defStyleRes)

    fun setImage(imgNormal: Bitmap?, imgChange: Bitmap?) {
        mImageViewA.setImageBitmap(imgChange)
        mImageViewB.setImageBitmap(imgNormal)
    }

    private val mTextPain by lazy {
        TextPaint().apply {
            color = Color.parseColor("#FFFFFF")
            textSize = SizeUtils.dp2px(14f).toFloat()
            strokeWidth = 0.9F
            style = Paint.Style.FILL_AND_STROKE
            isAntiAlias = true
            setShadowLayer(3f, 0f, SizeUtils.dp2px(1f).toFloat(), Color.parseColor("#80000000"))
        }
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        if (drawMode == 1) {
            mImageViewB.draw(canvas)
            return
        }
        mImageViewB.draw(canvas)
        canvas.save()
        canvas.clipRect(getLineValue(), 0, width, height)
        mImageViewA.draw(canvas)
        canvas.restore()
        //中线 start
        canvas.save()
        canvas.clipRect(
            getLineValue() - lineHalfWidth,
            0f,
            getLineValue() + lineHalfWidth,
            height.toFloat()
        )
        mDrawableLine?.setBounds(0, 0, width, height)
        mDrawableLine?.draw(canvas)
        canvas.restore()
        //中线 end
        canvas.save()
        canvas.translate(dp34, height-dp64)
        imgViewBefore?.setBounds(0, 0, SizeUtils.dp2px(68f), SizeUtils.dp2px(32f))
        imgViewBefore?.draw(canvas)
        canvas.restore()
        canvas.save()
        canvas.translate(width - SizeUtils.dp2px(68f) - dp34, height-dp64)
        imgViewAfter?.setBounds(0, 0, SizeUtils.dp2px(68f), SizeUtils.dp2px(32f))
        imgViewAfter?.draw(canvas)
        canvas.restore()
        canvas.save()
        canvas.translate(getLineValue() - dp64 / 2, height - dp64 - dp34 - dp64)
        mDrawableBar?.setBounds(0, 0, dp64.toInt(), dp34.toInt())
        mDrawableBar?.draw(canvas)
        canvas.restore()
    }

    /**
     * 0 默认  左右滑动对比
     * 1 全部显示处理后
     */
    fun setCompareMode(mode: Int) {
        if (drawMode == mode) return
        drawMode = mode
        postInvalidate()
    }


    var linePer = 0.5f

    private fun getLineValue(): Int {
        return (width * linePer).toInt()
    }

    /**
     * 播放对比效果
     * @param start 起始值
     * @param end 结束值
     * @param duration 动画时间
     */
    fun autoPlayDrawImage(start: Float, end: Float, duration: Long = 1000) {
        postDelayed({
            val va = ValueAnimator.ofFloat(start, end)
            va.interpolator = LinearInterpolator()
            va.addUpdateListener {
                linePer = it.animatedValue as Float
                postInvalidate()
            }
            va.duration = duration
            va.start()
        }, 150)

    }

    var startX = 0f

    override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
        //取消父布局对滑动离开view范围的事件拦截
        parent.requestDisallowInterceptTouchEvent(true)
        return super.dispatchTouchEvent(event)
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        event ?: return false
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                startX = event.rawX
                val lx = getLineValue()
                val boo = (startX > (lx - dp74)) && (startX < (lx + dp74))
                LogUtils.e("滑动开始,$boo")
                return boo
            }

            MotionEvent.ACTION_MOVE -> {
                val movx = event.rawX
                val dir = movx - startX
                val lx = getLineValue()
                linePer = (lx + dir) / width
                if (linePer < 0) {
                    linePer = 0f
                } else if (linePer >= 1) {
                    linePer = 1f
                }
                startX = movx
                invalidate()
                LogUtils.e("滑动中,true")
                return true
            }

            MotionEvent.ACTION_UP -> {
                LogUtils.e("滑动抬起,false")
                return false
            }
        }
        LogUtils.e("滑动五判断,true${event.action}")
        return true
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        mImageViewA.measure(widthMeasureSpec, heightMeasureSpec)
        mImageViewB.measure(widthMeasureSpec, heightMeasureSpec)
    }

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        mImageViewA.layout(left, top, right, bottom)
        mImageViewB.layout(left, top, right, bottom)
    }

    internal inline fun String.debugLog() {
        LogUtils.d(ComparisonImageView::class.java.simpleName, this)
    }
}