Launcher3向上滑动显示应用列表分析
Android7.0的代码开始,Launcher3可以在Hotseat的区域通过向上滑动打开应用列表界面。本文我们就来详细分析下这个过程吧。一开始以为是Hotseat里面有onTouchEvent相关的逻辑处理,但是查看代码发现里面基本没有相关的事件处理逻辑,所以我们只能看下布局文件了,由事件分发的机制我们知道,如果子view不拦截处理事件,则它就会向下传递给父容器处理。Hotseat的父容器..
Android7.0的代码开始,Launcher3可以在Hotseat的区域通过向上滑动打开应用列表界面。本文我们就来详细分析下这个过程吧。
一开始以为是Hotseat里面有onTouchEvent相关的逻辑处理,但是查看代码发现里面基本没有相关的事件处理逻辑,所以我们只能看下布局文件了,由事件分发的机制我们知道,如果子view不拦截处理事件,则它就会向下传递给父容器处理。Hotseat的父容器就是DragLayer。DragLayer不仅有拖拽事件的处理,还把Hotseat的滑动事件传递给AllAppsTransitionController处理。
packages\apps\Launcher3\src\com\android\launcher3\dragndrop\DragLayer.java
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
...
if (mDragController.onInterceptTouchEvent(ev)) {
mActiveController = mDragController;
return true;
}
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && mAllAppsController.onInterceptTouchEvent(ev)) {
mActiveController = mAllAppsController;
return true;
}
...
return false;
}
DragLayer中先判断此事件是否被拖拽事件消费,如果没有再给AllAppsTransitionController处理。其中通过修改FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP,就可以定制化是否允许向上滑动显示应用列表界面。
packages\apps\Launcher3\src\com\android\launcher3\allapps\AllAppsTransitionController.java
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
...
if (mNoIntercept) {
return false;
}
mDetector.onTouchEvent(ev);
if (mDetector.isSettlingState() && (isInDisallowRecatchBottomZone() || isInDisallowRecatchTopZone())) {
return false;
}
return mDetector.isDraggingOrSettling();
}
函数一开始是一大段用来计算mNoIntercept变量的,当mNoIntercept为真就不拦截此事件,要滑动显示应用列表就必须拦截事件,所以会调VerticalPullDetector的onTouchEvent()方法。我们来看下VerticalPullDetector的主要代码:
packages\apps\Launcher3\src\com\android\launcher3\allapps\VerticalPullDetector.java
enum ScrollState {
IDLE,
DRAGGING, // onDragStart, onDrag
SETTLING // onDragEnd
}
...
interface Listener {
void onDragStart(boolean start);
boolean onDrag(float displacement, float velocity);
void onDragEnd(float velocity, boolean fling);
}
...
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = ev.getX();
mDownY = ev.getY();
mLastDisplacement = 0;
mDisplacementY = 0;
mVelocity = 0;
if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
setState(ScrollState.DRAGGING);
}
break;
case MotionEvent.ACTION_MOVE:
mDisplacementX = ev.getX() - mDownX;
mDisplacementY = ev.getY() - mDownY;
computeVelocity(ev);
Log.i(TAG,"moving mDisplacementY=" +mDisplacementY+",mDisplacementX=" +mDisplacementX);
// handle state and listener calls.
if (mState != ScrollState.DRAGGING && shouldScrollStart()) {
setState(ScrollState.DRAGGING);
}
if (mState == ScrollState.DRAGGING) {
reportDragging();
}
break;
case MotionEvent.ACTION_UP:
// These are synthetic events and there is no need to update internal values.
if (mState == ScrollState.DRAGGING) {
setState(ScrollState.SETTLING);
}
break;
...
}
// Do house keeping.
mLastDisplacement = mDisplacementY;
mLastY = ev.getY();
return true;
}
可以看到VerticalPullDetector类有一个接口,还有一个枚举变量,并且AllAppsTransitionController实现了VerticalPullDetector的Listener接口。接下来我们就来看下这些接口方法的调用和状态的管理过程。在MotionEvent.ACTION_DOWN处理过程中,先记录下初始位置(mDownX,mDownY),在MotionEvent.ACTION_MOVE中计算移动的距离mDisplacementX,mDisplacementY,计算出滑动速度,然后调用setState()。setState方法的作用一是设置新的状态,二是根据状态回调AllAppsTransitionController的相应方法。最后调用reportDragging()从而调用AllAppsTransitionController的onDrag()方法。
packages\apps\Launcher3\src\com\android\launcher3\allapps\AllAppsTransitionController.java
@Override
public void onDragStart(boolean start) {
mCaretController.onDragStart();
cancelAnimation();
mCurrentAnimation = LauncherAnimUtils.createAnimatorSet();
mShiftStart = mAppsView.getTranslationY();
preparePull(start);
}
可以看到onDragStart()是一些初始化的操作,重新创建了一个mCurrentAnimation对象。比较简单,主要的还是onDrag()方法里面调用的setProgress()方法。
packages\apps\Launcher3\src\com\android\launcher3\allapps\AllAppsTransitionController.java
/**
* @param progress value between 0 and 1, 0 shows all apps and 1 shows workspace
*/
public void setProgress(float progress) {
float shiftPrevious = mProgress * mShiftRange;
mProgress = progress;
float shiftCurrent = progress * mShiftRange;
float workspaceHotseatAlpha = Utilities.boundToRange(progress, 0f, 1f);
float alpha = 1 - workspaceHotseatAlpha;
float interpolation = mAccelInterpolator.getInterpolation(workspaceHotseatAlpha);
int color = (Integer) mEvaluator.evaluate(mDecelInterpolator.getInterpolation(alpha),
mHotseatBackgroundColor, mAllAppsBackgroundColor);
int bgAlpha = Color.alpha((int) mEvaluator.evaluate(alpha,
mHotseatBackgroundColor, mAllAppsBackgroundColor));
mAppsView.setRevealDrawableColor(ColorUtils.setAlphaComponent(color, bgAlpha));
mAppsView.getContentView().setAlpha(alpha);
mAppsView.setTranslationY(shiftCurrent);
if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
mWorkspace.setHotseatTranslationAndAlpha(Workspace.Direction.Y, -mShiftRange + shiftCurrent,
interpolation);
} else {
mWorkspace.setHotseatTranslationAndAlpha(Workspace.Direction.Y,
PARALLAX_COEFFICIENT * (-mShiftRange + shiftCurrent),
interpolation);
}
if (mIsTranslateWithoutWorkspace) {
return;
}
mWorkspace.setWorkspaceYTranslationAndAlpha(
PARALLAX_COEFFICIENT * (-mShiftRange + shiftCurrent), interpolation);
if (!mDetector.isDraggingState()) {
mContainerVelocity = mDetector.computeVelocity(shiftCurrent - shiftPrevious,
System.currentTimeMillis());
}
mCaretController.updateCaret(progress, mContainerVelocity, mDetector.isDraggingState());
updateLightStatusBar(shiftCurrent);
}
setProgress()方法还是比较好理解的,根据progress的值计算出相对应的颜色、透明度,偏移量,并且设置到相应的view上面。
最后,我们要分析的ACTION_UP事件了。在事件处理中调用setState(ScrollState.SETTLING),从而调用AllAppsTransitionController的onDragEnd()方法。
packages\apps\Launcher3\src\com\android\launcher3\allapps\AllAppsTransitionController.java
public void onDragEnd(float velocity, boolean fling) {
if (mAppsView == null) {
return; // early termination.
}
if (fling) {//是否是快速滑动事件
if (velocity < 0) { //表示向上滑动
calculateDuration(velocity, mAppsView.getTranslationY());
if (!mLauncher.isAllAppsVisible()) {
mLauncher.getUserEventDispatcher().logActionOnContainer(
LauncherLogProto.Action.FLING,
LauncherLogProto.Action.UP,
LauncherLogProto.HOTSEAT);
}
mLauncher.showAppsView(true /* animated */,
false /* updatePredictedApps */,
false /* focusSearchBar */);
} else {
calculateDuration(velocity, Math.abs(mShiftRange - mAppsView.getTranslationY()));
mLauncher.showWorkspace(true);
}
// snap to top or bottom using the release velocity
} else {
Log.i(TAG,"onDragEnd mShiftRange / 2 =" + mShiftRange / 2+",getTranslationY="+ mAppsView.getTranslationY());
if (mAppsView.getTranslationY() > mShiftRange / 2) { //说明在一半以下
calculateDuration(velocity, Math.abs(mShiftRange - mAppsView.getTranslationY()));
mLauncher.showWorkspace(true);
} else {
calculateDuration(velocity, Math.abs(mAppsView.getTranslationY()));
if (!mLauncher.isAllAppsVisible()) {
mLauncher.getUserEventDispatcher().logActionOnContainer(
LauncherLogProto.Action.SWIPE,
LauncherLogProto.Action.UP,
LauncherLogProto.HOTSEAT);
}
mLauncher.showAppsView(true, /* animated */
false /* updatePredictedApps */,
false /* focusSearchBar */);
}
}
}
函数一开始先判断是否是快速滑动事件,如果是就看是否已经处于应用列表界面,如果不是就调用Launcher的showAppsView()方法显示应用列表,否则调用showWorkspace()来显示workspace。其中calculateDuration()是根据当前距离计算出动画的执行时间mAnimationDuration。如果不是快速滑动事件则判断当前滑动的距离是否滑过了一半,如果是则触发相应逻辑,之后走的流程类似。我们以滑动打开应用列表来分析,滑动显示workspace走的类似的流程,还请大家自己分析。showAppsView()中会调用showAppsOrWidgets方法。
packages\apps\Launcher3\src\com\android\launcher3\Launcher.java
private boolean showAppsOrWidgets(State toState, boolean animated, boolean focusSearchBar) {
...
if (toState == State.APPS) {
mStateTransitionAnimation.startAnimationToAllApps(mWorkspace.getState(), animated,focusSearchBar);
} else {
mStateTransitionAnimation.startAnimationToWidgets(mWorkspace.getState(), animated);
}
...
}
packages\apps\Launcher3\src\com\android\launcher3\LauncherStateTransitionAnimation.java
public void startAnimationToAllApps(final Workspace.State fromWorkspaceState,
final boolean animated, final boolean startSearchAfterTransition) {
...
int animType = CIRCULAR_REVEAL;
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
animType = PULLUP;
}
// Only animate the search bar if animating from spring loaded mode back to all apps
startAnimationToOverlay(fromWorkspaceState,
Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, animType, cb);
}
很显然我们是到APPS界面,toState肯定等于State.APPS也就是会进入startAnimationToOverlay()逻辑。
packages\apps\Launcher3\src\com\android\launcher3\LauncherStateTransitionAnimation.java
private void startAnimationToOverlay(
final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
final View buttonView, final BaseContainerView toView,
final boolean animated, int animType, final PrivateTransitionCallbacks pCb) {
final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
...
final View contentView = toView.getContentView();
playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
animated, initialized, animation, layerViews);
...
if (animType == CIRCULAR_REVEAL) {
...
} else if (animType == PULLUP) {
...
boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide);
...
final AnimatorSet stateAnimation = animation;
final Runnable startAnimRunnable = new Runnable() {
public void run() {
// Check that mCurrentAnimation hasn't changed while
// we waited for a layout/draw pass
if (mCurrentAnimation != stateAnimation)
return;
dispatchOnLauncherTransitionStart(fromView, animated, false);
dispatchOnLauncherTransitionStart(toView, animated, false);
// Enable all necessary layers
for (View v : layerViews.keySet()) {
if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
v.buildLayer();
}
}
toView.requestFocus();
stateAnimation.start();
}
};
mCurrentAnimation = animation;
if (shouldPost) {
toView.post(startAnimRunnable);
} else {
startAnimRunnable.run();
}
}
}
总的来说就是创建了一个AnimatorSet,然后先播放State状态改变的动画,再调用mAllAppsController.animateToAllApps()播放workspace到列表界面的动画。这其中会调用AllAppsTransitionController的animateToAllApps方法,里面有动画结束的监听,当动画结束时会调用Detector的finishedScrolling()方法更改State状态为ScrollState.IDLE。
好了,整个过程我们就分析了一遍了。由于本人知识水平有限,难免有写的不对的地方,还请大家指正。
更多推荐
所有评论(0)