GenjiDialogV2

Introduction: first
More: Author   ReportBugs   
Tags:

基于 kotlin 的通用 dialog

开始用 kotlin 开发之后, 发现很多东西都能简化,毕竟 kotlin 的语法糖不能浪费了,所以就有了这个库, 只要我还在做 Android 开发,应该会一直维护该库。

依赖

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}
androidx
dependencies {
    implementation 'com.github.q876625596:GenjiDialogV2:1.4.1'
}
support
dependencies {
    implementation 'com.github.q876625596:GenjiDialogV2:1.2.0'
}
混淆
-keep public class com.ly.genjidialog.GenjiDialog { *; }

版本更新

v1.4.1 优化 show
v1.4.0 优化 dataBinding 的绑定部分 2
v1.3.9 优化 dataBinding 的绑定部分
v1.3.8 修复沉浸式状态栏无法改变状态栏颜色的问题,更新 kotlin 到 1.3.40
v1.3.7 修改依附在 view 上的坐标为屏幕的绝对坐标
v1.3.6 忘记增加版本号导致 jitPack 不编译 1.3.5 版本,重新发布
v1.3.5 kotlin 升级到 1.3.31,添加混淆,修复 unLeak 模式下 dialog 点击空白处关闭后,熄屏再次打开时重新显示的 bug,移除 onKeyListenerForOptions 扩展中的 dialog 参数
v1.3.4 kotlin 升级到 1.3.21,默认初始化模式改为 DialogInitMode.NORMAL
v1.3.3 移除 rx
v1.3.2 删除 log 日志
v1.3.0 升级到 androidx
v1.2.0 同样是重要更新
主要修复一个内存泄露 bug,这是 google 留下的坑
bug 描述:当 DialogFragment 非一次性使用的时候,在 dismiss()之后,dialogFragment 和 dialog 的 Message 依旧相互引用,导致内存泄露。
解决方式:改写 show 方法,通过反射拿到状态值,然后在 dismiss 和 show 的时候仅使用 dialog 的方法
注意事项:由于这样做,会使 dialogFragment 始终被 add 到 fragmentManager 中,
所以,当在同一个 activity 使用多个 dialogFragment 时,需要注意管控,
但是我建议每次使用时都 newGenjiDialog{},只需要保存数据就行了

-使用方法(当不是一次性使用时)

val testDialog =  newGenjiDialog {
    ......
    //只需要将 unLeak 属性设置为 true
    unLeak = true
    ......
    }.showOnWindow(supportFragmentManager)
v1.1.9 重要更新
1、kotlin 升级到 1.3.11
2、新增初始化模式,现有 3 种模式,具体请看表格
3、支持 DataBinding

使用方法

-改变初始化模式

 newGenjiDialog {
    ......
    initMode = DialogInitMode.DRAW_COMPLETE //DialogInitMode.NORMAL 为默认模式
    ......
    }.showOnWindow(supportFragmentManager)

-DataBinding 的绑定方法

newGenjiDialog {
    //绑定 DataBinding 则不需要设置 layoutId
    //layoutId = R.layout.aaa
    gravity = DialogGravity.CENTER_CENTER
    animStyle = R.style.BottomTransAlphaADAnimation
    //此方法用于创建布局对应的 DataBinding,并且将创建的 binding 赋给 dialog 中的 dialogBinding 字段
    //最后返回视图   binding.root
    bindingListenerFun { container, dialog ->
        return@bindingListenerFun DataBindingUtil.inflate<AaaBinding>(inflater, R.layout.aaa, container, false).apply {
            this.setLifecycleOwner(dialog.viewLifecycleOwner)
            this.textStr = "hello"
            dialog.dialogBinding = this
        }.root
    }
    //初始化赋值操作(非必须)   也可以在 bindingListenerFun 中操作
    dataConvertListenerFun { dialogBinding, dialog ->
        dialogBinding as AaaBinding
        dialogBinding.text = "hello1"
    }
}.showOnWindow(supportFragmentManager)
v1.1.8
1、kotlin 升级到 1.3.0
2、新增自定义控件 MaskView,可做新用户引导层。

