Fab 和 Dialog 之間的過渡效果(Fab and Dialog Morphing Animation)
最近在讀Plaid的源碼,發現fab和dialog之間切換的動畫效果好舒服,于是就研究了下,將其從Plaid項目中抽離出來,然后再改進了些代碼,更加方便易懂,也更加簡單易用。效果如下,項目源碼地址

實現原理分析
1.在前面的《Android群英傳》的讀書筆記中提到過Activity共享元素過渡動畫的實現方式
共享元素過渡動畫:一個共享元素過渡動畫決定兩個Activity之間的過渡怎么共享它們的視圖,包括了
changeBounds:改變目標視圖的布局邊界;
changeClipBounds:裁剪目標視圖的邊界;
changeTransform:改變目標視圖的縮放比例和旋轉角度;
changeImageTransform:改變目標圖片的大小和縮放比例。
使用方式:假設Activity從A跳轉到B,那么將A中原來的startActivity改為如下代碼:
//單個共享元素的調用方式 startActivity(intent,ActivityOptions.makeSceneTransitionAnimation(this, view, "share").toBundle()); //多個共享元素的調用方式 startActivity(intent,ActivityOptions.makeSceneTransitionAnimation(this, Pair.create(view, "share"), Pair.create(fab, "fab")).toBundle());
然后在B的onCreate方法中添加如下代碼:
//聲明需要開啟Activity過渡動畫 getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
其次還要在Activity A和B的布局文件中為共享元素組件添加android:transitionName="xxx"屬性。
2.源碼中的Dialog實際上是Activity,并設置了android:windowIsTranslucent為true,所以從fab到dialog的動畫效果實際上是Activity的過渡動畫。但是,如果單純的只是使用Activity的共享元素過渡動畫,將fab作為共享元素的話,效果并不好,不是那么的舒服。
3.為了讓過渡效果更加舒服,這里添加了兩個漸變效果,一個是color,從fab的顏色到dialog的背景顏色的漸變;另一個是cornerRadius,即圓角幅度的漸變。請看下面的代碼實現:
/**
* MorphTransition擴展自ChangeBounds(共享元素的動畫的一種),它在原有動畫基礎上添加了color和cornerRadius的動畫效果,這個類實際上是整合了MorphFabToDialog和MorphDialogToFab兩個類的作用
* <p/>
* A transition that morphs a circle into a rectangle, changing it's background color.
*/
public class MorphTransition extends ChangeBounds {
private static final String PROPERTY_COLOR = "color";
private static final String PROPERTY_CORNER_RADIUS = "cornerRadius";
private static final String[] TRANSITION_PROPERTIES = {
PROPERTY_COLOR,
PROPERTY_CORNER_RADIUS
};
private int startColor = Color.TRANSPARENT;
private int endColor = Color.TRANSPARENT;
private int startCornerRadius = 0;
private int endCornerRadius = 0;
private boolean isShowViewGroup = false;
public MorphTransition(int startColor, int endColor, int startCornerRadius, int endCornerRadius, boolean isShowViewGroup) {
super();
setStartColor(startColor);
setEndColor(endColor);
setStartCornerRadius(startCornerRadius);
setEndCornerRadius(endCornerRadius);
setIsShowViewGroup(isShowViewGroup);
}
public MorphTransition(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public String[] getTransitionProperties() {
return TRANSITION_PROPERTIES;
}
@Override
public void captureStartValues(TransitionValues transitionValues) {
super.captureStartValues(transitionValues);
final View view = transitionValues.view;
if (view.getWidth() <= 0 || view.getHeight() <= 0) {
return;
}
transitionValues.values.put(PROPERTY_COLOR, startColor);
transitionValues.values.put(PROPERTY_CORNER_RADIUS, startCornerRadius);//view.getHeight() / 2
}
@Override
public void captureEndValues(TransitionValues transitionValues) {
super.captureEndValues(transitionValues);
final View view = transitionValues.view;
if (view.getWidth() <= 0 || view.getHeight() <= 0) {
return;
}
transitionValues.values.put(PROPERTY_COLOR, endColor);//ContextCompat.getColor(view.getContext(), R.color.dialog_background_color)
transitionValues.values.put(PROPERTY_CORNER_RADIUS, endCornerRadius);
}
@Override
public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues, final TransitionValues endValues) {
Animator changeBounds = super.createAnimator(sceneRoot, startValues, endValues);
if (startValues == null || endValues == null || changeBounds == null) {
return null;
}
Integer startColor = (Integer) startValues.values.get(PROPERTY_COLOR);
Integer startCornerRadius = (Integer) startValues.values.get(PROPERTY_CORNER_RADIUS);
Integer endColor = (Integer) endValues.values.get(PROPERTY_COLOR);
Integer endCornerRadius = (Integer) endValues.values.get(PROPERTY_CORNER_RADIUS);
if (startColor == null || startCornerRadius == null || endColor == null || endCornerRadius == null) {
return null;
}
MorphDrawable background = new MorphDrawable(startColor, startCornerRadius);
endValues.view.setBackground(background);
Animator color = ObjectAnimator.ofArgb(background, background.COLOR, endColor);
Animator corners = ObjectAnimator.ofFloat(background, background.CORNER_RADIUS, endCornerRadius);
////......
AnimatorSet transition = new AnimatorSet();
transition.playTogether(changeBounds, corners, color);
transition.setDuration(300);
transition.setInterpolator(AnimationUtils.loadInterpolator(sceneRoot.getContext(), android.R.interpolator.fast_out_slow_in));
return transition;
}
public void setEndColor(int endColor) {
this.endColor = endColor;
}
public void setEndCornerRadius(int endCornerRadius) {
this.endCornerRadius = endCornerRadius;
}
public void setStartColor(int startColor) {
this.startColor = startColor;
}
public void setStartCornerRadius(int startCornerRadius) {
this.startCornerRadius = startCornerRadius;
}
public void setIsShowViewGroup(boolean isShowViewGroup) {
this.isShowViewGroup = isShowViewGroup;
}
} 4.上面的代碼中用到了MorphDrawable類,它繼承自Drawable,并添加了前面提到的那兩個屬性以用于產生屬性動畫,默認是沒有為那兩個屬性添加set/get方法的,所以需要進行擴展。關于屬性動畫可以看以前的讀書筆記,重要代碼如下:
/**
* 形態和顏色可以發生變化的Drawable,形態變化是通過cornerRadius來實現的,顏色變化是通過paint的color來實現的
* 該類在Drawable的基礎上添加了cornerRadius和color兩個屬性,前者是float類型,后者是int類型
* <p/>
* A drawable that can morph size, shape (via it's corner radius) and color. Specifically this is
* useful for animating between a FAB and a dialog.
*/
public class MorphDrawable extends Drawable {
private Paint paint;
private float cornerRadius;
public static final Property<MorphDrawable, Float> CORNER_RADIUS = new Property<MorphDrawable, Float>(Float.class, "cornerRadius") {
@Override
public void set(MorphDrawable morphDrawable, Float value) {
morphDrawable.setCornerRadius(value);
}
@Override
public Float get(MorphDrawable morphDrawable) {
return morphDrawable.getCornerRadius();
}
};
public static final Property<MorphDrawable, Integer> COLOR = new Property<MorphDrawable, Integer>(Integer.class, "color") {
@Override
public void set(MorphDrawable morphDrawable, Integer value) {
morphDrawable.setColor(value);
}
@Override
public Integer get(MorphDrawable morphDrawable) {
return morphDrawable.getColor();
}
};
public MorphDrawable(@ColorInt int color, float cornerRadius) {
this.cornerRadius = cornerRadius;
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(color);
}
public float getCornerRadius() {
return cornerRadius;
}
public void setCornerRadius(float cornerRadius) {
this.cornerRadius = cornerRadius;
invalidateSelf();
}
public int getColor() {
return paint.getColor();
}
public void setColor(int color) {
paint.setColor(color);
invalidateSelf();
}
@Override
public void draw(Canvas canvas) {
canvas.drawRoundRect(getBounds().left, getBounds().top, getBounds().right, getBounds()
.bottom, cornerRadius, cornerRadius, paint);//hujiawei
}
@Override
public void getOutline(Outline outline) {
outline.setRoundRect(getBounds(), cornerRadius);
}
@Override
public void setAlpha(int alpha) {
paint.setAlpha(alpha);
invalidateSelf();
}
@Override
public void setColorFilter(ColorFilter cf) {
paint.setColorFilter(cf);
invalidateSelf();
}
@Override
public int getOpacity() {
return paint.getAlpha();
}
} 5.有了前面的準備之后,就可以在dialog中配置進入和退出的動畫效果了,重要代碼如下:
//DialogActivity.java
public void setupSharedEelementTransitions2() {
ArcMotion arcMotion = new ArcMotion();
arcMotion.setMinimumHorizontalAngle(50f);
arcMotion.setMinimumVerticalAngle(50f);
Interpolator easeInOut = AnimationUtils.loadInterpolator(this, android.R.interpolator.fast_out_slow_in);
//hujiawei 100是隨意給的一個數字,可以修改,需要注意的是這里調用container.getHeight()結果為0
MorphTransition sharedEnter = new MorphTransition(ContextCompat.getColor(this, R.color.fab_background_color),
ContextCompat.getColor(this, R.color.dialog_background_color), 100, getResources().getDimensionPixelSize(R.dimen.dialog_corners), true);
sharedEnter.setPathMotion(arcMotion);
sharedEnter.setInterpolator(easeInOut);
MorphTransition sharedReturn = new MorphTransition(ContextCompat.getColor(this, R.color.dialog_background_color),
ContextCompat.getColor(this, R.color.fab_background_color), getResources().getDimensionPixelSize(R.dimen.dialog_corners), 100, false);
sharedReturn.setPathMotion(arcMotion);
sharedReturn.setInterpolator(easeInOut);
if (container != null) {
sharedEnter.addTarget(container);
sharedReturn.addTarget(container);
}
getWindow().setSharedElementEnterTransition(sharedEnter);
getWindow().setSharedElementReturnTransition(sharedReturn);
} 6.從上面的分析可以看出,這個方案比較容易擴展,只要是該類型的動畫,使用開始和結束時的對應顏色和圓角值就可以構造相應的MorphTransition,將MorphTransition設置為Activity的進入或者退化動畫即可。
以上是我的分析和理解,有任何問題歡迎大家指點 ?(^ω^)?
原文:Fab and Dialog Morphing Animation
來自: http://www.jcodecraeer.com//a/anzhuokaifa/2015/1215/3776.html