Android實(shí)現(xiàn)自由拖動(dòng)并顯示文字的懸浮框
項(xiàng)目中需要實(shí)現(xiàn)一個(gè)狀態(tài)顯示的懸浮框,要求可以設(shè)置兩種模式:拖動(dòng)模式和不可拖動(dòng)模式。
實(shí)現(xiàn)效果圖如下:
實(shí)現(xiàn)步驟:
1.首先要設(shè)置該懸浮框的基本屬性:
/** * 顯示彈出框 * * @param context */ @SuppressWarnings("WrongConstant") public static void showPopupWindow(final Context context, String showtxt) { if (isShown) { return; } isShown = true; // 獲取WindowManager mWindowManager = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); mView = setUpView(context, showtxt); params = new WindowManager.LayoutParams(); // 類型,系統(tǒng)提示以及它總是出現(xiàn)在應(yīng)用程序窗口之上。 params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT | WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY; // 設(shè)置flag int flags = canTouchFlags; // | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; // 如果設(shè)置了WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,彈出的View收不到Back鍵的事件 params.flags = flags; // 不設(shè)置這個(gè)彈出框的透明遮罩顯示為黑色 params.format = PixelFormat.TRANSLUCENT; // FLAG_NOT_TOUCH_MODAL不阻塞事件傳遞到后面的窗口 // 設(shè)置 FLAG_NOT_FOCUSABLE 懸浮窗口較小時(shí),后面的應(yīng)用圖標(biāo)由不可長(zhǎng)按變?yōu)榭砷L(zhǎng)按 // 不設(shè)置這個(gè)flag的話,home頁(yè)的劃屏?xí)袉?wèn)題 params.width = LayoutParams.WRAP_CONTENT; params.height = LayoutParams.WRAP_CONTENT; params.gravity = Gravity.TOP; mWindowManager.addView(mView, params); }
比較重要的點(diǎn)是要注意設(shè)置flags,我這里提供了兩種flags以供切換:
private static int canTouchFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; private static int notTouchFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
第一種是可觸摸不可聚焦模式,第二種是不可觸摸不可聚焦模式。其他的flags可以從api中查閱。
2.設(shè)置懸浮框的拖動(dòng)監(jiān)聽(tīng)事件:
private static View setUpView(final Context context, String showtxt) { View view = LayoutInflater.from(context).inflate(R.layout.layout_popwindow, null); TextView showTv = (TextView) view.findViewById(R.id.tv_showinpop); showTv.setText(showtxt); rl_drag_showinpop = (RelativeLayout) view.findViewById(R.id.rl_drag_showinpop); rl_drag_showinpop.setOnTouchListener(new View.OnTouchListener() { private float lastX; //上一次位置的X.Y坐標(biāo) private float lastY; private float nowX; //當(dāng)前移動(dòng)位置的X.Y坐標(biāo) private float nowY; private float tranX; //懸浮窗移動(dòng)位置的相對(duì)值 private float tranY; @Override public boolean onTouch(View v, MotionEvent event) { boolean ret = false; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 獲取按下時(shí)的X,Y坐標(biāo) lastX = event.getRawX(); lastY = event.getRawY(); ret = true; break; case MotionEvent.ACTION_MOVE: // 獲取移動(dòng)時(shí)的X,Y坐標(biāo) nowX = event.getRawX(); nowY = event.getRawY(); // 計(jì)算XY坐標(biāo)偏移量 tranX = nowX - lastX; tranY = nowY - lastY; params.x += tranX; params.y += tranY; //更新懸浮窗位置 mWindowManager.updateViewLayout(mView, params); //記錄當(dāng)前坐標(biāo)作為下一次計(jì)算的上一次移動(dòng)的位置坐標(biāo) lastX = nowX; lastY = nowY; break; case MotionEvent.ACTION_UP: break; } return ret; } });
這里要在down的時(shí)候記錄坐標(biāo),move事件中使用修改params坐標(biāo)進(jìn)行移動(dòng)。
3.設(shè)置懸浮框文字屬性:
public static void setShowTxt(String txt) { try { TextView showTv = (TextView) mView.findViewById(R.id.tv_showinpop); showTv.setText(txt); mWindowManager.updateViewLayout(mView, params); }catch (Exception e){ Log.d(TAG, "setShowTxt: 更新懸浮框錯(cuò)誤"); e.printStackTrace(); if(e.getMessage().contains("not attached to window manager")){ mWindowManager.addView(mView, params); } } }
4.更新懸浮框圖片顯示:
public static void setShowImg(Bitmap bitmap) { try { ImageView showImg = (ImageView) mView.findViewById(R.id.iv_showinpop); showImg.setImageBitmap(bitmap); mWindowManager.updateViewLayout(mView, params); }catch (Exception e){ Log.d(TAG, "setShowTxt: 更新懸浮框錯(cuò)誤"); e.printStackTrace(); if(e.getMessage().contains("not attached to window manager")){ mWindowManager.addView(mView, params); } } }
介紹完畢,整個(gè)類都封裝好了,代碼如下:
/** * 懸浮窗工具類 * created by Pumpkin at 17/3/28 */ public class WindowsUitlity { private static String TAG = WindowsUitlity.class.getSimpleName(); private static WindowManager mWindowManager = null; private static WindowManager.LayoutParams params; public static Boolean isShown = false; private static View mView = null; /** * 顯示彈出框 * * @param context */ @SuppressWarnings("WrongConstant") public static void showPopupWindow(final Context context, String showtxt) { if (isShown) { return; } isShown = true; // 獲取WindowManager mWindowManager = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); mView = setUpView(context, showtxt); params = new WindowManager.LayoutParams(); // 類型,系統(tǒng)提示以及它總是出現(xiàn)在應(yīng)用程序窗口之上。 params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT | WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY; // 設(shè)置flag int flags = canTouchFlags; // | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; // 如果設(shè)置了WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,彈出的View收不到Back鍵的事件 params.flags = flags; // 不設(shè)置這個(gè)彈出框的透明遮罩顯示為黑色 params.format = PixelFormat.TRANSLUCENT; // FLAG_NOT_TOUCH_MODAL不阻塞事件傳遞到后面的窗口 // 設(shè)置 FLAG_NOT_FOCUSABLE 懸浮窗口較小時(shí),后面的應(yīng)用圖標(biāo)由不可長(zhǎng)按變?yōu)榭砷L(zhǎng)按 // 不設(shè)置這個(gè)flag的話,home頁(yè)的劃屏?xí)袉?wèn)題 params.width = LayoutParams.WRAP_CONTENT; params.height = LayoutParams.WRAP_CONTENT; params.gravity = Gravity.TOP; mWindowManager.addView(mView, params); } private static int canTouchFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; private static int notTouchFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; /** * 設(shè)置是否可響應(yīng)點(diǎn)擊事件 * * @param isTouchable */ public static void setTouchable(boolean isTouchable) { if (isTouchable) { params.flags = canTouchFlags; } else { params.flags = notTouchFlags; } mWindowManager.updateViewLayout(mView, params); } /** * 隱藏彈出框 */ public static void hidePopupWindow() { if (isShown && null != mView) { mWindowManager.removeView(mView); isShown = false; } } public static void setShowTxt(String txt) { try { TextView showTv = (TextView) mView.findViewById(R.id.tv_showinpop); showTv.setText(txt); mWindowManager.updateViewLayout(mView, params); }catch (Exception e){ Log.d(TAG, "setShowTxt: 更新懸浮框錯(cuò)誤"); e.printStackTrace(); if(e.getMessage().contains("not attached to window manager")){ mWindowManager.addView(mView, params); } } } public static void setShowImg(Bitmap bitmap) { try { ImageView showImg = (ImageView) mView.findViewById(R.id.iv_showinpop); showImg.setImageBitmap(bitmap); mWindowManager.updateViewLayout(mView, params); }catch (Exception e){ Log.d(TAG, "setShowTxt: 更新懸浮框錯(cuò)誤"); e.printStackTrace(); if(e.getMessage().contains("not attached to window manager")){ mWindowManager.addView(mView, params); } } } static RelativeLayout rl_drag_showinpop; private static View setUpView(final Context context, String showtxt) { View view = LayoutInflater.from(context).inflate(R.layout.layout_popwindow, null); TextView showTv = (TextView) view.findViewById(R.id.tv_showinpop); showTv.setText(showtxt); rl_drag_showinpop = (RelativeLayout) view.findViewById(R.id.rl_drag_showinpop); rl_drag_showinpop.setOnTouchListener(new View.OnTouchListener() { private float lastX; //上一次位置的X.Y坐標(biāo) private float lastY; private float nowX; //當(dāng)前移動(dòng)位置的X.Y坐標(biāo) private float nowY; private float tranX; //懸浮窗移動(dòng)位置的相對(duì)值 private float tranY; @Override public boolean onTouch(View v, MotionEvent event) { boolean ret = false; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 獲取按下時(shí)的X,Y坐標(biāo) lastX = event.getRawX(); lastY = event.getRawY(); ret = true; break; case MotionEvent.ACTION_MOVE: // 獲取移動(dòng)時(shí)的X,Y坐標(biāo) nowX = event.getRawX(); nowY = event.getRawY(); // 計(jì)算XY坐標(biāo)偏移量 tranX = nowX - lastX; tranY = nowY - lastY; params.x += tranX; params.y += tranY; //更新懸浮窗位置 mWindowManager.updateViewLayout(mView, params); //記錄當(dāng)前坐標(biāo)作為下一次計(jì)算的上一次移動(dòng)的位置坐標(biāo) lastX = nowX; lastY = nowY; break; case MotionEvent.ACTION_UP: break; } return ret; } }); return view; } }
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
詳解Flutter中StatefulBuilder組件的使用
StatefulBuilder小部件可以在這些區(qū)域的狀態(tài)發(fā)生變化時(shí)僅重建某些小區(qū)域而無(wú)需付出太多努力。本文將來(lái)詳細(xì)講講它的使用,需要的可以參考一下2022-05-05Android實(shí)現(xiàn)拍照、選擇圖片并裁剪圖片功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)拍照、選擇圖片并裁剪圖片功能的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-05-05Android實(shí)現(xiàn)長(zhǎng)按圖片保存至相冊(cè)功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)長(zhǎng)按圖片保存至相冊(cè)功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03Android App中實(shí)現(xiàn)可以雙擊放大和縮小圖片功能的實(shí)例
這篇文章主要介紹了Android App中實(shí)現(xiàn)可以雙擊放大和縮小圖片功能的實(shí)例,文中的例子不能做到逐級(jí)放大但可以做到邊界控制和以觸摸點(diǎn)為中心進(jìn)行放大,需要的朋友可以參考下2016-03-03Android BLE 藍(lán)牙開(kāi)發(fā)之實(shí)現(xiàn)掃碼槍基于BLESSED開(kāi)發(fā)
這篇文章主要介紹了Android BLE 藍(lán)牙開(kāi)發(fā)之實(shí)現(xiàn)掃碼槍基于BLESSED開(kāi)發(fā),示例代碼介紹了第三方庫(kù)BLESSED for Android的使用,需要的朋友可以參考下2022-03-03Android studio 廣播的簡(jiǎn)單使用代碼詳解
這篇文章主要介紹了Android studio 廣播的簡(jiǎn)單使用,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04Flutter實(shí)現(xiàn)滾動(dòng)選擇數(shù)字
這篇文章主要為大家詳細(xì)介紹了Flutter實(shí)現(xiàn)滾動(dòng)選擇數(shù)字,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03