關(guān)鍵類
- android.transition.TransitionManager
- android.transition.Transition 抽象類
- TransitionSet
- AutoTransition
- ChangeBounds
- Visibility 抽象類
- Fade
- Explode ( design 包中無適配)
- Slide ( design 包中無適配)
- TextScale
- ChangeClipBounds ( design 包中無適配)
- ChangeImageTransform ( design 包中無適配)
- ChangeScroll ( design 包中無適配)
- ChangeTransform ( design 包中無適配)
- TransitionSet
介紹
Visibility 抽象類的子類:Fade女坑, Explode换棚, Slide 動畫作用于 View 的 Visibility 屬性改變的時候。
適配
對應(yīng)的適配包在designcom.android.support:design:x.x.x
包中
compile 'com.android.support:design:25.0.0'
更好的適配方案: (使用的時候注意導(dǎo)包 com.transitionseverywhere.xxx
)
compile "com.andkulikov:transitionseverywhere:1.6.9"
上代碼
下面的例子 使用適配包 compile "com.andkulikov:transitionseverywhere:1.6.9" 測試系統(tǒng) Android4.1
默認(rèn)效果
布局關(guān)鍵代碼:
<LinearLayout
android:id="@+id/ll_container_one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/btn_one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fad"/>
<TextView
android:id="@+id/tv_one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Transitions are awesome!"
android:visibility="gone"/>
</LinearLayout>
Activity
中代碼:
final LinearLayout transitionsContainer = (LinearLayout) findViewById(R.id.ll_container_one);
final TextView text = (TextView) findViewById(R.id.tv_one);
final Button button = (Button) findViewById(R.id.btn_one);
button.setOnClickListener(v -> {
TransitionManager.beginDelayedTransition(transitionsContainer);
if (text.getVisibility() == View.VISIBLE) {
text.setVisibility(View.GONE);
} else {
text.setVisibility(View.VISIBLE);
}
});
最終效果:
代碼解釋
TransitionManager 中的 beginDelayedTransition 方法:
public static void beginDelayedTransition(final ViewGroup sceneRoot){...}
public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition) {...}
- 方法一:效果如上面的默認(rèn)效果
- 方法二:參數(shù)而用于定制動畫效果翼虫, 參數(shù)為 Transition 的子類毁涉,常用的有:Fade, ChangeBounds, Slide,
對用畫具體效果做定制: 設(shè)置動畫時間沉帮,設(shè)置動畫加速度,設(shè)置動畫延時
transition.setDuration(300);
transition.setInterpolator(new FastOutSlowInInterpolator());
transition.setStartDelay(200);
Fade 淡出淡入
布局如上 更改Activity
中代碼:
final LinearLayout transitionsContainer = (LinearLayout) findViewById(R.id.ll_container_one);
final TextView text = (TextView) findViewById(R.id.tv_one);
final Button button = (Button) findViewById(R.id.btn_one);
button.setOnClickListener(v -> {
Fade fade = new Fade();
TransitionManager.beginDelayedTransition(transitionsContainer贫堰, fade);
if (text.getVisibility() == View.VISIBLE) {
text.setVisibility(View.GONE);
} else {
text.setVisibility(View.VISIBLE);
}
});
最終效果
Slide 移動
布局如上 更改Activity
中代碼:
final LinearLayout transitionsContainer = (LinearLayout) findViewById(R.id.ll_container_one);
final TextView text = (TextView) findViewById(R.id.tv_one);
final Button button = (Button) findViewById(R.id.btn_one);
button.setOnClickListener(v -> {
// Slide slide = new Slide();
Slide slide = new Slide(Gravity.RIGHT);
TransitionManager.beginDelayedTransition(transitionsContainer, slide);
if (text.getVisibility() == View.VISIBLE) {
text.setVisibility(View.GONE);
} else {
text.setVisibility(View.VISIBLE);
}
});
最終效果
Slide 構(gòu)造方法
/**
* Constructor using the default {@link Gravity#BOTTOM}
* slide edge direction.
*/
public Slide() {
setSlideEdge(Gravity.BOTTOM);
}
設(shè)置滑出方向:
/**
* Constructor using the provided slide edge direction.
*/
public Slide(@GravityFlag int slideEdge) {
setSlideEdge(slideEdge);
}
Scale 縮放
布局如上 更改Activity
中代碼:
final LinearLayout transitionsContainer = (LinearLayout) findViewById(R.id.ll_container_one);
final TextView text = (TextView) findViewById(R.id.tv_one);
final Button button = (Button) findViewById(R.id.btn_one);
button.setOnClickListener(v -> {
// Scale scale = new Scale();
Scale scale = new Scale(0.7f);
TransitionManager.beginDelayedTransition(transitionsContainer, scale);
if (text.getVisibility() == View.VISIBLE) {
text.setVisibility(View.GONE);
} else {
text.setVisibility(View.VISIBLE);
}
});
最終效果
Scale 構(gòu)造方法
public Scale() {
}
/**
* @param disappearedScale Value of scale on start of appearing or in finish of disappearing.
* Default value is 0. Can be useful for mixing some Visibility
* transitions, for example Scale and Fade
*/
public Scale(float disappearedScale) {
setDisappearedScale(disappearedScale);
}
參數(shù)設(shè)置了縮放起始值或者最終值穆壕。
TransitionSet 動畫集合
final LinearLayout transitionsContainer = (LinearLayout) findViewById(R.id.ll_container_one);
final TextView text = (TextView) findViewById(R.id.tv_one);
final Button button = (Button) findViewById(R.id.btn_one);
button.setOnClickListener(v -> {
TransitionSet set = new TransitionSet()
.addTransition(new Scale(0.7f))
.addTransition(new Fade())
.setInterpolator(visible ? new LinearOutSlowInInterpolator() :
new FastOutLinearInInterpolator());
TransitionManager.beginDelayedTransition(transitionsContainer, set);
if (text.getVisibility() == View.VISIBLE) {
text.setVisibility(View.GONE);
} else {
text.setVisibility(View.VISIBLE);
}
});
最終效果
Recolor 顏色漸變
空間的背景色或者文字顏色改變的動畫
布局
<LinearLayout
android:id="@+id/ll_container_recolor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:visibility="visible">
<Button
android:id="@+id/btn_recolor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Recolor"/>
<Button
android:id="@+id/btn_normal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="Normal"
android:visibility="visible"/>
</LinearLayout>
Activity
代碼
final ViewGroup transitionsContainerRecolor = (ViewGroup) findViewById(R.id.ll_container_recolor);
final Button btnRecolor = (Button) findViewById(R.id.btn_recolor);
final Button btnNormal = (Button) findViewById(R.id.btn_normal);
int green = getResources().getColor(R.color.green);
int white = getResources().getColor(R.color.white);
int grey = getResources().getColor(R.color.grey);
btnRecolor.setOnClickListener(v -> {
TransitionManager.beginDelayedTransition(transitionsContainerRecolor, new Recolor());
// btnRecolor.setBackgroundColor(visible ? green : white); // 無動畫效果
btnRecolor.setTextColor(visible ? white : grey);
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
// btnRecolor.setBackground(new ColorDrawable(visible ? green : white));
// } else {
btnRecolor.setBackgroundDrawable(new ColorDrawable(visible ? green : white));
// }
visible = !visible;
});
btnNormal.setOnClickListener(v -> {
btnNormal.setBackgroundColor(visible ? green : white);
btnNormal.setTextColor(visible ? white : green);
visible = !visible;
});
注意: btnRecolor.setBackgroundColor(visible ? green : white); // 無動畫效果
通過 setBackgroundColor
背景色時沒有動畫效果,可以使用 setBackground
, setBackgroundDrawable
最終效果
Rotate 旋轉(zhuǎn)
布局
<LinearLayout
android:id="@+id/ll_container_rotate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:visibility="visible">
<Button
android:id="@+id/btn_rotate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rotate"/>
<ImageView
android:id="@+id/iv_rotate"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="10dp"
android:src="@drawable/ic_clear_black_24dp"
android:visibility="visible"/>
</LinearLayout>
Activity
代碼
final ViewGroup transitionsContainerRotate = (ViewGroup) findViewById(R.id.ll_container_rotate);
final Button btnRotate = (Button) findViewById(R.id.btn_rotate);
ImageView ivRotate = (ImageView) findViewById(R.id.iv_rotate);
btnRotate.setOnClickListener(v -> {
TransitionManager.beginDelayedTransition(transitionsContainerRotate, new Rotate());
ivRotate.setRotation(isRotated ? 0 : 135);
isRotated = !isRotated;
});
最終效果
ChangeText 文字改變時動畫
布局
<LinearLayout
android:id="@+id/ll_container_change_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:visibility="visible">
<Button
android:id="@+id/btn_change_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ChangeText"/>
<TextView
android:id="@+id/tv_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Transitions are awesome!"
android:visibility="visible"/>
</LinearLayout>
Activity
代碼
final LinearLayout transitionsContainerChangeText = (LinearLayout) findViewById(R.id.ll_container_change_text);
final TextView tvText = (TextView) findViewById(R.id.tv_text);
final Button btnChangeText = (Button) findViewById(R.id.btn_change_text);
String secText = " Second Text";
String firstText = "First Text";
btnChangeText.setOnClickListener(v -> {
TransitionManager.beginDelayedTransition(transitionsContainerChangeText,
new ChangeText().setChangeBehavior(ChangeText.CHANGE_BEHAVIOR_OUT_IN));
tvText.setText(isFirstText ? secText : firstText);
isFirstText = !isFirstText;
});
最終效果
TransitionName 制作 打亂動畫
使用場景:
- 需要動態(tài)生成 ViewGroup 的子 View 其屏, 并且子 View 內(nèi)容需要更新時
- 需要制作隨機(jī)時
布局
注意:這里把 Button 移到了 LinearLayout 的外面喇勋,原因是一會創(chuàng)建子 View 的時候會先刪除所有子 View(Button 也會被刪除)。
<Button
android:id="@+id/btn_transition_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="打亂"/>
<LinearLayout
android:id="@+id/ll_container_transition_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:visibility="visible">
Activity
代碼
// TransitionName 做打亂動畫
final LinearLayout transitionsContainerTransitionName = (LinearLayout) findViewById(R.id.ll_container_transition_name);
final Button btnTransitionName = (Button) findViewById(R.id.btn_transition_name);
LayoutInflater inflater = LayoutInflater.from(this);
ArrayList<String> titles = new ArrayList<>();
for (int i = 0; i < 4; i++) {
titles.add(String.format(Locale.CHINA, "Item %d", i));
}
createViews(inflater, transitionsContainerTransitionName, titles);
btnTransitionName.setOnClickListener(v -> {
TransitionManager.beginDelayedTransition(transitionsContainerTransitionName, new ChangeBounds());
Collections.shuffle(titles);
createViews(inflater, transitionsContainerTransitionName, titles);
});
// 獨立的方法
// In createViews we should provide transition name for every view.
private static void createViews(LayoutInflater inflater, ViewGroup layout, List<String> titles) {
layout.removeAllViews();
for (String title : titles) {
TextView textView = (TextView) inflater.inflate(R.layout.text_view, layout, false);
textView.setText(title);
TransitionManager.setTransitionName(textView, title);
layout.addView(textView);
}
}
最終效果
Explode and Propagation 爆炸和傳播
爆炸效果偎行,和移動過渡動畫比較相似川背,不過子 View 的移動方向是由其所在的位置決定的。子 View 的移動方向需要通過計算得到(通過 setEpicenterCallback
方法)
關(guān)鍵代碼
這個例子使用 RecyclerView 和 GridLayoutManager 做基本布局蛤袒,點擊里面的 item 讓其消失熄云。
public void onClick(View clickedView) {
// save rect of view in screen coordinates
final Rect viewRect = new Rect();
clickedView.getGlobalVisibleRect(viewRect);
// create Explode transition with epicenter
Transition explode = new Explode()
.setEpicenterCallback(new Transition.EpicenterCallback() {
@Override
public Rect onGetEpicenter(Transition transition) {
return viewRect;
}
});
explode.setDuration(1000);
TransitionManager.beginDelayedTransition(recyclerView, explode);
// remove all views from Recycler View
recyclerView.setAdapter(null);
}
最終效果
ChangeImageTransform
Path (Curved) motion 路徑過渡動畫
所有的過渡動畫都需要兩個值:起始值和結(jié)束值
比如:通過 ChangeBounds 來改變 view 的位置,通過 setPathMotion 來提供路徑
布局代碼
<FrameLayout
android:id="@+id/fl_container_path_motion"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn_path_motion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Path Motion"/>
</FrameLayout>
Activity
代碼
// path motion 路徑過渡動畫
final FrameLayout transitionsContainerPathMotion = (FrameLayout) findViewById(R.id.fl_container_path_motion);
Button btnPathMotion = (Button) findViewById(R.id.btn_path_motion);
btnPathMotion.setOnClickListener(v -> {
TransitionManager.beginDelayedTransition(transitionsContainerPathMotion,
new ChangeBounds().setPathMotion(new ArcMotion()).setDuration(500));
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) btnPathMotion.getLayoutParams();
params.gravity = isReturnAnimation ? (Gravity.LEFT | Gravity.TOP) :
(Gravity.BOTTOM | Gravity.RIGHT);
btnPathMotion.setLayoutParams(params);
isReturnAnimation = !isReturnAnimation;
});
這里通過 LayoutParams 來控制 Button 在其父控件中的位置妙真。
最終效果
Targets 設(shè)置動畫的目標(biāo)對象
小總結(jié)
定義動畫:
TransitionManager.beginDelayedTransition( viewGroup, transition);
默認(rèn)情況下缴允,這里的 transition 動畫會作用于 viewGroup 中的所有子 View
當(dāng)我們需要在一個 ViewGroup
中定義多個動畫,作用于不同的子 View
該如何做珍德?
比如 讓一個 TextView 移動练般, 另一個 TextView 淡出
布局
<LinearLayout
android:id="@+id/ll_container_target"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:visibility="visible">
<Button
android:id="@+id/btn_target"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Target"/>
<TextView
android:id="@+id/tv_target_fade"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Transitions are awesome fade!"
android:visibility="visible"/>
<TextView
android:id="@+id/tv_target_slide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Transitions are awesome slide!"
android:visibility="visible"/>
</LinearLayout>
Activity
代碼
// Targets 設(shè)置動畫的目標(biāo)對象
final LinearLayout transitionsContainerTarget = (LinearLayout) findViewById(R.id.ll_container_target);
final Button btnTarget = (Button) findViewById(R.id.btn_target);
final TextView tvFade = (TextView) findViewById(R.id.tv_target_fade);
final TextView tvSlide = (TextView) findViewById(R.id.tv_target_slide);
btnTarget.setOnClickListener(v -> {
Slide slide = new Slide(Gravity.RIGHT);
slide.excludeTarget(tvFade, true);
Fade fade = new Fade();
fade.excludeTarget(tvSlide, true);
TransitionSet transitionSet = new TransitionSet()
.addTransition(slide)
.addTransition(fade);
TransitionManager.beginDelayedTransition(transitionsContainerTarget, transitionSet);
if (tvFade.getVisibility() == View.VISIBLE) {
tvFade.setVisibility(View.GONE);
tvSlide.setVisibility(View.GONE);
} else {
tvFade.setVisibility(View.VISIBLE);
tvSlide.setVisibility(View.VISIBLE);
}
});
最終效果
transition 其他有關(guān) target 方法
Methods to add target:
- addTarget(View target)?—?view itself
- addTarget(int targetViewId)?—?id of view
- addTarget(String targetName)?—?do you remember about method TransitionManager.setTransitionName?
- addTarget(Class targetType)?—?for example android.widget.TextView.class
To remove target:
- removeTarget(View target)
- removeTarget(int targetId)
- removeTarget(String targetName)
- removeTarget(Class target)
To exclude some views:
- excludeTarget(View target, boolean exclude)
- excludeTarget(int targetId, boolean exclude)
- excludeTarget(Class type, boolean exclude)
- excludeTarget(Class type, boolean exclude)
And for excluding all children of some ViewGroup:
- excludeChildren(View target, boolean exclude)
- excludeChildren(int targetId, boolean exclude)
- excludeChildren(Class type, boolean exclude)
使用 xml 創(chuàng)建 Transition
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:app="http://schemas.android.com/apk/res-auto"
app:transitionOrdering="together"
app:duration="400">
<changeBounds/>
<changeImageTransform/>
<fade
app:fadingMode="fade_in"
app:startDelay="200">
<targets>
<target app:targetId="@id/transition_title"/>
</targets>
</fade>
</transitionSet>
使用
TransitionInflater.from(getContext()).inflateTransition(R.anim.my_the_best_transition);
Activity and Fragment transitions
Custom Transitions
上面所有代碼在:github
參考: