1、權限管理
直接看我另外一篇部落格吧,傳送門:
https://my.oschina.net/u/1462828/blog/1933162
2、Base類BaseSuspend
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Build;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import com.imxiaoyu.common.utils.entity.SizeEntity;
public abstract class BaseSuspend {
private Context context;
private View view;
private boolean isShowing = false;
/**
* UI
*/
private WindowManager.LayoutParams wmParams;//懸浮窗的布局
/**
* 變量
*/
private WindowManager mWindowManager;//建立浮動視窗設定布局參數的對象
/**
* 接口
*/
private OnSuspendDismissListener onSuspendDismissListener;
public BaseSuspend(Context context) {
this.context = context;
view = LayoutInflater.from(context).inflate(getLayoutId(), null);
init();
initView();
onCreateSuspension();
}
public void init() {
if (mWindowManager == null) {
mWindowManager = (WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
}
wmParams = getParams();//設定好懸浮窗的參數
// 懸浮窗預設顯示以左上角為起始坐标
wmParams.gravity = Gravity.LEFT | Gravity.TOP;
}
/**
* 布局檔案id,這裡是用不到的,但還是建議填寫,友善跳轉到布局管理
*
* @return
*/
protected abstract int getLayoutId();
/**
* 注冊需要使用的控件
*/
protected abstract void initView();
protected abstract void onCreateSuspension();
/**
* 根據id快速找到控件
*
* @param id
* @param <E>
* @return
*/
public final <E extends View> E findView(int id) {
try {
return (E) view.findViewById(id);
} catch (ClassCastException ex) {
throw ex;
}
}
/**
* 根據id快速找到控件
*
* @param id
* @param onClickListener
* @param <E>
* @return
*/
public final <E extends View> E findView(int id, View.OnClickListener onClickListener) {
E e = findView(id);
e.setOnClickListener(onClickListener);
return e;
}
/**
* 對windowManager進行設定
*
* @return
*/
public WindowManager.LayoutParams getParams() {
wmParams = new WindowManager.LayoutParams();
//設定window type 下面變量2002是在螢幕區域顯示,2003則可以顯示在狀态欄之上
//wmParams.type = LayoutParams.TYPE_PHONE;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
wmParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
} else {
wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
}
// wmParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
//設定圖檔格式,效果為背景透明
wmParams.format = PixelFormat.RGBA_8888;
//設定浮動視窗不可聚焦(實作操作除浮動視窗外的其他可見視窗的操作)
//wmParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE;
//設定可以顯示在狀态欄上
wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR |
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
return wmParams;
}
/**
* 全屏顯示懸浮視圖
*/
public void showSuspend() {
showSuspend(0, 0, true);
}
/**
* 顯示懸浮視圖
*
* @param sizeEntity
* @param isMatchParent 是否全屏顯示
*/
public void showSuspend(SizeEntity sizeEntity, boolean isMatchParent) {
if (sizeEntity != null) {
showSuspend(sizeEntity.getWidth(), sizeEntity.getHeight(), isMatchParent);
}
}
/**
* 顯示懸浮視圖
*
* @param width
* @param height
*/
public void showSuspend(int width, int height, boolean isMatchParent) {
//設定懸浮視窗長寬資料
if (isMatchParent) {
wmParams.width = WindowManager.LayoutParams.MATCH_PARENT;
wmParams.height = WindowManager.LayoutParams.MATCH_PARENT;
} else {
wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
}
//懸浮窗的開始位置,讀取緩存
wmParams.x = width;
wmParams.y = height;
if (isShowing) {
removeView();
}
mWindowManager.addView(view, wmParams);
isShowing = true;
}
/**
* 更新目前視圖的位置
*
* @param x 更新後的X軸的增量
* @param y 更新後的Y軸的增量
*/
public void updateSuspend(int x, int y) {
if (view != null) {
//必須是目前顯示的視圖才給更新
WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) view.getLayoutParams();
layoutParams.x += x;
layoutParams.y += y;
mWindowManager.updateViewLayout(view, layoutParams);
}
}
/**
* 移除目前懸浮窗
*/
public void dismissSuspend() {
if (view != null) {
mWindowManager.removeView(view);
isShowing = false;
if (onSuspendDismissListener != null) {
onSuspendDismissListener.onDismiss();
}
}
}
public Context getContext() {
return context;
}
public View getView() {
return view;
}
/**
* 是否正在顯示
*
* @return
*/
public boolean isShowing() {
return isShowing;
}
/**
* 移除彈窗的時候回調
*
* @param onSuspendDismissListener
*/
public void setOnSuspendDismissListener(OnSuspendDismissListener onSuspendDismissListener) {
this.onSuspendDismissListener = onSuspendDismissListener;
}
public interface OnSuspendDismissListener {
public void onDismiss();
}
}
還有裡面用到的一個size類:
/**
* 寬高實體
* Created by 她叫我小渝 on 2016/11/4.
*/
public class SizeEntity {
private int width;
private int height;
public SizeEntity(){}
public SizeEntity(int width,int height){
setWidth(width);
setHeight(height);
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}
3、定制視圖和使用
要實作的邏輯是,顯示一個懸浮球,然後可以拖動移動懸浮球的位置,效果圖:
然後建立一個類,LogoSuspend繼承BaseSuspend,裡面引用到了一些工具類就不貼出來了,用到的地方我會加上注釋
/**
* 懸浮球
* Created by 她叫我小渝 on 2017/1/1.
*/
public class LogoSuspend extends BaseSuspend {
/**
* ui
*/
private ImageView ivLogo;
/**
* 變量
*/
private int width, height;
private float mStartX, mStartY, mStopX, mStopY, touchStartX, touchStartY;
private long touchStartTime;
/**
* 接口
*/
private View.OnClickListener onClickListener;
public LogoSuspend(Context context) {
super(context);
}
@Override
protected int getLayoutId() {
return R.layout.suspend_logo;
}
@Override
protected void initView() {
ivLogo = findView(R.id.iv_logo);
ivLogo.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent event) {
final int action = event.getAction();
mStopX = event.getRawX();
mStopY = event.getRawY();
switch (action) {
case MotionEvent.ACTION_DOWN:
// 以目前父視圖左上角為原點
mStartX = event.getRawX();
mStartY = event.getRawY();
touchStartX = event.getRawX();
touchStartY = event.getRawY();
touchStartTime = DateUtil.getTimeForLong();//擷取目前時間戳
break;
case MotionEvent.ACTION_MOVE:
width = (int) (mStopX - mStartX);
height = (int) (mStopY - mStartY);
mStartX = mStopX;
mStartY = mStopY;
updateSuspend(width, height);
break;
case MotionEvent.ACTION_UP:
width = (int) (mStopX - mStartX);
height = (int) (mStopY - mStartY);
updateSuspend(width, height);
WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) getView().getLayoutParams();
SuspensionCache.setSuspendSize(getContext(), new SizeEntity(layoutParams.x + width, layoutParams.y + height));//緩存一下目前位置
if ((mStopX - touchStartX) < 30 && (mStartY - touchStartY) < 30 && (DateUtil.getTimeForLong() - touchStartTime) < 300) {
//左右上下移動距離不超過30的,并且按下和擡起時間少于300毫秒,算是單擊事件,進行回調
if (onClickListener != null) {
onClickListener.onClick(view);
}
}
break;
}
return true;
}
});
}
@Override
protected void onCreateSuspension() {
}
/**
* 設定點選監聽
*
* @param onClickListener
*/
public void setOnClickListener(View.OnClickListener onClickListener) {
this.onClickListener = onClickListener;
}
}
布局檔案syspend_logo.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rly_bg"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_home_add_normal" />
</RelativeLayout>
因為Activity是有生命周期的,是以打開懸浮窗的Context上下文,不要用Activity的,而是用Service的
建立并注冊一個Service,然後在onCreate方法中執行調用代碼就好
@Override
public void onCreate() {
super.onCreate();
ALog.e("服務已建立");
if (logoSuspend == null) {
logoSuspend = new LogoSuspend(this);
}
logoSuspend.showSuspend(SuspensionCache.getSuspendSize(this), false);//從緩存中提取上一次顯示的位置
logoSuspend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//處理單擊事件
}
});
}
4、廢話
上面的例子,其實還是比較簡單的,但一般開發對于懸浮球的需求并不算很大,Base類的話,目前隻是最基礎的東西,在開發的過程中,需要用到什麼了再往裡面加就好,問題不大。
目前代碼支援同時顯示多個懸浮窗、懸浮球,主要用于在于懸浮窗互動的時候,直接彈出其他的互動界面(也是以懸浮窗的狀态出現),但建議每一個頁面都有關閉按鈕或者做傳回鍵關閉的相關操作,畢竟是顯示在最前端的,要是關不掉就點哪裡都沒用,隻能是強制關機了…………()&@()……()@#*¥)()@*#…………@#)()*¥)()…………