前言
这章主要介绍 悬浮窗口
主要分为2种
1:悬浮在所有窗口上
2:悬浮在当前窗口上
这章讲述 悬浮在所有窗口上
1 准备
Android studio 4.1.1 或以上
win7 或以上
2 悬浮在所有窗口上
1> 需要注册个service
AndroidManifest.xml 如下
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.testfloatallwindow">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<!--uses-permission android:name="android.permission.CAMERA" /-->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Testfloatallwindow">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".FloatingVideoService"></service>
</application>
</manifest>
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>
video_display.xml
<?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
```clike
plugins {
id 'com.android.application'
}
android {
compileSdkVersion 33
buildToolsVersion "33.0.0"
defaultConfig {
applicationId "com.example.testfloatallwindow"
minSdkVersion 23
targetSdkVersion 33
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}"
}
app 下的 build.gradle
plugins {
id 'com.android.application'
}
android {
compileSdkVersion 33
buildToolsVersion "33.0.0"
defaultConfig {
applicationId "com.example.testcamerax"
minSdkVersion 23
targetSdkVersion 30
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'
def camerax_version = "1.2.0" //"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}"
// If you want to additionally use the CameraX VideoCapture library
implementation "androidx.camera:camera-video:${camerax_version}"
// CameraX core library using the camera2 implementation
// def camerax_version = "1.2.0-alpha02" //1.2.0-alpha02
// The following line is optional, as the core library is included indirectly by camera-camera2
/* implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
// If you want to additionally use the CameraX Lifecycle library
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
// If you want to additionally use the CameraX VideoCapture library
implementation "androidx.camera:camera-video:${camerax_version}"
// If you want to additionally use the CameraX View class
implementation "androidx.camera:camera-view:${camerax_version}"
// If you want to additionally add CameraX ML Kit Vision Integration
implementation "androidx.camera:camera-mlkit-vision:${camerax_version}"
// If you want to additionally use the CameraX Extensions library
implementation "androidx.camera:camera-extensions:${camerax_version}"*/
}
3>代码
MainActivity.java
```clike
package com.example.testfloatallwindow;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
//绑定方式 启动
// private FloatingVideoService service = null;
// private boolean isBound = false;
//
// private ServiceConnection conn = new ServiceConnection() {
// @Override
// public void onServiceConnected(ComponentName name, IBinder binder) {
// isBound = true;
// FloatingVideoService.MyBinder myBinder = (FloatingVideoService.MyBinder)binder;
// service = myBinder.getService();
// Log.i("DemoLog", "ActivityA onServiceConnected");
// // int num = service.getRandomNumber();
// // Log.i("DemoLog", "ActivityA 中调用 TestService的getRandomNumber方法, 结果: " + num);
// }
//
// @Override
// public void onServiceDisconnected(ComponentName name) {
// isBound = false;
// Log.i("DemoLog", "ActivityA onServiceDisconnected");
// }
// };
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void startbtn(View view) {
if (FloatingVideoService.isStarted) {
return;
}
if (!Settings.canDrawOverlays(this)) {
Toast.makeText(this, "当前无权限,请授权", Toast.LENGTH_SHORT);
startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), 2);
} else {
startService(new Intent(MainActivity.this, FloatingVideoService.class));
}
}
public void closebtn(View view){
//unbindService(conn);
stopService(new Intent(MainActivity.this,FloatingVideoService.class));
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 0) {
if (!Settings.canDrawOverlays(this)) {
Toast.makeText(this, "授权失败", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "授权成功", Toast.LENGTH_SHORT).show();
startService(new Intent(MainActivity.this, FloatingVideoService.class));
}
} else if (requestCode == 1) {
if (!Settings.canDrawOverlays(this)) {
Toast.makeText(this, "授权失败", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "授权成功", Toast.LENGTH_SHORT).show();
startService(new Intent(MainActivity.this, FloatingVideoService.class));
}
} else if (requestCode == 2) {
if (!Settings.canDrawOverlays(this)) {
Toast.makeText(this, "授权失败", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "授权成功", Toast.LENGTH_SHORT).show();
startService(new Intent(MainActivity.this, FloatingVideoService.class));
}
}
}
}
FloatingVideoService.java
package com.example.testfloatallwindow;
import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.ImageFormat;
import android.graphics.PixelFormat;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.provider.Settings;
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.Toast;
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.content.ContextCompat;
import com.google.common.util.concurrent.ListenableFuture;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
public class FloatingVideoService extends Service {
public static boolean isStarted = false;
private WindowManager windowManager;
private WindowManager.LayoutParams layoutParams;
private MediaPlayer mediaPlayer;
private View displayView;
public static boolean bSetup = true;
public static final int DMS_INPUT_IMG_W = 640;
public static final int DMS_INPUT_IMG_H = 480;
//
private final String TAG = MainActivity.class.getName() ;
private final int REQUEST_CODE_CONTACT = 1;
private boolean isBackCamera =false;
private ExecutorService cameraExecutor;
private ProcessCameraProvider mCameraPRrovider = null;
//
//下面的可以去掉,bind connect 方式 服务
public class MyBinder extends Binder{
public FloatingVideoService getService(){
return FloatingVideoService.this;
}
}
//通过binder实现调用者client与Service之间的通信
private MyBinder binder = new MyBinder();
public void methodInService() {
Log.i("MyService", "执行服务钟的methodInService()方法");
}
@Override
public boolean onUnbind(Intent intent) {
Log.i("MyService","jiebang ");
return super.onUnbind(intent);
}
@Override
public IBinder onBind(Intent intent) {
Log.i("MyService","绑定服务");
return null; //new MyBinder();
}
@Override
public void onCreate() {
super.onCreate();
isStarted = true;
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
layoutParams = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; //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;
mediaPlayer = new MediaPlayer();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
showFloatingWindowVideo();
return super.onStartCommand(intent, flags, startId);
}
private void showFloatingWindowVideo() {
if (Settings.canDrawOverlays(this)) {
LayoutInflater layoutInflater = LayoutInflater.from(this);
displayView = layoutInflater.inflate(R.layout.video_display, null);
displayView.setOnTouchListener(new FloatingOnTouchListener());
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
SurfaceView surfaceView = displayView.findViewById(R.id.video_display_surfaceview);
final SurfaceHolder surfaceHolder = surfaceView.getHolder();
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
mediaPlayer.setDisplay(surfaceHolder);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
});
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mediaPlayer.start();
}
});
try {
mediaPlayer.setDataSource(this, Uri.parse("视屏地址url"));
mediaPlayer.prepareAsync();
}
catch (IOException e) {
Toast.makeText(this, "打开失败", Toast.LENGTH_LONG).show();
}
windowManager.addView(displayView, layoutParams);
bSetup = true ;
}
}
private void showFloatingWindowCamera() {
if (Settings.canDrawOverlays(this)) {
LayoutInflater layoutInflater = LayoutInflater.from(this);
displayView = layoutInflater.inflate(R.layout.video_display, null);
displayView.setOnTouchListener(new FloatingOnTouchListener());
SurfaceView surfaceView = displayView.findViewById(R.id.video_display_surfaceview);
final SurfaceHolder surfaceHolder = surfaceView.getHolder();
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
});
windowManager.addView(displayView, layoutParams);
bSetup = true ;
}
}
private void setupCamera(){
// 将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.video_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蓝色
// @SuppressLint("UnsafeOptInUsageError") Bitmap bitmap = toBitmap(imageProxy.getImage());
// Bitmap bitmap1 = alterSizeBitmap(bitmap,DMS_INPUT_IMG_W,DMS_INPUT_IMG_H);
// byte[] data = getNV21FromBitmap(bitmap1);
// onPreviewResult(data);
}
imageProxy.close(); // 最后要关闭这个
});
// 重新绑定用例前先解绑
mCameraPRrovider.unbindAll();
// 绑定用例至相机
//需要LifecycleOwner view
// mCameraPRrovider.bindToLifecycle(this, cameraSelector,
// imageAnalysis,preview);
} catch (Exception e) {
Log.e(TAG, "用例绑定失败!" + e);
}
}, ContextCompat.getMainExecutor(this));
}
@Override
public void onDestroy() {
if(windowManager !=null && layoutParams !=null) {
if(bSetup){
windowManager.removeView(displayView);
bSetup = false ;
isStarted = false;
}
}
super.onDestroy();
}
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;
}
}
}
//生命周期上的区别
//执行startService时,Service会经历onCreate->onStartCommand。当执行stopService时,直接调用onDestroy方法。调用者如果没有stopService,Service会一直在后台运行,下次调用者再起来仍然可以stopService。
//执行bindService时,Service会经历onCreate->onBind。这个时候调用者和Service绑定在一起。调用者调用unbindService方法或者调用者Context不存在了(如Activity被finish了),Service就会调用onUnbind->onDestroy。这里所谓的绑定在一起就是说两者共存亡了。
**3 运行结果**
视屏地址自行修改,当前地址瞎写,所以播放不出
![在这里插入图片描述](https://img-blog.csdnimg.cn/bae22975ec154dd8ab728bffc5a28503.png)
**4下章讲述悬浮在当前窗口上**