Android工具栏顶出转场动画的实现

529 阅读5分钟

实现效果


image

为何做这个动画

起初对于这两个界面的转场动画打算简单使用android原生的共享元素动画,可是实现后发现效果并不是很好,在很多手机上流畅度太差。

以下在叙述时把转场前的页面称为A页面, 转场后的页面称为B页面

实现原理

在A页面,把需要顶出的区域截取出来

val contentView = (context as Activity).window.decorView.findViewById<View>(android.R.id.content) ?: return null
var captureImage: Bitmap? = null
contentView.isDrawingCacheEnabled = true
captureImageFromView(contentView)
contentView.destroyDrawingCache()

截图这个操作肯定是比较耗时。所以你可以提前截图,不过时机需要自己把握了。不过为了追求更流畅的用户体验可以不必截图,即直接问设计要一张图来代替。但要注意的时这个图需要无当前的tab信息,并且这个图尺寸适配的话可能存在问题。需要做好处理。

在B页面,把A页面截取的图片用ImageView展示在顶部,并做上移动画。同时,取消按钮做出现的动画。

ImageView的上移动画非常简单:通过不断改变topMarigin来实现上移效果

 val captureImageUpAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
    addUpdateListener {
        val newTopMargin = captureImageStartLocationY * (animatedValue as Float)
        (captureView.layoutParams as LinearLayout.LayoutParams)?.apply {
            topMargin = -(newTopMargin.toInt())
        }
    }
}
val captureImageDownAnimator = ValueAnimator.ofFloat(1f, 0f).apply {
    addUpdateListener {
        val newTopMargin = captureImageStartLocationY * (animatedValue as Float)
        (captureView.layoutParams as LinearLayout.LayoutParams)?.apply {
            topMargin = -(newTopMargin.toInt())
        }
    }
}

取消按钮的出现动画,同样可以通过改变margin来实现,不过要注意按钮起始的摆放位置。

下图大概描述了整个动画时如何实现的:

image

动画实现需要注意的点

页面A保存的截图如何传到B页面?

  1. 截图应该放到内存还是本地?

这个截图应该放在内存中,如果截图保存到本地。那么 I/O 占用的时间肯定会导致动画实现效果不好。

  1. 对于截取的图片需要压缩

对于不同分辨率的手机,截取出的图片的大小是不一样的。图片太大放在内存中是不合适的,因此在截取图片后对图片的大小做了压缩。

private fun captureFromView(view: View): Bitmap? {
    return translateToRgb555(view.drawingCache)
}

//为了压缩大小,同时保证图片宽高不变,直接将图片转成 RGB_565。
private fun translateToRgb555(srcBitmap: Bitmap): Bitmap? {
    try {
        /*传入bitmap参数,返回bitmap。*/
        val dataByte = ByteArrayOutputStream()
        srcBitmap.compress(Bitmap.CompressFormat.JPEG, 100, dataByte)
        val opts = BitmapFactory.Options()
        opts.inPreferredConfig = Bitmap.Config.RGB_565
        return BitmapFactory.decodeByteArray(dataByte.toByteArray(), 0, dataByte.size(), opts)
    } catch (e: Exception) {
        e.printStackTrace()
    } catch (e: OutOfMemoryError) {
        e.printStackTrace()
    }
    return null
}

这里把图片采用RGB_565做了处理,质量还是可以接受的。经过上面的处理,大部分手机截图大小在0.2MB左右

  1. B界面如何获取截取的图片?

-放在intent中传给B?

这样是有问题的,虽然android官方说intent中可以传递小于1MB的图片,但是国内各anroid厂商对framework做了不同的定制,有可能你的图片0.3MB就会出现崩溃,即TransactionTooLargeException。如果放在intent中传递的话,图片大小应至少在0.3MB以下。

放在Fresco的缓存中?

由于我们的网络框架采用的是Fresco,所以我想尝试将图片放入Fresco缓存中,不过Fresco提供的接口十分不友好,所以就算了:

CloseableReference<V> cache(K key, CloseableReference<V> value);

放在全局静态变量中?

这是一个较好的实践(0.2MB的内存占用,还是可以接受的),不过需要注意的是在页面finish时,将这个变量置null,以免占用内存。并且在使用图片做动画时需要做好判空和异常处理。

覆盖系统原生转场动画

如果对于默认转场动画不做处理的话,效果就不是我们想要的,因此要取消默认的转场动画

需要把B页面的Theme的动画相关属性置null就可以了

<style name="NullAnimation" parent="@android:style/Animation.Activity">
<item name="android:activityOpenEnterAnimation">@null</item>
<item name="android:activityOpenExitAnimation">@null</item>
<item name="android:activityCloseEnterAnimation">@null</item>
<item name="android:activityCloseExitAnimation">@null</item>
</style>

不过这样后,在一些手机,比如华为,还是会有默认的转场动画,为了保险起见,在startActivity是最好这样

  context.overridePendingTransition(0, 0)

页面闪烁的问题

在覆盖了系统原生动画后,大部分手机效果都还是ok的,不过在一些手机上会出现闪屏的问题,比如华为 Mate10。在网上参考一些大家的解决方法:

  <item name="android:windowIsTranslucent">true</item>

即设置B页面的背景为透明的。

不过问题到这里还没有结束

windowIsTranslucent引发的崩溃问题

在设置windowIsTranslucent属性后,在Mate10进行测试,发现页面启动就崩溃:

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.linkedin.android.XXXX.XXXX/com.linkedin.android.XXXX.XXXX.activity.LoginActivity}:
java.lang.IllegalStateException: Only fullscreen activities can request orientation

最后google找到了原因,原来 Android 8.0某SDK要求,如果界面是锁死方向的,那么是不允许设置这个属性的,不然会引发崩溃, 如果想知道详细细节可以看一下这篇文章:zhuanlan.zhihu.com/p/32190223

怎么解决呢?

前有山后有虎,脑壳子疼,最后决定取消B页面的锁屏属性,并且页面旋转时页面不做变化。

    <activity
        android:name=".activity.BActivity"
        android:exported="false"
        android:configChanges="orientation|keyboardHidden|screenSize"
        android:theme="@style/AnimationTheme">

这个转场动画在一些低端机型上会有一些卡顿。建议android5.0以下的手机不做转场动画来保证更好的用户体验。

最后,欢迎关注我的Android进阶计划。看更多干货