android P/Q/R/S 9/10/11/12多任务手势动画OverviewInputConsumer情况-第二节
hi,多任务手势分析了OtherActivity的情况,这一节来分析一下在桌面本身就是前台情况下,进入多任务的源码及情况分析。 首先来看看原生aosp上多任务的2个过程:
重点现象部分:[
[入门课,实战课,跨进程专题,input专题](https://ke.qq.com/course/package/51285?tuin=7d4eb354) ps需要学习深入framework课程和课程优惠 新课程优惠获取请加入qq群:422901085(获取demo源码)
1、手指慢慢滑动,workspace整体也跟着慢慢滑动
这个过程就是我们还处于手指底部上划过程,这个还是用个自己绘制的图好展示一些: 即手指底部上划过程中会有workspace上整体也会慢慢上划
2、上划到一定临界值时候,直接有个进入多任务的动画过程
这个过程相对比较简单好理解,就一个多任务界面不需要额外补充图片解释了
重点源码分析部分:
1、workspace慢慢上划过程
首先滑动其实全局的触摸监听器监听了触摸事件,这个情况下是OverviewInputConsumer来进行触摸监听的 具体路径在Launcher代码的如下类中: com/android/quickstep/inputconsumers/OverviewInputConsumer.java
public OverviewInputConsumer(T activity, @Nullable InputMonitorCompat inputMonitor,
boolean startingInActivityBounds) {
mActivity = activity;
mInputMonitor = inputMonitor;
mStartingInActivityBounds = startingInActivityBounds;
mTarget = activity.getDragLayer();
if (startingInActivityBounds) {
mEventReceiver = mTarget::dispatchTouchEvent;
mProxyTouch = true;
} else {
mEventReceiver = mTarget::proxyTouchEvent;
mTarget.getLocationOnScreen(mLocationOnScreen);
mProxyTouch = mTarget.prepareProxyEventStarting();
}
}
@Override
public int getType() {
return TYPE_OVERVIEW;
}
@Override
public boolean allowInterceptByParent() {
return !mTargetHandledTouch;
}
@Override
public void onMotionEvent(MotionEvent ev) {
if (!mProxyTouch) {
return;
}
int flags = ev.getEdgeFlags();
if (!mStartingInActivityBounds) {
ev.setEdgeFlags(flags | Utilities.EDGE_NAV_BAR);
}
ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
boolean handled = mEventReceiver.test(ev);
ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]);
ev.setEdgeFlags(flags);
}
}
这里其实onMotionEvent方法被调用后,其实调用的是proxyTouchEvent方法来处理 packages/apps/Trebuchet/src/com/android/launcher3/views/BaseDragLayer.java
public boolean proxyTouchEvent(MotionEvent ev) {
boolean handled;
if (mProxyTouchController != null) {
handled = mProxyTouchController.onControllerTouchEvent(ev);
} else {
mProxyTouchController = findControllerToHandleTouch(ev);
handled = mProxyTouchController != null;
}
int action = ev.getAction();
if (action == ACTION_UP || action == ACTION_CANCEL) {
mProxyTouchController = null;
mTouchDispatchState &= ~TOUCH_DISPATCHING_PROXY;
}
return handled;
}
private TouchController findControllerToHandleTouch(MotionEvent ev) {
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
return topView;
}
for (TouchController controller : mControllers) {
if (controller.onControllerInterceptTouchEvent(ev)) {
return controller;
}
}
return null;
}
这里我们mControllers实际是在 packages/apps/Trebuchet/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
public static TouchController[] createTouchControllers(Launcher launcher) {
ArrayList<TouchController> list = new ArrayList<>();
list.add(launcher.getDragController());
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
list.add(new LandscapeStatesTouchController(launcher));
list.add(new LandscapeEdgeSwipeController(launcher));
} else {
boolean allowDragToOverview = SysUINavigationMode.INSTANCE.get(launcher)
.getMode().hasGestures;
list.add(new PortraitStatesTouchController(launcher, allowDragToOverview));
}
if (FeatureFlags.PULL_DOWN_STATUS_BAR && Utilities.IS_DEBUG_DEVICE
&& !launcher.getDeviceProfile().isMultiWindowMode
&& !launcher.getDeviceProfile().isVerticalBarLayout()) {
list.add(new StatusBarTouchController(launcher));
}
return list.toArray(new TouchController[list.size()]);
}
这里可以看出我们其实会有PortraitStatesTouchController,所以考虑进入它的处理,但是PortraitStatesTouchController本身没有,但是AbstractStateChangeTouchController有处理: packages/apps/Trebuchet/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@Override
public final boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mNoIntercept = !canInterceptTouch(ev);
if (mNoIntercept) {
return false;
}
final int directionsToDetectScroll;
boolean ignoreSlopWhenSettling = false;
if (mCurrentAnimation != null) {
directionsToDetectScroll = SingleAxisSwipeDetector.DIRECTION_BOTH;
ignoreSlopWhenSettling = true;
} else {
directionsToDetectScroll = getSwipeDirection();
if (directionsToDetectScroll == 0) {
mNoIntercept = true;
return false;
}
}
mDetector.setDetectableScrollConditions(
directionsToDetectScroll, ignoreSlopWhenSettling);
}
if (mNoIntercept) {
return false;
}
onControllerTouchEvent(ev);
return mDetector.isDraggingOrSettling();
}
@Override
public final boolean onControllerTouchEvent(MotionEvent ev) {
return mDetector.onTouchEvent(ev);
}
最后处理会调用到mDetector.onTouchEvent,这里mDetector就是BaseSwipeDetector: com/android/launcher3/touch/BaseSwipeDetector.java
这里面的BaseSwipeDetector就是真正触摸事件逻辑重点
public boolean onTouchEvent(MotionEvent ev) {
int actionMasked = ev.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN && mVelocityTracker != null) {
mVelocityTracker.clear();
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
switch (actionMasked) {
case MotionEvent.ACTION_DOWN:
mActivePointerId = ev.getPointerId(0);
mDownPos.set(ev.getX(), ev.getY());
mLastPos.set(mDownPos);
mLastDisplacement.set(0, 0);
mDisplacement.set(0, 0);
if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
setState(ScrollState.DRAGGING);
}
break;
case MotionEvent.ACTION_MOVE:
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == INVALID_POINTER_ID) {
break;
}
mDisplacement.set(ev.getX(pointerIndex) - mDownPos.x,
ev.getY(pointerIndex) - mDownPos.y);
if (mIsRtl) {
mDisplacement.x = -mDisplacement.x;
}
if (mState != ScrollState.DRAGGING && shouldScrollStart(mDisplacement)) {
setState(ScrollState.DRAGGING);
}
if (mState == ScrollState.DRAGGING) {
reportDragging(ev);
}
mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (mState == ScrollState.DRAGGING) {
setState(ScrollState.SETTLING);
}
mVelocityTracker.recycle();
mVelocityTracker = null;
break;
default:
break;
}
return true;
}
这里来看看重点方法setState:
private void setState(ScrollState newState) {
if (newState == ScrollState.DRAGGING) {
initializeDragging();
if (mState == ScrollState.IDLE) {
reportDragStart(false );
} else if (mState == ScrollState.SETTLING) {
reportDragStart(true );
}
}
if (newState == ScrollState.SETTLING) {
reportDragEnd();
}
mState = newState;
}
private void reportDragStart(boolean recatch) {
reportDragStartInternal(recatch);
}
reportDragStartInternal实际是一个子类SingleAxisSwipeDetector实现的方法: com/android/launcher3/touch/SingleAxisSwipeDetector.java
@Override
protected void reportDragStartInternal(boolean recatch) {
mListener.onDragStart(!recatch);
}
这里有调到了 com/android/launcher3/touch/AbstractStateChangeTouchController.java
@Override
public void onDragStart(boolean start) {
mStartState = mLauncher.getStateManager().getState();
mIsLogContainerSet = false;
if (mCurrentAnimation == null) {
mFromState = mStartState;
mToState = null;
cancelAnimationControllers();
reinitCurrentAnimation(false, mDetector.wasInitialTouchPositive());
mDisplacementShift = 0;
} else {
mCurrentAnimation.pause();
mStartProgress = mCurrentAnimation.getProgressFraction();
mAtomicAnimAutoPlayInfo = null;
if (mAtomicComponentsController != null) {
mAtomicComponentsController.pause();
}
}
mCanBlockFling = mFromState == NORMAL;
mFlingBlockCheck.unblockFling();
}
上面已经分析完成了DragStart情况,那么手指滑动过程中呢? 那就又要回到BaseSwipeDetector的onTouchEvent中的reportDragging
if (mState == ScrollState.DRAGGING) {
reportDragging(ev);
}
这里来看
private void reportDragging(MotionEvent event) {
if (mDisplacement != mLastDisplacement) {
if (DBG) {
Log.d(TAG, String.format("onDrag disp=%s", mDisplacement));
}
mLastDisplacement.set(mDisplacement);
sTempPoint.set(mDisplacement.x - mSubtractDisplacement.x,
mDisplacement.y - mSubtractDisplacement.y);
reportDraggingInternal(sTempPoint, event);
}
}
这里有回到了 reportDraggingInternal(sTempPoint, event)方法又是调用SingleAxisSwipeDetector的 reportDraggingInternal:
@Override
protected void reportDraggingInternal(PointF displacement, MotionEvent event) {
mListener.onDrag(mDir.extractDirection(displacement), event);
}
这里又调用到com/android/launcher3/touch/AbstractStateChangeTouchController.java
@Override
public boolean onDrag(float displacement, MotionEvent ev) {
if (!mIsLogContainerSet) {
if (mStartState == ALL_APPS) {
mStartContainerType = LauncherLogProto.ContainerType.ALLAPPS;
} else if (mStartState == NORMAL) {
mStartContainerType = getLogContainerTypeForNormalState(ev);
} else if (mStartState == OVERVIEW) {
mStartContainerType = LauncherLogProto.ContainerType.TASKSWITCHER;
}
mIsLogContainerSet = true;
}
return onDrag(displacement);
}
@Override
public boolean onDrag(float displacement) {
float deltaProgress = mProgressMultiplier * (displacement - mDisplacementShift);
float progress = deltaProgress + mStartProgress;
updateProgress(progress);
boolean isDragTowardPositive = mSwipeDirection.isPositive(
displacement - mDisplacementShift);
if (progress <= 0) {
if (reinitCurrentAnimation(false, isDragTowardPositive)) {
mDisplacementShift = displacement;
if (mCanBlockFling) {
mFlingBlockCheck.blockFling();
}
}
} else if (progress >= 1) {
if (reinitCurrentAnimation(true, isDragTowardPositive)) {
mDisplacementShift = displacement;
if (mCanBlockFling) {
mFlingBlockCheck.blockFling();
}
}
} else {
mFlingBlockCheck.onEvent();
}
return true;
}
protected void updateProgress(float fraction) {
mCurrentAnimation.setPlayFraction(fraction);
if (mAtomicComponentsController != null) {
float start = Math.min(mAtomicComponentsStartProgress, 0.9f);
mAtomicComponentsController.setPlayFraction((fraction - start) / (1 - start));
}
maybeUpdateAtomicAnim(mFromState, mToState, fraction);
}
这里的mCurrentAnimation.setPlayFraction最后会调用到AnimatorPlaybackController的setPlayFraction方法 com/android/launcher3/anim/AnimatorPlaybackController.java
@Override
public void setPlayFraction(float fraction) {
mCurrentFraction = fraction;
if (mTargetCancelled) {
return;
}
long playPos = clampDuration(fraction);
for (ValueAnimator anim : mChildAnimations) {
anim.setCurrentPlayTime(Math.min(playPos, anim.getDuration()));
}
}
这里面就会对这个mChildAnimations动画集合由前面onDragStart里面reinitCurrentAnimation进行设置的,集合里面包含了若干个一起动画,其中包含Workspace的移动动画,这里会进行遍历,然后设置动画时间就可以完成对WorkSpace。。 是不是感觉动很妙,如果我们写代码是不是肯定这个时候直接调用ui来设置View的一些属性
2、progress到的阈值后启动进入多任务的动画 之前分析updateProgress还有一个maybeUpdateAtomicAnim方法
private void maybeUpdateAtomicAnim(LauncherState fromState, LauncherState toState,
float progress) {
if (!goingBetweenNormalAndOverview(fromState, toState)) {
return;
}
float threshold = toState == OVERVIEW ? ATOMIC_OVERVIEW_ANIM_THRESHOLD
: 1f - ATOMIC_OVERVIEW_ANIM_THRESHOLD;
boolean passedThreshold = progress >= threshold;
if (passedThreshold != mPassedOverviewAtomicThreshold) {
LauncherState atomicFromState = passedThreshold ? fromState: toState;
LauncherState atomicToState = passedThreshold ? toState : fromState;
mPassedOverviewAtomicThreshold = passedThreshold;
if (mAtomicAnim != null) {
mAtomicAnim.cancel();
}
mAtomicAnim = createAtomicAnimForState(atomicFromState, atomicToState, ATOMIC_DURATION);
mAtomicAnim.start();
mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
}
}
|