このアプリケーションでは、写真のアップロード機能を作成する必要が生じることがよくありますが、一般的なアプローチは、システムのカメラインターフェイスを直接調整するか、サードパーティが提供するインターフェイスを使用することです。ユーザーの要件がカスタマイズされている場合、または独自のニーズがある場合はどうすればよいですか?今日は、手順に従ってカスタムカメラの製造を完了します。
1.カメラ関連API
写真の撮影には主に2つのクラスを使用します。1つは前回紹介したSurfaceViewで、もう1つはカメラです。したがって、カメラ関連のAPIについて学ぶ必要があります。
- getNumberOfCameras:このデバイスのカメラの数を取得します。
- open:カメラをオンにします。リアカメラはデフォルトでオンになっています。複数のカメラがある場合、open(0)はリアカメラを開くことを意味し、open(1)はフロントカメラを開くことを意味します。
- getParameters:カメラパラメータを取得し、Camera.Parametersオブジェクトを返します。
- setParameters:写真を撮るためのカメラパラメータを設定します。特定のカメラパラメータは、Camera.Parametersの次のメソッドを呼び出すことによって設定されます。
setPreviewSize |
プレビューインターフェイスのサイズを設定する |
setPictureSize | 保存した画像のサイズを設定します。 |
setPictureFormat | 画像フォーマットを設定します。通常、ImageFormat.JPEGはJPG形式を表すために使用されます。 |
setFocusMode | フォーカスモードを設定します。値Camera.Parameters.FOCUS_MODE_AUTOは1回だけフォーカスし、値FOCUS_MODE_CONTINUOUS_PICTUREは継続的にフォーカスします |
- setPreviewDisplay:プレビューインターフェイスのサーフェスホルダー、つまり、SurfaceHolderオブジェクトを設定します。このメソッドは、SurfaceHolder.CallbackのsurfaceCreatedメソッドで呼び出す必要があります。
- startPreview:プレビューを開始します。このメソッドは、setPreviewDisplayメソッドの後に呼び出す必要があります。
- ロック解除:カメラが録画を続行できるように、録画時にカメラのロックを解除する必要があります。このメソッドは、startPreviewメソッドの後に呼び出す必要があります。
- setDisplayOrientation:プレビューの角度を設定します。Androidの0度は水平位置で3時、携帯電話の画面は垂直位置であり、水平位置から垂直位置まで90度回転させる必要があります。
- autoFocus:フォーカスイベントを設定します。パラメータオートフォーカスインターフェイスAutoFocusCallbackのonAutoFocusメソッドは、フォーカスが完了するとトリガーされ、フォーカスが完了するとユーザーは写真を撮るように求められます。
- takePicture:写真の撮影を開始し、写真を撮るための関連イベントを設定します。最初のパラメーターはシャッターコールバックインターフェイスShutterCallbackであり、そのonShutterメソッドはシャッターが押されたときにトリガーされます。通常はここで写真のサウンドを再生できます。デフォルトは「クリック」です。2番目のパラメーターPictureCallbackは元のコールバックインターフェイスを表します。 image、通常はnullを直接渡すことを処理する必要はありません。3番目のパラメーターPictureCallbackはJPGイメージのコールバックインターフェースを表し、圧縮されたイメージデータはこのインターフェースのonPictureTakenメソッドで取得できます。
- setZoomChangeListener:ズーム率変更イベントを設定します。ズーム変更リスナーのonZoomChangeメソッドOnZoomChangeListenerは、ズーム率が変更されたときにトリガーされます。
- setPreviewCallback:プレビューコールバックイベントを設定します。通常、連続撮影中に呼び出されます。プレビューコールバックインターフェイスPreviewCallbackのonPreviewFrameメソッドは、プレビュー画像が変更されたときにトリガーされます。
- stopPreview:プレビューを停止します。
- ロック:録画後にカメラをロックします。このメソッドは、stopPreviewメソッドの後に呼び出されます。
- リリース:カメラをリリースします。カメラを繰り返し開くことはできないため、カメラを終了するたびにカメラを離す必要があります。
2.コードはサーフェスビューSurfaceViewを設定します
それから私達は私達の機能を実現するために一歩一歩。まず、表面図を作成する必要があります。
まず、レイアウトはこんな感じです。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<Button
android:id="@+id/bt_take_photo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="拍照"/>
</RelativeLayout>
その中には2つの要素があります。1つはサーフェスビューSurfaceViewで、もう1つはカメラボタンです。
以下は私たちの活動のコードです
public class MainActivity extends WaterPermissionActivity<MainModel>
implements MainCallback, View.OnClickListener {
private Button bt_take_photo;
private SurfaceView surfaceView;
@Override
protected MainModel getModelImp() {
return new MainModel(this,this);
}
@Override
protected int getContentLayoutId() {
return R.layout.activity_main;
}
@Override
protected void initWidget() {
bt_take_photo = findViewById(R.id.bt_take_photo);
surfaceView = findViewById(R.id.surfaceView);
bt_take_photo.setOnClickListener(this);
//获取表面视图的表面持有者
SurfaceHolder holder = surfaceView.getHolder();
//给表面持有者添加表面变更监听器
holder.addCallback(mSurfaceCallback);
//去除黑色背景,TRANSLUCENT半透明,TRANSPARENT透明
holder.setFormat(PixelFormat.TRANSPARENT);
requestPermission(CAMERA);
}
@Override
protected void doCamera() {
requestPermission(READ_EXTERNAL_STORAGE);
}
@Override
protected void doSDRead() {
requestPermission(WRITE_EXTERNAL_STORAGE);
}
@Override
protected void doSDWrite() {
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.bt_take_photo:
//点击拍照
break;
}
}
/**
* 表面变更监听器
*/
private SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
}
};
}
MainActivity、MainCallback、MainModelは私のMVCフレームワーク構造であり、気にしないでください。学生について知りたい場合は、私の以前のブログを読むか、メッセージを残して私に尋ねることができます。コードには、動的アクセス許可のリクエストの内容、古いルールも含まれています。独自のルールを使用できます。
コアコードには2つの部分があり、最初の部分は
//获取表面视图的表面持有者
SurfaceHolder holder = surfaceView.getHolder();
//给表面持有者添加表面变更监听器
holder.addCallback(mSurfaceCallback);
//去除黑色背景,TRANSLUCENT半透明,TRANSPARENT透明
holder.setFormat(PixelFormat.TRANSPARENT);
2番目の部分は、最初の部分のmSurfaceCallbackの定義です。
private SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
}
};
マニフェストファイルで設定された権限を忘れないでください
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.FLASHLIGHT"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
この時点で、サーフェスビューが設定されます。
3.カメラで撮影したスクリーンショットをSurfaceViewに表示します
まず、カメラ操作用のツールクラスを紹介します。後で使用します。
public class CameraUtil {
private static final Pattern COMMA_PATTERN = Pattern.compile(",");
public static Point getSize(Context ctx) {
WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
Point size = new Point();
size.x = dm.widthPixels;
size.y = dm.heightPixels;
return size;
}
public static Point getCameraSize(Camera.Parameters params, Point screenSize) {
String previewSizeValueString = params.get("preview-size-values");
if (previewSizeValueString == null) {
previewSizeValueString = params.get("preview-size-value");
}
Point cameraSize = null;
if (previewSizeValueString != null) {
cameraSize = findBestPreviewSizeValue(previewSizeValueString, screenSize);
}
if (cameraSize == null) {
cameraSize = new Point((screenSize.x >> 3) << 3, (screenSize.y >> 3) << 3);
}
return cameraSize;
}
private static Point findBestPreviewSizeValue(CharSequence previewSizeValueString, Point screenSize) {
int bestX = 0;
int bestY = 0;
int diff = Integer.MAX_VALUE;
for (String previewSize : COMMA_PATTERN.split(previewSizeValueString)) {
previewSize = previewSize.trim();
int dimPosition = previewSize.indexOf('x');
if (dimPosition < 0) {
continue;
}
int newX;
int newY;
try {
newX = Integer.parseInt(previewSize.substring(0, dimPosition));
newY = Integer.parseInt(previewSize.substring(dimPosition + 1));
} catch (NumberFormatException nfe) {
continue;
}
int newDiff = Math.abs((newX - screenSize.x) + (newY - screenSize.y));
if (newDiff == 0) {
bestX = newX;
bestY = newY;
break;
} else if (newDiff < diff) {
bestX = newX;
bestY = newY;
diff = newDiff;
}
}
if (bestX > 0 && bestY > 0) {
return new Point(bestX, bestY);
}
return null;
}
}
次に、MainActivityに次のメンバー変数を追加しました
private Camera mCamera;//声明一个相机对象
private boolean isPreviewing;//是否正在预览
private Point mCameraSize;//相机画面的尺寸
private int mCameraType = 0;//设置前置还是后置 0:后置 1:前置
public static int CAMERA_BEHIND = 0; // 后置摄像头
public static int CAMERA_FRONT = 1; // 前置摄像头
SurfaceHolder.Callbackに、次のコードを追加します
/**
* 表面变更监听器
*/
private SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
//打开摄像头
mCamera = Camera.open(mCameraType);
try {
//设置相机的预览页面
mCamera.setPreviewDisplay(holder);
//获得相机画面的尺寸
mCameraSize = CameraUtil.getCameraSize(mCamera.getParameters()
, CameraUtil.getSize(MainActivity.this));
//获取相机的参数信息
Camera.Parameters parameters = mCamera.getParameters();
//设置预览界面的尺寸
parameters.setPreviewSize(1920, 1080);
//设置图片的分辨率
parameters.setPictureSize(1920, 1080);
parameters.setJpegQuality(100); // 设置照片质量
//设置图片的格式
parameters.setPictureFormat(ImageFormat.JPEG);
//设置对焦模式为自动对焦。前置摄像头似乎无法自动对焦
if (mCameraType == CAMERA_BEHIND) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
}
//设置相机的参数信息
mCamera.setParameters(parameters);
} catch (IOException e) {
e.printStackTrace();
mCamera.release();//遇到异常要释放相机资源
mCamera = null;
}
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
//设置相机的展示角度
mCamera.setDisplayOrientation(90);
//开始预览画面
mCamera.startPreview();
isPreviewing = true;
//开始自动对焦
mCamera.autoFocus(null);
//设置相机的预览监听器。注意这里的setPreviewCallback给连拍功能用
// mCamera.setPreviewCallback(mPr);
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
//将预览监听器置空
mCamera.setPreviewCallback(null);
//停止预览画面
mCamera.stopPreview();
//释放相机资源
mCamera.release();
mCamera = null;
}
};
このようにして、SurfaceViewを実行すると画像が表示されることがわかります。
4.写真を撮る
次に、写真を撮るための主要な機能を入力しようとしています。
1つ目は、カメラボタンのクリックイベントです。ここでは、メソッドを呼び出します。
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_take_photo:
//点击拍照
doTakePicture();
break;
}
}
カメラ方式の実装は次のとおりです。
private void doTakePicture(){
if (isPreviewing && mCamera!=null){
//命令相机拍摄一张照片
mCamera.takePicture(mShutterCallback,null,mPictureCallback);
}
}
ここでは2つのオブジェクトが使用されています。mShutterCallbackはシャッターを押して監視するために実装したオブジェクトであり、mPictureCallbackは写真の結果のコールバックです。
mShutterCallbackの実装
private Camera.ShutterCallback mShutterCallback = new Camera.ShutterCallback() {
@Override
public void onShutter() {
}
};
これはシャッターを押した後の監視です。ここでプログラムにクリック音を鳴らしたり、その他の操作を実行したりできます。
mPictureCallbackの実装
private Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
Bitmap raw = null;
if (null != data) {
//原始图像数据data是字节数组,需要将其解析成位图
raw = BitmapFactory.decodeByteArray(data, 0, data.length);
//停止预览画面
mCamera.stopPreview();
isPreviewing = false;
}
//旋转位图
Bitmap bitmap = getRotateBitmap(raw
, (mCameraType == CAMERA_BEHIND) ? 90 : -90);
//获取本次拍摄的照片保存路径
List<String> listPath = new ArrayList<>();
listPath.add("myCamera");
listPath.add("photos");
mPhotoPath = PathGetUtil.getLongwayPath(MainActivity.this, listPath);
File fileDir = new File(mPhotoPath);
if (!fileDir.exists()) {
fileDir.mkdirs();
}
File filePic = new File(mPhotoPath, "ww" + System.currentTimeMillis() + ".jpg");
if (!filePic.exists()) {
try {
filePic.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
saveImage(filePic.getPath(), bitmap);
//保存文件需要时间
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//再次进入预览画面
mCamera.startPreview();
isPreviewing = true;
}
};
その中で、ビットマップを回転させるためのツールメソッドを使用します。コードは次のとおりです。
// 获得旋转角度之后的位图对象
public static Bitmap getRotateBitmap(Bitmap b, float rotateDegree) {
// 创建操作图片用的矩阵对象
Matrix matrix = new Matrix();
// 执行图片的旋转动作
matrix.postRotate(rotateDegree);
// 创建并返回旋转后的位图对象
return Bitmap.createBitmap(b, 0, 0, b.getWidth(),b.getHeight(), matrix, false);
}
メンバー変数に、写真の保存パスを追加します
private String mPhotoPath;//照片的保存路径
ビットマップ関連のメソッドの操作
public static void saveImage(String path, Bitmap bitmap){
try {
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(path));
bitmap.compress(Bitmap.CompressFormat.JPEG,80,bos);
bos.flush();
bos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
このようにして、カスタムカメラ機能が完成します。
ps:現在の携帯電話では、カメラは時代遅れであると言えます。Android 9.0携帯電話テストを使用していますが、撮影効果が良くありません。私たちが設定したプレビューサイズと写真サイズは、すでにサポートされている最大の1920 * 1080であるため、現在の携帯電話には明らかにあまり適していません。ただし、開発者として、このテクノロジーを理解し、基本的な実装を理解する必要があります。これは、Camera2およびJetpackで提供されるCameraXの将来の研究において私たちにとって良い助けになる可能性があります。