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頁的劃屏?xí)袉栴}
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)聽事件:
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頁的劃屏?xí)袉栴}
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)文章
Kotlin startActivity跳轉(zhuǎn)Activity實(shí)現(xiàn)流程詳解
在Android當(dāng)中,Activity的跳轉(zhuǎn)有兩種方法,第一個(gè)是利用startActivity(Intent intent);的方法,第二個(gè)則是利用startActivityForResult(Intent intent,int requestCode);的方法,從字面上來看,這兩者之間的差別只在于是否有返回值的區(qū)別,實(shí)際上也確實(shí)只有這兩種區(qū)別2022-12-12
Android之在linux終端執(zhí)行shell腳本直接打印當(dāng)前運(yùn)行app的日志的實(shí)現(xiàn)方法
今天小編就為大家分享一篇關(guān)于Android之在linux終端執(zhí)行shell腳本直接打印當(dāng)前運(yùn)行app的日志的實(shí)現(xiàn)方法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-02-02
Jetpack?Compose?的新型架構(gòu)?MVI使用詳解
這篇文章主要介紹了Jetpack?Compose?的新型架構(gòu)?MVI使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
Android開發(fā)之sqlite3命令行簡(jiǎn)單使用方法
這篇文章主要介紹了Android開發(fā)之sqlite3命令行簡(jiǎn)單使用方法,分析了Android增刪改查等常用sqlite3的數(shù)據(jù)庫(kù)操作命令使用方法,需要的朋友可以參考下2016-02-02
全面解析Android中對(duì)EditText輸入實(shí)現(xiàn)監(jiān)聽的方法
這篇文章主要介紹了Android中對(duì)EditText輸入實(shí)現(xiàn)監(jiān)聽的方法,包括一個(gè)仿iOS的帶清除功能的ClearEditText輸入框控件的詳細(xì)使用介紹,需要的朋友可以參考下2016-04-04
使用PHP開發(fā)Android應(yīng)用程序技術(shù)介紹
這篇文章主要介紹了使用PHP開發(fā)Android應(yīng)用程序技術(shù)介紹,本文講解了安裝PHP for Android、設(shè)置PHP for Android開發(fā)環(huán)境、使用PHP構(gòu)建Android應(yīng)用程序,需要的朋友可以參考下2015-03-03
Android加載html中svg格式圖片進(jìn)行顯示
這篇文章主要為大家詳細(xì)介紹了Android加載html中svg格式圖片進(jìn)行顯示,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05
Android倒計(jì)時(shí)的開始與停止 剩余時(shí)分秒的展示
這篇文章主要為大家詳細(xì)介紹了Android倒計(jì)時(shí)的開始與停止,剩余時(shí)分秒的展示,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-09-09

