我在Android13上遇到了一个问题,如图,从解锁图标处上滑无法完成解锁。
修改效果:以解锁图标为起点上滑可以解锁,按下解锁图标抬起解锁
修改后的代码:
frameworks/base/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) {
// if (!onInterceptTouchEvent(event)) {
// cancelTouches();
// return false;
// }
mOnGestureDetectedRunnable = onGestureDetectedRunnable;
switch(event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_HOVER_ENTER:
if (!mDownDetected && mAccessibilityManager.isTouchExplorationEnabled()) {
mVibrator.vibrate(
Process.myUid(),
getContext().getOpPackageName(),
UdfpsController.EFFECT_CLICK,
"lock-icon-down",
TOUCH_VIBRATION_ATTRIBUTES);
}
// The pointer that causes ACTION_DOWN is always at index 0.
// We need to persist its ID to track it during ACTION_MOVE that could include
// data for many other pointers because of multi-touch support.
// mActivePointerId = event.getPointerId(0);
// if (mVelocityTracker == null) {
// // To simplify the lifecycle of the velocity tracker, make sure it's never null
// // after ACTION_DOWN, and always null after ACTION_CANCEL or ACTION_UP.
// mVelocityTracker = VelocityTracker.obtain();
// } else {
// // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new
// // ACTION_DOWN, in that case we should just reuse the old instance.
// mVelocityTracker.clear();
// }
// mVelocityTracker.addMovement(event);
mDownDetected = true;
// mLongPressCancelRunnable = mExecutor.executeDelayed(
// this::onLongPress, LONG_PRESS_TIMEOUT);
break;
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_HOVER_MOVE:
if(!inLockIconArea(event)){
mDownDetected = false;
}
// mVelocityTracker.addMovement(event);
// // Compute pointer velocity in pixels per second.
// mVelocityTracker.computeCurrentVelocity(1000);
// float velocity = UdfpsController.computePointerSpeed(mVelocityTracker,
// mActivePointerId);
// if (event.getClassification() != MotionEvent.CLASSIFICATION_DEEP_PRESS
// && UdfpsController.exceedsVelocityThreshold(velocity)) {
// Log.v(TAG, "lock icon long-press rescheduled due to "
// + "high pointer velocity=" + velocity);
// mLongPressCancelRunnable.run();
// mLongPressCancelRunnable = mExecutor.executeDelayed(
// this::onLongPress, LONG_PRESS_TIMEOUT);
// }
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_HOVER_EXIT:
// cancelTouches();
//add
if(inLockIconArea(event) && mDownDetected){
onLongPress();
}
//add end
break;
}
return true;
}
/**
* Intercepts the touch if the onDown event and current event are within this lock icon view's
* bounds.
*/
public boolean onInterceptTouchEvent(MotionEvent event) {
// if (!inLockIconArea(event) || !isActionable()) {
// return false;
// }
// if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
// return true;
// }
// return mDownDetected;
return false;
}
解决过程,感兴趣的可以看下:
Android Studio下载SDK里面有自带的布局查看工具uiautomatorviewer,通过这个工具抓取截图,从resource-id这一栏的内容"com.android.systemui:id/lock_icon"得知,我们要找的控件位于systemui,id是lock_icon。
在SystemUI模块中全局搜索lock_icon,发现在LockIconView中有使用到此id。
src/com/android/keyguard/LockIconView.java
src/com/android/keyguard/LockIconViewController.java
LockIconView --> LockIconViewController
LockIconViewController的onTouchEvent方法中实现了锁屏图标的触摸逻辑
public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) {
//---------------------------------------------------------------->(1)
if (!onInterceptTouchEvent(event)) {
cancelTouches();
return false;
}
//---------------------------------------------------------------->(1)
mOnGestureDetectedRunnable = onGestureDetectedRunnable;
switch(event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_HOVER_ENTER:
//---------------------------------------------------------------->(2)
//一般情况下不走这里
if (!mDownDetected && mAccessibilityManager.isTouchExplorationEnabled()) {
mVibrator.vibrate(
Process.myUid(),
getContext().getOpPackageName(),
UdfpsController.EFFECT_CLICK,
"lock-icon-down",
TOUCH_VIBRATION_ATTRIBUTES);
}
//---------------------------------------------------------------->(2)
// The pointer that causes ACTION_DOWN is always at index 0.
// We need to persist its ID to track it during ACTION_MOVE that could include
// data for many other pointers because of multi-touch support.
//---------------------------------------------------------------->(3)
//初始化mVelocityTracker,用于在(5)中计算触摸移动速度
mActivePointerId = event.getPointerId(0);
if (mVelocityTracker == null) {
// To simplify the lifecycle of the velocity tracker, make sure it's never null
// after ACTION_DOWN, and always null after ACTION_CANCEL or ACTION_UP.
mVelocityTracker = VelocityTracker.obtain();
} else {
// ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new
// ACTION_DOWN, in that case we should just reuse the old instance.
mVelocityTracker.clear();
}
mVelocityTracker.addMovement(event);
//---------------------------------------------------------------->(3)
//---------------------------------------------------------------->(4)
mDownDetected = true;
//LONG_PRESS_TIMEOUT是0.2s,延迟0.2s执行长按事件
mLongPressCancelRunnable = mExecutor.executeDelayed(
this::onLongPress, LONG_PRESS_TIMEOUT);
//---------------------------------------------------------------->(4)
break;
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_HOVER_MOVE:
//---------------------------------------------------------------->(5)
mVelocityTracker.addMovement(event);
// Compute pointer velocity in pixels per second.
mVelocityTracker.computeCurrentVelocity(1000);
float velocity = UdfpsController.computePointerSpeed(mVelocityTracker,
mActivePointerId);
//判断触摸移动速度是否足够快
if (event.getClassification() != MotionEvent.CLASSIFICATION_DEEP_PRESS
&& UdfpsController.exceedsVelocityThreshold(velocity)) {
Log.v(TAG, "lock icon long-press rescheduled due to "
+ "high pointer velocity=" + velocity);
//-------------------------------------------------->(5.1)
//取消ACTION_DOWN中的延迟0.2s触发长按事件
mLongPressCancelRunnable.run();
//再次开启延迟0.2s触发长按事件
mLongPressCancelRunnable = mExecutor.executeDelayed(
this::onLongPress, LONG_PRESS_TIMEOUT);
//-------------------------------------------------->(5.1)
}
//---------------------------------------------------------------->(5)
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_HOVER_EXIT:
cancelTouches();
break;
}
return true;
}
要想修改此处的触摸逻辑,我们就必须先了解它原本的逻辑是怎样的。
当onInterceptTouchEvent为false的时候,就返回false。
//---------------------------------------------------------------->(1)
if (!onInterceptTouchEvent(event)) {
cancelTouches();
return false;
}
//---------------------------------------------------------------->(1)
onInterceptTouchEvent当触摸区域不在解锁图标的范围内的时候,返回false。
这个很好理解,如果没有按在解锁图标上,就返回false不触发长按解锁按钮解锁的逻辑。
mDownDetected负责在ACTION_MOVE事件下移出解锁图标的边界的情况下拦截触摸事件,不让它继续分发,会造成以解锁图标为起点上滑无法解锁。
public boolean onInterceptTouchEvent(MotionEvent event) {
if (!inLockIconArea(event) || !isActionable()) {
return false;
}
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
return true;
}
return mDownDetected;
}
(2)处的代码忽略掉,一般情况下不走这里
//---------------------------------------------------------------->(2)
if (!mDownDetected && mAccessibilityManager.isTouchExplorationEnabled()) {
mVibrator.vibrate(
Process.myUid(),
getContext().getOpPackageName(),
UdfpsController.EFFECT_CLICK,
"lock-icon-down",
TOUCH_VIBRATION_ATTRIBUTES);
}
//---------------------------------------------------------------->(2)
(3)处的代码用于初始化VelocityTracker,这个类在(5)处用于计算触摸移动速度
//---------------------------------------------------------------->(3)
mActivePointerId = event.getPointerId(0);
if (mVelocityTracker == null) {
// To simplify the lifecycle of the velocity tracker, make sure it's never null
// after ACTION_DOWN, and always null after ACTION_CANCEL or ACTION_UP.
mVelocityTracker = VelocityTracker.obtain();
} else {
// ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new
// ACTION_DOWN, in that case we should just reuse the old instance.
mVelocityTracker.clear();
}
mVelocityTracker.addMovement(event);
//---------------------------------------------------------------->(3)
(4) 用于延迟0.2s调用onLongPress
//---------------------------------------------------------------->(4)
mDownDetected = true;
//计时两秒触发长按事件
mLongPressCancelRunnable = mExecutor.executeDelayed(
this::onLongPress, LONG_PRESS_TIMEOUT);
//---------------------------------------------------------------->(4)
(5) 首先,触摸点一定要在解锁图标上,否则在onTouchEvent的开头就返回了。
这里是在解锁图标的范围内快速移动的时候,0.2s触发长按事件会不停地刷新,重新计算时间
//---------------------------------------------------------------->(5)
mVelocityTracker.addMovement(event);
// Compute pointer velocity in pixels per second.
mVelocityTracker.computeCurrentVelocity(1000);
float velocity = UdfpsController.computePointerSpeed(mVelocityTracker,
mActivePointerId);
//判断触摸移动速度是否足够快
if (event.getClassification() != MotionEvent.CLASSIFICATION_DEEP_PRESS
&& UdfpsController.exceedsVelocityThreshold(velocity)) {
Log.v(TAG, "lock icon long-press rescheduled due to "
+ "high pointer velocity=" + velocity);
//-------------------------------------------------->(5.1)
//取消ACTION_DOWN中的延迟0.2s触发长按事件
mLongPressCancelRunnable.run();
//再次开启延迟0.2s触发长按事件
mLongPressCancelRunnable = mExecutor.executeDelayed(
this::onLongPress, LONG_PRESS_TIMEOUT);
//-------------------------------------------------->(5.1)
}
//---------------------------------------------------------------->(5)
cancelTouches()在onTouchEvent的开头和ACTION_UP的时候用到
private void cancelTouches() {
mDownDetected = false;
if (mLongPressCancelRunnable != null) {
//取消长按事件
mLongPressCancelRunnable.run();
}
if (mVelocityTracker != null) {
//释放用于计算触摸速度的对象
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
总:解锁图标的触摸逻辑
(1)按在图标外面,onInterceptTouchEvent返回false,onTouchEvent返回false,不拦截触摸事件,可以正常上滑解锁。
(2)按在图标区域0.2s且移动速度小于阈值,解锁。
(3)按在图标区域,快速移动大于阈值,直到移动速度小于阈值0.2s,解锁。
(4)按在图标区域,0.2s内快速滑出图标区域,onInterceptTouchEvent返回false,onTouchEvent返回true,触摸事件无法继续分发,此时上滑无法解锁。