懸浮窗

一辱士、原理

1、添加獨立的View

我們在APP中想不依賴Activity中的布局添加View時残炮,可以通過WindowManager.addView()的方式蜈缤,創(chuàng)建一個window,并顯示添加的View呐萨。

2杀饵、Window可分為三類

應(yīng)用window:一般位于最底層,對應(yīng)一個Activity谬擦;
子window:不能單獨存在切距,需要附屬在父window上赞季,如Dialog忘伞;
系統(tǒng)window:一般位于最頂層,不會被其他window遮住疲酌,如Toast北秽。

二葡幸、懸浮窗

1、檢查權(quán)限
// 檢查懸浮窗
Settings.canDrawOverlays(mContext)

// 跳轉(zhuǎn)到懸浮窗設(shè)置頁面
activity.startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                Uri.parse("package:" + activity.getPackageName())), requestCode);

2贺氓、添加懸浮窗

此處需要注意的是flag對于懸浮窗的影響:
FLAG_NOT_TOUCH_MODAL: 懸浮窗外部可響應(yīng)事件
FLAG_NOT_FOCUSABLE : 不處理系統(tǒng)按鈕事件
FLAG_LAYOUT_NO_LIMITS: 懸浮窗可以延伸到屏幕外

if (Settings.canDrawOverlays(mContext)) {
    // 獲取WindowManager服務(wù)
    mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
    mLayoutParams = new WindowManager.LayoutParams();
    // 設(shè)置LayoutParam
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
    } else {
        mLayoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
    }
    // 懸浮窗外部可響應(yīng)事件
    mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
            // 不處理系統(tǒng)按鈕事件
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
            // 懸浮窗可以延伸到屏幕外
            WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
    mLayoutParams.format = PixelFormat.RGBA_8888;
    mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
    mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
    mLayoutParams.gravity = Gravity.TOP | Gravity.START;
    mLayoutParams.x = 0;
    mLayoutParams.y = 300;
    // 將懸浮窗控件添加到WindowManager
    mWindowManager.addView(mRootView, mLayoutParams);
}


// 移除懸浮窗
try {
    mWindowManager.removeView(mRootView);
} catch (Exception e) {
    //nothing
}
3蔚叨、響應(yīng)點擊事件
// 可以給view直接設(shè)置
mRootView.setOnClickListener(onClickListener);
4、拖動

首先辙培, 在ACTION_DOWN中記錄當(dāng)前的x蔑水,y位置;
然后扬蕊, 在ACTION_MOVE中判斷滑動距離搀别,如果觸發(fā)了滑動,算出滑動后的x尾抑,y位置歇父,通過updateViewLayout進行更新;
最后蛮穿, 在ACTION_UP中處理貼邊庶骄,點擊等邏輯。

// 拖動可以給view設(shè)置時間監(jiān)聽
if (canDrag) {
    mRootView.setOnTouchListener(new EFWindowOnTouchListener());
}

private class EFWindowOnTouchListener implements View.OnTouchListener {
        private boolean isMoved;
        private int oX;
        private int oY;
        private int x;
        private int y;

