天天看點

Floating Window 詳解

一,原理

1,全局懸浮

floating view可以懸浮在應用的各個頁面。floating view是放在一個單獨的window中。 對于每個app而言,它所在的window在floating view所在的window之下,這樣,就可以懸浮在其至上。window可以設定相應的層級。比如,通知欄,就是在一個級别很高的window中。如果想要清晰的看清楚相應的結構,可以通過hierarchyviewer的工具,看view的層級關系。

2,全局移動

通過1,全局移動的關鍵其實是更改window的位置。關鍵是坐标的計算。一個坐标是絕對坐标,一個相對坐标。參看示意圖,了解以下的計算公式即可。

Floating Window 詳解

private void updateViewPosition() {
        mWindowLayoutParams.x = (int) (mRawX - mTouchStartX);
        mWindowLayoutParams.y = (int) (mRawY - mTouchStartY);
        mWindowManager.updateViewLayout(this, mWindowLayoutParams);
    }      

3,運作狀态監控。

floating view所在的顯示層級高于app,是以如何控制其顯示或者消失,比如類似360手機助手的顯示機制。我們關注最多的有四種狀态:

3-1 home(通過所有的launcher相應的包名篩選)

2-2 應用内 (通過指定包名)

3-3 應用某個特定頁面 (通過指定的完整類名)

3-3 其他狀态(其他應用,etc)(排除之後,剩下的情況)

是以,其實對于狀态的監控,就是對目前運作的task進行檢測判斷的過程。相應的代碼如下,很清晰,參照文檔看。

private boolean isInner() {
        boolean isInner = false;
        ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        List<RunningTaskInfo> info = manager
                .getRunningTasks(Integer.MAX_VALUE);
        String pkgName = info.get(0).topActivity.getPackageName();
        isInner = pkgName.startsWith(PKG_NAME_BASE);
        Log.d(TAG, "isInner() isInner = " + isInner);
        return isInner;
    }
    private boolean isHome() {
        boolean isHome = false;
        ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        List<RunningTaskInfo> info = manager
                .getRunningTasks(Integer.MAX_VALUE);
        isHome = homeLists.contains(info.get(0).topActivity.getPackageName());
        Log.d(TAG, "isHome() isHome = " + isHome);
        return isHome;
    }
    private List<String> getHomes() {
        List<String> packages = new ArrayList<String>();
        PackageManager packageManager = mServices.getPackageManager();
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        List<ResolveInfo> resolveInfo = packageManager
                .queryIntentActivities(intent,
                        PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo info : resolveInfo) {
            packages.add(info.activityInfo.packageName);
        }
        return packages;
    }      

二,注意事項

此部分,将列舉出開發中所遇到的問題。

2-1 window的顯示大小

假使window中添加的view為mContentView,影響window大小的最終參數隻有:

mWindowLayoutParams.width = LinearLayout.LayoutParams.FILL_PARENT;
mWindowLayoutParams.height = LinearLayout.LayoutParams.WRAP_CONTENT;      

2-2 window的顯示位置

定了坐标系之後,以下兩個參數決定window的顯示位置(以mContentView左上角為準)

mWindowLayoutParams.x = 0;
mWindowLayoutParams.y = mScreenHeight;// at the position of bottom      

2-3 TouchEvent 和ClickEvent的沖突處理

核心是定義touch事件,滑動超過指定值時,才被識别為touch事件,否則則識别為click事件。這裡需要深入的了解touch事件。

參看之前的日志。《Android Touch事件分析》

http://mikewang.blog.51cto.com/3826268/1204944

2-4 Service導緻的Asynctask不能執行的問題。

在網上沒有找到真正的原因,但是找到了解決方案。api level 11之後,Asynctask的預設模式從并行改為串行。即預設情況下,如果前一個task沒有執行完,後一個task将會被阻塞。

可以通過手動設定Asynctask的模式來解決這個問題。

LoginAsyncTask task = new LoginAsyncTask(AccountManagementActivity.this);
                if (Utils.isHoneycombOrHigher()) {
                    task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, sb.toString());
                } else {
                    task.execute(sb.toString());
                }      

三,執行個體

3-1 例子所涵蓋的内容:

This demo will finish the functions as followings:
1, display the global floating view on the top of the screen.
2, switch the floating state(visible or invisible) when the user toggle from two of three state (app inner, other app, home).
3, dynamically change the floating view's size or position
4, let the floating view automatically on the edge of the screen (n/a)
5, handle the conflict between touch event and click event      

3-2 demo 源碼

github位址:https://github.com/mikewang0326/FloatingViewDemo

四,其他

4-1 開源項目

當自己參考網上的代碼完成之後,發現xda上開源項目。但是花時間了解還是值得。以後如果再要做floating window相應的東西,可以直接使用這個開源庫。

xda位址:http://forum.xda-developers.com/showthread.php?t=1688531

對應的介紹都有,源碼在github上,從上述連接配接上都可以找到。

繼續閱讀