写在前面的几句话
<p>
其实大家在开发过程中,很少会接触到从竖屏到全屏的过程,只有在关于视频播放软件相关才会设计到这个方面的知识,刚好这次的开发中就遇到这样的问题了,那么就把这次遇到的针对关于全屏相关的东西记录下来。
一.简单实现横竖屏切换效果
<p>
其实这个很简单,只要打开手机的屏幕旋转功能就可以了,那么当手机旋转过来的时候,界面也会自动旋转过来的。
当然一般我们的开发都会屏蔽掉旋转,设置为单一的竖屏方向,设置如下
<activity
android:name=".TestActivity"
android:screenOrientation="portrait" />
这样的横竖屏其实可以满足部分的需求了,但是其实横竖屏相互旋转都是Activity重新的创建,这样在某些应用场景下就不满足需求了,比如当视频播放软件竖屏播放切换到横屏时,如果重新创建的话,那么视频自然就不能够持续播放了,这样自然就不能满足需求了,
如何实现旋转过程中不重新创建Activity呢?
configChanges
在AndroidManifest.xml中设置这个Activity的configChanges参数就可以了
如下
<activity
android:name=".TestActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
/>
添加这句的作用是,当Activity发生keyboardHidden(虚拟键盘隐藏),orientation(屏幕方向变化),screenSize(屏幕大小改变)这些变化的时候不会重新创建Activity,但是会在onConfigurationChanged方法中检测到响应的变化,通过这个方法才实现我们响应需要实现的逻辑
方法如下
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
那么我们结合下一个小的事例来讲解实现
图1 竖屏界面 图2 横屏界面如图所示,竖屏下上半部分为视频播放区域,下半部分则是相关的控制或者其他显示区域,而横屏下则全屏为视频播放区域,那么我们如何通过onConfigurationChanged来实现这样的功能呢?
首先通过onConfigurationChanged来判断横竖屏的转换
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
// TODO: 16/6/14 横屏相关操作
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
// TODO: 16/6/14 竖屏相关操作
}
}
接下来就是处理横竖屏幕响应的方法了
如果不做处理呢?我们可以看到旋转屏幕后会是这样的效果
图3 不做处理的横屏界面但是这样明显就和我们最开始给出的横屏界面不一致
所以为了实现这种效果我们分析一下,横屏的时候视频播放区域覆盖了整个屏幕,而控制显示区域则消失了,所以其实很好理解的是当横屏的时候将控制区域设置为GONE,而将视频播放区域设置为match_parent即可以,这样其实视频播放区域则可以自动拉升到充满整个屏幕,当竖屏的时候则将控制区域设置为VISIBLE,并将视频播放区域动态设置为之前的大小。
所以现在来看下onConfigurationChanged中的方法
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
//动态设置视频播放区域的为整个屏幕区域
DisplayMetrics dm = new DisplayMetrics();
this.getWindowManager().getDefaultDisplay().getMetrics(dm);
LinearLayout.LayoutParams linearParams = (LinearLayout.LayoutParams) mViedioLayout.getLayoutParams();
linearParams.height = dm.heightPixels;
linearParams.width = dm.widthPixels;
linearParams.setMargins(0, 0, 0, 0);
mViedioLayout.setLayoutParams(linearParams);
mControlLayout.setVisibility(View.GONE);
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
//动态设置视频播放区域为之前的大小
LinearLayout.LayoutParams linearParams = (LinearLayout.LayoutParams) mViedioLayout.getLayoutParams();
linearParams.height = (int) layoutheight;
linearParams.width = (int) layoutwidth;
mViedioLayout.setLayoutParams(linearParams);
mControlLayout.setVisibility(View.VISIBLE);
}
}
通过这样就实现所要求的结果,是不是很简单?
当然等等,还有新的需求,一般情况下视频播放软件是存在切换横竖屏的按钮的,点击则去旋转屏幕为横屏还是竖屏
怎么去做这个呢?查一下就知道有强制设置屏幕的方法了,如下
//设置为竖屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
//设置为横屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
当点击后使用上面的方法,屏幕则会旋转过来,而且在onConfigurationChanged中也会监听到屏幕的横竖方向转变,一切看起来如此完美
等等,怎么强制设置屏幕后,重力感应就没有了,wtf?这个是什么鬼?为什么会这样?
没办法只能想想通过别的方式来实现重力感应的效果了
二 .利用传感器服务实现横竖屏切换效果
<p>
首先我们需要知道下传感器,在Android设备中一般内置了很多的传感器,其中就包含重力感应传感器,虽然横竖屏切换与重力感应貌似有关,其实他们是两回事。横竖屏在重力感应中只是最粗略的说法了,你把手机平放着不动,重力感应都是时时刻刻发生着的,因为安卓设备能感应到很细微的震动。
Android中检测重力感应变化大致需要下面几个步骤:
- 得到传感器服务 getSystemService(SENSOR_SERVICE);
得到一个SensorManager,用来管理分配调度处理Sensor的工作,注意它并不服务运行于后台,真正属于Sensor的系统服务是SensorService,终端下#service list可以看到sensorservice: [android.gui.SensorServer]。
- 得到传感器类型 getDefaultSensor(Sensor.TYPE_GRAVITY);
当然还有各种千奇百怪的传感器,可以查阅Android官网API或者源码Sensor.java。
- 注册监听器 SensorEventListener
应用程序打开一个监听接口,专门处理传感器的数据,这个监听机制比较重要,被系统广泛使用。
- 实现监听器的回调函数 onSensorChanged, onAccuracyChanged
很多移动设备都内置了感应器,android通过Sensor和SensorManager类抽象了这些感应器,通过这些类可以使用android设备的传感器
所以我们通过onSensorChanged中的位置来判断手机的方向,通过这个方向来设置手机的横竖屏幕即可
ok,上代码如下
//注册重力感应器 屏幕旋转
SensorManager sm = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
Sensor sensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
OrientationSensorListener listener = new OrientationSensorListener(handler);
sm.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_UI);
private Handler handler = new Handler(){
public void handleMessage(Message msg) {
switch (msg.what) {
case 888:
int orientation = msg.arg1;
if (orientation>45&&orientation<135) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
}else if (orientation>135&&orientation<225){
}else if (orientation>225&&orientation<315){
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}else if ((orientation>315&&orientation<360)||(orientation>0&&orientation<45)){
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
break;
default:
break;
}
};
};
/**
* 重力感应监听者
*/
public class OrientationSensorListener implements SensorEventListener {
private static final int _DATA_X = 0;
private static final int _DATA_Y = 1;
private static final int _DATA_Z = 2;
public static final int ORIENTATION_UNKNOWN = -1;
private Handler rotateHandler;
public OrientationSensorListener(Handler handler) {
rotateHandler = handler;
}
public void onAccuracyChanged(Sensor arg0, int arg1) {
// TODO Auto-generated method stub
}
public void onSensorChanged(SensorEvent event) {
if(sensor_flag!=stretch_flag) //只有两个不相同才开始监听行为
{
float[] values = event.values;
int orientation = ORIENTATION_UNKNOWN;
float X = -values[_DATA_X];
float Y = -values[_DATA_Y];
float Z = -values[_DATA_Z];
float magnitude = X*X + Y*Y;
// Don't trust the angle if the magnitude is small compared to the y value
if (magnitude * 4 >= Z*Z) {
//屏幕旋转时
float OneEightyOverPi = 57.29577957855f;
float angle = (float)Math.atan2(-Y, X) * OneEightyOverPi;
orientation = 90 - (int)Math.round(angle);
// normalize to 0 - 359 range
while (orientation >= 360) {
orientation -= 360;
}
while (orientation < 0) {
orientation += 360;
}
}
System.out.println("orientation-->" + orientation);
if (rotateHandler!=null) {
rotateHandler.obtainMessage(888, orientation, 0).sendToTarget();
}
}
}
}
所以这里其实的逻辑就是检测重力感应传感器的变化,通过这个变化来设置屏幕的方向就可以了
看起来很完美的样子,实际运行就会发现问题了,当我们点击手动控制横竖屏幕的按钮后,发现又不起作用了,但是其实是有一个闪屏的效果,为什么会这样呢?
其实想想也可以理解,虽然我们强制设了横屏屏幕的方向,但是这个时候重力感应传感器是一直在监听手机的变化的啊,所以这时候变化就再次取强制设置屏幕方向,方向又被设为了竖屏,所以看起来就没有任何变化,但实际上这中间有这个设置的过程,既然知道是什么原因造成的了,那么就应该去解决这样的问题了
既然我们注册的重力感应监听的会对我们操作有影响,那么在按下的时候把重力感应监听的注册取消掉就好了,等后面在注册上就可以即对操作不影响,也在后面有了重力感应了啊,完美!!!!
等等,什么时候加上呢?问题来了,omg,无解,救命
没有什么事情是一个重力感应监听做不了,如果有,那么就用两个重力感应的监听
所以我们注册两个重力感应的监听,一个重力感应负责旋转屏幕方向,另一个负责动态去注册上面的那个监听,这样就解决了什么时候重力感应监听注册的问题了,
二话不说,直接上代码
//注册重力感应器 屏幕旋转
SensorManager sm = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
Sensor sensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
OrientationSensorListener listener = new OrientationSensorListener(handler);
sm.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_UI);
//根据 旋转之后 点击 符合之后 激活sm
SensorManager sm1 = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
Sensor sensor1 = sm1.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
listener1 = new OrientationSensorListener2();
sm1.registerListener(listener1, sensor1, SensorManager.SENSOR_DELAY_UI);
private Handler handler = new Handler(){
public void handleMessage(Message msg) {
switch (msg.what) {
case 888:
int orientation = msg.arg1;
if (orientation>45&&orientation<135) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
sensor_flag = false;
stretch_flag=false;
}else if (orientation>135&&orientation<225){
}else if (orientation>225&&orientation<315){
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
sensor_flag = false;
stretch_flag=false;
}else if ((orientation>315&&orientation<360)||(orientation>0&&orientation<45)){
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
sensor_flag = true;
stretch_flag=true;
}
break;
default:
break;
}
};
};
/**
* 重力感应监听者
*/
public class OrientationSensorListener implements SensorEventListener {
private static final int _DATA_X = 0;
private static final int _DATA_Y = 1;
private static final int _DATA_Z = 2;
public static final int ORIENTATION_UNKNOWN = -1;
private Handler rotateHandler;
public OrientationSensorListener(Handler handler) {
rotateHandler = handler;
}
public void onAccuracyChanged(Sensor arg0, int arg1) {
// TODO Auto-generated method stub
}
public void onSensorChanged(SensorEvent event) {
if(sensor_flag!=stretch_flag) //只有两个不相同才开始监听行为
{
float[] values = event.values;
int orientation = ORIENTATION_UNKNOWN;
float X = -values[_DATA_X];
float Y = -values[_DATA_Y];
float Z = -values[_DATA_Z];
float magnitude = X*X + Y*Y;
// Don't trust the angle if the magnitude is small compared to the y value
if (magnitude * 4 >= Z*Z) {
//屏幕旋转时
float OneEightyOverPi = 57.29577957855f;
float angle = (float)Math.atan2(-Y, X) * OneEightyOverPi;
orientation = 90 - (int)Math.round(angle);
// normalize to 0 - 359 range
while (orientation >= 360) {
orientation -= 360;
}
while (orientation < 0) {
orientation += 360;
}
}
System.out.println("orientation-->" + orientation);
if (rotateHandler!=null) {
rotateHandler.obtainMessage(888, orientation, 0).sendToTarget();
}
}
}
}
public class OrientationSensorListener2 implements SensorEventListener {
private static final int _DATA_X = 0;
private static final int _DATA_Y = 1;
private static final int _DATA_Z = 2;
public static final int ORIENTATION_UNKNOWN = -1;
public void onAccuracyChanged(Sensor arg0, int arg1) {
// TODO Auto-generated method stub
}
public void onSensorChanged(SensorEvent event) {
float[] values = event.values;
int orientation = ORIENTATION_UNKNOWN;
float X = -values[_DATA_X];
float Y = -values[_DATA_Y];
float Z = -values[_DATA_Z];
/**
* 这一段据说是 android源码里面拿出来的计算 屏幕旋转的 不懂 先留着 万一以后懂了呢
*/
float magnitude = X*X + Y*Y;
// Don't trust the angle if the magnitude is small compared to the y value
if (magnitude * 4 >= Z*Z) {
//屏幕旋转时
float OneEightyOverPi = 57.29577957855f;
float angle = (float)Math.atan2(-Y, X) * OneEightyOverPi;
orientation = 90 - (int)Math.round(angle);
// normalize to 0 - 359 range
while (orientation >= 360) {
orientation -= 360;
}
while (orientation < 0) {
orientation += 360;
}
}
if (orientation>45&&orientation<135) { //横屏
sensor_flag = false;
}else if (orientation>225&&orientation<315){ //横屏
sensor_flag = false;
}else if ((orientation>315&&orientation<360)||(orientation>0&&orientation<45)){ //竖屏
sensor_flag = true;
}
if(stretch_flag == sensor_flag){ //点击变成横屏 屏幕 也转横屏 激活
sm.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_UI);
}
}
}
ok,到这里就基本实现了所需求的功能了,其实全屏相关的内容应该大部分都已经涉及到了,相信后面如果有别的全屏需求,大家也应该可以以不变应万变了,
最后记得在pause的时候取消两个sensor的监听,因为真的很耗电的
写在后面的几句话
<p>
我的愿望是世界和平!!!!!!!
网友评论