android实现可自由移动、监听点击事件的悬浮窗

最近因为项目需要,自己实现了个可以自由移动,并且长按可以跳出一个控制播放的,额,大的悬浮窗。

好,开始吧。首先我们先聊权限,悬浮窗需要在manifest中声明一个权限:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

然后呢,嗯,我们来讲讲关于悬浮窗实现的原理。

在Andriod中,所有的界面元素都要通过windowmanger来实现,像Activity、Fragment等等这些也是在其上实现。因此,我们的悬浮窗自然要通过这个实现。

这个项目中,我们自定义了两个悬浮窗view。我们以其中一个比较简单的为例:
我们自定义一个管理可以统一管理悬浮窗的类MyWindowManager,负责创建,删除悬浮窗


/**
 * Created by shiwe on 2017/3/7.
 * 悬浮窗管理
 * 创建,移除
 * 单例模式
 */

public class MyWindowManager {

    private FloatNormalView normalView;
    private FloatControlView controlView;

    private static MyWindowManager instance;

    private MyWindowManager() {
    }

    public static MyWindowManager getInstance() {
        if (instance == null)
            instance = new MyWindowManager();
        return instance;
    }


    /**
     * 创建小型悬浮窗
     */
    public void createNormalView(Context context) {
        if (normalView == null)
            normalView = new FloatNormalView(context);
    }

    /**
     * 移除悬浮窗
     *
     * @param context
     */
    public void removeNormalView(Context context) {
        if (normalView != null) {
            WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            windowManager.removeView(normalView);
            normalView = null;
        }
    }

    /**
     * 创建小型悬浮窗
     */
    public void createControlView(Context context) {
        if (controlView == null)
            controlView = new FloatControlView(context);
    }

    /**
     * 移除悬浮窗
     *
     * @param context
     */
    public void removeControlView(Context context) {
        if (controlView != null) {
            WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            windowManager.removeView(controlView);
            controlView = null;
        }
    }
}

然后看看我们自定义的一个view,其继承自LinearLayout,我们在initLayoutParams初始化这个控件的位置等其他参数;在initEvent方法中定义随手指移动的监听事件以及长按的监听事件。

public class FloatNormalView extends LinearLayout {

    private Context context = null;
    private View view = null;
    private ImageView ivShowControlView = null;
    private WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
    private static WindowManager windowManager;

    private float mTouchStartX;
    private float mTouchStartY;
    private float x;
    private float y;
    private boolean initViewPlace = false;
    private MyWindowManager myWindowManager;
    private boolean isControlViewShowing = false;

    public FloatNormalView(Context context) {
        super(context);
        this.context = context;
        myWindowManager = MyWindowManager.getInstance();
        LayoutInflater.from(context).inflate(R.layout.float_normal_view, this);
        view = findViewById(R.id.ll_float_normal);
        ivShowControlView = (ImageView) findViewById(R.id.iv_show_control_view);
        windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        initLayoutParams();
        initEvent();
    }


    /**
     * 初始化参数
     */
    private void initLayoutParams() {
        //屏幕宽高
        int screenWidth = windowManager.getDefaultDisplay().getWidth();
        int screenHeight = windowManager.getDefaultDisplay().getHeight();

        //总是出现在应用程序窗口之上。
        lp.type = WindowManager.LayoutParams.TYPE_PHONE;

        // FLAG_NOT_TOUCH_MODAL不阻塞事件传递到后面的窗口
        // FLAG_NOT_FOCUSABLE 悬浮窗口较小时,后面的应用图标由不可长按变为可长按,不设置这个flag的话,home页的划屏会有问题
        lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;

        //悬浮窗默认显示的位置
        lp.gravity = Gravity.START | Gravity.TOP;
        //指定位置
        lp.x = screenWidth - view.getLayoutParams().width * 2;
        lp.y = screenHeight / 2 + view.getLayoutParams().height * 2;
        //悬浮窗的宽高
        lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
        lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
        lp.format = PixelFormat.TRANSPARENT;
        windowManager.addView(this, lp);
    }

    /**
     * 设置悬浮窗监听事件
     */
    private void initEvent() {
        ivShowControlView.setOnLongClickListener(new OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {
                if (!isControlViewShowing) {
                    myWindowManager.createControlView(context);
                    isControlViewShowing = true;
                } else {
                    myWindowManager.removeControlView(context);
                    isControlViewShowing = false;
                }
                return true;
            }
        });
        view.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        if (!initViewPlace) {
                            initViewPlace = true;
                            //获取初始位置
                            mTouchStartX += (event.getRawX() - lp.x);
                            mTouchStartY += (event.getRawY() - lp.y);
                        } else {
                            //根据上次手指离开的位置与此次点击的位置进行初始位置微调
                            mTouchStartX += (event.getRawX() - x);
                            mTouchStartY += (event.getRawY() - y);
                        }
                        break;
                    case MotionEvent.ACTION_MOVE:
                        // 获取相对屏幕的坐标,以屏幕左上角为原点
                        x = event.getRawX();
                        y = event.getRawY();
                        updateViewPosition();
                        break;

                    case MotionEvent.ACTION_UP:
                        break;
                }
                return true;
            }
        });
    }

    /**
     * 更新浮动窗口位置
     */
    private void updateViewPosition() {
        lp.x = (int) (x - mTouchStartX);
        lp.y = (int) (y - mTouchStartY);
        windowManager.updateViewLayout(this, lp);
    }

最后,只需要在Activity中调用mywindowManager中调用createxxx方法就可以。

public class MainActivity extends AppCompatActivity {

    MyWindowManager myWindowManager;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myWindowManager = MyWindowManager.getInstance();
        myWindowManager.createNormalView(this.getApplicationContext());
    }
}

最后,附上demo项目的下载地址:
http://download.csdn.net/detail/code_better/9774810

  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
实现自由移动监听点击事件悬浮窗,可以通过以下步骤来实现: 1. 在 AndroidManifest.xml 文件中添加悬浮窗权限: ```xml <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> ``` 2. 创建一个浮动窗口布局的 xml 文件,在其中添加一个可移动的 View 控件和必要的监听事件。 3. 在 Service 中创建 WindowManager 对象,并使用 addView() 方法将浮动窗口布局添加到 WindowManager 中。 4. 在浮动窗口布局的 View 控件中实现 onTouchEvent() 方法,监听触摸事件,并根据触摸事件的坐标改变浮动窗口的位置。 5. 在浮动窗口布局的 View 控件中实现 OnClickListener() 方法,监听点击事件,并执行相应的操作。 6. 在 Service 的 onDestroy() 方法中使用 removeView() 方法将浮动窗口布局从 WindowManager 中移除,释放资源。 下面是一个简单的实现代码示例: ```java public class FloatWindowService extends Service { private WindowManager mWindowManager; private View mFloatView; private WindowManager.LayoutParams mParams; @Override public void onCreate() { super.onCreate(); mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE); mFloatView = LayoutInflater.from(this).inflate(R.layout.float_window_layout, null); mParams = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_PHONE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); mParams.gravity = Gravity.TOP | Gravity.LEFT; mParams.x = 0; mParams.y = 0; mWindowManager.addView(mFloatView, mParams); // 监听触摸事件,改变浮动窗口位置 mFloatView.setOnTouchListener(new View.OnTouchListener() { private int mLastX; private int mLastY; private int mDownX; private int mDownY; @Override public boolean onTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mLastX = mParams.x; mLastY = mParams.y; mDownX = (int) event.getRawX(); mDownY = (int) event.getRawY(); break; case MotionEvent.ACTION_MOVE: int moveX = (int) event.getRawX(); int moveY = (int) event.getRawY(); int dx = moveX - mDownX; int dy = moveY - mDownY; mParams.x = mLastX + dx; mParams.y = mLastY + dy; mWindowManager.updateViewLayout(mFloatView, mParams); break; } return false; } }); // 监听点击事件 mFloatView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // 执行相应的操作 } }); } @Override public void onDestroy() { super.onDestroy(); mWindowManager.removeView(mFloatView); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值