Android 12平台上现在支持单手模式了,此功能也是第一次出现在AOSP的源码中,也是Gg仿 IOS 15 单手模式,来追一追,看看其工作原理。
1.设置应用功能开关界面
com.android.settings.gestures.OneHandedEnablePreferenceController.java
com.android.settings.gestures.OneHandedMainSwitchPreferenceController.java
com.android.settings.gestures.OneHandedSettingsUtils.java
由以上三个文件可以看到:
(1)单手模式的系统开关为:ro.support_one_handed_mode
(2)单手模式显示在系统手势界面
(3)单手模式开关逻辑代码为:
/**
* Sets one-handed mode enable or disable flag to Settings provider.
*
* @param context App context
* @param enable enable or disable one-handed mode.
*/
public static void setOneHandedModeEnabled(Context context, boolean enable) {
Settings.Secure.putIntForUser(context.getContentResolver(),
Settings.Secure.ONE_HANDED_MODE_ENABLED, enable ? ON : OFF, sCurrentUserId);
}
2.Launcher3 单手模式手势启动过程
com.android.quickstep.inputconsumers.OneHandedModeInputConsumer.java
com.android.quickstep.SystemUiProxy.java
OneHandedModeInputConsumer类是手势导航处理的类,对于此类的功能描述如下:
* Touch consumer for handling gesture event to launch one handed
* One handed gestural in quickstep only active on NO_BUTTON, TWO_BUTTONS, and portrait mode
* 触摸消费者处理手势事件以启动单手模式
* Quickstep 中的单手手势处理仅在 NO_BUTTON、TWO_BUTTONS 和手机纵向模式下有效。
开启单手模式功能,需要从屏幕底部边缘向下滑动可以开启手势导航,其启动逻辑为:
SystemUiProxy.INSTANCE.get(mContext).startOneHandedMode();
public void startOneHandedMode() {
if (mOneHanded != null) {
try {
mOneHanded.startOneHanded();
} catch (RemoteException e) {
Log.w(TAG, "Failed call startOneHandedMode", e);
}
}
}
3.framework单手模式服务处理过程
(1)单手模式推出进入控制器
com.android.wm.shell.onehanded.IOneHanded.aidl
com.android.wm.shell.onehanded.OneHandedController.java
com.android.wm.shell.onehanded.OneHandedDisplayAreaOrganizer.java
com.android.wm.shell.onehanded.OneHandedAnimationController.java
com.android.wm.shell.onehanded.OneHandedTouchHandler.java
com.android.wm.shell.onehanded.OneHandedSurfaceTransactionHelper.java
@VisibleForTesting
void startOneHanded() {
if (isLockedDisabled() || mKeyguardShowing) {
Slog.d(TAG, "Temporary lock disabled");
return;
}
if (!mDisplayAreaOrganizer.isReady()) {
// Must wait until DisplayAreaOrganizer is ready for transitioning.
mMainExecutor.executeDelayed(this::startOneHanded, DISPLAY_AREA_READY_RETRY_MS);
return;
}
if (mState.isTransitioning() || mState.isInOneHanded()) {
return;
}
final int currentRotation = mDisplayAreaOrganizer.getDisplayLayout().rotation();
if (currentRotation != Surface.ROTATION_0 && currentRotation != Surface.ROTATION_180) {
Slog.w(TAG, "One handed mode only support portrait mode");
return;
}
mState.setState(STATE_ENTERING);
final int yOffSet = Math.round(
mDisplayAreaOrganizer.getDisplayLayout().height() * mOffSetFraction);
mOneHandedAccessibilityUtil.announcementForScreenReader(
mOneHandedAccessibilityUtil.getOneHandedStartDescription());
mBackgroundPanelOrganizer.onStart();
mDisplayAreaOrganizer.scheduleOffset(0, yOffSet);
mTimeoutHandler.resetTimer();
mOneHandedUiEventLogger.writeEvent(
OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_GESTURE_IN);
}
由以上可以看到,设备锁定/锁屏/横屏是不允许进入单手模式的。进入单手模式时屏幕会有Y轴方向的偏移,偏移百分比为mOffSetFraction,mOffSetFraction的值获取是从 R.fraction.config_one_handed_offset获取的:
frameworks/base/libs/WindowManager/Shell/res/values/config.xml
<fraction name="config_one_handed_offset">40%</fraction>
接着上面说,单手模式会对的显示做偏移:
/**
* Offset the windows by a given offset on Y-axis, triggered also from screen rotation.
* Directly perform manipulation/offset on the leash.
*/
public void scheduleOffset(int xOffset, int yOffset) {
final float fromPos = mLastVisualOffset;
final int direction = yOffset > 0
? TRANSITION_DIRECTION_TRIGGER
: TRANSITION_DIRECTION_EXIT;
mDisplayAreaTokenMap.forEach(
(token, leash) -> {
animateWindows(token, leash, fromPos, yOffset, direction,
mEnterExitAnimationDurationMs);
});
mLastVisualOffset = yOffset;
}
private void animateWindows(WindowContainerToken token, SurfaceControl leash, float fromPos,
float toPos, @OneHandedAnimationController.TransitionDirection int direction,
int durationMs) {
final OneHandedAnimationController.OneHandedTransitionAnimator animator =
mAnimationController.getAnimator(token, leash, fromPos, toPos,
mLastVisualDisplayBounds);
if (animator != null) {
animator.setTransitionDirection(direction)
.addOneHandedAnimationCallback(mOneHandedAnimationCallback)
.addOneHandedAnimationCallback(mTutorialHandler)
.addOneHandedAnimationCallback(mBackgroundPanelOrganizer)
.setDuration(durationMs)
.start();
}
}
OneHandedAnimationController处理过程中会有动画处理,而主要针对SurfaceControl做处理:
@Override
void applySurfaceControlTransaction(SurfaceControl leash,
SurfaceControl.Transaction tx, float fraction) {
final float start = getStartValue();
final float end = getEndValue();
final float currentValue = getCastedFractionValue(start, end, fraction);
mTmpRect.set(
mTmpRect.left,
mTmpRect.top + Math.round(currentValue),
mTmpRect.right,
mTmpRect.bottom + Math.round(currentValue));
setCurrentValue(currentValue);
getSurfaceTransactionHelper()
.crop(tx, leash, mTmpRect)
.round(tx, leash)
.translate(tx, leash, currentValue);
tx.apply();
}
getSurfaceTransactionHelper返回的是OneHandedSurfaceTransactionHelper,其整合了一些对SurfaceControl的方法:
/**
* Operates the translation (setPosition) on a given transaction and leash
*
* @return same {@link OneHandedSurfaceTransactionHelper} instance for method chaining
*/
OneHandedSurfaceTransactionHelper translate(SurfaceControl.Transaction tx, SurfaceControl leash,
float offset) {
tx.setPosition(leash, 0, offset);
return this;
}
/**
* Operates the crop (setMatrix) on a given transaction and leash
*
* @return same {@link OneHandedSurfaceTransactionHelper} instance for method chaining
*/
OneHandedSurfaceTransactionHelper crop(SurfaceControl.Transaction tx, SurfaceControl leash,
Rect destinationBounds) {
tx.setWindowCrop(leash, destinationBounds.width(), destinationBounds.height());
return this;
}
/**
* Operates the round corner radius on a given transaction and leash
*
* @return same {@link OneHandedSurfaceTransactionHelper} instance for method chaining
*/
OneHandedSurfaceTransactionHelper round(SurfaceControl.Transaction tx, SurfaceControl leash) {
if (mEnableCornerRadius) {
tx.setCornerRadius(leash, mCornerRadius);
}
return this;
}
setWindowCrop设置裁切大小,setCornerRadius设置圆角,setPosition设置位置。
(2)单手模式触摸事件管理
com.android.wm.shell.onehanded.OneHandedTouchHandler
当触摸非应用界面的位置时,回调OneHandedController.stopOneHanded,推出单手模式:
private boolean onMotionEvent(MotionEvent ev) {
mIsInOutsideRegion = isWithinTouchOutsideRegion(ev.getX(), ev.getY());
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE: {
if (!mIsInOutsideRegion) {
mTimeoutHandler.resetTimer();
}
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
mTimeoutHandler.resetTimer();
if (mIsInOutsideRegion && !mIsOnStopTransitioning) {
mTouchEventCallback.onStop();
mIsOnStopTransitioning = true;
}
// Reset flag for next operation
mIsInOutsideRegion = false;
break;
}
}
return true;
}