国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > Android > 正文

Android應(yīng)用內(nèi)懸浮窗的實(shí)現(xiàn)方案示例

2019-12-12 02:20:51
字體:
供稿:網(wǎng)友

1、懸浮窗的基本介紹

懸浮窗,大家應(yīng)該也不陌生,凌駕于應(yīng)用之上的一個(gè)小彈窗,實(shí)現(xiàn)上很簡(jiǎn)單,就是添加一個(gè)系統(tǒng)級(jí)別的窗口,Android中通過WindowManagerService( WMS)來管理所有的窗口,對(duì)于WMS來說,管你是Activity、Toast、Dialog,都不過是通過WindowManagerGlobal.addView()添加的一個(gè)個(gè)View。

Android中的窗口分為三個(gè)級(jí)別:

1.1 應(yīng)用窗口,比如Activity的窗口;

1.2 子窗口,依賴于父窗口,比如PopupWindow;

1.3 系統(tǒng)窗口,比如狀態(tài)欄、Toast,目標(biāo)懸浮窗就是系統(tǒng)窗口.

2、根據(jù)產(chǎn)品需求進(jìn)行設(shè)計(jì)

先了解一下大概的產(chǎn)品需求:

1、懸浮窗需要跨越整個(gè)應(yīng)用
2、需要與懸浮窗進(jìn)行交互
3、懸浮窗得移動(dòng)
4、點(diǎn)擊跳轉(zhuǎn)特定的頁面
5、消息提示的拖拽小紅點(diǎn)

需求很簡(jiǎn)單,但是如果估算沒錯(cuò),不下一周產(chǎn)品經(jīng)理會(huì)添加新的需求,所以為了更好的后續(xù)擴(kuò)展,需要進(jìn)行合理的設(shè)計(jì),主要分為以下幾點(diǎn):

1、懸浮窗自定義一個(gè)FrameLayout布局FloatLayout,里面進(jìn)行拖動(dòng)及點(diǎn)擊響應(yīng)處理;
2、FloatMonkService,是一個(gè)服務(wù),開啟服務(wù)的時(shí)候創(chuàng)建懸浮窗;
3、FloatCallBack,交互接口,在FloatMonkService里面實(shí)現(xiàn)接口,用于交互;
4、FloatWindowManager,懸浮窗的管理,因?yàn)楹罄m(xù)懸浮窗布局可能有好幾個(gè),可以在這里面進(jìn)行切換;
5、HomeWatcherReceiver,廣播接收者,因?yàn)樵趹?yīng)用內(nèi)展示,需要監(jiān)聽用戶在點(diǎn)擊Home鍵和切換鍵的時(shí)候隱藏懸浮窗,需要FloatMonkService里頭動(dòng)態(tài)注冊(cè);
6、FloatActionController,其實(shí)就是代理,其它模塊需要通過它來和懸浮窗進(jìn)行交互,真正干活的是實(shí)現(xiàn)FloatCallBack接口的FloatMonkService;
7、FloatPermissionManager,需要適配各個(gè)傻逼機(jī)型的權(quán)限,慶幸網(wǎng)上已有大佬分享,只需要單獨(dú)對(duì)7.0系統(tǒng)進(jìn)行一些適配就行,懸浮窗權(quán)限適配;
8、拖拽控件DraggableFlagView,直接拿來在懸浮窗上出現(xiàn)很奇怪的問題,所以需要改造一下下才能達(dá)到圖中效果。

3、具體實(shí)現(xiàn)

float_littlemonk_layout.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"  xmlns:dfv="http://schemas.android.com/apk/res-auto"  android:layout_width="wrap_content"  android:layout_height="wrap_content"  android:gravity="center"  android:orientation="vertical">  <RelativeLayout    android:id="@+id/monk_relative_root"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <ImageView      android:id="@+id/float_id"      android:layout_width="70dp"      android:layout_height="80dp"      android:layout_gravity="center_vertical|end"      android:scaleType="center"      android:src="@drawable/little_monk" />  </RelativeLayout>  <FrameLayout    android:layout_width="match_parent"    android:layout_height="match_parent">    <floatwindow.xishuang.float_lib.view.DraggableFlagView      android:id="@+id/main_dfv"      android:layout_width="17dp"      android:layout_height="17dp"      android:layout_gravity="end"      dfv:color1="#FF3B30" />  </FrameLayout></FrameLayout>

