Android 6.0 Launcher3 增加屏幕切换动画

本文是在Android 6.0的系统上增加的Launcher3屏幕切换动画功能。实际效果如下:

功能分析

下面我们来详细介绍下怎么实现这个功能:
先来看一张图,对于这个功能的修改有个大致了解,然后一一分析之。
Android 6.0 Launcher3 增加屏幕切换动画_第1张图片
我们由上到下的顺序分析这个修改的作用。
res和values目录下的修改就不分析,相信大家都可以看明白是什么意图。layout/overview_panel.xml用于实现切换动画的按钮布局,也不多说了。主要看下src目录下java文件的修改。

DeviceProfile.java

在DeviceProfile.java中我们需要修改layout函数,因为它会去是设置overview_panel的大小,如果我们不去修改的话,整个overview_panel布局宽度就会限制在两个或三个按钮的宽度之间,这样UI效果不是很好,所以我们需要修改其大小,接下来看下怎么修改,在layout函数中我们可以找到如下代码:

// Layout the Overview Mode
ViewGroup overviewMode = launcher.getOverviewPanel();
if (overviewMode != null) {
    int overviewButtonBarHeight = getOverviewModeButtonBarHeight();
    lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
    lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;

    int visibleChildCount = getVisibleChildCount(overviewMode);
    int totalItemWidth = visibleChildCount * overviewModeBarItemWidthPx;
    int maxWidth = totalItemWidth + (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
//add by steven zhang 20170105
//  lp.width = Math.min(availableWidthPx, maxWidth);
    lp.width = (int) (availableWidthPx * 0.6);
//  lp.height = overviewButtonBarHeight;
    overviewMode.setLayoutParams(lp);

    if (lp.width > totalItemWidth && visibleChildCount > 1) {
        // We have enough space. Lets add some margin too.
        int margin = (lp.width - totalItemWidth) / (visibleChildCount-1);
        View lastChild = null;

        // Set margin of all visible children except the last visible child
        for (int i = 0; i < visibleChildCount; i++) {
            if (lastChild != null) {
                MarginLayoutParams clp = (MarginLayoutParams) lastChild.getLayoutParams();
                if (isLayoutRtl) {
                    clp.leftMargin = margin;
                } else {
                    clp.rightMargin = margin;
                }
                lastChild.setLayoutParams(clp);
                lastChild = null;
            }
            View thisChild = overviewMode.getChildAt(i);

            if (thisChild.getVisibility() != View.GONE) {
                lastChild = thisChild;
            }
        }
    }
}

上面是修改后的代码,修改前的是注释那部分,可以看到lp.width和lp.height都会被设置大小,而lp.width会取最小值,所以我们只需要将其设置为availableWidthPx的60%即可,当然这个可以根据实际需求修改。

Launcher.java

Launcher.java是launcher默认启动的activity,因此我们需要在其中设置切换动画的默认值,同时还要将其保存到文件中,以便下次开机启动时,设置为对应的切换动画。下面我们来看下实际的代码实现。

//add by steven zhang 20170105
static final String PAGEVIEWANIMATION_TYPE = "launcher.pageview_animation_type";
@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    setContentView(R.layout.launcher);

    //add by steven zhang 20170105
    //在activity启动的时候获取默认切换动画并设置
    int type = mSharedPrefs.getInt(PAGEVIEWANIMATION_TYPE, 0);
    PageViewAnimation.getInstance().setPageViewAnime(type);
    ...
}