        @Override
        public boolean onTouch(View view, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    // 初始化滑動的標記
                    isMoved = false;
                    oX = (int) event.getRawX();
                    oY = (int) event.getRawY();
                    x = (int) event.getRawX();
                    y = (int) event.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    int nowX = (int) event.getRawX();
                    int nowY = (int) event.getRawY();
                    int movedX = nowX - x;
                    int movedY = nowY - y;
                    x = nowX;
                    y = nowY;

                    // 如果沒有觸發(fā)滑動践磅,則進行滑動判斷
                    if (!isMoved && (Math.abs(oX - x) > mMinScrollDistance || Math.abs(oY - y) > mMinScrollDistance)) {
                        isMoved = true;
                    }

                    // 拖動中不能用貼邊計算
                    mLayoutParams.x = getRealX(mLayoutParams.x + movedX, false, false);
                    mLayoutParams.y = getRealY(mLayoutParams.y + movedY, false);
                    mWindowManager.updateViewLayout(view, mLayoutParams);
                    break;
                case MotionEvent.ACTION_UP:
                    if (mIsSticky) {
                        int viewWidth = view.getMeasuredWidth();
                        int screenWidth = ScreenUtils.getScreenWidth(mContext);
                        // 貼邊終點只有左、右兩種狀態(tài)
                        if ((mLayoutParams.x + viewWidth / 2) > screenWidth / 2) {
                            if (mOnPositionListener != null) {
                                mOnPositionListener.onPositionChanged(OnPositionListener.POSITION_RIGHT);
                            }
                            mLayoutParams.x = screenWidth - viewWidth - mXEdgeSize;
                        } else {
                            if (mOnPositionListener != null) {
                                mOnPositionListener.onPositionChanged(OnPositionListener.POSITION_LEFT);
                            }
                            mLayoutParams.x = mXEdgeSize;
                        }
                        mWindowManager.updateViewLayout(view, mLayoutParams);
                    }

                    // 處理移動后還會響應(yīng)點擊事件的情況
                    if (isMoved) {
                        isMoved = false;
                        return true;
                    }
                    break;
                default:
                    break;
            }
            return false;
        }
    }
5灸异、橫豎屏切換適配

監(jiān)聽到屏幕變化后府适,由于屏幕的長寬發(fā)生了變化羔飞,重新計算當(dāng)前的x,y檐春,并進行更新逻淌。

// 添加屏幕變化監(jiān)聽
DisplayManager displayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
displayManager.registerDisplayListener(new DisplayManager.DisplayListener() {
    @Override
    public void onDisplayAdded(int displayId) {
    }
    @Override
    public void onDisplayRemoved(int displayId) {
    }
    @Override
    public void onDisplayChanged(int displayId) {
        // 監(jiān)聽到屏幕發(fā)生變化
        if (mLayoutParams != null && mWindowManager != null
                && mRootView != null && mRootView.isAttachedToWindow()) {
            mLayoutParams.x = getRealX(mLayoutParams.x, false, true);
            mLayoutParams.y = getRealY(mLayoutParams.y, false);
            mWindowManager.updateViewLayout(mRootView, mLayoutParams);
        }
    }
}, new Handler(Looper.getMainLooper()));

6、貼邊疟暖,縮進等處理
private int getRealX(int xPosition, boolean isInit, boolean considerSticky) {
    if (isInit) {
        // 此時view還沒有觸發(fā)測量卡儒,主動測量一次
        getRootView().measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
    }
    int viewWidth = getRootView().getMeasuredWidth();
    int screenWidth = ScreenUtils.getScreenWidth(mContext);
    int screenHeight = ScreenUtils.getScreenHeight(mContext);
    // 部分手機,如:vivo R9s俐巴,橫豎屏切換骨望,獲取的值不會發(fā)生變化,此處做一個適配
    screenWidth = isHorizontalScreen(mContext) ? Math.max(screenWidth, screenHeight) : Math.min(screenWidth, screenHeight);
    // 貼邊初始只有左和右欣舵,沒有中間態(tài)
    if (considerSticky && mIsSticky) {
        if ((xPosition + viewWidth / 2) > screenWidth / 2) {// 處理縮進
            if (mOnPositionListener != null) {
                mOnPositionListener.onPositionChanged(OnPositionListener.POSITION_RIGHT);
            }
            // 貼右邊顯示擎鸠,真實坐標為(屏幕寬度 - view的寬度 - x邊距)
            return screenWidth - viewWidth - mXEdgeSize;
        } else {
            if (mOnPositionListener != null) {
                mOnPositionListener.onPositionChanged(OnPositionListener.POSITION_LEFT);
            }
            // 貼左邊顯示,真實坐標為(左起點 + x邊距)缘圈,左邊的起點為0劣光。
            return mXEdgeSize;
        }
    } else {
        if (xPosition > (screenWidth - viewWidth - mXEdgeSize)) {
            if (mOnPositionListener != null) {
                mOnPositionListener.onPositionChanged(OnPositionListener.POSITION_RIGHT);
            }
            // 當(dāng)前位置已經(jīng)超過允許的右邊距,返回最右邊距
            return screenWidth - viewWidth - mXEdgeSize;
        } else if (xPosition < mXEdgeSize) {
            if (mOnPositionListener != null) {
                mOnPositionListener.onPositionChanged(OnPositionListener.POSITION_LEFT);
            }
            // 當(dāng)前位置小于允許的左邊距糟把,返回最左邊距
            return mXEdgeSize;
        } else {
            if (mOnPositionListener != null) {
                mOnPositionListener.onPositionChanged(OnPositionListener.POSITION_MIDDLE);
            }
            // 由于不吸邊绢涡,所以展示真實位置
            return xPosition;
        }
    }
}


private int getRealY(int yPosition, boolean isInit) {
    // 此時view還沒有觸發(fā)測量,主動測量一次
    if (isInit) {
        getRootView().measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
    }
    int viewHeight = getRootView().getMeasuredHeight();
    int screenWidth = ScreenUtils.getScreenWidth(mContext);
    int screenHeight = ScreenUtils.getScreenHeight(mContext);
    // 部分手機遣疯,如:vivo R9s垂寥,橫豎屏切換,獲取的值不會發(fā)生變化另锋,此處做一個適配
    screenHeight = isHorizontalScreen(mContext) ? Math.min(screenWidth, screenHeight) : Math.max(screenWidth, screenHeight);
    if (yPosition > (screenHeight - viewHeight - mYBottomEdgeSize)) {
        // 當(dāng)前位置已經(jīng)超過允許的下邊距滞项,返回最下邊距
        return screenHeight - viewHeight - mYBottomEdgeSize;
    } else if (yPosition < mYTopEdgeSize) {
        // 當(dāng)前位置小于允許的上邊距,返回最上邊距
        return mYTopEdgeSize;
    } else {
        // 展示真實位置
        return yPosition;
    }
}

private boolean isHorizontalScreen(Context context) {
    int angle = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
    //屏幕旋轉(zhuǎn)90°或270°夭坪,表示橫屏
    return angle == Surface.ROTATION_90 || angle == Surface.ROTATION_270;
}

三文判、注意

部分場景需要點擊回到當(dāng)前的Activity,可以參考如下代碼:

// 方法一:跳轉(zhuǎn)到app展示的上一個頁面
public void onClick(View v) {
    Intent intent = BaseApplication.getGlobalContext().getPackageManager().getLaunchIntentForPackage(BaseApplication.getGlobalContext().getPackageName());
    BaseApplication.getGlobalContext().startActivity(intent);
}

