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

时间:2018-12-10
这篇文章主要为大家详细介绍了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项目的下载地址: android实现悬浮窗

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。