
前言
在日常的開發(fā)中,經(jīng)常會有彈框的操作。實現(xiàn)彈框有兩種選,PopupWindow或者Dialog,這里就先忽略Dialog。彈框可能會在各種位置出現(xiàn),在指定View的上、下、左、右、左對齊、右對齊等...
而PopupWindow似乎就提供了showAsDropDown方法(請忽略showAtLocation,這邊說的是相對于View顯示),這~~就有點尷尬了。
PopupWindow
平時我們可能是這樣用PopupWindow的:
- 創(chuàng)建一個布局,再創(chuàng)建一個類繼承
PopupWindow
public class TestPopupWindow extends PopupWindow {
public TestPopupWindow(Context context) {
super(context);
setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
setOutsideTouchable(true);
setFocusable(true);
setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
View contentView = LayoutInflater.from(context).inflate(R.layout.popup_test,
null, false);
setContentView(contentView);
}
}
- 然后直接使用它
TestPopupWindow mWindow = new TestPopupWindow(this);
//根據(jù)指定View定位
PopupWindowCompat.showAsDropDown(mWindow, mButtom, 0, 0, Gravity.START);
//或者
mWindow.showAsDropDown(...);
//又或者使用showAtLocation根據(jù)屏幕來定位
mWindow.showAtLocation(...);
Gravity.LEFT(Gravity.START):相對于View左對齊;
Gravity.RIGHT(Gravity.END):相對于View靠右顯示。
Gravity.CENTER:在showAsDropDown()中是跟 Gravity.LEFT一樣,在showAtLocation()中Gravity.CENTER才有效果
-
得到效果
left.gif
查了下showAsDropDown(),發(fā)現(xiàn)只能在指定控件的下面彈出,總感覺少了點什么~~
有時候我想彈在View的上面、左邊、右邊?怎么解?

可能有機智的boy已經(jīng)想到了showAsDropDown()中的另外兩個參數(shù),xoff、yoff。
要利用這兩個參數(shù),不過不建議在代碼中直接寫。為什么?
如果你的PopupWindow寬高不確定,這兩個參數(shù)你也不知道該寫多少。
什么!你的PopupWindow寬高都寫死了?騷年,你還是太年輕了。當你寬高確定的時候,如果PopupWindow中有文本,用戶把字體改得超大,你的字就沒掉一塊了
什么!你還是嘴硬非要把寬高寫死?好吧,你贏了。
各種位置的彈窗
下面就來利用xoff、yoff在你想要的任何位置彈框。
準備工作
彈框前,需要得到PopupWindow的大小(也就是PopupWindow中contentView的大小)。
由于contentView還未繪制,這時候的width、height都是0。因此需要通過measure測量出contentView的大小,才能進行計算。需要如下方法:
@SuppressWarnings("ResourceType")
private static int makeDropDownMeasureSpec(int measureSpec) {
int mode;
if (measureSpec == ViewGroup.LayoutParams.WRAP_CONTENT) {
mode = View.MeasureSpec.UNSPECIFIED;
} else {
mode = View.MeasureSpec.EXACTLY;
}
return View.MeasureSpec.makeMeasureSpec(View.MeasureSpec.getSize(measureSpec), mode);
}
測量contentView的大小
TestPopupWindow window = new TestPopupWindow(this);
View contentView = window.getContentView();
//需要先測量,PopupWindow還未彈出時,寬高為0
contentView.measure(makeDropDownMeasureSpec(window.getWidth()),
makeDropDownMeasureSpec(window.getHeight()));
彈框
測量好PopupWindow大小后,就在任意位置彈窗了
彈框的位置無非就是根據(jù)PopupWindow以及指定View的大小,計算水平、豎直方向偏移。
水平:居左;豎直:居下
計算偏移:

代碼、效果:
int offsetX = -window.getContentView().getMeasuredWidth();
int offsetY = 0;
PopupWindowCompat.showAsDropDown(window, mButton, offsetX, offsetY, Gravity.START);

水平:居中;豎直:居下
計算偏移:

代碼、效果:
offsetX = Math.abs(mWindow.getContentView().getMeasuredWidth()-mButton.getWidth()) / 2;
offsetY = 0;
PopupWindowCompat.showAsDropDown(window, mButton, offsetX, offsetY, Gravity.START);

水平:右對齊;豎直:居下
計算偏移:

代碼、效果:
offsetX = mButton.getWidth() - mWindow.getContentView().getMeasuredWidth();
offsetY = 0;
PopupWindowCompat.showAsDropDown(window, mButton, offsetX, offsetY, Gravity.START);

水平:居中;豎直:居上
計算偏移:

代碼、效果:
offsetX = Math.abs(mWindow.getContentView().getMeasuredWidth()-mButton.getWidth()) / 2;
offsetY = -(mWindow.getContentView().getMeasuredHeight()+mButton.getHeight());
PopupWindowCompat.showAsDropDown(window, mButton, offsetX, offsetY, Gravity.START);

