/*
* The MIT License (MIT)
*
* Copyright (c) 2017 razerdp
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package razerdp.basepopup;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.app.Activity;
import android.content.Context;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.PopupWindow;
import org.xmlpull.v1.XmlPullParser;
import java.lang.reflect.Field;
import razerdp.library.R;
import razerdp.util.InputMethodUtils;
import razerdp.util.SimpleAnimUtil;
/**
* Created by 大灯泡 on 2016/1/14.
* <p>
* 抽象通用popupwindow的父类
*/
public abstract class BasePopupWindow implements BasePopup, PopupWindow.OnDismissListener, PopupController {
private static final String TAG = "BasePopupWindow";
//元素定义
private PopupWindowProxy mPopupWindow;
//popup视图
private View mPopupView;
private Activity mContext;
protected View mAnimaView;
protected View mDismissView;
//是否自动弹出输入框(default:false)
private boolean autoShowInputMethod = false;
private OnDismissListener mOnDismissListener;
private OnBeforeShowCallback mOnBeforeShowCallback;
//anima
private Animation mShowAnimation;
private Animator mShowAnimator;
private Animation mExitAnimation;
private Animator mExitAnimator;
private boolean isExitAnimaPlaying = false;
private boolean needPopupFadeAnima = true;
//option
private int popupGravity = Gravity.NO_GRAVITY;
private int offsetX;
private int offsetY;
private int popupViewWidth;
private int popupViewHeight;
//锚点view的location
private int[] mAnchorViewLocation;
//是否参考锚点
private boolean relativeToAnchorView;
//是否自动适配popup的位置
private boolean isAutoLocatePopup;
//showasdropdown
private boolean showAtDown;
//点击popup外部是否消失
private boolean dismissWhenTouchOuside;
private int popupLayoutid;
public BasePopupWindow(Activity context) {
initView(context, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
}
public BasePopupWindow(Activity context, int w, int h) {
initView(context, w, h);
}
private void initView(Activity context, int w, int h) {
mContext = context;
mPopupView = onCreatePopupView();
mAnimaView = initAnimaView();
checkPopupAnimaView();
//默认占满全屏
mPopupWindow = new PopupWindowProxy(mPopupView, w, h, this);
mPopupWindow.setOnDismissListener(this);
setDismissWhenTouchOuside(true);
preMeasurePopupView(w, h);
//默认是渐入动画
setNeedPopupFade(Build.VERSION.SDK_INT <= 22);
//=============================================================为外层的view添加点击事件,并设置点击消失
mDismissView = getClickToDismissView();
if (mDismissView != null && !(mDismissView instanceof AdapterView)) {
mDismissView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
}
if (mAnimaView != null && !(mAnimaView instanceof AdapterView)) {
mAnimaView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
}
//=============================================================元素获取
mShowAnimation = initShowAnimation();
mShowAnimator = initShowAnimator();
mExitAnimation = initExitAnimation();
mExitAnimator = initExitAnimator();
mAnchorViewLocation = new int[2];
}
private void checkPopupAnimaView() {
//处理popupview与animaview相同的情况
//当popupView与animaView相同的时候,处理位置信息会出问题,因此这里需要对mAnimaView再包裹一层
if (mPopupView != null && mAnimaView != null && mPopupView == mAnimaView) {
try {
mPopupView = new FrameLayout(getContext());
if (popupLayoutid == 0) {
((FrameLayout) mPopupView).addView(mAnimaView);
} else {
mAnimaView = View.inflate(getContext(), popupLayoutid, (FrameLayout) mPopupView);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void preMeasurePopupView(int w, int h) {
if (mPopupView != null) {
//修复可能出现的android 4.2的measure空指针问题
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
int contentViewHeight = ViewGroup.LayoutParams.MATCH_PARENT;
final ViewGroup.LayoutParams layoutParams = mPopupView.getLayoutParams();
if (layoutParams != null && layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
contentViewHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
}
ViewGroup.LayoutParams p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, contentViewHeight);
mPopupView.setLayoutParams(p);
}
mPopupView.measure(w, h);
popupViewWidth = mPopupView.getMeasuredWidth();
popupViewHeight = mPopupView.getMeasuredHeight();
mPopupView.setFocusableInTouchMode(true);
}
}
//------------------------------------------抽象-----------------------------------------------
/**
* PopupWindow展示出来后,需要执行动画的View.一般为蒙层之上的View
*/
protected abstract Animation initShowAnimation();
/**
* 设置一个点击后触发dismiss PopupWindow的View,一般为蒙层
*/
public abstract View getClickToDismissView();
/**
* 设置展示动画View的属性动画
*/
protected Animator initShowAnimator() {
return null;
}
/**
* 设置一个拥有输入功能的View,一般为EditTextView
*/
public EditText getInputView() {
return null;
}
/**
* 设置PopupWindow销毁时的退出动画
*/
protected Animation initExitAnimation() {
return null;
}
/**
* 设置PopupWindow销毁时的退出属性动画
*/
protected Animator initExitAnimator() {
return null;
}
/**
* popupwindow是否需要淡入淡出
*/
public void setNeedPopupFade(boolean needPopupFadeAnima) {
this.needPopupFadeAnima = needPopupFadeAnima;
setPopupAnimaStyle(needPopupFadeAnima ? R.style.PopupAnimaFade : 0);
}
public boolean getNeedPopupFade() {
return needPopupFadeAnima;
}
/**
* 设置popup的动画style
*/
public void setPopupAnimaStyle(int animaStyleRes) {
mPopupWindow.setAnimationStyle(animaStyleRes);
}
//------------------------------------------showPopup-----------------------------------------------
/**
* 调用此方法时,PopupWindow将会显示在DecorView
*/
public void showPopupWindow() {
if (checkPerformShow(null)) {
tryToShowPopup(null);
}
}
/**
* 调用此方法时,PopupWindow左上角将会与anchorview左上角对齐
*
* @param anchorViewResid
*/
public void showPopupWindow(int anchorViewResid) {
View v = mContext.findViewById(anchorViewResid);
showPopupWindow(v);
}
/**
* 调用此方法时,PopupWindow左上角将会与anchorview左上角对齐
*
* @param v
*/
public void showPopupWindow(View v) {
if (checkPerformShow(v)) {
setRelativeToAnchorView(true);
tryToShowPopup(v);
}
}
//------------------------------------------Methods-----------------------------------------------
private void tryToShowPopup(View v) {
try {
int offset[];
//传递了view
if (v != null) {
offset = calcuateOffset(v);
if (showAtDown) {
mPopupWindow.showAsDropDown(v, offset[0], offset[1]);
} else {
mPopupWindow.showAtLocation(v, popupGravity, offset[0], offset[1]);
}
} else {
//什么都没传递,取顶级view的id
mPopupWindow.showAtLocation(mContext.findViewById(android.R.id.content), popupGravity, offsetX, offsetY);
}
if (mShowAnimation != null && mAnimaView != null) {
mAnimaView.clearAnimation();
mAnimaView.startAnimation(mShowAnimation);
}
if (mShowAnimation == null && mShowAnimator != null && mAnimaView != null) {
mShowAnimator.start();
}
//自动弹出键盘
if (autoShowInputMethod && getInputView() != null) {
getInputView().requestFocus();
InputMethodUtils.showInputMethod(getInputView(), 150);
}
} catch (Exception e) {
Log.e(TAG, "show error");
e.printStackTrace();
}
}
/**
* 暂时还不是很稳定,需要进一步测试优化
*
* @param anchorView
* @return
*/
private int[] calcuateOffset(View anchorView) {
int[] offset = {0, 0};
anchorView.getLocationOnScreen(mAnchorViewLocation);
//当参考了anchorView,那么意味着必定使用showAsDropDown,此时popup初始显示位置在anchorView的底部
//因此需要先将popupview与anchorView的左上角对齐
if (relativeToAnchorView) {
offset[0] = offset[0] + offsetX;
offset[1] = -anchorView.getHeight() + offsetY;
}
if (isAutoLocatePopup) {
final boolean onTop = (getScreenHeight() - mAnchorViewLocation[1] + offset[1] < popupViewHeight);
if (onTop) {
offset[1] = offset[1] - popupViewHeight + offsetY;
showOnTop(mPopupView);
} else {
showOnDown(mPopupView);
}
}
return offset;
}
/**
* PopupWindow是否需要自适应输入法,为输入法弹出让出区域
*
* @param needAdjust <br>
* ture for "SOFT_INPUT_ADJUST_RESIZE" mode<br>
* false for "SOFT_INPUT_ADJUST_NOTHING" mode
*/
public void setAdjustInputMethod(boolean needAdjust) {
if (needAdjust) {
mPopupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
} else {
mPopupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
}
}
/**
* 当PopupWindow展示的时候,这个参数决定了是否自动弹出输入法
* 如果使用这个方法,您必须保证通过 <strong>getInputView()<strong/>得到一个EditTextView
*/
public void setAutoShowInputMethod(boolean autoShow) {
this.autoShowInputMethod = autoShow;
if (autoShow) {
setAdjustInputMethod(true);
} else {
setAdjustInputMethod(false);
}
}
/**
* 这个参数决定点击返回键是否可以取消掉PopupWindow
*/
public void setBackPressEnable(boolean backPressEnable) {
if (backPressEnable) {
mPopupWindow.setBackgroundDrawable(new ColorDrawable());
} else {
mPopupWindow.setBackgroundDrawable(null);
}
}
/**
* 这个方法封装了LayoutInflater.from(context).inflate,方便您设置PopupWindow所用的xml
*
* @param resId reference of layout
* @return root View of the layout
*/
public View createPopupById(int resId) {
if (resId != 0) {
popupLayoutid = resId;
return LayoutInflater.from(mContext).inflate(resId, null);
} else {
return null;
}
}
protected View findViewById(int id) {
if (mPopupView != null && id != 0) {
return mPopupView.findViewById(id);
}
return null;
}
/**
* 是否允许popupwindow覆盖屏幕(包含状态栏)
*/
public void setPopupWindowFullScreen(boolean needFullScreen) {
fitPopupWindowOverStatusBar(needFullScreen);
}
/**
* 这个方法用于简化您为View设置OnClickListener事件,多个View将会使用同一个点击事件
*/
protected void setViewClickListener(View.OnClickListener listener, View... views) {
for (View view : views) {
if (view != null && listener != null) {
view.setOnClickListener(listener);
}
}
}
private void fitPopupWindowOverStatusBar(boolean needFullScreen) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
try {
Field mLayoutInScreen = PopupWindow.class.getDeclaredField("mLayoutInScreen");
mLayoutInScreen.setAccessible(true);
mLayoutInScreen.set(mPopupWindow, needFullScreen);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
//------------------------------------------Getter/Setter-----------------------------------------------
/**
* PopupWindow是否处于展示状态
*/
public boolean isShowing() {
return mPopupWindow.isShowing();
}
public OnDismissListener getOnDismissListener() {
return mOnDismissListener;
}
public void setOnDismissListener(OnDismissListener onDismissListener) {
mOnDismissListener = onDismissListener;
}
public OnBeforeShowCallback getOnBeforeShowCallback() {
return mOnBeforeShowCallback;
}
public void setOnBeforeShowCallback(OnBeforeShowCallback mOnBeforeShowCallback) {
this.mOnBeforeShowCallback = mOnBeforeShowCallback;
}
public void setShowAnimation(Animation showAnimation) {
if (mShowAnimation != null && mAnimaView != null) {
mAnimaView.clearAnimation();
mShowAnimation.cancel();
}
if (showAnimation != null && showAnimation != mShowAnimation) {
mShowAnimation = showAnimation;
}
}
public Animation getShowAnimation() {
return mShowAnimation;
}
public void setShowAnimator(Animator showAnimator) {
if (mShowAnimator != null) mShowAnimator.cancel();
if (showAnimator != null && showAnimator != mShowAnimator) {
mShowAnimator = showAnimator;
}
}
public Animator getShowAnimator() {
return mShowAnimator;
}
public void setExitAnimation(Animation exitAnimation) {
if (mExitAnimation != null && mAnimaView != null) {
mAnimaView.clearAnimation();
mExitAnimation.cancel();
}
if (exitAnimation != null && exitAnimation != mExitAnimation) {
mExitAnimation = exitAnimation;
}
}
public Animation getExitAnimation() {
return mExitAnimation;
}
public void setExitAnimator(Animator exitAnimator) {
if (mExitAnimator != null) mExitAnimator.cancel();
if (exitAnimator != null && exitAnimator != mExitAnimator) {
mExitAnimator = exitAnimator;
}
}
public Animator getExitAnimator() {
return mExitAnimator;
}
public Context getContext() {
return mContext;
}
/**
* 获取popupwindow的根布局
*
* @return
*/
public View getPopupWindowView() {
return mPopupView;
}
/**
* 获取popupwindow实例
*
* @return
*/
public PopupWindow getPopupWindow() {
return mPopupWindow;
}
public int getOffsetX() {
return offsetX;
}
/**
* 设定x位置的偏移量(中心点在popup的左上角)
* <p>
*
* @param offsetX
*/
public void setOffsetX(int offsetX) {
this.offsetX = offsetX;
}
public int getOffsetY() {
return offsetY;
}
/**
* 设定y位置的偏移量(中心点在popup的左上角)
*
* @param offsetY
*/
public void setOffsetY(int offsetY) {
this.offsetY = offsetY;
}
public int getPopupGravity() {
return popupGravity;
}
/**
* 设置参考点,一般情况下,参考对象指的不是指定的view,而是它的windoToken,可以看作为整个screen
*
* @param popupGravity
*/
public void setPopupGravity(int popupGravity) {
this.popupGravity = popupGravity;
}
public boolean isRelativeToAnchorView() {
return relativeToAnchorView;
}
/**
* 是否参考锚点view,如果是true,则会显示到跟指定view的x,y一样的位置(如果空间足够的话)
*
* @param relativeToAnchorView
*/
public void setRelativeToAnchorView(boolean relativeToAnchorView) {
setShowAtDown(true);
this.relativeToAnchorView = relativeToAnchorView;
}
public boolean isAutoLocatePopup() {
return isAutoLocatePopup;
}
public void setAutoLocatePopup(boolean autoLocatePopup) {
setShowAtDown(true);
isAutoLocatePopup = autoLocatePopup;
}
/**
* 这个值是在创建view时进行测量的,并不能当作一个完全准确的值
*
* @return
*/
public int getPopupViewWidth() {
return popupViewWidth;
}
/**
* 这个值是在创建view时进行测量的,并不能当作一个完全准确的值
*
* @return
*/
public int getPopupViewHeight() {
return popupViewHeight;
}
public boolean isShowAtDown() {
return showAtDown;
}
/**
* 决定使用showAtLocation还是showAsDropDown
* decide showAtLocation/showAsDropDown
*
* @param showAtDown
*/
public void setShowAtDown(boolean showAtDown) {
this.showAtDown = showAtDown;
}
/**
* 点击外部是否消失
* <p>
* dismiss popup when touch ouside from popup
*
* @param dismissWhenTouchOuside true for dismiss
*/
public void setDismissWhenTouchOuside(boolean dismissWhenTouchOuside) {
this.dismissWhenTouchOuside = dismissWhenTouchOuside;
if (dismissWhenTouchOuside) {
//指定透明背景,back键相关
mPopupWindow.setFocusable(true);
mPopupWindow.setOutsideTouchable(true);
mPopupWindow.setBackgroundDrawable(new ColorDrawable());
} else {
mPopupWindow.setFocusable(false);
mPopupWindow.setOutsideTouchable(false);
mPopupWindow.setBackgroundDrawable(null);
}
}
public boolean isDismissWhenTouchOuside() {
return dismissWhenTouchOuside;
}
//------------------------------------------状态控制-----------------------------------------------
/**
* 取消一个PopupWindow,如果有退出动画,PopupWindow的消失将会在动画结束后执行
*/
public void dismiss() {
try {
mPopupWindow.dismiss();
} catch (Exception e) {
Log.e(TAG, "dismiss error");
}
}
@Override
public boolean onBeforeDismiss() {
return checkPerformDismiss();
}
@Override
public boolean callDismissAtOnce() {
boolean hasAnima = false;
if (mExitAnimation != null && mAnimaView != null) {
if (!isExitAnimaPlaying) {
mExitAnimation.setAnimationListener(mAnimationListener);
mAnimaView.clearAnimation();
mAnimaView.startAnimation(mExitAnimation);
isExitAnimaPlaying = true;
hasAnima = true;
}
} else if (mExitAnimator != null) {
if (!isExitAnimaPlaying) {
mExitAnimator.removeListener(mAnimatorListener);
mExitAnimator.addListener(mAnimatorListener);
mExitAnimator.start();
isExitAnimaPlaying = true;
hasAnima = true;
}
}
//如果有动画,则不立刻执行dismiss
return !hasAnima;
}
/**
* 直接消掉popup而不需要动画
*/
public void dismissWithOutAnima() {
if (!checkPerformDismiss()) return;
try {
if (mExitAnimation != null && mAnimaView != null) mAnimaView.clearAnimation();
if (mExitAnimator != null) mExitAnimator.removeAllListeners();
mPopupWindow.callSuperDismiss();
} catch (Exception e) {
Log.e(TAG, "dismiss error");
}
}
private boolean checkPerformDismiss() {
boolean callDismiss = true;
if (mOnDismissListener != null) {
callDismiss = mOnDismissListener.onBeforeDismiss();
}
return callDismiss && !isExitAnimaPlaying;
}
private boolean checkPerformShow(View v) {
boolean result = true;
if (mOnBeforeShowCallback != null) {
result = mOnBeforeShowCallback.onBeforeShow(mPopupView, v, this.mShowAnimation != null || this.mShowAnimator != null);
}
return result;
}
//------------------------------------------Anima-----------------------------------------------
private Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
isExitAnimaPlaying = true;
}
@Override
public void onAnimationEnd(Animator animation) {
mPopupWindow.callSuperDismiss();
isExitAnimaPlaying = false;
}
@Override
public void onAnimationCancel(Animator animation) {
isExitAnimaPlaying = false;
}
@Override
public void onAnimationRepeat(Animator animation) {
}
};
private Animation.AnimationListener mAnimationListener = new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
isExitAnimaPlaying = true;
}
@Override
public void onAnimationEnd(Animation animation) {
mPopupWindow.callSuperDismiss();
isExitAnimaPlaying = false;
}
@Override
public void onAnimationRepeat(Animation animation) {
}
};
/**
* 生成TranslateAnimation
*
* @param durationMillis 动画显示时间
* @param start 初始位置
*/
protected Animation getTranslateAnimation(int start, int end, int durationMillis) {
return SimpleAnimUtil.getTranslateAnimation(start, end, durationMillis);
}
/**
* 生成ScaleAnimation
* <p>
* time=300
*/
protected Animation getScaleAnimation(float fromX,
float toX,
float fromY,
float toY,
int pivotXType,
float pivotXValue,
int pivotYType,
float pivotYValue) {
return SimpleAnimUtil.getScaleAnimation(fromX, toX, fromY, toY, pivotXType, pivotXValue, pivotYType, pivotYValue);
}
/**
* 生成自定义ScaleAnimation
*/
protected Animation getDefaultScaleAnimation() {
return SimpleAnimUtil.getDefaultScaleAnimation();
}
/**
* 生成默认的AlphaAnimation
*/
protected Animation getDefaultAlphaAnimation() {
return SimpleAnimUtil.getDefaultAlphaAnimation();
}
/**
* 从下方滑动上来
*/
protected AnimatorSet getDefaultSlideFromBottomAnimationSet() {
return SimpleAnimUtil.getDefaultSlideFromBottomAnimationSet(mAnimaView);
}
/**
* 获取屏幕高度(px)
*/
public int getScreenHeight() {
return getContext().getResources().getDisplayMetrics().heightPixels;
}
/**
* 获取屏幕宽度(px)
*/
public int getScreenWidth() {
return getContext().getResources().getDisplayMetrics().widthPixels;
}
//------------------------------------------callback-----------------------------------------------
protected void showOnTop(View mPopupView) {
}
protected void showOnDown(View mPopupView) {
}
@Override
public void onDismiss() {
if (mOnDismissListener != null) {
mOnDismissListener.onDismiss();
}
isExitAnimaPlaying = false;
}
//------------------------------------------Interface-----------------------------------------------
public interface OnBeforeShowCallback {
/**
* <b>return ture for perform show</b>
*
* @param popupRootView The rootView of popup,it's usually be your layout
* @param anchorView The anchorView whitch popup show
* @param hasShowAnima Check if show your popup with anima?
* @return
*/
boolean onBeforeShow(View popupRootView, View anchorView, boolean hasShowAnima);
}
public static abstract class OnDismissListener implements PopupWindow.OnDismissListener {
/**
* <b>return ture for perform dismiss</b>
*
* @return
*/
public boolean onBeforeDismiss() {
return true;
}
}
}