12018.07.09 21:49:31字数 2,153阅读 6,821
今天项目中遇到一个问题,因为我们的项目有几个视频播放的界面,所以避不开与全屏/半屏按钮、锁屏/解屏按钮、方向传感横竖屏自动变化打交道,我就被分到了这些任务。
先上一下大致的界面:
横屏
竖屏
一般来说,涉及到能播放视频的界面都大同小异(优酷、爱奇艺等),抛开弹幕、摄像头、倍速、截屏、截Gif等功能不谈,几乎都会有全屏/半屏播放、锁屏/解屏、方向传感这三个东西,而这三个东西都会牵涉到横竖屏问题。
一开始的时候,我认为很好做,也没多想,觉得思路就是这样:
⑴.全屏/半屏按钮点击逻辑:
监测点击全屏/半屏按钮时,用户屏幕所处的横竖位置,然后根据横竖屏,反向设置锁定死用户的屏幕:
/** * 横竖屏切换 */privatevoidchangeScreenOrientation(){//获取用户当前屏幕的横竖位置int currentOrientation =getResources().getConfiguration().orientation;//判断并设置用户点击全屏/半屏按钮的显示逻辑if(currentOrientation == Configuration.ORIENTATION_LANDSCAPE){//如果屏幕当前是横屏显示,则设置屏幕锁死为竖屏显示setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);}elseif(currentOrientation == Configuration.ORIENTATION_PORTRAIT){//如果屏幕当前是竖屏显示,则设置屏幕锁死为横屏显示setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);}}
⑵.锁屏按钮点击逻辑:
当点击锁屏按钮时,设置手机忽略方向传感;当点击解屏按钮时,设置手机恢复方向传感。
/** * 锁定/解锁屏幕点击事件 */privatevoidlockScreen(){if(mLockBtn.isChecked()){//锁定屏幕//忽略物理方向传感器,这样就不会随着用户旋转设备而横竖屏切换了setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);}else{//解锁屏幕//由设备的物理方向传感器决定,如果用户旋转设备,这屏幕就会横竖屏切换setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);}}
⑶.方向传感横竖屏自动变化逻辑
直接把这个界面在清单文件下设计一下即可:
<!-- 测试横竖屏视频播放界面 --><activityandroid:name=".modules.course.VodActivity"android:configChanges="orientation|screenSize|layoutDirection"android:screenOrientation="unspecified"/>
但是谁知道里面的坑很深,全然不是这么回事,写完之后测试就找上门了,说:我没锁死屏幕,点击全屏播放了,为啥我又竖着拿手机屏幕不自动切换回来了?
我擦,当然不能自动切换回来了,点击全屏播放,我锁死为手机横屏了!
当时觉得产品这个需求很不合理,又要全屏按钮控制横竖屏,又要小锁按钮控制横竖屏,还有手机方向感应控制横竖屏,一旦我在代码中强制设置屏幕的横竖变化,不论是设置为ActivityInfo的哪个控制横竖屏的值:
SCREEN_ORIENTATION_LANDSCAPE、
SCREEN_ORIENTATION_PORTRAIT、
SCREEN_ORIENTATION_SENSOR_LANDSCAPE、
SCREEN_ORIENTATION_SENSOR_PORTRAIT、
SCREEN_ORIENTATION_REVERSE_LANDSCAPE、
SCREEN_ORIENTATION_REVERSE_PORTRAIT、
SCREEN_ORIENTATION_USER_LANDSCAPE、
SCREEN_ORIENTATION_USER_PORTRAIT
手机的方向感应基本上算是都GG了。那点击全屏/竖屏按钮,强制设置了上面的值,还怎么再让手机方向感应自动变化呢。
而且还有一个不合理的场景,如果用户竖着拿手机,点击了界面的全屏播放,我设置界面为横屏了,但此处如果方向传感器还起作用的话,界面又立马变回竖屏了,那用户觉得全屏按钮还有啥作用呀。
:只能这么实现,没法做到点击全屏按钮之后,手机的方向传感器还在继续生效,就算继续生效,也会立马变回竖屏播放,相当于全屏按钮不好使。
:优酷做出来了
:....
:爱奇艺也做出来了
:...那啥,我再看看吧。
下了个爱奇艺和优酷,发现还真是做到了这个功能,他们的功能逻辑实现了方向感应和全屏/半屏按钮手动设置的兼容。对于上面的类似于功能冲突的点,他们的做法是,如果用户竖着拿手机,点击了界面的全屏播放,就会设置界面为横屏显示,方向传感器此时失效,但是当用户拿着手机变化了一下位置,比如我竖着拿手机,点击了全屏,屏幕此时向左横屏显示,但是我拿着手机往右横屏侧过来,此时方向传感器又起作用,把屏幕向右横屏显示。
好吧,用了一下午的时间在研究这个东西,发现不论怎么变换关键字,BD搜索到东西和这个功能没一点关联性,全是在说怎么设置横竖屏的问题,想念谷歌呀!
到后来自己摸索了一套方法,较为简单的解决了这个问题!
首先必须得先了解一下ActivityInfo类的各个属性值代表啥意思,不能乱用:
源码:@IntDef({ SCREEN_ORIENTATION_UNSET, SCREEN_ORIENTATION_UNSPECIFIED, SCREEN_ORIENTATION_LANDSCAPE, SCREEN_ORIENTATION_PORTRAIT, SCREEN_ORIENTATION_USER, SCREEN_ORIENTATION_BEHIND, SCREEN_ORIENTATION_SENSOR, SCREEN_ORIENTATION_NOSENSOR, SCREEN_ORIENTATION_SENSOR_LANDSCAPE, SCREEN_ORIENTATION_SENSOR_PORTRAIT, SCREEN_ORIENTATION_REVERSE_LANDSCAPE, SCREEN_ORIENTATION_REVERSE_PORTRAIT, SCREEN_ORIENTATION_FULL_SENSOR, SCREEN_ORIENTATION_USER_LANDSCAPE, SCREEN_ORIENTATION_USER_PORTRAIT, SCREEN_ORIENTATION_FULL_USER, SCREEN_ORIENTATION_LOCKED})
挨个解释一下(先找了这几个,剩下的有空搜到再一个个补充):
⑴首先先来分析全屏/半屏按钮,可以这么做:
还是顺着之前的思路来走:
/** * 横竖屏切换 */privatevoidchangeScreenOrientation(){//获取用户当前屏幕的横竖位置int currentOrientation =getResources().getConfiguration().orientation;//判断并设置用户点击全屏/半屏按钮的显示逻辑if(currentOrientation == Configuration.ORIENTATION_LANDSCAPE){//如果屏幕当前是横屏显示,则设置屏幕锁死为竖屏显示setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);}elseif(currentOrientation == Configuration.ORIENTATION_PORTRAIT){//如果屏幕当前是竖屏显示,则设置屏幕锁死为横屏显示setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);}}
设置完之后呢,发现手机的方向感应器失效了。那全屏半屏问题是解决了,
但是,最重要的事情也来了,我们需要在一个正确的“点”上恢复手机的方向感应器!
⑵怎么来找这个点呢?
我们来想,按照优酷的那个实现方法,竖屏拿着手机,点击全屏按钮之后,切换到横屏,此时如果用户不移动手机或者移动的幅度不大,屏幕还是横屏,一旦动作过大(比如我把手机横过来了),此时你会发现,方向感应器又好使了!
就这么来实现它!
在这里介绍一个监听器OrientationEventListener,可以用它来监听用户的手机位置变化情况:
OrientationEventListener orientationListener =newOrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL){ @OverridepublicvoidonOrientationChanged(int orientation){if(orientation == OrientationEventListener.ORIENTATION_UNKNOWN){//手机平放时,检测不到有效的角度return;}/** * 只检测是否有四个角度的改变 */if(orientation >350|| orientation<10){//0度,用户竖直拿着手机}elseif(orientation >80&& orientation <100){//90度,用户右侧(正向)横屏拿着手机}elseif(orientation >170&& orientation <190){//180度,用户反向竖直拿着手机}elseif(orientation >260&& orientation <280){//270度,用户左侧(反向)横屏拿着手机}}};
对应的具体方位图:
0度,用户竖直拿着手机
90度,用户右侧横屏拿着手机
180度,用户反向竖直拿着手机
270度,用户左侧横屏拿着手机
有了这个监听器,就变得很简单了。
我们可以这样,在onOrientationChanged(int orientation)实时记录用户的位置,看是否与记录的用户手机上一次手机所处的位置发生了改变,如果改变了,则用户手机发生了很大的位置偏移,就需要恢复用户手机的方向传感器。
@OverridepublicvoidonOrientationChanged(int orientation){//判nullif(mActivity ==null|| mActivity.isFinishing()){return;}//记录用户手机上一次放置的位置int mLastOrientation = mOrientation;if(orientation == OrientationEventListener.ORIENTATION_UNKNOWN){//手机平放时,检测不到有效的角度//重置为原始位置 -1 mOrientation =-1;return;}/** * 只检测是否有四个角度的改变 */if(orientation >350|| orientation<10){//0度,用户竖直拿着手机 mOrientation =0;}elseif(orientation >80&& orientation <100){//90度,用户右侧横屏拿着手机 mOrientation =90;}elseif(orientation >170&& orientation <190){//180度,用户反向竖直拿着手机 mOrientation =180;}elseif(orientation >260&& orientation <280){//270度,用户左侧横屏拿着手机 mOrientation =270;}//如果用户锁定了屏幕,不再开启代码自动旋转了,直接returnif(mIsLock){return;}//如果用户关闭了手机的屏幕旋转功能,不再开启代码自动旋转了,直接returntry{/** * 1 手机已开启屏幕旋转功能 * 0 手机未开启屏幕旋转功能 */if(Settings.System.getInt(getContentResolver(), Settings.System.ACCELEROMETER_ROTATION)==0){return;}}catch(Settings.SettingNotFoundException e){ e.printStackTrace();}//当检测到用户手机位置距离上一次记录的手机位置发生了改变,开启屏幕自动旋转if(mLastOrientation != mOrientation){ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);}}
大头搞定,剩下的就简单了,完善其他小逻辑。
⑶手机本身的方向传感器
这个简单,可以直接在AndroidManifest.xml中设置SCREEN_ORIENTATION_UNSPECIFIED属性就可以了,剩下的,交给OrientationEventListener和小锁的逻辑重新对其赋值即可
<activityandroid:name=".modules.course.DemoActivity"android:configChanges="orientation|screenSize|layoutDirection"android:screenOrientation="unspecified"/>
⑷小锁的逻辑
检测下小锁的开关是否打开,如果打开,就放开手机方向感应器(前提是用户打开了自己手机上的自动旋转);如果关闭,就是锁死手机方向感应器:
/** * 锁定/解锁屏幕点击事件 */privatevoidlockScreen(){if(mLockBtn.isChecked()){//锁定屏幕//获取用户当前屏幕的横竖位置int currentOrientation =getResources().getConfiguration().orientation;//判断并设置用户点击锁定屏幕按钮的显示逻辑if(currentOrientation == Configuration.ORIENTATION_LANDSCAPE){//如果屏幕当前是横屏显示,则设置屏幕锁死为横屏显示if(mMyOrientationDetector.getDisplayRotation()==90){//正向横屏setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);}elseif(mMyOrientationDetector.getDisplayRotation()==270){//反向横屏setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);}}elseif(currentOrientation == Configuration.ORIENTATION_PORTRAIT){//如果屏幕当前是竖屏显示,则设置屏幕锁死为竖屏显示setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);}}else{//解锁屏幕try{/** * 1 手机已开启屏幕旋转功能 * 0 手机未开启屏幕旋转功能 */if(Settings.System.getInt(getContentResolver(), Settings.System.ACCELEROMETER_ROTATION)==1){setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);}}catch(Settings.SettingNotFoundException e){ e.printStackTrace();}}}
其中,getDisplayRotation() 是一个检测手机正反向横竖屏的方法:
/** * 获取当前屏幕旋转角度 * * @return * * 0 - 表示是竖屏 90 - 表示是左横屏(正向) 180 - 表示是反向竖屏 270表示是右横屏(反向) */publicintgetDisplayRotation(){int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();switch(rotation){case Surface.ROTATION_0:return0;case Surface.ROTATION_90:return90;case Surface.ROTATION_180:return180;case Surface.ROTATION_270:return270;}return0;}
这里在赘述一点,当时我想的是:
①锁定屏幕,直接设置SCREEN_ORIENTATION_NOSENSOR,不再这么麻烦的先去判断屏幕正反向横竖屏,再去分别锁死,但是这里面具体实现起来发现了个坑,如果用户横屏拿着手机进行播放,界面也是横屏的,一旦设置了这个属性之后,手机界面会先变换到竖屏,然后才会锁死方向传感器。
②锁定屏幕,直接设置SCREEN_ORIENTATION_LOCKED,不再这么麻烦的先去判断屏幕正反向横竖屏,再去分别锁死,但是这里面也发现了个坑,这个属性在低版本手机上不生效,它也会直接变回到竖屏再锁死
③锁定屏幕,判断横竖屏再进行锁死:
if(mLockBtn.isChecked()){//锁定屏幕//获取用户当前屏幕的横竖位置int currentOrientation =getResources().getConfiguration().orientation;//判断并设置用户点击锁定屏幕按钮的显示逻辑if(currentOrientation == Configuration.ORIENTATION_LANDSCAPE){//如果屏幕当前是横屏显示,则设置屏幕锁死为横屏显示setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);}elseif(currentOrientation == Configuration.ORIENTATION_PORTRAIT){//如果屏幕当前是竖屏显示,则设置屏幕锁死为竖屏显示setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);}}
但是这样的话,也有一个bug:
我在上面介绍各个属性的时候,说过横屏分为正向横屏和反向横屏,竖屏同理。
此时如果用户是反向横屏拿着手机的,代码这么写的话,直接设置SCREEN_ORIENTATION_LANDSCAPE,手机屏幕会直接翻转到正向横屏再锁死,让用户感到奇怪。
以上就是我做锁定屏幕的时候,经历的种种探索,感觉好麻烦,但是做出来还是挺开心的。
OK,到了这里,就把三个逻辑融合到一起了。最后再优化优化逻辑(大体思路就是上面的这些东西)、封装成一个工具类:
/** * 自定义工具类 * * 实时的检测用户屏幕的位置改变,做到 重力感应 与 全屏/半屏按钮 逻辑的互容 */publicclassCLOrientationDetector extends OrientationEventListener {/** * 横竖屏BaseActivity */privateBaseActivity mActivity;/** * 用户是否锁定屏幕 */private boolean mIsLock =false;/** * 实时记录用户手机屏幕的位置 */privateint mOrientation =-1;/** * 构造方法 * @param activity */publicCLOrientationDetector(BaseActivity activity){super(activity); mActivity = activity;}/** * 设置用户屏幕是否被锁定 * @param isLock */publicvoidsetIsLock(boolean isLock){ mIsLock = isLock;} @OverridepublicvoidonOrientationChanged(int orientation){//判nullif(mActivity ==null|| mActivity.isFinishing()){return;}//记录用户手机上一次放置的位置int mLastOrientation = mOrientation;if(orientation == OrientationEventListener.ORIENTATION_UNKNOWN){//手机平放时,检测不到有效的角度//重置为原始位置 -1 mOrientation =-1;return;}/** * 只检测是否有四个角度的改变 */if(orientation >350|| orientation<10){//0度,用户竖直拿着手机 mOrientation =0;}elseif(orientation >80&& orientation <100){//90度,用户右侧横屏拿着手机 mOrientation =90;}elseif(orientation >170&& orientation <190){//180度,用户反向竖直拿着手机 mOrientation =180;}elseif(orientation >260&& orientation <280){//270度,用户左侧横屏拿着手机 mOrientation =270;}//如果用户锁定了屏幕,不再开启代码自动旋转了,直接returnif(mIsLock){return;}//如果用户关闭了手机的屏幕旋转功能,不再开启代码自动旋转了,直接returntry{/** * 1 手机已开启屏幕旋转功能 * 0 手机未开启屏幕旋转功能 */if(Settings.System.getInt(mActivity.getContentResolver(), Settings.System.ACCELEROMETER_ROTATION)==0){return;}}catch(Settings.SettingNotFoundException e){ e.printStackTrace();}//当检测到用户手机位置距离上一次记录的手机位置发生了改变,开启屏幕自动旋转if(mLastOrientation != mOrientation){ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);}}/** * 获取当前屏幕旋转角度 * * @return * * 0 - 表示是竖屏 90 - 表示是左横屏(正向) 180 - 表示是反向竖屏 270表示是右横屏(反向) */publicintgetDisplayRotation(){int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();switch(rotation){case Surface.ROTATION_0:return0;case Surface.ROTATION_90:return90;case Surface.ROTATION_180:return180;case Surface.ROTATION_270:return270;}return0;}}
这篇文章说的比较简单,主要想表明的就是这个思路,思路有了,就上手代码就容易了。小伙伴们如果有更好的思路的话,欢迎指正。