直接看我另一篇博客吧,傳送門:前端
https://my.oschina.net/u/1462828/blog/1933162android
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; } }
要實現的邏輯是,顯示一個懸浮球,而後能夠拖動移動懸浮球的位置,效果圖:ide
而後新建一個類,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的ui
建立並註冊一個Service,而後在onCreate方法中執行調用代碼就好this
@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) { //處理單擊事件 } }); }
上面的例子,其實仍是比較簡單的,但通常開發對於懸浮球的需求並不算很大,Base類的話,目前只是最基礎的東西,在開發的過程當中,須要用到什麼了再往裏面加就好,問題不大。spa
目前代碼支持同時顯示多個懸浮窗、懸浮球,主要用於在於懸浮窗交互的時候,直接彈出其餘的交互界面(也是以懸浮窗的狀態出現),但建議每個頁面都有關閉按鈕或者作返回鍵關閉的相關操做,畢竟是顯示在最前端的,要是關不掉就點哪裏都沒用,只能是強制關機了…………()&@()……()@#*¥)()@*#…………@#)()*¥)()………….net