设置背景色
在Android App的业务场景中,经常需要在屏幕弹出PopupWindow后,将背景设置为半透明的灰色效果。比较常见的解决方案就是设置Window的透明度。
具体代码如下:
//设置屏幕的透明度
public void setBackgroundAlpha(Activity activity,float alpha)
{
WindowManager.LayoutParams lp = activity .getWindow().getAttributes();
lp.alpha = alpha; //0.0-1.0
activity .getWindow().getWindow().setAttributes(lp);
}
PopupWindow在使用时,需要监听其Touch事件,允许触摸弹窗外地方时取消显示。同时需要监听dismiss事件,便于在弹窗消失后恢复背景的透明度。
moreZPop = new PopupWindow(mContext);
moreZPop.setContentView(popZSRootView);
moreZPop.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
moreZPop.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
moreZPop.setBackgroundDrawable(new BitmapDrawable());
moreZPop.setOutsideTouchable(true);
moreZPop.setFocusable(true);
moreZPop.setAnimationStyle(R.style.optional_index_pop_style);
popWin.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
setBackgroundAlpha(TestActivity.this, 1f);
}
});
但是该方案不适合于app页面中间某位置弹窗的场景,下图示例:
7.0手机showAsDropDown兼容性问题
针对上述场景,常见的实现就是PopupWindow的视图中包括两部分,一部分是上部的content,另一部分是下部的透明背景。其中透明背景部分使用match_parent充满全屏。
但是该方案配合showAsDropDown方法,在android 7.0及以上手机时,弹窗会出现在状态栏下面,而不是指定的控件下。
解决方案如下,通过设置PopupWindow的高度来解决。但是针对华为虚拟键盘存在兼容性问题,目前未找到解决方案。
public class NougatFixPopwindow extends PopupWindow {
public NougatFixPopwindow(Context context) {
super(context);
}
public NougatFixPopwindow(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NougatFixPopwindow(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public NougatFixPopwindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public NougatFixPopwindow() {
}
public NougatFixPopwindow(View contentView) {
super(contentView);
}
public NougatFixPopwindow(int width, int height) {
super(width, height);
}
public NougatFixPopwindow(View contentView, int width, int height) {
super(contentView, width, height);
}
public NougatFixPopwindow(View contentView, int width, int height, boolean focusable) {
super(contentView, width, height, focusable);
}
@Override
public void showAsDropDown(View anchor) {
if (Build.VERSION.SDK_INT >= 24) {
//TODO 下列代码在华为手机虚拟键盘隐藏的情况下有兼容性问题。目前未找到完美解决方案
Rect visibleFrame = new Rect();
anchor.getGlobalVisibleRect(visibleFrame);
int height = anchor.getResources().getDisplayMetrics().heightPixels - visibleFrame.bottom;
setHeight(height);
super.showAsDropDown(anchor);
//下列方案在小米android 7.1.1上遇到问题。popupwindow未在showAtLocation指定的位置出现,而是在屏幕顶部
// int[] location = new int[2];
// anchor.getLocationOnScreen(location);
// super.showAtLocation(anchor, Gravity.NO_GRAVITY, 0, location[1] + anchor.getHeight());
} else {
super.showAsDropDown(anchor);
}
}
}
向下展开向上收起动画
在上面的解决方案中,如果需要为弹窗加入向下展开向上收起的动画,不能使用简单的traslate,scale等系统动画。
我使用ValueAnimator来动态设置PopupWindow中content部分的高度来实现动画。具体见下面代码:
public class ExpandAndCollapseAnimPopupWindow extends NougatFixPopwindow {
private int mContentHeight;
private View mContentView;
private AnimatorSet mOpenAnimator;
private AnimatorSet mCloseAnimator;
private int mDuration = 200;
private boolean mIsDismiss = false;
public ExpandAndCollapseAnimPopupWindow(Context context) {
super(context);
init();
}
public ExpandAndCollapseAnimPopupWindow(View contentView, int width, int height) {
super(contentView, width, height);
init();
}
private void init(){
}
public void setAnimationView(View contentView, int duration){
contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
this.setAnimationView(contentView, contentView.getMeasuredHeight(), duration);
}
public void setAnimationView(View contentView, int contentHeight, int duration){
this.mContentView = contentView;
this.mContentHeight = contentHeight;
this.mDuration = duration;
if (mOpenAnimator == null){
mOpenAnimator = createOpenAnimation();
}
if (mCloseAnimator == null){
mCloseAnimator = createCloseAnimation();
}
//TODO 设置setOutsideTouchable为false,因目前未找到好的方法监听dismiss开始事件
this.setOutsideTouchable(false);
}
@Override
public void showAsDropDown(View anchor){
super.showAsDropDown(anchor);
if (mOpenAnimator != null){
mOpenAnimator.start();
}
}
@Override
public void dismiss(){
if (mIsDismiss){
super.dismiss();
mIsDismiss = false;
} else {
if (mCloseAnimator != null){
mCloseAnimator.start();
}
}
}
private static ValueAnimator createDropAnimator(final View v, int start, int end) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator arg0) {
int value = (int) arg0.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = v.getLayoutParams();
layoutParams.height = value;
v.setLayoutParams(layoutParams);
}
});
return animator;
}
private AnimatorSet createOpenAnimation() {
AnimatorSet set = new AnimatorSet();
set.setDuration(mDuration);
ValueAnimator downAnimator = createDropAnimator(mContentView, 0, mContentHeight);
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(this.getContentView(), View.ALPHA, 0.0f, 1.0f);
set.playTogether(downAnimator, alphaAnimator);
set.setDuration(mDuration);
return set;
}
private AnimatorSet createCloseAnimation() {
AnimatorSet set = new AnimatorSet();
set.setDuration(mDuration);
ValueAnimator upAnimator = createDropAnimator(mContentView, mContentHeight, 0);
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(this.getContentView(), View.ALPHA, 1.0f, 0.0f);
set.playTogether(upAnimator, alphaAnimator);
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mIsDismiss = true;
ExpandAndCollapseAnimPopupWindow.this.dismiss();
}
});
set.setDuration(mDuration);
return set;
}
}