web-dev-qa-db-ja.com

Clipboard Pro Appのようなフローティングウィンドウを作成するにはどうすればよいですか?

次の画像はアプリのウィンドウです https://play.google.com/store/apps/details?id=jp.snowlife01.Android.clipboard 。メインウィンドウと名付けました。

メインウィンドウは通常のウィンドウではないようです。他のウィンドウの上にあるため、メインウィンドウを移動することはできません。

メインウィンドウの最小化されたボタンをクリックすると小さなポイントアイコンが表示され、小さなポイントアイコンを移動したり、小さなポイントアイコンをクリックするとメインウィンドウを復元したりできます。

小さな点のアイコンはSYSTEM_ALERT_WINDOWだと思いますが、メインウィンドウはどうですか?

画像

enter image description here

7
HelloCW

権限を付与せずにWindowManagerを使用してフロートビューを作成できます。簡単に使えるサンプルコードを開発しました。

FloatViewManager.Java:

import Android.annotation.SuppressLint;
import Android.annotation.TargetApi;
import Android.app.Activity;
import Android.graphics.PixelFormat;
import Android.os.Build;
import Android.view.Gravity;
import Android.view.LayoutInflater;
import Android.view.MotionEvent;
import Android.view.View;
import Android.view.WindowManager;
import Android.widget.ImageButton;
import Android.widget.TextView;
import Android.widget.Toast;

import static Android.content.Context.WINDOW_SERVICE;


/**
 * Created by aminography on 11/1/2018.
 */
public class FloatViewManager {

    private static boolean mIsFloatViewShowing;

    private Activity mActivity;
    private WindowManager mWindowManager;
    private View mFloatView;
    private WindowManager.LayoutParams mFloatViewLayoutParams;
    private boolean mFloatViewTouchConsumedByMove = false;
    private int mFloatViewLastX;
    private int mFloatViewLastY;
    private int mFloatViewFirstX;
    private int mFloatViewFirstY;

    @SuppressLint("InflateParams")
    public FloatViewManager(Activity activity) {
        mActivity = activity;
        LayoutInflater inflater = LayoutInflater.from(activity);
        mFloatView = inflater.inflate(R.layout.float_view_layout, null);
        TextView textView = mFloatView.findViewById(R.id.textView);
        textView.setText("I'm a float view!");
        ImageButton imageButton = mFloatView.findViewById(R.id.imageButton);
        imageButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismissFloatView();
            }
        });

        mFloatView.setOnClickListener(mFloatViewOnClickListener);
        mFloatView.setOnTouchListener(mFloatViewOnTouchListener);

        mFloatViewLayoutParams = new WindowManager.LayoutParams();
        mFloatViewLayoutParams.format = PixelFormat.TRANSLUCENT;
        mFloatViewLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;

        mFloatViewLayoutParams.type = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
                ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
                : WindowManager.LayoutParams.TYPE_TOAST;

        mFloatViewLayoutParams.gravity = Gravity.CENTER;
        mFloatViewLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        mFloatViewLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;

    }

    public void dismissFloatView() {
        if (mIsFloatViewShowing) {
            mIsFloatViewShowing = false;
            mActivity.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if (mWindowManager != null) {
                        mWindowManager.removeViewImmediate(mFloatView);
                    }
                }
            });
        }
    }

    public void showFloatView() {
        if (!mIsFloatViewShowing) {
            mIsFloatViewShowing = true;
            mActivity.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if (!mActivity.isFinishing()) {
                        mWindowManager = (WindowManager) mActivity.getSystemService(WINDOW_SERVICE);
                        if (mWindowManager != null) {
                            mWindowManager.addView(mFloatView, mFloatViewLayoutParams);
                        }
                    }
                }
            });
        }
    }

    @SuppressWarnings("FieldCanBeLocal")
    private View.OnClickListener mFloatViewOnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mActivity.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(mActivity, "Float view is clicked!", Toast.LENGTH_SHORT).show();
                }
            });
        }
    };

    @SuppressWarnings("FieldCanBeLocal")
    private View.OnTouchListener mFloatViewOnTouchListener = new View.OnTouchListener() {

        @SuppressLint("ClickableViewAccessibility")
        @TargetApi(Build.VERSION_CODES.FROYO)
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            WindowManager.LayoutParams prm = mFloatViewLayoutParams;
            int totalDeltaX = mFloatViewLastX - mFloatViewFirstX;
            int totalDeltaY = mFloatViewLastY - mFloatViewFirstY;

            switch (event.getActionMasked()) {
                case MotionEvent.ACTION_DOWN:
                    mFloatViewLastX = (int) event.getRawX();
                    mFloatViewLastY = (int) event.getRawY();
                    mFloatViewFirstX = mFloatViewLastX;
                    mFloatViewFirstY = mFloatViewLastY;
                    break;
                case MotionEvent.ACTION_UP:
                    break;
                case MotionEvent.ACTION_MOVE:
                    int deltaX = (int) event.getRawX() - mFloatViewLastX;
                    int deltaY = (int) event.getRawY() - mFloatViewLastY;
                    mFloatViewLastX = (int) event.getRawX();
                    mFloatViewLastY = (int) event.getRawY();
                    if (Math.abs(totalDeltaX) >= 5 || Math.abs(totalDeltaY) >= 5) {
                        if (event.getPointerCount() == 1) {
                            prm.x += deltaX;
                            prm.y += deltaY;
                            mFloatViewTouchConsumedByMove = true;
                            if (mWindowManager != null) {
                                mWindowManager.updateViewLayout(mFloatView, prm);
                            }
                        } else {
                            mFloatViewTouchConsumedByMove = false;
                        }
                    } else {
                        mFloatViewTouchConsumedByMove = false;
                    }
                    break;
                default:
                    break;
            }
            return mFloatViewTouchConsumedByMove;
        }
    };

}

