说起实现一个弹窗的效果~你会想到什么实现方式呢?

用PopupWindow 或AlertDialog 或在布局文件添加一个布局隐藏或者显示等等,实现的方式很多

用popupWindow 和AlertDialog很方便,但代码不便于复用,扩展性不好,会出现一些输入焦点的问题。并且样式修改困难。

如果直接写在布局文件里,用显示隐藏的方式,还是不便于代码复用。

所以我想到了一种思路,动态的添加布局,并且把这整个布局显示隐藏或移除添加以及添加到activity的逻辑封装到一起作为基类。

实现类只需要实现弹窗相关的逻辑,这样就可以灵活的改动弹窗,实现各式各样的布局以及代码的复用了

思路就是在基类中创建一个约束布局作为所有菜单的父布局,这里使用约束布局,是方便灵活的配置显示的位置。然后把这个约束布局添加到activity中,并给他设置一个自定义的ID。这里之所以设置ID是为了让所有菜单子类共用一个父布局,而不是添加一个菜单就多一层父布局。有人会不解为什么要在菜单外面再套一层布局,而不是直接把菜单添加到activity 中。原因就是,activity的布局是不确定,如果直接添加,扩展性就不好。接下来就是实现一些显示和隐藏的方法。这里的显示和隐藏是直接把菜单的布局从约束布局里面移除所以动画实现的方式是设置LayoutTransition。如果改成显示隐藏的方式,就直接使用显示隐藏的动画设置就可以。这里直接移除是考虑到菜单并不经常会用到。没有必要让他一直站用资源。下面是源代码。以及实现类的源代码

这里我使用了viewbinding 当然也可以不使用,用相同的思路也是可以实现的。我觉得用viewbinding 可以省掉不少代码吧~

仅仅提供一种实习菜单的简洁的方式。具体的使用方法,就是在activity中直接创建这个对象,然后调用显示或者隐藏的方法。实现点击事件的回调方法就可以了。具体的点击事件是在activity中处理或者是封装在实现类中都是可以的。

这个思路的好处就是很灵活,和写一个普通的布局没有区别,缺点就是需要给activity多加一个布局吧~

abstract class BaseMenuModel<T : ViewBinding>{
    protected val binding: T
    protected val context: Activity
    protected var constraintLayout:ConstraintLayout
    var view:View?=null
    constructor(context: Activity,view:View) {
        this.view=view
        this.context=context
        this.binding=getBindingInstance()
        binding.root.id= View.generateViewId()
       //构造方法中实现 查找是否存在ID,不存在则调用addview方法
        if((context.findViewById<ConstraintLayout>(R.id.widget_bottom_menu_root)==null)){
           constraintLayout = ConstraintLayout(context)
            addView()
        }else{
            constraintLayout=context.findViewById(R.id.widget_bottom_menu_root)
        }
    }
    protected var menuWidth=ViewGroup.LayoutParams.MATCH_PARENT
    protected var menuHeight=ViewGroup.LayoutParams.MATCH_PARENT
    protected var gravityH=Gravity.LEFT
    protected var gravityV=Gravity.TOP


    protected abstract fun getBindingInstance(): T
    private fun addView(){

        constraintLayout.layoutParams= ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_PARENT,
            ConstraintLayout.LayoutParams.MATCH_PARENT)
        addAnim(constraintLayout)
        constraintLayout.id=R.id.widget_bottom_menu_root

