加载网络图片是 APP 中必不可少的功能,现在网络上有很多的开源库,比如 Glide、Picasso、Fresco 等。这里我用的 Glide,所以本文将是以 Glide 结合共享元素实现点击缩略图查看文章详情为例子探讨如何优化共享元素转场动画。
使用共享元素实现缩略图平滑过渡到详情
- 在详情页面设置共享元素动画
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val bigUrl = intent.getStringExtra("bigUrl")
window.sharedElementEnterTransition = TransitionSet()
.addTransition(ChangeImageTransform())
.addTransition(ChangeBounds())
window.enterTransition = Fade()
setContentView(R.layout.activity_image_detail)
image.load(bigUrl)
}
- 在启动页面携带共享元素
image.setOnClickListener {
val intent = Intent(this, ImageDetailActivity::class.java)
intent.putExtra("bigUrl", bigUrl)
val options = ActivityOptions.makeSceneTransitionAnimation(
this,
it,
it.transitionName
)
startActivity(intent, options.toBundle())
}
这样其实已经可以实现共享元素变换了,我们来康康效果是怎样的。
点击缩略图,图片边界确实是慢慢变换成详情页的边界,但是这个效果是分裂的,视觉体验很不好。点击缩略图,在变换的过程中竟然变成了缺省图片,然后过一阵子才加载出我们的详情页大图。但是这其实也是能理解的,因为到了详情页,我们用 Glide 去加载详情大图,在加载成功之前自然是显示了缺省图片。
让共享元素变换等等我
到这里,我们自然而然的就会想到,那不能等到详情大图加载完再执行动画吗,of cause!当然是可以的,framework 给我们提供了相应的 API:
- postponeEnterTransition()
/**
* Postpone the entering activity transition when Activity was started with
* {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
* android.util.Pair[])}.
* <p>This method gives the Activity the ability to delay starting the entering and
* shared element transitions until all data is loaded. Until then, the Activity won't
* draw into its window, leaving the window transparent. This may also cause the
* returning animation to be delayed until data is ready. This method should be
* called in {@link #onCreate(android.os.Bundle)} or in
* {@link #onActivityReenter(int, android.content.Intent)}.
* {@link #startPostponedEnterTransition()} must be called to allow the Activity to
* start the transitions. If the Activity did not use
* {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
* android.util.Pair[])}, then this method does nothing.</p>
*/
public void postponeEnterTransition() {
mActivityTransitionState.postponeEnterTransition();
}
This method gives the Activity the ability to delay starting the entering and
shared element transitions until all data is loaded.
这个方法给予 Activity 延时开始进场变换和共享元素变换直到所有数据加载完成的能力。
- startPostponedEnterTransition()
/**
* Begin postponed transitions after {@link #postponeEnterTransition()} was called.
* If postponeEnterTransition() was called, you must call startPostponedEnterTransition()
* to have your Activity start drawing.
*/
public void startPostponedEnterTransition() {
mActivityTransitionState.startPostponedEnterTransition();
}
If postponeEnterTransition() was called, you must call startPostponedEnterTransition() to have your Activity start drawing.
如果 postponeEnterTransition() 已经被调用过了,那么你必须调用startPostponedEnterTransition() 来让 Activity 开始绘制。
那这就好办了,我们可以先告诉详情 Activity 延时开始变换,然后等详情大图加载完毕后再通知 Activity 开始绘制。
setContentView(R.layout.activity_image_detail)
postponeEnterTransition()
image.load(bigUrl) {
startPostponedEnterTransition()
}
这样执行变换的时候就不会出现缺省图片了吧。
emmmmmm~一眼看过去很完美啊,点击缩略图,变换到详情大图的过程中没有再出现缺省图片的情况了,视觉体验提升不少,看来完美了。
too naive!你以为我加那个 Toast 是干嘛用的,就是为了说明它卡啊!虽然这个变换看上去是衔接流畅的,但是它实际有一种弊端,就是很容易给人带来点击没响应的反馈。我一点击 Toast 就弹出了,但是实际上过了好一会,变换才开始执行,这种操作体验真的很不好,尤其是网络差的时候,那简直了,都开始要怀疑 APP 当机了。
推迟加载详情大图的时机
在某度搜图片,浏览图片的时候,应该都会有发现,刚刚出现的图片是有些朦胧模糊的,过一会儿才变得清晰。其实我们也可以这样做,点击缩略图跳转到详情页的时候,可以先加载原先的缩略图,由于有缓存的存在,图片会很快的加载出来,等到共享元素变换动画执行完之后,这个时候再去加载高清大图,这样整个视觉效果会流畅很多。
window.sharedElementEnterTransition = TransitionSet()
.addTransition(ChangeImageTransform())
.addTransition(ChangeBounds())
.apply {
// 动画执行完成后加载高清大图
onEnd { image.load(bigUrl) }
}
window.enterTransition = Fade()
setContentView(R.layout.activity_image_detail)
postponeEnterTransition()
// 从缓存中加载缩略图,以最快的速度执行动画
image.load(url, true) {
startPostponedEnterTransition()
}
再看下改造后的效果
需要注意的是,如果我们不对缺省图片做处理又或者是不设置缺省图片的话,我们先加载了缩略图,而后动画完成接着加载详细大图,那么中间会出现白色闪屏,因为重新加载了新的图片,会重新设置缺省图片,因此,要想屏蔽这个问题,我们可以将当前显示的图片作为缺省图片,这样加载新图片的时候就不会出现闪白的现象了。
// 变换替换图片的时候出现白屏
val placeHolder =
if (drawable != null) drawable else ContextCompat.getDrawable(context, R.mipmap.ic_launcher)
val requestOptions = RequestOptions.placeholderOf(placeHolder)
.onlyRetrieveFromCache(onlyRetrieveFromCache)
Glide.with(this)
.load(url)
.apply(requestOptions)
.listener(listener)
.into(this)
Last
走过路过,来个 like 囖!
网友评论