1. 程式人生 > >Android——自由拖動並顯示文字的懸浮框實現

Android——自由拖動並顯示文字的懸浮框實現

專案中需要實現一個狀態顯示的懸浮框,要求可以設定兩種模式:拖動模式和不可拖動模式。

實現效果圖如下:


實現步驟:

1.首先要設定該懸浮框的基本屬性:

/**
     * 顯示彈出框
     *
     * @param context
     */
    @SuppressWarnings("WrongConstant")
    public static void showPopupWindow(final Context context, String showtxt) {
        if (isShown) {
            return;
        }
        isShown = true;
        // 獲取WindowManager
        mWindowManager = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        mView = setUpView(context, showtxt);

        params = new WindowManager.LayoutParams();
        // 型別,系統提示以及它總是出現在應用程式視窗之上。
        params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT |
                WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;

        // 設定flag
        int flags = canTouchFlags;

        // | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        // 如果設定了WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,彈出的View收不到Back鍵的事件
        params.flags = flags;
        // 不設定這個彈出框的透明遮罩顯示為黑色
        params.format = PixelFormat.TRANSLUCENT;
        // FLAG_NOT_TOUCH_MODAL不阻塞事件傳遞到後面的視窗
        // 設定 FLAG_NOT_FOCUSABLE 懸浮視窗較小時,後面的應用圖示由不可長按變為可長按
        // 不設定這個flag的話,home頁的劃屏會有問題
        params.width = LayoutParams.WRAP_CONTENT;
        params.height = LayoutParams.WRAP_CONTENT;
        params.gravity = Gravity.TOP;
        mWindowManager.addView(mView, params);
    }
比較重要的點是要注意設定flags,我這裡提供了兩種flags以供切換:
  private static int canTouchFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;

    private static int notTouchFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
            WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
第一種是可觸控不可聚焦模式,第二種是不可觸控不可聚焦模式。其他的flags可以從api中查閱。

