先直接上效果圖:java
相信你們在日常開發也會遇到相似的轉場動畫,若是想要要實現上圖的效果有哪些方式呢?android
首先分析一下轉場過程,咱們把起始 View
分別定義爲 startView
和 endView
。startView
爲常見的列表佈局,左側頭像和右側爲文本介紹;endView
爲詳情頁面,置頂的大圖和詳細的文本介紹。git
不難發現,這些元素都是對應關係,只不過起始狀態的基本屬性不一樣:github
scaleType
發生變化對於此效果,有不少辦法能夠實現,綜合其實現成本和預期效果進行最終選擇,我能想到的大概有三種:算法
直接把上述的每一個對象看作是獨立個體,各自建立獨立的動畫對象,控制其執行和結束狀態。canvas
這種方式,無疑是最簡單粗暴的,可是實現和維護起來都很困難,更不容易拓展緩存
使用 MotionLayout,不得不說很強大,是 Google 推崇的動畫組件,基本不用編寫 java
代碼就可完成負責的手勢和動畫,後面有時間會介紹。markdown
使用 Transition
,Google 在 Android 5.0 完整引入,雖沒有 MotionLayout 那麼強大,可是其複用性很強,而且很容易理解,上手也很快。app
今天我們就如下面三個方向並結合對應效果來帶你們瞭解一下 Transition。ide
Transition
類Transition
類核心關鍵類 TransitionManager
, TransitionManager.beginDelayedTransition(ViewGroup viewGroup, Transition transition);
做爲動畫的開始,傳入須要作轉場動畫的父佈局或根佈局,隨後改變 View
的相關屬性,好比 setVisible()
,即可自動完成轉場動畫效果。
默認實現的 AutoTransition
,內部集成了基礎動畫:
private void init() {
setOrdering(ORDERING_SEQUENTIAL);
addTransition(new Fade(Fade.OUT)).
addTransition(new ChangeBounds()).
addTransition(new Fade(Fade.IN));
}
複製代碼
這三者做爲 Visibility
的三個子類,經過控制 view.setVisible()
的方式來達到具體的效果。
Fade,淡出 出場,淡入 入場
Slide,向下離開屏幕出場,向上進入屏幕入場
Explode,四邊散開出場,四邊匯入入場
一樣,能夠經過:
Fade fade = new Fade();
Slide slide = new Slide();
TransitionSet set = new TransitionSet();
set.addTransition(fade).addTransition(slide).setOrdering(TransitionSet.ORDERING_TOGETHER);
複製代碼
達到組合的效果:
此處開始同一個頁面場景的切換,ChangeBounds
當 View 的位置或者大小發生變化時觸發對應的轉場效果。好比:
ChangeBounds transition = new ChangeBounds();
transition.setInterpolator(new AnticipateInterpolator());
TransitionManager.beginDelayedTransition(mRoot, transition);
ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) view3.getLayoutParams();
if (layoutParams.leftMargin == 400) {
layoutParams.leftMargin = 50;
} else {
layoutParams.leftMargin = 400;
}
view3.setLayoutParams(layoutParams);
複製代碼
最終的效果:
當調用 view.setClipBounds()
時會觸發轉場效果:
ChangeClipBounds transition = new ChangeClipBounds();
transition.setInterpolator(new BounceInterpolator());
TransitionManager.beginDelayedTransition(mRoot, transition);
int width = view2.getWidth();
int height = view2.getHeight();
int gap = 140;
Rect rect = new Rect(0, gap, width, height - gap);
if (rect.equals(view2.getClipBounds())) {
view2.setClipBounds(null);
} else {
view2.setClipBounds(rect);
}
複製代碼
最終效果:
當調用 view.scrollTo()
會觸發轉場效果:
ChangeScroll transition = new ChangeScroll();
transition.setInterpolator(new AnticipateOvershootInterpolator());
TransitionManager.beginDelayedTransition(mRoot, transition);
if (view1.getScrollX() == -100 && view1.getScrollY() == -100) {
view1.scrollTo(0, 0);
} else {
view1.scrollTo(-100, -100);
}
複製代碼
最終效果:
這個就厲害了,View 的 translation
、scale
和 rotation
發生改變時都會觸發:
ChangeTransform transition = new ChangeTransform();
transition.setInterpolator(new OvershootInterpolator());
TransitionManager.beginDelayedTransition(mRoot, transition);
if (view1.getTranslationX() == 100 && view1.getTranslationY() == 100) {
view1.setTranslationX(0);
view1.setTranslationY(0);
} else {
view1.setTranslationX(100);
view1.setTranslationY(100);
}
if (view2.getRotationX() == 30f) {
view2.setRotationX(0);
} else {
view2.setRotationX(30);
}
if (view3.getRotationY() == 30f) {
view3.setRotationY(0);
} else {
view3.setRotationY(30);
}
if (view4.getScaleX() == 0.5f && view4.getScaleY() == 0.5f) {
view4.setScaleX(1f);
view4.setScaleY(1f);
} else {
view4.setScaleX(0.5f);
view4.setScaleY(0.5f);
}
複製代碼
最終效果:
其實 Transition
的原理很簡單,大體的邏輯以下:
那落實到代碼中,首先先集成 Transition
類,會讓你實現三個方法:captureStartValues
、captureEndValues
和createAnimator
。
定義你關心的屬性值;
官方建議屬性定義的規則爲:package_name:transition_class:property_name
.
好比
private static String PROPNAME_TEXT_COLOR = "xiaweizi:changeTextColor:color";
複製代碼
我想在文本顏色發生改變時作轉場動畫,就能夠定義上述的屬性。
記錄起始狀態的屬性;
void captureStartValues(TransitionValues transitionValues) void captureEndValues(TransitionValues transitionValues);
複製代碼
上述方法分別存儲起始狀態下對應的屬性值:
transitionValues.values.put(PROPNAME_TEXT_COLOR, view.getCurrentTextColor());
複製代碼
建立動畫;
Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, final TransitionValues endValues) 複製代碼
參數值的 startValues
和endValues
分別能夠拿到你存儲的屬性值,以後建立動畫並返回便可,後續系統會根據你建立的動畫進行轉場。
是否是很簡單,接下來經過幾個案例帶你們感覺一下:
ChangeTextTransition.java 該類中定義了:
private static String PROPNAME_TEXT = "xiaweizi:changeText:text";
private static String PROPNAME_TEXT_COLOR = "xiaweizi:changeTextColor:color";
private static String PROPNAME_TEXT_SIZE = "xiaweizi:changeTextSize:size";
private static String PROPNAME_TEXT_LEVEL = "xiaweizi:changeTextTypeface:level";
複製代碼
分別表明文本內容變化、文本顏色變化、文本大小變化和文本字體變化。咱們只挑一個文本顏色來看一下動畫是如何實現的:
// 記錄下起始狀態屬性值
private void captureValues(TransitionValues transitionValues) {
if (transitionValues == null || !(transitionValues.view instanceof TextView)) return;
TextView view = (TextView) transitionValues.view;
transitionValues.values.put(PROPNAME_TEXT, view.getText());
transitionValues.values.put(PROPNAME_TEXT_COLOR, view.getCurrentTextColor());
transitionValues.values.put(PROPNAME_TEXT_SIZE, view.getTextSize());
transitionValues.values.put(PROPNAME_TEXT_LEVEL, view.getTag(R.id.type_face_level));
}
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, final TransitionValues endValues) {
if (startValues == null || endValues == null) {
return null;
}
if (!(endValues.view instanceof TextView)) {
return super.createAnimator(sceneRoot, startValues, endValues);
}
TextView endView = (TextView) endValues.view;
int startTextColor = (int) startValues.values.get(PROPNAME_TEXT_COLOR);
int endTextColor = (int) endValues.values.get(PROPNAME_TEXT_COLOR);
ObjectAnimator animator = ObjectAnimator.ofArgb(endView, new TextColorProperty(), startTextColor, endTextColor);
animator.setDuration(300);
return animator;
}
複製代碼
看一下這四種屬性發生變化時的效果:
相似於文本顏色,只不過針對的是 view.setBackground()
,主要的代碼在於建立 Animator
:
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, final TransitionValues endValues) {
if (startValues == null || endValues == null) {
return null;
}
final View endView = endValues.view;
ColorDrawable startColorDrawable = (ColorDrawable) startValues.values.get(PROPNAME_COLOR);
ColorDrawable endColorDrawable = (ColorDrawable) endValues.values.get(PROPNAME_COLOR);
if (startColorDrawable == null || endColorDrawable == null) return super.createAnimator(sceneRoot, startValues, endValues);
final int startColor = startColorDrawable.getColor();
final int endColor = endColorDrawable.getColor();
ValueAnimator animator = ValueAnimator.ofObject(new ArgbEvaluator(), startColor, endColor);
animator.setDuration(300);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int animatedValue = (int) animation.getAnimatedValue();
endView.setBackgroundColor(animatedValue);
}
});
return animator;
}
複製代碼
最終效果:
有的時候發現,在切換圖片的時候過分會很生硬,那能夠經過在對 View
的 alpha
屬性從 101 的過程當中替換圖片,這樣顯得很平滑。
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, final TransitionValues endValues) {
if (startValues == null || endValues == null) {
return null;
}
if (!(endValues.view instanceof ImageView)) {
return super.createAnimator(sceneRoot, startValues, endValues);
}
final ImageView endView = (ImageView) endValues.view;
final Drawable startDrawable = (Drawable) startValues.values.get(PROPNAME_IMAGE_RESOURCE);
final Drawable endDrawable = (Drawable) endValues.values.get(PROPNAME_IMAGE_RESOURCE);
ValueAnimator animator = ValueAnimator.ofFloat(0, 1f);
animator.setDuration(300);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float animatedValue = (float) animation.getAnimatedValue();
if (animatedValue <= 0.5f) {
endView.setImageDrawable(startDrawable);
float ratio = (0.5f - animatedValue) / 0.5f;
endView.setAlpha(ratio);
} else {
endView.setImageDrawable(endDrawable);
float ratio = (animatedValue - 0.5f) / 0.5f;
endView.setAlpha(ratio);
}
}
});
return animator;
複製代碼
最終效果:
除了 View
原生的屬性,自定義屬性一樣也能夠。
建立 Animator
沒什麼區別:
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, final TransitionValues endValues) {
if (startValues == null || endValues == null) {
return null;
}
if (!(endValues.view instanceof TransitionView)) {
return super.createAnimator(sceneRoot, startValues, endValues);
}
final TransitionView endView = (TransitionView) endValues.view;
final float startRatio = (float) startValues.values.get(PROPNAME_CUSTOM_RATIO);
final float endRatio = (float) endValues.values.get(PROPNAME_CUSTOM_RATIO);
ObjectAnimator animator = ObjectAnimator.ofFloat(endView, "ratio", startRatio, endRatio);
animator.setDuration(300);
return animator;
}
複製代碼
主要在自定義 View
的繪製邏輯:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 繪製左邊
canvas.save();
mRect.set(0, 0, (int) (getWidth() * mRatio), getHeight());
canvas.clipRect(mRect);
mTextPaint.setColor(mStartColor);
TransitionUtils.drawTextCenter(canvas, "文本三", getWidth() / 2, getHeight() / 2, mTextPaint);
canvas.restore();
// 繪製右邊
canvas.save();
mRect.set((int) (getWidth() * mRatio), 0, getWidth(), getHeight());
canvas.clipRect(mRect);
mTextPaint.setColor(mEndColor);
TransitionUtils.drawTextCenter(canvas, "三本文", getWidth() / 2, getHeight() / 2, mTextPaint);
canvas.restore();
}
複製代碼
最終的效果:
終於開始介紹文章開頭的效果是如何實現的:
有了前面的基礎鋪墊,實現起來就很簡單。
Scene
就是爲這種場景的過分而設計,不須要關注過分過程,只須要傳入先後的佈局,並保證各個元素的 id
保持一致便可。
layout
;layout_scene1.xml
和 layout_scene2.xml
具體代碼就補貼了Scene
對象; mScene1 = Scene.getSceneForLayout(mRoot, R.layout.layout_scene1, this);
mScene2 = Scene.getSceneForLayout(mRoot, R.layout.layout_scene2, this);
複製代碼
Transition
;咱們把以前自定的組合成 TransitionSet
: public class SceneTransition extends TransitionSet {
public SceneTransition() {
init();
}
public SceneTransition(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
addTransition(new ChangeTextTransition())
.addTransition(new ChangeScroll())
.addTransition(new ChangeBackgroundColorTransition())
.addTransition(new ChangeBounds());
}
}
複製代碼
TransitionManager.go(mScene1, mTransition);
TransitionManager.go(mScene2, mTransition);
複製代碼
到此,先詳細的和你們分享了系統自帶的 Transition
,並分析了其實現細節和原理,提供了多個自定義 Transition
,接着瞭解了 Scene
建立過程,並經過簡答的 demo
實現了從一個場景到另外一個場景的過分效果,由淺入深,圖文並茂,但願能夠幫助到你們。
文中的項目已上傳到 TransitionDemo。