Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android之項目級懸浮窗開發教程

Android之項目級懸浮窗開發教程

編輯:關於Android編程

在我們玩手機游戲時能看到,很多游戲的登錄界面兩側往往會有一個小小的懸浮窗,可以提供相應功能菜單項,簡潔實用且不影響游戲體驗。具體效果如下圖所示。這篇博客將帶大家開發一個可以直接用在項目中的懸浮窗實例。

\
\
\

一、需實現的功能分析

1.懸浮窗可在屏幕范圍內自由拖動;

2.懸浮窗拖動到屏幕中任意位置後放開手指,它會向近的一側移動並停靠;

3.停靠在屏幕兩側時,經過一定時間後能自動切換成半隱藏狀態;

4.點擊半隱藏狀態的懸浮窗,顯示懸浮窗,再次點擊則顯示懸浮窗側拉菜單。

 

二、開發所涉及知識點

1.WindowManager懸浮窗控制類

2.點擊觸摸事件處理

3.簡單的多線程編程

4.PopupWindow

 

三、開發實現過程

1.實現應用內簡單固定的懸浮窗

(1)實現懸浮窗布局

我通過自定義一個擴展自LinearLayout的類來實現懸浮窗

 

public class MainFloatWindow extends LinearLayout

 

懸浮窗的布局很簡單,直接就是兩張圖片,對應兩種狀態(半隱藏和顯示),所以我直接用Java代碼實現(資源在我的源碼中有)

 

private void initWindowView(){
        ivDefaultWindow = new ImageView(mCtx);
        this.ivDefaultWindow.setImageResource(ResourceUtil.getDrawableId(mCtx, "uac_gameassist_photo_default"));
        this.ivDefaultWindow.setBackgroundResource(ResourceUtil.getDrawableId(mCtx, "uac_gameassist_photo_bg"));
        ivHideWindow = new ImageView(mCtx);
        ivHideWindow.setImageResource(ResourceUtil.getDrawableId(mCtx, "uac_gameassist_photo_brand_left"));
        //顯示ivDefaultWindow,隱藏ivHideWindow
        setWindowHide(false);
    }

 

然後在MainFloatWindow的構造函數中把這些初始化的類addview到MainFloatWindow,從而實現布局

       this.addView(ivDefaultWindow);
       this.addView(ivHideWindow);

 

 

(2)通過WindowManager讓它顯示出來

懸浮窗權限

 

 

初始化WindowManager的參數

 

WindowManager mWindowManager = (WindowManager) mCtx.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.type = WindowManager.LayoutParams.TYPE_PHONE;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.format = PixelFormat.TRANSLUCENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.gravity = Gravity.LEFT |Gravity.TOP;
//懸浮窗初始位置
params.y = WindowUtil.getScreenHeight(mCtx)/2;

 

把之前實現的MainFloatWindow作為懸浮窗視圖,WindowManager控制顯示隱藏。其實就是簡單的addView和removeView方法。

 

public void showFloatWindow(){
        if(isShow){
            return;
        }
        mWindowManager.addView(mainFloatWindow,params);
        isShow = true;
    }

    public void hideFloatWindow(){
        if(!isShow){
            return;
        }
        mWindowManager.removeView(mainFloatWindow);
        isShow = false;
    }

 

 

至此,我們就可以在屏幕中看到一個固定不動的懸浮窗了。

 

 

2.讓懸浮窗動起來(實現拖動和點擊)

要實現懸浮窗隨手指進行拖動,從代碼角度理解,就是觸摸事件位置的改變,實時更新懸浮窗視圖的位置。

 

(1)更新懸浮窗位置用到了WindowManager的updateViewLayout方法,通過改變傳入的params實例的x、y參數,實現位置改變。

 

mParams.x = (int) x;
mParams.y = (int) y;
mWindowManager.updateViewLayout(MainFloatWindow.this, mParams);

 

 

(2)重寫onTouchEvent()方法,對監聽的動作進行處理

這裡需要講個注意的地方,MotionEvent.getX()是獲得相對當前觸摸的視圖的x左邊,而MotionEvent.getRawX()是獲得相對屏幕的x坐標。

 

    /**相對於View的x、y坐標 **/
    private float xInView, yInView;
    /**在屏幕中的x、y坐標 **/
    private float xDown, yDown;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                xInView = event.getX();
                yInView = event.getY();
                xDown = event.getRawX();
                yDown = event.getRawY() - WindowUtil.getStatusBarHeight(mCtx);
                break;

            case MotionEvent.ACTION_MOVE:
                //更新懸浮窗位置
                Message message = new Message();
                message.what = MSG_UPDATE_POS;
                message.arg1 = (int) (event.getRawX() - xInView);
                message.arg2 = (int) (event.getRawY() - WindowUtil.getStatusBarHeight(mCtx));
                mHandler.sendMessage(message);

                break;

            case MotionEvent.ACTION_UP:
                if(xDown == event.getRawX() && yDown == event.getRawY() - WindowUtil.getStatusBarHeight(mCtx)){
                    //點擊事件
                    onClick();
                }
                break;
        }

        return true;
    }

 

因為重寫的onTouchEvent()返回true,所以會覆蓋了該視圖的點擊監聽事件(具體原因不在此贅述,可以看Android觸摸事件處理機制),我們需要自行判斷動作監聽點擊事件。

 

