android實(shí)現(xiàn)可自由移動(dòng)、監(jiān)聽(tīng)點(diǎn)擊事件的懸浮窗
最近因?yàn)轫?xiàng)目需要,自己實(shí)現(xiàn)了個(gè)可以自由移動(dòng),并且長(zhǎng)按可以跳出一個(gè)控制播放的,大的懸浮窗。
好,開(kāi)始吧。首先我們先聊權(quán)限,懸浮窗需要在manifest中聲明一個(gè)權(quán)限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
然后呢,嗯,我們來(lái)講講關(guān)于懸浮窗實(shí)現(xiàn)的原理。
在Andriod中,所有的界面元素都要通過(guò)windowmanger來(lái)實(shí)現(xiàn),像Activity、Fragment等等這些也是在其上實(shí)現(xiàn)。因此,我們的懸浮窗自然要通過(guò)這個(gè)實(shí)現(xiàn)。
這個(gè)項(xiàng)目中,我們自定義了兩個(gè)懸浮窗view。我們以其中一個(gè)比較簡(jiǎn)單的為例:
我們自定義一個(gè)管理可以統(tǒng)一管理懸浮窗的類(lèi)MyWindowManager,負(fù)責(zé)創(chuàng)建,刪除懸浮窗
/**
* Created by shiwe on 2017/3/7.
* 懸浮窗管理
* 創(chuàng)建,移除
* 單例模式
*/
public class MyWindowManager {
private FloatNormalView normalView;
private FloatControlView controlView;
private static MyWindowManager instance;
private MyWindowManager() {
}
public static MyWindowManager getInstance() {
if (instance == null)
instance = new MyWindowManager();
return instance;
}
/**
* 創(chuàng)建小型懸浮窗
*/
public void createNormalView(Context context) {
if (normalView == null)
normalView = new FloatNormalView(context);
}
/**
* 移除懸浮窗
*
* @param context
*/
public void removeNormalView(Context context) {
if (normalView != null) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
windowManager.removeView(normalView);
normalView = null;
}
}
/**
* 創(chuàng)建小型懸浮窗
*/
public void createControlView(Context context) {
if (controlView == null)
controlView = new FloatControlView(context);
}
/**
* 移除懸浮窗
*
* @param context
*/
public void removeControlView(Context context) {
if (controlView != null) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
windowManager.removeView(controlView);
controlView = null;
}
}
}
然后看看我們自定義的一個(gè)view,其繼承自LinearLayout,我們?cè)趇nitLayoutParams初始化這個(gè)控件的位置等其他參數(shù);在initEvent方法中定義隨手指移動(dòng)的監(jiān)聽(tīng)事件以及長(zhǎng)按的監(jiān)聽(tīng)事件。
public class FloatNormalView extends LinearLayout {
private Context context = null;
private View view = null;
private ImageView ivShowControlView = null;
private WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
private static WindowManager windowManager;
private float mTouchStartX;
private float mTouchStartY;
private float x;
private float y;
private boolean initViewPlace = false;
private MyWindowManager myWindowManager;
private boolean isControlViewShowing = false;
public FloatNormalView(Context context) {
super(context);
this.context = context;
myWindowManager = MyWindowManager.getInstance();
LayoutInflater.from(context).inflate(R.layout.float_normal_view, this);
view = findViewById(R.id.ll_float_normal);
ivShowControlView = (ImageView) findViewById(R.id.iv_show_control_view);
windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
initLayoutParams();
initEvent();
}
/**
* 初始化參數(shù)
*/
private void initLayoutParams() {
//屏幕寬高
int screenWidth = windowManager.getDefaultDisplay().getWidth();
int screenHeight = windowManager.getDefaultDisplay().getHeight();
//總是出現(xiàn)在應(yīng)用程序窗口之上。
lp.type = WindowManager.LayoutParams.TYPE_PHONE;
// FLAG_NOT_TOUCH_MODAL不阻塞事件傳遞到后面的窗口
// FLAG_NOT_FOCUSABLE 懸浮窗口較小時(shí),后面的應(yīng)用圖標(biāo)由不可長(zhǎng)按變?yōu)榭砷L(zhǎng)按,不設(shè)置這個(gè)flag的話,home頁(yè)的劃屏?xí)袉?wèn)題
lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
//懸浮窗默認(rèn)顯示的位置
lp.gravity = Gravity.START | Gravity.TOP;
//指定位置
lp.x = screenWidth - view.getLayoutParams().width * 2;
lp.y = screenHeight / 2 + view.getLayoutParams().height * 2;
//懸浮窗的寬高
lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
lp.format = PixelFormat.TRANSPARENT;
windowManager.addView(this, lp);
}
/**
* 設(shè)置懸浮窗監(jiān)聽(tīng)事件
*/
private void initEvent() {
ivShowControlView.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
if (!isControlViewShowing) {
myWindowManager.createControlView(context);
isControlViewShowing = true;
} else {
myWindowManager.removeControlView(context);
isControlViewShowing = false;
}
return true;
}
});
view.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (!initViewPlace) {
initViewPlace = true;
//獲取初始位置
mTouchStartX += (event.getRawX() - lp.x);
mTouchStartY += (event.getRawY() - lp.y);
} else {
//根據(jù)上次手指離開(kāi)的位置與此次點(diǎn)擊的位置進(jìn)行初始位置微調(diào)
mTouchStartX += (event.getRawX() - x);
mTouchStartY += (event.getRawY() - y);
}
break;
case MotionEvent.ACTION_MOVE:
// 獲取相對(duì)屏幕的坐標(biāo),以屏幕左上角為原點(diǎn)
x = event.getRawX();
y = event.getRawY();
updateViewPosition();
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
});
}
/**
* 更新浮動(dòng)窗口位置
*/
private void updateViewPosition() {
lp.x = (int) (x - mTouchStartX);
lp.y = (int) (y - mTouchStartY);
windowManager.updateViewLayout(this, lp);
}
最后,只需要在Activity中調(diào)用mywindowManager中調(diào)用createxxx方法就可以。
public class MainActivity extends AppCompatActivity {
MyWindowManager myWindowManager;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myWindowManager = MyWindowManager.getInstance();
myWindowManager.createNormalView(this.getApplicationContext());
}
}
最后,附上demo項(xiàng)目的下載地址: android實(shí)現(xiàn)懸浮窗
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Native.js獲取監(jiān)聽(tīng)開(kāi)關(guān)等操作Android藍(lán)牙設(shè)備實(shí)例代碼
- android監(jiān)聽(tīng)View加載完成的示例講解
- android View 繪制完成監(jiān)聽(tīng)的實(shí)現(xiàn)方法
- Android控件Spinner實(shí)現(xiàn)下拉列表及監(jiān)聽(tīng)功能
- Android 7.0 監(jiān)聽(tīng)網(wǎng)絡(luò)變化的示例代碼
- Android EditText 監(jiān)聽(tīng)用戶輸入完成的實(shí)例
- android輸入框內(nèi)容改變的監(jiān)聽(tīng)事件實(shí)例
- Android監(jiān)聽(tīng)橫豎屏切換功能
- Android編程自定義View時(shí)添加自己的監(jiān)聽(tīng)器示例
- Android Usb設(shè)備的監(jiān)聽(tīng)(Dev)外設(shè)端口的判定以及耳機(jī)的插拔
相關(guān)文章
Android使用OkHttp進(jìn)行網(wǎng)絡(luò)同步異步操作
這篇文章主要為大家詳細(xì)介紹了Android使用OkHttp進(jìn)行網(wǎng)絡(luò)同步異步操作,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07
Android基準(zhǔn)配置文件Baseline?Profile方案提升啟動(dòng)速度
這篇文章主要為大家介紹了Android基準(zhǔn)配置文件Baseline?Profile方案提升啟動(dòng)速度示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
Android?LinearLayout快速設(shè)置每個(gè)item間隔
這篇文章主要介紹了Android?LinearLayout快速設(shè)置每個(gè)item間隔的相關(guān)資料,需要的朋友可以參考下2023-07-07
一文帶你搞清楚Android游戲發(fā)行切包資源ID那點(diǎn)事
這篇文章主要介紹了Android 解決游戲發(fā)行切包資源ID的一些問(wèn)題,幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2023-05-05
Android adb命令中pm工具的作用及用法說(shuō)明
這篇文章主要介紹了Android adb命令中pm工具的作用及用法說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08
Android CameraX打開(kāi)攝像頭預(yù)覽教程
大家好,本篇文章主要講的是Android CameraX打開(kāi)攝像頭預(yù)覽教程,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下2021-12-12
解決video標(biāo)簽在安卓webview下無(wú)法自動(dòng)播放問(wèn)題
這篇文章主要介紹了video標(biāo)簽在安卓webview下無(wú)法自動(dòng)播放問(wèn)題的解決方法 ,需要的朋友可以參考下2014-03-03
Android簡(jiǎn)單的短信驗(yàn)證功能的實(shí)現(xiàn)代碼
這篇文章主要介紹了Android簡(jiǎn)單的短信驗(yàn)證功能的實(shí)現(xiàn)代碼,本文是小編使用sdk過(guò)程的一些心得,需要的朋友可以參考下2018-07-07

