日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

安卓PopupWindow使用详解与源码分析(附项目实例)

發布時間:2023/12/15 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 安卓PopupWindow使用详解与源码分析(附项目实例) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

基本用法

首先定義彈窗的Layout文件

res/layout/popup_window.xml

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#44000000"android:gravity="center_vertical"android:orientation="horizontal"android:padding="5dp"><ImageView android:id="@+id/popup_icon"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@drawable/ic_launcher" /><TextView android:id="@+id/popup_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/app_name" /></LinearLayout>

顯示

private PopupWindow pop;private void showPopupWindowBasic() {View rootView = getLayoutInflater().inflate(R.layout.popup_window, null);mPopupText = (TextView) rootView.findViewById(R.id.popup_text);mPopupText.setText("PopupTextBasic");mPopupWindow = new PopupWindow(rootView,ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);mPopupWindow.showAsDropDown(mTextView); }

上述代碼中,在PopupWindow實例化時指定了顯示的View,寬高均為WRAP_CONTENT,也可以指定固定的尺寸(直接傳入int型的px像素值即可)。

注意:這里通過Java代碼設置的PopupWindow尺寸會直接覆蓋Layout文件中頂層控件的尺寸。如果希望能直接在xml中指定彈窗的固定尺寸,且修改尺寸時不需要修改Java代碼,從而讓代碼更加規范,可以考慮對Layout指定尺寸的同時,在其外層再嵌套一個FrameLayout,Java代碼中指定PopupWindow寬高均為WRAP_CONTENT,即:

<FrameLayout android:layout_width="wrap_content"android:layout_height="wrap_content"><LinearLayout android:layout_width="100dp"android:layout_height="50dp"><!-- ... --></LinearLayout> </FrameLayout>

隱藏

private void dismissPopupWindow() {if (mPopupWindow != null) {mPopupWindow.dismiss();} }

優化

在上述代碼中,每次調用show方法都會生成一個新的PopupWindow實例,并且必須通過調用此實例的dismiss方法才能隱藏彈窗。因此,如果連續多次調用show而沒有調用dismiss,就會生成多個實例,并且只有最后一個實例能被dismiss。

改進后的show方法如下。對于已經初始化的PopupWindow,當調用了setText等會改變彈窗內容和位置的方法后,需要調用update方法更新。update方法的參數和show方法類似。

