android之Display.getRotation()_传感器控制屏幕旋转

本文探讨了Android中屏幕方向设置的方法及其对传感器数据的影响。通过分析源代码,解释了如何根据不同屏幕方向调整加速度传感器的数据,确保游戏等应用在不同设备上的正确表现。

在看android自带的samples源码里面的AccelerometerPlayActivity时,看到下面这段代码,很不理解

public void onSensorChanged(SensorEvent event) {
	if(event.sensor.getType() != Sensor.TYPE_ACCELEROMETER){
		return;
	}
	switch (mDisplay.getRotation()) {
	    case Surface.ROTATION_0://手机处于正常状态
                  mSensorX = event.values[0];
                mSensorY = event.values[1];
                break;
            case Surface.ROTATION_90://手机旋转90度
                  mSensorX = -event.values[1];
                mSensorY = event.values[0];
                break;
            case Surface.ROTATION_180:
                mSensorX = -event.values[0];
                mSensorY = -event.values[1];
                break;
            case Surface.ROTATION_270:
                mSensorX = event.values[1];
                mSensorY = -event.values[0];
                break;
	}
}

经过查阅资料大体了解了
通过AndroidManifest.xml设置屏幕方向的话,安装后就不能改变,而程序内部设置屏幕方向就不会有这个限制。主要靠这两个API:getRequestedOrientation()和setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)这两个API通过ActivityManagerService.java的转换后,实际上都是调用的WindowManagerService的同名方法。

每个Activity在WindowManagerService端都有一个AppWindowToken做代表,而屏幕的方向信息就存储在这里。
PhoneWindowManager会自动根据屏幕物理特性决定屏幕方向,看这段代码:

if (mPortraitRotation < 0) {  
    // Initialize the rotation angles for each orientation once.   
    Display d = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))  
            .getDefaultDisplay();  
    if (d.getWidth() > d.getHeight()) {  
        mPortraitRotation = Surface.ROTATION_90;  
        mLandscapeRotation = Surface.ROTATION_0;  
        mUpsideDownRotation = Surface.ROTATION_270;  
        mSeascapeRotation = Surface.ROTATION_180;  
    } else {  
        mPortraitRotation = Surface.ROTATION_0;  
        mLandscapeRotation = Surface.ROTATION_90;  
        mUpsideDownRotation = Surface.ROTATION_180;  
        mSeascapeRotation = Surface.ROTATION_270;  
    }  
}  

这里的d.getWidth() 和 d.getHeight()得到的是物理屏幕的宽高。一般来说,平板和手机的是不一样的。

平板是宽比高大(0度时位于landscape模式,右转90度进入porit模式),手机是高比宽大(0度是位于porit模式,右转90度进入landscape模式)。如果应用程序只关心当前是横屏还是竖屏,而不直接使用传感器的话,没什么问题。如果像依靠重力感应的游戏那样直接使用传感器,就需要自己根据物理屏幕的坐标系对传感器数据做转化,否则就会出现坐标系混乱的问题。

如果没有没有通过上面的d.getWidth()和d.getHeight()来检测设备的物理屏幕从确定哪个是landscape和porit模式,而是直接假设设备是和手机一样的模式。由于游戏运行在landscape模式下,它们都把传感器数据右转90度。这样做法在手机上是没有问题,但在平板电脑上是不应该转化的,这是因为物理屏幕宽比高大的情况下,默认就是landscape模式。

现在回到源代码,在这里没有区分手机和平板,仅仅是用来转换加速度的方向而已,也没有必要区分.

case Surface.ROTATION_0://手机处于正常状态
                mSensorX = event.values[0];
                mSensorY = event.values[1];
                break;

这段就是如果手机的方向没有旋转,不管手机处于landscape还是porit模式,加速度的方向都不用变,而下面,如果手机旋转了180度,说明x轴和y轴的方向完全反过来了,这时候对于加速度的方向就要调整到反向.还有90度和270度的情况都类似.

case Surface.ROTATION_180:
                mSensorX = -event.values[0];
                mSensorY = -event.values[1];
                break;



