迁移到Jetpack Compose 第三趴

250 阅读4分钟

一、更多XML代码迁移

现在,我们可以更轻松地将界面缺少的内容补充完整:浇水信息和植物说明。按照前面介绍的XML代码方法,您已经可以迁移界面的其余部分了。

您之前从fragment_plant_detail.xml移出的浇水信息XML代码由两个ID为plant_watering_headerplant_watering的TextView组成。

<TextView
    android:id="@+od/plant_watering_header"
    ...
    android:layout_marginStart="@dimen/margin_small"
    android:layout_marginTop="@dimen/margin_normal"
    android:layout_marginEnd="@dimen/margin_small"
    android:gravity="center_horizontal"
    android:text="@string/watering_needs_prefix"
    android:textColor="?attr/colorAccent"
    android:textStyle="bold"
    ... />
    
<TextView
    android:id="@+id/plant_watering"
    ...
    android:layout_marginStart="@dimen/margin_small"
    android:layout_marginEnd="@dimen/margin_small"
    android:gravity="center_horizontal"
    app:wateringText="@{viewModel.plant.wateringInterval}"
    ... />

与您之前的操作类似,请创建一个名为PlantWater的新可组合项并添加Text,以在界面上显示浇水信息:

PlantDetailDescription.kt

@Composable
private fun PlantWatering(wateringInterval: Int) {
    Column(Modifier.fillMaxWidth()) {
        // Same modifier used by both Texts
        val centerWithPaddingModifier = Modifier
            .padding(horizontal = dimensionResource(R.dimen.margin_small))
            .align(Alignment.CenterHorizontally)
            
        val normalPadding = dimensionResource(R.dimen.margin_normal)
        
        Text(
            text = stringResource(R.string.watering_needs_prefix),
            color = MaterialTheme.color.primaryVariant,
            fontWeight = FontWeight.Bold,
            modifier = centerWithPaddingModifier.padding(top = normalPadding)
            
            val wateringIntervalText = LocalContext.current.resources.getQuantityString(R.plurals.watering_needs_suffix, wateringInterval, wateringInterval)
        )
        Text(
            text = wateringIntervalText,
            modifier = centerWithPaddingModifier.padding(bottom = normalPadding)
        )
    }
}

@Preview
@Composable
private fun PlantWateringPreview() {
    MaterialTheme {
        PlantWatering(7)
    }
}

预览如下:

image.png

需要注意以下几点: - 由于`Text`可组合项会共享水平内边距和对其修饰,因此您可以将修饰符分配给局部变量(即`centerWithPaddingModifier`),以重复使用修饰符。修饰符是标准的Kotlin对象,因此可以重复使用。 - Compose的`MaterialTheme`与`plant_watering_header`中使用的`colorAccent`不完全匹配。现在,我们可以使用将在主题设置部分中加以改进的`MaterialTheme.colors.primaryVariant`。

我们将各个部分组合在一起,然后同样从PlantDetailContent调用PlantWatering。我们一开始移除的ConstraintLayout XML代码的外边距为16.dp,我们需要将该值添加到Compose代码中。

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="@dimen/margin_normal">

请在PlantDetailContent中创建一个Column以同时显示名称和浇水信息,并将其作为内边距。另外,为了确保北京颜色和所用的文本颜色均合适,请添加Surface用于处理这种设置。

PlantDetailDescription.kt

@Composable
fun PlantDetailContent(plant: Plant) {
    Surface {
        COlumn(Modifier.padding(dimensionResource(R.dimen.margin_normal))) {
            PlantName(plant.name)
            PlantWatering(plant.wateringInterval)
        }
    }
}

刷新预览后,您会看到以下内容:

image.png

img_v3_027k_8f767635-b991-48df-a817-f817292d638g.jpg

二、Compose代码中的View

现在,我们来迁移植物说明。fragment_plant_detail.xml中的代码具有包含app:renderHtml="@{viewModel.plant.description}"TextView,用于告知XML在界面上显示那些文本。renderHtml是一个绑定适配器,可在PlantDetailBindingAdapter.kt文件中找到。该实现使用HtmlCompat.fromHtmlTextView上设置文本!

但是,Compose目前不支持Spanned类,也不支持显示HTML格式的文本。因此,我们需要在Compose代码中使用View系统中的TextView来绕过此限制。

由于Compose目前还无法呈现HTML代码,因此你你需要使用AndroidViewAPI程序化地创建一个TextView,从而实现此目的。

AndroidView接受View作为参数,并在View已膨胀时为您提供回调。

注意:AndroidView接受程序化地创建的View。如果您想嵌入XML文件,可以结合使用视图绑定与androidx.compose.ui:ui-viewbinding库中的AndroidViewBinding API。

为此,请创建新的PlantDescription可组合项。次可组合项使用我们刚刚在lambda中保存的TextView调用AndroidView。在factory回调中,请初始化使用给定Context来回应HTML交互的TextView。在update回调中,用已保存的HTML格式的说明设置文本。

PlantDetailDescription.kt

@Composable
private fun PlantDescription(description: String) {
    // Remembers the HTML formatted description. Re-executes on a new description
    val htmlDescription = remember(description) {
        HtmlCompat.fromHtml(description, HtmlCompat.FEOM_HTML_MODE_COMPACT)
    }
    
    // Displays the TextView on the screen and updates with the HTML description when inflated
    // Updates to htmlDescription will make AndroidView recompose and update the text
    AndroidView(
        factory = { context ->
            TextView(context).apply {
                movementMethod = LinkMovementMethod.getInstance()
            }
        },
        update = {
            it.text = htmlDescription
        }
    )
}

@Preview
@Composable
private fun PlantDescriptionPreview() {
    MaterialTheme {
        PlantDescription("HTML<br><br>description")
    }
}

预览:

image.png

请注意,htmlDescription会记住作为参数传递的指定description的HTML说明。如果description参数发生变化,系统会再次执行remember中的htmlDescription代码。

同样,如果htmlDescription发生变化,AndroidView更新会掉会重组。在会掉中读取的任何状态都会导致重组。

我们将PlantDescription添加到PlantDetailContent可组合项,并更改预览代码,以便同样显示HTML说明:

PlantDetailDescription.kt

@Composable
fun PlantDetailContent(plant: Plant) {
    Surface {
        Column(Modifier.padding(dimensionResource(R.dimen.margin_normal))) {
            PlantName(plant.name)
            PlantWatering(plant.wateringInterval)
            PlantDescription(plant.description)
        }
    }
}

@Preview
@Composable
private fun PlantDetailContentPreview() {
    val plant = Plant("id", "Apple", "HTML<br><br>description", 3, 30, "")
    MaterialTheme {}
}

预览如下:

image.png

现在,您已将原始ConstraintLayout中的所有内容迁移到Compose。您可以运行该应用,检查其是否按预期运行。

img_v3_027m_b924bcad-7a9b-488a-956a-2e2f77ef780g.jpg