1. 程式人生 > >Android桌面懸浮視窗舉例

Android桌面懸浮視窗舉例

概述

Android專案開發時,有時候需要開發一些懸浮在桌面上的檢視。比如桌面小精靈,各種音樂播放器的懸浮播放控制欄等等。本文就藉助一個小的demo,用程式碼的方式大概進行介紹。
桌面懸浮視窗

原理

開發桌面懸浮視窗一般遵循兩個大的原則。
1.最根本的原則是採用WindowManager類,WindowManager有addView(View view, ViewGroup.LayoutParams params)方法,改方法可以直接在Android根窗口布局中新增一個View檢視。這裡的view其實就是我們要在桌面上顯示的檢視。
同理,如果我們要讓這個檢視消失掉,就可以呼叫WindowManager的removeView(View view)方法。
2.另外一個原則是,需要藉助service。因為service是沒有介面的,如果在activity中進行的話,當前的介面會擋住這個懸浮框。
好了,話不多說直接上程式碼。

關鍵程式碼展示

需要展示的View檢視的佈局layout_float_window.xml

<?xml version="1.0" encoding="UTF-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/small_window_layout"
    android:layout_width="match_parent"
    android:layout_height="80dp"
    android:background
="#f4f2f3" android:paddingLeft="@dimen/margin_20" android:paddingRight="@dimen/margin_20">
<ImageView android:layout_width="@dimen/margin_60" android:layout_height="@dimen/margin_60" android:layout_gravity="center_vertical" android:src="@drawable/ic_microphone_white"
/>
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginLeft="70dp" android:layout_marginRight="50dp" android:gravity="center_vertical" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:lineSpacingExtra="0sp" android:text="咪咕家語音播報" android:textColor="#282e33" android:textSize="17sp" /> <TextView android:id="@+id/percent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="7dp" android:lineSpacingExtra="0sp" android:text="IM訊息" android:textColor="#66737f" android:textSize="14sp" /> </LinearLayout> <ImageView android:id="@+id/iv_close_notify" android:layout_width="40dp" android:layout_height="40dp" android:layout_gravity="center_vertical|right" android:scaleType="center" android:src="@drawable/ic_close_grey1" /> </FrameLayout>

需要展示的View檢視類FloatWindow.java

public class FloatWindow extends LinearLayout {

    public static int viewWidth;  //懸浮窗的寬度
    public static int viewHeight; //懸浮窗的高度
    private static int statusBarHeight; //系統狀態列的高度
    private WindowManager windowManager; //用於更新懸浮窗的位置
    private WindowManager.LayoutParams mParams; //懸浮窗的引數
    private float xInScreen; //當前手指位置在螢幕上的橫座標值
    private float yInScreen; //當前手指位置在螢幕上的縱座標值
    private float xDownInScreen; //手指按下時在螢幕上的橫座標的值
    private float yDownInScreen; //手指按下時在螢幕上的縱座標的值
    private float xInView; //手指按下時在懸浮窗的View上的橫座標的值
    private float yInView;//手指按下時在懸浮窗的View上的縱座標的值

    private TextView tittleView;  //標題
    private TextView contentView; //內容
    private ImageView closeView;  //關閉按鈕

