最近小伙伴有個(gè)需求资溃,就是實(shí)現(xiàn)類似于小紅書武翎、Lemon8的共享元素轉(zhuǎn)場(chǎng)效果,查了一圈發(fā)現(xiàn)并沒有特別合適的Library溶锭,于是便做了一個(gè)開源Library項(xiàng)目宝恶,方便大家集成后,一行代碼實(shí)現(xiàn)Android仿小紅書趴捅、Lemon8的共享元素轉(zhuǎn)場(chǎng)效果卑惜。
1.實(shí)現(xiàn)思路:
經(jīng)過分析,如果要實(shí)現(xiàn)上圖的效果驻售,我們需要解決以下問題:
- (1)實(shí)現(xiàn)自定義的共享元素:包括圓角過渡,TextView過渡更米,不同圖片間過渡等
- (2)實(shí)現(xiàn)多個(gè)連續(xù)頁(yè)面的共享元素過渡(Q及以上系統(tǒng)欺栗,3個(gè)及以上連續(xù)的activity擁有共享元素動(dòng)畫時(shí),會(huì)有共享元素動(dòng)畫丟失的BUG)
- (3)實(shí)現(xiàn)拖拽退出效果
問題已經(jīng)分析出來(lái)了征峦,接下來(lái)我們逐個(gè)解決:
- (1)實(shí)現(xiàn)自定義的共享元素我們通過自定義
Transition
迟几,在createAnimator
中返回響應(yīng)的動(dòng)畫來(lái)實(shí)現(xiàn) - (2)實(shí)現(xiàn)多頁(yè)面的共享元素過渡我們通過反射修復(fù)BUG(如果你對(duì)反射有顧慮或沒有該功能場(chǎng)景,則不需關(guān)注該方式)
- (3)實(shí)現(xiàn)拖拽退出效果栏笆,這里我通過另外一個(gè)開源項(xiàng)目來(lái)更加完整的解決該問題:FastDragExitLayout
2.集成方式:
allprojects {
repositories {
...
maven { url 'https://www.jitpack.io' }
}
}
// 注意:本Library基于androidx
implementation 'com.github.Arcns:fast-transition:1.0.2'
// 可選:如果你需要使用FastRoundedItem(圓角的共享元素動(dòng)畫)类腮,那么你項(xiàng)目中需要引入fast rounded
implementation 'com.github.Arcns.arc-fast:rounded:1.23.1'
// 可選:如果你需要使用FastDisposableFastTextViewItem(圓角的共享元素動(dòng)畫),那么你項(xiàng)目中需要引入fast textview
implementation 'com.github.Arcns.arc-fast:text-view:1.23.1'
3.使用方式
本Library對(duì)共享元素轉(zhuǎn)場(chǎng)的配置進(jìn)行了簡(jiǎn)化蛉加,減少了使用的復(fù)雜度蚜枢,在簡(jiǎn)單場(chǎng)景集成時(shí)僅需兩步:
- (1)在轉(zhuǎn)場(chǎng)
開始頁(yè)
跳轉(zhuǎn)到目標(biāo)頁(yè)
時(shí),使用FastTransitionViewManager
配置共享元素動(dòng)畫和啟動(dòng)目標(biāo)頁(yè)
// 在開始頁(yè) StartActivity.kt
// 跳轉(zhuǎn)到目標(biāo)頁(yè)
fun goTarget(){
// 1针饥、添加需要參與轉(zhuǎn)場(chǎng)的共享元素并配置所需動(dòng)畫
val fastTransitionViewManager = FastTransitionViewManager()
fastTransitionViewManager.addView(
"IMAGE", // 共享元素的key
ivImage, // 共享元素view
FastRoundedItem(FastRoundedValue(12f.dpToPx)),//共享元素動(dòng)畫:圓角動(dòng)畫
FastSystemTransitionItem(FastSystemTransitionType.ChangeImageTransform),//共享元素動(dòng)畫:圖片切換動(dòng)畫
... // 可以配置更多動(dòng)畫
)
fastTransitionViewManager.addView(...)
fastTransitionViewManager.addView(...)
fastTransitionViewManager.addView(...) // 可以添加更多需要參與轉(zhuǎn)場(chǎng)的共享元素
// 2厂抽、通過startActivity啟動(dòng)目標(biāo)頁(yè)
fastTransitionViewManager.startActivity(
activity = this, // 當(dāng)前頁(yè)activity
targetActivityCLass = TargetActivity::class.java, // 目標(biāo)頁(yè)Class
targetDataID = "1", // 可選:目標(biāo)頁(yè)對(duì)應(yīng)的數(shù)據(jù)ID,默認(rèn)為null
applyIntent = { intent->
// 可選:intent回調(diào)丁眼,你可以在這里為intent添加更多數(shù)據(jù)
}
)
}
- (2)在到
目標(biāo)頁(yè)
的onCreate中筷凤,使用FastTransitionTargetManager
配置與開始頁(yè)
對(duì)應(yīng)的共享元素并應(yīng)用轉(zhuǎn)場(chǎng)動(dòng)畫
// 在目標(biāo)頁(yè) TargetActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val transitionTargetManager = FastTransitionTargetManager.getManager(this)
// 1、配置與`開始頁(yè)`對(duì)應(yīng)的共享元素
transitionTargetManager?.setTransitionView(
"IMAGE", // 與`開始頁(yè)`對(duì)應(yīng)的共享元素的key
ivImage // 與`開始頁(yè)`對(duì)應(yīng)的共享元素view
)
transitionTargetManager?.setTransitionView(...)
// 2苞七、應(yīng)用轉(zhuǎn)場(chǎng)動(dòng)畫
transitionTargetManager?.applyTransitionEnterAndReturnConfig(
duration = 150, // 可選:轉(zhuǎn)場(chǎng)動(dòng)畫時(shí)長(zhǎng)藐守,默認(rèn)為150,
postponeEnterTransition = false, // 可選:是否暫停轉(zhuǎn)場(chǎng)動(dòng)畫挪丢,直到用戶調(diào)用startTransitionEnter再開始轉(zhuǎn)場(chǎng)動(dòng)畫,默認(rèn)為false
postponeEnterTransitionTimeout = 500, // 可選:如果暫停轉(zhuǎn)場(chǎng)動(dòng)畫卢厂,那么達(dá)到該超時(shí)時(shí)間仍未調(diào)用startTransitionEnter時(shí)乾蓬,管理器將自動(dòng)開始轉(zhuǎn)場(chǎng)動(dòng)畫
pageCurrentScale = { 1f }, // 可選:返回當(dāng)前頁(yè)面的縮放比例,該方法一般用于與拖拽退出結(jié)合使用足淆,默認(rèn)為null
onTransitionEnd = {
// 可選:轉(zhuǎn)場(chǎng)動(dòng)畫結(jié)束的回調(diào)巢块,默認(rèn)為null
}
,
)
}
// 可選:修復(fù)Q及以上系統(tǒng),activity調(diào)用onStop后共享元素動(dòng)畫丟失的BUG
override fun onStop() {
transitionTargetManager?.onStop()
super.onStop()
}
- 補(bǔ)充巧号,如何在跳轉(zhuǎn)到新頁(yè)面后族奢,在返回時(shí)更改兩邊的共享元素。
例如從RecyclerView頁(yè)跳轉(zhuǎn)到ViewPager頁(yè)丹鸿,然后用戶在ViewPager頁(yè)滑動(dòng)到了其他Item越走,在返回時(shí)希望能夠看到ViewPager頁(yè)該Item與RecyclerView頁(yè)對(duì)應(yīng)Item的共享元素動(dòng)畫
// 在開始頁(yè) ListActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
// 由于在目標(biāo)頁(yè),我們可能瀏覽到了其他的Item靠欢,因此返回到開始頁(yè)時(shí)廊敌,我們需要把共享元素更新到對(duì)應(yīng)的Item中
setExitSharedElementCallback(object : SharedElementCallback() {
override fun onMapSharedElements(
names: MutableList<String>,
sharedElements: MutableMap<String, View>
) {
// 在此處通過transitionTargetManager.changeExitSharedElements對(duì)共享元素的Item進(jìn)行更改,以下為演示代碼
if (viewPagerItem == -1) return
val selectedViewHolder =
recyclerView.findViewHolderForAdapterPosition(viewPagerItem) ?: return
transitionTargetManager.changeExitSharedElements(
sharedElements,
TestData.KEY_IMAGE to selectedViewHolder.itemView.findViewById(R.id.ivImage)
)
}
})
}
override fun onActivityReenter(resultCode: Int, data: Intent?) {
// 由于在目標(biāo)頁(yè)门怪,我們可能瀏覽到了其他的Item骡澈,因此返回到開始頁(yè)時(shí),我們需要把共享元素更新到對(duì)應(yīng)的Item中
// 在此處根據(jù)實(shí)際需求更改布局掷空,以下為演示代碼:先把動(dòng)畫暫停肋殴,然后把列表移動(dòng)到目標(biāo)Item龄捡,再恢復(fù)動(dòng)畫床嫌,以便在setExitSharedElementCallback回調(diào)中把共享元素更新到對(duì)應(yīng)的Item中
if (ViewPagerDataSource.currentItem == -1) return
postponeEnterTransition() // 暫停動(dòng)畫
recyclerView.layoutManager?.scrollToPosition(viewPagerItem)
recyclerView.post {
// 更改布局后恢復(fù)執(zhí)行動(dòng)畫
startPostponedEnterTransition()
}
}
// 在目標(biāo)頁(yè) ViewPagerActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
// 用戶切換Item時(shí),更新共享動(dòng)畫元素到當(dāng)前Item
viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
// 切換后怒详,通過transitionTargetManager.setTransitionView重新更改需要參與轉(zhuǎn)場(chǎng)的共享元素
// 以下為演示代碼:
ViewPagerDataSource.currentItem = position
val fragment = viewPager.findFragmentAtPosition(
supportFragmentManager,
position
) as? ViewPagerFragment
if (fragment != null) {
// 用戶切換Item時(shí)酿傍,更新共享動(dòng)畫元素到當(dāng)前Item
transitionTargetManager?.setTransitionView(
TestData.KEY_IMAGE,
fragment.binding.ivImage
)
}
super.onPageSelected(position)
}
})
}
override fun onBackPressed() {
// 注意返回到開始頁(yè)之前烙懦,您必須先調(diào)用setResult,以便開始頁(yè)觸發(fā)onActivityReenter回調(diào)(進(jìn)行布局更新處理)
setResult(100)
super.onBackPressed()
}
4.修復(fù)多個(gè)連續(xù)頁(yè)面的共享元素過渡時(shí)赤炒,共享元素動(dòng)畫丟失的BUG
注意:該BUG需要使用反射進(jìn)行修復(fù)氯析,截至到目前最新的API 33,該方法仍然能夠有效修復(fù)該BUG可霎,但如果你對(duì)反射有顧慮或沒有該功能場(chǎng)景魄鸦,則不要使用以下方法。
// 在自定義Application MyApplication.kt
class MyApplication : Application() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
// 啟用多個(gè)連續(xù)頁(yè)面的共享元素過渡功能
FastTransitionUtils.enableMultipleActivityTransition(this)
}
}
// 在目標(biāo)頁(yè) TargetActivity.kt
// 修復(fù)Q及以上系統(tǒng)癣朗,3個(gè)及以上連續(xù)的activity擁有共享元素動(dòng)畫時(shí)拾因,共享元素動(dòng)畫丟失的BUG(使用反射)
override fun finishAfterTransition() {
transitionTargetManager?.finishAfterTransition()
super.finishAfterTransition()
}
5.支持的動(dòng)畫
本Library在原有系統(tǒng)自帶的共享元素動(dòng)畫基礎(chǔ)上,擴(kuò)展了一些常用的動(dòng)畫效果,所有內(nèi)置動(dòng)畫如下:
動(dòng)畫名 | 簡(jiǎn)介 |
---|---|
FastTextViewItem | TextView的共享元素動(dòng)畫绢记,能夠?qū)崿F(xiàn)文字大小扁达、顏色、行高蠢熄、間距跪解、粗體等屬性的過渡動(dòng)畫 |
FastRoundedItem | 圓角的共享元素動(dòng)畫(需要使用圓角控件FastRounded) |
FastToggleImageViewItem | 支持根據(jù)狀態(tài)切換圖片的共享元素動(dòng)畫,通常用于點(diǎn)贊签孔、收藏叉讥、關(guān)注等需要根據(jù)狀態(tài)同時(shí)切換開始頁(yè) 與目標(biāo)頁(yè) 圖片的場(chǎng)景 |
FastSimpleItem | 可實(shí)現(xiàn)漸變或縮放的簡(jiǎn)單共享元素動(dòng)畫,通常用于那些內(nèi)部不一致的共享元素容器控件饥追,避免內(nèi)部不同導(dǎo)致過渡時(shí)突兀 |
FastImageItem | 支持切換圖片或背景的共享元素動(dòng)畫图仓,通常用于不同圖片間的過渡,該動(dòng)畫會(huì)通過漸變效果的過渡到另一張圖片 |
FastBackgroundFadeItem | 背景漸變顯示或隱藏的共享元素動(dòng)畫但绕,通常用于只有一邊有背景而另一邊沒有的場(chǎng)景 |
FastDisposableFastTextViewItem | FastTextView漸變消失的共享元素動(dòng)畫救崔,該動(dòng)畫會(huì)在目標(biāo)頁(yè)面創(chuàng)建相同控件以完成漸變消失動(dòng)畫 |
FastSystemTransitionItem | 系統(tǒng)自帶的共享元素動(dòng)畫,用于實(shí)現(xiàn)在本庫(kù)中使用系統(tǒng)自帶的動(dòng)畫 |
6.擴(kuò)展自定義的動(dòng)畫
如果內(nèi)置的動(dòng)畫不符合你的需求場(chǎng)景捏顺,或者你需要讓你的其他控件也參與共享元素動(dòng)畫六孵,那么你可以擴(kuò)展自定義的動(dòng)畫.
本Library對(duì)擴(kuò)展自定義的動(dòng)畫也進(jìn)行了簡(jiǎn)化,通常情況下你只需兩步即可實(shí)現(xiàn)擴(kuò)展自定義的動(dòng)畫:
- (1)創(chuàng)建自定義的動(dòng)畫計(jì)算器
// 1.1 繼承FastBaseCalculator<計(jì)算器的數(shù)據(jù)類型幅骄,控件的類型>
class CustomCalculator(
_first: Float,// 動(dòng)畫起始數(shù)據(jù)劫窒,演示用法,你可以按需替換為你自己的構(gòu)造參數(shù)
_last: Float, // 動(dòng)畫結(jié)束數(shù)據(jù)拆座,演示用法烛亦,你可以按需替換為你自己的構(gòu)造參數(shù)
) : FastBaseCalculator<Float, View>( // 此處<Float, View>僅為演示用法,你可以按需替換為你自己的數(shù)據(jù)類型與控件類型
viewClass = View::class,
first = _first,
last = _last
) {
// 1.2 返回動(dòng)畫起始數(shù)據(jù)與結(jié)束數(shù)據(jù)的差額
override val differ: Float by lazy {
last - first // 此處僅為演示用法懂拾,你可以按需替換為你自己的差額計(jì)算方式
}
// 1.3 返回某個(gè)進(jìn)度下的動(dòng)畫數(shù)據(jù)(progress的區(qū)間為0至1)
override fun getValue(progress: Float): Float =
calculatorFloatValue(first, last, differ, progress) // 此處僅為演示用法,你可以按需替換為你自己的進(jìn)度數(shù)據(jù)計(jì)算方式
// 1.4 把某個(gè)進(jìn)度下的動(dòng)畫數(shù)據(jù)設(shè)置到你的控件中
override fun setView(view: View, progress: Float, value: Float) {
// 此處僅為演示用法铐达,你可以按需替換為你自己的控件設(shè)置方式
view.alpha = value
}
}
- (2)創(chuàng)建自定義的動(dòng)畫Item岖赋,并返回上一步的動(dòng)畫計(jì)算器
@Parcelize
data class CustomItem(
var start: Float,
var end: Float,
) : FastTransitionItem() {
// 返回動(dòng)畫計(jì)算器
override fun getCalculator(
isEnter: Boolean,
pageCurrentScale: Float?
): FastSimpleCalculator {
// 此處僅為演示用法,你可以按需替換為你自己的計(jì)算器構(gòu)建及返回方式
return if (isEnter) CustomCalculator(start, end)
else CustomCalculator(end, start)
}
// 可選:校驗(yàn)動(dòng)畫Item當(dāng)前是否可用瓮孙,如果不可用將不調(diào)用getCalculator
override val enable: Boolean get() = start != end && start >= 0f && end >= 0f // 此處僅為演示用法唐断,你可以按需替換為你自己的校驗(yàn)方法
// 可選:視圖動(dòng)畫準(zhǔn)備(下一步將創(chuàng)建計(jì)算器)的回調(diào),您可以在此處進(jìn)行視圖相關(guān)的初始化杭抠,例如根據(jù)視圖準(zhǔn)備目標(biāo)頁(yè)對(duì)應(yīng)的共享元素?cái)?shù)據(jù)
override fun onViewAnimReady(isEnter: Boolean, view: View, pageCurrentScale: Float?) {
// 此處僅為演示用法脸甘,你可以按需替換為你自己的初始化方法
end = view.alpha
}
// 可選:執(zhí)行進(jìn)入動(dòng)畫前的回調(diào),您可以在此進(jìn)行進(jìn)入動(dòng)畫前的初始化工作
override fun onEnterBefore(activity: Activity, transitionConfig: FastTransitionConfig) {
}
// 可選:執(zhí)行離開動(dòng)畫前的回調(diào)偏灿,您可以在此進(jìn)行離開動(dòng)畫前的初始化工作
override fun onReturnBefore(view: View, pageCurrentScale: Float?) {
}
}
項(xiàng)目地址:
https://github.com/Arcns/fast-transition