本项目GitHub:github.com/razerdp/Bas…html
本文首发于CSDN,次发于泡网在简书这里发布,算是第三次修改了,这个项目也算是初步完成了,若是说要加些什么,转屏保持显示算不算一个。。。java
固然,今天写这个文章的目的是为了方便朋友圈那边文章的排版,毕竟我们朋友圈系列只要搞朋友圈相关的好了,其余的控件一概封装到别的文集里面。android
在安卓系统,咱们常常会接触到弹窗,说到弹窗,咱们常常接触到的也就dialog或者popupWindow了。而这二者的区别,简单的说就是“一大小二蒙层三阻塞”
,若是再简单点说,就是对话框与悬浮框的区别吧。。。具体仍是谷歌咯- -这里就不详细叙述了。git
若是咱们度娘过popupWindow,咱们会知道,要是用一个popup,基本要如下几个步骤:程序员
OMG!!!做为一个程序员,我想要的只是跟TextView同样,new一个对象,setText,完。作这么多东东,又是style什么的,真心想哭。github
首先,我们要针对以上的问题提出一个指望的目标,很简单,new一个popup,show,完- -。ide
那么为了之后的扩展,咱们须要咱们的popup最基本都要实现如下的功能: - 自由的定义样式布局
在开工前,咱们先说说popup吧,popup支持咱们添加view来将其浮在当前层上,说到底,还不是windowManger.addView,将view给弄到decorView(注意,此decorView指popup的内部类PopupDecorView,是一个FrameLayout
)上,那就悬浮了嘛。。。post
既然如此,在安卓里面,万(可见)物基于view嘛~因此咱们何不弄个ViewGroup进popup,而后咱们把它当成activity的布局同样,完成各类好玩的,好比点击事件,好比动画什么的。动画
因而咱们的工做流程就很清楚了:
OK,大体流程肯定,接下来咱们一步一步的实现它。
首先,定义一个interface:
public interface BasePopup {
View getPopupView();
View getAnimaView();
}
复制代码
该接口提供两个功能:
这里还有一个能够考虑,为了更加简便,咱们能够考虑再添加一个方法:int getPopupViewById(),这样咱们就不用在实现的时候写那么多的LayoutInflate.xxxxx了
能够确定的是,咱们要实现各类各样的popup,那么咱们确定不能是具体类,由于具体类限制一定不少,因此咱们抽象起来,至于具体的实现扔给子类完成就行了。
public abstract class BasePopupWindow implements BasePopup {
private static final String TAG = "BasePopupWindow";
//元素定义
protected PopupWindow mPopupWindow;
//popup视图
protected View mPopupView;
protected View mAnimaView;
protected View mDismissView;
protected Activity mContext;
//是否自动弹出输入框(default:false)
private boolean autoShowInputMethod = false;
private OnDismissListener mOnDismissListener;
//anima
protected Animation curExitAnima;
protected Animator curExitAnimator;
protected Animation curAnima;
protected Animator curAnimator;
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);
}
}
复制代码
这里解释一下:由于是抽象,咱们大多数的权限都给protected,在咱们的变量,能够看到彷佛重复了挺多的,从命名上看,咱们能够分红这么几类:
构造器里,咱们只给出两种,一种是传入context,一种是指定宽高,这样就能够适应绝大多数的使用场景了。
接下来咱们初始化咱们的view:
private void initView(Activity context, int w, int h) {
mContext = context;
mPopupView = getPopupView();
mPopupView.setFocusableInTouchMode(true);
//默认占满全屏
mPopupWindow = new PopupWindow(mPopupView, w, h);
//指定透明背景,back键相关
mPopupWindow.setBackgroundDrawable(new ColorDrawable());
mPopupWindow.setFocusable(true);
mPopupWindow.setOutsideTouchable(true);
//无需动画
mPopupWindow.setAnimationStyle(0);
//=============================================================为外层的view添加点击事件,并设置点击消失
mAnimaView = getAnimaView();
mDismissView = getClickToDismissView();
if (mDismissView != null) {
mDismissView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
if (mAnimaView != null) {
mAnimaView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
}
}
//=============================================================元素获取
curAnima = getShowAnimation();
curAnimator = getShowAnimator();
curExitAnima = getExitAnimation();
curExitAnimator = getExitAnimator();
}
复制代码
在初始化方法里,咱们主要是初始化一些常见的配置参数,但要注意的是,咱们的view是在popup new出来以前就获取好的,固然,是经过抽象方法给子类实现。至于为何mAnimaView 要给个点击事件但不实现呢,这里主要是防止点击事件被屏蔽了。
咱们能够看到各类getXXXX,在以前的版本中我给定所有都是抽象方法,后来发现,没这个必要,因而这些方法只保留了几个抽象的,其余的都是功用方法(应该改成protected?)
protected abstract Animation getShowAnimation();
protected abstract View getClickToDismissView();
public Animator getShowAnimator() { return null; }
public View getInputView() { return null; }
public Animation getExitAnimation() {
return null;
}
public Animator getExitAnimator() {
return null;
}
复制代码
接下来是showPopup,这里提供三个方法,分别是无参/紫苑id/view
这三个方法都指向于同一个方法:tryToShowPopup
private void tryToShowPopup(int res, View v) throws Exception {
//传递了view
if (res == 0 && v != null) {
mPopupWindow.showAtLocation(v, Gravity.CENTER, 0, 0);
}
//传递了res
if (res != 0 && v == null) {
mPopupWindow.showAtLocation(mContext.findViewById(res), Gravity.CENTER, 0, 0);
}
//什么都没传递,取顶级view的id
if (res == 0 && v == null) {
mPopupWindow.showAtLocation(mContext.findViewById(android.R.id.content), Gravity.CENTER, 0, 0);
}
if (curAnima != null && mAnimaView != null) {
mAnimaView.clearAnimation();
mAnimaView.startAnimation(curAnima);
}
if (curAnima == null && curAnimator != null && mAnimaView != null) {
curAnimator.start();
}
//自动弹出键盘
if (autoShowInputMethod && getInputView() != null) {
getInputView().requestFocus();
InputMethodUtils.showInputMethod(getInputView(), 150);
}
}
复制代码
相关的注释也写了,其中android.R.id.content是decorView的contnet的id,也就是咱们setContentView的父类id。
接下来咱们须要对一些状态操做进行控制,好比dismiss:
public void dismiss() {
try {
if (curExitAnima != null) {
curExitAnima.setAnimationListener(mAnimationListener);
mAnimaView.clearAnimation();
mAnimaView.startAnimation(curExitAnima);
}
else if (curExitAnimator != null) {
curExitAnimator.removeListener(mAnimatorListener);
curExitAnimator.addListener(mAnimatorListener);
curExitAnimator.start();
}
else {
mPopupWindow.dismiss();
}
} catch (Exception e) {
Log.d(TAG, "dismiss error");
}
}
复制代码
若是存在exit animation/animator,则在dismiss前播放,固然,咱们的anima须要给定监听器:
private Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
...animatorstart
@Override
public void onAnimationEnd(Animator animation) {
mPopupWindow.dismiss();
}
...animator cancel
...animator repeat
};
private Animation.AnimationListener mAnimationListener = new Animation.AnimationListener() {
...animationstart
@Override
public void onAnimationEnd(Animation animation) {
mPopupWindow.dismiss();
}
...animation repeat
};
复制代码
这样就能够确保咱们在执行完动画才去dismiss
这样,咱们的basepopup就封装好了,之后子类继承他仅仅须要实现四个方法,而后就能够跟平时写布局同样使用popup了(甚至getClickToDismissView也能够不用管,若是不是须要点击消失的话)
下面是一些根据这个basepopup写的例子(具体的能够到github看,而图一,将会是接下来为朋友圈点赞控件实现的效果):