美文网首页
2021 共享元素(ShareElement)动画实践

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

作者: 仔强小狐狸 | 来源:发表于2021-07-01 20:51 被阅读0次

    背景:工作中遇到 需要用到 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 中 去读取

    相关文章

      网友评论

          本文标题:2021 共享元素(ShareElement)动画实践

          本文链接:https://www.haomeiwen.com/subject/ubqsultx.html