实现Android拖拽按钮接听电话效果

本文介绍如何在Android中创建一个可拖动按钮来接听电话的效果,包括SlideSwitchView的实现,XML布局调用及方法使用,同时加入了动画效果。

实现Android拖拽按钮接听电话效果:

  • 向中心拖拽左右两侧的控件,实现对应的功能;
  • 添加动画效果;
    在这里插入图片描述

1、SlideSwitchView.kt

import android.content.Context
import android.graphics.*
import android.os.Handler
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import androidx.core.content.ContextCompat

class SlideSwitchView(
    context: Context?,
    attrs: AttributeSet?,
    defStyleAttr: Int,
    defStyleRes: Int
) : View(context, attrs, defStyleAttr, defStyleRes) {

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

    constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context?) : this(context, null)

    //画中心扩散圆相关
    private val centerCirPaint = Paint()//画中间大圆的画笔
    private val offSetTopAndBottom = 1f / 2//中心圆的边距布局顶部所占总高的比例
    private val centerCirWidthList = ArrayList<Float>()//中心圆的扩散的宽度
    private val centerCirAlphaList = ArrayList<Int>()//中心圆的扩散的透明度
    private val centerSpeed = 0.3f//中心圆的扩散速度
    private var centerSpeedAdd = 0f//中心圆增加的的扩散速度
    private val mDiffuseWidth = 20//扩散圆宽度(扩散圆与原始圆的半径的差值)
    private var offSetTopOrBottom: Float = 0f//中心圆的顶部到控件顶部的距离(底部到控件底部的距离)
    private var centerCirRadius: Float = 0f//中心圆的半径
    private var centerRatio = 1f//中心圆的倍率大小
    private val zoomRatio = 0.3f//中心圆变大时半径最多增加几倍
    private var centerCirColor = R.color.color_D5E7FF//中心圆和扩散圆的颜色
    private var CENTER_CIR_COLOR_ORIGINAL = R.color.color_D5E7FF//中心圆和扩散圆的默认颜色
    private var CENTER_CIR_COLOR_LEFT_START = R.color.color_589EFF
    private var CENTER_CIR_COLOR_RIGHT_START = R.color.color_FCA56B
    private var CENTER_CIR_COLOR_LEFT_END =
        R.color.color_FF8432//左侧小球移动到中心圆圆心时的中心圆和扩散圆的颜色  也做左侧小圆的颜色及左侧点的颜色
    private var CENTER_CIR_COLOR_RIGHT_END =
        R.color.color_1677FF//右侧小球移动到中心圆圆心时的中心圆和扩散圆的颜色 也做右侧小圆的颜色及右侧点的颜色
    private var AUTO_RESTORE = false//左右侧小圆是否自动恢复

    //画左右小圆相关
    private val smallCirPaint = Paint()//画左右两个小圆的画笔
    private var leftCirX = 0f//左边圆的圆心的X坐标
    private var leftCirY = 0f//左边圆的圆心的Y坐标
    private var rightCirX = 0f//右边圆的圆心的X坐标
    private var rightCirY = 0f  //右边圆的圆心的Y坐标
    private val smallCirPaddingBound = 10f//左边圆距左边控件的边距  右边圆距控件右边的边距
    private val smallCirShadowRadius = 10f//小圆的阴影半径
    private var smallCirRadius = 0f//小圆的半径
    private val smallRestoreOffset = 20//小圆恢复到原位置的速度
    var alphaLeft = 255//左边小圆及点的透明度
    var alphaRight = 255//右边小圆及点的透明度
    var actionIsLeft = false//执行操作的是哪一边

    //画点相关
    private val pointPaint = Paint()//画点的画笔
    private val leftPointList = ArrayList<Int>()//左边的点的透明度
    private val rightPointList = ArrayList<Int>()//右边的点的透明度
    private val pointCount = 4//点的个数
    private val pointRadius = context?.dp2px(2)?.toFloat()!!//点的半径

    //绘制文字相关
    var leftText = "备勤"
    var rightText = "上班"
    private val textPaint = Paint()
    val textMarginTop = context?.dp2px(4)!!//文字和图片的间距

    //绘制图片相关
    var leftImg = BitmapFactory.decodeResource(resources, R.drawable.img_change_prepare)
    var rightImg = BitmapFactory.decodeResource(resources, R.drawable.img_change_work)
    val imgHeight = context?.dp2px(26)!!
    val imgWidth = context?.dp2px(26)!!

    private var changedOffset = 10//状态改变的偏移量
    var leftChangedListener: View.OnClickListener? = null//左侧拖拽完成触发事件
    var rightChangedListener: View.OnClickListener? = null//右侧拖拽完成触发事件

    init {
        context?.let {
            //中心扩散圆相关初始化
            centerCirPaint.isAntiAlias = true
            centerCirWidthList.add(0f)
            centerCirAlphaList.add(200)

            //左右小圆的相关初始化
            smallCirPaint.color = ContextCompat.getColor(it, R.color.color_1677FF)
            smallCirPaint.isAntiAlias = true
            smallCirPaint.setShadowLayer(
                smallCirShadowRadius,
                0f,
                5f,
                ContextCompat.getColor(context, R.color.color_803E8EFF)
            )

            //点的相关初始化
            for (a in 0 until pointCount) {//透明度从255赋值到55
                leftPointList.add((a + 1) * (255 / pointCount))
                rightPointList.add((a + 1) * (255 / pointCount))
            }
            pointPaint.color = ContextCompat.getColor(it, R.color.color_1677FF)
            pointPaint.isAntiAlias = true

            //文字初始化相关
            textPaint.color = Color.WHITE
            smallCirPaint.isAntiAlias = true
            textPaint.textSize = context.sp2px(14)

            //初始化绘图相关
            startPointAnimal()
        }
    }

    private fun startPointAnimal() {
        Handler().postDelayed({
            val leftEndPoint = leftPointList.last()
            leftPointList.add(0, leftEndPoint)
            leftPointList.removeAt(leftPointList.count() - 1)

            val rightEndPoint = rightPointList.last()
            rightPointList.add(0, rightEndPoint)
            rightPointList.removeAt(rightPointList.count() - 1)
            startPointAnimal()
        }, 200)
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        if (offSetTopOrBottom == 0f) {
            offSetTopOrBottom = height * offSetTopAndBottom / 2
        }
        if (centerCirRadius == 0f) {
            centerCirRadius = (height / 2).toFloat() - offSetTopOrBottom
        }
        if (smallCirRadius == 0f) {
            smallCirRadius = centerCirRadius//和中心圆半径一样
        }

        if (leftCirX == 0f) {
            leftCirX = smallCirRadius
        }
        val leftAutoRestore =
            leftCirX < width / 2f - changedOffset - smallCirShadowRadius || AUTO_RESTORE//针对小球移动但没达到触发条件需要自动恢复
        if (IS_FREE && leftAutoRestore && leftCirX > smallCirRadius) {//小球自动恢复到原位置
            leftCirX -= smallRestoreOffset
            if (leftCirX < smallCirRadius) {
                leftCirX = smallCirRadius
            }
        }
        if (leftCirY == 0f) {
            leftCirY = height / 2f
        }
        val maxDistance =
            smallCirRadius * 2 + mDiffuseWidth//小圆和中心圆挨着的时候圆心的最大距离:此处中心圆半径按初始值算:(smallCirRadius+centerCirRadius)/2 因初始值smallCirRadius和centerCirRadius相等 所以直接等于smallCirRadius

        val leftOffset = width / 2 - leftCirX - smallCirPaddingBound//左侧小圆圆心离中心圆的距离

        var doLeft = false//执行了左侧的就不执行右侧的
        if (leftOffset > maxDistance) {
            centerRatio = 1f
            centerSpeedAdd = 0f
        } else {
            doLeft = true
            val tempOffset = (1 - leftOffset / maxDistance) * zoomRatio
            centerRatio = 1 + tempOffset
            centerSpeedAdd = tempOffset
        }
        centerCirColor =
            if (leftCirX >= width / 2f - changedOffset - smallCirShadowRadius) {//小圆在中心圆的圆心 变色
                CENTER_CIR_COLOR_LEFT_END
            } else {
                CENTER_CIR_COLOR_ORIGINAL
            }

        if (rightCirX == 0f) {
            rightCirX = width - smallCirRadius
        }
        val rightAutoRestore =
            rightCirX > width / 2f + changedOffset + smallCirShadowRadius || AUTO_RESTORE//针对小球移动但没达到触发条件需要自动恢复
        if (IS_FREE && rightAutoRestore && rightCirX < width - smallCirRadius) {//小球自动恢复到原位置
            rightCirX += smallRestoreOffset
            if (rightCirX > width - smallCirRadius) {
                rightCirX = width - smallCirRadius
            }
        }

        val rightOffset = rightCirX - width / 2//右侧小圆圆心离中心圆的距离
        if (doLeft.not()) {
            if (rightOffset > maxDistance) {
                centerRatio = 1f
                centerSpeedAdd = 0f
            } else {
                val tempOffset = (1 - rightOffset / maxDistance) * zoomRatio
                centerRatio = 1 + tempOffset
                centerSpeedAdd = tempOffset
            }
            centerCirColor =
                if (rightCirX <= width / 2f + changedOffset + smallCirShadowRadius) {//小圆在中心圆的圆心 变色
                    CENTER_CIR_COLOR_RIGHT_END
                } else {
                    CENTER_CIR_COLOR_ORIGINAL
                }
        }

        if (rightCirY == 0f) {
            rightCirY = height / 2f
        }

        val realCenterCirRadius = centerCirRadius * centerRatio
        //计算圆点的间隔 (控件宽的一半-小圆距离控件的边距-中心圆的半径-小圆的阴影半径*2-点的直径*个数)/计算出的多少个空隔
        val pointSpace =
            (width / 2f - (smallCirRadius * 2) - smallCirPaddingBound - realCenterCirRadius - (smallCirShadowRadius * 2) - (pointRadius * 2 * pointCount)) / (pointCount - 1)

        //绘制扩散圆
        centerCirPaint.color = ContextCompat.getColor(context, centerCirColor)
        for (i in 0 until centerCirAlphaList.count()) {
            val alpha = centerCirAlphaList[i]
            val radius = centerCirWidthList[i]
            centerCirPaint.alpha = alpha

            val cirWidth = realCenterCirRadius + radius
            canvas?.drawCircle(width.toFloat() / 2, height.toFloat() / 2, cirWidth, centerCirPaint)
            if (alpha > 0 && cirWidth * 2 < height) {
                centerCirAlphaList[i] =
                    (alpha - (200f / (offSetTopOrBottom / (centerSpeed + centerSpeedAdd)))).toInt()
                centerCirWidthList[i] = radius + centerSpeed + centerSpeedAdd
            }
        }
        // 判断当扩散圆扩散到指定宽度时添加新扩散圆
        if (centerCirWidthList[centerCirWidthList.count() - 1] >= mDiffuseWidth) {
            centerCirAlphaList.add(200)
            centerCirWidthList.add(0f)
        }
        // 超过最大数量扩散圆,删除最外层
        if (centerCirWidthList.count() > offSetTopOrBottom / mDiffuseWidth) {
            centerCirWidthList.removeAt(0)
            centerCirAlphaList.removeAt(0)
        }

        // 绘制中心圆
        centerCirPaint.alpha = 255
        canvas?.drawCircle(
            width.toFloat() / 2,
            height.toFloat() / 2,
            realCenterCirRadius,
            centerCirPaint
        )
        //绘制点
        pointPaint.color = ContextCompat.getColor(context, CENTER_CIR_COLOR_LEFT_END)
        for (i in 0 until leftPointList.count()) {

            val alpha = leftPointList[i]
            pointPaint.alpha = alpha
            val pointX =
                (smallCirRadius + smallCirShadowRadius) * 2 + smallCirPaddingBound + pointRadius + (i * pointSpace)
            if (pointX >= leftCirX + smallCirPaddingBound) {//不绘制被小球拖过的路径的点
                canvas?.drawCircle(
                    pointX,
                    height / 2f,
                    pointRadius,
                    pointPaint
                )
            }
        }

        pointPaint.color = ContextCompat.getColor(context, CENTER_CIR_COLOR_RIGHT_END)
        for (i in 0 until rightPointList.count()) {
            val alpha = rightPointList[i]
            pointPaint.alpha = alpha
            val pointX =
                width - smallCirPaddingBound - ((smallCirShadowRadius + smallCirRadius) * 2) - pointRadius - (i * pointSpace)
            if (pointX < rightCirX - smallCirPaddingBound) {//不绘制被小球拖过的路径的点
                canvas?.drawCircle(
                    pointX,
                    height / 2f,
                    pointRadius,
                    pointPaint
                )
            }
        }

        //绘制左右小圆
        smallCirPaint.alpha = alphaLeft
        if (alphaLeft < 255) {
            alphaLeft += 5
            if (alphaLeft > 255) {
                alphaLeft = 255
            }
        }
        smallCirPaint.setShadowLayer(
            smallCirShadowRadius,
            0f,
            5f,
            ContextCompat.getColor(context, CENTER_CIR_COLOR_LEFT_END)
        )

        val tempLeftX = leftCirX + smallCirPaddingBound

        smallCirPaint.shader = LinearGradient(
            tempLeftX,
            leftCirY - smallCirRadius,
            tempLeftX,
            leftCirY + smallCirRadius,
            ContextCompat.getColor(context, CENTER_CIR_COLOR_LEFT_START),
            ContextCompat.getColor(context, CENTER_CIR_COLOR_LEFT_END),
            Shader.TileMode.MIRROR
        )
//        smallCirPaint.color = ContextCompat.getColor(context, CENTER_CIR_COLOR_LEFT_END)
        canvas?.drawCircle(
            tempLeftX,
            leftCirY,
            smallCirRadius,
            smallCirPaint
        )

        smallCirPaint.alpha = alphaRight
        if (alphaRight < 255) {
            alphaRight += 5
            if (alphaRight > 255) {
                alphaRight = 255
            }
        }

        smallCirPaint.setShadowLayer(
            smallCirShadowRadius,
            0f,
            5f,
            ContextCompat.getColor(context, CENTER_CIR_COLOR_RIGHT_END)
        )
        smallCirPaint.shader = LinearGradient(
            tempLeftX,
            leftCirY - smallCirRadius,
            tempLeftX,
            leftCirY + smallCirRadius,
            ContextCompat.getColor(context, CENTER_CIR_COLOR_RIGHT_START),
            ContextCompat.getColor(context, CENTER_CIR_COLOR_RIGHT_END),
            Shader.TileMode.MIRROR
        )
//        smallCirPaint.color = ContextCompat.getColor(context, CENTER_CIR_COLOR_RIGHT_END)
        canvas?.drawCircle(
            rightCirX - smallCirPaddingBound,
            rightCirY,
            smallCirRadius,
            smallCirPaint
        )

        //绘制文字
        val rect = Rect()
        textPaint.getTextBounds(leftText, 0, leftText.length, rect)

        canvas?.drawText(
            leftText,
            leftCirX + smallCirPaddingBound - rect.width() / 2,
            leftCirY + rect.height() + textMarginTop,
            textPaint
        )

        textPaint.getTextBounds(rightText, 0, rightText.length, rect)
        canvas?.drawText(
            rightText,
            rightCirX - smallCirPaddingBound - rect.width() / 2,
            rightCirY + rect.height() + textMarginTop,
            textPaint
        )

        var imgLeft = (leftCirX + smallCirPaddingBound - imgWidth / 2).toInt()
        var imgTop = (leftCirY - imgHeight).toInt()
        var imgRight = imgLeft + imgWidth
        var imgBottom = imgTop + imgHeight
        // 指定图片在屏幕上显示的区域(原图大小)
        val dst = Rect(imgLeft, imgTop, imgRight, imgBottom)
        //绘制图片
        canvas?.drawBitmap(leftImg, null, dst, null)//绘制左侧图片

        imgLeft = (rightCirX - smallCirPaddingBound - imgWidth / 2).toInt()
        imgTop = (rightCirY - imgHeight).toInt()
        imgRight = imgLeft + imgWidth
        imgBottom = imgTop + imgHeight
        dst.set(imgLeft, imgTop, imgRight, imgBottom)
        canvas?.drawBitmap(rightImg, null, dst, null)//绘制右侧图片

        invalidate()
//        canvas?.drawArc(centerRectF,90f,360f,true,centerCirPaint)
    }

    fun setLeftImg(img: Int) {
        leftImg = BitmapFactory.decodeResource(resources, img)
    }

    fun setRightImg(img: Int) {
        BitmapFactory.decodeResource(resources, img)
    }

    var leftCanMove = false//左侧图片是否可以移动
    var rightCanMove = false//右侧图片是否可以移动

    var leftOffsetX = 0f
    var rightOffsetX = 0f
    var IS_FREE = true//如果不是在触摸状态则自动释放小圆回到原位置

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        event?.apply {
            when (action) {
                MotionEvent.ACTION_DOWN -> {
                    //点击在左侧圆范围内
                    if (leftCirX == smallCirRadius && rightCirX == width - smallCirRadius && x > smallCirPaddingBound && x < smallCirPaddingBound + smallCirRadius * 2 && y > height / 2 - smallCirRadius && y < height / 2 + smallCirRadius) {
                        leftCanMove = true
                        leftOffsetX = x
                        IS_FREE = false
                    }
                    //点击在右侧圆范围内
                    if (leftCirX == smallCirRadius && rightCirX == width - smallCirRadius && x > width - smallCirPaddingBound - smallCirRadius * 2 && x < width - smallCirPaddingBound && y > height / 2 - smallCirRadius && y < height / 2 + smallCirRadius) {
                        rightCanMove = true
                        rightOffsetX = x
                        IS_FREE = false
                    }
                }
                MotionEvent.ACTION_MOVE -> {
                    if (leftCanMove) {
                        val tempX = leftCirX + x - leftOffsetX
                        if (tempX >= smallCirRadius && tempX <= width / 2 - smallCirShadowRadius) {
                            leftCirX += x - leftOffsetX
                            if (leftCirX > width / 2 - smallCirShadowRadius) {//限定最大值
                                leftCirX = width / 2f - smallCirShadowRadius
                            }
                            if (leftCirX < smallCirRadius) {//限定最小值
                                leftCirX = smallCirRadius
                            }
                        }
                        leftOffsetX = x
                    }

                    if (rightCanMove) {
                        val tempX = rightCirX + x - rightOffsetX
                        if (tempX >= width / 2 + smallCirShadowRadius && tempX <= width - smallCirRadius) {
                            rightCirX += x - rightOffsetX
                            if (rightCirX < width / 2f + smallCirShadowRadius) {//限定最小值
                                rightCirX = width / 2f + smallCirShadowRadius
                            }
                            if (rightCirX > width - smallCirRadius) {//限定最大值
                                rightCirX = width - smallCirRadius
                            }
                        }
                        rightOffsetX = x
                    }
                }
                MotionEvent.ACTION_UP -> {
                    IS_FREE = true

                    if (leftCanMove && leftCirX >= width / 2 - changedOffset - smallCirShadowRadius) {
                        actionIsLeft = true
                        leftChangedListener?.onClick(this@SlideSwitchView)
                    }
                    if (rightCanMove && rightCirX <= width / 2 + changedOffset + smallCirShadowRadius) {
                        actionIsLeft = false
                        rightChangedListener?.onClick(this@SlideSwitchView)
                    }
                    leftCanMove = false
                    rightCanMove = false
                }
                else -> {
                    return super.onTouchEvent(event)
                }
            }
        }
        return true
    }

    //恢复原样
    fun reSet() {
        if (actionIsLeft) {
            alphaLeft = 0
        } else {
            alphaRight = 0
        }
        leftCirX = smallCirRadius
        rightCirX = width - smallCirRadius
    }

    fun Context.dp2px(dp: Int) = (resources.displayMetrics.density * dp + .5f).toInt()

    fun Context.sp2px(sp: Int) = resources.displayMetrics.scaledDensity * sp + .5f
}

2、xml调用:

        <com.view.SlideSwitchView
            android:id="@+id/ssv"
            android:layout_width="match_parent"
            android:layout_height="120dp"
            android:layout_marginTop="100dp"
            android:layout_marginStart="30dp"
            android:layout_marginEnd="30dp"
            android:layerType="software" />

3、方法使用:

       slideSwitchView.leftChangedListener = View.OnClickListener {
            Log.d("caowj", "左侧拖拽完成触发事件")
            showToast("左拖拽")
//            nViewDataBinding.ssv.setLeftImg()
            nViewDataBinding.ssv.leftText = "下班"
            nViewDataBinding.ssv.reSet()
        }

        slideSwitchView.rightChangedListener = View.OnClickListener {
            Log.d("caowj", "右侧拖拽完成触发事件")
//            nViewDataBinding.ssv.setRightImg()
            nViewDataBinding.ssv.rightText = "离职"
            showToast("右拖拽")
            nViewDataBinding.ssv.reSet()
        }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

唐诺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值