StudyDemos的Demo列表的动态配置实现

背景

每次在StudyDemos工程里,添加知识点Demo时,总有一部分工作是重复的,那就是在首页添加入口。省掉这个重复添加的过程就是我的目的。

实现思路

与APIDemo App类似,实现思路如下:

  1. 对manifest里的activity添加两个约定,查找规范和目录规范
  2. 首页自动扫描上面特定的intent-filter,获取Demo列表和名称
  3. 通过RecyclerView显示,同时支持多级目录

效果如下所示:

此Demo的源码:AndroidStudyDemo

实现过程

查找规范和目录规范

查找规范:通过指定特殊的intent-filter实现过滤出真正的Demo Activity,复用Android已有的SAMPLE_CODE类别,如下所示:

1
2
3
4
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.SAMPLE_CODE"/>
</intent-filter>

目录规范:通过“/”分割多级目录和名称,如下所示:

1
android:label="Binder/AIDL例子"

首页显示

由于有目录的概念,不是简单的查找出所有的Demo Activity后直接显示,需要判断类型:

  1. 当前如果是目录,跳转的Intent还首页,但只显示当前目录下的子目录或Demo
  2. 当前如果是Demo,跳转的Intent就是Demo界面

查询所有的Demo

1
2
3
4
5
6
7
val intent: Intent = Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_SAMPLE_CODE)
// 为了防止把系统的Demo也查询出来,指定包名查询
.setPackage(this.packageName)

val list: List<ResolveInfo> =
this.packageManager.queryIntentActivities(intent, 0) ?: return demos

对所有的Demo进行过滤处理,只获取当前目录下的子目录或Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
private fun getData(prefix: String): MutableList<DemosAdapter.Demo> {
val demos: MutableList<DemosAdapter.Demo> = mutableListOf<DemosAdapter.Demo>()

val intent: Intent = Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_SAMPLE_CODE)
.setPackage(this.packageName)

val list: List<ResolveInfo> =
this.packageManager.queryIntentActivities(intent, 0) ?: return demos

var prefixPath: List<String>? = null
var prefixWithSlash: String = prefix
if(prefix != "") {
prefixPath = prefix.split("/")
prefixWithSlash = "$prefix/"
}

// 此Map是用于过滤重复子目录
val map:MutableMap<String, Boolean> = mutableMapOf<String, Boolean>()

for (info in list) {
val label:String = info.activityInfo.loadLabel(this.packageManager).toString()

if(prefixPath == null || label.startsWith(prefixWithSlash)) {

val labelPath = label.split("/")

// 获取当前的显示名称
val nextLabel: String = labelPath[prefixPath?.size ?: 0]

// 通过长度判断当是否是最后一个path
if ((prefixPath?.size ?: 0) == (labelPath.size - 1)) {
val result = Intent()
result.setClassName(info.activityInfo.packageName, info.activityInfo.name)
demos.add(DemosAdapter.Demo(nextLabel, result))
} else {
if(map.get(nextLabel) == null){
val result = Intent(this, StudyDemos::class.java)
result.putExtra("com.example.android.apis.Path", if(prefix == "") nextLabel else "${prefix}/${nextLabel}")
demos.add(DemosAdapter.Demo(nextLabel, result))
map.put(nextLabel, true)
}
}
}
}
return demos
}

RecyclerView显示

初始化RecyclerView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
val recyclerView: RecyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.setHasFixedSize(true)

// set LayoutManager
val linearLayoutManager = LinearLayoutManager(this)
linearLayoutManager.orientation = LinearLayoutManager.VERTICAL
recyclerView.layoutManager = linearLayoutManager

// set Adapter
val demos: MutableList<DemosAdapter.Demo> = getData(path?:"")
recyclerView.adapter = DemosAdapter(demos, object: DemosAdapter.OnItemClickListener {
override fun onClick(holder: DemosAdapter.DemosViewHolder, demo: DemosAdapter.Demo) {
this@StudyDemos.startActivity(demo.intent)
}
})

// set 分隔线
recyclerView.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))

Adapter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class DemosAdapter(var demos: MutableList<Demo>, var itemClickListener: OnItemClickListener) : RecyclerView.Adapter<DemosAdapter.DemosViewHolder>() {

override fun getItemViewType(position: Int): Int {
return 0
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DemosViewHolder {
val inflater = LayoutInflater.from(parent.context)

val itemView = inflater.inflate(R.layout.item_demo, parent, false)
return DemosViewHolder(itemView)
}

override fun getItemCount(): Int {
return demos.size
}

override fun onBindViewHolder(holder: DemosViewHolder, position: Int) {
val demo = demos[position]
holder.titleView.setText(demo.title)
holder.itemView.setOnClickListener {
itemClickListener.onClick(holder, demo)
}
}

interface OnItemClickListener {
fun onClick(holder: DemosViewHolder, demo: Demo)
}

class Demo(var title: String, var intent: Intent) {}

class DemosViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

val titleView: TextView

init {
titleView = itemView.findViewById(R.id.title_view)
}
}
}

参考

  1. ApiDemos
感谢您的阅读,本文由 刘阳 版权所有。如若转载,请注明出处:刘阳(https://handsomeliuyang.github.io/2020/11/05/%E7%BB%8F%E9%AA%8C%E6%80%BB%E7%BB%93-StudyDemos%E7%9A%84Demo%E5%88%97%E8%A1%A8%E7%9A%84%E5%8A%A8%E6%80%81%E9%85%8D%E7%BD%AE%E5%AE%9E%E7%8E%B0/
hexo本地静态搜索实现
群晖:搭建个人和工作的数据中心