一、简介
- 在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等。