float_view_layout.xml:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:layout_width="wrap_content"
    Android:layout_height="wrap_content">

    <Android.support.v7.widget.AppCompatTextView
        Android:id="@+id/textView"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:background="#ff4444"
        Android:padding="32dp"
        Android:text="text"
        Android:textColor="#ffffff"
        Android:textSize="24sp" />

    <Android.support.v7.widget.AppCompatImageButton
        Android:id="@+id/imageButton"
        style="@style/Base.Widget.AppCompat.Button.Borderless"
        Android:layout_width="40dp"
        Android:layout_height="40dp"
        Android:layout_gravity="top|right"
        Android:src="@drawable/ic_close_white_24dp" />

</FrameLayout>

MainActivity.Java:

import Android.content.Intent;
import Android.net.Uri;
import Android.os.Build;
import Android.os.Bundle;
import Android.provider.Settings;
import Android.support.v7.app.AppCompatActivity;
import Android.view.View;

public class MainActivity extends AppCompatActivity {

    private static final int REQUEST_CODE_DRAW_OVERLAY_PERMISSION = 5;
    private FloatViewManager mFloatViewManager;

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

        mFloatViewManager = new FloatViewManager(MainActivity.this);

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (checkDrawOverlayPermission()) {
                    mFloatViewManager.showFloatView();
                }
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_CODE_DRAW_OVERLAY_PERMISSION) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Settings.canDrawOverlays(this)) {
                mFloatViewManager.showFloatView();
            }
        }
    }

    private boolean checkDrawOverlayPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
            startActivityForResult(intent, REQUEST_CODE_DRAW_OVERLAY_PERMISSION);
            return false;
        } else {
            return true;
        }
    }

}

視覚的結果:

enter image description here

8
aminography

タイプがTYPE_APPLICATION_OVERLAYであると想定します。

Android:theme="@Android:style/Theme.Dialog"と組み合わせる可能性があります。

Manifest.permission.SYSTEM_ALERT_WINDOWが必要です。

playストアからインストールしない限り、手動で権限を有効にする必要があります。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION));
}

ドキュメント を参照してください(実際には、適切なものはそれほど多くありません)。

0
Martin Zeitler