前言
在日常的開發(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才有效果
-
得到效果
查了下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詳解 (挺詳細的)
以上有錯誤之處隧枫,感謝指出