Launcher3 翻页动画详解与修改

Launcher3 翻页动画详解与修改

  一直对android桌面的翻页效果比较感兴趣,这两天有空所以去研究了下它的实现原理。
  Launcher3 的页面分为两块:工作区(workspace)和主菜单(APPS_CUSTOMIZE),这两块基本功能差不多,都是继承于PagedView,工作区的翻页效果是普通的平滑过渡,没什么特殊效果。主菜单页则采用了类似于扑克牌的层叠效果,本文重点研究其实现,最后顺便修改成其它效果。
                     

1,原理

  开始一直以为是由动画效果生成的,结果走了不少弯路,后来发现是Scroller滚动时不断利用重载dispatchDraw绘制子页生成的一个动画效果。关键代码如下:

    @Override
    protected void dispatchDraw(Canvas canvas) {
        int halfScreenSize = getViewportWidth() / 2; // 270
        // mOverScrollX is equal to getScrollX() when we're within the normal
        // scroll range.
        // Otherwise it is equal to the scaled overscroll position.
        int screenCenter = mOverScrollX + halfScreenSize;
        // 当位置为变化或强制滑动时
        if (screenCenter != mLastScreenCenter || mForceScreenScrolled) {
            // set mForceScreenScrolled before calling screenScrolled so that
            // screenScrolled can
            // set it for the next frame
            mForceScreenScrolled = false;
            //绘制翻页效果的关键位置,下面将重点展开详解。
            screenScrolled(screenCenter);   
            mLastScreenCenter = screenCenter;
        }
        // Find out which screens are visible; as an optimization we only call
        // draw on them
        final int pageCount = getChildCount();
        if (pageCount > 0) {
            getVisiblePages(mTempVisiblePagesRange);
            final int leftScreen = mTempVisiblePagesRange[0];
            final int rightScreen = mTempVisiblePagesRange[1];
            if (leftScreen != -1 && rightScreen != -1) {
                final long drawingTime = getDrawingTime();

                // Clip to the bounds
                canvas.save();
                canvas.clipRect(getScrollX(), getScrollY(), getScrollX()
                        + getRight() - getLeft(), getScrollY() + getBottom()
                        - getTop());

                // Draw all the children, leaving the drag view for last
                //绘制所有子页,拖动页最后绘制
                for (int i = pageCount - 1; i >= 0; i--) {
                    final View v = getPageAt(i);
                    if (v == mDragView)
                        continue;
                    if (mForceDrawAllChildrenNextFrame
                            || (leftScreen <= i && i <= rightScreen && shouldDrawChild(v))) {
                        drawChild(canvas, v, drawingTime);
                    }
                    // Draw the drag view on top (if there is one)
                    if (mDragView != null) {
                        drawChild(canvas, mDragView, drawingTime);
                    }
                    mForceDrawAllChildrenNextFrame = false;
                    canvas.restore();
                }
            }
        }
    }

  当Scroll位置有变化时,就进入screenScrolled这个类,对子页进行缩放,改变透明度,转换角度,移动等,不断变化就组合成了我们看到的层叠效果。都是view自带的一些类,没有涉及复杂的动画效果。

// launcher3\AppsCustomizePagedView.java
// In apps customize, we have a scrolling effect which emulates pulling cards off of a stack.
    @Override
    protected void screenScrolled(int screenCenter) {
        // 右到左布局,一般中东一些语言才采用,这里为false
        final boolean isRtl = isLayoutRtl();
        super.screenScrolled(screenCenter);

        // 循环处理每页
        for (int i = 0; i < getChildCount(); i++) {
            View v = getPageAt(i);
            if (v != null) {
                // 获取滑动的进度
                float scrollProgress = getScrollProgress(screenCenter, v, i);

                float interpolatedProgress;
                float translationX;
                float maxScrollProgress = Math.max(0, scrollProgress);
                float minScrollProgress = Math.min(0, scrollProgress);

                if (isRtl) {
                    translationX = maxScrollProgress * v.getMeasuredWidth();
                    interpolatedProgress = mZInterpolator.getInterpolation(Math.abs(maxScrollProgress));
                } else {
                    //子页X轴偏移
                    translationX = minScrollProgress * v.getMeasuredWidth();
                    //插值器,值是一条向上的抛物线,为了缩放过度更加自然
                    interpolatedProgress = mZInterpolator.getInterpolation(Math.abs(minScrollProgress));
                }
                float scale = (1 - interpolatedProgress) +
                        interpolatedProgress * TRANSITION_SCALE_FACTOR;
                float alpha;
                if (isRtl && (scrollProgress > 0)) {
                    alpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(maxScrollProgress));
                } else if (!isRtl && (scrollProgress < 0)) {
                    alpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(scrollProgress));
                } else {
                    //  On large screens we need to fade the page as it nears its leftmost position
                    alpha = mLeftScreenAlphaInterpolator.getInterpolation(1 - scrollProgress);
                }

                v.setCameraDistance(mDensity * CAMERA_DISTANCE);
                int pageWidth = v.getMeasuredWidth();
                int pageHeight = v.getMeasuredHeight();

                if (PERFORM_OVERSCROLL_ROTATION) {
                    float xPivot = isRtl ? 1f - TRANSITION_PIVOT : TRANSITION_PIVOT;
                    boolean isOverscrollingFirstPage = isRtl ? scrollProgress > 0 : scrollProgress < 0;
                    boolean isOverscrollingLastPage = isRtl ? scrollProgress < 0 : scrollProgress > 0;
                    //在第一页向右滑时
                    if (i == 0 && isOverscrollingFirstPage) {
                        // Overscroll to the left
                        v.setPivotX(xPivot * pageWidth);
                        v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
                        scale = 1.0f;
                        alpha = 1.0f;
                        // On the first page, we don't want the page to have any lateral motion
                        translationX = 0;
                    //最后一页向左滑时
                    } else if (i == getChildCount() - 1 && isOverscrollingLastPage) {
                        // Overscroll to the right
                        v.setPivotX((1 - xPivot) * pageWidth);
                        v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
                        scale = 1.0f;
                        alpha = 1.0f;
                        // On the last page, we don't want the page to have any lateral motion.
                        translationX = 0;
                    } else {
                        v.setPivotY(pageHeight / 2.0f);
                        v.setPivotX(pageWidth / 2.0f);
                        v.setRotationY(0f);
                    }
                }

                v.setTranslationX(translationX);    //X轴偏移
                v.setScaleX(scale); //缩放
                v.setScaleY(scale);
                v.setAlpha(alpha);  //透明度

                // If the view has 0 alpha, we set it to be invisible so as to prevent
                // it from accepting touches
                if (alpha == 0) {
                    v.setVisibility(INVISIBLE);
                } else if (v.getVisibility() != VISIBLE) {
                    v.setVisibility(VISIBLE);
                }
            }
        }
        enableHwLayersOnVisiblePages();
    }

