防手机锁屏解锁自定义View

一、简介

  • 在Android应用开发的过程中,有的时候为了提高用户留存,我们就会给应用加入锁屏壁纸的功能。锁屏壁纸解锁的功能还是相对简单,有很多种实现的方法,可以使用ObjectAnimator属性动画,也可以使用Scroller实现。

二、ObjectAnimator与Scroller实现比较

  • 在写锁屏自定义View过程中,曾经尝试过使用ObjectAnimator或Scroller两种方式进行实现,不过在实际过程中发现还是Scroller实现比较好。如果使用ObjectAnimator实现锁屏解锁的话,有部分机型会出现滑动卡顿的情况,貌似oppo之类的手机就会卡顿。

三、 Scroller实现核心

1、锁屏解锁主要有两个关键点
  • 一是手指触摸手机滑动的时候,带动页面进行滑动。二是当手指离开屏幕的时候进行判断解锁成功还是失败,成功时候要往上滑动到屏幕顶端进行解锁,失败重滑动到原来位置。至于解锁成的条件主要看业务需求,可以是屏幕高度的40%,或者屏幕宽度的40%,主要取决于你是要水平方向滑动解锁还是垂直方向滑动解锁。
2、触摸手机滑动带动页面进行滑动
  • Scroller实现手指触摸手机带动页面滑动主要难点在于每次滑动的距离是多少,若使用onTouchEvent事件中ACTION_MOVE滑动的距离来作为页面滑动距离,会产生卡顿的视觉效果。所以需要借助手势检测器监听器,在手势检测器的onScroll回调中获取滑动的距离,这样页面在滑动的时候就不会产生卡顿。下面贴出滑动实现代码:
	    //每次滑动的距离,整数代表向上滑,负数代表向下滑
    private fun beginScroll(dx: Int, dy: Int, duration: Int?) { 
        Loger.d("beginScroll", " mScroller!!.finalX : ${mScroller!!.finalX} mScroller!!.finalY : ${mScroller!!.finalY}  dx : $dx dy : $dy" )
        if (duration != null) {
            mScroller!!.startScroll(mScroller!!.finalX, mScroller!!.finalY, dx, dy, duration)
        } else {
            mScroller!!.startScroll(mScroller!!.finalX, mScroller!!.finalY, dx, dy)
        }
        //必须执行invalidate()从而调用computeScroll()
        invalidate()
    }
    
  • 然后在手势检测器的onScroll回调中调用(五种模式,分别是上下左右和同时支持上下滑动)
 override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean {
     when (dragMode) {
         MODE_LEFT -> {
             beginScroll(distanceX.toInt(), 0, null)
         }

         MODE_RIGHT -> {
             beginScroll(distanceX.toInt(), 0, null)
         }

         MODE_UP -> {
             beginScroll(0, distanceY.toInt(), null)
         }

         MODE_DOWN -> {
             beginScroll(0, distanceY.toInt(), null)
         }

         MODE_UP_AND_DOWN -> {
             beginScroll(0, distanceY.toInt(), null)
         }
     }
     return false
 }
 
3、手指离开屏幕判断是否解锁成功
  • 这个时候有两种情况,解锁成功或解锁失败,判断的条件可以使用屏幕高度的40%,或者屏幕宽度的40%,主要取决于你是要水平方向滑动解锁还是垂直方向滑动解锁。滑动解锁失败或成功都要重新滑动到指定的位置,屏幕的顶端,或者恢复到原来位置。滑动到指定位置代码如下:
    //滚动到目标位置
  private fun prepareScroll(fx: Int, fy: Int, duration: Int?) {
      val dx = fx - mScroller!!.finalX
      val dy = fy - mScroller!!.finalY
      beginScroll(dx, dy, duration)
      Loger.d("prepareScroll", " mScroller!!.finalX : ${mScroller!!.finalX} mScroller!!.finalY : ${mScroller!!.finalY}" )
  }
  • 当手指离开屏幕ACTION_UP时候调用,伪代码如下:
 MODE_UP_AND_DOWN -> {
                        //下滑
                        var height = mHeight
                        if (mCurrentOffsetY > 0) {
                            height = -mHeight
                        }

                        if ((abs(mCurrentOffsetY) >= mHeight * releasePercent)) {
                            prepareScroll(0, height, mAnimDuration.toInt())
                            isUnlock = true
                        } else {
                            prepareScroll(0, 0, mAnimDuration.toInt())
                            mDragListener?.onRestore()
                        }
                    }
                    
