使用CircularReveal動畫效果切換頁面
Android的Material Design設計理念, 帶來很多絢麗的動畫效果. 在頁面切換中, 最常用的就是SharedElementTransition, 通過設定控制元件的變換方式, 在進入時把控制元件變換為頁面, 在退出時, 把頁面變換為控制元件, 同時, 可以設定控制元件移動的軌跡. 這樣的控制元件, 可以應用於訊息通知, 或者廣告顯示, 提供非常好的使用者體驗. 那麼是如何實現的呢?
隨著廠商的版本迭代, 超過三分之一的手機都是5.0以上的作業系統, 隨著更多便宜的低端手機普及5.0+系統(如紅米系列), 給使用者帶來更好的體驗, 會大大增加應用留存率.
本文主要內容:
(1) 修改分享元素的滑動軌跡, 以90°圓弧方式出現和返回, 即transition動畫.
(2) 生成和銷燬頁面的爆炸和凝聚效果, 即CircularReveal的使用方式.
本文原始碼的GitHub下載地址.
最終動畫效果
1. 首頁
新建一個含義Fab按鈕的HelloWorld工程. 新增ButterKnife和Lambda庫.
設定Fab按鈕的跳轉事件.
// Fab的跳轉事件
public void startOtherActivity(View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ActivityOptions options =
ActivityOptions.makeSceneTransitionAnimation(this , mFab, mFab.getTransitionName());
startActivity(new Intent(this, OtherActivity.class), options.toBundle());
} else {
startActivity(new Intent(this, OtherActivity.class));
}
}
確保版本號大於5.0, 支援Material Design的動畫效果.
設定Fab的TransitionName, 即變化名稱.
<android.support .design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:clickable="true"
android:onClick="startOtherActivity"
android:src="@android:drawable/ic_dialog_email"
android:transitionName="@string/other_transition_name"
tools:targetApi="lollipop"/>
注意android:transitionName屬性, 表示變換名稱.
2. 跳轉頁
頁面由背景, 變化控制元件, 關閉控制元件, 這三部分組成.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:id="@+id/other_rl_container"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent">
<android.support.design.widget.FloatingActionButton
android:id="@+id/other_fab_circle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:transitionName="@string/other_transition_name"
app:backgroundTint="@color/colorAccent"
app:elevation="0dp"
app:fabSize="normal"
app:pressedTranslationZ="8dp"
tools:targetApi="21"/>
<TextView
android:id="@+id/other_tv_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorAccent"
android:gravity="center"
android:text="@string/other_text"
android:textColor="@android:color/white"
android:textSize="40sp"
android:textStyle="bold"
android:visibility="invisible"
tools:visibility="visible"/>
<ImageView
android:id="@+id/other_iv_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:contentDescription="@null"
android:onClick="backActivity"
android:src="@drawable/ic_close_white"
android:visibility="invisible"/>
</RelativeLayout>
注意, 變換控制元件FloatingActionButton與主頁的變換控制元件有相同的transitionName.
顯示邏輯, 設定入場和退場動畫, 爆炸的動畫效果.
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_other);
ButterKnife.bind(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setupEnterAnimation(); // 入場動畫
setupExitAnimation(); // 退場動畫
} else {
initViews();
}
}
退出邏輯, 退出動畫, 凝聚的動畫效果.
// 退出按鈕
public void backActivity(View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
onBackPressed();
} else {
defaultBackPressed();
}
}
下面仔細分析顯示和退場動畫.
3. 顯示動畫
分享元素切換使用弧度規矩, 即arc_motion, 90°旋轉過去和回來. 在動畫結束後, 頁面顯示使用爆炸效果.
// 入場動畫
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void setupEnterAnimation() {
Transition transition = TransitionInflater.from(this)
.inflateTransition(R.transition.arc_motion);
getWindow().setSharedElementEnterTransition(transition);
transition.addListener(new Transition.TransitionListener() {
@Override public void onTransitionStart(Transition transition) {
}
@Override public void onTransitionEnd(Transition transition) {
transition.removeListener(this);
animateRevealShow();
}
@Override public void onTransitionCancel(Transition transition) {
}
@Override public void onTransitionPause(Transition transition) {
}
@Override public void onTransitionResume(Transition transition) {
}
});
}
// 動畫展示
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void animateRevealShow() {
GuiUtils.animateRevealShow(
this, mRlContainer,
mFabCircle.getWidth() / 2, R.color.colorAccent,
new GuiUtils.OnRevealAnimationListener() {
@Override public void onRevealHide() {
}
@Override public void onRevealShow() {
initViews();
}
});
}
arc_motion角度變換
<transitionSet
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:interpolator="@android:interpolator/linear_out_slow_in">
<changeBounds>
<!--suppress AndroidElementNotAllowed -->
<arcMotion
android:maximumAngle="90"
android:minimumHorizontalAngle="90"
android:minimumVerticalAngle="0"/>
</changeBounds>
</transitionSet>
爆炸的動畫效果
// 圓圈爆炸效果顯示
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static void animateRevealShow(
final Context context, final View view,
final int startRadius, @ColorRes int color,
OnRevealAnimationListener listener) {
int cx = (view.getLeft() + view.getRight()) / 2;
int cy = (view.getTop() + view.getBottom()) / 2;
float finalRadius = (float) Math.hypot(view.getWidth(), view.getHeight());
// 設定圓形顯示動畫
Animator anim = ViewAnimationUtils.createCircularReveal(view, cx, cy, startRadius, finalRadius);
anim.setDuration(300);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
anim.addListener(new AnimatorListenerAdapter() {
@Override public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
view.setVisibility(View.VISIBLE);
listener.onRevealShow();
}
@Override public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
view.setBackgroundColor(ContextCompat.getColor(context, color));
}
});
anim.start();
}
使用CircularReveal, 即圓形顯示的動畫效果.
第一個引數是顯示的檢視, 第二個和第三個是變換的中心位置, 第三個和第四個是變換的起始半徑和結束半徑.
4. 退出動畫
退出動畫是凝聚效果, 同樣使用的是CircularReveal.
// 退出事件
@Override public void onBackPressed() {
GuiUtils.animateRevealHide(
this, mRlContainer,
mFabCircle.getWidth() / 2, R.color.colorAccent,
new GuiUtils.OnRevealAnimationListener() {
@Override
public void onRevealHide() {
defaultBackPressed();
}
@Override
public void onRevealShow() {
}
});
}
退出和顯示的區別就是起始和終止的半徑不同.
// 圓圈凝聚效果
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static void animateRevealHide(
final Context context, final View view,
final int finalRadius, @ColorRes int color,
OnRevealAnimationListener listener
) {
int cx = (view.getLeft() + view.getRight()) / 2;
int cy = (view.getTop() + view.getBottom()) / 2;
int initialRadius = view.getWidth();
// 與入場動畫的區別就是圓圈起始和終止的半徑相反
Animator anim = ViewAnimationUtils.createCircularReveal(view, cx, cy, initialRadius, finalRadius);
anim.setDuration(300);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
anim.addListener(new AnimatorListenerAdapter() {
@Override public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
view.setBackgroundColor(ContextCompat.getColor(context, color));
}
@Override public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
listener.onRevealHide();
view.setVisibility(View.INVISIBLE);
}
});
anim.start();
}
最後說一些有關使用者體驗的事情, 對於一款應用而言, 好的效能固然重要, 但優秀的設計也非常關鍵, 優異的產品需要優異的展現方式.
OK, that’s all! Enjoy it!