简单定制Android控件(3) - 打造通用的PopupWindow(一)
2016-01-15 11:33
573 查看
国际惯例,先上地址
https://github.com/razerdp/BasePopup
PS:效果图都放在了github,github有着我继承该类做出来的popupWindow
//2016-01-15 目前只写了两个PopupWindow
效果图:
普通的放大缩小:
从下方弹出:
通常情况下,面对各种浮动窗口,选择窗口什么的,我们通常都是使用popupWindow,但是很多时候我们都希望popupWindow可以在弹出的时候带有动画,但是就popup本身而言,使用的动画是在是太不舒服不自由了。
通常情况下,我们弄个popupWindow的动画都是这么玩的
我们还得去styles.xml弄弄我们的进入/退出动画,这多不自然啊,而且说好的控制呢对吧
于是这次我们就来打造一个通用的popupWindow,让我们可以随心自由的设置我们的popupWindow
这次我们要实现的popupWindow起码要实现以下几个要求{
自由的定义样式
便利的动画实现
可扩展
代码简洁易懂
}
好的,说了那么多,接下来我们就开工。
开工之前,我们先谈谈要求和实现方法吧:
第一点,自由的定义样式,popupWindow在new出来的时候参数里面有一个参数是View,这意味着popupWindow本身就支持添加view(其实楼主我一直都把popupWindow看作一个浮动的viewGroup)
第二点,便利的动画实现,开头说过,自带的popupWindow动画实现是在不舒服,于是我们打算这么做,动画由我们自己来指定,popupWindow只需要播放就好了。在第一点我说过,我把popupWindow看作一个浮动的viewGroup,既然有了viewGroup,那就意味着必定有view对吧,有了view,那就意味着必定有view的animation对吧,于是第二点的初步构造就出来了,popupWindow包裹着viewGroup,viewGroup里面的view(或者viewGroup)播放动画,实现我们的第二点需求。
第三点,可扩展,可扩展意味着我们可以轻易的继承父类从而实现我们各种各样的popup,比如listPopup,inputPopup甚至是含有viewpager的popup。那么显然,我们需要一个抽象类,作为顶级父类,并限定子类规则,防止不可预料的问题。
第四点,嗯。。。。。看个人代码风格吧
ps:楼主是个注释/分块 狂魔
正文:
我们首先创建一个abstract class,作为顶级父类,取名叫BasePopup就好了。
首先我们需要一个popupWindow,作为一个popup用来浮动在当前的activity上面,然后需要一个view,作为popup的整体,然后就是一些参数设置什么的,比如是否需要输入之类的,这是后话。
于是我们就有了下面的一段代码:
设置两个构造器是为了扩展,因为默认状态下我们的popup是全屏显示,但是特殊的popup呢,最经典的例子,微信朋友圈的点赞/评论弹出来的那个东东,我们可以用popup来做,但很明显我们不可以用全屏的popup对吧,于是就有了第二个构造器
从代码我们看到我们实现了一个接口,这个接口只提供两个方法,具体如下
getPopupView,在构造器我们可以看到,popupWindow的contentView就是通过这个得到
getAnimaView,明显用来播放动画用的
基本构造有了,接下来就是对子类的限定
期中getAnimation提供给子类用来设置播放动画,AnimationSet也是,但是AnimationSet是使用ObjectAnima的,也就是物理上改变的view参数,Animation只是改变视觉,对于物理事件(比如点击事件)的位置是不涉及改变的。
getInputView用于获取需要输入内容的popup里面的输入框,用于自动弹出输入法。
对子类规则定好后,接下来就是实现show方法。
对于popup我们都知道有一个showAtLocation方法来展示popup,我们这里也使用这个。
相关注释都写上了,这里就不解释
附上InputMethodUtils的代码:
150ms后弹出软键盘是为了给窗体绘制时间。
最后就是一些Setter/Getter和接口方法了
总体代码:
BasePopup.java:
ViewCreate.java:
InputMethodUtils.java:
下一章继续,通过继承我们的BasePopup来打造我们的popup
https://github.com/razerdp/BasePopup
PS:效果图都放在了github,github有着我继承该类做出来的popupWindow
//2016-01-15 目前只写了两个PopupWindow
效果图:
普通的放大缩小:
从下方弹出:
通常情况下,面对各种浮动窗口,选择窗口什么的,我们通常都是使用popupWindow,但是很多时候我们都希望popupWindow可以在弹出的时候带有动画,但是就popup本身而言,使用的动画是在是太不舒服不自由了。
通常情况下,我们弄个popupWindow的动画都是这么玩的
popupWindow.setAnimationStyle(R.style.PopMenuAnimation);
我们还得去styles.xml弄弄我们的进入/退出动画,这多不自然啊,而且说好的控制呢对吧
于是这次我们就来打造一个通用的popupWindow,让我们可以随心自由的设置我们的popupWindow
这次我们要实现的popupWindow起码要实现以下几个要求{
自由的定义样式
便利的动画实现
可扩展
代码简洁易懂
}
好的,说了那么多,接下来我们就开工。
开工之前,我们先谈谈要求和实现方法吧:
第一点,自由的定义样式,popupWindow在new出来的时候参数里面有一个参数是View,这意味着popupWindow本身就支持添加view(其实楼主我一直都把popupWindow看作一个浮动的viewGroup)
第二点,便利的动画实现,开头说过,自带的popupWindow动画实现是在不舒服,于是我们打算这么做,动画由我们自己来指定,popupWindow只需要播放就好了。在第一点我说过,我把popupWindow看作一个浮动的viewGroup,既然有了viewGroup,那就意味着必定有view对吧,有了view,那就意味着必定有view的animation对吧,于是第二点的初步构造就出来了,popupWindow包裹着viewGroup,viewGroup里面的view(或者viewGroup)播放动画,实现我们的第二点需求。
第三点,可扩展,可扩展意味着我们可以轻易的继承父类从而实现我们各种各样的popup,比如listPopup,inputPopup甚至是含有viewpager的popup。那么显然,我们需要一个抽象类,作为顶级父类,并限定子类规则,防止不可预料的问题。
第四点,嗯。。。。。看个人代码风格吧
ps:楼主是个注释/分块 狂魔
正文:
我们首先创建一个abstract class,作为顶级父类,取名叫BasePopup就好了。
首先我们需要一个popupWindow,作为一个popup用来浮动在当前的activity上面,然后需要一个view,作为popup的整体,然后就是一些参数设置什么的,比如是否需要输入之类的,这是后话。
于是我们就有了下面的一段代码:
public abstract class BasePopupWindow implements ViewCreate { private static final String TAG = "BasePopupWindow"; //元素定义 protected PopupWindow mPopupWindow; //popup视图 protected View mPopupView; protected Activity mContext; //是否自动弹出输入框(default:false) private boolean autoShowInputMethod = false; private OnDismissListener mOnDismissListener; public BasePopupWindow(Activity context) { mContext = context; mPopupView = getPopupView(); mPopupView.setFocusable(true); mPopupView.setFocusableInTouchMode(true); //默认占满全屏 mPopupWindow = new PopupWindow(mPopupView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); //指定透明背景,点击外面相关 mPopupWindow.setBackgroundDrawable(new ColorDrawable()); mPopupWindow.setFocusable(true); mPopupWindow.setOutsideTouchable(true); //无需动画 mPopupWindow.setAnimationStyle(0); } public BasePopupWindow(Activity context, int w, int h) { mContext = context; mPopupView = getPopupView(); mPopupView.setFocusable(true); mPopupView.setFocusableInTouchMode(true); //默认占满全屏 mPopupWindow = new PopupWindow(mPopupView, w, h); //指定透明背景,点击外面相关 mPopupWindow.setBackgroundDrawable(new ColorDrawable()); mPopupWindow.setFocusable(true); mPopupWindow.setOutsideTouchable(true); //无需动画 mPopupWindow.setAnimationStyle(0); }
设置两个构造器是为了扩展,因为默认状态下我们的popup是全屏显示,但是特殊的popup呢,最经典的例子,微信朋友圈的点赞/评论弹出来的那个东东,我们可以用popup来做,但很明显我们不可以用全屏的popup对吧,于是就有了第二个构造器
从代码我们看到我们实现了一个接口,这个接口只提供两个方法,具体如下
/** * Created by 大灯泡 on 2016/1/14. */ public interface ViewCreate { View getPopupView(); View getAnimaView(); }
getPopupView,在构造器我们可以看到,popupWindow的contentView就是通过这个得到
getAnimaView,明显用来播放动画用的
基本构造有了,接下来就是对子类的限定
//------------------------------------------抽象----------------------------------------------- public abstract Animation getAnimation(); public abstract AnimationSet getAnimationSet(); public abstract View getInputView();
期中getAnimation提供给子类用来设置播放动画,AnimationSet也是,但是AnimationSet是使用ObjectAnima的,也就是物理上改变的view参数,Animation只是改变视觉,对于物理事件(比如点击事件)的位置是不涉及改变的。
getInputView用于获取需要输入内容的popup里面的输入框,用于自动弹出输入法。
对子类规则定好后,接下来就是实现show方法。
对于popup我们都知道有一个showAtLocation方法来展示popup,我们这里也使用这个。
//------------------------------------------showPopup----------------------------------------------- public void showPopupWindow() { try { tryToShowPopup(0, null); } catch (Exception e) { Log.e(TAG, "show error"); e.printStackTrace(); } } public void showPopupWindow(int res) { try { tryToShowPopup(res, null); } catch (Exception e) { Log.e(TAG, "show error"); e.printStackTrace(); } } public void showPopupWindow(View v) { try { tryToShowPopup(0, v); } catch (Exception e) { Log.e(TAG, "show error"); e.printStackTrace(); } } //------------------------------------------Methods----------------------------------------------- private void tryToShowPopup(int res, View v) throws Exception { //传递了view if (res == 0 && v != null) { mPopupWindow.showAtLocation(v, Gravity.RIGHT | Gravity.CENTER_HORIZONTAL, 0, 0); } //传递了res if (res != 0 && v == null) { mPopupWindow.showAtLocation(mContext.findViewById(res), Gravity.RIGHT | Gravity.CENTER_HORIZONTAL, 0, 0); } //什么都没传递,取顶级view的id if (res == 0 && v == null) { mPopupWindow.showAtLocation(mContext.findViewById(android.R.id.content), Gravity.RIGHT | Gravity.CENTER_HORIZONTAL, 0, 0 ); } if (getAnimation() != null && getAnimaView() != null) { getAnimaView().startAnimation(getAnimation()); } //ViewHelper.setPivotX是包nineoldAndroid的方法,用于兼容低版本的anima以及方便的view工具 if (getAnimation() == null && getAnimationSet() != null && getAnimaView() != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { ViewHelper.setPivotX(getAnimaView(), getAnimaView().getMeasuredWidth() / 2.0f); ViewHelper.setPivotY(getAnimaView(), getAnimaView().getMeasuredHeight() / 2.0f); getAnimationSet().start(); } //自动弹出键盘 if (autoShowInputMethod && getInputView() != null) { InputMethodUtils.showInputMethod(getInputView(), 150); } }
相关注释都写上了,这里就不解释
附上InputMethodUtils的代码:
150ms后弹出软键盘是为了给窗体绘制时间。
/** * Created by 大灯泡 on 2016/1/14. * 显示键盘d工具类 */ public class InputMethodUtils { /** 显示软键盘 */ public static void showInputMethod(View view) { InputMethodManager imm = (InputMethodManager) view.getContext() .getSystemService(Context.INPUT_METHOD_SERVICE); if (imm != null) { imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); } } /** 显示软键盘 */ public static void showInputMethod(Context context) { InputMethodManager imm = (InputMethodManager) context .getSystemService(Context.INPUT_METHOD_SERVICE); imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS); } /** 多少时间后显示软键盘 */ public static void showInputMethod(final View view, long delayMillis) { // 显示输入法 new Handler().postDelayed(new Runnable() { @Override public void run() { InputMethodUtils.showInputMethod(view); } }, delayMillis); } }
最后就是一些Setter/Getter和接口方法了
总体代码:
BasePopup.java:
package razerdp.basepopup.basepopup; import android.app.Activity; import android.graphics.drawable.ColorDrawable; import android.os.Build; import android.util.Log; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.animation.AccelerateInterpolator; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.ScaleAnimation; import android.view.animation.TranslateAnimation; import android.widget.PopupWindow; import com.nineoldandroids.view.ViewHelper; import razerdp.basepopup.utils.InputMethodUtils; /** * Created by 大灯泡 on 2016/1/14. * 通用的popupWindow */ public abstract class BasePopupWindow implements ViewCreate { private static final String TAG = "BasePopupWindow"; //元素定义 protected PopupWindow mPopupWindow; //popup视图 protected View mPopupView; protected Activity mContext; //是否自动弹出输入框(default:false) private boolean autoShowInputMethod = false; private OnDismissListener mOnDismissListener; public BasePopupWindow(Activity context) { mContext = context; mPopupView = getPopupView(); mPopupView.setFocusable(true); mPopupView.setFocusableInTouchMode(true); //默认占满全屏 mPopupWindow = new PopupWindow(mPopupView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); //指定透明背景,back键相关 mPopupWindow.setBackgroundDrawable(new ColorDrawable()); mPopupWindow.setFocusable(true); mPopupWindow.setOutsideTouchable(true); //无需动画 mPopupWindow.setAnimationStyle(0); } public BasePopupWindow(Activity context, int w, int h) { mContext = context; mPopupView = getPopupView(); mPopupView.setFocusable(true); mPopupView.setFocusableInTouchMode(true); //默认占满全屏 mPopupWindow = new PopupWindow(mPopupView, w, h); //指定透明背景,back键相关 mPopupWindow.setBackgroundDrawable(new ColorDrawable()); mPopupWindow.setFocusable(true); mPopupWindow.setOutsideTouchable(true); //无需动画 mPopupWindow.setAnimationStyle(0); } //------------------------------------------抽象----------------------------------------------- public abstract Animation getAnimation(); public abstract AnimationSet getAnimationSet(); public abstract View getInputView(); //------------------------------------------showPopup----------------------------------------------- public void showPopupWindow() { try { tryToShowPopup(0, null); } catch (Exception e) { Log.e(TAG, "show error"); e.printStackTrace(); } } public void showPopupWindow(int res) { try { tryToShowPopup(res, null); } catch (Exception e) { Log.e(TAG, "show error"); e.printStackTrace(); } } public void showPopupWindow(View v) { try { tryToShowPopup(0, v); } catch (Exception e) { Log.e(TAG, "show error"); e.printStackTrace(); } } //------------------------------------------Methods----------------------------------------------- private void tryToShowPopup(int res, View v) throws Exception { //传递了view if (res == 0 && v != null) { mPopupWindow.showAtLocation(v, Gravity.RIGHT | Gravity.CENTER_HORIZONTAL, 0, 0); } //传递了res if (res != 0 && v == null) { mPopupWindow.showAtLocation(mContext.findViewById(res), Gravity.RIGHT | Gravity.CENTER_HORIZONTAL, 0, 0); } //什么都没传递,取顶级view的id if (res == 0 && v == null) { mPopupWindow.showAtLocation(mContext.findViewById(android.R.id.content), Gravity.RIGHT | Gravity.CENTER_HORIZONTAL, 0, 0 ); } if (getAnimation() != null && getAnimaView() != null) { getAnimaView().startAnimation(getAnimation()); } //ViewHelper.setPivotX是包nineoldAndroid的方法,用于兼容低版本的anima以及方便的view工具 if (getAnimation() == null && getAnimationSet() != null && getAnimaView() != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { ViewHelper.setPivotX(getAnimaView(), getAnimaView().getMeasuredWidth() / 2.0f); ViewHelper.setPivotY(getAnimaView(), getAnimaView().getMeasuredHeight() / 2.0f); getAnimationSet().start(); } //自动弹出键盘 if (autoShowInputMethod && getInputView() != null) { InputMethodUtils.showInputMethod(getInputView(), 150); } } public void setAdjustInputMethod(boolean needAdjust) { if (needAdjust) { mPopupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); } else { mPopupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING); } } public void setAutoShowInputMethod(boolean autoShow) { this.autoShowInputMethod = autoShow; if (autoShow) { setAdjustInputMethod(true); } else { setAdjustInputMethod(false); } } public void setBackPressEnable(boolean backPressEnable){ if (backPressEnable){ mPopupWindow.setBackgroundDrawable(new ColorDrawable()); }else { mPopupWindow.setBackgroundDrawable(null); } } //------------------------------------------Getter/Setter----------------------------------------------- public boolean isShowing() { return mPopupWindow.isShowing(); } public OnDismissListener getOnDismissListener() { return mOnDismissListener; } public void setOnDismissListener(OnDismissListener onDismissListener) { mOnDismissListener = onDismissListener; if (mOnDismissListener!=null){ mPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() { @Override public void onDismiss() { mOnDismissListener.onDismiss(); } }); } } //------------------------------------------状态控制----------------------------------------------- public void dismiss() { try { mPopupWindow.dismiss(); } catch (Exception e) { Log.d(TAG, "dismiss error"); } } //------------------------------------------Anima----------------------------------------------- /** * 生成TranslateAnimation * @param durationMillis 动画显示时间 * @param start 初始位置 */ protected Animation getTranslateAnimation(int start, int end, int durationMillis) { Animation translateAnimation = new TranslateAnimation(0, 0, start, end); translateAnimation.setDuration(durationMillis); translateAnimation.setFillEnabled(true); translateAnimation.setFillAfter(true); return translateAnimation; } /** * 生成ScaleAnimation */ protected Animation getScaleAnimation(float fromX, float toX, float fromY, float toY, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue) { Animation scaleAnimation = new ScaleAnimation(fromX, toX, fromY, toY, pivotXType, pivotXValue, pivotYType, pivotYValue); scaleAnimation.setDuration(300); scaleAnimation.setFillEnabled(true); scaleAnimation.setFillAfter(true); return scaleAnimation; } /** * 生成自定义ScaleAnimation */ protected Animation getDefaultScaleAnimation() { Animation scaleAnimation = new ScaleAnimation(0f, 1f, 0f, 1f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); scaleAnimation.setDuration(300); scaleAnimation.setInterpolator(new AccelerateInterpolator()); scaleAnimation.setFillEnabled(true); scaleAnimation.setFillAfter(true); return scaleAnimation; } /** * 生成默认的AlphaAnimation * */ protected Animation getDefaultAlphaAnimation() { Animation alphaAnimation = new AlphaAnimation(0.0f, 1.0f); alphaAnimation.setDuration(300); alphaAnimation.setInterpolator(new AccelerateInterpolator()); alphaAnimation.setFillEnabled(true); alphaAnimation.setFillAfter(true); return alphaAnimation; } //------------------------------------------Interface----------------------------------------------- public interface OnDismissListener { void onDismiss(); } }
ViewCreate.java:
import android.view.View; /** * Created by 大灯泡 on 2016/1/14. */ public interface ViewCreate { View getPopupView(); View getAnimaView(); }
InputMethodUtils.java:
import android.content.Context;
import android.os.Handler;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
/** * Created by 大灯泡 on 2016/1/14. * 显示键盘d工具类 */ public class InputMethodUtils { /** 显示软键盘 */ public static void showInputMethod(View view) { InputMethodManager imm = (InputMethodManager) view.getContext() .getSystemService(Context.INPUT_METHOD_SERVICE); if (imm != null) { imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); } } /** 显示软键盘 */ public static void showInputMethod(Context context) { InputMethodManager imm = (InputMethodManager) context .getSystemService(Context.INPUT_METHOD_SERVICE); imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS); } /** 多少时间后显示软键盘 */ public static void showInputMethod(final View view, long delayMillis) { // 显示输入法 new Handler().postDelayed(new Runnable() { @Override public void run() { InputMethodUtils.showInputMethod(view); } }, delayMillis); } }
下一章继续,通过继承我们的BasePopup来打造我们的popup
相关文章推荐
- Android高效率编码-第三方SDK详解系列(一)——百度地图,绘制,覆盖物,导航,定位,细腻分解!
- Android 布局优化之include与merge
- Android中attrs.xml文件的使用详解
- Android drivers/switch驱动详解(用于通过GPIO状态检测耳机、HDMI等的插拔状态)
- Android 支付宝支付开发
- android 应用的生命周期
- Android activity之间传值关键性代码
- Android手机 Fildder真机抓包
- Android开发之如何监听让服务不被杀死(service+broadcast)
- Android应用开发中的风格和主题(style,themes)
- android EditText Border 点击和点击状态的选择 shape 进行绘制
- 解决Android从相册中获取图片出错图片却无法裁剪问题的方法
- Android开发笔记(四十九)异步任务处理AsyncTask
- Android通过webservice对sqlserver数据库进行操作
- Android 用Animation-list实现逐帧动画
- 【Android自定义控件】选择输入框的实现
- Android studio 中使用xUtils报错
- Dialog dismiss 和 cancel的区别
- Android EditText 自定义控件 带消除功能
- android shape的使用