【桃花潭水深千尺,不及汪倫送我情】
不知道大家有沒有發(fā)現(xiàn)喂饥,Android版的掘金有下面這個小小動畫:點擊作者頭像跳轉到作者的詳情頁挪鹏,而作者頭像會從當前界面通過動畫過渡詳情頁界面涮较。
知識貧乏限制了我的視野,真心想不到這怎么實現(xiàn)的盾似?
最近在寫動畫方面文章時候,從網(wǎng)上找到了答案:Activity過渡動畫中的共享元素過渡。
本文的初衷建蹄,是和大家一起掃盲,如果對你有用裕偿,歡迎點贊洞慎,讓更多的小伙伴多學點知識。小小的動畫嘿棘,隱藏著巨大的知識點劲腿;怪不得面試造火箭,工作擰螺絲鸟妙,這是知識儲備焦人,雖然可能一輩子也用不上。
【系列好文推薦】
一重父、Activity切換過渡動畫
Activity
過渡動畫包含進入過渡和退出過渡花椭、共享元素過渡三個動畫,它們同樣僅支持Android 5.0+
版本房午。
一)矿辽、共享元素過渡動畫
共享元素過渡指的兩個Activity
共享的視圖如何在兩個Activity
之間進行過渡。例如上面的Gif
圖郭厌,共享視圖就是ImageView
袋倔。
共享元素也分一個元素和多個元素。
定義共享元素過渡效果步驟如下:
- 在兩個
Activity
定義兩個相同類型的View沪曙; - 給兩個
View
設置相同的transitionName
屬性奕污; - 通過
ActivityOptions.makeSceneTransitionAnimation()
函數(shù)生成Bundle
對象; -
startActivity()
函數(shù)傳遞bundle
對象液走。
栗子講解碳默,清晰易懂:
- 分別在
activity_first.xml
和activity_second.xml
布局文件定義ImageView
組件贾陷,并將transitionName
屬性設為activityTransform
。
<!--activity_first.xml文件內容-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical">
<ImageView
android:id="@+id/ivImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_one"
android:transitionName="activityTransform" />
<TextView
android:id="@+id/tvText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center"
android:text="我是第一個Activity"
android:textColor="@color/c_333"
android:textSize="18sp" />
</LinearLayout>
<!--activity_second.xml文件內容-->
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/ivImage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:adjustViewBounds="true"
android:src="@mipmap/ic_one"
android:transitionName="activityTransform" />
<TextView
android:id="@+id/tvText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@id/ivImage"
android:layout_marginBottom="10dp"
android:gravity="center"
android:text="我是第2個Activity"
android:textColor="@color/c_333"
android:textSize="18sp" />
</RelativeLayout>
預覽圖
activityTransform
屬性也可以通過代碼設置嘱根。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ivImage.transitionName="activityTransform"
}
- 在
FirstActivity
中給ImageView
設置點擊事件髓废,跳轉到第二個Activity。
ivImage.setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {//判斷Android版本
val bundle =
ActivityOptions.makeSceneTransitionAnimation(this, ivImage, "activityTransform")
.toBundle()
startActivity(Intent(this, SecondActivity::class.java), bundle)
} else {
startActivity(Intent(this, SecondActivity::class.java))
}
}
代碼中该抒,先判斷當前Android
版本是否大于等于5.0慌洪,大于或等于Android 5.0
的話就設置共享元素動畫,小于5.0 就正常啟動第二個Activity
。
通過ActivityOptions.makeSceneTransitionAnimation()
創(chuàng)建啟動Activity
過渡的一些參數(shù)凑保,makeSceneTransitionAnimation()
函數(shù)第一個參數(shù)為Activity
對象;第二個參數(shù)為共享元素組件冈爹,這里設置為id
是ivImage
的ImageView
視圖;第三個參數(shù)為transitionName
屬性的值欧引,這里是activityTransform
频伤。在調用AcivityOptions
對象toBundle
函數(shù),包裝成Bundle
對象芝此。
效果圖:
多個共享元素過渡
多個共享元素過渡也很簡單憋肖,只需要調用makeSceneTransitionAnimation()
函數(shù)的另外一個重載函數(shù)即可。
- 在前面XML布局的基礎上婚苹,給
TextView
增加transitionName
屬性:textTransform
岸更。
<!--activity_first.xml文件內容-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical">
<ImageView
android:id="@+id/ivImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_one"
android:transitionName="activityTransform" />
<TextView
android:id="@+id/tvText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center"
android:transitionName="textTransform"
android:text="我是第一個Activity"
android:textColor="@color/c_333"
android:textSize="18sp" />
</LinearLayout>
<!--activity_second.xml文件內容-->
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/ivSecondImage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:adjustViewBounds="true"
android:src="@mipmap/ic_one"
android:transitionName="activityTransform" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:transitionName="textTransform"
android:layout_above="@id/ivSecondImage"
android:layout_marginBottom="10dp"
android:gravity="center"
android:text="我是第2個Activity"
android:textColor="@color/c_333"
android:textSize="18sp" />
</RelativeLayout>
- 構建多個
Pair
對象,并傳遞給makeSceneTransitionAnimation()
函數(shù)膊升,啟動Activity
怎炊。
ivImage.setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val imagePair=Pair<View,String>(ivImage,"activityTransform")
val textPair=Pair<View,String>(ivImage,"textTransform")
val bundle =
ActivityOptions.makeSceneTransitionAnimation(this,
imagePair,textPair).toBundle()
startActivity(Intent(this, SecondActivity::class.java), bundle)
} else {
startActivity(Intent(this, SecondActivity::class.java))
}
}
這里主要是通過將共享視圖和transitionName
屬性的值包裝到Pair
對象,其他操作和一個共享元素的操作步驟并無區(qū)別用僧。
效果圖:
深坑提醒
有時從RecyclerView
界面進入到詳情頁结胀,由于詳情頁加載延遲赞咙,可能出現(xiàn)沒有效果责循。例如ImageView
從網(wǎng)絡加載圖片,可能A界面到B界面沒效果攀操,B回到A界面有效果院仿。
解決步驟:
- 在
setContentView
后添加下面代碼,延遲加載過渡動畫速和。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
postponeEnterTransition()
}
- 在共享元素視圖加載完畢歹垫,或者圖片加載完畢后調用下面代碼,開始加載過渡動畫。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
startPostponedEnterTransition()
}
例如我是在Glide加載完再調用:
Glide.with(mContext)
.asBitmap()
.load(value?.avatar ?: "")
.listener(object : RequestListener<Bitmap> {
override fun onResourceReady(resource: Bitmap?, model: Any?, target: Target<Bitmap>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
animatorCallback?.invoke()//回調開始加載過渡動畫
return false
}
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Bitmap>?, isFirstResource: Boolean): Boolean {
animatorCallback?.invoke()//回調開始加載過渡動畫
return false
}
})
.apply(RequestOptions.circleCropTransform())
.placeholder(R.mipmap.ic_default)
.error(R.mipmap.ic_default)
.into(authorBinding!!.ivAvatar)
大家也可以考慮下面代碼:
shareElement.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
shareElement!!.viewTreeObserver.removeOnPreDrawListener(this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
animatorCallback?.invoke()
}
return true
}
})
二)颠放、Activity進入過渡與退出過渡動畫
與共享元素相反的排惨,就是Activity進入與退出過渡動畫,兩個Activity之間在沒有共享的視圖情況下進行動畫切換碰凶。下面先看三種動畫效果圖:爆炸式效果和淡入淡出式效果暮芭、滑動式效果鹿驼。
- 爆炸式:將視圖移入場景中心或從中移出;
- 滑動式:將視圖從場景的其中一個邊緣移入或移出辕宏;
- 爆炸式:通過更改視圖的不透明度畜晰,在場景中添加視圖或從中移除視圖;
第一個界面采用Fade
淡入淡出效果瑞筐,第二個界面采用了Explode
爆炸效果凄鼻。
前后兩個界面都采用了
Slide
滑入滑出效果。
利用Android現(xiàn)有的過渡框架,實現(xiàn)起來是很簡單的聚假,步驟如下:
- 在
Activity
的onCreate()
方法中調用setContentView()
前設置啟用窗口過渡屬性;
window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS)
- 創(chuàng)建過渡效果對象
Slide
块蚌、Explode
、Fade
;
val slide=Slide()
slide.slideEdge=Gravity.START
slide.duration=300//效果時長膘格,一般Activity切換時間很短匈子,不建議設置過長
如果是Slide
效果,可以設置slideEdge
屬性來指定滑動方向闯袒,默認是Gravity.BOTTOM
虎敦。
- 將過渡效果設置給window相關屬性,設置政敢;
//退出當前界面的過渡動畫
window.exitTransition = slide
//進入當前界面的過渡動畫
window.enterTransition = slide
//重新進入界面的過渡動畫
window.reenterTransition = slide
- 調用第二個
Activity
界面其徙,使用過渡效果。
startActivity(
Intent(this, SecondActivity::class.java),
ActivityOptions.makeSceneTransitionAnimation(this).toBundle())
那么Activity
的OnCreate()
方法看起來是這樣子的喷户。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS)
window.allowEnterTransitionOverlap=false
Slide().apply {
duration = 300
excludeTarget(android.R.id.statusBarBackground, true)
excludeTarget(android.R.id.navigationBarBackground, true)
}.also {
window.exitTransition = it
window.enterTransition = it
window.reenterTransition = it
}
}
setContentView(R.layout.activity_first)
ivContent.setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
startActivity(
Intent(this, SecondActivity::class.java),
ActivityOptions.makeSceneTransitionAnimation(this).toBundle()
)
}
}
}
上面代碼中調用 了excludeTarget()
方法將狀態(tài)欄和導航欄排除在過渡動畫效果之外唾那。否則會跟著一起起動畫效果,不是很美觀褪尝。
正常情況闹获,退出與進入過渡動畫會有一小段交叉的過程,而window.allowEnterTransitionOverlap=false
就是禁止交叉河哑,只有退出過渡動畫結束后才會再顯示進入過渡動畫避诽。
如果第二個Activity
在finish
掉后,回到第一個Activity
界面也想有過渡效果璃谨,就不要手動調用finish()
,可以調用finishAfterTransition ()
方法沙庐。
三)、兼容Android 5.0前
如果Android 5.0前也想要有切換動畫怎么辦佳吞?
- 在
res/anim
文件夾下創(chuàng)建想要的效果:
<alpha
xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@interpolator/decelerate_quad"
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="@android:integer/config_longAnimTime" />
- 在啟動
Activity
后調用overridePendingTransition()
方法拱雏。
val intent = Intent(this, TestActivity2::class.java)
startActivity(intent)
overridePendingTransition(R.anim.fade_in, R.anim.fade_out)
overridePendingTransition()
方法第一個參數(shù)為下一個界面進入動畫,第二個參數(shù)為當前界面退出動畫底扳。
到這里铸抑,Activity
的切換使用過渡動畫基本就結束了。有朋友可能會問衷模,只有Activity切換才能應用過渡效果么鹊汛?
二菇爪、布局變化過渡效果
在上一節(jié)要理解一個概念:場景。布局的顯示與隱藏可以理解分別為一個場景柒昏,過渡動畫就是解決場景切換帶來的生硬視覺感受凳宙。Activity切換過渡動畫指在兩個Activity之間,而布局變化過渡動畫职祷,是指同個Activity之間View的變化過渡動畫氏涩。
一)、手動創(chuàng)建Scene
手動創(chuàng)建場景的話有梆,需要我們自己創(chuàng)建起始和結束場景是尖,利用現(xiàn)有的過渡效果來達到兩個場景的切換。默認情況下泥耀,當前界面就是起始場景饺汹。
- 創(chuàng)建起始場景和結束場景的
xml
布局。起始場景和結束場景需要有相同根元素痰催,例如下面代碼id
為flConatent
的FrameLayout
布局兜辞。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tvText"
android:text="內容過渡動畫"
android:gravity="center"
android:textSize="18sp"
android:layout_width="match_parent"
android:layout_height="50dp"/>
<FrameLayout
android:id="@+id/flContent"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp">
<include layout="@layout/layout_first_scene"/>
</FrameLayout>
</LinearLayout>
初始視圖,第一個場景,布局layout_first_scene.xml
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tvFirst"
android:textSize="18sp"
android:layout_marginTop="100dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal|top"
android:text="感謝大家閱讀文章" />
</LinearLayout>
第二個場景,布局layout_second_scene.xml
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:textSize="18sp"
android:layout_marginTop="100dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal|top"
android:text="我是新小夢\n歡迎大家點贊支持一下" />
</LinearLayout>
- 創(chuàng)建起始場景和結束場景夸溶。
val firstScene = Scene.getSceneForLayout(flContent, R.layout.layout_first_scene, this)
val secondScene = Scene.getSceneForLayout(flContent, R.layout.layout_sencod_scene, this)
默認情況下逸吵,過渡動畫應用整個場景,如果場景某個View
不參加,可以通過過渡效果對象
的removeTarget()
方法進行移除缝裁。
Slide(Gravity.TOP).removeTarget(tvNoJoin)
- 點擊時扫皱,進行場景過渡。
tvText.setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (isFirst) {
TransitionManager.go(secondScene, Slide(Gravity.TOP))
}else{
TransitionManager.go(firstScene, Slide(Gravity.TOP))
}
isFirst=!isFirst
}
}
TransitionManager.go()
第一個參數(shù)表示結束場景捷绑,第二個參數(shù)表示當前場景退出時過渡效果韩脑,當前場景就是初始場景。
效果圖:
二)粹污、系統(tǒng)自動創(chuàng)建Scene
這種情況段多,我們調用TransitionManager.beginDelayedTransition(sceneRoot)
函數(shù)時,系統(tǒng)會自動記錄當前sceneRoot
節(jié)點下所有要進行動畫的視圖作為起始節(jié)點厕怜,下一幀中再次記錄sceneRoot
子節(jié)點下所有 起始場景進行動畫狀態(tài)的視圖作為結束場景衩匣。這種一般用來改變視圖的屬性,然后進行動畫過渡粥航,如View
的寬高。
栗子:
定義只有一個正方形的View
,通過改變正方形的寬高為原來的2倍生百,來看看動畫效果递雀。
-
activity_text.xml
布局文件,定義id
為sceneRoot
的根節(jié)點蚀浆,也是場景的根節(jié)點缀程。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/sceneRoot"
android:background="@color/colorPrimary">
<View
android:id="@+id/vSquare"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="50dp"
android:background="@color/white" />
</RelativeLayout>
- 在
TestActivity
的OnCreate
方法中調用下面代碼搜吧,將正方形的寬高設置200dp。
vSquare.setOnClickListener {
TransitionManager.beginDelayedTransition(sceneRoot)
vSquare.layoutParams.apply {
width = dp2px(200f, this@TestActivity)
height = dp2px(200f, this@TestActivity)
}.also {
vSquare.layoutParams = it
}
}
效果圖:
三杨凑、過渡動畫效果
上面的動畫效果滤奈,都是采用系統(tǒng)內置的,那具體有哪些動畫效果撩满,或支持自定義么蜒程?
過渡效果類都繼承自Transition
類,Transition
類持有場景切動畫的相關信息伺帘,子類的主要作用是捕獲屬性值(例如起始值和結束值)和如何演奏動畫昭躺。從這里也可以看出,過渡動畫也是屬性動畫的一個擴展與應用伪嫁。
一)领炫、系統(tǒng)自帶過渡動畫效果
系統(tǒng)支持將任何擴展Visibility
類的過渡作為進入或退出過渡,內置繼承自Visibility
的類有Explode
张咳、Slide
帝洪、Fade
;支持共享元素過渡的有:
-
changeScroll
為目標視圖滑動添加動畫效果 -
changeBounds
為目標視圖布局邊界的變化添加動畫效果 -
changeClipBounds
為目標視圖裁剪邊界的變化添加動畫效果 -
changeTransform
為目標視圖縮放和旋轉方面的變化添加動畫效果 -
changeImageTransform
為目標圖片尺寸和縮放方面的變化添加動畫效果
代碼示例:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
TransitionSet().apply {
addTransition(ChangeImageTransform())
addTransition(ChangeBounds())
addTransition(Fade(Fade.MODE_IN))
}.also {
window.sharedElementEnterTransition=it
}
}
TransitionSet
對象是動畫的合集脚猾,可以將多個過渡效果組織起來碟狞。
也可以通過XML布局來實現(xiàn),在res/transition
文件夾創(chuàng)建``:
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:transitionOrdering="together">
<changeImageTransform />
<changeBounds />
<fade />
</transitionSet>
transitionSet
和fade...
等等的一些屬性和 Android矢量圖動畫:每人送一輛掘金牌小黃車文章 講到的一些屬性大同小異,這里不再復述婚陪。
代碼調用:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
TransitionInflater.from(this).inflateTransition(R.transition.transition_set).also {
window.sharedElementEnterTransition=it
}
}
效果圖:
當現(xiàn)有的過渡效果不滿足日常需求時族沃,可以通過繼承
Transition
,定制自己的動畫特效泌参。
二)脆淹、自定義過渡動畫
子類繼承Transition
類,并重寫其三個方法沽一。
class MyTransition : Transition() {
override fun captureStartValues(transitionValues: TransitionValues?) {}
override fun captureEndValues(transitionValues: TransitionValues?) {}
override fun createAnimator(
sceneRoot: ViewGroup?,
startValues: TransitionValues?,
endValues: TransitionValues?
): Animator {
return super.createAnimator(sceneRoot, startValues, endValues)
}
}
captureStartValues()
與captureEndValues()
方法是必須實現(xiàn)的盖溺,捕獲動畫的起始值和結束值,而createAnimator()
方法铣缠,是用來創(chuàng)建自定義的動畫烘嘱。
參數(shù)TransitionValues
可以理解是用來存儲View的一些屬性值,參數(shù)sceneRoot
為根視圖蝗蛙。
自定義過渡效果感興趣可以參考:Android自定義Transition動畫
好啦蝇庭,過渡動畫就講到這里~
參考文章:
【碼字不易捡硅,點個贊哮内,日后好查看】