CameraX打开摄像头预览,显示在界面上。结合悬浮窗的功能。实现一个可拖动悬浮窗,实时预览摄像头的例子。

这个例子放进了单独的模块里。使用时注意gradle里的细微差别。

操作摄像头,打开预览。这部分代码与Android CameraX 打开摄像头预览相同。

悬浮窗相关代码与可拖动悬浮窗相同。在此基础上增加了对拖动范围的限制。

引入依赖

模块gradle的一些配置,使用的Android SDK版本为31,启用databinding

plugins {
id 'com.android.library'
id 'kotlin-android'
id 'kotlin-android-extensions'
id 'kotlin-kapt'
} android {
compileSdk 31 defaultConfig {
minSdk 21
targetSdk 31
versionCode 1
versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
dataBinding {
enabled = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
} dependencies {
implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'com.google.android.material:material:1.4.0'
implementation project(path: ':baselib')
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' implementation "androidx.camera:camera-core:1.1.0-alpha11"
implementation "androidx.camera:camera-camera2:1.1.0-alpha11"
implementation "androidx.camera:camera-lifecycle:1.1.0-alpha11"
implementation "androidx.camera:camera-view:1.0.0-alpha31"
implementation "androidx.camera:camera-extensions:1.0.0-alpha31"
}

引入CameraX依赖(CameraX 核心库是用camera2实现的),目前主要用1.1.0-alpha11版本

权限

需要动态申请android.permission.CAMERA权限

<uses-permission android:name="android.permission.CAMERA" />

本文略过动态申请权限的地方

layout

CameraX提供了androidx.camera.view.PreviewView

把它放在一个FrameLayout里,如下的me_act_simple_preivew_x_scale.xml

<?xml version="1.0" encoding="utf-8"?>
<layout> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"> <androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout> <LinearLayout
android:id="@+id/func_field"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:gravity="center"
android:orientation="vertical"
android:padding="4dp"> <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"> <Button
android:id="@+id/start"
style="@style/NormalBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="打开摄像头" /> <Button
android:id="@+id/end"
style="@style/NormalBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:text="停止摄像头" /> </LinearLayout> <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:orientation="horizontal"> <Button
android:id="@+id/enable_ana"
style="@style/NormalBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="setAnalyzer" /> <Button
android:id="@+id/clr_ana"
style="@style/NormalBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:text="clearAnalyzer" /> <Button
android:id="@+id/take_one_analyse"
style="@style/NormalBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:text="截取" /> </LinearLayout>
</LinearLayout> <View
android:id="@+id/touch_move"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" /> <ImageView
android:id="@+id/zoom_iv"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_margin="12dp"
android:src="@drawable/me_ic_to_small" /> <TextView
android:id="@+id/tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="rustfisher.com" /> </RelativeLayout>
</layout>

func_field装着一些按钮。缩小和还原界面用zoom_iv

style

准备一个style

<style name="MeTranslucentAct" parent="AppTheme">
<item name="android:windowBackground">#80000000</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>
</style>

manifest里注册

<activity
android:name=".camera.MeSimplePreviewXFloatingAct"
android:exported="true"
android:theme="@style/MeTranslucentAct" />

activity

开启摄像头

新建MeSimplePreviewXFloatingAct,继承androidx.appcompat.app.AppCompatActivity

// onCreate中获取mCameraProvider
mCameraProviderFuture = ProcessCameraProvider.getInstance(this);
mCameraProviderFuture.addListener(() -> {
try {
mCameraProvider = mCameraProviderFuture.get();
Log.d(TAG, "获取到了 cameraProvider");
} catch (ExecutionException | InterruptedException e) {
// 这里不用处理
}
}, ContextCompat.getMainExecutor(this));

为了获得ProcessCameraProvider,用ProcessCameraProvider.getInstance方法拿到一个cameraProviderFuture

cameraProviderFuture完成后取出ProcessCameraProvidercameraProvider)。

开启摄像头的方法bindPreview

private void bindPreview(ProcessCameraProvider cameraProvider) {
if (cameraProvider == null) {
Toast.makeText(getApplicationContext(), "没获取到相机", Toast.LENGTH_SHORT).show();
return;
}
Toast.makeText(getApplicationContext(), "相机启动", Toast.LENGTH_SHORT).show();
Preview preview = new Preview.Builder().build(); CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build(); preview.setSurfaceProvider(mBinding.previewView.getSurfaceProvider()); cameraProvider.bindToLifecycle(this, cameraSelector, preview, mImageAnalysis);
mRunning = true;
}

要开启预览,通过Preview.Builder构建一个Preview。用CameraSelector来选择后置摄像头。

PreviewSurfaceProvider由layout中的androidx.camera.view.PreviewView提供。

cameraProvider.bindToLifecycle绑定上后,启动摄像头预览

悬浮窗

setContentView之前设置一下window的flag

WindowManager.LayoutParams layoutParams = getWindow().getAttributes();
layoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;

缩小和放大窗口

缩小放大窗口需要用android.view.WindowManager.LayoutParams

private void toSmallWindow() {
mBinding.funcField.setVisibility(View.GONE);
mIsSmallWindow = true;
mBinding.zoomIv.setImageResource(R.drawable.me_to_big); android.view.WindowManager.LayoutParams p = getWindow().getAttributes();
p.height = 480; // 悬浮窗大小可以自己定
p.width = 360;
p.dimAmount = 0.0f;
getWindow().setAttributes(p);
} private void toBigWindow() {
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.x = 0;
lp.y = 0;
getWindow().setAttributes(lp); getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
mBinding.funcField.setVisibility(View.VISIBLE);
mIsSmallWindow = false;
mBinding.zoomIv.setImageResource(R.drawable.me_ic_to_small);
}

按钮的图片资源请自备

限制拖动范围

先拿到一个参考范围

mBinding.container.post(() -> {
mBigWid = mBinding.container.getWidth();
mBigHeight = mBinding.container.getHeight();
Log.d(TAG, "container size: " + mBigWid + ", " + mBigHeight);
});

相关阅读:获取view的宽高

activity完整代码

// package com.rustfisher.mediasamples.camera;
import android.os.Bundle;
import android.util.Log;
import android.util.Size;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Toast; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.Preview;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.core.content.ContextCompat;
import androidx.databinding.DataBindingUtil; import com.google.common.util.concurrent.ListenableFuture;
import com.rustfisher.mediasamples.R;
import com.rustfisher.mediasamples.databinding.MeActSimplePreivewXScaleBinding; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; /**
* 预览照相机 悬浮窗
*
* @author an.rustfisher.com
* @date 2021-12-31 15:53
*/
public class MeSimplePreviewXFloatingAct extends AppCompatActivity {
private static final String TAG = "rfDevX";
private MeActSimplePreivewXScaleBinding mBinding;
private ListenableFuture<ProcessCameraProvider> mCameraProviderFuture;
private ProcessCameraProvider mCameraProvider;
private boolean mRunning = false; private boolean mIsSmallWindow = false;
private boolean mLimitArea = true; private boolean mTakeOneYuv = false; // 获取一帧 实际工程中不要这么做 private final ImageAnalysis mImageAnalysis =
new ImageAnalysis.Builder()
//.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
.setTargetResolution(new Size(720, 1280)) // 图片的建议尺寸
.setOutputImageRotationEnabled(true) // 是否旋转分析器中得到的图片
.setTargetRotation(Surface.ROTATION_0) // 允许旋转后 得到图片的旋转设置
.setBackpressureStrategy(ImageAnalysis.STRATEGY_BLOCK_PRODUCER)
.build(); private float mLastTx = 0; // 手指的上一个位置
private float mLastTy = 0; private int mBigHeight = 0;
private int mBigWid = 0; @Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); WindowManager.LayoutParams layoutParams = getWindow().getAttributes();
layoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; mBinding = DataBindingUtil.setContentView(this, R.layout.me_act_simple_preivew_x_scale);
mCameraProviderFuture = ProcessCameraProvider.getInstance(this);
mCameraProviderFuture.addListener(() -> {
try {
mCameraProvider = mCameraProviderFuture.get();
Log.d(TAG, "获取到了 cameraProvider");
// bindPreview(mCameraProvider);
} catch (ExecutionException | InterruptedException e) {
// 这里不用处理
}
}, ContextCompat.getMainExecutor(this));
mBinding.start.setOnClickListener(v -> {
if (mCameraProvider != null && !mRunning) {
bindPreview(mCameraProvider);
}
});
mBinding.end.setOnClickListener(v -> {
mCameraProvider.unbindAll();
mRunning = false;
}); mBinding.takeOneAnalyse.setOnClickListener(v -> {
mTakeOneYuv = true;
Log.d(TAG, "获取一帧, 输出图片旋转: " + mImageAnalysis.isOutputImageRotationEnabled());
}); final ExecutorService executorService = Executors.newFixedThreadPool(2);
mBinding.enableAna.setOnClickListener(v -> {
Toast.makeText(getApplicationContext(), "启用分析器", Toast.LENGTH_SHORT).show();
mImageAnalysis.setAnalyzer(executorService, imageProxy -> {
// 下面处理数据
if (mTakeOneYuv) {
mTakeOneYuv = false;
Log.d(TAG, "旋转角度: " + imageProxy.getImageInfo().getRotationDegrees());
ImgHelper.useYuvImgSaveFile(imageProxy, true); // 存储这一帧为文件
runOnUiThread(() -> Toast.makeText(getApplicationContext(), "截取一帧", Toast.LENGTH_SHORT).show());
}
imageProxy.close(); // 最后要关闭这个
});
});
mBinding.clrAna.setOnClickListener(v -> {
mImageAnalysis.clearAnalyzer();
Toast.makeText(getApplicationContext(), "clearAnalyzer", Toast.LENGTH_SHORT).show();
});
mBinding.zoomIv.setOnClickListener(v -> {
if (mIsSmallWindow) {
toBigWindow();
} else {
toSmallWindow();
}
}); mBinding.root.setOnTouchListener((v, event) -> {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "down " + event);
mLastTx = event.getRawX();
mLastTy = event.getRawY();
return true;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "move " + event);
float dx = event.getRawX() - mLastTx;
float dy = event.getRawY() - mLastTy;
mLastTx = event.getRawX();
mLastTy = event.getRawY();
Log.d(TAG, " dx: " + dx + ", dy: " + dy);
if (mIsSmallWindow) {
WindowManager.LayoutParams lp = getWindow().getAttributes();
int tx = (int) (lp.x + dx);
int ty = (int) (lp.y + dy);
Log.d(TAG, "move to " + tx + ", " + ty);
if (mLimitArea) {
tx = Math.max(lp.width / 2 - mBigWid / 2, tx);
tx = Math.min(mBigWid / 2 - lp.width / 2, tx);
ty = Math.max(lp.height / 2 - mBigHeight / 2, ty);
ty = Math.min(mBigHeight / 2 - lp.height / 2, ty);
}
lp.x = tx;
lp.y = ty;
getWindow().setAttributes(lp);
}
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "up " + event);
return true;
case MotionEvent.ACTION_CANCEL:
Log.d(TAG, "cancel " + event);
return true;
}
return false;
});
mBinding.container.post(() -> {
mBigWid = mBinding.container.getWidth();
mBigHeight = mBinding.container.getHeight();
Log.d(TAG, "container size: " + mBigWid + ", " + mBigHeight);
});
} private void bindPreview(ProcessCameraProvider cameraProvider) {
if (cameraProvider == null) {
Toast.makeText(getApplicationContext(), "没获取到相机", Toast.LENGTH_SHORT).show();
return;
}
Toast.makeText(getApplicationContext(), "相机启动", Toast.LENGTH_SHORT).show();
Preview preview = new Preview.Builder().build(); CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build(); preview.setSurfaceProvider(mBinding.previewView.getSurfaceProvider()); cameraProvider.bindToLifecycle(this, cameraSelector, preview, mImageAnalysis);
mRunning = true;
} private void toSmallWindow() {
mBinding.funcField.setVisibility(View.GONE);
mIsSmallWindow = true;
mBinding.zoomIv.setImageResource(R.drawable.me_to_big); android.view.WindowManager.LayoutParams p = getWindow().getAttributes();
p.height = 480;
p.width = 360;
p.dimAmount = 0.0f;
getWindow().setAttributes(p);
} private void toBigWindow() {
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.x = 0;
lp.y = 0;
getWindow().setAttributes(lp); getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
mBinding.funcField.setVisibility(View.VISIBLE);
mIsSmallWindow = false;
mBinding.zoomIv.setImageResource(R.drawable.me_ic_to_small);
}
}

