1. Compose Challenge Week 3

For those of you who have read my previous posts, you may be familiar with the Compose Challenge, which is currently in its third round. #AndroidDevChallenge Week 3

Different from the previous two rounds, this round is mainly about speed. Only the first person to complete and submit as required wins the prize of a Pixel 5 phone. The topic asks for Compose to complete the following three pages. Google will provide the resources and visual design necessary to complete the page.

The difficulty of the topic itself is not high, mainly spelling hand speed. Since my marriage, my husband’s hand speed deteriorated seriously, so I didn’t expect to get top1, but I still insisted on completing the project in the spirit of focusing on participation, mainly hoping to find something to share with everyone.

During the whole development process, in addition to the Layout, Modifier and other basic technologies, the biggest experience is Compose Theme is too easy to use! This is what Google wants to explore and convey in this topic. The above three pages could have been completed without the Theme, but the development efficiency would have been significantly reduced.


2. Compose Theme

Traditional Android development also requires a Theme, or Theme. Theme can provide uniform colors and styles for UI controls to ensure visual consistency of App. Compose is based entirely on Kotlin, which makes its type safer, performance better, and easier to use.

The advantage of Kotlin

When we create a Compose template project in AndroidStudio, the IDE automatically creates the theme folder

Kt uses Kotlin’s constants to define various styles, and Theme. Kt applies these styles to the global Theme:

//Thmem.kt
private val DarkColorPalette = darkColors(
        primary = purple200,
        primaryVariant = purple700,
        secondary = teal200
)

private val LightColorPalette = lightColors(
        primary = purple500,
        primaryVariant = purple700,
        secondary = teal200
)

@Composable
fun MyAppTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable()() - >Unit) {
    // Set different colors according to the theme
    val colors = if (darkTheme) {
        DarkColorPalette
    } else {
        LightColorPalette
    }

    MaterialTheme(
            colors = colors,
            typography = typography,
            shapes = shapes,
            content = content
    )
}
Copy the code

As mentioned above, it is as simple as using Kotlin to define and toggle the theme, select the configuration in the Composable based on the if statement, and then wait for the next composition to take effect.

Theme Working Principle

Each project provides a ${app name}Theme for custom themes. For example, MyAppTheme, which eventually calls the MaterialTheme, maps the configuration to environment variables through a series of providers:

@Composable
fun MaterialTheme(
    colors: Colors = MaterialTheme.colors,
    typography: Typography = MaterialTheme.typography,
    shapes: Shapes = MaterialTheme.shapes,
    content: @Composable() - >Unit
) {
    val rememberedColors = remember { colors }.apply { updateColorsFrom(colors) }
    val rippleIndication = rememberRipple()
    val selectionColors = rememberTextSelectionColors(rememberedColors)
    CompositionLocalProvider(
        LocalColors provides rememberedColors,
        LocalContentAlpha provides ContentAlpha.high,
        LocalIndication provides rippleIndication,
        LocalRippleTheme provides MaterialRippleTheme,
        LocalShapes provides shapes,
        LocalTextSelectionColors provides selectionColors,
        LocalTypography provides typography
    ) {
        ProvideTextStyle(value = typography.body1, content = content)
    }
}
Copy the code

