Android 可拖動可點選懸浮窗
阿新 • • 發佈:2019-01-08
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("啟動懸浮窗介面");
}
}
}
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以下的會是不是的掃描是否有懸浮框,如果有,它就是自動禁止掉,這個很無奈啊!