//在setupView中初始化切换动画的UI
private void setupViews() {
    ...
    //add by steven zhang 20170105
    final ViewGroup pageAnimViewGroup = (ViewGroup) findViewById(R.id.id_pageainm_layout);
    final int count = pageAnimViewGroup.getChildCount();
    for (int i = 0; i < count; i++) {
        TextView child = (TextView) pageAnimViewGroup.getChildAt(i);
        //设置当前切换动画按钮哪个为选中状态
        setAnimPageFocusDot(PageViewAnimation.getInstance().getPageViewAnime(), i, child);
        child.setId(i);
        child.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                int id = v.getId();
                //设置切换动画类型
                PageViewAnimation.getInstance().setPageViewAnime(id);
                //保存切换动画类型到文件
                setPageViewAnimaType(id);
                //重置焦点
                for (int j = 0; j < count; j++) {
                    TextView child = (TextView) pageAnimViewGroup.getChildAt(j);
                    setAnimPageFocusDot(id, j, child);
                }
            }
        });

        child.setOnTouchListener(getHapticFeedbackTouchListener());
    }


    mOverviewPanel.setAlpha(0f);
    ...
}

/**
 * add by steven zhang 20170105
 * 设置页面切换动画
 */
private void setPageViewAnimaType(int type) {
    SharedPreferences.Editor editor = mSharedPrefs.edit();
    editor.putInt(PAGEVIEWANIMATION_TYPE, type);
    editor.apply();
}

/**
 * add by steven zhang 20170105
 * 设置切换动画焦点
 * @param selected
 * @param index
 * @param child
 */
private void setAnimPageFocusDot(int selected, int index, TextView child) {
    Drawable drawable = getResources().getDrawable(R.drawable.focus_dot);
    Drawable[] drawables = child.getCompoundDrawables();
    child.setCompoundDrawables(null, drawables[1], null, null);

    if (selected == index) {
        drawable.setBounds(0, 0, 10, 10);
        child.setCompoundDrawables(null, drawables[1], null, drawable);
    }
}

上面的代码中,有详细的注释,就不过多分析了,在Launcher.java主要做的事情,就是初始化切换动画的类型及其UI。

PageViewAnimation.java

PageViewAnimation.java是具体的屏幕切换实现,里面实现了各种切换效果。

//add by steven zhang 20170105
public class PageViewAnimation {

    private static final String TAG = "PageViewAnimation";

    public static final int PAGEVIEW_ANIMATION_NORMAL               = 0;//经典动画
    public static final int PAGEVIEW_ANIMATION_TURNTABLE            = 1;//转盘动画
    public static final int PAGEVIEW_ANIMATION_LAYERED              = 2;//层叠动画
    public static final int PAGEVIEW_ANIMATION_ROTATE               = 3;//旋转动画
    public static final int PAGEVIEW_ANIMATION_PAGETURN             = 4;//翻页动画
    public static final int PAGEVIEW_ANIMATION_ROTATEBYLEFTTOPPOINT = 5;//绕左上角旋转的动画
    public static final int PAGEVIEW_ANIMATION_ROTATEBYCENTERPOINT  = 6;//绕中心点旋转的动画
    public static final int PAGEVIEW_ANIMATION_BLOCKS               = 7;//设置方块动画

