前言
上章讲述了service 悬浮窗口
这章主要介绍 悬浮在当前窗口上
1 准备
Android studio 4.1.1 或以上
win7 或以上
2 相关文件
activity_main.xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/show_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="start"
android:onClick="startbtn"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="close"
android:onClick="closebtn"/>
</LinearLayout>```
camera_display.xml
```css
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.camera.view.PreviewView
android:id="@+id/video_display_surfaceview"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<!--SurfaceView
android:id="@+id/video_display_surfaceview"
android:layout_width="wrap_content"
android:layout_height="wrap_content" /-->
</LinearLayout>
app 下 build.gradle
plugins {
id 'com.android.application'
}
android {
compileSdkVersion 31
buildToolsVersion '30.0.3'
defaultConfig {
applicationId "com.example.testfloatcurwindow"
minSdkVersion 26
targetSdkVersion 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
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.1.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
//附加的
implementation 'org.apache.commons:commons-pool2:2.9.0'
def camerax_version = "1.1.0-beta03"
// CameraX core library
implementation "androidx.camera:camera-core:$camerax_version"
// CameraX Camera2 extensions[可选]拓展库可实现人像、HDR、夜间和美颜、滤镜但依赖于OEM
implementation "androidx.camera:camera-camera2:$camerax_version"
// CameraX Lifecycle library[可选]避免手动在生命周期释放和销毁数据
implementation "androidx.camera:camera-lifecycle:$camerax_version"
// CameraX View class[可选]最佳实践,最好用里面的PreviewView,它会自行判断用SurfaceView还是TextureView来实现
implementation "androidx.camera:camera-view:$camerax_version"
implementation "androidx.camera:camera-extensions:${camerax_version}"
}
3 相关代码
MainActivity.java
package com.example.testfloatcurwindow;
import androidx.annotation.NonNull;
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.camera.view.PreviewView;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.media.AudioManager;
import android.media.Image;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.util.Size;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;
import android.widget.Toast;
import com.google.common.util.concurrent.ListenableFuture;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MainActivity extends AppCompatActivity {
private static String TAG = "MainActivity" ;// MainActivity.class.getName() ;
private WindowManager windowManager;
private WindowManager.LayoutParams layoutParams;
private View displayView;
TextView show_text ;
///carmera
private final int REQUEST_CODE_CONTACT = 1;
private ExecutorService cameraExecutor;
private ProcessCameraProvider mCameraPRrovider = null;
private boolean isBackCamera =false;
public static final int DMS_INPUT_IMG_SIZE = 640 * 480 * 3 / 2;
public static final int DMS_INPUT_IMG_W = 640;
public static final int DMS_INPUT_IMG_H = 480;
private boolean isInit = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
layoutParams = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL; //TYPE_APPLICATION_SUB_PANEL ; //TYPE_APPLICATION_OVERLAY;
} else {
layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}
layoutParams.format = PixelFormat.RGBA_8888;
layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
layoutParams.width = DMS_INPUT_IMG_W;
layoutParams.height = DMS_INPUT_IMG_H;
layoutParams.x = 300;
layoutParams.y = 300;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.INTERNET,
Manifest.permission.CAMERA,
}; //Manifest.permission.RECORD_AUDIO,
for (String str : permissions) {
if (ContextCompat.checkSelfPermission(this, str) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE_CONTACT);
return;
}
}
setupother();
}
/
}
void setupother(){
cameraExecutor = Executors.newSingleThreadExecutor();
// startCamera();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
// super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_CONTACT) {
if (!isInit) {
isInit = true;
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// doInitWork();
setupother();
} else if (grantResults.length == 0) {
// 已知魅族手机会进这个结果
} else {
Toast.makeText(this, "您拒绝了相关权限,无法使用!", Toast.LENGTH_SHORT).show();
finish();
}
}
}
}
public void startbtn(View view) {
// 将Camera的生命周期和Activity绑定在一起(设定生命周期所有者),这样就不用手动控制相机的启动和关闭。
ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this);
cameraProviderFuture.addListener(() -> {
try {
// 将你的相机和当前生命周期的所有者绑定所需的对象
ProcessCameraProvider mCameraPRrovider = cameraProviderFuture.get();
//
//要预览开启下面3句 + processCameraProvider.bindToLifecycle(MainActivity.this, cameraSelector,
// imageAnalysis,preview); preview 加上,不要预览就去跳
// 创建一个Preview 实例,并设置该实例的 surface 提供者(provider)。
LayoutInflater layoutInflater = LayoutInflater.from(this);
displayView = layoutInflater.inflate(R.layout.camera_display, null);
displayView.setOnTouchListener(new FloatingOnTouchListener());
PreviewView viewFinder = displayView.findViewById(R.id.video_display_surfaceview);
// PreviewView viewFinder = (PreviewView)findViewById(R.id.viewFinder);
Preview preview = new Preview.Builder()
.build();
preview.setSurfaceProvider(viewFinder.getSurfaceProvider());
windowManager.addView(displayView, layoutParams);
///
// 选择前置摄像头作为默认摄像头
CameraSelector cameraSelector = isBackCamera?CameraSelector.DEFAULT_BACK_CAMERA:CameraSelector.DEFAULT_FRONT_CAMERA;
// 创建拍照所需的实例
// imageCapture = new ImageCapture.Builder().build();
// 设置预览帧分析
// ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
// .build();
//ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888
ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
.setTargetResolution(new Size(DMS_INPUT_IMG_W, DMS_INPUT_IMG_H)) // 图片的建议尺寸
.setOutputImageRotationEnabled(true) // 是否旋转分析器中得到的图片
.setTargetRotation(Surface.ROTATION_0) // 允许旋转后 得到图片的旋转设置
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setImageQueueDepth(1)
.build();
// imageAnalysis.setAnalyzer(cameraExecutor, new MyAnalyzer());
imageAnalysis.setAnalyzer(cameraExecutor, imageProxy -> {
int picformat = imageProxy.getFormat();
if(imageProxy.getFormat() == ImageFormat.YUV_420_888){
// byte[] data = test2(imageProxy);
// onPreviewResult(data);
// byte[] data = bitmap.
// byte[] data = getUvData(imageProxy);
// onPreviewResult(data);
// Image image = imageProxy.getImage();
// ImageProxy.PlaneProxy[] mPlanes = imageProxy.getPlanes();
// byte[] data = YUV_420_888toNV21(mPlanes[0].getBuffer(),mPlanes[1].getBuffer(),mPlanes[2].getBuffer());
// byte[] data = BitmapUtils.yuv420ThreePlanesToNV21(image.getPlanes(),DMS_INPUT_IMG_W,DMS_INPUT_IMG_H);
// onPreviewResult(data);
// ImageProxy.PlaneProxy mY = mPlanes[0];//Y分量
// ImageProxy.PlaneProxy mU = mPlanes[1];//U分量
// ImageProxy.PlaneProxy mV = mPlanes[2];//V风量
// Log.e(TAG, "planes0:" + mY.getPixelStride() + " planes1:" + mU.getPixelStride() + " planes2:" + mV.getPixelStride());
}else if(imageProxy.getFormat() == ImageFormat.FLEX_RGBA_8888 || picformat == PixelFormat.RGBA_8888){
// ImageProxy.PlaneProxy[] mPlanes = imageProxy.getPlanes();
// imageProxy.getPlanes()[0].getBuffer().get(0); //alpha透明度
// imageProxy.getPlanes()[0].getBuffer().get(1); //red红色
// imageProxy.getPlanes()[0].getBuffer().get(2); //green绿色
// imageProxy.getPlanes()[0].getBuffer().get(3); //blue蓝色
// Log.d(TAG,"startCamera filename="+i+".jpg");
}
imageProxy.close(); // 最后要关闭这个
});
// 重新绑定用例前先解绑
mCameraPRrovider.unbindAll();
// 绑定用例至相机
mCameraPRrovider.bindToLifecycle(MainActivity.this, cameraSelector,
imageAnalysis,preview);
// 绑定用例至相机 unity
// mCameraPRrovider.bindToLifecycle((LifecycleOwner) UnityPlayerActivity.this, cameraSelector,
// imageAnalysis,preview);
} catch (Exception e) {
Log.e(TAG, "用例绑定失败!" + e);
}
}, ContextCompat.getMainExecutor(this));
}
public void closebtn(View view) {
if(mCameraPRrovider !=null) mCameraPRrovider.unbindAll();
windowManager.removeView(displayView);
}
private class FloatingOnTouchListener implements View.OnTouchListener {
private int x;
private int y;
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x = (int) event.getRawX();
y = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int nowX = (int) event.getRawX();
int nowY = (int) event.getRawY();
int movedX = nowX - x;
int movedY = nowY - y;
x = nowX;
y = nowY;
layoutParams.x = layoutParams.x + movedX;
layoutParams.y = layoutParams.y + movedY;
windowManager.updateViewLayout(view, layoutParams);
break;
default:
break;
}
return true;
}
}
}
4 unity 上弹出
需要申明LifecycleOwner
public class UnityPlayerActivity extends Activity implements IUnityPlayerLifecycleEvents,LifecycleOwner{
private LifecycleRegistry mLifecycleRegistry ;
............................................
@Override protected void onCreate(Bundle savedInstanceState){
...........................................
mLifecycleRegistry=new LifecycleRegistry(UnityPlayerActivity.this);
mLifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
}
@NonNull
@Override
public Lifecycle getLifecycle() {
return mLifecycleRegistry;
}
@Override
protected void onStart() {
super.onStart();
mLifecycleRegistry.setCurrentState(Lifecycle.State.STARTED);
}
// Resume Unity
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override protected void onResume()
{
super.onResume();
mUnityPlayer.resume();
mLifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED);
}
// Quit Unity
@Override protected void onDestroy ()
{
mUnityPlayer.destroy();
super.onDestroy();
mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
}
}
5 运行结果
这个是启动摄像机的
6 工程代码如有需求后续上传