4、记得重写computeScroll方法让滑动生效
    override fun computeScroll() {
      super.computeScroll()
      if (mScroller != null) {
          if (mScroller!!.computeScrollOffset()) {
              scrollTo(mScroller!!.currX, mScroller!!.currY)
              postInvalidate()
          } else {
              if (isUnlock) {
                  mDragListener?.onRelease()
              }
          }
      }

  }
  

四、 Scroller滑动实现自定义View的所有代码如下:

package com.qimiaoptu.camera.lockscreen

import android.content.Context
import android.util.AttributeSet
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.widget.FrameLayout
import android.widget.Scroller
import com.qimiaoptu.camera.photostar.ShareUtils
import kotlin.math.abs


/**
 * ┌───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┐
 * │Esc│ │ F1│ F2│ F3│ F4│ │ F5│ F6│ F7│ F8│ │ F9│F10│F11│F12│ │P/S│S L│P/B│ ┌┐    ┌┐    ┌┐
 * └───┘ └───┴───┴───┴───┘ └───┴───┴───┴───┘ └───┴───┴───┴───┘ └───┴───┴───┘ └┘    └┘    └┘
 * ┌──┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───────┐┌───┬───┬───┐┌───┬───┬───┬───┐
 * │~`│! 1│@ 2│# 3│$ 4│% 5│^ 6│& 7│* 8│( 9│) 0│_ -│+ =│ BacSp ││Ins│Hom│PUp││N L│ / │ * │ - │
 * ├──┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─────┤├───┼───┼───┤├───┼───┼───┼───┤
 * │Tab │ Q │ W │ E │ R │ T │ Y │ U │ I │ O │ P │{ [│} ]│ | \ ││Del│End│PDn││ 7 │ 8 │ 9 │   │
 * ├────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤└───┴───┴───┘├───┼───┼───┤ + │
 * │Caps │ A │ S │ D │ F │ G │ H │ J │ K │ L │: ;│" '│ Enter  │             │ 4 │ 5 │ 6 │   │
 * ├─────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────────┤    ┌───┐    ├───┼───┼───┼───┤
 * │Shift  │ Z │ X │ C │ V │ B │ N │ M │< ,│> .│? /│  Shift   │    │ ↑ │    │ 1 │ 2 │ 3 │   │
 * ├────┬──┴─┬─┴──┬┴───┴───┴───┴───┴───┴──┬┴───┼───┴┬────┬────┤┌───┼───┼───┐├───┴───┼───┤ E││
 * │Ctrl│Ray │Alt │         Space         │ Alt│code│fuck│Ctrl││ ← │ ↓ │ → ││   0   │ . │←─┘│
 * └────┴────┴────┴───────────────────────┴────┴────┴────┴────┘└───┴───┴───┘└───────┴───┴───┘
 *
 * @author Rayhahah
 * @blog http://rayhahah.com
 * @time 2019/10/21
 * @tips 这个类是Object的子类
 * @fuction
 */

class DragView : FrameLayout, GestureDetector.OnGestureListener {

    companion object {
        /**
         * 向左滑动
         */
        const val MODE_LEFT = 0

        /**
         * 向右滑动
         */
        const val MODE_RIGHT = 1
        /**
         * 向上滑动
         */
        const val MODE_UP = 2
        /**
         * 向下滑动
         */
        const val MODE_DOWN = 3
        /**
         * 向上与向下同时兼容
         */
        const val MODE_UP_AND_DOWN = 4
    }

    private var mHeight: Int = 0
    private var mWidth: Int = 0
    private var mDownX: Float = 0f
    private var mDownY: Float = 0f
    private var mCurrentX: Float = 0f
    private var mCurrentY: Float = 0f

    private var mCurrentOffsetX: Float = 0f
    private var mCurrentOffsetY: Float = 0f

    private var clickOffSet = 30
    private var moveOffSet = 5

    private var mWrapView: View? = null

    private var mAnimDuration: Long = 500
    private var mDragListener: onDragListener? = null

    var releasePercent: Float = 0.4f
        set(value) {
            field = value
            requestLayout()
            invalidate()
        }

    var dragMode: Int = MODE_UP_AND_DOWN
        set(value) {
            field = value
            requestLayout()
            invalidate()
        }

    private var mScroller: Scroller? = null
    private var mGestureDetector: GestureDetector? = null
    private var isUnlock = false

