博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一起来封装一个BasePopupWindow吧
阅读量:6803 次
发布时间:2019-06-26

本文共 7172 字,大约阅读时间需要 23 分钟。

本项目GitHub:

BasePopup 2.x更新思路分享链接:

非常欢迎PR(dev分支)哦

本文首发于,次发于在简书这里发布,算是第三次修改了,这个项目也算是初步完成了,如果说要加些什么,转屏保持显示算不算一个。。。

当然,今天写这个文章的目的是为了方便朋友圈那边文章的排版,毕竟咱们朋友圈系列只要搞朋友圈相关的好了,其他的控件一律封装到别的文集里面。


介绍(超级简单版)

在安卓系统,我们经常会接触到弹窗,说到弹窗,我们经常接触到的也就dialog或者popupWindow了。而这两者的区别,简单的说就是“一大小二蒙层三阻塞”,如果再简单点说,就是对话框与悬浮框的区别吧。。。具体还是谷歌咯- -这里就不详细叙述了。

问题

如果我们度娘过popupWindow,我们会知道,要是用一个popup,基本要以下几个步骤:

  1. 弄个布局
  2. new 一个popup(传入大小)
  3. 这个popup对象一大堆setxxxxx(特别是setBackgroundDrawable)
  4. 如果还需要动画,那么你通常会搜到的方法是。。。。xml弄出动画, style里面设定android:windowEnterAnimation和android:windowExitAnimation,然后执行第三步setXXXXX
  5. showAtLocation或者showAsDropDown什么的

OMG!!!作为一个程序员,我想要的只是跟TextView一样,new一个对象,setText,完。做这么多东东,又是style什么的,真心想哭。

于是,对此解决方法就是,封装吧,亲。


封装

首先,咱们要针对以上的问题提出一个期望的目标,很简单,new一个popup,show,完- -。

那么为了以后的扩展,我们需要我们的popup最基本都要实现以下的功能:   - 自由的定义样式

  • 便利的动画实现   - 可扩展   - 代码简洁易懂

在开工前,我们先说说popup吧,popup支持我们添加view来将其浮在当前层上,说到底,还不是windowManger.addView,将view给弄到decorView(注意,此decorView指popup的内部类PopupDecorView,是一个FrameLayout)上,那就悬浮了嘛。。。

既然如此,在安卓里面,万(可见)物基于view嘛~所以我们何不弄个ViewGroup进popup,然后我们把它当成activity的布局一样,完成各种好玩的,比如点击事件,比如动画什么的。

于是我们的工作流程就很清楚了:

  1. 提供设置view的接口
  2. 提供设置动画方法
  3. 提供额外的辅助方法,比如点击事件什么的
  4. 统一管理showAtLocation方法

OK,大致流程确定,接下来我们一步一步的实现它。

Step 1 - 接口定义

首先,定义一个interface:

public interface BasePopup {     View getPopupView();     View getAnimaView();}复制代码

该接口提供两个功能:

  • 得到popup的view(即我们需要inflate的xml)
  • 得到需要播放动画的view

这里还有一个可以考虑,为了更加简便,我们可以考虑再添加一个方法:int getPopupViewById(),这样我们就不用在实现的时候写那么多的LayoutInflate.xxxxx了

Step 2 - BasePopup抽象

可以肯定的是,我们要实现各种各样的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,在我们的变量,可以看到似乎重复了挺多的,从命名上看,我们可以分成这么几类:

  • View:
    • popup主体(即xml)
    • 需要播放动画的view
    • 点击执行dismiss的view
  • Anima,分为两种主要是因为有些特别点的效果用animator更好:
    • animation(enter/exit)
    • animator(enter/exit)
  • Other:一些配置和接口

构造器里,我们只给出两种,一种是传入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看,而图一,将会是接下来为朋友圈点赞控件实现的效果):

转载地址:http://xznwl.baihongyu.com/

你可能感兴趣的文章
使用tcpdump验证tcp三次握手
查看>>
Java 复习
查看>>
关于iFrame高度小于父元素DIv3~5px的解决方案
查看>>
【免费报名】亚洲诚信&看雪学院:“走进企业看安全”技术分享沙龙等你来侃~...
查看>>
Flutter学习之Dart语言基础(内置类型)
查看>>
OpenGL 3D矩阵的位移、缩放变换
查看>>
kotlin笔记--基础篇
查看>>
传统模式下WebService与WebAPI的相同与不同
查看>>
mysql实现首字母从A-Z排序
查看>>
[转]C++11 随机数学习
查看>>
谈谈对Spring IOC(控制反转)的理解--转
查看>>
spring注解开发AnnotationConfigApplicationContext的使用
查看>>
9.redis安全
查看>>
2018焦作区域赛E. Resistors in Parallel
查看>>
Scrum立会报告+燃尽图(十一月二十二日总第三十次):加强回归测试
查看>>
python网络编程socketserver模块(实现TCP客户端/服务器)
查看>>
[python] 线程简介
查看>>
pure响应式布局
查看>>
homework-09
查看>>
jquery文档处理如after错误
查看>>