Android 悬浮窗口(二)

前言

上章讲述了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 工程代码如有需求后续上传

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值