// 方法二:本地緩存頁面室梅,判斷登錄狀態(tài)跳轉(zhuǎn)不同的頁面
public void onClick(View v) {
    // 注意戏仓,這里的跳轉(zhuǎn)沒有參數(shù)傳遞,在一些activity的onNewIntent中會對intent進行再次解析
    // 因此亡鼠,需要判斷是否是從懸浮窗跳過去的赏殃,進行攔截,防止出現(xiàn)異常
    // 也可以將正確的參數(shù)傳遞過去间涵,成本較高仁热,用作備選項
    // FLAG_ACTIVITY_SINGLE_TOP,如果當(dāng)前activity已經(jīng)在棧頂勾哩,不再創(chuàng)建抗蠢,直接跳轉(zhuǎn)
    // FLAG_ACTIVITY_REORDER_TO_FRONT举哟,如果activity已經(jīng)創(chuàng)建了,如:ABCD,跳轉(zhuǎn)到B,不會重新創(chuàng)建,且變?yōu)锳CDB
    if (ACCOUNT_CONTROLLER.hasLogin()) {
        if (mCurrentActivity != null) {
            // 正常情況都不是null迅矛,除非長時間被系統(tǒng)回收了
            Intent targetIntent = new Intent(BaseApplication.getGlobalContext(), mCurrentActivity.getClass());
            targetIntent.putExtra(SpName.INTENT_FROM, FROM_EFWINDOW);
            targetIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
            mCurrentActivity.startActivity(targetIntent);
        } else {
            // 跳轉(zhuǎn)首頁妨猩,然后通過恢復(fù)接口進行再次跳轉(zhuǎn)
            Intent targetIntent = new Intent(BaseApplication.getGlobalContext(), HomeActivity.class);
            targetIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
            targetIntent.putExtra(SpName.INTENT_FROM, FROM_EFWINDOW);
            BaseApplication.getGlobalContext().startActivity(targetIntent);
        }
    } else {
        // 登錄信息失效了
        Intent targetIntent = new Intent(BaseApplication.getGlobalContext(), LoginActivity.class);
        targetIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
        targetIntent.putExtra(SpName.INTENT_FROM, FROM_EFWINDOW);
        BaseApplication.getGlobalContext().startActivity(targetIntent);
        ToastUtil.showMessage("登錄信息失效,請重新登錄");
    }
}

四秽褒、懸浮窗代碼

//try {
//    if (mEfWindow == null) {
//        View view = LayoutInflater.from(BaseApplication.getGlobalContext()).inflate(R.layout.layout_edj_floating_window, null);
//        mEfWindow = new EFWindow.Builder(BaseApplication.getGlobalContext())
//                .view(view)
//                .yPosition(ScreenUtils.getScreenHeight(BaseApplication.getGlobalContext()) / 3)
//                .isSticky(true)
//                .canDrag(true)
//                .xEdgeSize(-UIUtils.dp2px(BaseApplication.getGlobalContext(), 20))
//                .onClickListener(this)
//                .build();
//    }
//    mEfWindow.show();
//} catch (Exception e) {
//    e.printStackTrace();
//}
public class EFWindow {
    private final int mMinScrollDistance;
    private WindowManager mWindowManager;
    private WindowManager.LayoutParams mLayoutParams;
    private final View mRootView;
    private boolean mIsShow;
    private boolean mIsSticky;
    private int mXEdgeSize;
    private int mYTopEdgeSize;
    private int mYBottomEdgeSize;
    private OnPositionListener mOnPositionListener;
    private final Context mContext;