    private static ZInterpolator mZInterpolator = new ZInterpolator(0.5f);
    private static AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f);
    private static DecelerateInterpolator mLeftScreenAlphaInterpolator = new DecelerateInterpolator(4);
    private static PageViewAnimation mInstance;

    private static float CAMERA_DISTANCE = 6500;
    private static float TRANSITION_SCALE_FACTOR = 0.74f;
    private static float TRANSITION_PIVOT = 0.65f;
    private static float TRANSITION_MAX_ROTATION = 24;

    private float mTranslationX = 0f;
    private float mScale = 1f;
    private float mAlpha = 1f;
    private float mRotation = 0f;
    private float mRotationY = 0f;
    private float mPivotX = 0f;
    private float mPivotY = 0f;

    private int mAnimaType = PAGEVIEW_ANIMATION_NORMAL;

    public static PageViewAnimation getInstance() {
        if (mInstance == null) {
            synchronized (PageViewAnimation.class) {
                if (mInstance == null) {
                    mInstance = new PageViewAnimation();
                }
            }
        }

        return mInstance;
    }

    public void setPageViewAnime(int type) {
        mAnimaType = type;
    }

    public int getPageViewAnime() {
        return mAnimaType;
    }

    public void pageViewAnime(float scrollProgress, int i, int count, float density, View v) {
        resetAttr(v);

        switch (mAnimaType) {
        case PAGEVIEW_ANIMATION_TURNTABLE:
            mRotation = -TRANSITION_MAX_ROTATION * scrollProgress;
            mPivotX = v.getMeasuredWidth() * 0.5f;
            mPivotY = v.getMeasuredHeight();
            break;
        case PAGEVIEW_ANIMATION_LAYERED:
            {
                float minScrollProgress = Math.min(0, scrollProgress);

                float interpolatedProgress;

                mTranslationX = minScrollProgress * v.getMeasuredWidth();
                interpolatedProgress = mZInterpolator.getInterpolation(Math.abs(minScrollProgress));

                mScale = (1 - interpolatedProgress) + interpolatedProgress * TRANSITION_SCALE_FACTOR;

                if ((scrollProgress < 0)) {
                    mAlpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(scrollProgress));
                } else {
                    mAlpha = mLeftScreenAlphaInterpolator.getInterpolation(1 - scrollProgress);
                }

            }
            break;
        case PAGEVIEW_ANIMATION_ROTATE:
            mPivotX = v.getMeasuredWidth() * 0.5f;
            mPivotY = v.getMeasuredHeight();

            mRotationY = -90 * scrollProgress;

            mTranslationX = scrollProgress * v.getMeasuredWidth();

            mAlpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(scrollProgress));
            break;
        case PAGEVIEW_ANIMATION_PAGETURN:
            mPivotX = 0.0f;
            mPivotY = 0.0f;

            mRotationY = -90 * scrollProgress;

            mTranslationX = scrollProgress * v.getMeasuredWidth();

            mAlpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(scrollProgress));
            break;
        case PAGEVIEW_ANIMATION_ROTATEBYLEFTTOPPOINT:
            mPivotX = 0.0f;
            mPivotY = 0.0f;

            mRotation = -90 * scrollProgress;

            mTranslationX = scrollProgress * v.getMeasuredWidth();

            mAlpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(scrollProgress));
            break;
        case PAGEVIEW_ANIMATION_ROTATEBYCENTERPOINT:
            mPivotX = v.getMeasuredWidth() * 0.5f;
            mPivotY = v.getMeasuredHeight() * 0.5f;

            mRotation = -90 * scrollProgress;

            mTranslationX = scrollProgress * v.getMeasuredWidth();

            mAlpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(scrollProgress));
            break;

        case PAGEVIEW_ANIMATION_BLOCKS:
            if (scrollProgress < 0) {
                mPivotX = 0.0f;
            } else {
                mPivotX = v.getMeasuredWidth();
            }

            mRotationY = -90 * scrollProgress;
            break;
        case PAGEVIEW_ANIMATION_NORMAL:

        default:
            break;
        }


        overScrollAnimation(scrollProgress, i, count, density, v);

        setViewAttr(v);

        showOrHideView(v);
    }

    /**
     * 设置转盘动画
     * 
     * @param scrollProgress
     * @param view
     */
    public void setTurntableAnim(float scrollProgress, int i, int count, float density, View v) {
        resetAttr(v);

        mRotation = -TRANSITION_MAX_ROTATION * scrollProgress;
        mPivotX = v.getMeasuredWidth() * 0.5f;
        mPivotY = v.getMeasuredHeight();

        overScrollAnimation(scrollProgress, i, count, density, v);

        setViewAttr(v);

        showOrHideView(v);
    }

    /**
     * 设置层叠动画
     * 
     * @param scrollProgress
     * @param density
     * @param v
     */
    public void setLayeredAnim(float scrollProgress, int i, int count, float density, View v) {
        resetAttr(v);

        float minScrollProgress = Math.min(0, scrollProgress);

        float interpolatedProgress;

        mTranslationX = minScrollProgress * v.getMeasuredWidth();
        interpolatedProgress = mZInterpolator.getInterpolation(Math.abs(minScrollProgress));

        mScale = (1 - interpolatedProgress) + interpolatedProgress * TRANSITION_SCALE_FACTOR;

        if ((scrollProgress < 0)) {
            mAlpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(scrollProgress));
        } else {
            mAlpha = mLeftScreenAlphaInterpolator.getInterpolation(1 - scrollProgress);
        }

        overScrollAnimation(scrollProgress, i, count, density, v);

        setViewAttr(v);

        showOrHideView(v);
    }

    /**
     * 经典模式,水平左出右进
     * @param scrollProgress
     * @param i
     * @param count
     * @param density
     * @param v
     */
    public void setNormalAnim(float scrollProgress, int i, int count, float density, View v) {
        resetAttr(v);

        overScrollAnimation(scrollProgress, i, count, density, v);

        setViewAttr(v);

        showOrHideView(v);
    }

    /**
     * 设置旋转动画
     * @param scrollProgress
     * @param i
     * @param count
     * @param density
     * @param v
     */
    public void  setRotateAnim(float scrollProgress, int i, int count, float density, View v) {
        resetAttr(v);

        mPivotX = v.getMeasuredWidth() * 0.5f;
        mPivotY = v.getMeasuredHeight();

        mRotationY = -90 * scrollProgress;

        mTranslationX = scrollProgress * v.getMeasuredWidth();

        mAlpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(scrollProgress));

        overScrollAnimation(scrollProgress, i, count, density, v);

        setViewAttr(v);

        showOrHideView(v);
    }

    /**
     * 设置翻页动画
     * @param scrollProgress
     * @param i
     * @param count
     * @param density
     * @param v
     */
    public void setPageTurnAnim(float scrollProgress, int i, int count, float density, View v) {
        resetAttr(v);

        mPivotX = 0.0f;
        mPivotY = 0.0f;

        mRotationY = -90 * scrollProgress;

        mTranslationX = scrollProgress * v.getMeasuredWidth();

        mAlpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(scrollProgress));

        overScrollAnimation(scrollProgress, i, count, density, v);

        setViewAttr(v);

        showOrHideView(v);
    }

    /**
     * 设置以左上角为中心点的旋转
     * @param scrollProgress
     * @param i
     * @param count
     * @param density
     * @param v
     */
    public void setRotateByLeftTopPointAnim(float scrollProgress, int i, int count, float density, View v) {
        resetAttr(v);

        mPivotX = 0.0f;
        mPivotY = 0.0f;

        mRotation = -90 * scrollProgress;

        mTranslationX = scrollProgress * v.getMeasuredWidth();

        mAlpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(scrollProgress));

        overScrollAnimation(scrollProgress, i, count, density, v);

        setViewAttr(v);

        showOrHideView(v);
    }

    /**
     * 设置以正中心为中心点的旋转
     * @param scrollProgress
     * @param i
     * @param count
     * @param density
     * @param v
     */
    public void setRotateByCenterPointAnim(float scrollProgress, int i, int count, float density, View v) {
        resetAttr(v);

        mPivotX = v.getMeasuredWidth() * 0.5f;
        mPivotY = v.getMeasuredHeight() * 0.5f;

        mRotation = -90 * scrollProgress;

        mTranslationX = scrollProgress * v.getMeasuredWidth();

        mAlpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(scrollProgress));

        overScrollAnimation(scrollProgress, i, count, density, v);

        setViewAttr(v);

        showOrHideView(v);
    }

    /**
     * 设置方块动画的旋转
     * @param scrollProgress
     * @param i
     * @param count
     * @param density
     * @param v
     */
    public void setBlocksAnim(float scrollProgress, int i, int count, float density, View v) {
        resetAttr(v);

        if (scrollProgress < 0) {
            mPivotX = 0.0f;
        } else {
            mPivotX = v.getMeasuredWidth();
        }

        mRotationY = -90 * scrollProgress;

        overScrollAnimation(scrollProgress, i, count, density, v);

        setViewAttr(v);

        showOrHideView(v);
    }


    /**
     * 设置View的属性
     * @param v
     */
    private void setViewAttr(View v) {
        v.setPivotY(mPivotY);
        v.setPivotX(mPivotX);
        v.setRotation(mRotation);
        v.setRotationY(mRotationY);

        v.setTranslationX(mTranslationX);
        v.setScaleX(mScale);
        v.setScaleY(mScale);
        v.setAlpha(mAlpha);
    }

    /**
     * 根据alpha的值来判断是否显示view
     * 
     * @param alpha
     * @param v
     */
    private void showOrHideView(View v) {
        if (mAlpha == 0) {
            v.setVisibility(View.INVISIBLE);
        } else if (v.getVisibility() != View.VISIBLE) {
            v.setVisibility(View.VISIBLE);
        }
    }

    /**
     * 重置属性
     */
    private void resetAttr(View v) {
        mTranslationX = 0f;
        mScale = 1f;
        mAlpha = 1f;
        mRotation = 0f;
        mRotationY = 0f;
        mPivotX = v.getMeasuredWidth() * 0.5f;
        mPivotY = v.getMeasuredHeight() * 0.5f;
    }

    /**
     * 设置pageView左右两端时的动画
     * 
     * @param scrollProgress
     * @param i
     * @param count
     * @param density
     * @param v
     */
    public void overScrollAnimation(float scrollProgress, int i,
            int count, float density, View v) {
        float xPivot = TRANSITION_PIVOT;
        boolean isOverscrollingFirstPage = scrollProgress < 0;
        boolean isOverscrollingLastPage = scrollProgress > 0;

        int pageWidth = v.getMeasuredWidth();
        int pageHeight = v.getMeasuredHeight();

        v.setCameraDistance(density * CAMERA_DISTANCE);

        if (i == 0 && isOverscrollingFirstPage) {
            mPivotX = xPivot * pageWidth;
            mPivotY = pageHeight / 2.0f;
            mRotation = 0f;
            mRotationY = -TRANSITION_MAX_ROTATION * scrollProgress;
            mScale = 1.0f;
            mAlpha = 1.0f;
            mTranslationX = 0f;
        } else if (i == count - 1 && isOverscrollingLastPage) {
            mPivotX = xPivot * pageWidth;
            mPivotY = pageHeight / 2.0f;
            mRotation = 0f;
            mRotationY = -TRANSITION_MAX_ROTATION * scrollProgress;
            mScale = 1.0f;
            mAlpha = 1.0f;
            mTranslationX = 0f;
        }
    }

    private static class ZInterpolator implements TimeInterpolator {
        private float focalLength;

        public ZInterpolator(float foc) {
            focalLength = foc;
        }

        public float getInterpolation(float input) {
            return (1.0f - focalLength / (focalLength + input)) /
                    (1.0f - focalLength / (focalLength + 1.0f));
        }
    }
}

Workspace.java

Workspace.java就是监听屏幕滑动时的位移变化,然后通过PageViewAnimation类,实现真正的动画切换效果。

protected void screenScrolled(int screenCenter) {
    ...
     //add by steven zhang 20170105
    for (int i = 0; i < getChildCount(); i++) {
        View v = getPageAt(i);
        if (v != null) {
             float scrollProgress = getScrollProgress(screenCenter, v, i);
             //根据滑动位置设置每个屏幕的动画效果
             PageViewAnimation.getInstance().pageViewAnime(scrollProgress, i, getChildCount(), mDensity, v);
         }
    }
    ...
}

总结

通过增加屏幕切换的功能,我们可以了解到增加这个需求可以分为三步:
1.初始化类型及UI(Launcher.java)
2.实现具体的需求(PageViewAnimation.java)
3.监听数据变化(Workspace.java)

修改的资源及源码包

你可能感兴趣的:(android,launcher)