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