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("image", binding.image),
                    ShareElementInfo("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> = 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>)?,
            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?,
                    sharedElements: MutableMap?
                ) {
                    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?,
                    sharedElements: MutableList?,
                    sharedElementSnapshots: MutableList?
                ) {
                    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?,
                    sharedElements: MutableList?,
                    sharedElementSnapshots: MutableList?
                ) {
                    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?,
                    sharedElements: MutableMap?
                ) {
                    mapSharedElements(names, sharedElements, shareElementInfoList)
                }

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



        }

        private fun mapSharedElements(
            names: MutableList?,
            sharedElements: MutableMap?,
            shareElementInfoList: (() -> List>)?,
        ) {
            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 去设置share info, 但这里统一去用onMapSharedElements 回调获取

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

你可能感兴趣的:(2021 共享元素(ShareElement)动画实践)