2021 共享元素(ShareElement)动画实践

背景:工作中遇到 需要用到 ShareElement 效果的 需求,就查阅了一下相关文章,和github 按自己的理解封装下
参考: Android高阶转场动画-ShareElement完全攻略

我自己的实现
优点:一次配置,回调获取需要的 元素,不需要反复设置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中 配置要 转场动画 + ShareElement 需要的配置

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

2、跳转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 之前设置, 或在主题设置
        //activity.window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
        fun setUpTransition(
            activity: Activity,
            shareElementInfoList: (() -> List<ShareElementInfo<*>>)?,
            transitionFactory: IShareElementTransitionFactory = DefaultShareElementTransitionFactory(),
        ){

            //是否覆盖执行,其实可以理解成是否同时执行还是顺序执行 false顺序执行
            activity.window.allowEnterTransitionOverlap = true
            activity.window.allowReturnTransitionOverlap = true
            //是否在透明层做动画,false 会受到 其他转场动画影响
            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
            //防止状态栏闪烁
            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、配置页面转场动画(activity.window.enterTransition 等)
2、配置共享元素动画(activity.window.sharedElementEnterTransition 等)
3、设置 setEnterSharedElementCallback、setExitSharedElementCallback
SharedElementCallback 中这里打算统一 走回调的形式 执行共享元素动画 之前回调获取相关配置
例子:
进入:A -> B
A的Exit SharedElementCallback、B的Enter SharedElementCallback 分别起作用
后退:B -> A
A的Exit SharedElementCallback、B的Enter SharedElementCallback 分别起作用(还是相同的callback 起作用,所以两个callback 有相同的回调,但enter 和 exit 需要实现的方法有所 不同)

要想share element 起相关,需要A、B页面设置 能匹配上的配置才行,关键是onMapSharedElements 去匹配,这里走回调去动态拿配置,本来ActivityOptionsCompat.makeSceneTransitionAnimation 也可以配置 Pair<View, String> 去设置share info, 但这里统一去用onMapSharedElements 回调获取

其他额外 的代码 就是为了自定义共享元素动画 去做的,码云上有详细例子,ChangeTextTransition, 字体大小和字体颜色 变化。 主要是因问 onSharedElementStart、onSharedElementEnd 进入 和后退 的回调顺序不同,所以要额外处理 使得 自定义的数据。能带到Transition 中 去读取

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容