private void showPopupWindowOptimized() {// 如果正在顯示則不處理if (mPopupWindow != null && mPopupWindow.isShowing()) {return;}// 如果沒有初始化則初始化if (mPopupWindow == null) {View rootView = getLayoutInflater().inflate(R.layout.popup_window, null);mPopupText = (TextView) rootView.findViewById(R.id.popup_text);mPopupWindow = new PopupWindow(rootView,ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);}// 設置文本mPopupText.setText("PopupText");// 刷新內容mPopupWindow.update();// 顯示mPopupWindow.showAsDropDown(mTextView); }

注意事項

在使用PopupWindow時,要注意dismiss方法的調用。當Activity被關閉時,如果PopupWindow仍在顯示,此時就會拋出Window Leaked異常,原因是PopupWindow附屬于Activity的WindowManager,而Activity被關閉了,窗體也不再存在。所以應該覆寫onStop方法如下,確保在Activity退出前先關閉PopupWindow。

@Override protected void onStop() {dismissPopupWindow();super.onStop(); }

定義彈窗動畫

首先定義彈窗顯示、隱藏時的動畫
res/anim/popup_window_in.xml

<?xml version="1.0" encoding="utf-8"?> <alpha xmlns:android="http://schemas.android.com/apk/res/android"android:duration="300"android:fromAlpha="0"android:toAlpha="1" />

res/anim/popup_window_out.xml

<?xml version="1.0" encoding="utf-8"?> <alpha xmlns:android="http://schemas.android.com/apk/res/android"android:duration="300"android:fromAlpha="1"android:toAlpha="0" />

然后在Style中引用動畫
res/values/styles.xml

<style name="popup_window"><item name="android:windowEnterAnimation">@anim/popup_window_in</item><item name="android:windowExitAnimation">@anim/popup_window_out</item> </style>

最后,在PopupWindow初始化的代碼中設置即可

mPopupWindow.setAnimationStyle(R.style.popup_window);

事件響應

默認情況下,PopupWindow彈出后只有在調用dismiss時才會隱藏。彈窗顯示的過程中:
- 彈窗區域可以響應點擊事件,例如Button可被點擊并響應;
- Activity中彈窗以外的區域,也可以進行點擊操作;
- 按鍵事件會被Activity響應,例如按返回鍵會退出Activity。

點擊彈窗以外區域隱藏彈窗

如果想實現點擊彈窗以外區域隱藏彈窗,只需在初始化代碼中添加以下代碼即可。注意,需要給PopupWindow設置一個背景才能生效,這里設置的是透明的ColorDrawable。

mPopupWindow.setOutsideTouchable(true); mPopupWindow.setBackgroundDrawable(new ColorDrawable(0));

設置彈窗可獲取焦點

默認的PopupWindow不能獲取焦點,根據在模擬器上的實際測試,PopupWindow窗口中:
- 部分機型中,ListView的Item不能響應點擊事件
- EditText不能輸入文本,因為按鍵事件會被Activity響應
- Button可響應點擊,但由于不能獲取焦點,因此點擊時不會顯示默認點擊動畫效果
- ……

通過以下代碼可以設置PopupWindow可獲取焦點。這里同樣要給PopupWindow設置一個背景。

mPopupWindow.setFocusable(true); mPopupWindow.setBackgroundDrawable(new ColorDrawable(0));

當設置可獲取焦點后,按鍵操作會被PopupWindow攔截(HOME、電源鍵除外),因此可以在EditText中輸入文本。同時,返回鍵也會被攔截,按返回鍵時先隱藏PopupWindow彈窗,再按返回鍵時Activity才會退出。

顯示位置

主要有兩種類型的顯示方法:showAsDropDown和showAtLocation。

showAsDropDown

public void showAsDropDown(View anchor, int xoff, int yoff, int gravity);

  • 彈窗會顯示在anchor控件的正下方。
  • 如果指定了xoff和yoff,則會在原有位置向右偏移xoff,向下偏移yoff。
  • 如果指定gravity為Gravity.RIGHT,則彈窗和控件右對齊;否則左對齊。注意,計算右對齊時使用了PopupWindow的寬度,如果指定的寬度不是固定值,則計算會失效(可以從源碼中看出來)。
  • 如果彈窗位置超出了Window的范圍,會自動處理使其處于Window中。
  • 如果anchor可以滾動,則滾動過程中,PopupWindow可以自動更新位置,跟隨anchor控件。

如圖是showAsDropDown使用默認值即左對齊的效果。

showAtLocation

public void showAtLocation(View parent, int gravity, int x, int y);

  • 彈窗會顯示在Activity的Window中。
  • parent可以為Activity中的任意一個View(最終的效果一樣),會通過這個View找到其父Window,也就是Activity的Window。
  • gravity,默認為Gravity.NO_GRAVITY,等效于Gravity.LEFT | Gravity.TOP。
  • x, y,邊距。這里的x,y表示距離Window邊緣的距離,方向由Gravity決定。例如:設置了Gravity.TOP,則y表示與Window上邊緣的距離;而如果設置了Gravity.BOTTOM,則y表示與下邊緣的距離。
  • 如果彈窗位置超出了Window的范圍,會自動處理使其處于Window中。

顯示位置的計算

實際應用中,自帶方法的默認值很難滿足要求,經常需要自行計算PopupWindow的顯示位置。對于固定尺寸的PopupWindow,計算起來并不難,而對于寬高設置為WRAP_CONTENT尺寸不確定的PopupWindow以及一些特殊情況(例如帶箭頭彈窗箭頭位置的控制),計算時會出現一個問題,就是PopupWindow顯示之前,獲取到的控件寬高都是0,因此沒法正確計算位置。

而如果了解控件的尺寸計算流程,解決方案也比較容易,可以在初始化PopupWindow時調用下面的代碼觸發控件計算尺寸。其中rootView為指定給PopupWindow顯示的View。

// 對控件尺寸進行測量 rootView.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);

調用完成后,通過調用View.getMeasuredWidth()和View.getMeasuredHeight()即可獲取控件的尺寸。

文章末尾的附件項目中實現了一個帶箭頭的PopupWindow彈窗,為了實現箭頭恰好能指向頁面底部三個Tab的效果,將箭頭作為獨立的View放在LinearLayout中,通過計算對其設置合適的gravity和margin。具體見源碼。

PopupWindow源碼分析

show

閱讀PopupWindow的源碼可以發現,方法showAsDropDown首先會調用registerForScrollChanged()方法注冊監聽View anchor的滾動,從而及時更新彈窗的位置,使其能跟隨View的滾動。而showAtLocation會調用unregisterForScrollChanged()取消注冊監聽。

然后會調用WindowManager.LayoutParams createPopupLayout(IBinder token)創建一個WindowManager.LayoutParams對象,這個靜態內部類繼承自ViewGroup.LayoutParams。在createPopupLayout中通過調用computeFlags,根據設置的Touchable、OutsideTouchable、Focusable等屬性計算WindowManager.LayoutParams.flag屬性。

WindowManager.LayoutParams的官方文檔如下
http://developer.android.com/reference/android/view/WindowManager.LayoutParams.html

計算完成后,會調用preparePopup方法。這里比較重要的一點是,如果給PopupWindow設置了背景,則mBackground != null,此時會在PopupWindow的View對象外嵌套一層PopupViewContainer,而PopupViewContainer繼承自FrameLayout并重寫了按鍵和觸摸事件攔截方法。因此前面提到點擊彈窗外則隱藏彈窗時,需要給PopupWindow設置一個背景。

private void preparePopup(WindowManager.LayoutParams p) {if (mContentView == null || mContext == null || mWindowManager == null) {throw new IllegalStateException("You must specify a valid content view by "+ "calling setContentView() before attempting to show the popup.");}if (mBackground != null) {final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();int height = ViewGroup.LayoutParams.MATCH_PARENT;if (layoutParams != null &&layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {height = ViewGroup.LayoutParams.WRAP_CONTENT;}// when a background is available, we embed the content view// within another view that owns the background drawablePopupViewContainer popupViewContainer = new PopupViewContainer(mContext);PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height);popupViewContainer.setBackgroundDrawable(mBackground);popupViewContainer.addView(mContentView, listParams);mPopupView = popupViewContainer;} else {mPopupView = mContentView;}mPopupViewInitialLayoutDirectionInherited =(mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);mPopupWidth = p.width;mPopupHeight = p.height; }

然后會繼續計算WindowManager.LayoutParams中的gravity、width、height等參數,最后調用invokePopup方法。

private void invokePopup(WindowManager.LayoutParams p) {if (mContext != null) {p.packageName = mContext.getPackageName();}mPopupView.setFitsSystemWindows(mLayoutInsetDecor);setLayoutDirectionFromAnchor();mWindowManager.addView(mPopupView, p); }

invokePopup中最終調用的是WindowManager.addView(View view, ViewGroup.LayoutParams params)。傳入的參數有兩個,一個是PopupWindow的mPopupView,另一個是計算好的WindowManager.LayoutParams對象。于是View被添加到WindowManager窗口對象中從而顯示出來。

dismiss

調用dismiss隱藏PopupWindow時,最終調用了WindowManager.removeViewImmediate(View view)方法,其本質是從Window中移除View。

update

調用update更新PopupWindow時,會根據傳入參數重新計算LayoutParams,計算過程和show方法類似,然后調用WindowManager.updateViewLayout(View view, ViewGroup.LayoutParams params)方法更新View。

WindowManager.LayoutParams

前面提到退出Activity時,要確保PopupWindow隱藏,因為PopupWindow依附于Activity的Window。如果不使用PopupWindow,而直接調用WindowManager添加懸浮窗,通過設置WindowManager.LayoutParams,不僅可以自行實現類似PopupWindow的效果,還可以結合后臺Service實現系統級懸浮窗,類似一些優化軟件在桌面懸浮窗的效果,而不局限于在一個App或者一個Activity中彈窗。

實現系統彈窗只需設置WindowManager.LayoutParams.type參數即可。

params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;

同時需要在Manifest中添加權限:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />

文章末尾的附件項目中也有提供一個利用Service顯示桌面懸浮窗的簡單例子。更詳細的介紹可以通過搜索Android 桌面懸浮窗找到。

附件項目地址
https://github.com/jzj1993/PopupWindow

本文由jzj1993原創,轉載請注明來源:http://www.paincker.com/android-popup-window

總結

以上是生活随笔為你收集整理的安卓PopupWindow使用详解与源码分析(附项目实例)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。