前言
使用Transition動畫框架,可以幫你做到:
1不同Activity切換的時候,根據每個activity對應的layout內容的不同做整體的場景變換的動畫。
2 不同activity切換的時候,不同activity對應的layout有相同的元素,比如activity1中有一個button,activity2有一個相同的button,transition框架可以在整體場景變換的同時,特別照顧到這個button,讓動畫變換的過程中,這個button享有視覺連貫性。
3 在相同的activity裏,當同一個view內容發生變化,比如在代碼中removie或者add了某個ui元素,或者更改了某個已有元素的尺寸,顏色信息,Transition動畫框架也可以根據這個變化做動畫變換。
一 不同Activity切換的Transition動畫
當從activity1跳轉到activity2的時候,1會執行exitTransition, 而2會執行enterTransition,你可以在代碼中使用
getWindow().setExitTransition(slide/fade...);
getWindow().setEnterTransition(slide/fade...);
爲這個activity設置它進入和退出時需要用到的過場動畫(就是方法參數),谷歌爲我們預設了幾個transition,分別是Explode,Slide,Fade。可以看一下他們的效果
Explode | Slide | Fade |
---|---|---|
在定義Explode,Slide,Fade這幾個動畫效果的時候,可以使用xml也可以直接在代碼中定義
1 使用xml
在res/transtion文件夾下定義一個xml文件
res/transition/activity_fade.xml
<?xml version="1.0" encoding="utf-8"?>
<fade xmlns:android="http://schemas.android.com/apk/res/"
android:duration="1000"/>
res/transition/activity_slide.xml
<?xml version="1.0" encoding="utf-8"?>
<slide xmlns:android="http://schemas.android.com/apk/res/"
android:duration="1000"/>
然後需要在代碼中使用TransitionInflator方法來加載這個xml文件生成對應的動畫效果。
MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_transition);
setupWindowAnimations();
}
private void setupWindowAnimations() {
Slide slide = TransitionInflater.from(this).inflateTransition(R.transition.activity_slide);
getWindow().setExitTransition(slide);
}
TransitionActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_transition);
setupWindowAnimations();
}
private void setupWindowAnimations() {
Fade fade = TransitionInflater.from(this).inflateTransition(R.transition.activity_fade);
getWindow().setEnterTransition(fade);
}
MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_transition);
setupWindowAnimations();
}
private void setupWindowAnimations() {
Slide slide = new Slide();
slide.setDuration(1000);
getWindow().setExitTransition(slide);
}
TransitionActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_transition);
setupWindowAnimations();
}
private void setupWindowAnimations() {
Fade fade = new Fade();
fade.setDuration(1000);
getWindow().setEnterTransition(fade);
}
在這個過程中
首先Actvity1啓動了Activity2
Transition框架發現Activity1退出,爲activity1裏的可見ui組件執行Slide動畫
Transition框架發現Activity2退出,爲activity2裏的可見ui組件執行Fade動畫
當按下返回鍵,如果我們沒有爲其設置returnTransition和reenterTransition選項的話,Transition框架會對應的activity執行我們爲其設置的enter和exit動畫運動過程相反的動畫作爲默認選項。
3 ReturnTransition & ReenterTransition
TransitionActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_transition);
setupWindowAnimations();
}
private void setupWindowAnimations() {
Fade fade = new Fade();
fade.setDuration(1000);
getWindow().setEnterTransition(fade);
Slide slide = new Slide();
slide.setDuration(1000);
getWindow().setReturnTransition(slide);
}
下面可以看到 沒有設置return動畫和設置了return動畫的區別Without Return Transition | With Return Transition |
---|---|
Enter: Fade
In | Enter: Fade In |
Exit: Fade
Out | Exit: Slide out |
你可以通過爲兩個不同activity中某個view設置一個唯一的統一的標識,告訴Transition這個view是這兩aictivity共享的元素,變換的時候要額外照顧到,那麼transition動畫框架就會在場景切換的時候,讓這個view順滑的從前一個activity轉移到另一個activity(注意,這裏的轉移並不是真正的轉移,這兩個item並不是同一個對象,他們是彼此獨立的兩個view)
1 設置windowContentTransition
首先要對app的style文件做一些修改
values/styles.xml
<style name="MaterialAnimations" parent="@style/Theme.AppCompat.Light.NoActionBar">
...
<item name="android:windowContentTransitions">true</item
...
</style>
還可以在這裏定義整個app默認的 enter, exit 和 shared element transitions動畫
<style name="MaterialAnimations" parent="@style/Theme.AppCompat.Light.NoActionBar">
...
<!-- specify enter and exit transitions -->
<item name="android:windowEnterTransition">@transition/explode</item>
<item name="android:windowExitTransition">@transition/explode</item>
<!-- specify shared element transitions -->
<item name="android:windowSharedElementEnterTransition">@transition/changebounds</item>
<item name="android:windowSharedElementExitTransition">@transition/changebounds</item>
...
</style>
2在佈局文件中爲享元view定義相同的Transition Name,通過android:transitioName來定義,享元之間id和屬性可以不同,但是TransitionName必須相同。
layout/activity_a.xml
<ImageView
android:id="@+id/small_blue_icon"
style="@style/MaterialAnimations.Icon.Small"
android:src="@drawable/circle"
android:transitionName="@string/blue_name" />
layout/activity_b.xml
<ImageView
android:id="@+id/big_blue_icon"
style="@style/MaterialAnimations.Icon.Big"
android:src="@drawable/circle"
android:transitionName="@string/blue_name" />
c 通過享元方式啓動另一個activity
通過ActivityOptions.makeSceneTransitionAnimation()方法來定義享元具體是哪個view和Transition Name
MainActivity.java
blueIconImageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent(MainActivity.this, SharedElementActivity.class);
View sharedView = blueIconImageView;
String transitionName = getString(R.string.blue_name);
ActivityOptions transitionActivityOptions = ActivityOptions.makeSceneTransitionAnimation(MainActivity.this, sharedView, transitionName);
startActivity(i, transitionActivityOptions.toBundle());
}
});
最終效果如下圖
三 在Fragment之間進行享元切換動畫
前兩步和上面基本相同
1 設置windowContentTransition
values/styles.xml
<style name="MaterialAnimations" parent="@style/Theme.AppCompat.Light.NoActionBar">
...
<item name="android:windowContentTransitions">true</item>
...
</style>
2 在佈局文件中爲享元view定義相同的Transition Namelayout/fragment_a.xml
<ImageView
android:id="@+id/small_blue_icon"
style="@style/MaterialAnimations.Icon.Small"
android:src="@drawable/circle"
android:transitionName="@string/blue_name" />
layout/fragment_b.xml
<ImageView
android:id="@+id/big_blue_icon"
style="@style/MaterialAnimations.Icon.Big"
android:src="@drawable/circle"
android:transitionName="@string/blue_name" />
3 享元方式啓動另一個Fragment
FragmentB fragmentB = FragmentB.newInstance(sample);
// Defines enter transition for all fragment views
Slide slideTransition = new Slide(Gravity.RIGHT);
slideTransition.setDuration(1000);
sharedElementFragment2.setEnterTransition(slideTransition);
// Defines enter transition only for shared element
ChangeBounds changeBoundsTransition = TransitionInflater.from(this).inflateTransition(R.transition.change_bounds);
fragmentB.setSharedElementEnterTransition(changeBoundsTransition);
getFragmentManager().beginTransaction()
.replace(R.id.content, fragmentB)
.addSharedElement(blueView, getString(R.string.blue_name))
.commit();
最終結果如下
四 疊加Transition動畫效果
你可以設置是否讓exit動畫和enter動畫疊加在一起執行
如果設置爲true 那麼enter動畫會立刻執行
如果設置爲false 那麼exter動畫會等到exit動畫執行完畢之後再執行
這對Fragment和Activity之間的享元切換都有效
FragmentB fragmentB = FragmentB.newInstance(sample);
// Defines enter transition for all fragment views
Slide slideTransition = new Slide(Gravity.RIGHT);
slideTransition.setDuration(1000);
sharedElementFragment2.setEnterTransition(slideTransition);
// Defines enter transition only for shared element
ChangeBounds changeBoundsTransition = TransitionInflater.from(this).inflateTransition(R.transition.change_bounds);
fragmentB.setSharedElementEnterTransition(changeBoundsTransition);
// Prevent transitions for overlapping
fragmentB.setAllowEnterTransitionOverlap(overlap);
fragmentB.setAllowReturnTransitionOverlap(overlap);
getFragmentManager().beginTransaction()
.replace(R.id.content, fragmentB)
.addSharedElement(blueView, getString(R.string.blue_name))
.commit();
動畫是否疊加的具體區別見下面的動圖
Overlap True | Overlap False |
---|---|
Fragment_2 appears on top of Fragment_1 | Fragment_2 waits until Fragment_1 is gone |
1 Scene
在activity對應的佈局文件中,定義一個佔位用的視圖,一般用framelayout,稱之爲稍後場景變換動畫執行
所在的根視圖rootView,然後在layout文件夾下,新建幾個layout文件,每一個layout文件對應的視圖都是一個scene,
可以這麼理解,我們打算在Activity對應的界面視圖的上半部分放置即將運行的動畫,這個上半部分我們用幀佈局
佔位,稱之爲rootView,然後動畫效果是一個小球從左邊移動到右邊,那麼我們只需要在layout文件夾下面
定義兩個layout文件,一個layout文件中,小球在左邊,另一個文件中小球在右邊。這兩個layout,在代碼中通過
Scene.getSceneForLayout(rootView,R.layout.***,this)生成一個Scene,此時,rootView和每一個scene的鏈接關係
就建立起來了。在通過TransitionManager.go方法就可以讓動畫在rootView中運行起來。
還是看一下代碼吧
scene1 = Scene.getSceneForLayout(sceneRoot, R.layout.activity_animations_scene1, this);
scene2 = Scene.getSceneForLayout(sceneRoot, R.layout.activity_animations_scene2, this);
scene3 = Scene.getSceneForLayout(sceneRoot, R.layout.activity_animations_scene3, this);
scene4 = Scene.getSceneForLayout(sceneRoot, R.layout.activity_animations_scene4, this);
(...)
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button1:
TransitionManager.go(scene1, new ChangeBounds());
break;
case R.id.button2:
TransitionManager.go(scene2, TransitionInflater.from(this).inflateTransition(R.transition.slide_and_changebounds));
break;
case R.id.button3:
TransitionManager.go(scene3, TransitionInflater.from(this).inflateTransition(R.transition.slide_and_changebounds_sequential));
break;
case R.id.button4:
TransitionManager.go(scene4, TransitionInflater.from(this).inflateTransition(R.transition.slide_and_changebounds_sequential_with_interpolators));
break;
}
}
上面的代碼就會在同一個Activity中的四個Scene中間生成對應的動畫,而且每個動畫都不同
2 佈局文件中View屬性的變動
Transition動畫還可以檢測到同一個佈局文件中某些視圖元素的屬性變化,然後做相應的動畫效果,你可以隨意在代碼中修改
你需要的一切變動,比如顏色,大小,位置之類的,之後的動畫Transition會自動幫你實現。
做到這些 只需要
a 先調用BeginDelayTransition方法
TransitionManager.beginDelayedTransition(sceneRoot);
b 更改layout佈局中的view屬性
ViewGroup.LayoutParams params = greenIconView.getLayoutParams();
params.width = 200;
greenIconView.setLayoutParams(params);
更改greenIconView的width時,會觸發view的onMeasure()方法,此時Transition框架會記錄下該view的起始width值並
做出相應的動畫
六 享元動畫+circle Reveal動畫
享元方式的過場動畫前面已經提到了,而CircleReveal是屬性動畫的一種,和享元切換一樣都是安卓5.0之後引入的新特性
,對了忘了提,上面享元切換和這個circleReveal動畫在執行前最好都加上一個版本判斷
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
}
CircleReveal實現圓形縮放效果,可用來突出顯示某個部分。他和享元切換一起使用可以引導用戶的視覺焦點,給他的點擊以反饋。
上面的動畫 依次執行了
從MainActivity到RevealActivity的一個享元切換動畫
在RevealActivity中有一個監聽器,當它監聽到RevealActivity中的享元切換動畫執行完之後,爲Toobar執行了一個CircleReveal屬性動畫
同時爲RevealActivity中其他視圖執行了一個放大的屬性動畫
Listen to shared element enter transition end
Transition transition = TransitionInflater.from(this).inflateTransition(R.transition.changebounds_with_arcmotion);
getWindow().setSharedElementEnterTransition(transition);
transition.addListener(new Transition.TransitionListener() {
@Override
public void onTransitionEnd(Transition transition) {
animateRevealShow(toolbar);
animateButtonsIn();
}
(...)
});
Reveal Toolbar
private void animateRevealShow(View viewRoot) {
int cx = (viewRoot.getLeft() + viewRoot.getRight()) / 2;
int cy = (viewRoot.getTop() + viewRoot.getBottom()) / 2;
int finalRadius = Math.max(viewRoot.getWidth(), viewRoot.getHeight());
Animator anim = ViewAnimationUtils.createCircularReveal(viewRoot, cx, cy, 0, finalRadius);
viewRoot.setVisibility(View.VISIBLE);
anim.setDuration(1000);
anim.setInterpolator(new AccelerateInterpolator());
anim.start();
}
Scale up activity layout views
private void animateButtonsIn() {
for (int i = 0; i < bgViewGroup.getChildCount(); i++) {
View child = bgViewGroup.getChildAt(i);
child.animate()
.setStartDelay(100 + i * DELAY)
.setInterpolator(interpolator)
.alpha(1)
.scaleX(1)
.scaleY(1);
}
}
更多CircleReveal動畫的例子
你可以隨意設置你想要的Reveal動畫,但前提是這些動畫可以有效的告知用戶 app對用戶的點擊都做了什麼反饋
1 從目標視圖中心開始動畫
int cx = (viewRoot.getLeft() + viewRoot.getRight()) / 2;
int cy = viewRoot.getTop();
int finalRadius = Math.max(viewRoot.getWidth(), viewRoot.getHeight());
Animator anim = ViewAnimationUtils.createCircularReveal(viewRoot, cx, cy, 0, finalRadius);
viewRoot.setBackgroundColor(color);
anim.start();
2 從目標視圖頂部開始 並結合其他屬性動畫
int cx = (viewRoot.getLeft() + viewRoot.getRight()) / 2;
int cy = (viewRoot.getTop() + viewRoot.getBottom()) / 2;
int finalRadius = Math.max(viewRoot.getWidth(), viewRoot.getHeight());
Animator anim = ViewAnimationUtils.createCircularReveal(viewRoot, cx, cy, 0, finalRadius);
viewRoot.setBackgroundColor(color);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
animateButtonsIn();
}
});
anim.start();
3 從點擊位置開始動畫
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
if (view.getId() == R.id.square_yellow) {
revealFromCoordinates(motionEvent.getRawX(), motionEvent.getRawY());
}
}
return false;
}
private Animator animateRevealColorFromCoordinates(int x, int y) {
float finalRadius = (float) Math.hypot(viewRoot.getWidth(), viewRoot.getHeight());
Animator anim = ViewAnimationUtils.createCircularReveal(viewRoot, x, y, 0, finalRadius);
viewRoot.setBackgroundColor(color);
anim.start();
}
4 結合Transition動畫
Transition transition = TransitionInflater.from(this).inflateTransition(R.transition.changebounds_with_arcmotion);
transition.addListener(new Transition.TransitionListener() {
@Override
public void onTransitionEnd(Transition transition) {
animateRevealColor(bgViewGroup, R.color.red);
}
(...)
});
TransitionManager.beginDelayedTransition(bgViewGroup, transition);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
btnRed.setLayoutParams(layoutParams);