运行测试

运行到手机上,打开这个Activity就可以看到摄像头预览。图像宽高比正常,没有拉伸现象。

缩小成悬浮窗后,可以拖动。

  • 荣耀 EMUI 3.1 Lite,Android 5.1 运行正常
  • Redmi 9A,MIUI 12.5.1稳定版,Android 10 运行正常

小结

从简单的打开相机预览来看,CameraX简化了开发者的工作。提供了PreviewView,开发者不需要自定义SurfaceView或者TextureView。实时预览中,相机能够自动对焦。可以试试按home键回桌面,或者锁屏,然后再回来。

参考

Android 摄像头预览悬浮窗的更多相关文章

  1. Android 摄像头预览悬浮窗,可拖动,可显示在其他app上方

    市面上常见的摄像头悬浮窗,如微信.手机QQ的视频通话功能,有如下特点: 整屏页面能切换到一个小的悬浮窗 悬浮窗能运行在其他app上方 悬浮窗能跳回整屏页面,并且悬浮窗消失 我们探讨过用CameraX打 ...

  2. Android CameraX 打开摄像头预览

    目标很简单,用CameraX打开摄像头预览,实时显示在界面上.看看CameraX有没有Google说的那么好用.先按最简单的来,把预览显示出来. 引入依赖 模块gradle的一些配置,使用的Andro ...

  3. 【腾讯优测干货分享】Android 相机预览方向及其适配探索

    本文来自于腾讯bugly开发者社区,未经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/583ba1df25d735cd2797004d 由于Android系统的开放策略 ...

  4. 基于开源的GOCW和Directshow.net,实现摄像头预览、采集、录像等操作

    本文基于开源的GOCW和Directshow.net,实现图像采集等操作.最为关键的部分在于可以实现摄像头的控制,同时关于视频采集进行了实现. 具体的内容请关注首发于51CTO的课程<基于Csh ...

  5. 关于降低android手机摄像头预览分辨率

    假设现在有这样一个需求需要一直开着手机摄像头 但是不做任何拍照动作 但是每个手机的相机分辨率都不同 而默认预览的时候参数是最大分辨率 这样有时候就回导致电量损耗的加快 所以我们可以采取降低相机分辨率的 ...

  6. android camera 摄像头预览画面变形

    问题:最近在处理一下camera的问题,发现在竖屏时预览图像会变形,而横屏时正常.但有的手机则是横竖屏都会变形. 结果:解决了预览变形的问题,同时支持前后摄像头,预览无变形,拍照生成的jpg照片方向正 ...

  7. Android开发:实时处理摄像头预览帧视频------浅析PreviewCallback,onPreviewFrame,AsyncTask的综合应用(转)

    原文地址:http://blog.csdn.net/yanzi1225627/article/details/8605061# 很多时候,android摄像头模块不仅预览,拍照这么简单,而是需要在预览 ...

  8. Android Camera2 预览功能实现

    1. 概述 最近在做一些关于人脸识别的项目,需要用到 Android 相机的预览功能.网上查阅相关资料后,发现 Android 5.0 及以后的版本中,原有的 Camera API 已经被 Camer ...

  9. Android Camera2 预览,拍照,人脸检测并实时展现

    https://www.jianshu.com/p/5414ba2b5508 背景     最近需要做一个人脸检测并实时预览的功能.就是边检测人脸,边在预览界面上框出来.     当然本人并不是专门做 ...