使用方法

 newGenjiDialog {
    layoutId = R.layout.dialog_mask
    dimAmount = 0f
    isFullHorizontal = true
    isFullVerticalOverStatusBar = true
    gravity = DialogGravity.CENTER_CENTER
    animStyle = R.style.AlphaEnterExitAnimation
    convertListenerFun { holder, dialog ->
        holder.getView<MaskView>(R.id.maskView)?.apply {
        this.highlightArea = HighlightArea(RectF(
                                this@MaskViewActivity.btn.left.toFloat(),
                                this@MaskViewActivity.btn.top.toFloat(),
                                this@MaskViewActivity.btn.right.toFloat(),
                                this@MaskViewActivity.btn.bottom.toFloat()))
            }
        }
    }.showOnWindow(supportFragmentManager)

详细设置请看 MaskView 中注释,十分详细

v1.1.7
为 dialog 的 show 和 dismiss 新增增量方法 onAddDialogShow 和 onAddDialogDismiss,用于在同一个监听中新增逻辑
v1.1.6
修复 viewHolder 中的 setVisible 类型转换问题,kotlin 升级到 1.2.71

废话不多说,直接上图

onWindow

onView

onView

内置大量基础动画,基本能满足基本需求

第一次写 README 也不知道该怎么吹,就直接贴代码吧

newGenjiDialog {
    width = dp2px(100f)
    height = dp2px(100f)
}.showOnWindow(supportFragmentManager)

就这么简单,一个默认居中的加载中 loadDialog 就出来了,如图:

loading.GIF

1、位置

如果我想让他显示在屏幕右上角怎么办?非常简单!

newGenjiDialog {
    width = dp2px(100f)
    height = dp2px(100f)
    gravity = DialogGravity.RIGHT_TOP
}.showOnWindow(supportFragmentManager)

DialogGravity 这个枚举中我设定了 9 种显示方式,左上,中上,右上,左中,正中,右中,左下,中下,右下

因此只需要添加一行代码指定 dialog 的位置就行了、效果如图:

loading_right_top.GIF

你现在可能要问了,你只设置了 9 个位置,我需要偏移怎么办呢, 当然,这时候就需要用到偏移了

newGenjiDialog {
    width = dp2px(100f)
    height = dp2px(100f)
    verticalMargin = dp2px(100f).toFloat()
    horizontalMargin = dp2px(100f).toFloat()
    gravity = DialogGravity.RIGHT_TOP
}.showOnWindow(supportFragmentManager)

就这样我就给 dialog 加上了横向纵向分别 100dp 的偏移,效果如图:

loading_right_top_margin

关于这个偏移量,这里我多说两句,原本偏移量的取值范围是在[0-1],指的是所占屏幕宽高的百分比, 但是为了方便起见,我这里给大于 1 的偏移量自动换算成了百分比,如果针对个别机型有误差的,可以自行换算成[0-1]即可

2、动画

细心地你可能发现了,在屏幕右上角显示的时候是从屏幕边缘滑出的, 没错,我给 DialogGravity 的每一个显示位置都设定了默认的动画, 当没有指定动画的时候就会按照默认的动画来显示、

当然自定义动画是肯定要有的

newGenjiDialog {
    width = dp2px(100f)
    height = dp2px(100f)
    animStyle = R.style.ScaleADEnterExitAnimationX50Y50
    gravity = DialogGravity.RIGHT_TOP
}.showOnWindow(supportFragmentManager)

这样就完成了动画的自定义,当然你还可以这样写:

dialog.showOnWindow(supportFragmentManager,DialogGravity.RIGHT_TOP,R.style.ScaleADEnterExitAnimationX50Y50)

效果如图:

loading_right_top_scale_ad

