最近公司刚起了一个直播项目,我被抽过去参与,负责其中的直播页,在写直播页的过程中,尤其是横屏状态下,用户的交互出现了很大的问题。
问题是:当用户点击EditText控件时,输入法键盘挡住了部分EditText控件。
在解决的过程中,翻阅的大量的文章:
Android 5.0及以上如何在Translucent模式下防止键盘挡住EditText
Android 5.0 如何实现将布局的内容延伸到状态栏实?
关于透明状态栏的使用以及与软键盘冲突的解决办法
android 动态控制状态栏显示和隐藏的方法实例
动态显示和隐藏状态栏
StatusBarUtil
JKeyboardPanelSwitch
虽然,翻阅了这么多文章,但是然并卵,问题并没有得到解决。最后还是在谷歌官方的例子中找到启发。
1 让你知道,你到底需要哪种全屏模式。
2 解决全屏模式下(添加透明状态栏情况下),EditText被挡住的问题。
对于视频类项目来说,横屏代表着用户的参与度更高,那么也相应的需要更好的交互体验,一般来说横屏状态下,存在两个View:一个是用于视频播放的View,另一个是用于控制视频状态的控制器View。想要做到完善的交互,对于布局文件来说,还是有一定的格式要求的,请看:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" >
<!-- 用于全屏化的View,你可以替代成VideoView, SurfaceView,TextureView等 -->
<TextView android:id="@+id/fullscreen_content" android:layout_width="match_parent" android:layout_height="200dp" android:background="#ff00ff" android:gravity="center" android:keepScreenOn="true" android:text="@string/dummy_content" android:textColor="#33b5e5" android:textSize="50sp" android:textStyle="bold" />
<!-- 这里是控制器的布局。对于FrameLayout来说,让其使用 android:fitsSystemWindows=true的属性,可以适应系统UI,以保证App的View不被系统UI遮挡。 -->
<FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true">
<LinearLayout android:id="@+id/fullscreen_content_controls" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="bottom" android:orientation="vertical">
<EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:imeOptions="actionDone|flagNoExtractUi" />
</LinearLayout>
</FrameLayout>
</FrameLayout>
剩下的就需要代码处理了,既然需要横屏全屏化,你往往会添加如下或者与之类似的代码,以达到全屏化的目的:
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
但是,千万不要这么做。这个全屏化和我们理想中的全屏化是有差异的,具体差异稍后再说。
为了达到全屏化的目的,我们会监听屏幕旋转事件,当进入横屏模式时, 重新修改需要全屏化View的布局参数(VideoView等),让其宽高等于屏幕的宽高,然后在处理真正的用于全屏化的逻辑。
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
hideSystemBar();
showSystemBarDelay();
setTranslucentState();
float height = DensityUtil.getWidthInPx(this);
float width = DensityUtil.getHeightInPx(this);
video_player.getLayoutParams().height = (int) height;
video_player.getLayoutParams().width = (int) width;
} else if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
showPortraitStateSystemBar();
clearTranslucentState();
float width = DensityUtil.getWidthInPx(this);
float height = getResources().getDimension(R.dimen.super_video_default_height);
video_player.getLayoutParams().height = (int) height;
video_player.getLayoutParams().width = (int) width;
}
}
其中最核心的代码为:
public void hideSystemBar() {
int systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
systemUiVisibility |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
}
video_player.setSystemUiVisibility(systemUiVisibility);
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) video_player.getLayoutParams();
layoutParams.topMargin = 0;
video_player.setLayoutParams(layoutParams);
}
public void showSystemBar() {
video_player.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
}
只要你按照这种方式处理完,那么横屏状态下EditText控件被遮挡的问题也就随之解决了,即使你加上了透明状态栏想关的代码,EditText也会正常显示。
有兴趣的朋友,可以去在AS上新建一个工程,选择FullScreen的模板,然后自己研究研究。
首先,我们注意到,在两个关键方法中,都是对被全屏化的View调用了其setSystemUiVisibility(int visibility)
方法,通过改变参数,来达到隐藏和显示状态栏的目的。
具体看看这个方法的说明:
public void setSystemUiVisibility(int visibility)
总结来说:如果你做的是视频播放器,使用这个方法来达到全屏化的目的,比单纯调用getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
这块代码来达到全屏化,得到的交互效果会更加流畅和丝滑。
接下来,我们具体看看这些参数到底什么意思。
在查找资料的过程中,翻看到这篇文章:android 动态控制状态栏显示和隐藏的方法实例
其中博主提到,它使用的方法1没有实现效果的原因在于。调用setSystemUiVisibility()
的View不对,并不是随便一个View来调用都能达到全屏化的效果的,必须是被全屏化的那个View来调用才行。
接下来,我们就挨个看看这些常量都是什么意思。
View.SYSTEM_UI_FLAG_LOW_PROFILE
View.SYSTEM_UI_FLAG_FULLSCREEN
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
请求系统UI进入一个“低调”的模式。在游戏,阅读器,视频播放器等需要“沉浸式”体验的应用中,系统UI常常会让用户分心。在这种模式下,状态栏和导航栏会变暗。
请求进入全屏模式,但同时仍然允许用户与系统UI进行交互,这与WindowManager.LayoutParams.FLAG_FULLSCREEN具有相同的视觉效果,一些非关键画面UI(如状态栏)将被隐藏。
但是,WindowManager.LayoutParams.FLAG_FULLSCREEN更强调让用户一直处于一个连续的状态,比如玩游戏。而这个标记则更强调一种沉浸状态,用户可以迅速的退出这种状态。
举个例子,我们看小说的时候,很多时间会专心与小说本身,但是当用户想看一下时间时,点击屏幕,我们可以迅速友好显示系统UI和一些操作控制UI。
简单来说,就是防止隐藏和显示系统UI时,控件发生抖动。
如果加上这个设置,无论怎么显示和隐藏系统UI,我们都回到得到一个平滑的过渡效果。
假如你设置了WindowManager.FLAG_FULLSCREEN标记而不是View.SYSTEM_UI_FLAG_FULLSCREE,那么你将得到一直处于隐藏状态的状态栏。注意,改变或清除WindowManager.FLAG_FULLSCREEN不会得到一个平滑的过渡效果。
其效果是增强显示和隐藏系统UI时的交互体验。
如果不设置的话,那么任何与系统UI的交互,都会导致退出全屏状态。如果设置的话,那么系统UI会短暂的覆盖应用的内容,而且还有一定程度的透明度,并且短暂时间后自动隐藏,并不会退出全屏化。
看一下效果图会更明显一些。
如果窗口有被设置为SYSTEM_UI_FLAG_FULLSCREEN的需求,那么请设置这两个标记,这样可以避免切换系统UI显示与隐藏时,AppUI被覆盖。
隐藏导航栏
以上都是被全屏化的View所需要进行的处理。对于控制器来说,如果不在布局中设置android:fitsSystemWindows=true
属性,那么控制器的UI就会跑到状态栏的下面。
为了避免以上的情况,就不得不解释setFitsSystemWindows(boolean)
和fitSystemWindows(rect)
方法了。
setFitsSystemWindows()
方法是设置此视图是否应该考虑系统UI,例如状态栏。并为其留出空间。如果参数为true,代表会为系统UI留出空间,那么fitSystemWindows(rect)
会被执行。
而fitSystemWindows(rect)
方法是,当Window内容的边距发生改变时(进入全屏化),允许调用该方法调正我们App的UI,以适应Window的变化。
正常情况下,系统UI会入侵到AppUI的一些空间(状态栏,输入法,导航栏等),也就是我们App的UI不会出现在系统UI之下。
不过万一你使用了SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION ,你的内容可能会被系统UI覆盖。此时,你能调用这个方法,来确保你的UI不会被系统的UI覆盖。
这个方法的简单实现是使用padding来做到的,并且这个方法默认是不执行的。
其实,在横屏全屏状态下,之所以EditText不会被输入法遮挡,都是由于执行了这个方法。