    @RequiresApi(api = Build.VERSION_CODES.M)
    private EFWindow(Builder builder) {
        // 賦值
        mContext = builder.mContext;
        mMinScrollDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
        mIsSticky = builder.mIsSticky;
        mXEdgeSize = builder.mXEdgeSize;
        mYTopEdgeSize = builder.mYTopEdgeSize;
        mYBottomEdgeSize = builder.mYBottomEdgeSize;
        mOnPositionListener = builder.mOnPositionListener;
        mRootView = builder.mRootView;
        boolean canDrag = builder.mCanDrag;
        int xPosition = builder.mXPosition;
        int yPosition = builder.mYPosition;
        View.OnClickListener onClickListener = builder.mOnClickListener;

        // 添加屏幕變化監(jiān)聽
        DisplayManager displayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
        displayManager.registerDisplayListener(new DisplayManager.DisplayListener() {
            @Override
            public void onDisplayAdded(int displayId) {

            }

            @Override
            public void onDisplayRemoved(int displayId) {

            }

            @Override
            public void onDisplayChanged(int displayId) {
                // 監(jiān)聽到屏幕發(fā)生變化
                if (mLayoutParams != null && mWindowManager != null
                        && mRootView != null && mRootView.isAttachedToWindow()) {
                    mLayoutParams.x = getRealX(mLayoutParams.x, false, true);
                    mLayoutParams.y = getRealY(mLayoutParams.y, false);
                    mWindowManager.updateViewLayout(mRootView, mLayoutParams);
                }
            }
        }, new Handler(Looper.getMainLooper()));

        if (Settings.canDrawOverlays(mContext)) {
            // 獲取WindowManager服務(wù)
            mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
            if (canDrag) {
                mRootView.setOnTouchListener(new EFWindowOnTouchListener());
            }
            mRootView.setOnClickListener(onClickListener);

            mLayoutParams = new WindowManager.LayoutParams();
            // 設(shè)置LayoutParam
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
            } else {
                mLayoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
            }
            // 懸浮窗外部可響應(yīng)事件
            mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
                    // 不處理系統(tǒng)按鈕事件
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
                    // 懸浮窗可以延伸到屏幕外
                    WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
            mLayoutParams.format = PixelFormat.RGBA_8888;
            mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
            mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
            mLayoutParams.gravity = Gravity.TOP | Gravity.START;
            mLayoutParams.x = getRealX(xPosition, true, true);
            mLayoutParams.y = getRealY(yPosition, true);
        }
    }

    private int getRealX(int xPosition, boolean isInit, boolean considerSticky) {
        if (isInit) {
            // 此時view還沒有觸發(fā)測量壶硅,主動測量一次
            getRootView().measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
        }
        int viewWidth = getRootView().getMeasuredWidth();
        int screenWidth = ScreenUtils.getScreenWidth(mContext);
        int screenHeight = ScreenUtils.getScreenHeight(mContext);

        // 部分手機,如:vivo R9s销斟,橫豎屏切換庐椒,獲取的值不會發(fā)生變化,此處做一個適配
        screenWidth = isHorizontalScreen(mContext) ? Math.max(screenWidth, screenHeight) : Math.min(screenWidth, screenHeight);

        // 貼邊初始只有左和右票堵,沒有中間態(tài)
        if (considerSticky && mIsSticky) {
            if ((xPosition + viewWidth / 2) > screenWidth / 2) {// 處理縮進
                if (mOnPositionListener != null) {
                    mOnPositionListener.onPositionChanged(OnPositionListener.POSITION_RIGHT);
                }
                // 貼右邊顯示扼睬,真實坐標為(屏幕寬度 - view的寬度 - x邊距)
                return screenWidth - viewWidth - mXEdgeSize;
            } else {
                if (mOnPositionListener != null) {
                    mOnPositionListener.onPositionChanged(OnPositionListener.POSITION_LEFT);
                }
                // 貼左邊顯示,真實坐標為(左起點 + x邊距)悴势,左邊的起點為0窗宇。
                return mXEdgeSize;
            }
        } else {
            if (xPosition > (screenWidth - viewWidth - mXEdgeSize)) {
                if (mOnPositionListener != null) {
                    mOnPositionListener.onPositionChanged(OnPositionListener.POSITION_RIGHT);
                }
                // 當(dāng)前位置已經(jīng)超過允許的右邊距,返回最右邊距
                return screenWidth - viewWidth - mXEdgeSize;
            } else if (xPosition < mXEdgeSize) {
                if (mOnPositionListener != null) {
                    mOnPositionListener.onPositionChanged(OnPositionListener.POSITION_LEFT);
                }
                // 當(dāng)前位置小于允許的左邊距特纤,返回最左邊距
                return mXEdgeSize;
            } else {
                if (mOnPositionListener != null) {
                    mOnPositionListener.onPositionChanged(OnPositionListener.POSITION_MIDDLE);
                }
                // 由于不吸邊军俊,所以展示真實位置
                return xPosition;
            }
        }
    }

    private int getRealY(int yPosition, boolean isInit) {
        // 此時view還沒有觸發(fā)測量,主動測量一次
        if (isInit) {
            getRootView().measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
        }
        int viewHeight = getRootView().getMeasuredHeight();
        int screenWidth = ScreenUtils.getScreenWidth(mContext);
        int screenHeight = ScreenUtils.getScreenHeight(mContext);

        // 部分手機捧存,如:vivo R9s粪躬,橫豎屏切換,獲取的值不會發(fā)生變化昔穴,此處做一個適配
        screenHeight = isHorizontalScreen(mContext) ? Math.min(screenWidth, screenHeight) : Math.max(screenWidth, screenHeight);

        if (yPosition > (screenHeight - viewHeight - mYBottomEdgeSize)) {
            // 當(dāng)前位置已經(jīng)超過允許的下邊距镰官,返回最下邊距
            return screenHeight - viewHeight - mYBottomEdgeSize;
        } else if (yPosition < mYTopEdgeSize) {
            // 當(dāng)前位置小于允許的上邊距,返回最上邊距
            return mYTopEdgeSize;
        } else {
            // 展示真實位置
            return yPosition;
        }
    }

    public View getRootView() {
        return mRootView;
    }

    public boolean isShow() {
        return mIsShow;
    }

    public void show() {
        mIsShow = true;

        // 先嘗試移除
        try {
            mLayoutParams.x = getRealX(mLayoutParams.x, false, true);
            mLayoutParams.y = getRealY(mLayoutParams.y, false);
            mWindowManager.removeView(mRootView);
        } catch (Exception e) {
            //nothing
        }
        // 將懸浮窗控件添加到WindowManager
        mWindowManager.addView(mRootView, mLayoutParams);
    }

    public void dismiss() {
        // 嘗試移除
        try {
            mWindowManager.removeView(mRootView);
        } catch (Exception e) {
            //nothing
        }
        mIsShow = false;
    }


    private class EFWindowOnTouchListener implements View.OnTouchListener {
        private boolean isMoved;
        private int oX;
        private int oY;
        private int x;
        private int y;

        @Override
        public boolean onTouch(View view, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    // 初始化滑動的標記
                    isMoved = false;
                    oX = (int) event.getRawX();
                    oY = (int) event.getRawY();
                    x = (int) event.getRawX();
                    y = (int) event.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    int nowX = (int) event.getRawX();
                    int nowY = (int) event.getRawY();
                    int movedX = nowX - x;
                    int movedY = nowY - y;
                    x = nowX;
                    y = nowY;

                    // 如果沒有觸發(fā)滑動吗货,則進行滑動判斷
                    if (!isMoved && (Math.abs(oX - x) > mMinScrollDistance || Math.abs(oY - y) > mMinScrollDistance)) {
                        isMoved = true;
                    }

                    // 拖動中不能用貼邊計算
                    mLayoutParams.x = getRealX(mLayoutParams.x + movedX, false, false);
                    mLayoutParams.y = getRealY(mLayoutParams.y + movedY, false);
                    mWindowManager.updateViewLayout(view, mLayoutParams);
                    break;
                case MotionEvent.ACTION_UP:
                    if (mIsSticky) {
                        int viewWidth = view.getMeasuredWidth();
                        int screenWidth = ScreenUtils.getScreenWidth(mContext);
                        // 貼邊終點只有左泳唠、右兩種狀態(tài)
                        if ((mLayoutParams.x + viewWidth / 2) > screenWidth / 2) {
                            if (mOnPositionListener != null) {
                                mOnPositionListener.onPositionChanged(OnPositionListener.POSITION_RIGHT);
                            }
                            mLayoutParams.x = screenWidth - viewWidth - mXEdgeSize;
                        } else {
                            if (mOnPositionListener != null) {
                                mOnPositionListener.onPositionChanged(OnPositionListener.POSITION_LEFT);
                            }
                            mLayoutParams.x = mXEdgeSize;
                        }
                        mWindowManager.updateViewLayout(view, mLayoutParams);
                    }

                    // 處理移動后還會響應(yīng)點擊事件的情況
                    if (isMoved) {
                        isMoved = false;
                        return true;
                    }
                    break;
                default:
                    break;
            }
            return false;
        }
    }

    public static class Builder {
        private final Context mContext;
        private View mRootView;
        private int mXPosition;
        private int mYPosition;
        private boolean mCanDrag;
        private boolean mIsSticky;
        private int mXEdgeSize;
        private int mYTopEdgeSize;
        private int mYBottomEdgeSize;
        private View.OnClickListener mOnClickListener;
        private OnPositionListener mOnPositionListener;

        public Builder(Context context) {
            mContext = context;
        }

        public Builder onPositionListener(OnPositionListener onPositionListener) {
            mOnPositionListener = onPositionListener;
            return this;
        }

        public Builder onClickListener(View.OnClickListener onClickListener) {
            mOnClickListener = onClickListener;
            return this;
        }

        public Builder xPosition(int xPosition) {
            mXPosition = xPosition;
            return this;
        }

        public Builder yPosition(int yPosition) {
            mYPosition = yPosition;
            return this;
        }

        public Builder view(View view) {
            mRootView = view;
            return this;
        }

        public Builder canDrag(boolean canDrag) {
            mCanDrag = canDrag;
            return this;
        }

        public Builder isSticky(boolean isSticky) {
            mIsSticky = isSticky;
            return this;
        }

        public Builder xEdgeSize(int xEdgeSize) {
            mXEdgeSize = xEdgeSize;
            return this;
        }

        public Builder yTopEdgeSize(int yTopEdgeSize) {
            mYTopEdgeSize = yTopEdgeSize;
            return this;
        }

        public Builder yBottomEdgeSize(int yBottomEdgeSize) {
            mYBottomEdgeSize = yBottomEdgeSize;
            return this;
        }

        public Builder view(int viewRes) throws Exception {
            if (mContext == null) {
                throw new Exception("EFWindow:請傳入正確的context.");
            }
            mRootView = LayoutInflater.from(mContext).inflate(viewRes, null);
            return this;
        }

        @RequiresApi(api = Build.VERSION_CODES.M)
        public EFWindow build() throws Exception {
            if (mContext == null) {
                throw new Exception("EFWindow:請傳入正確的context.");
            }
            if (mRootView == null) {
                throw new Exception("EFWindow:請先設(shè)置Window布局.");
            }
            return new EFWindow(this);
        }

    }

    public interface OnPositionListener {
        int POSITION_LEFT = 0;
        int POSITION_MIDDLE = 1;
        int POSITION_RIGHT = 2;

        void onPositionChanged(int positionStatus);
    }

    private boolean isHorizontalScreen(Context context) {
        int angle = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
        //屏幕旋轉(zhuǎn)90°或270°,表示橫屏
        return angle == Surface.ROTATION_90 || angle == Surface.ROTATION_270;
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宙搬,一起剝皮案震驚了整個濱河市笨腥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌勇垛,老刑警劉巖脖母,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異闲孤,居然都是意外死亡谆级,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哨苛,“玉大人鸽凶,你說我怎么就攤上這事币砂〗ㄇ停” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵决摧,是天一觀的道長亿蒸。 經(jīng)常有香客問我,道長掌桩,這世上最難降的妖魔是什么边锁? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮波岛,結(jié)果婚禮上茅坛,老公的妹妹穿的比我還像新娘。我一直安慰自己则拷,他們只是感情好贡蓖,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著煌茬,像睡著了一般斥铺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上坛善,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天晾蜘,我揣著相機與錄音,去河邊找鬼眠屎。 笑死剔交,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的改衩。 我是一名探鬼主播岖常,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼燎字!你這毒婦竟也來了腥椒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤候衍,失蹤者是張志新(化名)和其女友劉穎笼蛛,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蛉鹿,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡滨砍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惋戏。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡领追,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出响逢,到底是詐尸還是另有隱情绒窑,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布舔亭,位于F島的核電站些膨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏钦铺。R本人自食惡果不足惜订雾,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望矛洞。 院中可真熱鬧洼哎,春花似錦、人聲如沸沼本。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽擅威。三九已至壕探,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間郊丛,已是汗流浹背李请。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留厉熟,地道東北人导盅。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像揍瑟,于是被迫代替她去往敵國和親白翻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容