    constructor(context: Context) : this(context, null)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
            context,
            attrs,
            defStyleAttr
    ) {
        mScroller = Scroller(context)
        // 初始化手势检测器
        mGestureDetector = GestureDetector(context, this)
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        mWidth = w
        mHeight = h
        mWrapView = getChildAt(0)
    }

    override fun onTouchEvent(ev: MotionEvent): Boolean {
        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                mDownX = ev.x
                mDownY = ev.y
                mCurrentX = 0f
                mCurrentY = 0f
                mCurrentOffsetX = 0f
                mCurrentOffsetY = 0f

                return mGestureDetector?.onTouchEvent(ev)!!
            }
            MotionEvent.ACTION_MOVE -> {
                mCurrentX = ev.x
                mCurrentY = ev.y
                if (isMoving()) {
                    return super.onTouchEvent(ev)
                }

                mCurrentOffsetX = mCurrentX - mDownX
                mCurrentOffsetY = mCurrentY - mDownY

                when (dragMode) {
                    MODE_LEFT -> {
                        if ((mCurrentOffsetX < 0)) {
                            if ((abs(mCurrentOffsetX) >= (abs(mCurrentOffsetY) * 2))) {
                                return mGestureDetector?.onTouchEvent(ev)!!
                            }
                        }
                    }
                    MODE_RIGHT -> {
                        if ((mCurrentOffsetX > 0)) {
                            if ((abs(mCurrentOffsetX) >= (abs(mCurrentOffsetY) * 2))) {
                                return mGestureDetector?.onTouchEvent(ev)!!
                            }
                        }
                    }
                    MODE_UP -> {
                        if ((mCurrentOffsetY < 0)) {
                            if ((abs(mCurrentOffsetY) >= (abs(mCurrentOffsetX) * 2))) {
                                return mGestureDetector?.onTouchEvent(ev)!!
                            }
                        }
                    }
                    MODE_DOWN -> {
                        if ((mCurrentOffsetY > 0)) {
                            if ((abs(mCurrentOffsetY) >= (abs(mCurrentOffsetX) * 2))) {
                                return mGestureDetector?.onTouchEvent(ev)!!
                            }
                        }
                    }
                    MODE_UP_AND_DOWN -> {
                        if ((abs(mCurrentOffsetY) >= (abs(mCurrentOffsetX) * 2))) {
                            return mGestureDetector?.onTouchEvent(ev)!!
                        }
                    }
                    else -> {
                    }
                }

                return super.onTouchEvent(ev)
            }
            MotionEvent.ACTION_UP -> {
                if (isSingleClick()) {
                    return super.onTouchEvent(ev)
                }

                mDownX = 0f
                mDownY = 0f
                mCurrentX = 0f
                mCurrentY = 0f
                when (dragMode) {
                    MODE_LEFT -> {
                        if ((mCurrentOffsetX < 0)) {
                            if ((abs(mCurrentOffsetX) >= mWidth * releasePercent)) {
                                prepareScroll(mWidth, 0, mAnimDuration.toInt())
                                isUnlock = true
                            } else {
                                prepareScroll(0, 0, mAnimDuration.toInt())
                                mDragListener?.onRestore()
                            }
                        }
                    }
                    MODE_RIGHT -> {
                        if ((mCurrentOffsetX > 0)) {
                            if ((abs(mCurrentOffsetX) >= mWidth * releasePercent)) {
                                prepareScroll(mWidth, 0, mAnimDuration.toInt())
                                isUnlock = true
                            } else {
                                prepareScroll(0, 0, mAnimDuration.toInt())
                                mDragListener?.onRestore()
                            }
                        }
                    }
                    MODE_UP -> {
                        if ((mCurrentOffsetY < 0)) {
                            if ((abs(mCurrentOffsetY) >= mHeight * releasePercent)) {
                                prepareScroll(0, mHeight, mAnimDuration.toInt())
                                isUnlock = true
                            } else {
                                prepareScroll(0, 0, mAnimDuration.toInt())
                                mDragListener?.onRestore()
                            }
                        }
                    }
                    MODE_DOWN -> {
                        if ((mCurrentOffsetY > 0)) {
                            if ((abs(mCurrentOffsetY) >= mHeight * releasePercent)) {
                                prepareScroll(0, mHeight, mAnimDuration.toInt())
                                isUnlock = true
                            } else {
                                prepareScroll(0, 0, mAnimDuration.toInt())
                                mDragListener?.onRestore()
                            }
                        }
                    }
                    MODE_UP_AND_DOWN -> {
                        //下滑
                        var height = mHeight
                        if (mCurrentOffsetY > 0) {
                            height = -mHeight
                        }

                        if ((abs(mCurrentOffsetY) >= mHeight * releasePercent)) {
                            prepareScroll(0, height, mAnimDuration.toInt())
                            isUnlock = true
                        } else {
                            prepareScroll(0, 0, mAnimDuration.toInt())
                            mDragListener?.onRestore()
                        }
                    }
                    else -> {
                    }
                }


                return true
            }
            else -> {
                return super.onTouchEvent(ev)
            }
        }
    }


    private fun isSingleClick(): Boolean {
        if (mCurrentX <= mDownX + clickOffSet && mCurrentX >= mDownX - clickOffSet
                && mCurrentY <= mDownY + clickOffSet && mCurrentY >= mDownY - clickOffSet
        ) {
            return true
        }
        return false
    }

    private fun isMoving(): Boolean {
        if (mCurrentX <= mDownX + moveOffSet && mCurrentX >= mDownX - moveOffSet
                && mCurrentY <= mDownY + moveOffSet && mCurrentY >= mDownY - moveOffSet
        ) {
            return true
        }
        return false
    }

    fun wrap(view: View) {
        mWrapView = view
        removeAllViews()

        addView(
                mWrapView,
                LayoutParams(
                        LayoutParams.MATCH_PARENT,
                        LayoutParams.MATCH_PARENT
                )
        )
    }

    fun setListener(listener: onDragListener) {
        mDragListener = listener
    }

    public interface onDragListener {
        fun onRelease()
        fun onRestore()
    }

    private var mShareUtils: ShareUtils? = null
    public fun setShareUtils(shareUtils: ShareUtils?) {
        mShareUtils = shareUtils
    }

    private var mLastXIntercept = 0f
    private var mLastYIntercept = 0f
    override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
        var intercepted = false
        var x = event.x
        var y = event.y
        when (event?.action) {
            MotionEvent.ACTION_DOWN -> {
                intercepted = false                              //注解1
                mDownX = event.x
                mDownY = event.y
                mCurrentX = 0f
                mCurrentY = 0f
                mCurrentOffsetX = 0f
                mCurrentOffsetY = 0f
                mGestureDetector?.onTouchEvent(event)!!
            }

            MotionEvent.ACTION_MOVE -> {
                var deltaX = x - mLastXIntercept;
                var deltaY = y - mLastYIntercept;
                intercepted = if (null != mShareUtils && mShareUtils!!.isVisible) {
                    false
                } else {
                    abs(deltaX) < abs(deltaY) && (abs(deltaX) > 5 || abs(deltaY) > 5)
                }
            }

            MotionEvent.ACTION_UP -> {
                intercepted = false
            }

        }
        mLastXIntercept = x
        mLastYIntercept = y

        return intercepted
    }

    override fun computeScroll() {
        super.computeScroll()
        if (mScroller != null) {
            if (mScroller!!.computeScrollOffset()) {
                scrollTo(mScroller!!.currX, mScroller!!.currY)
                postInvalidate()
            } else {
                if (isUnlock) {
                    mDragListener?.onRelease()
                }
            }
        }

    }

    //滚动到目标位置
    private fun prepareScroll(fx: Int, fy: Int, duration: Int?) {
        val dx = fx - mScroller!!.finalX
        val dy = fy - mScroller!!.finalY
        beginScroll(dx, dy, duration)
    }


    //设置滚动的相对偏移
    private fun beginScroll(dx: Int, dy: Int, duration: Int?) { //第一,二个参数起始位置;第三,四个滚动的偏移量
        if (duration != null) {
            mScroller!!.startScroll(mScroller!!.finalX, mScroller!!.finalY, dx, dy, duration)
        } else {
            mScroller!!.startScroll(mScroller!!.finalX, mScroller!!.finalY, dx, dy)
        }
        //必须执行invalidate()从而调用computeScroll()
        invalidate()
    }

    override fun onShowPress(e: MotionEvent?) {
    }

    override fun onSingleTapUp(e: MotionEvent?): Boolean {
        return false
    }

    override fun onDown(e: MotionEvent?): Boolean {
        return true
    }

    override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean {
        return false
    }

    //控制拉动幅度:
    //int disY=(int)((distanceY - 0.5)/2);
    //亦可直接调用:
    //smoothScrollBy(0, (int)distanceY);
    // val disY = ((distanceY - 0.5) * 0.8).toInt()
    override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean {
        when (dragMode) {
            MODE_LEFT -> {
                beginScroll(distanceX.toInt(), 0, null)
            }

            MODE_RIGHT -> {
                beginScroll(distanceX.toInt(), 0, null)
            }

            MODE_UP -> {
                beginScroll(0, distanceY.toInt(), null)
            }

            MODE_DOWN -> {
                beginScroll(0, distanceY.toInt(), null)
            }

            MODE_UP_AND_DOWN -> {
                beginScroll(0, distanceY.toInt(), null)
            }
        }
        return false
    }

    override fun onLongPress(e: MotionEvent?) {
    }

}