水平:居左;豎直:居中
計算偏移:

代碼、效果:
offsetX = -mWindow.getContentView().getMeasuredWidth();
offsetY = -(mWindow.getContentView().getMeasuredHeight() + mButton.getHeight()) / 2;
PopupWindowCompat.showAsDropDown(window, mButton, offsetX, offsetY, Gravity.START);

水平:居右;豎直:居中
計算偏移:

代碼、效果:
offsetX = 0;
offsetY = -(mWindow.getContentView().getMeasuredHeight() + mButton.getHeight()) / 2;
PopupWindowCompat.showAsDropDown(window, mButton, offsetX, offsetY, Gravity.END);

畫這些圖比敲代碼還累~~~
基本上完成了所有位置的彈框。還有一些位置上面沒提到,不過通過上面那些水平、豎直的偏移也能拼湊出來。
背景變暗
說完位置方案,順便提下背景色的變化方案。
通改變Window的透明度來實現(xiàn)背景變暗是常用的一種做法。
在PopupWindow中,先寫個修改Window透明度的方法(注意,這邊的mContext必須是Activity)
/**
* 控制窗口背景的不透明度
*/
private void setWindowBackgroundAlpha(float alpha) {
if (mContext == null) return;
if (mContext instanceof Activity) {
Window window = ((Activity) mContext).getWindow();
WindowManager.LayoutParams layoutParams = window.getAttributes();
layoutParams.alpha = alpha;
window.setAttributes(layoutParams);
}
}
然后定義透明度
private float mAlpha = 1f; //背景灰度 0-1 1表示全透明
最后在PopupWindow show的時候調(diào)用以下方法。
/**
* 窗口顯示,窗口背景透明度漸變動畫
*/
private void showBackgroundAnimator() {
if (mAlpha >= 1f) return;
ValueAnimator animator = ValueAnimator.ofFloat(1.0f, mAlpha);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float alpha = (float) animation.getAnimatedValue();
setWindowBackgroundAlpha(alpha);
}
});
animator.setDuration(360);
animator.start();
}
通過動畫來改變Window達到漸變的效果。
已有的庫
這么麻煩的彈框,當然有人已經(jīng)為我們封裝好了
-
RelativePopupWindow:代碼簡潔,支持各種位置的彈框。還能超出屏幕(感覺用不上)。
用起來是這樣的:
popup.showOnAnchor(anchor, VerticalPosition.ABOVE, HorizontalPosition.CENTER, false);
-
EasyPopup:一個功能比較全的庫,支持背景變暗,背景不可點擊(6.0以上通用)等...而且可以鏈式調(diào)用哦。不過有個缺點:背景變暗效果只支持 4.2 以上的版本。
用起來是這樣的:
private EasyPopup mCirclePop;
circlePop = new EasyPopup(this)
.setContentView(R.layout.layout_circle_comment)
.setAnimationStyle(R.style.CirclePopAnim)
//是否允許點擊PopupWindow之外的地方消失
.setFocusAndOutsideEnable(true)
.createPopup();
//顯示
circlePop.showAtAnchorView(view, VerticalGravity.CENTER, HorizontalGravity.LEFT, 0, 0);
這兩個庫的跟我上面的思路基本一樣,然后通過水平、豎直參數(shù)來使用。
用的話,如果最小版本大于等于18的話,直接用- EasyPopup就可以了。(畢竟是國人寫的,有中文文檔~~)
自己寫個工具類
介于EasyPopup只適配4.2 以上的版本,而項目要適配到4.1。只好自己寫一個了~~
結(jié)合上面的提到的兩個庫,以及背景變暗的方案。改造一個自己的庫。具體的實現(xiàn)代碼就不貼出來了,用法也借鑒了上面的兩個庫。
SmartPopupWindow popupWindow= SmartPopupWindow.Builder
.build(Activity.this, view)
.setAlpha(0.4f) //背景灰度 默認全透明
.setOutsideTouchDismiss(false) //點擊外部消失 默認true(消失)
.createPopupWindow(); //創(chuàng)建PopupWindow
popupWindow.showAtAnchorView(view, VerticalPosition.ABOVE, HorizontalPosition.CENTER);
水平方向參數(shù)HorizontalPosition:LEFT 、 RIGHT 、 ALIGN_LEFT 、 ALIGN_RIGHT、 CENTER
豎直方向參數(shù)VerticalPosition :ABOVE 、 BELOW、 ALIGN_TOP 、 ALIGN_BOTTOM、 CENTER
項目地址SmartPopupWindow
功能很簡單,由于是通過別人的庫改造的,就不上傳JCenter了。
里面就三個文件,想用的話去拷下來就可以了。
參考
RelativePopupWindow
EasyPopup
Android彈窗_PopupWindow詳解 (挺詳細的)
以上有錯誤之處,感謝指出