内置动画我在 style 文件中注释写了作用,可以自己去看看

如果想要贴在一个 view 附近怎么办?

newGenjiDialog {
    width = dp2px(100f)
    height = dp2px(100f)
    animStyle = R.style.ScaleADEnterExitAnimationX50Y100
    gravityAsView = DialogGravity.CENTER_TOP
}.showOnView(supportFragmentManager,showLoading)

需要注意的是这里的 gravaty 换成了 gravityAsView,效果如图:

loading_center_top_as_view

同样你还可以这样写:

dialog.showOnWindow(supportFragmentManager,DialogGravity.RIGHT_TOP,R.style.ScaleADEnterExitAnimationX50Y50)
相对 view 的偏移量 offsetX 和 offsetY 属性

这两个属性建议去 DialogOptions 中的 dialogAsView()方法去查看方法注释

附加一个稍微特殊点的滑出方式(带遮罩)
newGenjiDialog { genjiDialog ->
    //设置布局
    layoutId = R.layout.slide_view_bottom
    //isLazy = true
    //设置横纵向占满
    isFullHorizontal = true
    isFullVerticalOverStatusBar = true
    //阴影透明度
    dimAmount = 0f
    //处理事件/数据绑定
    convertListenerFun { holder, dialog ->
        //设置点击 realView 以外的部分就 dismiss
        holder.setOnClickListener(R.id.bottomTouchView) {
            if (canClick) {
                dialog.dismiss()
            }
        }.setOnClickListener(R.id.topTouchView) {
            if (canClick) {
                dialog.dismiss()
            }
        }
    }
    setOnEnterAnimator { rootView ->
        //在此处设置进入动画
        AnimatorSet().apply {
            duration = 500L
            val realView = rootView.findViewById<View>(R.id.realView)
            val touchView = rootView.findViewById<View>(R.id.bottomTouchView)
            val topTouchView = rootView.findViewById<View>(R.id.topTouchView)
            val maskLayout = rootView.findViewById<View>(R.id.maskLayout)
            //给 realView 的父布局(遮罩布局)设置距顶部 margin
            maskLayout?.apply {
                layoutParams = (layoutParams as ConstraintLayout.LayoutParams).apply {
                    topMargin = (slideForBottom.y + slideForBottom.height).toInt()
                }
            }
            play(ObjectAnimator
                    .ofFloat(realView, "y", -UtilsExtension.dp2px(resources, 200f).toFloat(), 0f))
                    .with(ObjectAnimator
                            .ofFloat(touchView, "alpha", 0f, 1f))
                    .with(ObjectAnimator
                            .ofFloat(topTouchView, "alpha", 0f, 1f))
        }
    }
    setOnExitAnimator {
        //退出动画
        AnimatorSet().apply {
            duration = 500L
            val realView = it.findViewById<View>(R.id.realView)
            val touchView = it.findViewById<View>(R.id.bottomTouchView)
            val topTouchView = it.findViewById<View>(R.id.topTouchView)
            play(ObjectAnimator
                    .ofFloat(realView, "y", 0f, -UtilsExtension.dp2px(resources, 200f).toFloat()))
                    .with(ObjectAnimator
                            .ofFloat(touchView, "alpha", 1f, 0f))
                    .with(ObjectAnimator
                            .ofFloat(topTouchView, "alpha", 1f, 0f))
        }
    }
}.showOnWindow(supportFragmentManager)

效果图:

show_mask_slide_down

基本的显示模式都已经说了,接下来就放一个整体可见的参数表格出来

