1. 程式人生 > >Android 可拖動可點選懸浮窗

Android 可拖動可點選懸浮窗

Android 懸浮窗在5.0以上,特別是小米手機,魅族手機,就算給到了<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW">
2
</uses-permission>這個許可權也是不會顯示出來的,還需要在設定把懸浮許可權開關給開啟


<uses-permission android:name="android.permission.WRITE_SETTINGS"/>


最近公司專案要用到一個應用內的懸浮框,需要在當前的這個應用每個頁面都顯示出來,並且懸浮框可隨手指移動,但是停止時,要靠邊,可以點選,其實這些問題都不是大問題,就是Android5.0,6.0這些機Android型別,又或者不同機型,許可權問題的解決掉,啥也不說了,直接看程式碼吧!

## 1.自定義懸浮View


    package com.amei.floatwindow.view;


    import android.content.Context;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.MotionEvent;
    import android.view.WindowManager;
    import android.widget.ImageView;
    import android.widget.Toast;


    import com.amei.floatwindow.application.MyApplication;


    /**
     * Created by kitchee on 2017/1/10.
     * Description:全域性懸浮視窗
     */


    public class FloatScanView extends ImageView {




    private final int statusHeight;
    int sW;
    int sH;
    private float mTouchStartX;
    private float mTouchStartY;
    private float x;
    private float y;
    private boolean isMove=false;
    private Context context;


    private WindowManager wm = (WindowManager) getContext().getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
    //此wmParams變數為獲取的全域性變數,用以儲存懸浮視窗的屬性
    private WindowManager.LayoutParams wmParams = ((MyApplication) getContext().getApplicationContext()).getMywmParams();
    private float mLastX;
    private float mLastY;
    private float mStartX;
    private float mStartY;
    private long mLastTime ;
    private long mCurrentTime;




    public FloatScanView(Context context) {
        this(context,null);
        this.context = context;
    }


    public FloatScanView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }


    public FloatScanView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        sW = wm.getDefaultDisplay().getWidth();
        sH = wm.getDefaultDisplay().getHeight();
        statusHeight = getStatusHeight(context);


    }


    /**
     * 獲得狀態列的高度
     *
     * @param context
     * @return
     */
    public static int getStatusHeight(Context context) {


        int statusHeight = -1;
        try {
            Class clazz = Class.forName("com.android.internal.R$dimen");
            Object object = clazz.newInstance();
            int height = Integer.parseInt(clazz.getField("status_bar_height")
                    .get(object).toString());
            statusHeight = context.getResources().getDimensionPixelSize(height);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return statusHeight;
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //獲取相對螢幕的座標,即以螢幕左上角為原點
        x = event.getRawX();
        y = event.getRawY() - statusHeight;   //statusHeight是系統狀態列的高度
        Log.i("currP", "currX" + x + "====currY" + y);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:    //捕獲手指觸控按下動作
                //獲取相對View的座標,即以此View左上角為原點
                mTouchStartX = event.getX();
                mTouchStartY = event.getY();
                mStartX = event.getRawX();
                mStartY = event.getRawY();
                mLastTime = System.currentTimeMillis();
                Log.i("startP", "startX" + mTouchStartX + "====startY" + mTouchStartY);
                isMove = false;
                break;


            case MotionEvent.ACTION_MOVE:   //捕獲手指觸控移動動作
                updateViewPosition();
                isMove = true;
                break;


            case MotionEvent.ACTION_UP:    //捕獲手指觸控離開動作
                mLastX = event.getRawX();
                mLastY = event.getRawY();


                // 擡起手指時讓floatView緊貼螢幕左右邊緣
                wmParams.x = wmParams.x <= (sW / 2) ? 0 : sW;
                wmParams.y = (int) (y - mTouchStartY);
                wm.updateViewLayout(this, wmParams);




                mCurrentTime = System.currentTimeMillis();
                if(mCurrentTime - mLastTime < 800){
                    if(Math.abs(mStartX- mLastX )< 10.0 && Math.abs(mStartY - mLastY) < 10.0){
                        //處理點選的事件
                        Toast.makeText(context,"可以處理點選事件",Toast.LENGTH_SHORT).show();
                    }
                }


                break;
        }
        return true;
    }




    private void updateViewPosition() {
        //更新浮動視窗位置引數
        wmParams.x = (int) (x - mTouchStartX);
        wmParams.y = (int) (y - mTouchStartY);
        wm.updateViewLayout(this, wmParams);  //重新整理顯示


    }


    }
    
## 2.定義一個MyApplication


    在裡面進行WindowManager的初始化




    public class MyApplication extends Application {


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


    public WindowManager.LayoutParams getMywmParams(){
        return wmParams;
    }


    @Override
    public void onCreate() {
        super.onCreate();


    }
    }


