Android快速开发工具——Dialog封装

1,380 阅读3分钟

项目中经常使用弹窗,有些业务我都喜欢封装在Dialog中统一实现,避免在activty/fragment层中搞得乱七八糟的,这里主要记录一下我日常的Dialog封装

弹窗类型

我主要使用的是这几种:

  • Dialog
  • DialogFragment
  • BottoSheet

一些演示效果:

nav1 nav2

Dialog封装

Dialog其实用的很少,大部分是DialogFragment,这里就简单使用Viewbinding封装了下

abstract class BaseDialog<VB : ViewBinding>(
    context: Context,
    themeResId: Int,
    private val inflate: (LayoutInflater) -> VB
) : Dialog(context, themeResId) {

    lateinit var binding: VB

    constructor(context: Context, inflate: (LayoutInflater) -> VB) : this(context, 0, inflate)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = inflate(layoutInflater)
        setContentView(binding.root)
    }
}

DialogFragment封装

首先需要明确封装的意义,就是为了在使用的时候更加方便,省略模板代码。

  • 使用viewbinding
  • dialog参数初始化:大小,透明度,位置,动画等
  • 点击事件监听
/**
 * 基础Dialog封装
 */
abstract class BaseDialogFragment<VB : ViewBinding>(
    private val inflate: (LayoutInflater, ViewGroup?, Boolean) -> VB
) : DialogFragment() {

    val TAG by lazy {
        this.javaClass.name
    }

    private var _binding: VB? = null
    val binding: VB get() = _binding!!

    private var isLoaded = false
    lateinit var mContext: Context

    private var cancel:Boolean = true
    private var gravity:Int = Gravity.CENTER
    private var width:Int = WRAP_CONTENT
    private var height:Int = WRAP_CONTENT

    @StyleRes
    private var animation: Int = R.style.dialogAnimation_center
    @StyleRes
    private var style:Int = R.style.DialogThemeTrans
    @DrawableRes
    private var layoutBackground:Int = android.R.color.transparent
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setStyle(STYLE_NO_TITLE, style)
    }

    override fun onStart() {
        super.onStart()
        dialog?.window?.apply {
            setWindowAnimations(animation)
        }
    }
    
    override fun onAttach(context: Context) {
        super.onAttach(context)
        mContext = context
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        _binding = inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        dialog?.apply {
            setCancelable(cancel)
            setCanceledOnTouchOutside(cancel)
            isCancelable = cancel
        }
        dialog?.window?.apply {
            setLayout(width,height)
            setGravity(gravity)
            setBackgroundDrawableResource(layoutBackground)
        }
    }

    override fun onResume() {
        super.onResume()
        lifecycleScope.launch {
            initView()
            initData()
        }
    }

    fun initParams(cancel:Boolean = this.cancel,
                   width:Int = this.width,
                   height:Int = this.height,
                   @DrawableRes layoutBackground:Int = this.layoutBackground,
                   gravity: Int = this.gravity,
                   @StyleRes anim:Int = this.animation,
                   @StyleRes style:Int = this.style
    ) = apply{
        this.animation = anim
        this.style = style
        this.cancel = cancel
        this.width = width
        this.height = height
        this.layoutBackground = layoutBackground
        this.gravity = gravity
    }

    abstract suspend fun initView()

    abstract suspend fun initData()

    override fun onDestroy() {
        _binding = null
        lifecycleScope.cancel()
        super.onDestroy()
    }

}

使用:还是非常简单的,使用lifecycleScope来执行逻辑代码函数,创建只需要在init中初始化各种参数就行

实例:

class BottomDialog : BaseDialogFragment<DialogTestBinding>(DialogTestBinding::inflate) {

    companion object{
        @JvmStatic
        fun getInstance() = BottomDialog()
    }

    init {
        //进行初始化参数配置
        initParams(
            cancel = false,
            gravity = Gravity.BOTTOM,
            width = ViewGroup.LayoutParams.MATCH_PARENT,
            height = 300.dp.toInt(),
            layoutBackground = R.drawable.dialog_bottom_back,
            anim = R.style.dialogAnimation_bottom,
            style = R.style.DialogThemeShadow
        )
    }

    override suspend fun initView() {
    
    }

    override suspend fun initData() {

    }

}