随机推荐

  1. vue-cli4脚手架搭建二

    vue-cli4脚手架搭建一 vue create vue3 cd vue3 yarn serve http://localhost:8080/#/ main.js文件 import Vue from ...

  2. springboot项目中集成ip2region遇到的问题及终极解决办法

    1.问题回顾 按照ip2region项目的官方集成到springboot项目后,运行测试一切都ok,没有任何问题.但是当项目打成可执行的jar包后再运行,却显示找不到ip2region.db,无法找到 ...

  3. Mycat的事务异常:Caused by: java.sql.SQLException: Transaction error, need to rollback.Distributed transaction is disabled!

    工作中踩到的一个坑 ,一个报错,导致整个服务不能用.工程部署四个节点,请求是按轮询机制分发的,所以请求四次报错,整个系统瘫痪.记录下 . 项目环境:spring +Mybaties +mycat +D ...

  4. Controller返回类的自动识别,WEB-INF,jsp位置

    Controller: @Controller@RequestMapping("/params")public class ParamsController { @RequestM ...

  5. SpringBoot自定义控制层参数解析

    一.背景 在Spring的Controller中,我们通过@RequestParam或@RequestBody就可以将请求中的参数映射到控制层具体的参数中,那么这个是怎么实现的呢?如果我现在控制层中的 ...

  6. 基于Kubernetes的hpa实现pod实例数量的自动伸缩

    Pod 是在 Kubernetes 体系中,承载用户业务负载的一种资源.Pod 们运行的好坏,是用户们最为关心的事情.在业务流量高峰时,手动快速扩展 Pod 的实例数量,算是玩转 Kubernetes ...

  7. HBuilderX无法启动微信小程序?仅三步

    1.复制微信开发者工具启动路径 : "C:\Program Files (x86)\Tencent\微信web开发者工具\微信web开发者工具.exe" 不要后面的 "微 ...

  8. [BUUCTF]PWN——bjdctf_2020_babyrop2

    bjdctf_2020_babyrop2 附件 步骤: 例行检查,64位程序,开启了NX和canary保护 2. 试运行一下程序,看看大概的情况 提示我们去泄露libc 3. 64位ida载入,从ma ...

  9. java JDK8 时间处理

    目录 时间格式化 LocalDate:年月日 LocalTime:时分秒毫秒 LocalDateTime:年月日时分秒 Instant:纳秒时间戳 Duration:两时间间隔 Duration:处理 ...

  10. 在myeclipse里加大tomcat内存,jdk内存方法

    这是在myeclipse里加大的方法: -Xms4096m -Xmx4096m -XX:MaxNewSize=4096m -XX:MaxPermSize=4096m 如图所示: -XX:PermSiz ...