简单定制Android控件(3) - 打造通用的PopupWindow(一)

国际惯例,先上地址

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起码要实现以下几个要求{

  1.   自由的定义样式
  2.   便利的动画实现
  3.   可扩展
  4.   代码简洁易懂
}

好的,说了那么多,接下来我们就开工。

开工之前,我们先谈谈要求和实现方法吧:

  第一点,自由的定义样式,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)