Android 動(dòng)畫(huà)總結(jié)(1) - 概述
Android 動(dòng)畫(huà)總結(jié)(2) - 幀動(dòng)畫(huà)
Android 動(dòng)畫(huà)總結(jié)(3) - 補(bǔ)間動(dòng)畫(huà)
Android 動(dòng)畫(huà)總結(jié)(4) - 插值器
Android 動(dòng)畫(huà)總結(jié)(5) - 屬性動(dòng)畫(huà)
Android 動(dòng)畫(huà)總結(jié)(6) - 估值器
Android 動(dòng)畫(huà)總結(jié)(7) - ViewGroup 子元素間的動(dòng)畫(huà)
Android 動(dòng)畫(huà)總結(jié)(9) - 過(guò)渡動(dòng)畫(huà)
對(duì)于 Activity,在 startActivity 或 finish 后調(diào)用
overridePendingTransition(R.anim.activity_in, R.anim.activity_out)
對(duì)于 Fragment:
supportFragmentManager.beginTransaction().setCustomAnimations(R.anim.fragment_enter, R.anim.fragment_exit)
ActivityOptions
從 Android 5.0 之后,可以用 ActivityOptions 來(lái)實(shí)現(xiàn),ActivityOptionsCompat 是 support v4 的兼容實(shí)現(xiàn),可以支持到 4.1(SDK 16),它有幾個(gè) make 開(kāi)頭的方法
- makeCustomAnimation(Context context, int enterResId, int exitResId)
- makeScaleUpAnimation(View source, int startX, int startY, int startWidth, int startHeight)
- makeThumbnailScaleUpAnimation(View source, Bitmap thumbnail, int startX, int startY)
- makeClipRevealAnimation(View source, int startX, int startY, int width, int height)
- makeSceneTransitionAnimation(Activity activity, View sharedElement, String sharedElementName)
- makeSceneTransitionAnimation(Activity activity, Pair<View, String>... sharedElements)
makeCustomAnimation
custom.onClick {
val compat = ActivityOptionsCompat.makeCustomAnimation(ctx, R.anim.activity_in, R.anim.activity_out)
start(it!!, compat)
}
private fun start(view: View, compat: ActivityOptionsCompat) {
val intent = Intent(ctx, OptionAfterActivity::class.java)
intent.putExtra("from", (view as Button).text)
// SDK 16 以下會(huì)忽略 compat.toBundle()
ActivityCompat.startActivity(ctx, intent, compat.toBundle())
}
最普通的,效果和過(guò)去的 overridePendingTransition 一樣。
OptionAfterActivity 的布局就只有一個(gè) TextView
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".OptionAfterActivity"
android:background="#5500f2f0">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#000000"
android:textSize="18sp"
android:layout_centerInParent="true"/>
</RelativeLayout>
class OptionAfterActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_option_after)
// 接收前一個(gè)方法傳過(guò)來(lái)的名字顯示出來(lái)
tv.text = intent.extras["from"].toString()
}
override fun onBackPressed() {
ActivityCompat.finishAfterTransition(this)
}
}
發(fā)現(xiàn) ActivityCompat.finishAfterTransition(this)
并沒(méi)什么用。看源碼
public static void finishAfterTransition(Activity activity) {
if (Build.VERSION.SDK_INT >= 21) {
activity.finishAfterTransition();
} else {
activity.finish();
}
}
好吧,ActivityCompat.startActivity
是 SDK >= 16
就生效,這退出的必須 >= 21
才行。再繼續(xù)往下走
public void finishAfterTransition() {
if (!mActivityTransitionState.startExitBackTransition(this)) {
finish();
}
}
public boolean startExitBackTransition(final Activity activity) {
if (mEnteringNames == null || mCalledExitCoordinator != null) {
return false;
} else {
if (!mHasExited) {
// 能判斷對(duì)這里才真正執(zhí)行頁(yè)面返回的動(dòng)畫(huà)
}
return true;
}
}
而這個(gè) mEnteringNames 的定義是:
The shared elements that the calling Activity has said that they transferred to this
很可惜,此時(shí)的 mEnteringNames 是 null,直接返回 false 調(diào)用 finish 了。下面會(huì)講到什么是 shared elements
。
makeScaleUpAnimation
參照 Activity 上的某個(gè) View,新 Activity 從指定大小放大到最大顯示。
動(dòng)畫(huà)速度太快了,也沒(méi)找到可以控制時(shí)間的地方,查了許多資料,包括看源碼注釋?zhuān)鋵?shí)還不是很明白這個(gè) View 到底有沒(méi)有放大。
使用的一個(gè)場(chǎng)景是可能點(diǎn)擊一個(gè)小 View,然后第二個(gè)頁(yè)面某個(gè)位置顯示的放大版的,這樣看著好像是點(diǎn)擊放大到另一個(gè)頁(yè)面似的。
scaleUp.onClick {
val compat = ActivityOptionsCompat.makeScaleUpAnimation(image, image.width / 2, image.height / 2, 0, 0)
start(it!!, compat)
}
看下它的 5 個(gè)參數(shù):
- View source - 參照物
- int startX - 相對(duì)于 source,新 Activity 開(kāi)始的位置
- int startY - 同 startX,只不過(guò)這是 Y 軸方向上的
- int startWidth - 第二個(gè) Activity 在做放大動(dòng)畫(huà)前一開(kāi)始的初始寬度
- int startHeight - 這當(dāng)然就是初始高度了
makeThumbnailScaleUpAnimation
和 makeScaleUpAnimation 的區(qū)別是,不再是放大頁(yè)面上的一個(gè) View,而是指定一張圖,在轉(zhuǎn)場(chǎng)時(shí),放大這張圖片。不過(guò)也許是太快了,根本看不見(jiàn),也不知理解是否正確。
thumbnailScaleUp.onClick {
val compat = ActivityOptionsCompat.makeThumbnailScaleUpAnimation(text, BitmapFactory.decodeResource(resources, R.drawable.timg), 0, 0)
start(it!!, compat)
}
參數(shù):
- View source - 圖片放大的參照物
- Bitmap thumbnail - 要放大的圖片
- int startX - 相對(duì)于參照物 source,這張圖片開(kāi)始放大的 X 軸位置
- int startY - 圖片開(kāi)始放大相對(duì)于 source 的 Y 軸位置
makeClipRevealAnimation
說(shuō)是一個(gè)點(diǎn)圓形漸變到全部顯示,參數(shù)含義和 makeScaleUpAnimation 的一樣。也是太快,沒(méi)看出什么特別的。
clipReveal.onClick {
val compat = ActivityOptionsCompat.makeClipRevealAnimation(text, text.getWidth() / 2, text.getHeight() / 2, 0, 0)
start(it!!, compat)
}
makeSceneTransitionAnimation 單個(gè) View
Scene 就是場(chǎng)景,兩個(gè) Activity 中的某些 View 協(xié)同完成過(guò)渡動(dòng)畫(huà)。
在兩個(gè) Activity 的布局文件中,要協(xié)同做動(dòng)畫(huà)的 View 要有一個(gè)屬性 android:transitionName
并將值設(shè)為一樣的。
第一個(gè) Activity 的 xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp">
...
<ImageView
android:id="@+id/image1"
android:layout_width="230dp"
android:layout_height="78dp"
android:layout_gravity="center_horizontal"
android:background="@drawable/timg"
android:transitionName="image_name" />
...
</LinearLayout>
要跳轉(zhuǎn)的新 Activity 的 xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="@+id/image2"
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="@drawable/timg"
android:layout_alignParentBottom="true"
android:transitionName="image_name" />
</LinearLayout>
兩個(gè)要協(xié)同的 ImageView 都有一句 android:transitionName="image_name"
。
sceneTransitionSingleView.onClick {
val compat = ActivityOptionsCompat.makeSceneTransitionAnimation(this@OptionActivity, image1, "image_name")
start(it!!, compat)
}
參數(shù):
- Activity activity - 當(dāng)前所在 Activity
- View sharedElement - 要協(xié)同過(guò)渡的 View,就是
共享元素
- String sharedElementName - 就是 xml 里定義的 transitionName
上面說(shuō)到 ActivityCompat.finishAfterTransition(this)
沒(méi)生效是在一處判斷 shared elements
為 null 就返回執(zhí)行普通的 finish 了,現(xiàn)在這里有 shared elements
了,發(fā)現(xiàn)返回原來(lái)頁(yè)面確實(shí)也有動(dòng)畫(huà)效果了。
這種協(xié)同過(guò)渡用同類(lèi)型甚至內(nèi)容都差不多的 View 來(lái)做看著效果好,但就算讓兩個(gè)完全不一樣的 View 做協(xié)同過(guò)渡,也是可以的,如第一個(gè) Activity 的一個(gè) Button,點(diǎn)擊就跳轉(zhuǎn)到新 Activity,就讓這個(gè) Button 和新 Activity 里的一個(gè) TextView 做過(guò)渡,也是可以,效果還好,就是返回時(shí)有個(gè)突變。
<Button
android:id="@+id/sceneTransitionDifferent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="makeSceneTransitionAnimation(不同類(lèi)型View過(guò)渡)"
android:textAllCaps="false"
android:transitionName="transition" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#ff0000"
android:textSize="12sp"
android:background="#ffffff"
android:padding="10dp"
android:layout_margin="10dp"
android:text="共享元素"
android:transitionName="transition" />
sceneTransitionDifferent.onClick {
val compat = ActivityOptionsCompat.makeSceneTransitionAnimation(this@OptionActivity, sceneTransitionDifferent, "transition")
start(it!!, compat)
}
makeSceneTransitionAnimation 多個(gè) View
修改 xml 再給其它 View 也加上 android:transitionName。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp">
...
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:background="#669812"
android:text="我欲乘風(fēng)歸去"
android:layout_gravity="center_horizontal"
android:gravity="center"
android:textSize="14sp"
android:transitionName="text_name" />
<ImageView
android:id="@+id/image1"
android:layout_width="230dp"
android:layout_height="78dp"
android:layout_gravity="center_horizontal"
android:background="@drawable/timg"
android:transitionName="image_name" />
...
</LinearLayout>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="30dp"
android:background="#669812"
android:text="我欲乘風(fēng)歸去"
android:layout_gravity="center_horizontal"
android:gravity="center"
android:textSize="26sp"
android:transitionName="text_name" />
<ImageView
android:id="@+id/image2"
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="@drawable/timg"
android:layout_alignParentBottom="true"
android:transitionName="image_name" />
</LinearLayout>
sceneTransitionMultiView.onClick {
val textPair = android.support.v4.util.Pair<View, String>(text, "text_name")
val imagePair = android.support.v4.util.Pair<View, String>(image1, "image_name")
val compat = ActivityOptionsCompat.makeSceneTransitionAnimation(this@OptionActivity, textPair, imagePair)
start(it!!, compat)
}
有共享元素時(shí)的動(dòng)畫(huà)效果
使用共享元素時(shí) Activity 的效果
有三種:
- explode - 爆裂,從場(chǎng)景中間移動(dòng)視圖進(jìn)入或者退出屏幕
- slide - 滑動(dòng),視圖從場(chǎng)景的一個(gè)邊緣進(jìn)入或者退出屏幕
- android:slideEdge 屬性控制滑動(dòng)方向,取值可以是
LEFT, TOP, RIGHT, BOTTOM, START, END
- android:slideEdge 屬性控制滑動(dòng)方向,取值可以是
- fade - 淡入淡出,從場(chǎng)景添加或者移除一個(gè)視圖時(shí)改變他的透明
可以指定 target,只在某個(gè) View 或排除某個(gè) View 上做動(dòng)畫(huà)。
主題中可以配置
- android:windowEnterTransition - 當(dāng) A start B 時(shí),B 頁(yè)面進(jìn)入場(chǎng)景的 transition
- android:windowExitTransition - 當(dāng) A start B 時(shí),A 頁(yè)面退出場(chǎng)景的 transition
- android:windowReturnTransition - 當(dāng) B 返回 A 時(shí),B 頁(yè)面退出場(chǎng)景的 transition
- android:windowReenterTransition - 當(dāng) B 返回 A 時(shí),A 頁(yè)面進(jìn)入場(chǎng)景的 transition
如果不在主題配置,在 Activity 的代碼設(shè)置,如 getWindow().setEnterTransition(new Explode());
,那么
- setEnterTransition - B 中設(shè)置
- setExitTransition() - A 中設(shè)置
- setReturnTransition() - B 中設(shè)置
- setReenterTransition() - A 中設(shè)置
在 res/transiton
目錄創(chuàng)建兩個(gè)文件,可以定義其時(shí)間和插值器
transition_slide.xml
<?xml version="1.0" encoding="utf-8"?>
<slide xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:slideEdge="right" />
transition_set.xml
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:transitionOrdering="together" >
<explode
android:duration="2000"
android:interpolator="@android:interpolator/accelerate_decelerate" />
<fade
android:duration="2000" />
</transitionSet>
然后配置主題
<item name="android:windowEnterTransition">@transition/transition_set</item>
<item name="android:windowReturnTransition">@transition/transition_slide</item>
如果不配置主題,也可以在 B 頁(yè)面(不是跳轉(zhuǎn)前的 A)代碼設(shè)置
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val set = TransitionInflater.from(ctx).inflateTransition(R.transition.transition_set)
val slide = TransitionInflater.from(ctx).inflateTransition(R.transition.transition_slide)
window.enterTransition = set
window.returnTransition = slide
}
也可以不用 xml,全部代碼配置
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val set = TransitionSet()
set.ordering = TransitionSet.ORDERING_TOGETHER
val explode = Explode()
explode.duration = 2000
explode.interpolator = AccelerateInterpolator()
val fade = Fade()
fade.duration = 2000
fade.interpolator = AccelerateInterpolator()
set.addTransition(explode)
set.addTransition(fade)
window.enterTransition = set
val slide = Slide()
slide.duration = 2000
slide.slideEdge = Gravity.RIGHT
window.returnTransition = slide
}
共享元素間的效果
- changeBounds - 改變目標(biāo)視圖的布局邊界
- android:resizeClip
- changeClipBounds - 裁剪目標(biāo)視圖邊界
- changeTransform - 改變目標(biāo)視圖的縮放比例和旋轉(zhuǎn)角度
android:reparent 是否追蹤父容器的變化
-
android:reparentWithOverlay 默認(rèn) true。
When the parent change doesn't use an overlay, it affects the transforms of the child
- changeImageTransform - 改變目標(biāo)圖片的大小和縮放比例
- ChangeScroll
在 res/transition
目錄下創(chuàng)建 transition_elements_enter.xml
<?xml version="1.0" encoding="utf-8"?>
<changeBounds xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:resizeClip="true" />
創(chuàng)建 transition_elements_return_set.xml
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:transitionOrdering="together">
<changeTransform xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000" />
<changeClipBounds android:duration="2000"
android:resizeClip="true" />
</transitionSet>
配置主題
<item name="android:windowSharedElementEnterTransition">@transition/transition_elements_enter</item>
<item name="android:windowSharedElementReturnTransition">@transition/transition_elements_return_set</item>
也可以在 Activity B 中代碼設(shè)置
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// val set = TransitionInflater.from(ctx).inflateTransition(R.transition.transition_elements_return_set)
// val enter = TransitionInflater.from(ctx).inflateTransition(R.transition.transition_elements_enter)
val set = TransitionSet()
set.ordering = TransitionSet.ORDERING_TOGETHER
val changeTransform = ChangeTransform()
changeTransform.duration = 2000
val changeClipBounds = ChangeClipBounds()
changeClipBounds.duration = 2000
set.addTransition(changeTransform)
set.addTransition(changeClipBounds)
//
val enter = ChangeBounds()
enter.duration = 2000
enter.resizeClip = true
window.sharedElementEnterTransition = enter
window.sharedElementReturnTransition = set
}
Overlap
To start an enter transition as soon as possible, use the Window.setAllowEnterTransitionOverlap() method on the called activity. This lets you have more dramatic enter transitions.
更鮮活的動(dòng)畫(huà)效果?沒(méi)看出什么來(lái)。在 Activity B 中設(shè)置
window.allowEnterTransitionOverlap = true
window.allowReturnTransitionOverlap = true
或者配置主題
<item name="android:windowAllowEnterTransitionOverlap">true</item>
<item name="android:windowAllowReturnTransitionOverlap">true</item>
關(guān)于主題
看網(wǎng)上有些文章說(shuō)必須在主題里設(shè)置 <item name="android:windowContentTransitions">true</item>
或者代碼里在 setContentView()
之前 getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
,可我沒(méi)設(shè)置什么問(wèn)題都沒(méi)有,都生效了啊。然后去看官方文檔 https://developer.android.com/training/transitions/start-activity.html,又出現(xiàn)一個(gè)新的配置 <item name="android:windowActivityTransitions">true</item>
:
First, enable window content transitions with the android:windowActivityTransitions attribute when you define a style that inherits from the material theme.
這意思是說(shuō)用 material theme 時(shí)才需要設(shè)置這個(gè)屬性嗎?而我 Demo 用的主題是 Theme.AppCompat.Light.DarkActionBar
。不太清楚到底是不是這個(gè)原因。