基于之前的业务有一些弹窗的需求,这个弹窗需要在多个业务场景显示,所以选择了PopupWindow来实现相关业务。至于为何要选择PopupWindow 这个主要还是取决于自己的业务和使用场景,具体PopupWindow和Dialog的区别可以去参考:Dialog和PopUpWindow的抉择 这里讲的挺详细的。
自定义PopupWindow
我们大部分的需求都是需要弹窗内容在底部显示,也有一些需求会根据控件位置显示,首先先定义PopupWindow 控件类:
public class CustomPopupWindow extends PopupWindow implements View.OnClickListener{
private View mView;
private TextView mTvTips;
@IntDef({
VerticalPosition.CENTER,
VerticalPosition.ABOVE,
VerticalPosition.BELOW,
VerticalPosition.ALIGN_TOP,
VerticalPosition.ALIGN_BOTTOM,
})
@Retention(RetentionPolicy.SOURCE)
public @interface VerticalPosition {
int CENTER = 0;
int ABOVE = 1;
int BELOW = 2;
int ALIGN_TOP = 3;
int ALIGN_BOTTOM = 4;
}
@IntDef({
HorizontalPosition.CENTER,
HorizontalPosition.LEFT,
HorizontalPosition.RIGHT,
HorizontalPosition.ALIGN_LEFT,
HorizontalPosition.ALIGN_RIGHT,
})
@Retention(RetentionPolicy.SOURCE)
public @interface HorizontalPosition {
int CENTER = 0;
int LEFT = 1;
int RIGHT = 2;
int ALIGN_LEFT = 3;
int ALIGN_RIGHT = 4;
}
public CustomPopupWindow(Context context) {
init(context);
}
private void init(Context context) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mView = inflater.inflate(R.layout.popupwindow_layout, null);
mTvTips = mView.findViewById(R.id.tv_popupwindow_tips);
mTvTips.setOnClickListener(this);
// 设置外部可点击
this.setOutsideTouchable(false);
/* 设置弹出窗口特征 */
// 设置视图
this.setContentView(this.mView);
// 设置弹出窗体的宽和高
this.setHeight(RelativeLayout.LayoutParams.WRAP_CONTENT);
this.setWidth(RelativeLayout.LayoutParams.WRAP_CONTENT);
// 设置弹出窗体可点击
this.setFocusable(true);
// 实例化一个ColorDrawable颜色为半透明
ColorDrawable dw = new ColorDrawable(0x10000000);
// 设置弹出窗体的背景
this.setBackgroundDrawable(dw);
// 设置弹出窗体显示时的动画,从底部向上弹出
this.setAnimationStyle(R.style.shop_popup_window_anim);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.tv_popupwindow_tips:
if (mListener != null){
mListener.onClick();
}
break;
default:
break;
}
}
public void setOnItemClickListener(onItemClickListener listener) {
mListener = listener;
}
private onItemClickListener mListener;
public interface onItemClickListener {
void onClick();
}
WindowManager.LayoutParams params;
public void showBottom(Activity activity){
showAtLocation(activity.getWindow().getDecorView().findViewById(android.R.id.content), Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, 0);
params = activity.getWindow().getAttributes();
params.alpha = 0.7f;
activity.getWindow().setAttributes(params);
}
public void showPopupWindow(Activity activity, @NonNull View anchor, @VerticalPosition int vertPos,
@HorizontalPosition int horizPos, int x, int y, boolean fitInScreen){
setClippingEnabled(fitInScreen);
View contentView = getContentView();
contentView.measure(makeDropDownMeasureSpec(getWidth()), makeDropDownMeasureSpec(getHeight()));
final int measuredW = contentView.getMeasuredWidth();
final int measuredH = contentView.getMeasuredHeight();
if (!fitInScreen) {
final int[] anchorLocation = new int[2];
anchor.getLocationInWindow(anchorLocation);
x += anchorLocation[0];
y += anchorLocation[1] + anchor.getHeight();
}
switch (vertPos) {
case VerticalPosition.ABOVE:
y -= measuredH + anchor.getHeight();
break;
case VerticalPosition.ALIGN_BOTTOM:
y -= measuredH;
break;
case VerticalPosition.CENTER:
y -= anchor.getHeight()/2 + measuredH/2;
break;
case VerticalPosition.ALIGN_TOP:
y -= anchor.getHeight();
break;
case VerticalPosition.BELOW:
// Default position.
break;
}
switch (horizPos) {
case HorizontalPosition.LEFT:
x -= measuredW;
break;
case HorizontalPosition.ALIGN_RIGHT:
x -= measuredW - anchor.getWidth();
break;
case HorizontalPosition.CENTER:
x += anchor.getWidth()/2 - measuredW/2;
break;
case HorizontalPosition.ALIGN_LEFT:
// Default position.
break;
case HorizontalPosition.RIGHT:
x += anchor.getWidth();
break;
}
if (fitInScreen) {
PopupWindowCompat.showAsDropDown(this, anchor, x, y, Gravity.NO_GRAVITY);
} else {
showAtLocation(anchor, Gravity.NO_GRAVITY, x, y);
}
params = activity.getWindow().getAttributes();
params.alpha = 0.7f;
activity.getWindow().setAttributes(params);
}
@SuppressWarnings("ResourceType")
private static int makeDropDownMeasureSpec(int measureSpec) {
return View.MeasureSpec.makeMeasureSpec(View.MeasureSpec.getSize(measureSpec), getDropDownMeasureSpecMode(measureSpec));
}
private static int getDropDownMeasureSpecMode(int measureSpec) {
switch (measureSpec) {
case ViewGroup.LayoutParams.WRAP_CONTENT:
return View.MeasureSpec.UNSPECIFIED;
default:
return View.MeasureSpec.EXACTLY;
}
}
}
自定义Style
<style name="shop_popup_window_anim" parent="android:Animation">
<item name="android:windowEnterAnimation">@anim/pop_enter_anim</item>
<item name="android:windowExitAnimation">@anim/pop_exit_anim</item>
</style>
自定义进入进出动画
pop_enter_anim
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="200"
android:fromYDelta="100%p"
android:toYDelta="0" />
<alpha
android:duration="200"
android:fromAlpha="0.0"
android:toAlpha="1.0" />
</set>
pop_exit_anim
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="200"
android:fromYDelta="0"
android:toYDelta="50%p" />
<alpha
android:duration="200"
android:fromAlpha="1.0"
android:toAlpha="0.0" />
</set>
这里代码片段比较长,是由业务代码抽离出来的,也是为了完整展示整个自定义PopupWindow,其中有两个点是值得注意的,就是我们提供展示的方法。
一个是showPopupWindow
这里我们需要几个参数,主要的有弹窗的根控件,根据根控件位置去调整需要展示的位置,具体逻辑可以参考代码。
一个是showBottom
这里我们为了能在不同业务场景中使用而且是在底部显示,通常情况下我们会由第一种方式传入父布局的控件,但是在不同activity中,我们就需要去把activity的最外层布局给find出来再传进来,相对麻烦,我们也很难做到所有的activity根布局都用同一个id,有什么方式是可以最简单快捷的找到activity的根视图呢?
上面的方法其实已经给出了答案
getWindow().getDecorView().findViewById(android.R.id.content)
通过这句代码我们就能找出当前activity的根视图,这时候只需要将PopupWindow根据它的位置来展示就可以了
使用PopupWindow
mCustomPopupWindow.showBottom(mActivity);
or
mCustomPopupWindow.showPopupWindow(mActivity, tvShow, vertPos, horizPos, 0, 0, fitInScreen);
如果需要参考可以参考一下例子
https://github.com/cyihui/CustomPopupWindowDemo/tree/master
这里只是简单的抽离,如果需要使用还可以自行去封装,如果有更好的建议也可以一起探讨一下。
网友评论