我以前在做通信业务中,遇到过需要根据字段动态构建和填充UI的需求。而Android布局非常适合用建造者模式来构建来自各个视图组件的布局和视图组。比如,我们经常会用AlertDialog.Builder来构建布局,它们就是由有序的较小对象集合而组成的。
现在我们遵循建造者模式,来用一系列预定的布局视图进行线性布局的填充:我们需要创建两种布局项,分别是标题视图和内容视图,然后再构建一个可以由构造者构建的具体Demo。
①创建一个视图类接口。
interface LayoutView {
fun layoutParams():ViewGroup.LayoutParams
fun textSize():Int
fun content():Int
fun shading():Shading//文本和背景色
fun padding():IntArray
}
注意一下shading()方法,这个方法返回值是Shading,Shading表示的是所有视图都共有的一些特征(例如文本和背景色),所以我们会通过另一个接口来避免复制方法。
②创建文本和背景色接口。
/**
* 所有视图的共同特征:文本和背景色
*/
interface Shading {
fun shade():Int
fun background():Int
}
③创建Shading的具体实现,标题视图和内容视图是不一样的,所以需要分别实现。
/**
* Shading的具体实现类
*/
class HeaderShading:Shading {
override fun shade(): Int {
return R.color.text_primary_light
}
override fun background(): Int {
return R.color.title_background
}
}
/**
* Shading的具体实现类
*/
class ContentShading :Shading{
override fun shade(): Int {
return R.color.text_secondary_light
}
override fun background(): Int {
return R.color.content_background
}
}
④接下来就到了处理标题视图和内容视图的抽象实现了。创建抽象实现可以使我们以后更方便地增加对应的可选功能,这个好处一会在创建标题视图的时候就能看出来了。
/**
* 标题视图的抽象实现
*/
abstract class Header :LayoutView{
override fun shading(): Shading {
return HeaderShading()
}
}
/**
* 内容视图的抽象实现
*/
abstract class Content:LayoutView {
override fun shading(): Shading {
return ContentShading()
}
}
⑤然后创建具体类,先创建标题视图的,我们创建两个标题视图,分别是主标题和次标题。
/**
* Header类型的具体类
*/
class Headline:Header() {
override fun layoutParams(): ViewGroup.LayoutParams {
val width = ViewGroup.LayoutParams.MATCH_PARENT
val height = ViewGroup.LayoutParams.WRAP_CONTENT
return ViewGroup.LayoutParams(width,height)
}
override fun textSize(): Int {
return 24
}
override fun content(): Int {
return R.string.headline
}
override fun padding(): IntArray {
return intArrayOf(24,16,16,0)
}
}
/**
* Header类型的具体类2
*/
class SubHeadline :Header(){
override fun layoutParams(): ViewGroup.LayoutParams {
val width = ViewGroup.LayoutParams.MATCH_PARENT
val height = ViewGroup.LayoutParams.WRAP_CONTENT
return ViewGroup.LayoutParams(width,height)
}
override fun textSize(): Int {
return 18
}
override fun content(): Int {
return R.string.sub_head
}
override fun padding(): IntArray {
return intArrayOf(32,0,16,8)
}
}
⑥创建内容视图的具体类,分别是简单内容和详细内容。
/**
* Content类型的具体类
*/
class SimpleContent :Content(){
override fun layoutParams(): ViewGroup.LayoutParams {
val width = ViewGroup.LayoutParams.MATCH_PARENT
val height = ViewGroup.LayoutParams.MATCH_PARENT
return ViewGroup.LayoutParams(width,height)
}
override fun textSize(): Int {
return 14
}
override fun content(): Int {
return R.string.short_text
}
override fun padding(): IntArray {
return intArrayOf(16,18,16,16)
}
}
/**
* Content类型的具体类
*/
class DetailedContent:Content() {
override fun layoutParams(): ViewGroup.LayoutParams {
val width = ViewGroup.LayoutParams.MATCH_PARENT
val height = ViewGroup.LayoutParams.WRAP_CONTENT
return ViewGroup.LayoutParams(width,height)
}
override fun textSize(): Int {
return 12
}
override fun content(): Int {
return R.string.long_text
}
override fun padding(): IntArray {
return intArrayOf(16,18,16,16)
}
}
⑦创建一个辅助类,以便按照我们希望的顺序将这些视图放在一起。
/**
* 建造者
*/
class LayoutBuilder {
//详细布局
fun displayDetailed():List<LayoutView>{
val views = ArrayList<LayoutView>()
views.add(Headline())
views.add(SubHeadline())
views.add(DetailedContent())
return views
}
//简单输出
fun displaySimple():List<LayoutView>{
val views = ArrayList<LayoutView>()
views.add(Headline())
views.add(SimpleContent())
return views
}
}
到这里,就只差在Activity中进行动态填充了。在这之前,我们先来看看UML图。
可以看到,作为客户端的Activity仅仅和LayoutBuilder发生关系,这样我们之前所做的所有模型逻辑对于客户端来说都是隐藏的。
最后,在Activity中使用建造者动态地填充布局。
/**
* 动态填充布局Demo
*/
class DynamicActivity : AppCompatActivity() {
lateinit var textView:TextView
lateinit var layout:LinearLayout
override fun onCreate(savedInstanceState: Bundle?) {
val width = ViewGroup.LayoutParams.MATCH_PARENT
val height = ViewGroup.LayoutParams.WRAP_CONTENT
super.onCreate(savedInstanceState)
layout = LinearLayout(this)
layout.orientation = LinearLayout.VERTICAL
layout.layoutParams = ViewGroup.LayoutParams(width, height)
setContentView(layout)
//创建详细布局
makeDisplay(LayoutBuilder().displayDetailed())
//创建简单布局
makeDisplay(LayoutBuilder().displaySimple())
}
private fun makeDisplay(layoutViews:List<LayoutView>){
for (layoutView in layoutViews){
val params = layoutView.layoutParams()
textView = TextView(this)
textView.layoutParams = params
textView.setText(layoutView.content())
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, layoutView.textSize().toFloat())
textView.setTextColor(layoutView.shading().shade())
textView.setBackgroundColor(layoutView.shading().background())
val pad = layoutView.padding()
textView.setPadding(dp(pad[0]),dp(pad[1]),dp(pad[2]),dp(pad[3]))
layout.addView(textView)
}
}
//将px转换为dp
private fun dp(px:Int):Int{
val scale = resources.displayMetrics.density
return (px*scale+0.5f).toInt()
}
}
注意这里需要将px转为dp。
最后运行,看下效果。