3.實現懸浮窗自動平移到兩側

 

    /**
     * 自動平移到兩側停靠
     */
    private void autoMoveToSide() {

        new Thread() {
            @Override
            public void run() {
                //保存x、y坐標
                int[] location = new int[2];
                getLocationOnScreen(location);
                isOnLeft = location[0]+getWidth()/2 < WindowUtil.getScreenWidth(mCtx)/2;
                mSubFloatWindow.setOnLeft(isOnLeft);
                int moveParam = isOnLeft ? -10 : 10;    //每次移動10個像素
                while (true) {
                    location[0] = location[0] + moveParam;
                    int newX = location[0];
                    int newY = location[1];
                    if (isOnLeft && newX<=0) {     //已移至最左側
                        newX = 0;
                        Message message = new Message();
                        message.what = MSG_UPDATE_POS;
                        message.arg1 = newX;
                        message.arg2 = newY;
                        mHandler.sendMessage(message);
                        break;
                    }else if(!isOnLeft && newX>=WindowUtil.getScreenWidth(mCtx)){     //已移至最右側
                        newX = WindowUtil.getScreenWidth(mCtx);
                        Message message = new Message();
                        message.what = MSG_UPDATE_POS;
                        message.arg1 = newX;
                        message.arg2 = newY;
                        mHandler.sendMessage(message);
                        break;
                    } else {
                        Message message = new Message();
                        message.what = MSG_UPDATE_POS;
                        message.arg1 = newX;
                        message.arg2 = newY;
                        mHandler.sendMessage(message);
                    }

                    try {
                        Thread.sleep(100/ SPEED);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }
開啟一條子線程計算自動平移時新狀態的位置,並通過Thread.sleep()模擬每隔一段時間移動一段距離的。

 

 

4.半隱藏狀態的切換

當懸浮窗停靠在屏幕兩側時,隔一段時間沒有進行操作時,需要自動切換成半隱藏狀態。半隱藏狀態不能被拖動,點擊此狀態下的懸浮窗,可以顯示出完整的懸浮窗。

實現此功能需要為懸浮窗添加兩個狀態判斷標識,第一個是懸浮窗能否隱藏標識,當懸浮窗菜單打開時不能隱藏,當懸浮窗被拖動時不能隱藏;第二個是懸浮窗是否處於隱藏狀態,當處於隱藏狀態時,保證懸浮窗不能被拖動。

 

    /**
     * 等待一定時間後隱藏懸浮窗
     */
    private void waitToHideWindow(){
        if(!canHide){
            return;
        }
        new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep(WAIT_TIME);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(canHide) {
                    mHandler.sendEmptyMessage(MSG_WINDOW_HIDE);
                }
            }
        }.start();
    }
此處在子線程啟動前判斷能否隱藏,然後在線程運行時再判斷一次能否隱藏,做雙重鎖,是為了防止線程睡眠前懸浮窗可以隱藏,但等線程睡眠結束後處於不能隱藏的狀態,卻把懸浮窗切換成隱藏狀態了。舉一個實際情況,當線程停靠在兩側,應用調用waitToHideWindow()方法等待一段時候後隱藏懸浮窗,但等待過程中用戶拖動了懸浮窗,等待結束後還處於被拖動狀態,這時按需求是不應該隱藏懸浮窗的,故需要對狀態加以判斷。

 

 

5.實現懸浮窗的子菜單

子菜單我是采用PopupWindow來做的,原因是想到了它可以在顯示在父視圖的特定位置,懸浮窗和菜單分開處理,便於操作。

 

    public SubFloatWindow(Context context){
        mCtx = context;
        setContentView(getWindowView());
        setWidth(WindowUtil.dip2px(mCtx, 220));
        setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
        setFocusable(true);
        setOutsideTouchable(true);
        // 實例化一個ColorDrawable顏色為半透明
        ColorDrawable dw = new ColorDrawable(0000000000);
        // 點back鍵和其他地方使其消失,設置了這個才能觸發OnDismisslistener ,設置其他控件變化等操作
        this.setBackgroundDrawable(dw);

    }
PopupWindow的初始化很簡單,設置幾個基本屬性即好。

 

 

         if (isOnLeft) {
             mSubFloatWindow.showAtLocation(this, Gravity.CENTER | Gravity.START, getWidth(), 0);
             ivDefaultWindow.setBackgroundResource(ResourceUtil.getDrawableId(mCtx, "uac_gameassist_photo_right_bg"));
         } else {
             mSubFloatWindow.showAtLocation(this, Gravity.CENTER | Gravity.END, getWidth(), 0);
             ivDefaultWindow.setBackgroundResource(ResourceUtil.getDrawableId(mCtx, "uac_gameassist_photo_left_bg"));
         }
子菜單的顯示,需要用到該方法
public void showAtLocation(android.view.View parent,
                           int gravity,
                           int x,
                           int y)
需要注意的是在左側時,gravity參數要為
Gravity.CENTER | Gravity.START
在右側時,則是
Gravity.CENTER | Gravity.END
對齊方式不同,子菜單的位置也會有所改變。

至此,懸浮窗的關鍵實現都基本寫完,如有寫得不好的地方,歡迎指教~

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved