拖动布局的两种方式

一种是弹窗的拖动布局,一种是非弹窗。

代码如下:

非弹窗:这里加载了一个本地的视频

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>

视频文件:

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值