android手势分析(应用界面左往右边滑动退出应用)
其中,move状态下的 handleMoveEvent()是主要的处理逻辑:判断 x 轴的 offset 数值是否达到了阈值 mSwipeThreshold,从而 回调 BackCallback 事件 和当前视图的更新,可看出 NavigationBarEdgePanel 就是一个 自定义view,根据 控制器 传递过来的 MotionEvent 实现具体的UI 效果,并回传事件。备注:在PIXE
Android系统启动篇
4,《Android SystemServer进程启动流程》
Android系统开发准备篇
3,《Android Framework代码IDE加载和调试》
Android系统开发实践篇
4,《android单独编译framework模块并push》
Android系统开发核心知识储备篇
1,《Android编译系统-envsetup和lunch代码篇》
6,《Android中Activity、View和Window关系详解》
11,《android中AMS进程通知Zygote进程fork新进程的通信方式》
Android核心功能详解篇
2,《Android 手势导航(从下往上滑动进入多任务页面)》
3,《android手势分析(应用界面左往右边滑动退出应用)》
———————————————————————————————————————————
一、如何找到入口
Android10推出了全新的手势导航功能,原生的Android系统就提供了此功能,根据这个切入点查询相关实现,Android 10和11的源码里面,在SystemUI 模块里面可以找到对应的关键代码类如下:
SystemUI\src\com\android\systemui\statusbar\phone\EdgeBackGestureHandler.java SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarEdgePanel.java
EdgeBackGestureHandler.java,这个类是整个返回手势的核心管理类,EdgeBackGestureHandler 类的初始化,以及调用过程,放在 SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarView.java 中实现的,具体逻辑如下:
// SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarView.java
public NavigationBarView(Context context, AttributeSet attrs) {
super(context, attrs);
... ...
// 构造方法中实例化EdgeBackGestureHandler对象
mEdgeBackGestureHandler = new EdgeBackGestureHandler(context, mOverviewProxyService,
mSysUiFlagContainer, mPluginManager, this::updateStates);
}
@Override
public void onNavigationModeChanged(int mode) {
... ...
// 系统导航模式发生变化时回调 (全屏手势导航/按键导航)
mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode);
... ...
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
... ...
// 当NavigationBarView 回调onAttachedToWindow() 时,回调onNavBarAttached(),保持add
// 到window 的时机一致
mEdgeBackGestureHandler.onNavBarAttached();
... ...
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
... ...
// 移除
mEdgeBackGestureHandler.onNavBarDetached();
... ...
}
EdgeBackGestureHandler内部关键代码,注册input事件监听器,实例化NavigationBarEdgePanel
// SystemUI\src\com\android\systemui\statusbar\phone\EdgeBackGestureHandler.java
// 定义一个 input 事件 Reciever
class SysUiInputEventReceiver extends InputEventReceiver {
SysUiInputEventReceiver(InputChannel channel, Looper looper) {
super(channel, looper);
}
public void onInputEvent(InputEvent event) {
EdgeBackGestureHandler.this.onInputEvent(event);
finishInputEvent(event, true);
}
}
// 更新返回手势控制器的状态
// 此方法调用时机:
// 1、EdgeBackGestureHandler.onUserSwitched()
// 2、EdgeBackGestureHandler.onNavBarAttached()
// 3、EdgeBackGestureHandler.onNavBarDetached()
// 4、EdgeBackGestureHandler.onNavigationModeChanged()
private void updateIsEnabled() {
boolean isEnabled = mIsAttached && mIsGesturalModeEnabled;
... ...
if (!mIsEnabled) {
... ...
// Register input event receiver
// 通过 InputMonitor 实现全局手势监听
mInputMonitor = InputManager.getInstance().monitorGestureInput(
"edge-swipe", mDisplayId);
mInputEventReceiver = new SysUiInputEventReceiver(
mInputMonitor.getInputChannel(), Looper.getMainLooper());
// Add a nav bar panel window
// 添加 NavigationBarEdgePanel
setEdgeBackPlugin(new NavigationBarEdgePanel(mContext));
... ...
}
}
// NavigationBarEdgePanel extends NavigationEdgeBackPlugin
private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) {
if (mEdgeBackPlugin != null) {
mEdgeBackPlugin.onDestroy();
}
mEdgeBackPlugin = edgeBackPlugin;
// 添加回调,NavigationBarEdgePanel 与 当前类通信
mEdgeBackPlugin.setBackCallback(mBackCallback);
// 创建NavigationBarEdgePanel 参数,并未显示
mEdgeBackPlugin.setLayoutParams(createLayoutParams());
... ...
}
二、手势导航(返回)流程整理
- 在NavigationBarView 构造方法中 初始化 EdgeBackGestureHandler 对象 ,并且在在 NavigationBarView 的 onNavigationModeChanged()、onAttachedToWindow() 、onDetachedFromWindow() 中调用 EdgeBackGestureHandler 的对应方法,调用之后,最后会走到 EdgeBackGestureHandler 的 updateIsEnabled() 方法;
- EdgeBackGestureHandler. updateIsEnabled() 方法中实例化 实例化InputMonitor 对象,并且注册 InputEventReceive监听事件,实现input 事件监听,同时 初始化 NavigationBarEdgePanel ,添加到windwow TIPS:此时NavigationBarEdgePanel 还是不显示的状态
- 监听InputEventReceiver.onInputEvent() 方法回调,实现输入事件处理逻辑。
三、输入事件核心处理逻辑
在 InputEventReceiver.onInputEvent() 中,进入到自定义的处理逻辑中:
private void onMotionEvent(MotionEvent ev) {
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
// Verify if this is in within the touch region and we aren't in immersive mode, and
// either the bouncer is showing or the notification panel is hidden
// 判断是否是左边滑动
mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset;
mMLResults = 0;
mLogGesture = false;
mInRejectedExclusion = false;
// 看是否开启了此功能,并且判断是否在排除区域
mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed
&& !mGestureBlockingActivityRunning
&& !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
&& isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
// 把 MotionEvent 传递给 NavigationBarEdgePanel 处理
if (mAllowGesture) {
mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge);
mEdgeBackPlugin.onMotionEvent(ev);
}
... ...
} else if (mAllowGesture || mLogGesture) {
if (!mThresholdCrossed) {
mEndPoint.x = (int) ev.getX();
mEndPoint.y = (int) ev.getY();
// 多点触碰的情况,直接取消当前 input事件
if (action == MotionEvent.ACTION_POINTER_DOWN) {
if (mAllowGesture) {
logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_MULTI_TOUCH);
// We do not support multi touch for back gesture
cancelGesture(ev);
}
mLogGesture = false;
return;
} else if (action == MotionEvent.ACTION_MOVE) {
// 筛选不合格的其他 输入事件
if ((ev.getEventTime() - ev.getDownTime()) > mLongPressTimeout) {
if (mAllowGesture) {
logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_LONG_PRESS);
cancelGesture(ev);
}
mLogGesture = false;
return;
}
float dx = Math.abs(ev.getX() - mDownPoint.x);
float dy = Math.abs(ev.getY() - mDownPoint.y);
if (dy > dx && dy > mTouchSlop) {
if (mAllowGesture) {
logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_VERTICAL_MOVE);
cancelGesture(ev);
}
mLogGesture = false;
return;
} else if (dx > dy && dx > mTouchSlop) {
if (mAllowGesture) {
mThresholdCrossed = true;
// Capture inputs
// 捕获当前 手势,防止干扰界面
mInputMonitor.pilferPointers();
} else {
logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_FAR_FROM_EDGE);
}
}
}
}
if (mAllowGesture) {
// forward touch
mEdgeBackPlugin.onMotionEvent(ev);
}
}
... ...
}
// frameworks/base/core/java/android/view/InputMonitor.java
/**
* Takes all of the current pointer events streams that are currently being sent to this
* monitor and generates appropriate cancellations for the windows that would normally get
* them.
*
* This method should be used with caution as unexpected pilfering can break fundamental user
* interactions.
*/
public void pilferPointers() {
try {
mHost.pilferPointers();
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
可看出 NavigationBarEdgePanel 就是一个 自定义view,根据 控制器 传递过来的 MotionEvent 实现具体的UI 效果,并回传事件。
@Override
public void onMotionEvent(MotionEvent event) {
// MotionEvent 预处理逻辑
... ...
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
... ...
setVisibility(VISIBLE);
// 记录dwon初始坐标点信息
... ...
break;
case MotionEvent.ACTION_MOVE:
handleMoveEvent(event);
break;
case MotionEvent.ACTION_UP:
// 手势抬起,回调
if (mTriggerBack) {
triggerBack();
} else {
cancelBack();
}
... ...
break;
case MotionEvent.ACTION_CANCEL:
cancelBack();
... ...
break;
default:
break;
}
}
其中,move状态下的 handleMoveEvent()是主要的处理逻辑:判断 x 轴的 offset 数值是否达到了阈值 mSwipeThreshold,从而 回调 BackCallback 事件 和当前视图的更新,
private void handleMoveEvent(@NonNull MotionEvent event) {
float x = event.getX();
float y = event.getY();
... ...
// Apply a haptic on drag slop passed
// 已经超过阈值的话
// 设置达到触发返回事件条件
if (!mDragSlopPassed && touchTranslation > mSwipeThreshold) {
mDragSlopPassed = true;
mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
mVibrationTime = SystemClock.uptimeMillis();
// Let's show the arrow and animate it in!
mDisappearAmount = 0.0f;
setAlpha(1f);
// And animate it go to back by default!
setTriggerBack(true /* triggerBack */, true /* animated */);
}
// Let's make sure we only go to the baseextend and apply rubberbanding afterwards
// 控制绘制和动画的参数赋值
... ...
// By default we just assume the current direction is kept
boolean triggerBack = mTriggerBack;
// First lets see if we had continuous motion in one direction for a while
if (Math.abs(mTotalTouchDelta) > mMinDeltaForSwitch) {
triggerBack = mTotalTouchDelta > 0;
}
// 计算方向和偏移值
// Then, let's see if our velocity tells us to change direction
mVelocityTracker.computeCurrentVelocity(1000);
float xVelocity = mVelocityTracker.getXVelocity();
float yVelocity = mVelocityTracker.getYVelocity();
float velocity = MathUtils.mag(xVelocity, yVelocity);
mAngleOffset = Math.min(velocity / 1000 * ARROW_ANGLE_ADDED_PER_1000_SPEED,
ARROW_MAX_ANGLE_SPEED_OFFSET_DEGREES) * Math.signum(xVelocity);
if (mIsLeftPanel && mArrowsPointLeft || !mIsLeftPanel && !mArrowsPointLeft) {
mAngleOffset *= -1;
}
// 如果纵向偏移值达到了横向偏移两倍 取消返回事件触发
// Last if the direction in Y is bigger than X * 2 we also abort
if (Math.abs(yOffset) > Math.abs(x - mStartX) * 2) {
triggerBack = false;
}
setTriggerBack(triggerBack, true /* animated */);
... ...
}
手势处理结果
// NavigationBarEdgePanel.java
private void triggerBack() {
// 事件回调到 EdgeBackGestureHandler 进行处理,触发返回事件
mBackCallback.triggerBack();
// 产生 click 振动
if (isSlow
|| SystemClock.uptimeMillis() - mVibrationTime >= GESTURE_DURATION_FOR_CLICK_MS) {
mVibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK);
}
...
// 隐藏动画的执行
Runnable translationEnd = () -> {
mAngleOffset = Math.max(0, mAngleOffset + 8);
updateAngle(true /* animated */);
mTranslationAnimation.setSpring(mTriggerBackSpring);
setDesiredTranslation(mDesiredTranslation - dp(32), true /* animated */);
// 隐藏视图
animate().alpha(0f).setDuration(DISAPPEAR_FADE_ANIMATION_DURATION_MS)
.withEndAction(() -> setVisibility(GONE));
mArrowDisappearAnimation.start();
scheduleFailsafe();
};
...
}
// 取消事件
private void cancelBack() {
mBackCallback.cancelBack();
if (mTranslationAnimation.isRunning()) {
mTranslationAnimation.addEndListener(mSetGoneEndListener);
} else {
setVisibility(GONE);
}
}
再看下EdgeBackGestureHandler中的 事件处理:
private final NavigationEdgeBackPlugin.BackCallback mBackCallback =
new NavigationEdgeBackPlugin.BackCallback() {
@Override
public void triggerBack() {
sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
// mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x,
// (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
Log.d(TAG, "triggerBack: ");
}
@Override
public void cancelBack() {
Log.d(TAG, "cancelBack: ");
// mOverviewProxyService.notifyBackAction(false, (int) mDownPoint.x,
// (int) mDownPoint.y, false isButton , !mIsOnLeftEdge);
}
};
private void sendEvent(int action, int code) {
long when = SystemClock.uptimeMillis();
final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
InputDevice.SOURCE_KEYBOARD);
... ...
// 用 InputManager 注入返回事件
InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
}
备注:在PIXEL手机手势设置页面,关闭gesture navigation会回调
public void onNavigationModeChanged(int mode) {
mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mode);
updateIsEnabled();
updateCurrentUserResources();
}
最终会回调解除touch事件监听:
private void disposeInputChannel() {
if (mInputEventReceiver != null) {
mInputEventReceiver.dispose();
mInputEventReceiver = null;
}
if (mInputMonitor != null) {
mInputMonitor.dispose();
mInputMonitor = null;
}
}
更多推荐
所有评论(0)