1. 程式人生 > >android實現可自由移動、監聽點選事件的懸浮窗

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());
    }
}