2,修改

  基本上就是这样,没什么要过多解释的,试一下效果就比较清楚。下面将他们稍微改动下,变成翻转效果。第三方的Launcher有很多翻页效果,大家可以多尝试。看起来挺高端的,其实也是很简单的效果叠加的。还有,现在很多Launcher都是循环滑动的,具体怎样改为循环的,有机会再开一贴。

   // In apps customize, we have a scrolling effect which emulates pulling cards off of a stack.
   // http://blog.csdn.net/heymi_csdn
    @Override
    protected void screenScrolled(int screenCenter) {
        final boolean isRtl = isLayoutRtl();
        super.screenScrolled(screenCenter);
        for (int i = 0; i < getChildCount(); i++) {
            View v = getPageAt(i);
            if (v != null) {
                float scrollProgress = getScrollProgress(screenCenter, v, i);

                float interpolatedProgress;
                float translationX;
                float maxScrollProgress = Math.max(0, scrollProgress);
                float minScrollProgress = scrollProgress;//Math.min(0, scrollProgress);

                if (isRtl) {
                    translationX = maxScrollProgress * v.getMeasuredWidth();
                    interpolatedProgress = mZInterpolator.getInterpolation(Math.abs(maxScrollProgress));
                } else {
                    translationX = minScrollProgress * v.getMeasuredWidth();
                    interpolatedProgress = mZInterpolator.getInterpolation(Math.abs(minScrollProgress));

                }
                float scale = (1 - interpolatedProgress) +
                        interpolatedProgress * TRANSITION_SCALE_FACTOR;
                        float alpha;
                alpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(scrollProgress));
                v.setCameraDistance(mDensity * CAMERA_DISTANCE);
                int pageWidth = v.getMeasuredWidth();
                int pageHeight = v.getMeasuredHeight();

                if (PERFORM_OVERSCROLL_ROTATION) {
                    float xPivot = isRtl ? 1f - TRANSITION_PIVOT : TRANSITION_PIVOT;
                    boolean isOverscrollingFirstPage = isRtl ? scrollProgress > 0 : scrollProgress < 0;
                    boolean isOverscrollingLastPage = isRtl ? scrollProgress < 0 : scrollProgress > 0;

                    if (i == 0 && isOverscrollingFirstPage) {
                        // Overscroll to the left
                        v.setPivotX(xPivot * pageWidth); //设置中心点
                        v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress); //Y轴旋转
                        // On the first page, we don't want the page to have any lateral motion
                        translationX = 0;
                    } else if (i == getChildCount() - 1 && isOverscrollingLastPage) {
                        // Overscroll to the right
                        v.setPivotX((1 - xPivot) * pageWidth);
                        v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
                        // On the last page, we don't want the page to have any lateral motion.
                        translationX = 0;
                    } else {
                        v.setPivotY(pageHeight / 2.0f);
                        v.setPivotX(pageWidth / 2.0f);
                        v.setTranslationX(translationX);//X轴偏移方向
                        v.setRotationY(180 * scrollProgress);
                        v.setAlpha(alpha);
                    }
                }
                // If the view has 0 alpha, we set it to be invisible so as to prevent
                // it from accepting touches
                if (alpha == 0 || Math.abs(translationX) > pageWidth/2) {
                    v.setVisibility(INVISIBLE);
                } else if (v.getVisibility() != VISIBLE) {
                    v.setVisibility(VISIBLE);
                }
            }
        }
        enableHwLayersOnVisiblePages();
    }

                 
  注:本文基于的是android4.4 MTK定制过的代码,和谷歌原生有一点差异。下次有空做一个demo。

你可能感兴趣的:(Launcher3 翻页动画详解与修改)