关于主题和动画配置,在style中设置背景透明等。。。

 <!--背景透明的dialog-->
    <style name="DialogThemeTrans" parent="android:Animation">
        <!-- Dialog以外的区域遮盖效果 -->
        <item name="android:backgroundDimEnabled">false</item>
        <!-- 无标题 -->
        <item name="android:windowNoTitle">true</item>
        <!-- 边框 -->
        <item name="android:windowFrame">@null</item>
        <item name="android:windowBackground">@android:color/transparent</item>
    </style>

    <!--带阴影的Dialog-->
    <style name="DialogThemeShadow" parent="android:Animation">
        <!-- Dialog以外的区域遮盖效果 -->
        <item name="android:backgroundDimEnabled">true</item>
        <!-- 无标题 -->
        <item name="android:windowNoTitle">true</item>
        <!-- 边框 -->
        <item name="android:windowFrame">@null</item>
        <item name="android:windowBackground">@android:color/transparent</item>
    </style>

关于动画,需要在onStart中设置:

    override fun onStart() {
        super.onStart()
        dialog?.window?.apply {
            setWindowAnimations(animation)
        }
    }

    <!--dialog动画:中心渐入-->
    <style name="dialogAnimation_center">
        <item name="android:windowEnterAnimation">@anim/dialog_in_center</item>
        <item name="android:windowExitAnimation">@anim/dialog_out_center</item>
    </style>

    <!--dialog动画:底部进入-->
    <style name="dialogAnimation_bottom">
        <item name="android:windowEnterAnimation">@anim/dialog_in_bottom</item>
        <item name="android:windowExitAnimation">@anim/dialog_out_bottom</item>
    </style>

关于具体的动画代码在Demo中查看

BottomSheet

BottomSheetDialogFragment继承自AppCompatDialogFragment,最终实现也是DialogFragment,所以一些参数上设置是相同的,主要是折叠初始化设置:


abstract class BaseBottomSheetDialogFragment<VB : ViewBinding>(
    private val inflate: (LayoutInflater, ViewGroup?, Boolean) -> VB
) : BottomSheetDialogFragment() {


    private val TAG by lazy {
        this.javaClass.name
    }

    lateinit var mContext: Context
    private var _binding: VB? = null
    val binding: VB get() = _binding!!

    //是否展示背景阴影
    var showShadow = false

    //是否能否点击外部取消
    var cancel = true

    //默认状态:折叠
    var defaultState = BottomSheetBehavior.STATE_COLLAPSED

    //默认折叠高度
    var peekHeight:Int = 100.dp.toInt()

    //布局默认背景
    @DrawableRes
    var layoutBackground:Int = android.R.color.transparent
    @StyleRes
    var animation: Int = R.style.dialogAnimation_bottom
    @StyleRes
    var style:Int = R.style.DialogThemeShadow

    var width:Int = ViewGroup.LayoutParams.WRAP_CONTENT
    var height:Int = ViewGroup.LayoutParams.WRAP_CONTENT


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setStyle(STYLE_NORMAL,style)
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        mContext = context
    }

    override fun onStart() {
        super.onStart()
        dialog?.window?.apply {
            setWindowAnimations(animation)
            if (!showShadow){
                val windowParams: WindowManager.LayoutParams = attributes
                windowParams.dimAmount = 0f
                attributes = windowParams
            }
        }
        //拿到系统的 bottom_sheet
        val view: FrameLayout = dialog?.findViewById(R.id.design_bottom_sheet)!!
        //设置view高度
        view.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
        //获取behavior
        val behavior = BottomSheetBehavior.from(view)
        //设置弹出高度
        behavior.peekHeight = peekHeight
        //设置展开状态
        //behavior.state = BottomSheetBehavior.STATE_EXPANDED

        behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
            override fun onStateChanged(bottomSheet: View, newState: Int) {
                when (newState) {
                    BottomSheetBehavior.STATE_EXPANDED -> {
                    }
                    BottomSheetBehavior.STATE_COLLAPSED -> {
                    }
                    BottomSheetBehavior.STATE_DRAGGING -> {
                    }
                    BottomSheetBehavior.STATE_SETTLING -> {
                    }
                    BottomSheetBehavior.STATE_HIDDEN -> {
                    }
                }
            }

            override fun onSlide(bottomSheet: View, slideOffset: Float) {

            }
        })
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        _binding = inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        dialog?.apply {
            setCancelable(cancel)
            setCanceledOnTouchOutside(cancel)
            isCancelable = cancel
        }
        dialog?.window?.apply {
            setLayout(width,height)
            setBackgroundDrawableResource(layoutBackground)
        }
    }


    override fun onResume() {
        super.onResume()
        lifecycleScope.launch {
            initView()
            initData()
        }
    }

    abstract suspend fun initData()

    abstract suspend fun initView()


}

Dialog中的小问题还是挺多的,需要根据自己的需要配置,具体代码大家看这里: mvvm_develop快速开发框架 是我日常测试的代码合集,一些常用工具封装,或者jetpak组件测试等。卑微Androider求个Star

还有一些关于UI的基础工具: