文章目录

    • 引言
    • 环境要求
    • 代码实现
    • 总结

引言

  之前做过几种水印需求,这篇文章是关于使用Android原生库开发录制视频自带满帧文字水印。

环境要求

  • Android 7.0以上
  • Android Studio ,官方开发者官网
  • 视频录制功能参考开源库PictureSelector的camerax库
  //用到的Google 集成摄像头库const val camerax = "1.4.2" const val appcompat = "1.2.0"val cameraEffect="androidx.camera:camera-effects:${Versions.camerax}"  //新版升级关键水印库val cameraHigh = "com.otaliastudios:cameraview:2.7.2"val cameraCore = "androidx.camera:camera-core:${Versions.camerax}"val camera2 = "androidx.camera:camera-camera2:${Versions.camerax}"val cameraLife = "androidx.camera:camera-lifecycle:${Versions.camerax}"val cameraView = "androidx.camera:camera-view:${Versions.camerax}"val cameraTransient = "androidx.transition:transition:${Versions.appcompat}"val cameraFutures = "androidx.concurrent:concurrent-futures:1.1.0"

代码实现

  参考开源库PictureSelector的camerax库,如果代码缺失某个类,那么去PictureSelector库拷贝类使用即可。
最新的Camerax修改buildCase的逻辑,改用Recording类,VideoCapture,也不支持设置帧率、比特率,使用分辨率选项调整视频的清晰度和文件大小,即 setQualitySelector(QualitySelector.from(Quality.LOWEST))
核心变动 bindCameraVideoUseCases(),编码参考官网OverlayEffect写法。


