WindowManager (ウィンドウ管理サービス)


このセクションの紹介:

このセクションでは、Android が提供するシステム サービスの 1 つである WindowManager (ウィンドウ管理サービス) について説明します。 View を表示する最下層の Toast、Activity、Dialog はすべてこの WindowManager を使用します。 彼はグローバルです!このクラスの中核は、メソッド addView、removeView、および updateViewLayout を呼び出すことにほかなりません。 View を表示し、関連プロパティを設定するには、WindowManager.LayoutParams API を使用します。

このセクションでは、実際の開発におけるこの WindowManager のいくつかのアプリケーション例について説明します~

公式 API ドキュメント: WindowManager


1. WindowManager のいくつかの概念:

1) WindowManager の概要

APIウィンドウマネージャーと対話するために Android によって提供されています。アプリのインターフェイスは次のとおりであることは誰もが知っています。 一つ一つのアクティビティから構成されており、インターフェースを表示したい場合には、アクティビティはビューから構成されます。 最初に思い浮かぶのは、アクティビティですよね?またはダイアログと乾杯。

ただし、場合によっては、最初の 3 つがニーズを満たさない場合があります。たとえば、単純な表示があるだけです。 Activity を使うのはちょっと冗長な気がするし、Dialog には Context オブジェクトが必要だし、Toast はクリックできないし…。 上記の状況では、WindowManager を使用して View を画面に追加できます。 または、画面からビューを削除してください。 Androidのウィンドウ機構を管理するインターフェースで、View!の最下層を表示します。


2) WindowManagerインスタンスの取得方法

WindowManagerオブジェクトを取得:

WindowManager wManager = getApplicationContext().getSystemService(Context. WINDOW_ SERVICE);

後続の操作に備えてWindowManager.LayoutParamsオブジェクトを取得

WindowManager.LayoutParams wmParams=new WindowManager.LayoutParams();

2.WindowManager使用例:

例 1: 画面の幅と高さを取得する

Android 4.2 より前は、次のメソッドを使用して画面の幅と高さを取得できました:

public static int[] getScreenHW(Context context) {
    WindowManager manager = (WindowManager)context
    .getSystemService(Context.WINDOW_SERVICE);
    Display display = manager.getDefaultDisplay();
    int width = display.getWidth();
    int height = display.getHeight();
    int[] HW = new int[] { width, height };
    return HW;
}

上記のメソッドは Android 4.2 以降では廃止されました。別のメソッドを使用して行うことができます。画面の幅と高さを取得します。 :

public static int[] getScreenHW2(Context context) {
    WindowManager manager = (WindowManager) context.
    getSystemService(Context.WINDOW_SERVICE);
    DisplayMetrics dm = new DisplayMetrics();
    manager.getDefaultDisplay().getMetrics(dm);
    int width = dm.widthPixels;
    int height = dm.heightPixels;
    int[] HW = new int[] { width, height };
    return HW;
}

次に、幅と高さを取得するためのメソッドをさらに 2 つ記述します。例として、画面の幅と高さを取得する 2 番目のメソッドを示します。別のツール クラスを作成する必要はありません。例:

public static int getScreenW(Context context) {
    return getScreenHW2(context)[0];
}

public static int getScreenH(Context context) {
    return getScreenHW2(context)[1];
}

実行結果

:

1.png

例 2: ウィンドウを全画面表示に設定します

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        WindowManager wManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        wManager.getDefaultDisplay().getMetrics(dm);
        Toast.makeText(MainActivity.this, "当前手机的屏幕宽高:" + dm.widthPixels + "*" +
                dm.heightPixels, Toast.LENGTH_SHORT).show();
    }
}

実行結果

:

2.png

例 3: 画面を常にオンにする

getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
        getSupportActionBar().hide();


例 4: 単純なフローティング ボックスの実装

実行効果図

:

実装コード:

