Android錄制視頻自帶鋪滿多行水印

文章目錄

    • 引言
    • 環境要求
    • 代碼實現
    • 總結

引言

? 之前做過幾種水印需求,這篇文章是關于使用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單片機為核心簡易交通燈功能如下。…