android實現(xiàn)可自由移動、監(jiān)聽點擊事件的懸浮窗
最近因為項目需要,自己實現(xiàn)了個可以自由移動,并且長按可以跳出一個控制播放的,大的懸浮窗。
好,開始吧。首先我們先聊權(quán)限,懸浮窗需要在manifest中聲明一個權(quán)限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
然后呢,嗯,我們來講講關(guān)于懸浮窗實現(xiàn)的原理。
在Andriod中,所有的界面元素都要通過windowmanger來實現(xiàn),像Activity、Fragment等等這些也是在其上實現(xiàn)。因此,我們的懸浮窗自然要通過這個實現(xiàn)。
這個項目中,我們自定義了兩個懸浮窗view。我們以其中一個比較簡單的為例:
我們自定義一個管理可以統(tǒng)一管理懸浮窗的類MyWindowManager,負責創(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; } } }
然后看看我們自定義的一個view,其繼承自LinearLayout,我們在initLayoutParams初始化這個控件的位置等其他參數(shù);在initEvent方法中定義隨手指移動的監(jiān)聽事件以及長按的監(jiān)聽事件。
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)在應用程序窗口之上。 lp.type = WindowManager.LayoutParams.TYPE_PHONE; // FLAG_NOT_TOUCH_MODAL不阻塞事件傳遞到后面的窗口 // FLAG_NOT_FOCUSABLE 懸浮窗口較小時,后面的應用圖標由不可長按變?yōu)榭砷L按,不設(shè)置這個flag的話,home頁的劃屏會有問題 lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; //懸浮窗默認顯示的位置 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)聽事件 */ 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ù)上次手指離開的位置與此次點擊的位置進行初始位置微調(diào) mTouchStartX += (event.getRawX() - x); mTouchStartY += (event.getRawY() - y); } break; case MotionEvent.ACTION_MOVE: // 獲取相對屏幕的坐標,以屏幕左上角為原點 x = event.getRawX(); y = event.getRawY(); updateViewPosition(); break; case MotionEvent.ACTION_UP: break; } return true; } }); } /** * 更新浮動窗口位置 */ 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項目的下載地址: android實現(xiàn)懸浮窗
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
- Native.js獲取監(jiān)聽開關(guān)等操作Android藍牙設(shè)備實例代碼
- android監(jiān)聽View加載完成的示例講解
- android View 繪制完成監(jiān)聽的實現(xiàn)方法
- Android控件Spinner實現(xiàn)下拉列表及監(jiān)聽功能
- Android 7.0 監(jiān)聽網(wǎng)絡變化的示例代碼
- Android EditText 監(jiān)聽用戶輸入完成的實例
- android輸入框內(nèi)容改變的監(jiān)聽事件實例
- Android監(jiān)聽橫豎屏切換功能
- Android編程自定義View時添加自己的監(jiān)聽器示例
- Android Usb設(shè)備的監(jiān)聽(Dev)外設(shè)端口的判定以及耳機的插拔
相關(guān)文章
Android使用OkHttp進行網(wǎng)絡同步異步操作
這篇文章主要為大家詳細介紹了Android使用OkHttp進行網(wǎng)絡同步異步操作,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-07-07Android基準配置文件Baseline?Profile方案提升啟動速度
這篇文章主要為大家介紹了Android基準配置文件Baseline?Profile方案提升啟動速度示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02Android?LinearLayout快速設(shè)置每個item間隔
這篇文章主要介紹了Android?LinearLayout快速設(shè)置每個item間隔的相關(guān)資料,需要的朋友可以參考下2023-07-07一文帶你搞清楚Android游戲發(fā)行切包資源ID那點事
這篇文章主要介紹了Android 解決游戲發(fā)行切包資源ID的一些問題,幫助大家更好的理解和學習使用Android,感興趣的朋友可以了解下2023-05-05