簡(jiǎn)單的布局,就是一張圖片+右上角放一個(gè)自定義的小紅點(diǎn)。

FloatLayout.java

@Override  public boolean onTouchEvent(MotionEvent event) {    // 獲取相對(duì)屏幕的坐標(biāo),即以屏幕左上角為原點(diǎn)    int x = (int) event.getRawX();    int y = (int) event.getRawY();    //下面的這些事件,跟圖標(biāo)的移動(dòng)無關(guān),為了區(qū)分開拖動(dòng)和點(diǎn)擊事件    int action = event.getAction();    switch (action) {      case MotionEvent.ACTION_DOWN:        startTime = System.currentTimeMillis();        mTouchStartX = event.getX();        mTouchStartY = event.getY();        break;      case MotionEvent.ACTION_MOVE:        //圖標(biāo)移動(dòng)的邏輯在這里        float mMoveStartX = event.getX();        float mMoveStartY = event.getY();        // 如果移動(dòng)量大于3才移動(dòng)        if (Math.abs(mTouchStartX - mMoveStartX) > 3            && Math.abs(mTouchStartY - mMoveStartY) > 3) {          // 更新浮動(dòng)窗口位置參數(shù)          mWmParams.x = (int) (x - mTouchStartX);          mWmParams.y = (int) (y - mTouchStartY);          mWindowManager.updateViewLayout(this, mWmParams);          return false;        }        break;      case MotionEvent.ACTION_UP:        endTime = System.currentTimeMillis();        //當(dāng)從點(diǎn)擊到彈起小于半秒的時(shí)候,則判斷為點(diǎn)擊,如果超過則不響應(yīng)點(diǎn)擊事件        if ((endTime - startTime) > 0.1 * 1000L) {          isclick = false;        } else {          isclick = true;        }        break;    }    //響應(yīng)點(diǎn)擊事件    if (isclick) {      Toast.makeText(mContext, "我是大傻叼", Toast.LENGTH_SHORT).show();    }    return true;  }

為了把懸浮窗的view操作抽離出來,自定義了這個(gè)布局,主要進(jìn)行兩部分功能,懸浮窗的移動(dòng)和點(diǎn)擊處理,重點(diǎn)是通過mWindowManager.updateViewLayout(this, mWmParams)來進(jìn)行懸浮窗的位置移動(dòng),我這個(gè)Demo里面只是簡(jiǎn)單的通過時(shí)間來判斷點(diǎn)擊事件,有必要的話點(diǎn)擊事件需要添加特定View范圍判斷來響應(yīng)點(diǎn)擊。

// 如果移動(dòng)量大于3才移動(dòng)if (Math.abs(mTouchStartX - mMoveStartX) > 3 && Math.abs(mTouchStartY - mMoveStartY) > 3) 

這個(gè)判斷是為了避免點(diǎn)擊懸浮窗不在重心位置會(huì)出現(xiàn)移動(dòng)的現(xiàn)象。

FloatMonkService.java

/** * 懸浮窗在服務(wù)中創(chuàng)建,通過暴露接口FloatCallBack與Activity進(jìn)行交互 */public class FloatMonkService extends Service implements FloatCallBack {  /**   * home鍵監(jiān)聽   */  private HomeWatcherReceiver mHomeKeyReceiver;  @Override  public void onCreate() {    super.onCreate();    FloatActionController.getInstance().registerCallLittleMonk(this);    //注冊(cè)廣播接收者    mHomeKeyReceiver = new HomeWatcherReceiver();    final IntentFilter homeFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);    registerReceiver(mHomeKeyReceiver, homeFilter);    //初始化懸浮窗UI    initWindowData();  }  @Override  public IBinder onBind(Intent intent) {    return null;  }  /**   * 初始化WindowManager   */  private void initWindowData() {    FloatWindowManager.createFloatWindow(this);  }  @Override  public void onDestroy() {    super.onDestroy();    //移除懸浮窗    FloatWindowManager.removeFloatWindowManager();    //注銷廣播接收者    if (null != mHomeKeyReceiver) {      unregisterReceiver(mHomeKeyReceiver);    }  }  /////////////////////////////////////////////////////////實(shí)現(xiàn)接口////////////////////////////////////////////////////  @Override  public void guideUser(int type) {    FloatWindowManager.updataRedAndDialog(this);  }  /**   * 懸浮窗的隱藏   */  @Override  public void hide() {    FloatWindowManager.hide();  }  /**   * 懸浮窗的顯示   */  @Override  public void show() {    FloatWindowManager.show();  }  /**   * 添加可領(lǐng)取的數(shù)量   */  @Override  public void addObtainNumer() {    FloatWindowManager.addObtainNumer(this);    guideUser(4);  }  /**   * 減少可領(lǐng)取的數(shù)量   */  @Override  public void setObtainNumber(int number) {    FloatWindowManager.setObtainNumber(this, number);  }}

服務(wù)開啟的時(shí)候通過FloatWindowManager.createFloatWindow(this)來創(chuàng)建懸浮窗,實(shí)現(xiàn)FloatCallBack 實(shí)現(xiàn)需要交互的接口。下面看一下創(chuàng)建懸浮窗的真正操作是怎樣的。

FloatWindowManager.java

/**   * 創(chuàng)建一個(gè)小懸浮窗。初始位置為屏幕的右下角位置。   */  public static void createFloatWindow(Context context) {    wmParams = new WindowManager.LayoutParams();    WindowManager windowManager = getWindowManager(context);    mFloatLayout = new FloatLayout(context);    if (Build.VERSION.SDK_INT >= 24) { /*android7.0不能用TYPE_TOAST*/      wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;    } else { /*以下代碼塊使得android6.0之后的用戶不必再去手動(dòng)開啟懸浮窗權(quán)限*/      String packname = context.getPackageName();      PackageManager pm = context.getPackageManager();      boolean permission = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.SYSTEM_ALERT_WINDOW", packname));      if (permission) {        wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;      } else {        wmParams.type = WindowManager.LayoutParams.TYPE_TOAST;      }    }    //設(shè)置圖片格式,效果為背景透明    wmParams.format = PixelFormat.RGBA_8888;    //設(shè)置浮動(dòng)窗口不可聚焦(實(shí)現(xiàn)操作除浮動(dòng)窗口外的其他可見窗口的操作)    wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;    //調(diào)整懸浮窗顯示的停靠位置為左側(cè)置頂    wmParams.gravity = Gravity.START | Gravity.TOP;    DisplayMetrics dm = new DisplayMetrics();    //取得窗口屬性    mWindowManager.getDefaultDisplay().getMetrics(dm);    //窗口的寬度    int screenWidth = dm.widthPixels;    //窗口高度    int screenHeight = dm.heightPixels;    //以屏幕左上角為原點(diǎn),設(shè)置x、y初始值,相對(duì)于gravity    wmParams.x = screenWidth;    wmParams.y = screenHeight;    //設(shè)置懸浮窗口長(zhǎng)寬數(shù)據(jù)    wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;    wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;    mFloatLayout.setParams(wmParams);    windowManager.addView(mFloatLayout, wmParams);    mHasShown = true;    //是否展示小紅點(diǎn)展示    checkRedDot(context);  }/**   * 返回當(dāng)前已創(chuàng)建的WindowManager。   */  private static WindowManager getWindowManager(Context context) {    if (mWindowManager == null) {      mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);    }    return mWindowManager;  }

核心代碼其實(shí)就是mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE),其中的context不能是Activity的,一開始就說了,Activity會(huì)返回它專享的WindowManager,而Activity的窗口級(jí)別是屬于應(yīng)用層的。進(jìn)行一些初始化操作之后 windowManager.addView(mFloatLayout, wmParams)把布局添加進(jìn)去就ok了。

 if (Build.VERSION.SDK_INT >= 24) { /*android7.0不能用TYPE_TOAST*/      wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;    } else { /*以下代碼塊使得android6.0之后的用戶不必再去手動(dòng)開啟懸浮窗權(quán)限*/      String packname = context.getPackageName();      PackageManager pm = context.getPackageManager();      boolean permission = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.SYSTEM_ALERT_WINDOW", packname));      if (permission) {        wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;      } else {        wmParams.type = WindowManager.LayoutParams.TYPE_TOAST;      }    }

說一下這段代碼的意義,當(dāng)WindowManager.LayoutParams.type設(shè)置為WindowManager.LayoutParams.TYPE_TOAST的時(shí)候,是可以跳過權(quán)限申請(qǐng)的,但是為毛又單獨(dú)適配各個(gè)機(jī)型呢,因?yàn)槲覀冇行∶譇ndroid系統(tǒng),魅族Android系統(tǒng),還有華為等等Android系統(tǒng),特別是產(chǎn)品經(jīng)理的魅族,一些特殊機(jī)型上是沒有效果的,所以為了更保險(xiǎn),得再加一份權(quán)限申請(qǐng),還有一點(diǎn)得提一下,那就是7.0上WindowManager.LayoutParams.TYPE_TOAST,懸浮窗只能持續(xù)一秒的時(shí)間,所以7.0不設(shè)這個(gè)type,谷歌爸爸最叼,7.0以上老老實(shí)實(shí)申請(qǐng)權(quán)限。

FloatActionController.java

/** * Author:xishuang * Date:2017.08.01 * Des:與懸浮窗交互的控制類,真正的實(shí)現(xiàn)邏輯不在這 */public class FloatActionController {  private FloatActionController() {  }  public static FloatActionController getInstance() {    return LittleMonkProviderHolder.sInstance;  }  // 靜態(tài)內(nèi)部類  private static class LittleMonkProviderHolder {    private static final FloatActionController sInstance = new FloatActionController();  }  private FloatCallBack mCallLittleMonk;  /**   * 開啟服務(wù)懸浮窗   */  public void startMonkServer(Context context) {    Intent intent = new Intent(context, FloatMonkService.class);    context.startService(intent);  }  /**   * 關(guān)閉懸浮窗   */  public void stopMonkServer(Context context) {    Intent intent = new Intent(context, FloatMonkService.class);    context.stopService(intent);  }  /**   * 注冊(cè)監(jiān)聽   */  public void registerCallLittleMonk(FloatCallBack callLittleMonk) {    mCallLittleMonk = callLittleMonk;  }  /**   * 懸浮窗的顯示   */  public void show() {    if (mCallLittleMonk == null) return;    mCallLittleMonk.show();  }  /**   * 懸浮窗的隱藏   */  public void hide() {    if (mCallLittleMonk == null) return;    mCallLittleMonk.hide();  }}

這就是暴露出來的接口,按需添加,效果大概是這樣的。

大概效果如下:

Demo:代碼地址感興趣可以看看完整的。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持武林網(wǎng)。

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 江陵县| 化州市| 沁水县| 财经| 汝城县| 泽库县| 平乐县| 博兴县| 固原市| 甘孜县| 咸宁市| 徐汇区| 公安县| 分宜县| 上犹县| 兴化市| 平舆县| 仙游县| 建宁县| 班戈县| 远安县| 吴堡县| 于都县| 呼图壁县| 什邡市| 怀远县| 桃源县| 札达县| 四子王旗| 新乐市| 海伦市| 临汾市| 那曲县| 湘潭县| 太原市| 多伦县| 买车| 什邡市| 自贡市| 田林县| 堆龙德庆县|