在项目开发中,我们经常需要创建各种各样的自定义弹窗,为了简约代码和统一管理,我初步搭建了这么一个简单的框架,待日后在新项目中使用后再不断维护。
------ 更新 2019/11/27 ------
这只是初始的想法搭建的框架,本文只说明思维方式,并不提供其他类型弹窗代码,有需要的朋友根据以下代码,进行自定义开发
另外本文也是提供一种在每个弹窗上都带有进度条的代码方法,供大家参考
/**
* 基础弹窗
*
* @author D10NG
* @date on 2019-10-25 14:49
*/
open class BaseDialog<T> constructor(
private val context: Context
) {
/** 弹窗创建器 */
protected val builder = AlertDialog.Builder(context)
/** 最终的弹窗实例 */
protected var alert: AlertDialog? = null
/** 基础弹窗布局 */
val binding: DialogDefLoadingViewBinding = DataBindingUtil.inflate(
LayoutInflater.from(context),
R.layout.dialog_def_loading_view, null, false)
/**
* 创建
*/
open fun create() : T {
binding.loadIndeterminate = true
binding.loadVisible = false
builder.setView(binding.root)
builder.setCancelable(false)
alert = builder.create()
return this as T
}
/**
* 移除所有button
*/
open fun removeAllButtons() {
binding.buttonLayout.removeAllViews()
}
/**
* 移除content内容
*/
open fun removeContent() {
binding.contentLayout.removeAllViews()
}
/**
* 设置标题
*/
open fun setTittle(tittle: String) : T {
binding.tittle = tittle
return this as T
}
/**
* 设置二级文本
*/
open fun setMsg(msg: String) : T {
binding.message = msg
return this as T
}
/**
* 设置图标
*/
open fun setIcon(resId: Int) : T {
binding.image.setImageResource(resId)
return this as T
}
/**
* 设置图标
*/
open fun setIcon(bitmap: Bitmap) : T {
binding.image.setImageBitmap(bitmap)
return this as T
}
/**
* 开始加载中
*/
open fun startLoad(indeterminate: Boolean, progress: Int, max: Int) {
binding.loadIndeterminate = indeterminate
binding.loadProgress = progress
binding.loadMax = max
binding.loadVisible = true
}
/**
* 停止加载中
*/
open fun stopLoad() {
binding.loadVisible = false
}
/**
* 添加button事件
*/
open fun addAction(text: String, style: Int, onBtnClick: OnBtnClick?) : T {
binding.buttonLayout.addView(createButton(text, style, onBtnClick))
return this as T
}
/**
* 显示
*/
open fun show() {
alert?.show()
}
/**
* 关闭
*/
open fun dismiss() {
alert?.dismiss()
}
protected fun createButton(text: String, style: Int, onBtnClick: OnBtnClick?) : Button {
val button = Button(context)
button.background = ContextCompat.getDrawable(context, R.drawable.button_top_line_bg)
button.text = text
when(style) {
ButtonStyle.THEME -> button.setTextColor(ContextCompat.getColor(context, R.color.colorPrimary))
ButtonStyle.NORMAL -> button.setTextColor(ContextCompat.getColor(context, R.color.text_hint_color))
ButtonStyle.ERROR -> button.setTextColor(ContextCompat.getColor(context, R.color.text_wrong_color))
}
button.setOnClickListener {
if (null == onBtnClick) {
dismiss()
} else {
onBtnClick.click(this, text)
}
}
return button
}
}
此处用了 DataBinding 使用方法参考我的另一篇文章 :
Android Kotlin学习 Jitpack 组件之DataBinding
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View"/>
<variable
name="loadIndeterminate"
type="boolean" />
<variable
name="loadProgress"
type="int" />
<variable
name="loadMax"
type="int" />
<variable
name="loadVisible"
type="boolean" />
<variable
name="tittle"
type="String" />
<variable
name="message"
type="String" />
data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
<TextView
android:id="@+id/txt_tittle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:gravity="center"
android:singleLine="true"
android:text="@{tittle}"
android:textColor="#ff333333"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:adjustViewBounds="true"
android:scaleType="centerInside"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/txt_tittle" />
<TextView
android:id="@+id/txt_message"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:lineSpacingExtra="6sp"
android:maxLines="10"
android:text="@{message}"
android:textAlignment="center"
android:textColor="#ff666666"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image" />
<LinearLayout
android:id="@+id/content_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/txt_message">
LinearLayout>
<ProgressBar
android:id="@+id/pb_load"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:indeterminate="@{loadIndeterminate}"
android:max="@{loadMax}"
android:progress="@{loadProgress}"
android:visibility="@{loadVisible? View.VISIBLE : View.INVISIBLE}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/content_layout" />
<LinearLayout
android:id="@+id/button_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/pb_load">
LinearLayout>
androidx.constraintlayout.widget.ConstraintLayout>
layout>
object ButtonStyle {
const val THEME = 1
const val ERROR = 2
const val NORMAL = 3
}
interface OnBtnClick {
fun click(d0: BaseDialog<*>, text: String)
}
class ButtonDialog constructor(
context: Context
) : BaseDialog<ButtonDialog>(context)
dialog_edit_view.xml
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/ti_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:errorEnabled="true">
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/edt_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="hint" />
com.google.android.material.textfield.TextInputLayout>
LinearLayout>
layout>
/**
* 带编辑框的弹窗
*
* @author D10NG
* @date on 2019-11-23 10:05
*/
class EditDialog constructor(
private val context: Context
) : BaseDialog<EditDialog>(context) {
init {
// 改变宽度
val params = binding.contentLayout.layoutParams
params.width = LinearLayout.LayoutParams.MATCH_PARENT
binding.contentLayout.layoutParams = params
}
/** 编辑框列表 */
private val edtMap: MutableMap<String, DialogEditViewBinding> = mutableMapOf()
/**
* 添加一个编辑框
* @param tag 标签
* @param text 初始文本
* @param hint 提示文本
*/
fun addEdit(tag: String, text: String, hint: String) : EditDialog {
val viewBinding: DialogEditViewBinding = DataBindingUtil.inflate(
LayoutInflater.from(context),
R.layout.dialog_edit_view, null, false
)
viewBinding.edtText.setText(text)
viewBinding.tiLayout.hint = hint
binding.contentLayout.addView(viewBinding.root)
edtMap[tag] = viewBinding
return this
}
/**
* 获取输入文本
* @param tag 标签
*/
fun getInputText(tag: String) : String {
return edtMap[tag]?.edtText?.text.toString().trim()
}
/**
* 显示错误信息
* @param tag 标签
* @param value 信息
*/
fun setError(tag: String, value: String) : EditDialog {
edtMap[tag]?.tiLayout?.error = value
return this
}
}
dialog_recycle_view.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="startText"
type="String" />
<variable
name="endText"
type="String" />
data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/txt_start"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="@{startText}" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rcv"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
androidx.recyclerview.widget.RecyclerView>
<TextView
android:id="@+id/txt_end"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="@{endText}" />
LinearLayout>
layout>
dialog_normal_item_view.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="itemText"
type="String" />
<variable
name="isSelected"
type="boolean" />
data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="35dp"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:ellipsize="middle"
android:gravity="center"
android:minWidth="100dp"
android:text="@{itemText}"
android:textColor="@{isSelected? @color/colorPrimary : @color/text_hint_color}"
android:textSize="@{isSelected? 80 : 50}" />
LinearLayout>
layout>
/**
* 单选弹窗
*
* @author D10NG
* @date on 2019-11-25 16:36
*/
class SingleChooseDialog constructor(
private val context: Context
) : BaseDialog<SingleChooseDialog>(context) {
init {
// 改变内容排列方向
binding.contentLayout.orientation = LinearLayout.HORIZONTAL
// 改变固定高度
val params = binding.contentLayout.layoutParams
params.height = 600
binding.contentLayout.layoutParams = params
}
/** 选择项列表 */
private val recyclerMap: MutableMap<String, DialogRecycleViewBinding> = mutableMapOf()
/**
* 添加选择列表
* @param tag 标签
* @param selectItem 选择项
* @param list 全部选项
* @param start 开始文本
* @param end 结束文本
*/
fun addSelectionList(tag: String, selectItem: String, list: List<String>, start: String, end: String) : SingleChooseDialog {
val viewBinding: DialogRecycleViewBinding = DataBindingUtil.inflate(
LayoutInflater.from(context),
R.layout.dialog_recycle_view, null, false
)
viewBinding.startText = start
viewBinding.endText = end
viewBinding.rcv.layoutManager = LinearLayoutManager(context)
val adapter = NormalAdapter(selectItem, list)
viewBinding.rcv.adapter = adapter
viewBinding.rcv.post {
viewBinding.rcv.smoothScrollToPosition(list.indexOf(selectItem) + 3)
}
binding.contentLayout.addView(viewBinding.root)
recyclerMap[tag] = viewBinding
return this
}
/**
* 获取选中项文本内容
*/
fun getSelectOnTag(tag: String) : String {
val viewBinding = recyclerMap[tag]?: return ""
val adapter = viewBinding.rcv.adapter as NormalAdapter
return adapter.selectStr
}
}
fun test(v: View) {
val buttonDialog = ButtonDialog(this)
.setTittle("标题")
.setMsg("文本,段落,清晰,强大觉得还u会丢啊就是不对劲啊混合双打u看见我还大手大脚卡号!「大三大四的」")
.setIcon(R.mipmap.icon_test)
.addAction("确定", ButtonStyle.THEME, null)
.addAction("取消", ButtonStyle.NORMAL, null)
.addAction("乱来", ButtonStyle.ERROR,
object : OnBtnClick{
override fun click(d0: BaseDialog<*>, text: String) {
// 进度条
d0.startLoad(true, 50, 100)
}
})
.create()
buttonDialog.show()
// 取消进度条
buttonDialog.stopLoad()
}
fun test(v: View) {
val editDialog = EditDialog(this)
.setTittle("标题")
.setMsg("设置文本")
.addEdit("tag1", "12345678901234567890", "请输入密码")
.addAction("确定", ButtonStyle.THEME,
object : OnBtnClick{
override fun click(d0: BaseDialog<*>, text: String) {
val dialog = d0 as EditDialog
if (dialog.getInputText("tag1") != "666666") {
dialog.setError("tag1", "密码错误")
} else {
dialog.dismiss()
}
}
})
.create()
editDialog.show()
}
val list = mutableListOf<String>()
for (i in 0 .. 100) {
list.add("$i")
}
SingleChooseDialog(this)
.setTittle("提示")
.setMsg("设定温度")
.addSelectionList("temp", "50", list,
"", "℃")
.addAction(resources.getString(R.string.sure), ButtonStyle.THEME, object : OnBtnClick{
override fun click(d0: BaseDialog<*>, text: String) {
val d = d0 as SingleChooseDialog
// 拿到温度
val temp = d.getSelectOnTag("temp").toInt()
viewModel.update(temp)
d.dismiss()
}
})
.addAction(resources.getString(R.string.cancel), ButtonStyle.NORMAL, null)
.create()
.show()