Android 动态生成页面

date
Aug 10, 2022
slug
android page generate
status
Published
tags
Android
RecyclerView
View
summary
Android 动态生成页面的几种方案
type
Post
通常情况下,Android 开发的布局绘制都是在 xml 中利用基础控件对页面结构进行基础布局,及时有复杂 View,也是配合自定义 View 的方式来进行。这种方式简单快捷,但却不利于动态更新。就像下面这个页面。
 
notion image

方案:列表视图

遇到这种情况我们第一反应是用列表布局,ListView 或 RecyclerView,用列表布局看起来很好,其实是有一些问题:
  1. 页面的配置项可能很少,就像上图算上 Toolbar 差不多也就占用了屏幕的 1/2 的空间,用不上滑动;
  1. 列表布局通常是带有 Adapter,每个页面都要写一个 Adapter 是不是过于繁琐,尤其是现在 RecyclerView 正在大行其道,还得写他的 ViewHolder。
这这个基础,出现了一些新的实现方式
  1. 创建全局统一的 RecyclerView 和 Adapter
  1. 不同的单行 View 使用不同的 ViewHolder、数据类,且在数据类的基类中申明当前的类型;
这种方式完全能解决这个问题,不过这种解决方案也有一个弊端,不便于扩展,或者说耦合度太高。

方案:列表解耦

举个例子:当另一种风格的配置页面出现或者需要在另一个项目中使用时就不那么友好了,预先定义的数据类的基类可能并不适用,或者不便更改,RecyclerView 的 Adapter 如何动态的支持不同的数据类型,这都是一些问题。
 
经常逛 GitHub 的朋友可能熟悉这个 RecyclerView 的库 ,这个库很好的将 RecyclerView 的多类型进行了拆分,只需要在使用前向 Adapter 进行注册即可。
class FooViewDelegate : ViewDelegate<Foo, FooView>() {

  override fun onCreateView(context: Context): FooView {
    return FooView(context).apply { layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT) }
  }

  override fun onBindView(view: FooView, item: Foo) {
    view.imageView.setImageResource(item.imageResId)
    view.textView.text = item.text

    view.textView.text = """
		|${item.text}
		|viewHolder: ${view.holder}
		|layoutPosition: ${view.layoutPosition}
		|absoluteAdapterPosition: ${view.absoluteAdapterPosition}
		|bindingAdapterPosition: ${view.bindingAdapterPosition}
""".trimMargin()
  }
}

class SampleActivity : AppCompatActivity() {

  private val adapter = MultiTypeAdapter()
  private val items = ArrayList<Any>()

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_list)
    val recyclerView = findViewById<RecyclerView>(R.id.list)

    adapter.register(TextItemViewDelegate())
    adapter.register(ImageItemViewDelegate())
    adapter.register(RichItemViewDelegate())
    recyclerView.adapter = adapter

    val textItem = TextItem("world")
    val imageItem = ImageItem(R.mipmap.ic_launcher)
    val richItem = RichItem("小艾大人赛高", R.drawable.img_11)

    for (i in 0..19) {
      items.add(textItem)
      items.add(imageItem)
      items.add(richItem)
    }
    adapter.items = items
    adapter.notifyDataSetChanged()
  }
}
 
不过当遇到针对多个 item 共用背景的时候再使用 RecyclerView 就不是很方便了。
notion image

方案:ConstraintLayout 动态布局

在这种情况下,出现了组的概念,同一个 Item 组共用一个圆角矩形的背景。这种该如何去实现呢?
我的方案是借鉴 的方案,不同的是整个页面用 ConstraintLayout 进行实现,通过
ViewDelegate 生成 View 动态添加到 ConstraintLayout 上并为其配置相关约束条件即可。组的背景同样也是通过 View 进行实现,并将约束关联到他所属的 View 上。
private fun applyBackground(creator: GroupCreator, group: IntArray) {
  val createView = creator.createView(container.context, group)
  if (createView.id==NO_ID) {
    createView.id= generateViewId()
  }
  val tempLayoutParams = if (createView.layoutParams== null) {
    ConstraintLayout.LayoutParams(0, 0)
  } else {
    ConstraintLayout.LayoutParams(createView.layoutParams)
  }
  if (container.contains(group[0], group[group.size - 1])) {
    tempLayoutParams.topToTop = container.get(group[0]).id
		tempLayoutParams.bottomToBottom = container.get(group[group.size - 1]).id
		tempLayoutParams.startToStart = container.get(group[0]).id
		tempLayoutParams.endToEnd = container.get(group[0]).id
		container.addView(createView, 0, tempLayoutParams)
    bgCount += 1
  } else {
    Log.w(TAG, "group ${group.joinToString{"$it"}} index not fount in container(${container.childCount})")
  }
}
 

© Craig Hart 2021 - 2025