五、 ObjectAnimator滑动实现自定义View的所有代码如下:

package com.qimiaoptu.camera.lockscreen

import android.animation.Animator
import android.animation.ValueAnimator
import android.content.Context
import android.util.AttributeSet
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.view.animation.LinearInterpolator
import android.widget.FrameLayout
import com.qimiaoptu.camera.log.Loger
import com.qimiaoptu.camera.photostar.ShareUtils
import kotlin.math.abs

/**
 * ┌───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┐
 * │Esc│ │ F1│ F2│ F3│ F4│ │ F5│ F6│ F7│ F8│ │ F9│F10│F11│F12│ │P/S│S L│P/B│ ┌┐    ┌┐    ┌┐
 * └───┘ └───┴───┴───┴───┘ └───┴───┴───┴───┘ └───┴───┴───┴───┘ └───┴───┴───┘ └┘    └┘    └┘
 * ┌──┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───────┐┌───┬───┬───┐┌───┬───┬───┬───┐
 * │~`│! 1│@ 2│# 3│$ 4│% 5│^ 6│& 7│* 8│( 9│) 0│_ -│+ =│ BacSp ││Ins│Hom│PUp││N L│ / │ * │ - │
 * ├──┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─────┤├───┼───┼───┤├───┼───┼───┼───┤
 * │Tab │ Q │ W │ E │ R │ T │ Y │ U │ I │ O │ P │{ [│} ]│ | \ ││Del│End│PDn││ 7 │ 8 │ 9 │   │
 * ├────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤└───┴───┴───┘├───┼───┼───┤ + │
 * │Caps │ A │ S │ D │ F │ G │ H │ J │ K │ L │: ;│" '│ Enter  │             │ 4 │ 5 │ 6 │   │
 * ├─────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────────┤    ┌───┐    ├───┼───┼───┼───┤
 * │Shift  │ Z │ X │ C │ V │ B │ N │ M │< ,│> .│? /│  Shift   │    │ ↑ │    │ 1 │ 2 │ 3 │   │
 * ├────┬──┴─┬─┴──┬┴───┴───┴───┴───┴───┴──┬┴───┼───┴┬────┬────┤┌───┼───┼───┐├───┴───┼───┤ E││
 * │Ctrl│Ray │Alt │         Space         │ Alt│code│fuck│Ctrl││ ← │ ↓ │ → ││   0   │ . │←─┘│
 * └────┴────┴────┴───────────────────────┴────┴────┴────┴────┘└───┴───┴───┘└───────┴───┴───┘
 *
 * @author Rayhahah
 * @blog http://rayhahah.com
 * @time 2019/10/21
 * @tips 这个类是Object的子类
 * @fuction
 */

class DragView : FrameLayout {

    companion object {
        /**
         * 向左滑动
         */
        const val MODE_LEFT = 0

        /**
         * 向右滑动
         */
        const val MODE_RIGHT = 1
        /**
         * 向上滑动
         */
        const val MODE_UP = 2
        /**
         * 向下滑动
         */
        const val MODE_DOWN = 3
        /**
         * 向上与向下同时兼容
         */
        const val  MODE_UP_AND_DOWN = 4
    }

    private var mValueListener: Animator.AnimatorListener? = null
    private var mHeight: Int = 0
    private var mWidth: Int = 0
    private var mDownX: Float = 0f
    private var mDownY: Float = 0f
    private var mCurrentX: Float = 0f
    private var mCurrentY: Float = 0f

    private var mCurrentOffsetX: Float = 0f
    private var mCurrentOffsetY: Float = 0f

    private var clickOffSet = 30
    private var moveOffSet = 5

    private var mWrapView: View? = null

    private var mAnimDuration: Long = 500
    private var mValueAnimator: ValueAnimator? = null
    private var mDragListener: onDragListener? = null

    var releasePercent: Float = 0.4f
        set(value) {
            field = value
            requestLayout()
            invalidate()
        }

    var dragMode: Int = MODE_UP_AND_DOWN
        set(value) {
            field = value
            requestLayout()
            invalidate()
        }

