在Android中使用PopupWindow,通常都是通过LayoutInflater.from(context).inflate获取View,再通过setContentView设置弹窗布局,如果要处理View上的控件,还需要单独对View进行findViewById和setOnClickListener等等再setContentView,这个过程有点繁琐。如果弹窗布局有多个的话,这样一个一个地去组装PopupWindow就更加繁杂了。所以自己的目的是实现一个PopupWindow类,通过布局id去setContentView,能够匹配各种各样的布局,同时简化PopupWindow的封装过程。

       这里先说明一下PopupWindow的一些属性和方法:

方法 方法说明
setContentView(View contentView) 设置弹窗的布局
setWidth(int width) 设置弹窗的宽度
setHeight(int height) 设置弹窗的高度
setAnimationStyle(int animationStyle) 设置弹窗出现和消失的动画效果
setBackgroundDrawable(Drawable background) 设置弹窗的背景,但如果弹窗的根布局已经设置了android:background属性,有可能会覆盖整个弹窗的背景导致这个方法看起来无效
setOutsideTouchable(boolean touchable)

设置弹窗外部区域是否可触摸,设为true时当点击外部区域弹窗会消失,false不会消失

showAsDropDown(View anchor) 在指定View的左下角显示弹窗
showAsDropDown(View anchor, int xoff, int yoff)

在指定View的左下角显示弹窗,其中xoff表示相对于View左下角在水平方向上的偏移量,yoff表示相对于View左下角在竖直方向上的偏移量

(Android坐标系的X轴和Y轴的正方向分别是向右和向下的,因此如果xoff为10表示向右偏移10像素,yoff为-10表示向上偏移10像素)。

showAtLocation(View parent, int gravity, int x, int y) 在父控件指定的位置显示弹窗,其中x和y分别表示相对于父控件指定位置在水平和竖直方向上的偏移量,gravity表示在相对于父控件的位置,Gravity.CENTER在父控件正中间显示,Gravity.BOTTOM在父控件底部显示,Gravity.NO_GRAVITY相当于Gravity.LEFT|Gravity.TOP。

       更多PopupWindow的信息可以到 android developers 上了解。

       现在开始封装PopupWindow,完整代码如下:

import android.app.Activityimport android.content.Contextimport android.graphics.drawable.ColorDrawableimport android.graphics.drawable.Drawableimport android.support.annotation.FloatRangeimport android.view.LayoutInflaterimport android.view.Viewimport android.view.ViewGroupimport android.widget.PopupWindowclass CommonPopupWindow private constructor(context: Context) : PopupWindow() {    private var mWindowHelper: WindowHelper? = null    init {        if (context is Activity) {            mWindowHelper = WindowHelper(context)        }    }    override fun dismiss() {        super.dismiss()        mWindowHelper?.setBackGroundAlpha(1.0f)    }    class Builder(private var mContext: Context) {        private var mLayoutId: Int = -1          //弹窗的布局id        private var mWidth: Int = 0              //弹窗的宽度        private var mHeight: Int = 0             //弹窗的高度        private var mAlpha: Float = 1.0f        //背景透明度        private var mAnimationStyle: Int = -1   //动画        private var mTouchable: Boolean = true  //是否可点击        private var mBackgroundDrawable: Drawable = ColorDrawable(0x00000000)  //背景drawable        private var mOnViewListener: ((holder: ViewHolder, popupWindow: PopupWindow) -> Unit)? =            null        //通过布局id设置弹窗布局的View        fun setContentView(layoutId: Int): Builder {            mLayoutId = layoutId            return this        }        //设置宽高        fun setViewParams(width: Int, height: Int): Builder {            mWidth = width            mHeight = height            return this        }        //设置外部区域背景透明度,0:完全不透明,1:完全透明        fun setBackGroundAlpha(@FloatRange(from = 0.0, to = 1.0) alpha: Float): Builder {            mAlpha = alpha            return this        }        //设置显示和消失动画        fun setAnimationStyle(animationStyle: Int): Builder {            mAnimationStyle = animationStyle            return this        }        //设置外部区域是否可点击取消对话框        fun setOutsideTouchable(touchable: Boolean): Builder {            mTouchable = touchable            return this        }        //设置弹窗背景        fun setBackgroundDrawable(drawable: Drawable): Builder {            mBackgroundDrawable = drawable            return this        }        //设置事件监听        fun setOnViewListener(listener: (holder: ViewHolder, popupWindow: PopupWindow) -> Unit): Builder {            mOnViewListener = listener            return this        }        fun build(): CommonPopupWindow {            val popupWindow = CommonPopupWindow(mContext)            with(popupWindow) {                //设置contentView                if (mLayoutId != -1) {                    val view = LayoutInflater.from(mContext).inflate(mLayoutId, null)                    //因为PopupWindow在显示前无法获取准确的宽高值(getWidth和getHeight可能会返回0或-2),                    //通过提前测量contentView的宽高就可以通过getMeasuredWidth和getMeasuredHeight获取contentView的宽高                    view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)                    contentView = view                } else {                    throw NullPointerException("The contentView of PopupWindow is null")                }                //设置宽高,没有设置宽高的话默认为ViewGroup.LayoutParams.WRAP_CONTENT                if (mWidth == 0) {                    width = ViewGroup.LayoutParams.WRAP_CONTENT                } else {                    width = mWidth                }                if (mHeight == 0) {                    height = ViewGroup.LayoutParams.WRAP_CONTENT                } else {                    height = mHeight                }                mWindowHelper?.setBackGroundAlpha(mAlpha)  //设置外部区域的透明度                //设置弹窗显示和消失的动画效果                if (mAnimationStyle != -1) {                    animationStyle = mAnimationStyle                }                //设置弹窗背景,如果contentView对应的View已经设置android:background可能会覆盖弹窗背景                setBackgroundDrawable(mBackgroundDrawable)                //设置点击外部区域是否可取消弹窗                isOutsideTouchable = mTouchable                isFocusable = mTouchable                //设置contentView上控件的事件监听                mOnViewListener?.invoke(ViewHolder(contentView), this)            }            return popupWindow        }    }}

       代码是用Kotlin写的,因为都有注释,这里就不再做过多介绍了,主要说一下两个辅助类:WindowHelper和ViewHolder。(这几个类也有用Java语言编写,在最后的demo地址)

       WindowHelper主要用于实现弹窗外部区域的阴影效果,代码如下:

import android.app.Activityimport android.support.annotation.FloatRangeclass WindowHelper(private var mActivity: Activity) {    //设置外部区域背景透明度,0:完全不透明,1:完全透明    fun setBackGroundAlpha(@FloatRange(from = 0.0, to = 1.0) alpha: Float) {        val window = mActivity.window        val lp = window.attributes        lp.alpha = alpha        window.attributes = lp    }}

       ViewHolder用于简化View的处理,比如findViewById和setOnClickListener等,代码如下:

import android.support.annotation.IdResimport android.util.SparseArrayimport android.util.TypedValueimport android.view.Viewimport android.view.ViewGroupimport android.widget.ImageViewimport android.widget.TextView@Suppress("UNCHECKED_CAST")class ViewHolder(private var mView: View) {    //缓存View    private var mViewList: SparseArray    init {        mViewList = SparseArray()    }    //查找View中的控件    fun  getView(@IdRes viewId: Int): T? {        //对已有的view做缓存        var view: View? = mViewList.get(viewId)        //使用缓存的方式减少findViewById的次数        if (view == null) {            view = mView.findViewById(viewId)            mViewList.put(viewId, view)        }        return view as? T    }    //设置文本    fun setText(@IdRes viewId: Int, text: CharSequence): ViewHolder {        val view = getView(viewId)        view?.text = text        return this //链式调用    }    //设置文本字体颜色    fun setTextColor(@IdRes viewId: Int, color: Int): ViewHolder {        val view = getView(viewId)        view?.setTextColor(color)        return this    }    //设置文本字体大小,单位默认为SP,故设置时只需要传递数值就可以,如setTextSize(R.id.xxx,15f)    fun setTextSize(@IdRes viewId: Int, textSize: Float): ViewHolder {        val view = getView(viewId)        view?.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize)        return this    }    //设置图片    fun setImageResource(@IdRes viewId: Int, resId: Int): ViewHolder {        val iv = getView(viewId)        iv?.setImageResource(resId)        return this    }    //显示View    fun setViewVisible(@IdRes viewId: Int): ViewHolder {        getView(viewId)?.visibility = View.VISIBLE        return this    }    //隐藏View    fun setViewGone(@IdRes viewId: Int): ViewHolder {        getView(viewId)?.visibility = View.GONE        return this    }    //设置View宽度    fun setViewWidth(@IdRes viewId: Int, width: Int): ViewHolder {        return setViewParams(viewId, width, -1)    }    //设置View高度    fun setViewHeight(@IdRes viewId: Int, height: Int): ViewHolder {        return setViewParams(viewId, -1, height)    }    //设置View的宽度和高度    fun setViewParams(@IdRes viewId: Int, width: Int, height: Int): ViewHolder {        getView(viewId)?.let {            val params = it.layoutParams as ViewGroup.MarginLayoutParams            if (width >= 0) {                params.width = width            }            if (height >= 0) {                params.height = height            }            it.layoutParams = params        }        return this    }    //设置点击事件    fun setOnClickListener(@IdRes viewId: Int, listener: (v: View) -> Unit): ViewHolder {        getView(viewId)?.setOnClickListener { v -> listener.invoke(v) }        return this    }    //设置长按事件    fun setOnLongClickListener(@IdRes viewId: Int, listener: (v: View) -> Boolean): ViewHolder {        getView(viewId)?.setOnLongClickListener { v -> listener.invoke(v) }        return this    }}

       ViewHolder使用举例:

holder.setText(R.id.share_tv, "分享")    .setTextSize(R.id.share_tv, 15f)    .setTextColor(R.id.share_tv, Color.BLACK)    .setOnClickListener(R.id.share_tv) {    }.setOnClickListener(R.id.copy_tv) {            }

       是不是要比一个一个地findViewById和setText方便多了,至此所有相关的代码已经列举出来了。

       CommonPopupWindow使用举例:

CommonPopupWindow.Builder(this)    .setContentView(R.layout.layout_popup_window_to_top)    .setAnimationStyle(R.style.AnimScaleBottom)    .setOnViewListener { holder, popupWindow ->        holder.setOnClickListener(R.id.reply_tv) {            showToast("回复")            popupWindow.dismiss()        }.setOnClickListener(R.id.share_tv) {            showToast("分享")            popupWindow.dismiss()        }.setOnClickListener(R.id.report_tv) {            showToast("举报")            popupWindow.dismiss()        }.setOnClickListener(R.id.copy_tv) {            showToast("复制")            popupWindow.dismiss()        }    }.build()    .showAsDropDown(view);

       对应的弹窗布局:

<?xml version="1.0" encoding="utf-8"?>                                        

       效果图如下:

       最后,附上整个项目的 github地址 ,里面同时包含了Java和Kotlin版本。

更多相关文章

  1. android 系统属性
  2. Android基类BaseActivity简单封装
  3. 布局及视图(一)
  4. Android(安卓)获取OnItemClick事件中组件的内容
  5. Android语音转文字一识别语音
  6. Android(安卓)Studio设置,减少对C盘空间的占用
  7. android之Android(安卓)Studio下自定义属性的定义和使用
  8. 解决给一组Button设置Background导致点击效果错乱问题
  9. 让应用程序具体相应权限

随机推荐

  1. Android Widget 小部件(四---完结) 使用L
  2. android 创建水平进度条
  3. Android tab 背景及字体颜色设置
  4. Android使用Unicode码对中文进行字母索引
  5. Android两种播放声音的方式
  6. Android apk重新签名
  7. android network develop(2)----network
  8. Canvas.clipPath不能用
  9. 在Android中使用NDK调用OpenGl
  10. Android 解析CSV文件,中文乱码