Subsequent UIs are created within the Content of MyAppTheme, sharing the configuration provided by the Provider

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContent {
            MyAppTheme {
                // A surface container using the 'background' color from the theme. }}}}Copy the code

When the theme configuration is required, the MaterialTheme static object is accessed as follows:

@Composable
fun Scaffold(... drawerShape:Shape = MaterialTheme.shapes.large,
    ...
    drawerBackgroundColor: Color = MaterialTheme.colors.surface,
    ...
    backgroundColor: Color = MaterialTheme.colors.background,
    ...
    content: @Composable (PaddingValues) - >Unit
)
Copy the code

The MaterialTheme gets the current configuration from the Provider.

object MaterialTheme {

    val colors: Colors
        @Composable
        @ReadOnlyComposable
        get() = LocalColors.current

    val typography: Typography
        @Composable
        @ReadOnlyComposable
        get() = LocalTypography.current

    val shapes: Shapes
        @Composable
        @ReadOnlyComposable
        get() = LocalShapes.current
}
Copy the code


3. Practical Theme

Bloom is the name of the challenge project. With the help of Compose’s Theme, I basically restored the requirements of the design draft.

Here is the result, code address: Bloom

Define the Theme

As requested in the design draft, we define Theme in our code:

Color

Start by defining the relevant constants in color.kt

//Color.kt
val pink100 = Color(0xFFFFF1F1)
val pink900 = Color(0xFF3f2c2c)
val gray = Color(0xFF232323)
val white = Color.White
val whit850 = Color.White.copy(alpha = .85f)
val whit150 = Color.White.copy(alpha = .15f)
val green900 = Color(0xFF2d3b2d)
val green300 = Color(0xFFb8c9b8)
Copy the code

You then define the daytime colors with lightColors

private val LightColorPalette = lightColors(
    primary = pink100,
    primaryVariant = purple700,
    secondary = pink900,
    background = white,
    surface = whit850,
    onPrimary = gray,
    onSecondary = white,
    onBackground = gray,
    onSurface = gray,
)
Copy the code

Among them, the definition of primary and so on comes from the MaterialDesign design specification and is distinguished according to the frequency of the scene used by the color. Those interested can refer to the MD design specification.

onPrimaryEtc denotes the default foreground color under the corresponding background color, such as the color of text and icon, etc. :

Accordingly, night themes are defined as follows:

private val DarkColorPalette = darkColors(
    primary = green900,
    primaryVariant = purple700,
    secondary = green300,
    background = gray,
    surface = whit150,
    onPrimary = white,
    onSecondary = gray,
    onBackground = white,
    onSurface = whit850,
)
Copy the code

Type

//type.kt
val typography = Typography(
    h1 = TextStyle(
        fontFamily = FontFamily.SansSerif,
        fontWeight = FontWeight.Bold,
        fontSize = 18.sp,
    ),

    h2 = TextStyle(
        fontFamily = FontFamily.SansSerif,
        fontWeight = FontWeight.Bold,
        fontSize = 14.sp,
        letterSpacing = 0.15.sp
    ),

    subtitle1 = TextStyle(
        fontFamily = FontFamily.SansSerif,
        fontWeight = FontWeight.Light,
        fontSize = 16.sp
    ),

    body1 = TextStyle(
        fontFamily = FontFamily.SansSerif,
        fontWeight = FontWeight.Light,
        fontSize = 11.sp
    ),

    body2 = TextStyle(
        fontFamily = FontFamily.SansSerif,
        fontWeight = FontWeight.Light,
        fontSize = 12.sp
    ),

    button = TextStyle(
        fontFamily = FontFamily.SansSerif,
        fontWeight = FontWeight.SemiBold,
        fontSize = 14.sp,
        letterSpacing = 1.sp
    ),

    caption = TextStyle(
        fontFamily = FontFamily.SansSerif,
        fontWeight = FontWeight.SemiBold,
        fontSize = 12.sp
    )

)
Copy the code

Typography defines text styles. H1, body1, etc. are also from the definition of text purpose in MaterialDesign.

Shape

//Shape.kt
val shapes = Shapes(
    small = RoundedCornerShape(4.dp),
    medium = RoundedCornerShape(26.dp),
    large = RoundedCornerShape(0.dp)
)
Copy the code

Use the Theme

Next, it is OK to get the current configuration from the MaterialTheme in your code, regardless of the current theme.

Take the Text of Beautiful Home Garden Solutions on the welcome page for example, the Text color needs to change depending on the theme (Light or Dart).

As follows, setting the Color from the MaterialTheme avoids the if statement

Text(
	"Beautiful home graden solutions",
	style = MaterialTheme.typography.subtitle1,
	/ / color = MaterialTheme. Colors. OnPrimary, / / can be omitted
	modifier = Modifier.align(Alignment.CenterHorizontally),
)
Copy the code

As mentioned earlier, when the background Color is primary, the foreground Color is onPrimary by default, so even if Color is not set here, the most appropriate Color will be automatically selected.

Now if I look at the Create Account Button,

 Button(
	onClick = {},
	modifier = Modifier
		.height(48.dp)
		.fillMaxWidth()
		.padding(start = 16.dp, end = 16.dp)
		.clip(MaterialTheme.shapes.medium),
		/ / background (MaterialTheme. Colors. Secondary), / / Modifier set the background color
        colors = ButtonDefaults.buttonColors(
			backgroundColor = MaterialTheme.colors.secondary
		)
) {
		Text(
			 "Create account"./ / style = MaterialTheme. Typography. The button / / can be omitted)}Copy the code

The text needs to be displayed in the style of typography. Button. The default text inside button is button style, so it can also be omitted here.

Note: Button has a dedicated color setting field. Using the Modifier to set background does not work

Because of the Button sets the backgroundColor to MaterialTheme. Colors. Secondary, so, the color of the Text within the automatic application onSecondary, no additional specified.

As you can see, Theme is not only good for uniform configuration of styles, but also saves a lot of code.


4. Use the @ the Preview


Visual tuning sometimes requires repeated validation, which can be time-consuming if you have to install it to the device every time. Compose provides @Preview that looks almost like a real machine, as opposed to traditional XML layout Preview, and can Preview multiple themes and resolutions on the same screen for easy comparison.

@Preview(widthDp = 360, heightDp = 640)
@Composable
fun PreviewWelcomeLight(a) {
    MyTheme(darkTheme = false) {
        Surface(color = MaterialTheme.colors.background) {
            WelcomeScreen(darkTheme = false)}}}@Preview(widthDp = 360, heightDp = 640)
@Composable
fun PreviewWelcomeDark(a) {
    MyTheme(darkTheme = true) {
        Surface(color = MaterialTheme.colors.background) {
            WelcomeScreen(darkTheme = true)}}}Copy the code

As shown above, Preview the DarkTheme and LightTheme separately, set the resolution in @Preview, and you can see the Preview in real time.

@Preview is a true Preview of the Composabl through the actual running of the Composabl, so it needs to be built before the Preview, but it is much faster than installing the Composabl for viewing.

Another advantage of Runtime-based Preview is that interactions can be previewed. Click the “finger” icon in the upper right corner to interact with preview. Click the “mobile phone” icon to deploy the preview screen to the real phone.

Note that Preview only accepts a Composable without its parameters because the Preview needs to ensure that the Composable is runnable. For Composable with parameters, mock can be made with @previewParameter. However, mock data has its own cost, so we need to consider whether the Composable interface signature is friendly to Preview and can reduce unnecessary parameter passing. Or provide default values for it.


5. The last


Finally, some conclusions are made about the function and use of Theme:

  1. Compose’s Theme is more efficient and convenient than the XML approach
  2. Proper use of Theme also helps reduce the amount of code
  3. It is recommended to ask PM or designer to provide a detailed Theme definition before the project starts to improve the efficiency of RD development
  4. Creating @Preview for Composable will greatly enhance the UI development experience

reference

AndroidDevChallenge #Bloom

Theming in Compose