背景:工作中遇到 需要用到 ShareElement 效果的 需求,就查阅了一下相关文章,和github 按自己的理解封装下
参考: Android高阶转场动画-ShareElement完全攻略
我自己的实现
优点:一次配置,回调获取需要的 元素,不需要反复设置window 的动画(enterTransition)
码云 地址:https://gitee.com/zaiqiang231/BaseLib/tree/master/base/src/main/java/com/ziq/base/transition
效果图:(第二段 没有配置 字体动画)
使用方式:
基本思路:
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
其他额外 的代码 就是为了自定义共享元素动画 去做的,码云上有详细例子,ChangeTextTransition, 字体大小和字体颜色 变化。 主要是因问 onSharedElementStart、onSharedElementEnd 进入 和后退 的回调顺序不同,所以要额外处理 使得 自定义的数据。能带到Transition 中 去读取