        if(view==null) {
            (context.window.decorView as ViewGroup).addView(constraintLayout)
        }else{
            view?.let {
                (it.parent as ViewGroup).addView(constraintLayout)
            }
        }


    }
    fun showMenu(){
        constraintLayout.isClickable=true
        constraintLayout.setBackgroundColor(Color.parseColor("#80000000"))
        constraintLayout.setOnClickListener({
            if(constraintLayout.childCount>0) {
                hindMenu()
            }
        })
        var cs=ConstraintSet()
        constraintLayout.addView(binding.root,menuWidth,menuHeight)
        cs.clone(constraintLayout)
        if(gravityH!=Gravity.LEFT){
            cs.connect(binding.root.id,ConstraintSet.RIGHT,constraintLayout.id,ConstraintSet.RIGHT)
        }
        if(gravityH!=Gravity.RIGHT) {
            cs.connect(binding.root.id, ConstraintSet.LEFT, constraintLayout.id, ConstraintSet.LEFT)
        }
        if(gravityV!=Gravity.TOP){
            cs.connect(binding.root.id,ConstraintSet.BOTTOM,constraintLayout.id,ConstraintSet.BOTTOM)
        }
        if(gravityV!=Gravity.BOTTOM){
            cs.connect(binding.root.id,ConstraintSet.TOP,constraintLayout.id,ConstraintSet.TOP)
        }
        cs.applyTo(constraintLayout)
    }
    fun hindMenu(){
        constraintLayout.isClickable=false
        constraintLayout.setBackgroundColor(Color.parseColor("#00000000"))
        constraintLayout.removeView(binding.root)
    }
    private fun addAnim(v:ViewGroup){

        var mLayoutTransition =  LayoutTransition();
        mLayoutTransition.setAnimator(LayoutTransition.APPEARING, getAppearingAnimation());
        mLayoutTransition.setDuration(LayoutTransition.APPEARING, 200);
        mLayoutTransition.setStartDelay(LayoutTransition.APPEARING, 0);//源码中带有默认300毫秒的延时,需要移除,不然view添加效果不好!!

        mLayoutTransition.setAnimator(LayoutTransition.DISAPPEARING, getDisappearingAnimation());
        mLayoutTransition.setDuration(LayoutTransition.DISAPPEARING, 200);

        mLayoutTransition.setAnimator(LayoutTransition.CHANGE_APPEARING,getAppearingChangeAnimation());
        mLayoutTransition.setDuration(200);

        mLayoutTransition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING,getDisappearingChangeAnimation());
        mLayoutTransition.setDuration(200);

        mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
        mLayoutTransition.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);//源码中带有默认300毫秒的延时,需要移除,不然view添加效果不好!!
        mLayoutTransition.addTransitionListener( object:LayoutTransition.TransitionListener {
            override fun startTransition(
                transition: LayoutTransition?,
                container: ViewGroup?,
                view: View?,
                transitionType: Int
            ) {

            }

            override fun endTransition(
                transition: LayoutTransition?,
                container: ViewGroup?,
                view: View?,
                transitionType: Int
            ) {

            }

        });

            v.setLayoutTransition(mLayoutTransition);
    }
    @SuppressLint("ObjectAnimatorBinding")
    open protected  fun getAppearingAnimation(): Animator {
        var mSet = AnimatorSet();
        mSet.playTogether(
//            ObjectAnimator.ofFloat(null, "ScaleX", 2.0f, 1.0f),
//            ObjectAnimator.ofFloat(null, "ScaleY", 2.0f, 1.0f),
//            ObjectAnimator.ofFloat(null, "Alpha", 0.0f, 1.0f),
            ObjectAnimator.ofFloat(null,"translationY",400f,0f))
        return mSet;
    }

    @SuppressLint("ObjectAnimatorBinding")
    open protected fun getDisappearingAnimation():Animator {
        var mSet = AnimatorSet();
        mSet.playTogether(
//            ObjectAnimator.ofFloat(null, "ScaleX", 1.0f, 0f),
//            ObjectAnimator.ofFloat(null, "ScaleY", 1.0f, 0f),
//            ObjectAnimator.ofFloat(null, "Alpha", 1.0f, 0.0f),
            ObjectAnimator.ofFloat(null,"translationY",0f,400f));
        return mSet;
    }

    open protected fun getDisappearingChangeAnimation():Animator{
        var pvhLeft = PropertyValuesHolder.ofInt("left", 0, 0);
        var pvhTop = PropertyValuesHolder.ofInt("top", 0, 0);
        var pvhRight = PropertyValuesHolder.ofInt("right", 0, 0);
        var pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 0);
        var scaleX = PropertyValuesHolder.ofFloat("scaleX",1.0f,0f,1.0f);
        var scaleY = PropertyValuesHolder.ofFloat("scaleY",1.0f,0f,1.0f);
        var rotate = PropertyValuesHolder.ofFloat("rotation",0f,0f,0f);
        return ObjectAnimator.ofPropertyValuesHolder(pvhLeft, pvhTop, pvhRight, pvhBottom,scaleX,scaleY,rotate);
    }

    open protected fun getAppearingChangeAnimation():Animator{
        var pvhLeft = PropertyValuesHolder.ofInt("left", 0, 0);
        var pvhTop = PropertyValuesHolder.ofInt("top", 0, 0);
        var pvhRight = PropertyValuesHolder.ofInt("right", 0, 0);
        var pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 0);
        var scaleX = PropertyValuesHolder.ofFloat("scaleX",1.0f,3f,1.0f);
        var scaleY = PropertyValuesHolder.ofFloat("scaleY",1.0f,3f,1.0f);
        return ObjectAnimator.ofPropertyValuesHolder(pvhLeft, pvhTop, pvhRight, pvhBottom,scaleX,scaleY);
    }

    fun removeView() {
        constraintLayout.removeView(binding.root)
        if(view==null) {
            (context.window.decorView as ViewGroup).removeView(constraintLayout)
        }else{
                view?.let {
                    (it.parent as ViewGroup).removeView(constraintLayout)
                }

        }
    }

}
class BottomPhotoMenuModel(context: Activity,view:View) : BaseMenuModel<MenuSendPhotoBinding>(context,view),View.OnClickListener
{
    var callback:((id)->Unit)?=null
    init {
        //配置了菜单的显示方式和位置
        menuHeight=ViewGroup.LayoutParams.WRAP_CONTENT
        menuWidth=ViewGroup.LayoutParams.MATCH_PARENT
        gravityH=Gravity.CENTER
        gravityV=Gravity.BOTTOM

            binding.tvCancel.setOnClickListener(this)
            binding.tvSelect.setOnClickListener(this)
            binding.tvTake.setOnClickListener(this)
    }
    //覆盖父类方法填充布局
    override fun getBindingInstance(): MenuSendPhotoBinding {
        return MenuSendPhotoBinding.inflate(context.layoutInflater)
    }

    

    override fun onClick(v: View) {
        //可以不处理,直接回调,在回调方法里处理
        callback?.let{
                    it(id)
                  }
        when(v.id){
            R.id.tv_cancel->{
                
                hindMenu()
            }
            R.id.tv_select->{
//在这里回调或者处理后回调

                hindMenu()
            }
            R.id.tv_take->{
//在这里回调或者处理后回调

                hindMenu()
            }
        }
    }
}