## 3.最後看看主頁MainActivity
    
    public class MainActivity extends AppCompatActivity {


    private WindowManager wm = null;
    private WindowManager.LayoutParams wmParams = null;
    private FloatScanView fsv = null;


    private AlertDialog dialog;




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


        dialog = new AlertDialog.Builder(this)
                .setTitle("懸浮窗許可權管理")
                .setMessage("是否去開啟懸浮窗許可權?")
                .setPositiveButton("是", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        //開啟許可權設定
                        openSetting();
                    }
                })
                .setNegativeButton("否",null)
                .create();


        //建立懸浮框
        createFloatView();


        }


    private void createFloatView() {


        //開啟懸浮窗前先請求許可權
        if ("Xiaomi".equals(Build.MANUFACTURER)) {//小米手機
    //            LogUtil.E("小米手機");
            requestPermission();
        } else if ("Meizu".equals(Build.MANUFACTURER)) {//魅族手機
    //            LogUtil.E("魅族手機");
            requestPermission();
        } else {//其他手機
    //            LogUtil.E("其他手機");
            if (Build.VERSION.SDK_INT >= 23) {
                if (!Settings.canDrawOverlays(this)) {
                    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
                    startActivityForResult(intent, 12);
                } else {
    //
                }
            } else {


            }
        }


        wm=(WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
    //        wmParams = new WindowManager.LayoutParams();
        wmParams = ((MyApplication)getApplication()).getMywmParams();


    //        wmParams.type=2002;          //type是關鍵,這裡的2002表示系統級視窗
        wmParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
        wmParams.format= PixelFormat.RGBA_8888;//設定圖片格式,效果為背景透明


        wmParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE ;//
        wmParams.gravity = Gravity.LEFT|Gravity.TOP;//
        wmParams.x = 0;
        wmParams.y = 0;
        wmParams.width=100;
        wmParams.height=100;
        fsv = new FloatScanView(getApplicationContext());
        fsv.setImageResource(R.mipmap.add_coupon_by_scan);
        fsv.setBackgroundColor(getResources().getColor(R.color.piegps_bg_new));
        wm.addView(fsv, wmParams);
    }




    @Override
    protected void onRestart() {
        super.onRestart();
        wm.addView(fsv,wmParams);
    }




    @Override
    protected void onStop() {
        if(fsv != null){
            wm.removeView(fsv);
        }
        super.onStop();
    }
    //
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(fsv != null){
            wm.removeView(fsv);
        }
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 11) {
            if (isFloatWindowOpAllowed(this)) {//已經開啟
            } else {
                Toast.makeText(this,"開啟懸浮窗失敗",Toast.LENGTH_SHORT).show();
            }
        } else if (requestCode == 12) {
            if (Build.VERSION.SDK_INT >= 23) {
                if (!Settings.canDrawOverlays(MainActivity.this)) {


                    Toast.makeText(this,"許可權授予失敗,無法開啟懸浮窗",Toast.LENGTH_SHORT).show();
                } else {
                }
            }
        }


    }
    /**
     * 判斷懸浮窗許可權
     *
     * @param context
     * @return
     */
    @TargetApi(Build.VERSION_CODES.KITKAT)
    public static boolean isFloatWindowOpAllowed(Context context) {
        final int version = Build.VERSION.SDK_INT;
        if (version >= 19) {
            return checkOp(context, 24);  // AppOpsManager.OP_SYSTEM_ALERT_WINDOW
        } else {
            if ((context.getApplicationInfo().flags & 1 << 27) == 1 << 27) {
                return true;
            } else {
                return false;
            }
        }
    }


    @TargetApi(Build.VERSION_CODES.KITKAT)
    public static boolean checkOp(Context context, int op) {
        final int version = Build.VERSION.SDK_INT;


        if (version >= 19) {
            AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
            try {
                Class<?> spClazz = Class.forName(manager.getClass().getName());
                Method method = manager.getClass().getDeclaredMethod("checkOp", int.class, int.class, String.class);
                int property = (Integer) method.invoke(manager, op,
                        Binder.getCallingUid(), context.getPackageName());
                Log.e("399", " property: " + property);


                if (AppOpsManager.MODE_ALLOWED == property) {
                    return true;
                } else {
                    return false;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            Log.e("399", "Below API 19 cannot invoke!");
        }
        return false;
    }




    /**
     * 請求使用者給予懸浮窗的許可權
     */
    public void requestPermission() {
        if (isFloatWindowOpAllowed(this)) {//已經開啟


        } else {
            dialog.show();
        }
    }




    /**
     * 開啟許可權設定介面
     */
    public void openSetting() {
        try {
            Intent localIntent = new Intent(
                    "miui.intent.action.APP_PERM_EDITOR");
            localIntent.setClassName("com.miui.securitycenter",
                    "com.miui.permcenter.permissions.AppPermissionsEditorActivity");
            localIntent.putExtra("extra_pkgname", getPackageName());
            startActivityForResult(localIntent, 11);
    //            LogUtil.E("啟動小米懸浮窗設定介面");
        } catch (ActivityNotFoundException localActivityNotFoundException) {
            Intent intent1 = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
            Uri uri = Uri.fromParts("package", getPackageName(), null);
            intent1.setData(uri);
            startActivityForResult(intent1, 11);
    //            LogUtil.E("啟動懸浮窗介面");
        }




    }


    }
    

    其實這裡面還是有一個問題,就是對於華為手機5.0以下的會是不是的掃描是否有懸浮框,如果有,它就是自動禁止掉,這個很無奈啊!

資源下載