我们在桌面启动自己辛苦创建的APP时,总是会看到黑屏或是白屏现象,这让人的体验感觉不是很好,看看大厂的APP为什么不会有这个现象?有问题就要解决,即便不是BUG,用户体验一样很重要。
1. APP启动黑/白屏的原因
首先,我们需要知道一个APP启动时,屏幕上都会有什么。在我们的APP里,显示在屏幕上的自然是各个View了,而我们的View又都是在Activity的onCreate()方法中调用了setContentView()方法,传入了我们的layout文件,也就是我们理论上应该看到的Activity内容。但是Android系统在启动一个新的Activity时,首先进行的并不是绘制Activity的内容,我们来看看一个Activity的UI结构。
我们可以看到,一个Activity中在ContentView的外围还有PhoneWindow、DecorView、TitleView,当Activity进行绘制时会先绘制这三个View,这时ContentView还没加载进来,所以什么东西都看不到,系统会将屏幕填充主题默认的背景色,亮系主题填充白色,暗系主题填充黑色,就出现了Activity启动之前的黑/白屏现象。
2. 解决黑/白屏的方法
刚才说了,系统会为屏幕填充主题默认的背景色,那么要解决这个问题就应该从屏幕的背景下手了。一想到背景,第一反应就是去layout里设置ContentView的background,但是系统并不会先加载ContentView,那有什么在系统绘制之前就能调整屏幕背景呢?
注意,系统会填充主题默认的背景色,所以主题会在绘制之前加载,我们可以修改主题的背景达到目的。一般一个APP第一个启动的Activity都是Splash,作为一个Splash并不需要标题栏,而且普遍是全屏的。那么我们可以将主题进行修改一下,大概有两种方式:
- 将主题背景变成透明的,这样在ContentView加载出来之前,我们会透过启动的Activity看到桌面,就不会有黑/白屏的现象。再把标题栏去掉,把Activity设置成全屏的,效果挺不错,缺点是如果启动的是一个有复杂耗时操作的Activity,那么会有一种延迟的感觉。
1 | <style name="AppTheme" parent="android:Theme.Light.NoActionBar"> |
- 将主题背景设置成一张图片,把标题栏去掉,把Activity设置成全屏的,这这样在ContentView加载出来之前,我们就能看到一张默认背景图,但是图片的屏幕适配问题就需要考虑了,主题里的背景图片会自动拉伸,可能会导致失真或者比例失调的问题。
1 | <style name="AppTheme" parent="android:Theme.Light.NoActionBar"> |
3. 背景显示优化
这里再将上述解决方法进行优化,减少用户使用时不好的体验。(PS:当然你可以不做此优化,如果你想忽悠老板,把锅甩给Android系统、手机的硬件配置、UI的图给的不匹配屏幕等等)
3.1 方法一优化
方法一中的问题在于延迟感严重,那么我们需要做的就是尽量加快Splash的启动速度,在Splash中不加入任何逻辑操作,并且Application中任何的数据及开源框架的初始化方法都不应调用,当Splash启动完全后,在Splash的OnResume()方法中可以启动子线程进行各初始化操作,宁可让用户在背景图中等待,不要让用户看着手机桌面认为手机死机了。
3.2 方法二优化
方法二中的问题在于图片拉伸可能导致失真或者比例失调,使得界面不够美观。简单的方式就是建立各个drawable文件夹,覆盖所有的屏幕尺寸类型,每个文件夹下塞一张让UI做的合理的背景图。这种方法超级令人无语,UI的工作量较大,而且你也不可能覆盖所有的屏幕尺寸,比如这样:
那么怎样可以拥有更好的用户体验呢?这时候我们需要的是drawable。
3.2.1 drawable的类型
在Android中,我们可以使用xml自定义一个drawable,用的最多的场景就是背景图了,Android系统的一些默认图标也都是用xml实现的,当然那涉及到了一些矢量图的知识。
首先我们先了解一下drawable的类型,常见的几种有:BitmapDrawable
、ShapeDrawable
、StateListDrawable
、LevelListDrawable
、LayerDrawable
、TransitionDrawable
、ScaleDrawable
、AnimationDrawable
、InsetDrawable
、NinePatchDrawable
、ClipDrawable
、VectorDrawable
。
这里我采用了LayerDrawable来解决图片拉伸的问题,其他的drawable以后再写一篇文章专门分析各个drawable。
3.2.2 LayerDrawable解决图片拉伸
LayerDrawable为什么能解决图片拉伸问题呢?这要从LayerDrawble的性质说起了:
XML标签为layer-list
层次化的Drawable合集
可以包含多个item,每个item表示一个Drawable
item中可以通过android:drawable直接引用资源
item中可以通过android:top等指定相对于父节点的位置
多个Drawable的层次化叠加,并且可以指定每个Drawable的位置,是不是和layout很像?一些简单的布局显示可以用LayerDrawble来完成,不过只能塞Drawable进去,文字什么的就不行了。
那么我们来看一下一个可以很好适配屏幕的背景图改如何完成。首先在drawable文件夹下建立一个layer-list类型的drawable文件bg_splash.xml,随后写入如下代码:
1 |
|
我们在layer-list中放入了两个item:第一个是一整个页面的背景,可以是图片,但是笔者建议用纯色的ShapeDrawable,一定程度上减少内存开销并且无需考虑图片失真之类的问题;第二个是一个Bitmap,<bitmap>
这个标签是按照图片大小插入一张图片,这样避免了图片在屏幕上的拉伸,通过android:top
来指定这个item顶部的偏移距离,同样还可以指定android:bottom
、android:left
、android:right
来定位item的位置,随后对<bitmap>
的android:gravity
设置为top
,让logo可以显示在顶部。这样一个能随着屏幕进行适配并且不会失真的背景就做好了,按照方法二设置为android:windowBackground
即可。
3.2.3 style主题优化
按照方法二的设定,整个App将使用我们制作的bg_splash作为背景,这时候如果不给每个Activity设置背景或者在使用虚拟键盘时,进入App之后屏幕上也会看到bg_splash出现在没有控件的位置,造成用户的疑惑或者反感。
我们知道Activity也是可以设置主题的,那么我们可以给Application设置一个默认的主题AppTheme,然后给SplashActivity设置我们的全屏带背景的主题SplashTheme,这样在我们的SplashActivity中就可以迅速显示启动背景图,进入App中,在其他Activity中也不会出现启动背景图,最终的styles和AndroidManifest文件如下:
1 | styles.xml |
1 | AndroidManifest.xml |