一种是弹窗的拖动布局,一种是非弹窗。
代码如下:
非弹窗:这里加载了一个本地的视频
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.MediaController;
import android.widget.VideoView;
import androidx.appcompat.app.AppCompatActivity;
public class MoveActivity extends AppCompatActivity implements View.OnTouchListener{
private VideoView video;
private int sx;
private int sy;
private SharedPreferences sp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_move);
if (getSupportActionBar() != null){
getSupportActionBar().hide();
}
sp = this.getSharedPreferences("config", Context.MODE_PRIVATE);
video = this.findViewById(R.id.video_view);
video.setVideoPath("android.resource://" + this.getPackageName() + "/" + R.raw.videos);
video.setOnTouchListener(this);
//创建MediaController对象
MediaController mediaController = new MediaController(this);
video.setMediaController(mediaController); //让videoView 和 MediaController相关联
video.setFocusable(true); //让VideoView获得焦点
video.start(); //开始播放视频
}
@Override
protected void onResume() {
super.onResume();
}
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (v.getId()) {
// 如果手指放在imageView上拖动
case R.id.video_view:
// event.getRawX(); //获取手指第一次接触屏幕在x方向的坐标
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:// 获取手指第一次接触屏幕
sx = (int) event.getRawX();
sy = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:// 手指在屏幕上移动对应的事件
int x = (int) event.getRawX();
int y = (int) event.getRawY();
// 获取手指移动的距离
int dx = x - sx;
int dy = y - sy;
// 得到imageView最开始的各顶点的坐标
int l = video.getLeft();
int r = video.getRight();
int t = video.getTop();
int b = video.getBottom();
// 更改imageView在窗体的位置
video.layout(l + dx, t + dy, r + dx, b + dy);
// 获取移动后的位置
sx = (int) event.getRawX();
sy = (int) event.getRawY();
break;
case MotionEvent.ACTION_UP:// 手指离开屏幕对应事件
// 记录最后图片在窗体的位置
int lasty = video.getTop();
int lastx = video.getLeft();
SharedPreferences.Editor editor = sp.edit();
editor.putInt("lasty", lasty);
editor.putInt("lastx", lastx);
editor.commit();
break;
}
break;
}
return true;// 不会中断触摸事件的返回
}
}
布局:
<?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"
android:orientation="horizontal">
<com.example.mymovevideo.tools.ConditionVideoView
android:id="@+id/video_view"
android:layout_width="100dp"
android:layout_height="100dp" />
</LinearLayout>
弹窗试:弹窗加载一个视频
import androidx.appcompat.app.AppCompatActivity;
import android.Manifest;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.View;
import com.example.mymovevideo.databinding.ActivityMainBinding;
import com.example.mymovevideo.tools.HomeWatcher;
import com.example.mymovevideo.tools.SmallWindowsView;
import pub.devrel.easypermissions.EasyPermissions;
public class MainActivity extends AppCompatActivity {
SmallWindowsView smallWindowsView;
ActivityMainBinding mainBinding;
private HomeWatcher mHomeWatcher;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mainBinding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(mainBinding.getRoot());
smallWindowsView = new SmallWindowsView(MainActivity.this);
mainBinding.btShowWindow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (Build.VERSION.SDK_INT >= 23) {
if (!(Settings.canDrawOverlays(MainActivity.this))) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
return;
}
} else {
if (!EasyPermissions.hasPermissions(MainActivity.this, Manifest.permission.SYSTEM_ALERT_WINDOW)) {
EasyPermissions.requestPermissions(MainActivity.this, "需要权限用以展示悬浮窗",
2048, Manifest.permission.SYSTEM_ALERT_WINDOW);
return;
}
}
smallWindowsView.show();
mainBinding.btShowWindow.setClickable(false);
}
});
mHomeWatcher = new HomeWatcher(this);
mHomeWatcher.setOnHomePressedListener(new HomeWatcher.OnHomePressedListener() {
@Override
public void onHomePressed() {
smallWindowsView.dismiss();
mainBinding.btShowWindow.setClickable(true);
}
@Override
public void onHomeLongPressed() {
smallWindowsView.dismiss();
mainBinding.btShowWindow.setClickable(true);
}
});
mHomeWatcher.startWatch();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
//按下BACK键
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
smallWindowsView.dismiss();
mainBinding.btShowWindow.setClickable(true);
}
return super.onKeyDown(keyCode, event);
}
@Override
public void onOptionsMenuClosed(Menu menu) {
super.onOptionsMenuClosed(menu);
smallWindowsView.dismiss();
mainBinding.btShowWindow.setClickable(true);
}
}
布局:
<?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"
android:background="@color/white"
android:orientation="vertical">
<Button
android:text="展示滑动布局"
android:id="@+id/bt_show_window"
android:layout_width="match_parent"
android:layout_height="50dp"/>
</LinearLayout>
工具类:
import android.content.Context;
import android.util.AttributeSet;
import android.widget.VideoView;
public class ConditionVideoView extends VideoView {
public ConditionVideoView(Context context) {
super(context);
}
public ConditionVideoView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ConditionVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//使视频全屏播放
int width = getDefaultSize(0, widthMeasureSpec);
int height = getDefaultSize(0, heightMeasureSpec);
setMeasuredDimension(width, height);
}
}
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
public class HomeWatcher {
private Context mContext;
private IntentFilter mFilter;
private OnHomePressedListener mListener;
private InnerRecevier mRecevier;
// 回调接口
public interface OnHomePressedListener {
public void onHomePressed();
public void onHomeLongPressed();
}
public HomeWatcher(Context context) {
mContext = context;
mRecevier = new InnerRecevier();
mFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
}
/**
* 设置监听
*
* @param listener
*/
public void setOnHomePressedListener(OnHomePressedListener listener) {
mListener = listener;
}
/**
* 开始监听,注册广播
*/
public void startWatch() {
if (mRecevier != null) {
mContext.registerReceiver(mRecevier, mFilter);
}
}
/**
* 广播接收者
*/
class InnerRecevier extends BroadcastReceiver {
final String SYSTEM_DIALOG_REASON_KEY = "reason";
final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
if (reason != null) {
if (mListener != null) {
if (reason.equals(SYSTEM_DIALOG_REASON_HOME_KEY)) {
// 短按home键
mListener.onHomePressed();
} else if (reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {
// 长按home键
mListener.onHomeLongPressed();
}
}
}
}
}
}
}
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class HomeWatcherReceiver extends BroadcastReceiver {
private static final String LOG_TAG = "HomeReceiver";
private static final String SYSTEM_DIALOG_REASON_KEY = "reason";
private static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
private static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
private static final String SYSTEM_DIALOG_REASON_LOCK = "lock";
private static final String SYSTEM_DIALOG_REASON_ASSIST = "assist";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
Log.i(LOG_TAG, "reason: " + reason);
if (SYSTEM_DIALOG_REASON_HOME_KEY.equals(reason)) {
// 短按Home键
Log.i(LOG_TAG, "homekey");
}
else if (SYSTEM_DIALOG_REASON_RECENT_APPS.equals(reason)) {
// 长按Home键 或者 activity切换键
Log.i(LOG_TAG, "long press home key or activity switch");
}
else if (SYSTEM_DIALOG_REASON_LOCK.equals(reason)) {
// 锁屏
Log.i(LOG_TAG, "lock");
}
else if (SYSTEM_DIALOG_REASON_ASSIST.equals(reason)) {
// samsung 长按Home键
Log.i(LOG_TAG, "assist");
}
}
}
}
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Build;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.MediaController;
import android.widget.VideoView;
import androidx.annotation.NonNull;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.core.content.ContextCompat;
import com.example.mymovevideo.R;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.ExecutionException;
public class SmallWindowsView extends FrameLayout {
private int mSlop;//触发移动事件的最小距离
private float downX;//手指放下去的x坐标
private float downY;//手指放下去的Y坐标
/**
* 下面四个数据都为像素
*/
private int screenWidth;//屏幕宽度
private int screenHeight;//屏幕高度
private int viewWidth;//小窗的宽度
private int viewHeight;//小窗的高度
private WindowManager wm;//窗口管理器,用来把view添加进窗口层
private WindowManager.LayoutParams wmParams;
private ProcessCameraProvider mCameraProvider;
private ListenableFuture<ProcessCameraProvider> mCameraProviderFuture;
public SmallWindowsView(@NonNull Context context) {
super(context);
init(context);
}
private void init(Context context) {
ViewConfiguration vc = ViewConfiguration.get(getContext());
// 窗口里的布局
View view = View.inflate(context, R.layout.view_floating_window, null);
mSlop = vc.getScaledTouchSlop();
screenWidth = getContext().getResources().getDisplayMetrics().widthPixels;
screenHeight = getContext().getResources().getDisplayMetrics().heightPixels;
viewWidth = dp2px(getContext(), 130);
viewHeight = dp2px(getContext(), 130);
mCameraProviderFuture = ProcessCameraProvider.getInstance(context);
mCameraProviderFuture.addListener(() -> {
try {
mCameraProvider = mCameraProviderFuture.get();
} catch (ExecutionException | InterruptedException e) {
// 这里不用处理
}
}, ContextCompat.getMainExecutor(context));
VideoView video = view.findViewById(R.id.videoView);
video.setVideoPath("android.resource://" + context.getPackageName() + "/" + R.raw.videos);
/**
* 控制视频的播放 主要通过MediaController控制视频的播放
*/
//创建MediaController对象
MediaController mediaController = new MediaController(context);
video.setMediaController(mediaController); //让videoView 和 MediaController相关联
video.setFocusable(true); //让VideoView获得焦点
video.start(); //开始播放视频
// 实际上就是拿到一个View从WindowManager给addView进去
LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
addView(view, params);
}
//dp转px
public int dp2px(Context context, int dp) {
return (int) (getDensity(context) * dp + 0.5);
}
public float getDensity(Context context) {
return context.getResources().getDisplayMetrics().density;
}
public void show() {
wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
wmParams = new WindowManager.LayoutParams(
viewWidth, viewHeight,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,//8.0以上需要用这个权限
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT);
wmParams.gravity = Gravity.NO_GRAVITY;
wmParams.x = screenWidth / 2 - viewWidth / 2;
wmParams.y = screenHeight / 2 - viewHeight / 2;
// 适配8.0以上
if (Build.VERSION.SDK_INT > 24) {
wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
wmParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
wm.addView(this, wmParams);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
//拦截触摸事件自己消费
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
}
private long downTime;
private float lastMoveX;
private float lastMoveY;
//消费触摸事件
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = event.getRawX();
downY = event.getRawY();
lastMoveX = downX;
lastMoveY = downY;
downTime = System.currentTimeMillis();
break;
case MotionEvent.ACTION_MOVE:
float moveX = event.getRawX();
float moveY = event.getRawY();
//就两个坐标算他们距离要大于触发移动事件的最小距离
//这里也可以减去lastMoveX lastMoveY 但是移动会有卡顿感 因此这里使用的还是downX downY
if (Math.pow(Math.abs(moveX - downX), 2) + Math.pow(Math.abs(moveY - downY), 2) > Math.pow(mSlop, 2)) {
updateViewPosition(moveX - lastMoveX, moveY - lastMoveY);
lastMoveX = moveX;
lastMoveY = moveY;
}
break;
case MotionEvent.ACTION_UP:
float upX = event.getRawX();
float upY = event.getRawY();
long upTime = System.currentTimeMillis();
long time = upTime - downTime;
//点击事件实现 点击小窗口消失
//这里加了时间判断,是因为假如移动到原来的地方,也会触发成点击事件
if (Math.pow(Math.abs(upX - downX), 2) + Math.pow(Math.abs(upY - downY), 2) < Math.pow(mSlop, 2) && time < 1000) {
showRtcVideo();
}
break;
}
return true;
}
private void showRtcVideo() {
dismiss();
}
public void dismiss() {
if (null != wm) {
wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
wm.removeView(this);
}
}
private void updateViewPosition(float moveX, float moveY) {
wmParams.gravity = Gravity.NO_GRAVITY;
//更新浮动窗口位置参数
wmParams.x = (int) (wmParams.x + moveX);
wmParams.y = (int) (wmParams.y + moveY);
//刷新显示
wm.updateViewLayout(this, wmParams);
}
}
工具类布局:
<?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"
android:background="@color/cardview_light_background"
android:orientation="vertical">
<com.example.mymovevideo.tools.ConditionVideoView
android:id="@+id/videoView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
配置文件:
plugins {
id 'com.android.application'
}
android {
namespace 'com.example.mymovevideo'
compileSdk 32
defaultConfig {
applicationId "com.example.mymovevideo"
minSdk 21
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
dataBinding {
enabled = true
}
viewBinding {
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 'pub.devrel:easypermissions:3.0.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"
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
清单文件
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<!-- 授予该程序移动窗口的权限 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<!-- 授予该程序使用摄像头的权限 -->
<uses-permission android:name="android.permission.CAMERA"/>
<!-- 授予使用外部存储器的权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.NoActionBar"
tools:targetApi="31">
<activity
android:name=".MoveActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
</application>
</manifest>
视频文件:












![[附源码]计算机毕业设计Python的家政服务平台(程序+源码+LW文档)](https://img-blog.csdnimg.cn/1e3ffa1acaac4d28a430c26be8ce9bcb.png)






