本文是在Android 6.0的系统上增加的Launcher3屏幕切换动画功能。实际效果如下:
下面我们来详细介绍下怎么实现这个功能:
先来看一张图,对于这个功能的修改有个大致了解,然后一一分析之。
我们由上到下的顺序分析这个修改的作用。
res和values目录下的修改就不分析,相信大家都可以看明白是什么意图。layout/overview_panel.xml用于实现切换动画的按钮布局,也不多说了。主要看下src目录下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默认启动的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是具体的屏幕切换实现,里面实现了各种切换效果。
//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就是监听屏幕滑动时的位移变化,然后通过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)