//   CustomCameraView.kt
package com.luck.lib.camerax;import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.display.DisplayManager;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;import androidx.annotation.NonNull;
import androidx.arch.core.util.Function;
import androidx.camera.camera2.interop.Camera2CameraInfo;
import androidx.camera.core.AspectRatio;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraControl;
import androidx.camera.core.CameraEffect;
import androidx.camera.core.CameraInfo;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.FocusMeteringResult;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureException;
import androidx.camera.core.MeteringPoint;
import androidx.camera.core.MeteringPointFactory;
import androidx.camera.core.Preview;
import androidx.camera.core.UseCaseGroup;
import androidx.camera.core.ZoomState;
import androidx.camera.effects.Frame;
import androidx.camera.effects.OverlayEffect;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.video.FileOutputOptions;
import androidx.camera.video.Quality;
import androidx.camera.video.QualitySelector;
import androidx.camera.video.Recorder;
import androidx.camera.video.Recording;
import androidx.camera.video.VideoCapture;
import androidx.camera.video.VideoRecordEvent;
import androidx.camera.view.LifecycleCameraController;
import androidx.camera.view.PreviewView;
import androidx.core.content.ContextCompat;
import androidx.core.util.Consumer;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;import com.google.common.util.concurrent.ListenableFuture;
import com.luck.lib.camerax.listener.CameraListener;
import com.luck.lib.camerax.listener.CameraXOrientationEventListener;
import com.luck.lib.camerax.listener.CameraXPreviewViewTouchListener;
import com.luck.lib.camerax.listener.CaptureListener;
import com.luck.lib.camerax.listener.ClickListener;
import com.luck.lib.camerax.listener.ImageCallbackListener;
import com.luck.lib.camerax.listener.TypeListener;
import com.luck.lib.camerax.permissions.PermissionChecker;
import com.luck.lib.camerax.permissions.PermissionResultCallback;
import com.luck.lib.camerax.permissions.SimpleXPermissionUtil;
import com.luck.lib.camerax.utils.CameraUtils;
import com.luck.lib.camerax.utils.DensityUtil;
import com.luck.lib.camerax.utils.FileUtils;
import com.luck.lib.camerax.utils.SimpleXSpUtils;
import com.luck.lib.camerax.widget.CaptureLayout;
import com.luck.lib.camerax.widget.FocusImageView;import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;/*** @describe:自定义相机View*/
public class CustomCameraView extends RelativeLayout implements CameraXOrientationEventListener.OnOrientationChangedListener {private static final double RATIO_4_3_VALUE = 4.0 / 3.0;private static final double RATIO_16_9_VALUE = 16.0 / 9.0;/*** 闪关灯状态*/private static final int TYPE_FLASH_AUTO = 0x021;private static final int TYPE_FLASH_ON = 0x022;private static final int TYPE_FLASH_OFF = 0x023;private int typeFlash = TYPE_FLASH_OFF;private PreviewView mCameraPreviewView;private ProcessCameraProvider mCameraProvider;private ImageCapture mImageCapture;private ImageAnalysis mImageAnalyzer;private VideoCapture<Recorder> mVideoCapture;private Recording mRecording;private int displayId = -1;/*** 相机模式*/private int buttonFeatures;/*** 自定义拍照输出路径*/private String outPutCameraDir;/*** 自定义拍照文件名*/private String outPutCameraFileName;/*** 设置每秒的录制帧数*/private int videoFrameRate;/*** 设置编码比特率。*/private int videoBitRate;/*** 视频录制最小时长*/private int recordVideoMinSecond;/*** 是否显示录制时间*/private boolean isDisplayRecordTime;/*** 图片文件类型*/private String imageFormat, imageFormatForQ;/*** 视频文件类型*/private String videoFormat, videoFormatForQ;/*** 相机模式*/private int useCameraCases = LifecycleCameraController.IMAGE_CAPTURE;/*** 摄像头方向*/private int lensFacing = CameraSelector.LENS_FACING_BACK;/*** 手指点击对焦*/private boolean isManualFocus;/*** 双击可放大缩小*/private boolean isZoomPreview;/*** 是否自动纠偏*/private boolean isAutoRotation;private long recordTime = 0;private String markTxt = "Gree";/*** 回调监听*/private CameraListener mCameraListener;private ClickListener mOnClickListener;private ImageCallbackListener mImageCallbackListener;private ImageView mImagePreview;private View mImagePreviewBg;private ImageView mSwitchCamera;private ImageView mFlashLamp;private TextView tvCurrentTime;private CaptureLayout mCaptureLayout;private MediaPlayer mMediaPlayer;private TextureView mTextureView;private DisplayManager displayManager;private DisplayListener displayListener;private CameraXOrientationEventListener orientationEventListener;private CameraInfo mCameraInfo;private CameraControl mCameraControl;private FocusImageView focusImageView;private Executor mainExecutor;private Activity activity;private boolean isImageCaptureEnabled() {return useCameraCases == LifecycleCameraController.IMAGE_CAPTURE;}public CustomCameraView(Context context) {super(context);initView();}public CustomCameraView(Context context, AttributeSet attrs) {super(context, attrs);initView();}public CustomCameraView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initView();}private void initView() {inflate(getContext(), R.layout.picture_camera_view, this);activity = (Activity) getContext();setBackgroundColor(ContextCompat.getColor(getContext(), R.color.picture_color_black));mCameraPreviewView = findViewById(R.id.cameraPreviewView);mTextureView = findViewById(R.id.video_play_preview);focusImageView = findViewById(R.id.focus_view);mImagePreview = findViewById(R.id.cover_preview);mImagePreviewBg = findViewById(R.id.cover_preview_bg);mSwitchCamera = findViewById(R.id.image_switch);mFlashLamp = findViewById(R.id.image_flash);mCaptureLayout = findViewById(R.id.capture_layout);tvCurrentTime = findViewById(R.id.tv_current_time);mSwitchCamera.setImageResource(R.drawable.picture_ic_camera);displayManager = (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE);displayListener = new DisplayListener();displayManager.registerDisplayListener(displayListener, null);mainExecutor = ContextCompat.getMainExecutor(getContext());mCameraPreviewView.post(new Runnable() {@Overridepublic void run() {if (mCameraPreviewView != null) {Display display = mCameraPreviewView.getDisplay();if (display != null) {displayId = display.getDisplayId();}}}});mFlashLamp.setOnClickListener(v -> {typeFlash++;if (typeFlash > 0x023) {typeFlash = TYPE_FLASH_AUTO;}setFlashMode();});mSwitchCamera.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {toggleCamera();}});mCaptureLayout.setCaptureListener(new CaptureListener() {@Overridepublic void takePictures() {if (!mCameraProvider.isBound(mImageCapture)) {bindCameraImageUseCases();}useCameraCases = LifecycleCameraController.IMAGE_CAPTURE;mCaptureLayout.setButtonCaptureEnabled(false);mSwitchCamera.setVisibility(INVISIBLE);mFlashLamp.setVisibility(INVISIBLE);tvCurrentTime.setVisibility(GONE);ImageCapture.Metadata metadata = new ImageCapture.Metadata();metadata.setReversedHorizontal(isReversedHorizontal());ImageCapture.OutputFileOptions fileOptions;File cameraFile;if (isSaveExternal()) {cameraFile = FileUtils.createTempFile(getContext(), false);} else {cameraFile = FileUtils.createCameraFile(getContext(), CameraUtils.TYPE_IMAGE,outPutCameraFileName, imageFormat, outPutCameraDir);}fileOptions = new ImageCapture.OutputFileOptions.Builder(cameraFile).setMetadata(metadata).build();mImageCapture.takePicture(fileOptions, mainExecutor,new MyImageResultCallback(CustomCameraView.this, mImagePreview, mImagePreviewBg,mCaptureLayout, mImageCallbackListener, mCameraListener));}@SuppressLint("MissingPermission")@Overridepublic void recordStart() {if (!mCameraProvider.isBound(mVideoCapture)) {bindCameraVideoUseCases();}useCameraCases = LifecycleCameraController.VIDEO_CAPTURE;mSwitchCamera.setVisibility(INVISIBLE);mFlashLamp.setVisibility(INVISIBLE);tvCurrentTime.setVisibility(isDisplayRecordTime ? VISIBLE : GONE);
//                VideoCapture.OutputFileOptions fileOptions;//                ImageCapture.OutputFileOptions fileOptions;File cameraFile;if (isSaveExternal()) {cameraFile = FileUtils.createTempFile(getContext(), true);} else {cameraFile = FileUtils.createCameraFile(getContext(), CameraUtils.TYPE_VIDEO,outPutCameraFileName, videoFormat, outPutCameraDir);}//                fileOptions = new VideoCapture.OutputFileOptions.Builder(cameraFile).build();
//                mVideoCapture.startRecording(fileOptions, mainExecutor,
//                        new VideoCapture.OnVideoSavedCallback() {
//                            @Override
//                            public void onVideoSaved(@NonNull @NotNull VideoCapture.OutputFileResults outputFileResults) {
//                                long minSecond = recordVideoMinSecond <= 0 ? CustomCameraConfig.DEFAULT_MIN_RECORD_VIDEO : recordVideoMinSecond;
//                                if (recordTime < minSecond || outputFileResults.getSavedUri() == null) {
//                                    return;
//                                }
//                                Uri savedUri = outputFileResults.getSavedUri();
//                                SimpleCameraX.putOutputUri(activity.getIntent(), savedUri);
//                                String outPutPath = FileUtils.isContent(savedUri.toString()) ? savedUri.toString() : savedUri.getPath();
//                                mTextureView.setVisibility(View.VISIBLE);
//                                tvCurrentTime.setVisibility(GONE);
//                                if (mTextureView.isAvailable()) {
//                                    startVideoPlay(outPutPath);
//                                } else {
//                                    mTextureView.setSurfaceTextureListener(surfaceTextureListener);
//                                }
//                            }
//
//                            @Override
//                            public void onError(int videoCaptureError, @NonNull @NotNull String message,
//                                                @Nullable @org.jetbrains.annotations.Nullable Throwable cause) {
//                                if (mCameraListener != null) {
//                                    if (videoCaptureError == ERROR_RECORDING_TOO_SHORT || videoCaptureError == ERROR_MUXER) {
//                                        recordShort(0);
//                                    } else {
//                                        mCameraListener.onError(videoCaptureError, message, cause);
//                                    }
//                                }
//                            }
//                        });//1.4.2FileOutputOptions videoOptions = new FileOutputOptions.Builder(cameraFile).build();//准备Recorder的录制会话mRecording = mVideoCapture.getOutput().prepareRecording(getContext(), videoOptions).withAudioEnabled().start(mainExecutor, videoRecordEvent -> {if (videoRecordEvent instanceof VideoRecordEvent.Start) {} else if (videoRecordEvent instanceof VideoRecordEvent.Finalize) {VideoRecordEvent.Finalize finalizeEvent = (VideoRecordEvent.Finalize) videoRecordEvent;if (finalizeEvent.hasError()) {Log.d("Video", "录制失败: " + finalizeEvent.getError());recordShort(0);} else {long minSecond = recordVideoMinSecond <= 0 ? CustomCameraConfig.DEFAULT_MIN_RECORD_VIDEO : recordVideoMinSecond;if (recordTime < minSecond || finalizeEvent.getOutputResults().getOutputUri() == null) {return;}Uri savedUri = finalizeEvent.getOutputResults().getOutputUri();SimpleCameraX.putOutputUri(activity.getIntent(), savedUri);String outPutPath = FileUtils.isContent(savedUri.toString()) ? savedUri.toString() : savedUri.getPath();mTextureView.setVisibility(View.VISIBLE);tvCurrentTime.setVisibility(GONE);if (mTextureView.isAvailable()) {startVideoPlay(outPutPath);} else {mTextureView.setSurfaceTextureListener(surfaceTextureListener);}Log.d("Job", "录制完成");}}});}@Overridepublic void changeTime(long duration) {if (isDisplayRecordTime && tvCurrentTime.getVisibility() == VISIBLE) {String format = String.format(Locale.getDefault(), "%02d:%02d",TimeUnit.MILLISECONDS.toMinutes(duration),TimeUnit.MILLISECONDS.toSeconds(duration)- TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(duration)));if (!TextUtils.equals(format, tvCurrentTime.getText())) {tvCurrentTime.setText(format);}if (TextUtils.equals("00:00", tvCurrentTime.getText())) {tvCurrentTime.setVisibility(GONE);}}}@Overridepublic void recordShort(final long time) {recordTime = time;mSwitchCamera.setVisibility(VISIBLE);mFlashLamp.setVisibility(VISIBLE);tvCurrentTime.setVisibility(GONE);mCaptureLayout.resetCaptureLayout();mCaptureLayout.setTextWithAnimation(getContext().getString(R.string.picture_recording_time_is_short));try {
//                    mVideoCapture.stopRecording();mRecording.stop();} catch (Exception e) {e.printStackTrace();}}@Overridepublic void recordEnd(long time) {recordTime = time;try {
//                    mVideoCapture.stopRecording();mRecording.stop();} catch (Exception e) {e.printStackTrace();}}@Overridepublic void recordZoom(float zoom) {}@Overridepublic void recordError() {if (mCameraListener != null) {mCameraListener.onError(0, "An unknown error", null);}}});mCaptureLayout.setTypeListener(new TypeListener() {@Overridepublic void cancel() {onCancelMedia();}@Overridepublic void confirm() {String outputPath = SimpleCameraX.getOutputPath(activity.getIntent());if (isSaveExternal()) {outputPath = isMergeExternalStorageState(activity, outputPath);} else {// 对前置镜头导致的镜像进行一个纠正if (isImageCaptureEnabled() && isReversedHorizontal()) {File cameraFile = FileUtils.createCameraFile(getContext(), CameraUtils.TYPE_IMAGE,outPutCameraFileName, imageFormat, outPutCameraDir);if (FileUtils.copyPath(activity, outputPath, cameraFile.getAbsolutePath())) {outputPath = cameraFile.getAbsolutePath();SimpleCameraX.putOutputUri(activity.getIntent(), Uri.fromFile(cameraFile));}}}if (isImageCaptureEnabled()) {mImagePreview.setVisibility(INVISIBLE);mImagePreviewBg.setAlpha(0F);if (mCameraListener != null) {mCameraListener.onPictureSuccess(outputPath);}} else {stopVideoPlay();if (mCameraListener != null) {mCameraListener.onRecordSuccess(outputPath);}}}});mCaptureLayout.setLeftClickListener(new ClickListener() {@Overridepublic void onClick() {if (mOnClickListener != null) {mOnClickListener.onClick();}}});}private String isMergeExternalStorageState(Activity activity, String outputPath) {try {// 对前置镜头导致的镜像进行一个纠正if (isImageCaptureEnabled() && isReversedHorizontal()) {File tempFile = FileUtils.createTempFile(activity, false);if (FileUtils.copyPath(activity, outputPath, tempFile.getAbsolutePath())) {outputPath = tempFile.getAbsolutePath();}}// 当用户未设置存储路径时,相片默认是存在外部公共目录下Uri externalSavedUri;if (isImageCaptureEnabled()) {ContentValues contentValues = CameraUtils.buildImageContentValues(outPutCameraFileName, imageFormatForQ);externalSavedUri = getContext().getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);} else {ContentValues contentValues = CameraUtils.buildVideoContentValues(outPutCameraFileName, videoFormatForQ);externalSavedUri = getContext().getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentValues);}if (externalSavedUri == null) {return outputPath;}OutputStream outputStream = getContext().getContentResolver().openOutputStream(externalSavedUri);boolean isWriteFileSuccess = FileUtils.writeFileFromIS(new FileInputStream(outputPath), outputStream);if (isWriteFileSuccess) {FileUtils.deleteFile(getContext(), outputPath);SimpleCameraX.putOutputUri(activity.getIntent(), externalSavedUri);return externalSavedUri.toString();}} catch (FileNotFoundException e) {e.printStackTrace();}return outputPath;}private boolean isSaveExternal() {return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && TextUtils.isEmpty(outPutCameraDir);}private boolean isReversedHorizontal() {return lensFacing == CameraSelector.LENS_FACING_FRONT;}/*** 用户针对相机的一些参数配制** @param intent*/public void setCameraConfig(Intent intent) {Bundle extras = intent.getExtras();if (extras == null) {return;}markTxt = intent.getStringExtra("mark");boolean isCameraAroundState = extras.getBoolean(SimpleCameraX.EXTRA_CAMERA_AROUND_STATE, false);buttonFeatures = extras.getInt(SimpleCameraX.EXTRA_CAMERA_MODE, CustomCameraConfig.BUTTON_STATE_BOTH);lensFacing = isCameraAroundState ? CameraSelector.LENS_FACING_FRONT : CameraSelector.LENS_FACING_BACK;outPutCameraDir = extras.getString(SimpleCameraX.EXTRA_OUTPUT_PATH_DIR);outPutCameraFileName = extras.getString(SimpleCameraX.EXTRA_CAMERA_FILE_NAME);videoFrameRate = extras.getInt(SimpleCameraX.EXTRA_VIDEO_FRAME_RATE);videoBitRate = extras.getInt(SimpleCameraX.EXTRA_VIDEO_BIT_RATE);isManualFocus = extras.getBoolean(SimpleCameraX.EXTRA_MANUAL_FOCUS);isZoomPreview = extras.getBoolean(SimpleCameraX.EXTRA_ZOOM_PREVIEW);isAutoRotation = extras.getBoolean(SimpleCameraX.EXTRA_AUTO_ROTATION);int recordVideoMaxSecond = extras.getInt(SimpleCameraX.EXTRA_RECORD_VIDEO_MAX_SECOND, CustomCameraConfig.DEFAULT_MAX_RECORD_VIDEO);recordVideoMinSecond = extras.getInt(SimpleCameraX.EXTRA_RECORD_VIDEO_MIN_SECOND, CustomCameraConfig.DEFAULT_MIN_RECORD_VIDEO);imageFormat = extras.getString(SimpleCameraX.EXTRA_CAMERA_IMAGE_FORMAT, CameraUtils.JPEG);imageFormatForQ = extras.getString(SimpleCameraX.EXTRA_CAMERA_IMAGE_FORMAT_FOR_Q, CameraUtils.MIME_TYPE_IMAGE);videoFormat = extras.getString(SimpleCameraX.EXTRA_CAMERA_VIDEO_FORMAT, CameraUtils.MP4);videoFormatForQ = extras.getString(SimpleCameraX.EXTRA_CAMERA_VIDEO_FORMAT_FOR_Q, CameraUtils.MIME_TYPE_VIDEO);int captureLoadingColor = extras.getInt(SimpleCameraX.EXTRA_CAPTURE_LOADING_COLOR, 0xFF7D7DFF);isDisplayRecordTime = extras.getBoolean(SimpleCameraX.EXTRA_DISPLAY_RECORD_CHANGE_TIME, false);mCaptureLayout.setButtonFeatures(buttonFeatures);if (recordVideoMaxSecond > 0) {setRecordVideoMaxTime(recordVideoMaxSecond);}if (recordVideoMinSecond > 0) {setRecordVideoMinTime(recordVideoMinSecond);}String format = String.format(Locale.getDefault(), "%02d:%02d",TimeUnit.MILLISECONDS.toMinutes(recordVideoMaxSecond),TimeUnit.MILLISECONDS.toSeconds(recordVideoMaxSecond)- TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(recordVideoMaxSecond)));tvCurrentTime.setText(format);if (isAutoRotation && buttonFeatures != CustomCameraConfig.BUTTON_STATE_ONLY_RECORDER) {orientationEventListener = new CameraXOrientationEventListener(getContext(), this);startCheckOrientation();}setCaptureLoadingColor(captureLoadingColor);setProgressColor(captureLoadingColor);boolean isCheckSelfPermission = PermissionChecker.checkSelfPermission(getContext(), new String[]{Manifest.permission.CAMERA});if (isCheckSelfPermission) {buildUseCameraCases();} else {if (CustomCameraConfig.explainListener != null) {if (!SimpleXSpUtils.getBoolean(getContext(), Manifest.permission.CAMERA, false)) {CustomCameraConfig.explainListener.onPermissionDescription(getContext(), this, Manifest.permission.CAMERA);}}PermissionChecker.getInstance().requestPermissions(activity, new String[]{Manifest.permission.CAMERA},new PermissionResultCallback() {@Overridepublic void onGranted() {buildUseCameraCases();if (CustomCameraConfig.explainListener != null) {CustomCameraConfig.explainListener.onDismiss(CustomCameraView.this);}}@Overridepublic void onDenied() {if (CustomCameraConfig.deniedListener != null) {SimpleXSpUtils.putBoolean(getContext(), Manifest.permission.CAMERA, true);CustomCameraConfig.deniedListener.onDenied(getContext(), Manifest.permission.CAMERA, PermissionChecker.PERMISSION_SETTING_CODE);if (CustomCameraConfig.explainListener != null) {CustomCameraConfig.explainListener.onDismiss(CustomCameraView.this);}} else {SimpleXPermissionUtil.goIntentSetting(activity, PermissionChecker.PERMISSION_SETTING_CODE);}}});}}/*** 检测手机方向*/private void startCheckOrientation() {if (orientationEventListener != null) {orientationEventListener.star();}}/*** 停止检测手机方向*/public void stopCheckOrientation() {if (orientationEventListener != null) {orientationEventListener.stop();}}private int getTargetRotation() {return mImageCapture.getTargetRotation();}@Overridepublic void onOrientationChanged(int orientation) {if (mImageCapture != null) {mImageCapture.setTargetRotation(orientation);}if (mImageAnalyzer != null) {mImageAnalyzer.setTargetRotation(orientation);}}/*** We need a display listener for orientation changes that do not trigger a configuration* change, for example if we choose to override config change in manifest or for 180-degree* orientation changes.*/private class DisplayListener implements DisplayManager.DisplayListener {@Overridepublic void onDisplayAdded(int displayId) {}@Overridepublic void onDisplayRemoved(int displayId) {}@Overridepublic void onDisplayChanged(int displayId) {if (displayId == CustomCameraView.this.displayId) {if (mImageCapture != null) {mImageCapture.setTargetRotation(mCameraPreviewView.getDisplay().getRotation());}if (mImageAnalyzer != null) {mImageAnalyzer.setTargetRotation(mCameraPreviewView.getDisplay().getRotation());}}}}/*** 开始打开相机预览*/public void buildUseCameraCases() {ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(getContext());cameraProviderFuture.addListener(new Runnable() {@Overridepublic void run() {try {mCameraProvider = cameraProviderFuture.get();bindCameraUseCases();} catch (Exception e) {e.printStackTrace();}}}, mainExecutor);}/*** 初始相机预览模式*/private void bindCameraUseCases() {if (null != mCameraProvider && isBackCameraLevel3Device(mCameraProvider)) {if (CustomCameraConfig.BUTTON_STATE_ONLY_RECORDER == buttonFeatures) {bindCameraVideoUseCases();} else {bindCameraImageUseCases();}} else {switch (buttonFeatures) {case CustomCameraConfig.BUTTON_STATE_ONLY_CAPTURE:bindCameraImageUseCases();break;case CustomCameraConfig.BUTTON_STATE_ONLY_RECORDER:bindCameraVideoUseCases();break;default:bindCameraWithUserCases();break;}}}@SuppressLint("UnsafeOptInUsageError")private boolean isBackCameraLevel3Device(ProcessCameraProvider cameraProvider) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {List<CameraInfo> cameraInfos = CameraSelector.DEFAULT_BACK_CAMERA.filter(cameraProvider.getAvailableCameraInfos());if (!cameraInfos.isEmpty()) {return Objects.equals(Camera2CameraInfo.from(cameraInfos.get(0)).getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL), CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY);}}return false;}/*** bindCameraWithUserCases*/private void bindCameraWithUserCases() {try {CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(lensFacing).build();// PreviewPreview preview = new Preview.Builder().setTargetRotation(mCameraPreviewView.getDisplay().getRotation()).build();// ImageCapturebuildImageCapture();// VideoCapturebuildVideoCapture();UseCaseGroup.Builder useCase = new UseCaseGroup.Builder();useCase.addUseCase(preview);useCase.addUseCase(mImageCapture);useCase.addUseCase(mVideoCapture);UseCaseGroup useCaseGroup = useCase.build();// Must unbind the use-cases before rebinding themmCameraProvider.unbindAll();// Attach the viewfinder's surface provider to preview use casepreview.setSurfaceProvider(mCameraPreviewView.getSurfaceProvider());// A variable number of use-cases can be passed here -// camera provides access to CameraControl & CameraInfoCamera camera = mCameraProvider.bindToLifecycle((LifecycleOwner) getContext(), cameraSelector, useCaseGroup);// setFlashModesetFlashMode();mCameraInfo = camera.getCameraInfo();mCameraControl = camera.getCameraControl();initCameraPreviewListener();} catch (Exception e) {e.printStackTrace();}}/*** bindCameraImageUseCases*/private void bindCameraImageUseCases() {try {int screenAspectRatio = aspectRatio(DensityUtil.getScreenWidth(getContext()), DensityUtil.getScreenHeight(getContext()));int rotation = mCameraPreviewView.getDisplay().getRotation();CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(lensFacing).build();// PreviewPreview preview = new Preview.Builder().setTargetAspectRatio(screenAspectRatio).setTargetRotation(rotation).build();// ImageCapturebuildImageCapture();// ImageAnalysismImageAnalyzer = new ImageAnalysis.Builder().setTargetAspectRatio(screenAspectRatio).setTargetRotation(rotation).build();// Must unbind the use-cases before rebinding themmCameraProvider.unbindAll();// Attach the viewfinder's surface provider to preview use casepreview.setSurfaceProvider(mCameraPreviewView.getSurfaceProvider());// A variable number of use-cases can be passed here -// camera provides access to CameraControl & CameraInfoCamera camera = mCameraProvider.bindToLifecycle((LifecycleOwner) getContext(), cameraSelector, preview, mImageCapture, mImageAnalyzer);// setFlashModesetFlashMode();mCameraInfo = camera.getCameraInfo();mCameraControl = camera.getCameraControl();initCameraPreviewListener();} catch (Exception e) {e.printStackTrace();}}/*** bindCameraVideoUseCases*/private void bindCameraVideoUseCases() {try {Log.d("Job", "my-bindCameraVideoUseCases>");CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(lensFacing).build();// PreviewPreview preview = new Preview.Builder().setTargetRotation(mCameraPreviewView.getDisplay().getRotation()).build();buildVideoCapture();Paint textPaint = new Paint();textPaint.setColor(Color.parseColor("#33000000"));textPaint.setTextSize(30f);textPaint.setAntiAlias(true);textPaint.setTextAlign(Paint.Align.LEFT);Handler handler = new Handler(Looper.getMainLooper());OverlayEffect effect = new OverlayEffect(CameraEffect.VIDEO_CAPTURE, 0,handler, new Consumer<Throwable>() {@Overridepublic void accept(Throwable throwable) {}});float lineH = textPaint.getFontSpacing();SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd");String markDate = sf.format(new Date());effect.clearOnDrawListener();effect.setOnDrawListener(new Function<Frame, Boolean>() {@Overridepublic Boolean apply(Frame frame) {Canvas canvas = frame.getOverlayCanvas();canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);canvas.save();canvas.rotate(-100f, canvas.getWidth() / 2f, canvas.getHeight() / 2f);double diagonal = Math.hypot(canvas.getWidth(), canvas.getHeight());//对角线float step = 200f;float y = (float) -diagonal;while (y < diagonal) {float x = (float) -diagonal;while (x < diagonal) {if (markDate != null) {canvas.drawText(markDate, x, y, textPaint);canvas.drawText(" " + markTxt, x, y + lineH, textPaint);} else {canvas.drawText(markTxt, x, y, textPaint);}x += step;}y += step;}canvas.restore();return true;}});UseCaseGroup.Builder useCase = new UseCaseGroup.Builder();useCase.addUseCase(preview);useCase.addUseCase(mVideoCapture);useCase.addEffect(effect);// Must unbind the use-cases before rebinding themmCameraProvider.unbindAll();// Attach the viewfinder's surface provider to preview use casepreview.setSurfaceProvider(mCameraPreviewView.getSurfaceProvider());// A variable number of use-cases can be passed here -// camera provides access to CameraControl & CameraInfo
//            Camera camera = mCameraProvider.bindToLifecycle((LifecycleOwner) getContext(), cameraSelector, preview, mVideoCapture);Camera camera = mCameraProvider.bindToLifecycle((LifecycleOwner) getContext(), cameraSelector, useCase.build());mCameraInfo = camera.getCameraInfo();mCameraControl = camera.getCameraControl();initCameraPreviewListener();} catch (Exception e) {e.printStackTrace();}}private void buildImageCapture() {int screenAspectRatio = aspectRatio(DensityUtil.getScreenWidth(getContext()), DensityUtil.getScreenHeight(getContext()));mImageCapture = new ImageCapture.Builder().setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY).setTargetAspectRatio(screenAspectRatio).setTargetRotation(mCameraPreviewView.getDisplay().getRotation()).build();}@SuppressLint("RestrictedApi")private void buildVideoCapture() {
//        VideoCapture.Builder videoBuilder = new VideoCapture.Builder();
//        videoBuilder.setTargetRotation(mCameraPreviewView.getDisplay().getRotation());
//        if (videoFrameRate > 0) {
//            videoBuilder.setVideoFrameRate(videoFrameRate);
//        }
//        if (videoBitRate > 0) {
//            videoBuilder.setBitRate(videoBitRate);
//        }
//        mVideoCapture = videoBuilder.build();//1.4.2后重大架构变化
//        MediaSpec mediaSpec = new MediaSpec.Builder()
//                .configureVideo(new Consumer<VideoSpec.Builder>() {
//                    @Override
//                    public void accept(VideoSpec.Builder builder) {
//                        if (videoFrameRate > 0) {
//                            builder.setFrameRate(videoFrameRate);
//                        }
//                        if (videoBitRate > 0) {
//                            builder.setBitrate(videoBitRate)
//                        }
//                    }
//                }).build(); //新版不能设置了Recorder recorder = new Recorder.Builder().setExecutor(mainExecutor)//SD-480P HD FHD-1080P UHD-4K
//                .setQualitySelector(QualitySelector.from(Quality.SD)).setQualitySelector(QualitySelector.from(Quality.LOWEST)).build();mVideoCapture = VideoCapture.withOutput(recorder);}private void initCameraPreviewListener() {LiveData<ZoomState> zoomState = mCameraInfo.getZoomState();CameraXPreviewViewTouchListener cameraXPreviewViewTouchListener = new CameraXPreviewViewTouchListener(getContext());cameraXPreviewViewTouchListener.setCustomTouchListener(new CameraXPreviewViewTouchListener.CustomTouchListener() {@Overridepublic void zoom(float delta) {if (isZoomPreview) {if (zoomState.getValue() != null) {float currentZoomRatio = zoomState.getValue().getZoomRatio();mCameraControl.setZoomRatio(currentZoomRatio * delta);}}}@Overridepublic void click(float x, float y) {if (isManualFocus) {MeteringPointFactory factory = mCameraPreviewView.getMeteringPointFactory();MeteringPoint point = factory.createPoint(x, y);FocusMeteringAction action = new FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF).setAutoCancelDuration(3, TimeUnit.SECONDS).build();if (mCameraInfo.isFocusMeteringSupported(action)) {mCameraControl.cancelFocusAndMetering();focusImageView.setDisappear(false);focusImageView.startFocus(new Point((int) x, (int) y));ListenableFuture<FocusMeteringResult> future = mCameraControl.startFocusAndMetering(action);future.addListener(new Runnable() {@Overridepublic void run() {try {FocusMeteringResult result = future.get();focusImageView.setDisappear(true);if (result.isFocusSuccessful()) {focusImageView.onFocusSuccess();} else {focusImageView.onFocusFailed();}} catch (Exception ignored) {}}}, mainExecutor);}}}@Overridepublic void doubleClick(float x, float y) {if (isZoomPreview) {if (zoomState.getValue() != null) {float currentZoomRatio = zoomState.getValue().getZoomRatio();float minZoomRatio = zoomState.getValue().getMinZoomRatio();if (currentZoomRatio > minZoomRatio) {mCameraControl.setLinearZoom(0f);} else {mCameraControl.setLinearZoom(0.5f);}}}}});mCameraPreviewView.setOnTouchListener(cameraXPreviewViewTouchListener);}/*** [androidx.camera.core.ImageAnalysis.Builder] requires enum value of* [androidx.camera.core.AspectRatio]. Currently it has values of 4:3 & 16:9.* <p>* Detecting the most suitable ratio for dimensions provided in @params by counting absolute* of preview ratio to one of the provided values.** @param width  - preview width* @param height - preview height* @return suitable aspect ratio*/private int aspectRatio(int width, int height) {double aspect = Math.max(width, height);double previewRatio = aspect / Math.min(width, height);if (Math.abs(previewRatio - RATIO_4_3_VALUE) <= Math.abs(previewRatio - RATIO_16_9_VALUE)) {return AspectRatio.RATIO_4_3;}return AspectRatio.RATIO_16_9;}/*** 拍照回调*/private static class MyImageResultCallback implements ImageCapture.OnImageSavedCallback {private final WeakReference<ImageView> mImagePreviewReference;private final WeakReference<View> mImagePreviewBgReference;private final WeakReference<CaptureLayout> mCaptureLayoutReference;private final WeakReference<ImageCallbackListener> mImageCallbackListenerReference;private final WeakReference<CameraListener> mCameraListenerReference;private final WeakReference<CustomCameraView> mCameraViewLayoutReference;public MyImageResultCallback(CustomCameraView cameraView, ImageView imagePreview, View imagePreviewBg, CaptureLayout captureLayout,ImageCallbackListener imageCallbackListener,CameraListener cameraListener) {this.mCameraViewLayoutReference = new WeakReference<>(cameraView);this.mImagePreviewReference = new WeakReference<>(imagePreview);this.mImagePreviewBgReference = new WeakReference<>(imagePreviewBg);this.mCaptureLayoutReference = new WeakReference<>(captureLayout);this.mImageCallbackListenerReference = new WeakReference<>(imageCallbackListener);this.mCameraListenerReference = new WeakReference<>(cameraListener);}@Overridepublic void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {Uri savedUri = outputFileResults.getSavedUri();if (savedUri != null) {CustomCameraView customCameraView = mCameraViewLayoutReference.get();if (customCameraView != null) {customCameraView.stopCheckOrientation();}ImageView mImagePreview = mImagePreviewReference.get();if (mImagePreview != null) {Context context = mImagePreview.getContext();SimpleCameraX.putOutputUri(((Activity) context).getIntent(), savedUri);mImagePreview.setVisibility(View.VISIBLE);if (customCameraView != null && customCameraView.isAutoRotation) {int targetRotation = customCameraView.getTargetRotation();// 这种角度拍出来的图片宽比高大,所以使用ScaleType.FIT_CENTER缩放模式if (targetRotation == Surface.ROTATION_90 || targetRotation == Surface.ROTATION_270) {mImagePreview.setAdjustViewBounds(true);} else {mImagePreview.setAdjustViewBounds(false);mImagePreview.setScaleType(ImageView.ScaleType.FIT_CENTER);}View mImagePreviewBackground = mImagePreviewBgReference.get();if (mImagePreviewBackground != null) {mImagePreviewBackground.animate().alpha(1F).setDuration(220).start();}}ImageCallbackListener imageCallbackListener = mImageCallbackListenerReference.get();if (imageCallbackListener != null) {String outPutCameraPath = FileUtils.isContent(savedUri.toString()) ? savedUri.toString() : savedUri.getPath();imageCallbackListener.onLoadImage(outPutCameraPath, mImagePreview);}}CaptureLayout captureLayout = mCaptureLayoutReference.get();if (captureLayout != null) {captureLayout.setButtonCaptureEnabled(true);captureLayout.startTypeBtnAnimator();}}}@Overridepublic void onError(@NonNull ImageCaptureException exception) {if (mCaptureLayoutReference.get() != null) {mCaptureLayoutReference.get().setButtonCaptureEnabled(true);}if (mCameraListenerReference.get() != null) {mCameraListenerReference.get().onError(exception.getImageCaptureError(),exception.getMessage(), exception.getCause());}}}private final TextureView.SurfaceTextureListener surfaceTextureListener = new TextureView.SurfaceTextureListener() {@Overridepublic void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {String outputPath = SimpleCameraX.getOutputPath(activity.getIntent());startVideoPlay(outputPath);}@Overridepublic void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}@Overridepublic boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {return false;}@Overridepublic void onSurfaceTextureUpdated(SurfaceTexture surface) {}};public void setCameraListener(CameraListener cameraListener) {this.mCameraListener = cameraListener;}/*** 设置录制视频最大时长 秒*/public void setRecordVideoMaxTime(int maxDurationTime) {mCaptureLayout.setDuration(maxDurationTime);}/*** 设置录制视频最小时长 秒*/public void setRecordVideoMinTime(int minDurationTime) {mCaptureLayout.setMinDuration(minDurationTime);}/*** 设置拍照时loading色值** @param color*/public void setCaptureLoadingColor(int color) {mCaptureLayout.setCaptureLoadingColor(color);}/*** 设置录像时loading色值** @param color*/public void setProgressColor(int color) {mCaptureLayout.setProgressColor(color);}/*** 切换前后摄像头*/public void toggleCamera() {lensFacing = CameraSelector.LENS_FACING_FRONT == lensFacing ? CameraSelector.LENS_FACING_BACK : CameraSelector.LENS_FACING_FRONT;bindCameraUseCases();}/*** 闪光灯模式*/private void setFlashMode() {if (mImageCapture == null) {return;}switch (typeFlash) {case TYPE_FLASH_AUTO:mFlashLamp.setImageResource(R.drawable.picture_ic_flash_auto);mImageCapture.setFlashMode(ImageCapture.FLASH_MODE_AUTO);break;case TYPE_FLASH_ON:mFlashLamp.setImageResource(R.drawable.picture_ic_flash_on);mImageCapture.setFlashMode(ImageCapture.FLASH_MODE_ON);break;case TYPE_FLASH_OFF:mFlashLamp.setImageResource(R.drawable.picture_ic_flash_off);mImageCapture.setFlashMode(ImageCapture.FLASH_MODE_OFF);break;}}/*** 关闭相机界面按钮** @param clickListener*/public void setOnCancelClickListener(ClickListener clickListener) {this.mOnClickListener = clickListener;}public void setImageCallbackListener(ImageCallbackListener mImageCallbackListener) {this.mImageCallbackListener = mImageCallbackListener;}/*** 重置状态*/private void resetState() {if (isImageCaptureEnabled()) {mImagePreview.setVisibility(INVISIBLE);mImagePreviewBg.setAlpha(0F);} else {try {
//                mVideoCapture.stopRecording();mRecording.stop();} catch (Exception e) {e.printStackTrace();}}mSwitchCamera.setVisibility(VISIBLE);mFlashLamp.setVisibility(VISIBLE);mCaptureLayout.resetCaptureLayout();}/*** 开始循环播放视频** @param url*/private void startVideoPlay(String url) {try {if (mMediaPlayer == null) {mMediaPlayer = new MediaPlayer();} else {mMediaPlayer.reset();}if (FileUtils.isContent(url)) {mMediaPlayer.setDataSource(getContext(), Uri.parse(url));} else {mMediaPlayer.setDataSource(url);}mMediaPlayer.setSurface(new Surface(mTextureView.getSurfaceTexture()));mMediaPlayer.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT);mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {@Overridepublic voidonVideoSizeChanged(MediaPlayer mp, int width, int height) {updateVideoViewSize(mMediaPlayer.getVideoWidth(), mMediaPlayer.getVideoHeight());}});mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {@Overridepublic void onPrepared(MediaPlayer mp) {mMediaPlayer.start();}});mMediaPlayer.setLooping(true);mMediaPlayer.prepareAsync();} catch (Exception e) {e.printStackTrace();}}/*** updateVideoViewSize** @param videoWidth* @param videoHeight*/private void updateVideoViewSize(float videoWidth, float videoHeight) {if (videoWidth > videoHeight) {int height = (int) ((videoHeight / videoWidth) * getWidth());RelativeLayout.LayoutParams videoViewParam = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, height);videoViewParam.addRule(CENTER_IN_PARENT, TRUE);mTextureView.setLayoutParams(videoViewParam);}}/*** 取消拍摄相关*/public void onCancelMedia() {String outputPath = SimpleCameraX.getOutputPath(activity.getIntent());FileUtils.deleteFile(getContext(), outputPath);stopVideoPlay();resetState();startCheckOrientation();}/*** 停止视频播放*/private void stopVideoPlay() {if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {mMediaPlayer.stop();mMediaPlayer.release();mMediaPlayer = null;}mTextureView.setVisibility(View.GONE);}/*** onConfigurationChanged** @param newConfig*/public void onConfigurationChanged(@NonNull Configuration newConfig) {buildUseCameraCases();}/*** onDestroy*/public void onDestroy() {displayManager.unregisterDisplayListener(displayListener);stopCheckOrientation();focusImageView.destroy();}
}

总结

  这个录制视频水印如此简单其实是借助官方camerax库升级版的API实现的,也就OverlayEffect的绘制,我这是实现铺满斜着的多行文本水印。之前camerax版本val camerax = “1.1.0-beta02” 就不支持,要升到v1.4.2,随着升级版本整个camerax用法上有比较大差别,引入了新的Recording类等,我是根据Android开发者官网示例修改的。

2025/6/20
手机饭煲 何

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/910436.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/910436.shtml
英文地址,请注明出处:http://en.pswp.cn/news/910436.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

观远ChatBI:加速零售消费企业数据驱动的敏捷决策

近年来&#xff0c;随着国产大模型&#xff08;如DeepSeek&#xff09;的快速发展&#xff0c;企业对智能化数据分析工具的需求日益增长。观远数据推出的ChatBI&#xff0c;基于大语言模型&#xff08;LLM&#xff09;打造&#xff0c;旨在通过自然语言交互降低数据分析门槛&am…

鸿蒙NEXT-鸿蒙三层架构搭建,嵌入HMRouter,实现便捷跳转,新手攻略。(1/3)

接下来&#xff0c;我将手把手带领大家去完善&#xff0c;搭建一个鸿蒙的三层架构&#xff0c;另实现HMRouter的嵌入。完成后&#xff0c;大家可任意跳转页面&#xff0c;在三层架构中&#xff0c;书写属于自己的篇章。 第0步&#xff0c;项目与AGC华为控制台关联起来 首先AG…

鸿蒙ArkTs仿网易云音乐项目:架构剖析与功能展示

鸿蒙ArkTs仿网易云音乐项目&#xff1a;架构剖析与功能展示 一、引言 在移动应用开发的浪潮中&#xff0c;音乐类应用始终占据着重要的一席之地。网易云音乐凭借其丰富的音乐资源、个性化的推荐算法和独特的社交互动功能&#xff0c;深受广大用户的喜爱。本文将详细介绍一个基…

【web 安全】从 HTTP 无状态到现代身份验证机制

文章目录 Web 安全与系统设计Web存在的问题&#xff1a;Web 是无状态的解决方案一、早期解决方案&#xff1a;Session Cookie 的诞生二、第二阶段&#xff1a;Token 的出现&#xff08;前后端分离 移动端的解决方案&#xff09;三、分析总结&#xff1a;1.早期版本&#xff1…

FlutterUnit TolyUI | 布局游乐场

FlutterUnit 基于 TolyUI 大大简化了界面构建的代码复杂程度&#xff0c;因此之前想要实现的一些小功能&#xff0c;就可以轻松支持。布局游乐场是通过交互的方式来 直观体验 组件的布局特性&#xff0c;从而更易学和掌握。目前 FlutterUnit 已在 知识集录模块新增了 布局宝库&…

【数据分析一:Data Collection】信息检索

本节内容含有各典型数据集的推荐&#xff0c;以及其网址&#xff0c;大家根据需要自取 一、检索 最简单、最灵活的数据获取方式就是依靠检索&#xff1a; Google&#xff1a;更适合搜索英文信息 Google Dataset Search&#xff08;Google 数据集搜索&#xff09; 网址&…

23.ssr和csr的对比?如何依赖node.js实现

1.为什么说ssr 的node中间层请求速度快。相当于内网&#xff1f; 那vue.js加载怎么没有ssr和csr的说法啊 第一问&#xff1a;为什么说 SSR 的 Node 中间层请求速度快&#xff1f;是不是相当于内网&#xff1f; ✅ 是的&#xff0c;本质上就是「内网请求」&#xff0c;所以更快…

力扣刷题(第六十四天)

灵感来源 - 保持更新&#xff0c;努力学习 - python脚本学习 第一个错误的版本 解题思路 初始化左右边界&#xff1a;左边界 left 1&#xff0c;右边界 right n。二分查找循环&#xff1a; 计算中间版本号 mid。若 mid 是错误版本&#xff0c;说明第一个错误版本在 [le…

【图像处理入门】11. 深度学习初探:从CNN到GAN的视觉智能之旅

摘要 深度学习为图像处理注入了革命性动力。本文将系统讲解卷积神经网络(CNN)的核心原理,通过PyTorch实现图像分类实战;深入解析迁移学习的高效应用策略,利用预训练模型提升自定义任务性能;最后揭开生成对抗网络(GAN)的神秘面纱,展示图像生成与增强的前沿技术。结合代…

C++法则4: 如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数。

C法则4&#xff1a; 如果一个构造函数的第一个参数是自身类类型的引用&#xff0c;且任何额外参数都有默认值&#xff0c;则此构造函数是拷贝构造函数。 拷贝构造函数的定义&#xff1a; 第一个参数是自身类类型的引用&#xff1a; 必须是引用&#xff08;通常为const引用&…

从头搭建环境安装k8s遇到的问题

基本信息 master节点IP&#xff1a; 172.31.0.3 node01节点IP&#xff1a;172.31.0.4 node02节点IP&#xff1a;172.31.0.5 子网掩码&#xff1a;255.255.0.0 网关&#xff1a;172.31.0.2 DNS:114.114.114.114 安装前要检查的信息 检查三台主机的mac地址是否重复&#xff1a…

Flask入门指南:从零构建Python微服务

1. Flask 是什么&#xff1f; Flask 是一个 微框架&#xff08;Microframework&#xff09;&#xff0c;特点包括&#xff1a; 轻量灵活&#xff1a;核心仅包含路由和模板引擎&#xff0c;其他功能通过扩展实现易于学习&#xff1a;代码直观&#xff0c;适合快速开发小型应用…

【LINUX网络】网络socet接口的基本使用以及实现简易UDP通信

根据本系列上两篇关于网络的初识介绍&#xff0c;现在我们开始实现一个UDP接口&#xff0c;以加强对该接口的理解。 1 . 服务器端 在本篇中&#xff0c;主要按照下面内容来实现&#xff1a; 创建并封装服务端&#xff1a;了解创建服务端的基本步骤 创建并封装客户端&#xff0…

MySQL的索引事务

索引 是什么 类似于目录&#xff0c;提高查询的速度&#xff0c;但是本身会占用空间&#xff0c;增删数据的时候也需要维护索引。所以查询操作频繁的时候可以创建索引。如果非条件查询列&#xff0c;或经常做插入、修改操作&#xff0c;或磁盘空间不足时&#xff0c;不考虑创…

安卓9.0系统修改定制化____第三方美化 bug修复 移植相关 辅助工具 常识篇 八

在修改rom中。有时候不可避免的需要对系统进行美化以及一些第三方系统的bug修复。在操作前需要了解系统的一些基本常识。例如同平台移植 跨平台移植以及内核移植 apk反编译等等相关的知识。今天解析的这款工具虽然不是直接面向安卓9.0.但对于了解以上的一些必备常识还是不错的 …

云服务器与物理服务器对比:选择最适合的业务服务器解决方案

更多云服务器知识&#xff0c;尽在hostol.com 在现代 IT 基础设施中&#xff0c;云服务器与物理服务器是两种常见的服务器解决方案。随着云计算技术的迅猛发展&#xff0c;越来越多的企业开始转向云服务器&#xff0c;但也有一些企业仍然坚持使用物理服务器&#xff0c;尤其是…

【redis使用场景——缓存——双写一致性】

redis使用场景——缓存——双写一致性 双写一致性问题的本质与场景典型不一致场景分析​​并发写操作导致的不一致​​​​读写交叉导致的不一致​​​​主从同步延迟导致的不一致​​ 解决延迟双删策略&#xff08;推荐&#xff09;优点​​&#xff1a;​​缺点​​&#xff…

【ArcGIS】在线影像底图调用

【ArcGIS】在线影像底图调用 一、 历史影像的调用二、ArcGIS online底图调用三、结语 一、 历史影像的调用 ESRI官方推出了World Imagery Wayback是一个提供全球范围内历史影像的在线服务。 官网地址&#xff1a;https://livingatlas.arcgis.com/wayback/ 操作步骤&#xff1…

密度估计:从零星足迹重建整体画像

想象你是一位侦探&#xff0c;案发现场只留下几个零散的脚印。**如何通过这些碎片&#xff0c;推断嫌疑人的身高体重&#xff1f;甚至预测他下一步的藏身之处&#xff1f;** 这种从局部反推整体的能力&#xff0c;正是**密度估计&#xff08;Density Estimation&#xff09;** …

B004基于STM32F401单片机简易交通灯实训数码管显示设计仿真资料

视频演示地址:https://www.bilibili.com/video/BV1GvNDzFEd9/ 运行环境 仿真软件:proteus8.17(切记别的版本不能运行) 编程软件:MDK525 STM32 cubmx版本:6.11.1(切记别的版本不能运行) 原理图画图软件:AD10 功能说明&#xff1a; 以STM32F401CB单片机为核心简易交通灯功能如下。…