需求
首页内容可动态变化,需要从json读取布局配置,然后再组织内容
项目参考
参考地址
json 结构设计
例如将整个区域,4*3(横屏)个单元格。
{
"listItems": [
{
"type": "COLUMN",
"listItems": [
{
"type": "CLASSINFO",
"wCell": 2,
"hCell": 1
},
{
"type": "CLASSGALLERY",
"wCell": 2,
"hCell": 2
}
],
"wCell": 2,
"hCell": 3
},
//...略
}
listItems:表示该区域包含的子模块,自由第一层和 type是 COLUMN或者 ROW的模块才有该属性。
上述json表示,该区域,包含一个类型为column的区域,column的大小为 23,column里面的又包含两个子区域,一个是 CLASSINFO,大小 21,另外一个 CLASSGALLERY,大小2*2。
解析json文件
那我们怎么解析这个文件呢,就需要导入 kotlin友好的现代 JSON 库 moshi。
1. 导入依赖
kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.13.0'
implementation "com.squareup.moshi:moshi:1.13.0"
implementation "com.squareup.moshi:moshi-kotlin:1.13.0"
implementation "com.squareup.moshi:moshi-adapters:1.13.0"
2. 读取文件
fun readAssetsFile(context: Context, fileName: String): String {
try {
val inputStream = context.assets.open(fileName)
val filelength = inputStream.available()
val byteArray = ByteArray(filelength)
inputStream.read(byteArray)
inputStream.close()
return String(byteArray, Charsets.UTF_8)
} catch (e: Exception) {
e.printStackTrace()
}
return "read json file error"
}
3. 解析string
fun loadHomeComponents(jsonComponents: String): HomeComponent? {
val moshi = Moshi.Builder()
.add(
PolymorphicJsonAdapterFactory.of(ComponentItem::class.java, "type")
.withSubtype(
Row::class.java,
ComponentType.ROW.name
)
.withSubtype(
Column::class.java,
ComponentType.COLUMN.name
)
.withSubtype(
ClassInfoCard::class.java,
ComponentType.CLASSINFO.name
)
.withSubtype(
ClassGalleryCard::class.java,
ComponentType.CLASSGALLERY.name
)
.....扩展
)
.add(KotlinJsonAdapterFactory())
.build()
val adapter: JsonAdapter<HomeComponent> = moshi.adapter(HomeComponent::class.java)
return adapter.fromJson(jsonComponents)
}
其中
class HomeComponent(val listItems: List<ComponentItem>)
sealed class ComponentItem(
val type: ComponentType,
var weight: Float = 0f,
var parent: ComponentType,
var wCell: Int = 1,
var hCell: Int = 1,
var parentCell: Int = 1,
var isEnd: Boolean = false
)
enum class ComponentType {
CARD,//空白卡片
CLASSGALLERY,//班级相册
CLASSINFO,//班级信息
COLUMN,//列容器,容器中的内容排成一列
ROW,//行容器,容器中的内容排成一行
}
根据 HomeComponent的内容解析 Json文件里面的内容是根据ComponentItem定义的属性来解析的,其中,解析到 type的时候,添加withSubtype,不断扩展,根据类型名称,找到对应的 ClassInfoCard,和 ClassGalleryCard等。
这样就将json里面的内容读取到 HomeComponent中了。接下来就是根据 HomeComponent来进行内容布局。
4. 遍历布局
Row(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.Top
) {
loadHomeComponents?.listItems?.map {
ConstructComponent(it, navController,modifier = Modifier.weight(it.weight))
}
}
5. 根据布局类型进行构建
@Composable
fun ConstructComponent(
componentItem: ComponentItem,
navController: NavHostController,
modifier: Modifier = Modifier
) {
when (componentItem.type) {
ComponentType.COLUMN -> {
val column = componentItem as Column
Column(
modifier = modifier
) {
for (item in column.listItems) {
ConstructComponent(item, navController, createModifier(item))
}
}
}
ComponentType.ROW -> {
val row = componentItem as Row
Row(
modifier = modifier
) {
for (item in row.listItems) {
ConstructComponent(item, navController, createModifier(item))
}
}
}
ComponentType.CARD -> {
val baseCard = componentItem as BaseCard
BaseCardView(
fillSizeModifier(modifier, baseCard)
)
}
ComponentType.CLASSGALLERY -> {
val classGalleryCard = componentItem as ClassGalleryCard
ClassGalleryCard2X2(
modifier = fillSizeModifier(modifier, classGalleryCard),
navController
)
}
ComponentType.CLASSINFO -> {
val classInfoCard = componentItem as ClassInfoCard
ClassInfoCard2X1(
fillSizeModifier(modifier, classInfoCard),
navController
)
}
........
}
}
fun RowScope.createModifier(
componentItem: ComponentItem
): Modifier {
val modifier = if (componentItem.weight > 0) {
Modifier.weight(componentItem.weight)
} else Modifier
return modifier
}
fun ColumnScope.createModifier(
componentItem: ComponentItem
): Modifier {
val modifier = if (componentItem.weight > 0) {
Modifier.weight(componentItem.weight)
} else Modifier
return modifier
}
fun RowScope.createModifier(
componentItem: ComponentItem
): Modifier {
val modifier = if (componentItem.weight > 0) {
Modifier.weight(componentItem.weight)
} else Modifier
return modifier
}
fun ColumnScope.createModifier(
componentItem: ComponentItem
): Modifier {
val modifier = if (componentItem.weight > 0) {
Modifier.weight(componentItem.weight)
} else Modifier
return modifier
}
6. 根据具体类型尺寸,构建内容
@Composable
fun ClassInfoCard2X1(
modifier: Modifier = Modifier,
navHostController: NavHostController? = null
) {
val classInfo = ClassInfo(
"三年级(2)班", 49, R.drawable.class_info_image, "" +
"三分天注定,七分靠打拼!", "胡玲玲"
)
BaseCardViewDark(
modifier = modifier
) {
val context = LocalContext.current
if (classInfo == null) {
EmptyView()
} else {
BoxWithConstraints(modifier = Modifier.fillMaxSize().padding(20.dp)) {
Column(
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.Start,
modifier = Modifier.clickable {
// navHostController?.navigateOrPop(NavHomeItem.Video.name)
// navHostController?.navigateOrPop(NavHomeItem.VERIFY.name)
// context.startActivity(Intent(context, SettingsActivity::class.java))
}
) {
Text(
text = classInfo.className,
style = MaterialTheme.typography.headlineLarge,
textAlign = TextAlign.Center,
)
Text(
text = classInfo.label,
style = MaterialTheme.typography.labelLarge,
modifier = Modifier.padding(top = 10.dp, bottom = 10.dp)
.weight(1f),
)
Text(
text = "班主任:" + classInfo.headerTeacher,
style = MaterialTheme.typography.titleMedium,
)
SpacerHeight5()
Text(
text = "班级人数:" + classInfo.classStudentCount.toString(),
style = MaterialTheme.typography.titleMedium,
)
}
Image(
painter = painterResource(id = classInfo.image),
contentDescription = classInfo.className,
modifier = Modifier.align(Alignment.CenterEnd),
contentScale = ContentScale.FillHeight
)
}
}
}
}