まず、描画の完了やフローティングボックスの削除など、バックグラウンドでの操作を待機するバックグラウンドサービスが必要です。 そこでサービスを定義します: MyService.java: フローティング ボックス ビューを作成するメソッドが必要です:

public void setKeepScreenOn(Activity activity,boolean keepScreenOn)  
{  
    if(keepScreenOn)  
    {  
        activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);  
    }else{  
        activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);  
    }  
}

次に、フローティング ボックスの読み込みを開始するには、OnCreate() メソッドで上記の createWindowView() メソッドを呼び出すだけです。 しかし、私たちが発見したことが 1 つあります。これはオフにできないようです。次は需要を分析しましょう!

これは、携帯電話の通常のインターフェイスにある場合にのみ表示されます。デスクトップ、および他のアプリを起動すると、このフローティング ボックスが表示されます。 アプリを起動してデスクトップに戻ると、消えます。

次に、まずアプリがデスクトップ上にあるかどうかを判断する必要があるため、次のコードを追加します。

private void createWindowView() {
    btnView = new Button(getApplicationContext());
    btnView.setBackgroundResource(R.mipmap.ic_launcher);
    windowManager = (WindowManager) getApplicationContext()
            .getSystemService(Context.WINDOW_SERVICE);
    params = new WindowManager.LayoutParams();

    // 设置Window Type
    params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
    // 设置悬浮框不可触摸
    params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
            | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
    // 悬浮窗不可触摸,不接受任何事件,同时不影响后面的事件响应
    params.format = PixelFormat.RGBA_8888;
    // 设置悬浮框的宽高
    params.width = 200;
    params.height = 200;
    params.gravity = Gravity.LEFT;
    params.x = 200;
    params.y = 000;
    // 设置悬浮框的Touch监听
    btnView.setOnTouchListener(new View.OnTouchListener() {
        //保存悬浮框最后位置的变量
        int lastX, lastY;
        int paramX, paramY;

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    lastX = (int) event.getRawX();
                    lastY = (int) event.getRawY();
                    paramX = params.x;
                    paramY = params.y;
                    break;
                case MotionEvent.ACTION_MOVE:
                    int dx = (int) event.getRawX() - lastX;
                    int dy = (int) event.getRawY() - lastY;
                    params.x = paramX + dx;
                    params.y = paramY + dy;
                    // 更新悬浮窗位置
                    windowManager.updateViewLayout(btnView, params);
                    break;
            }
            return true;
        }
    });
    windowManager.addView(btnView, params);
    isAdded = true;
}

OK、次に Aデスクトップ上にあるかどうか、フローティングボックスがロードされているかどうかなど、一連の判断を時々行う必要があります。 それ以外の場合はロードします。そうでない場合は、フローティング ボックスを削除します。これは、子スレッドで直接実行できないためです。 UI を更新します。つまり、上記の操作を完了するためのハンドラーを作成します。

/**  
 * 判断当前界面是否是桌面  
 */    
public boolean isHome(){    
    if(mActivityManager == null) {  
        mActivityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);    
    }  
    List rti = mActivityManager.getRunningTasks(1);    
    return homeList.contains(rti.get(0).topActivity.getPackageName());    
}  
  
/**  
 * 获得属于桌面的应用的应用包名称  
 * @return 返回包含所有包名的字符串列表  
 */  
private List getHomes() {  
    List names = new ArrayList();    
    PackageManager packageManager = this.getPackageManager();    
    // 属性    
    Intent intent = new Intent(Intent.ACTION_MAIN);    
    intent.addCategory(Intent.CATEGORY_HOME);    
    List resolveInfo = packageManager.queryIntentActivities(intent,    
            PackageManager.MATCH_DEFAULT_ONLY);    
    for(ResolveInfo ri : resolveInfo) {    
        names.add(ri.activityInfo.packageName);    
    }  
    return names;    
}