package com.android.example.cameraappxjava; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import android.content.pm.PackageManager; import android.graphics.ImageFormat; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.params.StreamConfigurationMap; import android.media.Image; import android.media.ImageReader; import android.os.Bundle; import android.util.DisplayMetrics; import android.util.Log; import android.util.Size; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.widget.Toast; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.List; import android.Manifest; import android.content.Context; public class MainActivity extends AppCompatActivity { private CameraManager cameraManager; private String cameraId; private Size photoSize; private TextureView textureView; private CameraDevice cameraDevice; private CameraCaptureSession captureSession; private ImageReader imageReader; private int screenWidth, screenHeight; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); CameraUtils.init(this); initCamera(); initViews(); setupTextureListener(); } private void initCamera() { List<Size> outputSizes; // 获取屏幕尺寸 DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); screenWidth = metrics.widthPixels; screenHeight = metrics.heightPixels; try { CameraUtils cameraUtils = CameraUtils.getInstance(); cameraManager = cameraUtils.getCameraManager(); cameraId = cameraUtils.getBackCameraId(); outputSizes = cameraUtils.getCameraOutputSizes(cameraId, SurfaceTexture.class); // 根据屏幕比例选择最佳预览尺寸 photoSize = chooseOptimalSize(outputSizes); Log.d("Camera", "Selected preview size: " + photoSize.getWidth() + "x" + photoSize.getHeight()); } catch (Exception e) { Log.e("Camera", "初始化失败", e); } } private Size chooseOptimalSize(List<Size> choices) { // 获取视图的实际尺寸 int viewWidth = textureView.getWidth(); int viewHeight = textureView.getHeight(); if (viewWidth == 0 || viewHeight == 0) { // 视图未渲染时使用屏幕尺寸 DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); viewWidth = metrics.widthPixels; viewHeight = metrics.heightPixels; } float targetRatio = (float) viewWidth / viewHeight; float minAspectDiff = Float.MAX_VALUE; float minSizeDiff = Float.MAX_VALUE; Size optimalSize = null; for (Size size : choices) { float aspectRatio = (float) size.getWidth() / size.getHeight(); float aspectDiff = Math.abs(aspectRatio - targetRatio); float sizeDiff = Math.abs(size.getWidth() - viewWidth) + Math.abs(size.getHeight() - viewHeight); // 优先选择宽高比匹配的尺寸 if (aspectDiff < minAspectDiff || (aspectDiff == minAspectDiff && sizeDiff < minSizeDiff)) { minAspectDiff = aspectDiff; minSizeDiff = sizeDiff; optimalSize = size; } } return optimalSize != null ? optimalSize : choices.get(0); } private void initViews() { textureView = findViewById(R.id.texture_view); } private void setupTextureListener() { textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { openCamera(); } @Override public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) { // 视图尺寸变化时重新配置相机 closeCamera(); configureTextureSize(width, height); openCamera(); } private void configureTextureSize(int width, int height) { // 计算视图宽高比 float viewRatio = (float) width / height; // 获取相机特性 CameraCharacteristics characteristics = null; characteristics = CameraUtils.getCameraCharacteristics(cameraId); StreamConfigurationMap map = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); // 找出匹配视图比例的输出尺寸 for (Size size : map.getOutputSizes(SurfaceTexture.class)) { float sizeRatio = (float) size.getWidth() / size.getHeight(); if (Math.abs(sizeRatio - viewRatio) < 0.01) { photoSize = size; break; } } } @Override public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) { return false; } @Override public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { } // 其他回调方法省略... }); } private void openCamera() { try { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { cameraManager.openCamera(cameraId, new CameraDevice.StateCallback() { @Override public void onOpened(@NonNull CameraDevice camera) { cameraDevice = camera; createPreviewSession(); } @Override public void onDisconnected(@NonNull CameraDevice camera) { } @Override public void onError(@NonNull CameraDevice camera, int error) { } // 其他回调方法省略... }, null); } } catch (CameraAccessException e) { e.printStackTrace(); } } private void createPreviewSession() { try { SurfaceTexture texture = textureView.getSurfaceTexture(); // 确保尺寸匹配 texture.setDefaultBufferSize(photoSize.getWidth(), photoSize.getHeight()); Surface previewSurface = new Surface(texture); // 添加会话状态回调 cameraDevice.createCaptureSession(Arrays.asList(previewSurface), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { captureSession = session; try { CaptureRequest.Builder builder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); builder.addTarget(previewSurface); // 设置连续自动对焦 builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); session.setRepeatingRequest(builder.build(), null, null); } catch (CameraAccessException e) { Log.e("Camera", "预览失败", e); } } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { Toast.makeText(MainActivity.this, "预览配置失败", Toast.LENGTH_SHORT).show(); } }, null); } catch (CameraAccessException e) { Log.e("Camera", "创建会话异常", e); } } public void capturePhoto(View view) { if (cameraDevice == null) return; try { // 确保只有一个ImageReader实例 if (imageReader != null) { imageReader.close(); } imageReader = ImageReader.newInstance( photoSize.getWidth(), photoSize.getHeight(), ImageFormat.JPEG, 2); imageReader.setOnImageAvailableListener(reader -> { Image image = reader.acquireNextImage(); saveImage(image); image.close(); }, null); CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); Surface previewSurface = new Surface(textureView.getSurfaceTexture()); captureBuilder.addTarget(previewSurface); // 维持预览目标 captureBuilder.addTarget(imageReader.getSurface()); // 添加拍照目标 captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); // 设置拍照方向(根据设备方向调整) int rotation = getWindowManager().getDefaultDisplay().getRotation(); captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation)); // 停止预览,拍照,然后恢复预览 captureSession.stopRepeating(); captureSession.capture(captureBuilder.build(), new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { super.onCaptureCompleted(session, request, result); // 拍照完成后恢复预览 try { CaptureRequest.Builder previewBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); previewBuilder.addTarget(previewSurface); session.setRepeatingRequest(previewBuilder.build(), null, null); } catch (CameraAccessException e) { e.printStackTrace(); } } }, null); } catch (CameraAccessException e) { e.printStackTrace(); } } // 根据屏幕旋转计算照片方向 private int getOrientation(int rotation) { switch (rotation) { case Surface.ROTATION_0: return 90; case Surface.ROTATION_90: return 0; case Surface.ROTATION_180: return 270; case Surface.ROTATION_270: return 180; default: return 0; } } private void saveImage(Image image) { ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); File file = new File(getExternalFilesDir(null), "photo_" + System.currentTimeMillis() + ".jpg"); try (FileOutputStream output = new FileOutputStream(file)) { output.write(bytes); Toast.makeText(this, "Saved: " + file.getAbsolutePath(), Toast.LENGTH_SHORT).show(); } catch (IOException e) { e.printStackTrace(); } } // CameraUtils类扩展 public static class CameraUtils { private static Context appContext; private static CameraManager cameraManager; private static CameraUtils instance; public static void init(Context context) { if (appContext == null) { appContext = context.getApplicationContext(); cameraManager = (CameraManager) appContext.getSystemService(Context.CAMERA_SERVICE); } } public static CameraUtils getInstance() { if (instance == null) { instance = new CameraUtils(); } return instance; } public CameraManager getCameraManager() { return cameraManager; } public String getBackCameraId() throws CameraAccessException { for (String id : cameraManager.getCameraIdList()) { CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id); Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); if (facing != null && facing == CameraCharacteristics.LENS_FACING_BACK) { return id; } } return null; } public List<Size> getCameraOutputSizes(String cameraId, Class klass) throws CameraAccessException { CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId); StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); return Arrays.asList(map.getOutputSizes(klass)); } public static CameraCharacteristics getCameraCharacteristics(String cameraId) { try { return cameraManager.getCameraCharacteristics(cameraId); } catch (CameraAccessException e) { Log.e("Camera", "获取特性失败", e); return null; } } public static int getCameraOrientation(String cameraId) { CameraCharacteristics characteristics = getCameraCharacteristics(cameraId); if (characteristics != null) { return characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); } return 0; } } // 添加权限请求码 private static final int CAMERA_PERMISSION_CODE = 101; @Override protected void onResume() { super.onResume(); if (!checkCameraPermission()) { requestCameraPermission(); } else if (textureView.isAvailable()) { openCamera(); } } private boolean checkCameraPermission() { return ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED; } private void requestCameraPermission() { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, CAMERA_PERMISSION_CODE); } @Override protected void onPause() { closeCamera(); super.onPause(); } private void closeCamera() { if (captureSession != null) { captureSession.close(); captureSession = null; } if (cameraDevice != null) { cameraDevice.close(); cameraDevice = null; } if (imageReader != null) { imageReader.close(); imageReader = null; } } } 现在代码闪退
09-06
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值