2.設定懸浮框的拖動監聽事件:

 private static View setUpView(final Context context, String showtxt) {
        View view = LayoutInflater.from(context).inflate(R.layout.layout_popwindow,
                null);

        TextView showTv = (TextView) view.findViewById(R.id.tv_showinpop);
        showTv.setText(showtxt);

        rl_drag_showinpop = (RelativeLayout) view.findViewById(R.id.rl_drag_showinpop);
        rl_drag_showinpop.setOnTouchListener(new View.OnTouchListener() {
            private float lastX; //上一次位置的X.Y座標
            private float lastY;
            private float nowX;  //當前移動位置的X.Y座標
            private float nowY;
            private float tranX; //懸浮窗移動位置的相對值
            private float tranY;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                boolean ret = false;
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:

                        // 獲取按下時的X,Y座標
                        lastX = event.getRawX();
                        lastY = event.getRawY();
                        ret = true;

                        break;
                    case MotionEvent.ACTION_MOVE:
                        // 獲取移動時的X,Y座標
                        nowX = event.getRawX();
                        nowY = event.getRawY();
                        // 計算XY座標偏移量
                        tranX = nowX - lastX;
                        tranY = nowY - lastY;
                        params.x += tranX;
                        params.y += tranY;

                        //更新懸浮窗位置
                        mWindowManager.updateViewLayout(mView, params);
                        //記錄當前座標作為下一次計算的上一次移動的位置座標
                        lastX = nowX;
                        lastY = nowY;

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

這裡要在down的時候記錄座標,move事件中使用修改params座標進行移動。

3.設定懸浮框文字屬性:

    public static void setShowTxt(String txt) {
        try {
            TextView showTv = (TextView) mView.findViewById(R.id.tv_showinpop);
            showTv.setText(txt);
            mWindowManager.updateViewLayout(mView, params);
        }catch (Exception e){
            Log.d(TAG, "setShowTxt: 更新懸浮框錯誤");
            e.printStackTrace();
            if(e.getMessage().contains("not attached to window manager")){
                mWindowManager.addView(mView, params);
            }
        }
    }
4.更新懸浮框圖片顯示:
 public static void setShowImg(Bitmap bitmap) {
        try {
            ImageView showImg = (ImageView) mView.findViewById(R.id.iv_showinpop);
            showImg.setImageBitmap(bitmap);
            mWindowManager.updateViewLayout(mView, params);
        }catch (Exception e){
            Log.d(TAG, "setShowTxt: 更新懸浮框錯誤");
            e.printStackTrace();
            if(e.getMessage().contains("not attached to window manager")){
                mWindowManager.addView(mView, params);
            }
        }
    }



介紹完畢,整個類都封裝好了,程式碼如下:
/**
 * 懸浮窗工具類
 * created by Pumpkin at 17/3/28
 */
public class WindowsUitlity {
    private static String TAG = WindowsUitlity.class.getSimpleName();
    private static WindowManager mWindowManager = null;
    private static WindowManager.LayoutParams params;
    public static Boolean isShown = false;
    private static View mView = null;

    /**
     * 顯示彈出框
     *
     * @param context
     */
    @SuppressWarnings("WrongConstant")
    public static void showPopupWindow(final Context context, String showtxt) {
        if (isShown) {
            return;
        }
        isShown = true;
        // 獲取WindowManager
        mWindowManager = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        mView = setUpView(context, showtxt);

        params = new WindowManager.LayoutParams();
        // 型別,系統提示以及它總是出現在應用程式視窗之上。
        params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT |
                WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;

        // 設定flag
        int flags = canTouchFlags;

        // | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        // 如果設定了WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,彈出的View收不到Back鍵的事件
        params.flags = flags;
        // 不設定這個彈出框的透明遮罩顯示為黑色
        params.format = PixelFormat.TRANSLUCENT;
        // FLAG_NOT_TOUCH_MODAL不阻塞事件傳遞到後面的視窗
        // 設定 FLAG_NOT_FOCUSABLE 懸浮視窗較小時,後面的應用圖示由不可長按變為可長按
        // 不設定這個flag的話,home頁的劃屏會有問題
        params.width = LayoutParams.WRAP_CONTENT;
        params.height = LayoutParams.WRAP_CONTENT;
        params.gravity = Gravity.TOP;
        mWindowManager.addView(mView, params);
    }


    private static int canTouchFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;

    private static int notTouchFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
            WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;


    /**
     * 設定是否可響應點選事件
     *
     * @param isTouchable
     */
    public static void setTouchable(boolean isTouchable) {
        if (isTouchable) {
            params.flags = canTouchFlags;
        } else {
            params.flags = notTouchFlags;
        }
        mWindowManager.updateViewLayout(mView, params);

    }


    /**
     * 隱藏彈出框
     */
    public static void hidePopupWindow() {
        if (isShown && null != mView) {
            mWindowManager.removeView(mView);
            isShown = false;
        }
    }

    public static void setShowTxt(String txt) {
        try {
            TextView showTv = (TextView) mView.findViewById(R.id.tv_showinpop);
            showTv.setText(txt);
            mWindowManager.updateViewLayout(mView, params);
        }catch (Exception e){
            Log.d(TAG, "setShowTxt: 更新懸浮框錯誤");
            e.printStackTrace();
            if(e.getMessage().contains("not attached to window manager")){
                mWindowManager.addView(mView, params);
            }
        }
    }


    public static void setShowImg(Bitmap bitmap) {
        try {
            ImageView showImg = (ImageView) mView.findViewById(R.id.iv_showinpop);
            showImg.setImageBitmap(bitmap);
            mWindowManager.updateViewLayout(mView, params);
        }catch (Exception e){
            Log.d(TAG, "setShowTxt: 更新懸浮框錯誤");
            e.printStackTrace();
            if(e.getMessage().contains("not attached to window manager")){
                mWindowManager.addView(mView, params);
            }
        }
    }

    static RelativeLayout rl_drag_showinpop;

    private static View setUpView(final Context context, String showtxt) {
        View view = LayoutInflater.from(context).inflate(R.layout.layout_popwindow,
                null);

        TextView showTv = (TextView) view.findViewById(R.id.tv_showinpop);
        showTv.setText(showtxt);

        rl_drag_showinpop = (RelativeLayout) view.findViewById(R.id.rl_drag_showinpop);
        rl_drag_showinpop.setOnTouchListener(new View.OnTouchListener() {
            private float lastX; //上一次位置的X.Y座標
            private float lastY;
            private float nowX;  //當前移動位置的X.Y座標
            private float nowY;
            private float tranX; //懸浮窗移動位置的相對值
            private float tranY;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                boolean ret = false;
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:

                        // 獲取按下時的X,Y座標
                        lastX = event.getRawX();
                        lastY = event.getRawY();
                        ret = true;

                        break;
                    case MotionEvent.ACTION_MOVE:
                        // 獲取移動時的X,Y座標
                        nowX = event.getRawX();
                        nowY = event.getRawY();
                        // 計算XY座標偏移量
                        tranX = nowX - lastX;
                        tranY = nowY - lastY;
                        params.x += tranX;
                        params.y += tranY;

                        //更新懸浮窗位置
                        mWindowManager.updateViewLayout(mView, params);
                        //記錄當前座標作為下一次計算的上一次移動的位置座標
                        lastX = nowX;
                        lastY = nowY;

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

        return view;
    }
}