最後に行うことは、Service の onStartCommand() メソッドを書き換えることです。これは、判断を行ってインテントを取り出すことです。の データ、フローティング ボックスを追加する必要があるか削除する必要があるかを決定します!

//定义一个更新界面的Handler  
private Handler mHandler = new Handler() {  
    @Override  
    public void handleMessage(Message msg) {  
        switch(msg.what) {  
        case HANDLE_CHECK_ACTIVITY:  
            if(isHome()) {  
                if(!isAdded) {  
                    windowManager.addView(btnView, params);  
                    isAdded = true;  
                new Thread(new Runnable() {  
                    public void run() {  
                        for(int i=0;i<10;i++){  
                            try {  
                                Thread.sleep(1000);  
                            } catch (InterruptedException e) {e.printStackTrace();}  
                            Message m = new Message();  
                            m.what=2;  
                            mHandler.sendMessage(m);  
                        }  
                    }  
                }).start();}  
            } else {  
                if(isAdded) {  
                    windowManager.removeView(btnView);  
                    isAdded = false;  
                }  
            }  
            mHandler.sendEmptyMessageDelayed(HANDLE_CHECK_ACTIVITY, 0);  
            break;  
        }  
    }  
};

さて、これで主な作業は完了しました。その後、アクティビティを使用して断片的な作業がいくつかあります。 このサービスを開始するには: MainActivity.java:

@Override  
public int onStartCommand(Intent intent, int flags, int startId) {  
    int operation = intent.getIntExtra(OPERATION, OPERATION_SHOW);  
    switch(operation) {  
    case OPERATION_SHOW:  
        mHandler.removeMessages(HANDLE_CHECK_ACTIVITY);  
        mHandler.sendEmptyMessage(HANDLE_CHECK_ACTIVITY);  
        break;  
    case OPERATION_HIDE:  
        mHandler.removeMessages(HANDLE_CHECK_ACTIVITY);  
        break;  
    }  
    return super.onStartCommand(intent, flags, startId);  
}

次に、AndroidManifest.xml に権限を追加し、MainService に登録します:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button btn_on;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindViews();
    }

    private void bindViews() {
        btn_on = (Button) findViewById(R.id.btn_on);
        btn_on.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_on:
                Intent mIntent = new Intent(MainActivity.this, MainService.class);
                mIntent.putExtra(MainService.OPERATION, MainService.OPERATION_SHOW);
                startService(mIntent);
                Toast.makeText(MainActivity.this, "悬浮框已开启~", Toast.LENGTH_SHORT).show();
                break;
        }
    }
}

さて、ロジックは比較的簡単に理解できます~自分で見てみましょう~


3文献の拡張:

4 番目の例から、WindowManager.LayoutParams はマークです。 たとえば、フルスクリーン〜時間の関係はいちいち記載されていませんので、公式サイトまたは以下のリンクで確認できます:

公式ドキュメント: WindowManager.LayoutParams

Android system service-WindowManager

さらに、上記に興味がある場合は、フローティング フレームに興味があり、より深く研究したい場合は、Uncle Guo (Guo Lin) のブログを参照してください:

Android デスクトップのフローティング ウィンドウ効果の実装、模倣360 モバイル ガード フローティング ウィンドウ エフェクト

Android デスクトップ フローティング ウィンドウの高度な、QQ モバイル バトラー リトル ロケット エフェクトの実装


4. このセクションのコード サンプルをダウンロードします:

WindowManagerDemo2.zip


このセクションの概要:

このセクションでは、Android システム サービスの WindowManager について説明しました。最初の 3 つの例は次のとおりです。 実際の開発では、最初のサンプルをツールクラスとして記述することをお勧めします。 結構あるんですよ~ フローティングフレームに関しては、理解できれば理解できても大丈夫ですよ~ 実際の開発では、作ってと言われることはほとんどありません。 フローティングフレーム... さて、このセクションはここまでです、ありがとう~

4.gif