GenjiDialog 中
属性名/方法名 介绍
rootView layoutId 所对应的布局
getMyActivity() 获取该 dialog 所在的 activity
setDialogOptions(...) 设置 dialogOptions
getDialogOptions() 获取 dialogOptions
extendsOptions() 当继承 GenjiDialog 时需要重写该方法,在该方法里面设置新的 dialogOptions
showOnWindow(...) 将 dialog 显示在屏幕中,有多个重载方法,具体可见源码注释
showOnView(...) 将 dialog 依附于某个 View,有多个重载方法,具体可见源码注释
DialogOptions
属性名/方法名 介绍
layoutId 布局 id,默认:R.layout.loading_layout
dialogStyle dialog 的样式,一般情况下不用修改,为了方便某些朋友可能有特殊需求,所以放出来可供重写,默认:DialogFragment.STYLE_NO_TITLE
dialogThemeFun dialog 的主题,同上,重写方法 setDialogTheme(fun)
setStatusBarModeFun dialog 的状态栏设置,同上,重写方法 setStatusMode(fun)
animStyle dialog 的进出动画,用于一般情况,内置了很多日常所需动画,可以到 res/values/styles 中查看,动画文件在 res/anim 中查看,默认根据 gravity 来判断
setOnEnterAnimator(fun) dialog 的进入动画,这个动画是用于一些特殊情况,比如上面的带遮罩的滑出动画,默认:null
exitAnimator(fun) dialog 的退出动画,同上 (这两个动画的示例请看上面带遮罩滑出动画的代码,也可以去源码查看)
canClick 否可以触发取消,默认:true,比如在动画开始时将此属性设置 false,防止在动画进行时,被再次触发动画,当使用上面两种自定义特殊动画时,我已经默认添加了改变这个状态值的监听
initMode 初始化模式,默认:DialogInitMode.DRAW_COMPLETE,表示在视图绘制完成时调用 convertListener,其余类型请查看 DialogInitMode 类
duration 懒加载的延时,默认:0L,配合 isLazy 使用,这个值一般设置为动画的时长,为了保证动画流畅
dialogStatusBarColor dialog 的 statusBar 颜色,默认:透明,一般来说无需改变
width dialog 宽度,默认:0px
height dialog 高度,默认:0px
isFullHorizontal dialog 是否横向占满,默认:false
isFullVertical dialog 是否纵向占满,默认:false,该纵向占满并非全屏,纵向占满会自动扣掉状态栏的高度
isFullVerticalOverStatusBar dialog 是否纵向占满,默认:false,该纵向占满全屏不会扣掉状态栏高度,是真正的全屏
verticalMargin dialog 上下边距,默认:0,详细注释请在源码中查看
horizontalMargin dialog 左右边距,默认:0,详细注释请在源码中查看
fullVerticalMargin dialog 在上下占满时的边距,默认:0px
fullHorizontalMargin dialog 在左右占满时的边距,默认:0px
dimAmount dialog 背景的阴影的透明度:默认:0.3f
gravity dialog 显示为 showOnWindow()时的位置:默认:DialogGravity.CENTER_CENTER
gravityAsView dialog 显示为 showOnView()时的位置:默认:DialogGravity.CENTER_BOTTOM
dialogViewX x 轴坐标值,用于特殊动画时定位 dialog,默认:0px
dialogViewY y 轴坐标值,用于特殊动画时定位 dialog,默认:0px
offsetX 当 dialog 依附在 view 上时 x 轴的偏移量,默认:0px
offsetX 当 dialog 依附在 view 上时 x 轴的偏移量,默认:0px
touchCancel 是否点击屏幕区域取消(不包含返回按钮),默认:false
outCancel 是否点击外部取消,默认:false,当 touchCancel == true 时此属性无效,必须是 touchCancel 和该属性均为 false 时,那么点击屏幕区域和返回按钮都不能关闭 dialog
showDismissMap 显示与消失的监听 map
onKeyListener 按钮监听
convertListener view 初始化

这里我给出的属性/方法,注释不详细可以到 DialogOptions 源码里面查看,那里面注释很详细

因为也是第一次写 kotlin 的库,可能有一些东西不算很完美,希望有能力强的大佬能够指出

喜欢的话请点个 star 支持一下,谢谢

MIT License

Copyright (c) 2018 q876625596

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools