2021 共享元素(ShareElement)動畫實(shí)踐

背景:工作中遇到 需要用到 ShareElement 效果的 需求兼雄,就查閱了一下相關(guān)文章偷线,和github 按自己的理解封裝下
參考: Android高階轉(zhuǎn)場動畫-ShareElement完全攻略

我自己的實(shí)現(xiàn)
優(yōu)點(diǎn):一次配置貌矿,回調(diào)獲取需要的 元素雷蹂,不需要反復(fù)設(shè)置window 的動畫(enterTransition)

碼云 地址:https://gitee.com/zaiqiang231/BaseLib/tree/master/base/src/main/java/com/ziq/base/transition
效果圖:(第二段 沒有配置 字體動畫)

Screenrecorder-2021-07-01-19-56-20-519 (1).gif

使用方式:


image.png

基本思路:
1姆怪、activity.onCreate中 配置要 轉(zhuǎn)場動畫 + ShareElement 需要的配置

TransitionHelper.setUpTransition(this,
            shareElementInfoList = {
                listOf(
                    ShareElementInfo<ShareElementInfoDataUpdate>("image", binding.image),
                    ShareElementInfo<ShareElementInfoDataUpdate>("title", binding.title),
                )
            }
        )

2也物、跳轉(zhuǎn)activity

val options = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, *pairs.toTypedArray())
ActivityCompat.startActivity(activity, intent, options.toBundle())

詳解配置:setUpTransition

class TransitionHelper {


    companion object {

        fun startActivity(activity: Activity, intent: Intent){
            val pairs: MutableList<Pair<View, String>> = mutableListOf()
            val options = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, *pairs.toTypedArray())
            ActivityCompat.startActivity(activity, intent, options.toBundle())
        }

        //需要 FEATURE_ACTIVITY_TRANSITIONS
        //在 super.oncreate 之前設(shè)置, 或在主題設(shè)置
        //activity.window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
        fun setUpTransition(
            activity: Activity,
            shareElementInfoList: (() -> List<ShareElementInfo<*>>)?,
            transitionFactory: IShareElementTransitionFactory = DefaultShareElementTransitionFactory(),
        ){

            //是否覆蓋執(zhí)行宫屠,其實(shí)可以理解成是否同時(shí)執(zhí)行還是順序執(zhí)行 false順序執(zhí)行
            activity.window.allowEnterTransitionOverlap = true
            activity.window.allowReturnTransitionOverlap = true
            //是否在透明層做動畫,false 會受到 其他轉(zhuǎn)場動畫影響
            activity.window.sharedElementsUseOverlay = true

            val customEnterTransition = transitionFactory.buildEnterTransition()
            val customExitTransition = transitionFactory.buildExitTransition()
            activity.window.enterTransition = customEnterTransition
            activity.window.exitTransition = customExitTransition
            activity.window.reenterTransition = customExitTransition
            activity.window.returnTransition = customEnterTransition
            //防止?fàn)顟B(tài)欄閃爍
            val enterTransition = activity.window.enterTransition
            val exitTransition = activity.window.exitTransition
            if (enterTransition != null) {
                enterTransition.excludeTarget(Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME, true)
                enterTransition.excludeTarget(
                    Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
                    true
                )
            }
            if (exitTransition != null) {
                exitTransition.excludeTarget(Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME, true)
                exitTransition.excludeTarget(Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME, true)
            }

            activity.window.sharedElementEnterTransition = transitionFactory.buildShareElementEnterTransition()
            activity.window.sharedElementExitTransition = transitionFactory.buildShareElementExitTransition()

            activity.setEnterSharedElementCallback(object : SharedElementCallback() {
                override fun onMapSharedElements(
                    names: MutableList<String>?,
                    sharedElements: MutableMap<String, View>?
                ) {
                    mapSharedElements(names, sharedElements, shareElementInfoList)
                }

                override fun onCreateSnapshotView(context: Context?, snapshot: Parcelable?): View? {
                    var view : View?= null
                    if(snapshot is ShareElementInfo.ShareElementInfoData<*>){
                        view = super.onCreateSnapshotView(context, snapshot.snapShot)
                        ShareElementInfo.ShareElementInfoData.saveToView(view, snapshot)
                    } else {
                        view = super.onCreateSnapshotView(context, snapshot)
                    }
                    return view
                }

                override fun onSharedElementStart(
                    sharedElementNames: MutableList<String>?,
                    sharedElements: MutableList<View>?,
                    sharedElementSnapshots: MutableList<View>?
                ) {
                    if(sharedElements?.isNotEmpty() == true && sharedElementSnapshots?.isNotEmpty() == true){
                        val length = sharedElementSnapshots.size
                        for (i in 0 until length){
                            val shareElementView = sharedElements.get(i)
                            val snapshotView = sharedElementSnapshots.get(i)
                            var info = ShareElementInfo.ShareElementInfoData.getFromView(snapshotView)
                            if(info == null){
                                info = ShareElementInfo.ShareElementInfoData.getFromView(shareElementView)
                            }
                            info?.updateByView(ShareElementInfo.ShareElementInfoData.DATA_STATUS_START, shareElementView)
                            ShareElementInfo.ShareElementInfoData.saveToView(shareElementView, info)
                        }
                    }
                }

                override fun onSharedElementEnd(
                    sharedElementNames: MutableList<String>?,
                    sharedElements: MutableList<View>?,
                    sharedElementSnapshots: MutableList<View>?
                ) {
                    if(sharedElements?.isNotEmpty() == true && sharedElementSnapshots?.isNotEmpty() == true){
                        val length = sharedElementSnapshots.size
                        for (i in 0 until length){
                            val shareElementView = sharedElements.get(i)
                            val snapshotView = sharedElementSnapshots.get(i)
                            var info = ShareElementInfo.ShareElementInfoData.getFromView(snapshotView)
                            if(info == null){
                                info = ShareElementInfo.ShareElementInfoData.getFromView(shareElementView)
                            }
                            info?.updateByView(ShareElementInfo.ShareElementInfoData.DATA_STATUS_END, shareElementView)
                            ShareElementInfo.ShareElementInfoData.saveToView(shareElementView, info)
                        }
                    }
                }


            })
            activity.setExitSharedElementCallback(object : SharedElementCallback() {
                override fun onMapSharedElements(
                    names: MutableList<String>?,
                    sharedElements: MutableMap<String, View>?
                ) {
                    mapSharedElements(names, sharedElements, shareElementInfoList)
                }

                override fun onCaptureSharedElementSnapshot(
                    sharedElement: View?,
                    viewToGlobalMatrix: Matrix?,
                    screenBounds: RectF?
                ): Parcelable {
                    val snapshot = super.onCaptureSharedElementSnapshot(sharedElement, viewToGlobalMatrix, screenBounds)
                    var clz : Class<ShareElementInfoDataUpdate>? = null
                    shareElementInfoList?.invoke()?.let { list ->
                        for (info in list) {
                            if (info.getView() == sharedElement){
                                clz = info.clz as Class<ShareElementInfoDataUpdate>?
                                break
                            }
                        }
                    }
                    val shareElementInfoData = ShareElementInfo.ShareElementInfoData(snapshot, clz)
                    shareElementInfoData.updateByView(ShareElementInfo.ShareElementInfoData.DATA_STATUS_CAPTURE_SNAPSHOT, sharedElement)
                    return shareElementInfoData
                }
            })



        }

        private fun mapSharedElements(
            names: MutableList<String>?,
            sharedElements: MutableMap<String, View>?,
            shareElementInfoList: (() -> List<ShareElementInfo<*>>)?,
        ) {
            names?.clear()
            sharedElements?.clear()
            shareElementInfoList?.invoke()?.let { list ->
                for (info in list) {
                    val view: View = info.getView()
                    ViewCompat.getTransitionName(view)?.let {
                        names?.add(it)
                        sharedElements?.put(it, view)
                    }
                }
            }
        }


    }
}

setUpTransition方法分了下面幾步:
1滑蚯、配置頁面轉(zhuǎn)場動畫(activity.window.enterTransition 等)
2浪蹂、配置共享元素動畫(activity.window.sharedElementEnterTransition 等)
3抵栈、設(shè)置 setEnterSharedElementCallback、setExitSharedElementCallback
SharedElementCallback 中這里打算統(tǒng)一 走回調(diào)的形式 執(zhí)行共享元素動畫 之前回調(diào)獲取相關(guān)配置
例子:
進(jìn)入:A -> B
A的Exit SharedElementCallback坤次、B的Enter SharedElementCallback 分別起作用
后退:B -> A
A的Exit SharedElementCallback古劲、B的Enter SharedElementCallback 分別起作用(還是相同的callback 起作用,所以兩個(gè)callback 有相同的回調(diào)缰猴,但enter 和 exit 需要實(shí)現(xiàn)的方法有所 不同)

要想share element 起相關(guān)产艾,需要A、B頁面設(shè)置 能匹配上的配置才行滑绒,關(guān)鍵是onMapSharedElements 去匹配闷堡,這里走回調(diào)去動態(tài)拿配置,本來ActivityOptionsCompat.makeSceneTransitionAnimation 也可以配置 Pair<View, String> 去設(shè)置share info疑故, 但這里統(tǒng)一去用onMapSharedElements 回調(diào)獲取

其他額外 的代碼 就是為了自定義共享元素動畫 去做的,碼云上有詳細(xì)例子缚窿,ChangeTextTransition, 字體大小和字體顏色 變化焰扳。 主要是因問 onSharedElementStart倦零、onSharedElementEnd 進(jìn)入 和后退 的回調(diào)順序不同,所以要額外處理 使得 自定義的數(shù)據(jù)吨悍。能帶到Transition 中 去讀取

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末扫茅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子育瓜,更是在濱河造成了極大的恐慌葫隙,老刑警劉巖,帶你破解...
    沈念sama閱讀 223,002評論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件躏仇,死亡現(xiàn)場離奇詭異恋脚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)焰手,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評論 3 400
  • 文/潘曉璐 我一進(jìn)店門糟描,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人书妻,你說我怎么就攤上這事船响。” “怎么了躲履?”我有些...
    開封第一講書人閱讀 169,787評論 0 365
  • 文/不壞的土叔 我叫張陵见间,是天一觀的道長。 經(jīng)常有香客問我工猜,道長米诉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,237評論 1 300
  • 正文 為了忘掉前任篷帅,我火速辦了婚禮史侣,結(jié)果婚禮上汗销,老公的妹妹穿的比我還像新娘。我一直安慰自己抵窒,他們只是感情好弛针,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,237評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著李皇,像睡著了一般削茁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上掉房,一...
    開封第一講書人閱讀 52,821評論 1 314
  • 那天茧跋,我揣著相機(jī)與錄音,去河邊找鬼卓囚。 笑死瘾杭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的哪亿。 我是一名探鬼主播粥烁,決...
    沈念sama閱讀 41,236評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蝇棉!你這毒婦竟也來了讨阻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,196評論 0 277
  • 序言:老撾萬榮一對情侶失蹤篡殷,失蹤者是張志新(化名)和其女友劉穎钝吮,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體板辽,經(jīng)...
    沈念sama閱讀 46,716評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡奇瘦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,794評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了劲弦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耳标。...
    茶點(diǎn)故事閱讀 40,928評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖瓶您,靈堂內(nèi)的尸體忽然破棺而出麻捻,到底是詐尸還是另有隱情纲仍,我是刑警寧澤呀袱,帶...
    沈念sama閱讀 36,583評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站郑叠,受9級特大地震影響夜赵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜乡革,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,264評論 3 336
  • 文/蒙蒙 一寇僧、第九天 我趴在偏房一處隱蔽的房頂上張望摊腋。 院中可真熱鬧,春花似錦嘁傀、人聲如沸兴蒸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,755評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽橙凳。三九已至,卻和暖如春笑撞,著一層夾襖步出監(jiān)牢的瞬間岛啸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,869評論 1 274
  • 我被黑心中介騙來泰國打工茴肥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坚踩,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,378評論 3 379
  • 正文 我出身青樓瓤狐,卻偏偏與公主長得像瞬铸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子础锐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,937評論 2 361

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