Android 動(dòng)畫(huà)總結(jié)(8) - Activity 轉(zhuǎn)場(chǎng)動(dòng)畫(huà)

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)頭的方法

  1. makeCustomAnimation(Context context, int enterResId, int exitResId)
  2. makeScaleUpAnimation(View source, int startX, int startY, int startWidth, int startHeight)
  3. makeThumbnailScaleUpAnimation(View source, Bitmap thumbnail, int startX, int startY)
  4. makeClipRevealAnimation(View source, int startX, int startY, int width, int height)
  5. makeSceneTransitionAnimation(Activity activity, View sharedElement, String sharedElementName)
  6. 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.startActivitySDK >= 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ù):

  1. View source - 參照物
  2. int startX - 相對(duì)于 source,新 Activity 開(kāi)始的位置
  3. int startY - 同 startX,只不過(guò)這是 Y 軸方向上的
  4. int startWidth - 第二個(gè) Activity 在做放大動(dòng)畫(huà)前一開(kāi)始的初始寬度
  5. 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ù):

  1. Activity activity - 當(dāng)前所在 Activity
  2. View sharedElement - 要協(xié)同過(guò)渡的 View,就是共享元素
  3. 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)
}
activity_option1.gif

有共享元素時(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
  • 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
}
activity_option2.gif

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è)原因。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容