    constructor(context: Context) : this(context, null)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
            context,
            attrs,
            defStyleAttr
    ) {
        mValueAnimator = ValueAnimator.ofFloat(0f, 1f)
        mValueAnimator?.apply {
            duration = mAnimDuration
            repeatCount = 0
            repeatMode = ValueAnimator.RESTART
            interpolator = LinearInterpolator()
            mValueListener = object : Animator.AnimatorListener {
                override fun onAnimationRepeat(animation: Animator?) {
                }

                override fun onAnimationEnd(animation: Animator?) {
                    when (dragMode) {
                        MODE_LEFT -> {
                            if ((Math.abs(mCurrentOffsetX) >= mWidth * releasePercent)) {
                                mWrapView?.apply {
                                    visibility = View.VISIBLE
                                    alpha = 0f
                                    translationX = 0f
                                }
                                mDragListener?.onRelease()
                            } else {
                                mDragListener?.onRestore()
                            }
                        }
                        MODE_RIGHT -> {
                            if ((Math.abs(mCurrentOffsetX) >= mWidth * releasePercent)) {
                                mWrapView?.apply {
                                    visibility = View.VISIBLE
                                    alpha = 0f
                                    translationX = 0f
                                }
                                mDragListener?.onRelease()
                            } else {
                                mDragListener?.onRestore()
                            }
                        }
                        MODE_UP -> {
                            if ((Math.abs(mCurrentOffsetY) >= mHeight * releasePercent)) {
                                mWrapView?.apply {
                                    visibility = View.VISIBLE
                                    alpha = 0f
                                    translationY = 0f
                                }
                                mDragListener?.onRelease()
                            } else {
                                mDragListener?.onRestore()
                            }
                        }
                        MODE_DOWN -> {
                            if ((Math.abs(mCurrentOffsetX) >= mWidth * releasePercent)) {
                                mWrapView?.apply {
                                    visibility = View.VISIBLE
                                    alpha = 0f
                                    translationY = 0f
                                }
                                mDragListener?.onRelease()
                            } else {
                                mDragListener?.onRestore()
                            }
                        }
                        MODE_UP_AND_DOWN -> {
                            //MODE_UP
                            if ((mCurrentOffsetY < 0)) {
                                if ((Math.abs(mCurrentOffsetY) >= mHeight * releasePercent)) {
                                    mWrapView?.apply {
                                        visibility = View.VISIBLE
                                        alpha = 0f
                                        translationY = 0f
                                    }
                                    mDragListener?.onRelease()
                                } else {
                                    mDragListener?.onRestore()
                                }
                            } else {//MODE_DOWN
                                if ((Math.abs(mCurrentOffsetY) >= mHeight * releasePercent)) {
                                    mWrapView?.apply {
                                        visibility = View.VISIBLE
                                        alpha = 0f
                                        translationY = 0f
                                    }
                                    mDragListener?.onRelease()
                                } else {
                                    mDragListener?.onRestore()
                                }
                            }
                        }
                        else -> {
                        }
                    }
                }

                override fun onAnimationCancel(animation: Animator?) {
                }

                override fun onAnimationStart(animation: Animator?) {
                }
            }

            addUpdateListener {
                val progress = it.animatedValue as Float
                when (dragMode) {
                    MODE_LEFT -> {
                        if ((Math.abs(mCurrentOffsetX) >= mWidth * releasePercent)) {
                            mWrapView?.apply {
                                val targetOffsetX = progress * (-mWidth - mCurrentOffsetX)
                                translationX = mCurrentOffsetX + targetOffsetX
                            }
                        } else {
                            mWrapView?.apply {
                                val targetOffsetX = progress * (-mCurrentOffsetX)
                                translationX = mCurrentOffsetX + targetOffsetX
                            }
                        }
                    }
                    MODE_RIGHT -> {
                        if ((Math.abs(mCurrentOffsetX) >= mWidth * releasePercent)) {
                            mWrapView?.apply {
                                val targetOffsetX = progress * (mWidth - mCurrentOffsetX)
                                translationX = mCurrentOffsetX + targetOffsetX
                            }
                        } else {
                            mWrapView?.apply {
                                val targetOffsetX = progress * (-mCurrentOffsetX)
                                translationX = mCurrentOffsetX + targetOffsetX
                            }
                        }
                    }
                    MODE_UP -> {
                        if ((Math.abs(mCurrentOffsetY) >= mHeight * releasePercent)) {
                            mWrapView?.apply {
                                val targetOffsetY = progress * (-mHeight - mCurrentOffsetY)
                                translationY = mCurrentOffsetY + targetOffsetY
                            }
                        } else {
                            mWrapView?.apply {
                                val targetOffsetY = progress * (-mCurrentOffsetY)
                                translationY = mCurrentOffsetY + targetOffsetY
                            }
                        }
                    }
                    MODE_DOWN -> {
                        if ((Math.abs(mCurrentOffsetY) >= mHeight * releasePercent)) {
                            mWrapView?.apply {
                                val targetOffsetY = progress * (mHeight - mCurrentOffsetY)
                                translationY = mCurrentOffsetY + targetOffsetY
                            }
                        } else {
                            mWrapView?.apply {
                                val targetOffsetY = progress * (-mCurrentOffsetY)
                                translationY = mCurrentOffsetY + targetOffsetY
                            }
                        }
                    }
                    MODE_UP_AND_DOWN -> {
                        //MODE_UP
                        if ((mCurrentOffsetY < 0)) {
                            if ((Math.abs(mCurrentOffsetY) >= mHeight * releasePercent)) {
                                mWrapView?.apply {
                                    val targetOffsetY = progress * (-mHeight - mCurrentOffsetY)
                                    translationY = mCurrentOffsetY + targetOffsetY
                                }
                            } else {
                                mWrapView?.apply {
                                    val targetOffsetY = progress * (-mCurrentOffsetY)
                                    translationY = mCurrentOffsetY + targetOffsetY
                                }
                            }
                        } else {//MODE_DOWN
                            if ((Math.abs(mCurrentOffsetY) >= mHeight * releasePercent)) {
                                mWrapView?.apply {
                                    val targetOffsetY = progress * (mHeight - mCurrentOffsetY)
                                    translationY = mCurrentOffsetY + targetOffsetY
                                }
                            } else {
                                mWrapView?.apply {
                                    val targetOffsetY = progress * (-mCurrentOffsetY)
                                    translationY = mCurrentOffsetY + targetOffsetY
                                }
                            }
                        }
                    }
                    else -> {
                    }
                }
            }

            addListener(mValueListener)
        }
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        mWidth = w
        mHeight = h
        mWrapView = getChildAt(0)
    }

    override fun onTouchEvent(ev: MotionEvent): Boolean {
        Loger.e("xxxxxxxxxxxxx", " onTouchEvent")
        if (null != mGestureDetector) {
//            mGestureDetector.onTouchEvent(ev)
        }
        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                if (mValueAnimator?.isRunning == true) {
                    return super.onTouchEvent(ev)
                }

                mDownX = ev.x
                mDownY = ev.y
                mCurrentX = 0f
                mCurrentY = 0f
                mCurrentOffsetX = 0f
                mCurrentOffsetY = 0f

                return true
            }
            MotionEvent.ACTION_MOVE -> {
                mCurrentX = ev.x
                mCurrentY = ev.y

                if (isMoving()) {
                    return super.onTouchEvent(ev)
                }

                mCurrentOffsetX = mCurrentX - mDownX
                mCurrentOffsetY = mCurrentY - mDownY

                when (dragMode) {
                    MODE_LEFT -> {
                        if ((mCurrentOffsetX < 0)) {
                            if ((Math.abs(mCurrentOffsetX) >= (Math.abs(mCurrentOffsetY) * 2))) {
                                mWrapView?.let {
                                    it.translationX = mCurrentOffsetX
                                }
                            }
                        }
                    }
                    MODE_RIGHT -> {
                        if ((mCurrentOffsetX > 0)) {
                            if ((Math.abs(mCurrentOffsetX) >= (Math.abs(mCurrentOffsetY) * 2))) {
                                mWrapView?.let {
                                    it.translationX = mCurrentOffsetX
                                }
                            }
                        }
                    }
                    MODE_UP -> {
                        if ((mCurrentOffsetY < 0)) {
                            if ((Math.abs(mCurrentOffsetY) >= (Math.abs(mCurrentOffsetX) * 2))) {
                                mWrapView?.let {
                                    it.translationY = mCurrentOffsetY
                                }
                            }
                        }
                    }
                    MODE_DOWN -> {
                        if ((mCurrentOffsetY > 0)) {
                            if ((Math.abs(mCurrentOffsetY) >= (Math.abs(mCurrentOffsetX) * 2))) {
                                mWrapView?.let {
                                    it.translationY = mCurrentOffsetY
                                }
                            }
                        }
                    }
                    MODE_UP_AND_DOWN -> {
                        //MODE_UP
                        if ((mCurrentOffsetY < 0)) {
                            if ((Math.abs(mCurrentOffsetY) >= (Math.abs(mCurrentOffsetX) * 2))) {
                                mWrapView?.let {
                                    it.translationY = mCurrentOffsetY
                                }
                            }
                        } else {//MODE_DOWN
                            if ((Math.abs(mCurrentOffsetY) >= (Math.abs(mCurrentOffsetX) * 2))) {
                                mWrapView?.let {
                                    it.translationY = mCurrentOffsetY
                                }
                            }
                        }
                    }
                    else -> {
                    }
                }

                return super.onTouchEvent(ev)
            }
            MotionEvent.ACTION_UP -> {
                if (isSingleClick()) {
                    return super.onTouchEvent(ev)
                }

                mDownX = 0f
                mDownY = 0f
                mCurrentX = 0f
                mCurrentY = 0f
                when (dragMode) {
                    MODE_LEFT -> {
                        if ((mCurrentOffsetX < 0)) {
                            mValueAnimator?.start()
                        }
                    }
                    MODE_RIGHT -> {
                        if ((mCurrentOffsetX > 0)) {
                            mValueAnimator?.start()
                        }
                    }
                    MODE_UP -> {
                        if ((mCurrentOffsetY < 0)) {
                            mValueAnimator?.start()
                        }
                    }
                    MODE_DOWN -> {
                        if ((mCurrentOffsetY > 0)) {
                            mValueAnimator?.start()
                        }
                    }
                    MODE_UP_AND_DOWN -> {
                        if ((mCurrentOffsetY < 0)) {
                            mValueAnimator?.start()
                        } else{
                            mValueAnimator?.start()
                        }
                    }
                    else -> {
                    }
                }


                return true
            }
            else -> {
                return super.onTouchEvent(ev)
            }
        }
    }


    private fun isSingleClick(): Boolean {
        if (mCurrentX <= mDownX + clickOffSet && mCurrentX >= mDownX - clickOffSet
                && mCurrentY <= mDownY + clickOffSet && mCurrentY >= mDownY - clickOffSet
        ) {
            return true
        }
        return false
    }

    private fun isMoving(): Boolean {
        if (mCurrentX <= mDownX + moveOffSet && mCurrentX >= mDownX - moveOffSet
                && mCurrentY <= mDownY + moveOffSet && mCurrentY >= mDownY - moveOffSet
        ) {
            return true
        }
        return false
    }

    fun wrap(view: View) {
        mWrapView = view
        removeAllViews()

        addView(
                mWrapView,
                LayoutParams(
                        LayoutParams.MATCH_PARENT,
                        LayoutParams.MATCH_PARENT
                )
        )
    }

    fun setListener(listener: onDragListener) {
        mDragListener = listener
    }

    public interface onDragListener {
        fun onRelease()
        fun onRestore()
    }

    private lateinit var mGestureDetector: GestureDetector

    public fun setGestureDetector(gestureDetector: GestureDetector) {
        mGestureDetector = gestureDetector
    }

    private var mShareUtils: ShareUtils? = null
    public fun setShareUtils(shareUtils: ShareUtils?) {
        mShareUtils = shareUtils
    }

    private var mLastXIntercept = 0f
    private var mLastYIntercept = 0f
    override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
        var intercepted = false
        var x = event.x
        var y = event.y
        when (event?.action) {
            MotionEvent.ACTION_DOWN -> {
                intercepted = false                              //注解1
//                if (isMoving()) {                       //注解2
//                    intercepted = true
//                }
                if (mValueAnimator?.isRunning == true) {
                    return super.onInterceptTouchEvent(event)
                }

                mDownX = event.x
                mDownY = event.y
                mCurrentX = 0f
                mCurrentY = 0f
                mCurrentOffsetX = 0f
                mCurrentOffsetY = 0f
            }

            MotionEvent.ACTION_MOVE -> {
                var deltaX = x - mLastXIntercept;
                var deltaY = y - mLastYIntercept;
                intercepted = if(null != mShareUtils && mShareUtils!!.isVisible) {
                    false
                } else {
                    abs(deltaX) < abs(deltaY) && (abs(deltaX) > 5 || abs(deltaY) > 5)
                }
            }

            MotionEvent.ACTION_UP -> {
                intercepted = false
            }

        }
        mLastXIntercept = x
        mLastYIntercept = y

        return intercepted
    }
}

六、 总结

  • 锁屏自定义View的实现方式肯定还有很多方式,不过我只是尝试了ObjectAnimator和Scroller这个两种实现方式,这两种方式比较推荐Scroller实现,毕竟ObjectAnimator实现会存在部分机型动画卡顿,比如oppo,miui等。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值