    public FloatWindow(final Context context) {
        super(context);
        windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        LayoutInflater.from(context).inflate(R.layout.layout_float_window, this);
        View view = findViewById(R.id.small_window_layout);
        viewWidth = view.getLayoutParams().width;
        viewHeight = view.getLayoutParams().height;
        contentView = (TextView) findViewById(R.id.percent);
        contentView.setText("IM訊息");
        closeView = (ImageView) findViewById(R.id.iv_close_notify);
        closeView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                FloatWindowManager.removeFloatWindow(context);
                Intent intent = new Intent(getContext(), VoiceBroadcastService.class);
                context.stopService(intent);
            }
        });
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 手指按下時記錄必要資料,縱座標的值都需要減去狀態列高度
                xInView = event.getX();
                yInView = event.getY();
                xDownInScreen = event.getRawX();
                yDownInScreen = event.getRawY() - getStatusBarHeight();
                xInScreen = event.getRawX();
                yInScreen = event.getRawY() - getStatusBarHeight();
                break;
            case MotionEvent.ACTION_MOVE:
                xInScreen = event.getRawX();
                yInScreen = event.getRawY() - getStatusBarHeight();
                updateViewPosition();
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
        }
        return true;
    }

    /**
     * 將懸浮窗的引數傳入,用於更新懸浮窗的位置。
     *
     * @param params 懸浮窗的引數
     */
    public void setParams(WindowManager.LayoutParams params) {
        mParams = params;
    }

    /**
     * 更新懸浮窗在螢幕中的位置(手指拖動)
     */
    private void updateViewPosition() {
        mParams.x = (int) (xInScreen - xInView);
        mParams.y = (int) (yInScreen - yInView);
        windowManager.updateViewLayout(this, mParams);
    }

    /**
     * 用於獲取狀態列的高度。
     *
     * @return 返回狀態列高度的畫素值。
     */
    private int getStatusBarHeight() {
        if (statusBarHeight == 0) {
            try {
                Class<?> c = Class.forName("com.android.internal.R$dimen");
                Object o = c.newInstance();
                Field field = c.getField("status_bar_height");
                int x = (Integer) field.get(o);
                statusBarHeight = getResources().getDimensionPixelSize(x);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return statusBarHeight;
    }

}

Windowmanager管理類,該類是懸浮視窗的主要操作類。可以在桌面新增和刪除視窗,並設定視窗必要的顯示引數。FloatWindowManager.java

public class FloatWindowManager {

    private static FloatWindow mFloatWindow;  //懸浮窗View的例項
    private static LayoutParams mWindowLayoutParams; //懸浮窗View的引數
    private static WindowManager mWindowManager; //用於控制在螢幕上新增或移除懸浮窗

    /**
     * 建立一個懸浮窗
     * 初始位置為螢幕的右部中間位置。
     * @param context 必須為應用程式的Context.
     */
    public static void createFloatWindow(Context context) {
        WindowManager windowManager = getWindowManager(context);
        int screenWidth = windowManager.getDefaultDisplay().getWidth();
        int screenHeight = windowManager.getDefaultDisplay().getHeight();
        if (mFloatWindow == null) {
            mFloatWindow = new FloatWindow(context);
            if (mWindowLayoutParams == null) {
                mWindowLayoutParams = new LayoutParams();
                mWindowLayoutParams.type = LayoutParams.TYPE_PHONE;
                mWindowLayoutParams.format = PixelFormat.RGBA_8888;
                mWindowLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE;
                mWindowLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
                mWindowLayoutParams.width = FloatWindow.viewWidth;
                mWindowLayoutParams.height = FloatWindow.viewHeight;
                mWindowLayoutParams.x = screenWidth;
                mWindowLayoutParams.y = screenHeight / 2;
            }
            mFloatWindow.setParams(mWindowLayoutParams);
            //最為關鍵的一步操作,該操作之後就可以在桌面上看到檢視
            windowManager.addView(mFloatWindow, mWindowLayoutParams); 
        }
    }

    /**
     * 將懸浮窗從螢幕上移除。
     *
     * @param context 必須為應用程式的Context.
     */
    public static void removeFloatWindow(Context context) {
        if (mFloatWindow != null) {
            WindowManager windowManager = getWindowManager(context);
            windowManager.removeView(mFloatWindow);
            mFloatWindow = null;
        }
    }

    /**
     * 更新懸浮窗文字顯示
     * @param context
     * @param tittle
     * @param content
     */
    public static void updateFloatShow(Context context,String tittle,String content) {
        if (mFloatWindow != null) {
            TextView percentView = (TextView) mFloatWindow.findViewById(R.id.percent);
            percentView.setText("IM訊息");
        }
    }

    public static boolean isWindowShowing() {
        return mFloatWindow != null;
    }

    private static WindowManager getWindowManager(Context context) {
        if (mWindowManager == null) {
            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        }
        return mWindowManager;
    }

}

具體測操作service類FloatWindowService.class

public class FloatWindowService extends Service {

    private Timer mTimer;
    private Handler mHandler = new Handler();

    @Override
    public void onCreate() {
        super.onCreate();
        if (mTimer == null) {
            mTimer = new Timer();
            mTimer.scheduleAtFixedRate(new RefreshTask(), 0, 1000);
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (mTimer == null) {
            mTimer = new Timer();
            mTimer.scheduleAtFixedRate(new RefreshTask(), 0, 1000);
        }
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        mTimer.cancel();
        mTimer = null;
        super.onDestroy();
    }

    class RefreshTask extends TimerTask {

        /**
         * 當前介面是桌面,且沒有懸浮窗顯示,則建立懸浮窗
         * 當前介面不是桌面,且有懸浮窗顯示,則移除懸浮窗
         * 當前介面是桌面,且有懸浮窗顯示,設定懸浮窗內容
         */
        @Override
        public void run() {
            if (WindowUtils.getInstance().isAtHome(getApplicationContext()) && !FloatWindowManager.isWindowShowing()) {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        FloatWindowManager.createFloatWindow(getApplicationContext());
                    }
                });
            } else if (!WindowUtils.getInstance().isAtHome(getApplicationContext()) && FloatWindowManager.isWindowShowing()) {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        FloatWindowManager.removeFloatWindow(getApplicationContext());
                    }
                });
            }
        }
    }
}

如果要開啟android懸浮視窗就啟用以上service即可。大致如下

Intent intent = new Intent(....this, FloatWindow.class);
        VoiceBroadcastService.voiceUrl = voicePath;
        startService(intent);

以上就是android桌面懸浮視窗的大致操作,希望能給大家帶來幫助。另外,這裡小小留一個懸念,就是FloatWindowService類中的isAtHome方法。這是判斷當前介面是否為桌面的方法,需要的朋友可以訪問Android判斷當前介面是否為桌面進行檢視。

另外需要特別注意的是WindowManager如果要呼叫.addView方法,需要為應用新增ACTION_MANAGE_OVERLAY_PERMISSION許可權。否則系統會閃退,請各位格外注意。具體的新增方式這裡不再多說了。