android中的dialog,以及activiy形式的dialog均是模態對話框,
對話框不消失時,不能對其他頁面進行操作,也就是其他頁面不能獲得焦點。
而
PopupWindow是
非模態對話框
,對話框顯示的時候,其他界面仍然可以獲得焦點,仍然可以進行點擊等操作,同時也可以對
對話框
進行點擊等操作。
很好的例子就是輸入法,通過查看源碼就可以看到,其界面是幾個popupwindow組成的。
三個關鍵設置
// 如果不設置PopupWindow的背景,有些版本就會出現一個問題:無論是點擊外部區域還是Back鍵都無法dismiss彈框
popupWindow.setBackgroundDrawable(new ColorDrawable());
// setOutsideTouchable設置生效的前提是setTouchable(true)和setFocusable(false)
popupWindow.setOutsideTouchable(true);
// 設置為true之后,PopupWindow內容區域 才可以響應點擊事件
popupWindow.setTouchable(true);
Activity
public class MainActivity extends ListActivity {
private PopupWindow pop;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String[] array = { "在指定view的正左下方,無偏移", //
"相對指定view正左下方,有偏移",//
"Gravity.NO_GRAVITY:相對屏幕左上角,注意包含狀態欄區域",//
"Gravity.TOP:相對屏幕正上方居中",//
"pop不會偏移出屏幕,偏移值過大時取臨界值",//
"Gravity.BOTTOM:相對屏幕正下方居中,注意包含虛擬按鍵區域",//
"Gravity.BOTTOM:此時yoff為正時表示向上偏移(而非向下偏移)",//
"出現在View的任意位置", };
setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, new ArrayList<String>(Arrays.asList(array))));
pop = new PopupWindow(this);
pop.setFocusable(true);//必備設置1
pop.setBackgroundDrawable(new ColorDrawable(0x00ff0000));//必備設置2
pop.setOutsideTouchable(true);//setFocusable(true)后這個設置就沒用了
pop.setAnimationStyle(R.style.popAniStyle);//設置動畫所對應的style
}
private boolean b;
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
b = !b;
if (b) pop.setContentView(LayoutInflater.from(this).inflate(R.layout.view1, null));
else pop.setContentView(LayoutInflater.from(this).inflate(R.layout.view2, null));
switch (position) {
case 0://在指定view的正左下方,無偏移
pop.setWidth(LinearLayout.LayoutParams.WRAP_CONTENT);//必須設置寬和高
pop.setHeight(LinearLayout.LayoutParams.WRAP_CONTENT);
pop.showAsDropDown(v);
break;
case 1://相對指定view正左下方的位置,有偏移
pop.setWidth(DensityUtils.getScreenWidth(this) - 20);
pop.setHeight(LinearLayout.LayoutParams.WRAP_CONTENT);
pop.showAsDropDown(v, 20, 10);
break;
case 2://相對於父控件的某個相對位置的偏移,默認的Gravity.NO_GRAVITY的參照物為【屏幕左上角】,注意包含狀態欄區域
case 3://若偏移值大於pop偏出屏幕的臨界值,將取臨界值。也即pop不可能會偏移出屏幕
case 4://不管view設置的是哪一個,參照物都是整個屏幕的根布局
pop.setWidth(DensityUtils.getScreenWidth(this) - 20);
pop.setHeight(20);
if (position == 2) pop.showAtLocation(v, Gravity.NO_GRAVITY, 20, DensityUtils.getStatusBarHeight(this) + 10);//Gravity.NO_GRAVITY:相對屏幕左上角
else if (position == 3) pop.showAtLocation(v, Gravity.TOP, 10, DensityUtils.getStatusBarHeight(this) + 10);//Gravity.TOP:相對屏幕正上方居中
else pop.showAtLocation(v, Gravity.TOP, 100, DensityUtils.getStatusBarHeight(this));//臨界值為 20/2=10,偏移值大於10時僅偏移10
break;
case 5://Gravity.BOTTOM:相對屏幕正下方居中,注意包含虛擬按鍵區域,並且此時yoff為正時表示向上偏移(而非向下偏移)
case 6://
pop.setWidth(DensityUtils.getScreenWidth(this) - 20);
pop.setHeight(LinearLayout.LayoutParams.WRAP_CONTENT);
if (position == 5) pop.showAtLocation(v, Gravity.BOTTOM, 0, DensityUtils.getBottomBarHeight(this));//Gravity.BOTTOM:相對屏幕正下方居中
else if (position == 6) pop.showAtLocation(v, Gravity.BOTTOM, -10, DensityUtils.getBottomBarHeight(this) + 10);//此時yoff為正時表示向上偏移
break;
case 7://
startActivity(new Intent(this, SecondActivity.class));
break;
}
}
}
Activity2
public class SecondActivity extends Activity {
private int num = 0;
private PopupWindow pop;
private int[] location = new int[2];;
private View contentView;
@SuppressLint("InflateParams")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout);
contentView = LayoutInflater.from(this).inflate(R.layout.view2, null);
pop = new PopupWindow(contentView, LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
pop.setFocusable(true);//必備設置1
pop.setBackgroundDrawable(new ColorDrawable(0x00ff0000));//必備設置2
pop.setOutsideTouchable(true);//setFocusable(true)后這個設置就沒用了
pop.setAnimationStyle(R.style.popAniStyle);//設置動畫所對應的style
}
public void onClick(View v) {
v.getLocationOnScreen(location);// 獲取錨點View在屏幕上的左上角坐標位置
switch (num % 4) {
case 0://右下
pop.showAtLocation(v, Gravity.NO_GRAVITY, location[0] + v.getWidth(), location[1] + v.getHeight());
break;
case 1://右上
pop.showAtLocation(v, Gravity.NO_GRAVITY, location[0] + v.getWidth(), location[1] - contentView.getHeight());
break;
case 2://左上
pop.showAtLocation(v, Gravity.NO_GRAVITY, location[0] - contentView.getWidth(), location[1] - contentView.getHeight());
break;
default://左下
pop.showAtLocation(v, Gravity.NO_GRAVITY, location[0] - contentView.getWidth(), location[1] + v.getHeight());
break;
}
Toast.makeText(this, num % 4 + "", Toast.LENGTH_SHORT).show();
num++;
}
}
工具類
public class DensityUtils {
//******************************************************************************************
// 單位轉換
//******************************************************************************************
/**像素密度*/
public static float getDisplayMetrics(Context context) {
return context.getResources().getDisplayMetrics().density;
}
/** dp 轉成為 px */
public static int dp2px(Context context, float dpValue) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, context.getResources().getDisplayMetrics());
}
/** px 轉成為 dp */
public static int px2dp(Context context, float pxValue) {
return (int) (pxValue / getDisplayMetrics(context) + 0.5f);
}
/** sp轉px */
public static int sp2px(Context context, float spVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spVal, context.getResources().getDisplayMetrics());
}
/** px轉sp */
public static float px2sp(Context context, float pxVal) {
return (pxVal / context.getResources().getDisplayMetrics().scaledDensity);
}
//******************************************************************************************
// 屏幕寬高
//******************************************************************************************
/** 獲取屏幕寬 */
public static int getScreenWidth(Context context) {
DisplayMetrics metric = new DisplayMetrics();
((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(metric);
return metric.widthPixels;
}
/** 獲取屏幕高,
包含狀態欄,但不包含虛擬按鍵
,如1920屏幕只有1794 */
public static int getScreenHeight(Context context) {
DisplayMetrics metric = new DisplayMetrics();
((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(metric);
return metric.heightPixels;
}
/** 獲取屏幕寬 */
public static int getScreenWidth2(Context context) {
Point point = new Point();
((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getSize(point);
return point.x;
}
/** 獲取屏幕高,包含狀態欄,但不包含某些手機最下面的【HOME鍵那一欄】,如1920屏幕只有1794 */
public static int getScreenHeight2(Context context) {
Point point = new Point();
((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getSize(point);
return point.y;
}
/** 獲取屏幕原始尺寸高度,包括狀態欄以及虛擬功能鍵高度 */
public static int getAllScreenHeight(Context context) {
Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
try {
DisplayMetrics displayMetrics = new DisplayMetrics();
Method method = Class.forName("android.view.Display").getMethod("getRealMetrics", DisplayMetrics.class);
method.invoke(display, displayMetrics);
return displayMetrics.heightPixels;
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
//******************************************************************************************
// 狀態欄、標題欄、虛擬按鍵
//******************************************************************************************
/** 狀態欄高度,單位px,一般為25dp */
public static int getStatusBarHeight(Context context) {
int height = 0;
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
height = context.getResources().getDimensionPixelSize(resourceId);
}
return height;
}
/** 狀態欄高度,單位px,【注意】要在onWindowFocusChanged中獲取才可以 */
public static int getStatusBarHeight2(Activity activity) {
Rect rect = new Rect();
//DecorView是Window中的最頂層view,可以從DecorView獲取到程序顯示的區域,包括標題欄,但不包括狀態欄。所以狀態欄的高度即為顯示區域的top坐標值
activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
return rect.top;
}
/**標題欄的高度,【注意】要在onWindowFocusChanged中獲取才可以*/
public static int getTitleBarHeight(Activity activity) {
int contentTop = activity.getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTop();
return contentTop - getStatusBarHeight(activity);
}
/**獲取 虛擬按鍵的高度 */
public static int getBottomBarHeight(Context context) {
return getAllScreenHeight(context) - getScreenHeight(context);
}
}
常用API
構造方法
- public PopupWindow(View contentView)
- public PopupWindow(Context context)
- public PopupWindow(View contentView, int width, int height)
- public PopupWindow(View contentView, int width, int height, boolean focusable)

參數說明
- contentView為要顯示的view;PopupWindow沒有默認布局,它不會像AlertDialog那樣只setTitle就能彈出來一個框,所以contentView必須設置。
- width和height為要顯示的view的寬和高,值為像素值或MATCHT_PARENT、WRAP_CONTENT;如果View是從xml得到的,那么xml的第一層view的大小屬性將被忽略;必須設置寬和高,否則顯示不出來。
- focusable為是否能獲得焦點
舉例
View contentView = LayoutInflater.from(
this)
.inflate(
R.layout.popuplayout, null)
;
方法1:PopupWindow popupWindow = new PopupWindow(
contentview,LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT)
;
方法
2:
PopupWindow popupWindow = new PopupWindow(
contentview)
;
方法3:PopupWindwo popWnd = PopupWindow (context);
popWnd.setContentView(contentView);
popupWindow.setWidth(LayoutParams.
MATCHT_PARENT)
;
popupWindow.setHeight(LayoutParams.WRAP_CONTENT);
show方法
- showAsDropDown(View anchor) 相對某個控件的位置(正左下方),無偏移。最終調用的是下面的方法
- showAsDropDown(View anchor, int xoff, int yoff) 相對某個控件的位置,有偏移,xoff表示x軸的偏移,正值表示向左,負值表示向右;yoff表示相對y軸的偏移,正值是向下,負值是向上;最終調用的是下面的方法
- showAsDropDown(View anchor, int xoff, int yoff, int gravity) 【注:這個方法貌似不起作用】相對某個控件的某個位置,有偏移。gravity的默認值為DEFAULT_ANCHORED_GRAVITY= Gravity.TOP | Gravity.START
- showAtLocation(View parent, int gravity, int x, int y) 相對於父控件(不管view設置的是哪一個,參照物都是整個屏幕的根布局)的某個位置(例如正中央Gravity.CENTER,下方Gravity.BOTTOM等),可以設置偏移或無偏移。

/**
* Displays the content view in a popup window anchored to the corner of
* another view. The window is positioned according to the specified
* gravity and offset by the specified x and y coordinates.
* <p>
* If there is not enough room on screen to show the popup in its entirety,
* this method tries to find a parent scroll view to scroll. If no parent
* view can be scrolled, the specified vertical gravity will be ignored and
* the popup will anchor itself such that it is visible.
* <p>
* If the view later scrolls to move <code>anchor</code> to a different
* location, the popup will be moved correspondingly.
*
* @param anchor the view on which to pin the popup window
* @param xoff A horizontal offset from the anchor in pixels
* @param yoff A vertical offset from the anchor in pixels
* @param gravity Alignment of the popup relative to the anchor
*
* @see #dismiss()
*/
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity)
/**
* <p>
* Display the content view in a popup window at the specified location. If the popup window
* cannot fit on screen, it will be clipped. See {@link android.view.WindowManager.LayoutParams}
* for more information on how gravity and the x and y parameters are related. Specifying
* a gravity of {@link android.view.Gravity#NO_GRAVITY} is similar to specifying
* <code>Gravity.LEFT | Gravity.TOP</code>.
* </p>
*
* @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from
* @param gravity the gravity which controls the placement of the popup window
* @param x the popup's x location offset
* @param y the popup's y location offset
*/
public void showAtLocation(View parent, int gravity, int x, int y)
showAtLocation的parent參數可以很隨意,只要是activity中的view都可以。
setFocusable
setFocusable(boolean focusable) 設置PopupWindow是否獲取焦點
/**
* <p>Changes the focusability of the popup window. When focusable, the
* window will grab the focus from the current focused widget if the popup
* contains a focusable {@link android.view.View}. By default a popup
* window is not focusable.</p>
*
* <p>If the popup is showing, calling this method will take effect only
* the next time the popup is shown or through a manual call to one of
* the {@link #update()} methods.</p>
*
* @param focusable true if the popup should grab focus, false otherwise.
*
* @see #isFocusable()
* @see #isShowing()
* @see #update()
*/
public void setFocusable(boolean focusable) {
mFocusable = focusable;
}
setOutsideTouchable
/**
* <p>Controls whether the pop-up will be informed of touch events outside
* of its window. This only makes sense for pop-ups that are touchable
* but not focusable, which means touches outside of the window will
* be delivered to the window behind. The default is false.</p>
*
* <p>If the popup is showing, calling this method will take effect only
* the next time the popup is shown or through a manual call to one of
* the {@link #update()} methods.</p>
*
* @param touchable true if the popup should receive outside
* touch events, false otherwise
*
* @see #isOutsideTouchable()
* @see #isShowing()
* @see #update()
*/
public void setOutsideTouchable(boolean touchable) {
mOutsideTouchable = touchable;
}
2017-2-20
附件列表