Simple use and case implementation of Android Studio compose

Compose is a new-generation UI framework vigorously promoted by the Android team and JetBrain. It can simplify the development of the Android interface and change the original cumbersome xml file writing method into a simple kt file writing method.

Its declarative UI, simpler customization, real-time and interactive preview function make Android development even better

1. Pre-knowledge: Jetpack

1. What is Jetpack?

Jetpack is not a framework or a component, it is a collection of libraries, tools, and guidelines that help developers write high-quality applications more easily. These components help developers follow best practices, get rid of writing boilerplate code, and simplify complex tasks so they can focus on the code they need.

2. Why use Jetpack

1. Replace the previous support library and provide a better, more independent and robust compatibility solution
2. Officially implement many components that are needed in daily software development, eliminating repetitive boilerplate code
3. Improve software quality, the core library helps you bind data, manage lifecycle and content, allowing you to focus on important parts of your business

3. Commonly used Jetpack libraries

The picture is taken from the Android development document

2. Positive film: getting started with Compose

1. The programming idea of ​​Compose

Compose is a declarative interface framework . Over the past few years, the industry as a whole has begun to move to a declarative interface model that greatly simplifies the engineering tasks associated with building and updating interfaces. The technique works by conceptually regenerating the entire screen from scratch, then performing only the necessary changes. This approach avoids the complexity of manually updating stateful view hierarchies.

simple composable functions

Using Compose, interfaces can be built by defining a set of composable functions that accept data and emit interface elements. A simple example is the Greeting widget, which takes a String and emits a Text widget that displays a greeting message.
simple caseThis function is annotated with @Composable. All composable functions must have this annotation; this annotation tells the Compose compiler that this function is intended to convert data into an interface. ( Similar to @Component of the Spring framework )

This function accepts data. Composable functions can accept parameters that allow application logic to describe the interface. In this case, our widget accepts a String so it can greet the user by name.

This function can display text in the interface. To do this, it calls the Text() composable function, which actually creates the text interface element. Composable functions emit interface hierarchies by calling other composable functions.

This function does not return anything. Compose functions that emit screens do not need to return anything, because they describe the desired screen state, rather than constructing the screen widgets.

2. Create a Compose project

1. If you are in the Welcome to Android Studio window, click Start a new Android Studio project. If you already have an Android Studio project open, choose File > New > New Project from the menu bar.

insert image description here
2. In the Select a Project Template window, select Empty Compose Activity and click Next.
insert image description here3. In the Configure your project window, do the following:

按照常规方法设置 Name、Package name 和 Save location。请注意,在 Language 下拉菜单中,Kotlin 是唯一可用的选项,因为 Jetpack Compose 仅适用于使用 Kotlin 编写的类。
在 Minimum API level dropdown 菜单中,*选择 API 级别 21 或更高级别*。

insert image description here4. Click finish.

3. Use Compose to complete the "marquee" case

1. Build the project

This part refers to the above;

2. Create a new kt file under the main package

I named it "test" here
insert image description here

3. Build the MarqueeText method

This is the main part of the marquee implementation, in this part to edit the static parameters and dynamic effects of the marquee effect

Refer to the ui parameter defined in Compose

insert image description hereillustrate:

Loop scrolling marquee Text control

  • text text content
  • modifier control modifier
  • textModifier text modifier
  • gradientEdgeColor The gradient transparent color of the left and right borders, the default is white gradient.
  • color text color
  • letterSpacing character spacing
  • textDecoration text decoration

create control method

Create Text control method, equivalent to @Composable fun createText(localModifier: Modifier)

insert image description here

Encoding dynamic effects

var offset by remember {
    
     mutableStateOf(0) }
        val textLayoutInfoState = remember {
    
     mutableStateOf<TextLayoutInfo?>(null) }
        LaunchedEffect(textLayoutInfoState.value) {
    
    
            val textLayoutInfo = textLayoutInfoState.value ?: return@LaunchedEffect
            if (textLayoutInfo.textWidth <= textLayoutInfo.containerWidth) return@LaunchedEffect
            if(textLayoutInfo.containerWidth == 0) return@LaunchedEffect
            // 计算播放一遍的总时间
            val duration = 7500 * textLayoutInfo.textWidth / textLayoutInfo.containerWidth
            // 父层不要有其他元素,不然这句很容易发生Error java.lang.ArithmeticException: divide by zero(除以零)
            // 动画间隔时间
            val delay = 1000L

            do {
    
    
                // 定义动画,文字偏移量从0到-文本宽度
                val animation = TargetBasedAnimation(
                    animationSpec = infiniteRepeatable(
                        animation = tween(
                            durationMillis = duration,
                            delayMillis = 1000,
                            easing = LinearEasing,
                        ),
                        repeatMode = RepeatMode.Restart
                    ),
                    typeConverter = Int.VectorConverter,
                    initialValue = 0,
                    targetValue = -textLayoutInfo.textWidth
                )
                // 根据动画帧时间,获取偏移量值。
                // 起始帧时间
                val startTime = withFrameNanos {
    
     it }
                do {
    
    
                    val playTime = withFrameNanos {
    
     it } - startTime
                    offset = animation.getValueFromNanos(playTime)
                } while (!animation.isFinishedFromNanos(playTime))
                // 延迟重新播放
                delay(delay)
            } while (true)
        }

        SubcomposeLayout(
            modifier = modifier.clipToBounds()
        ) {
    
     constraints ->
            // 测量文本总宽度
            val infiniteWidthConstraints = constraints.copy(maxWidth = Int.MAX_VALUE)
            var mainText = subcompose(MarqueeLayers.MainText) {
    
    
                createText(textModifier)
            }.first().measure(infiniteWidthConstraints)

            var gradient: Placeable? = null

            var secondPlaceableWithOffset: Pair<Placeable, Int>? = null
            if (mainText.width <= constraints.maxWidth) {
    
    // 文本宽度小于容器最大宽度, 则无需跑马灯动画
                mainText = subcompose(MarqueeLayers.SecondaryText) {
    
    
                    createText(textModifier.fillMaxWidth())
                }.first().measure(constraints)
                textLayoutInfoState.value = null
            } else {
    
    
                // 循环文本增加间隔
                val spacing = constraints.maxWidth * 2 / 3
                textLayoutInfoState.value = TextLayoutInfo(
                    textWidth = mainText.width + spacing,
                    containerWidth = constraints.maxWidth
                )
                // 第二遍文本偏移量
                val secondTextOffset = mainText.width + offset + spacing
                val secondTextSpace = constraints.maxWidth - secondTextOffset
                if (secondTextSpace > 0) {
    
    
                    secondPlaceableWithOffset = subcompose(MarqueeLayers.SecondaryText) {
    
    
                        createText(textModifier)
                    }.first().measure(infiniteWidthConstraints) to secondTextOffset
                }
                // 测量左右两边渐变控件
                gradient = subcompose(MarqueeLayers.EdgesGradient) {
    
    
                    Row {
    
    
                        GradientEdge(gradientEdgeColor, Color.Transparent)
                        Spacer(Modifier.weight(1f))
                        GradientEdge(Color.Transparent, gradientEdgeColor)
                    }
                }.first().measure(constraints.copy(maxHeight = mainText.height))
            }

            // 将文本、渐变控件 进行位置布局
            layout(
                width = constraints.maxWidth,
                height = mainText.height
            ) {
    
    
                mainText.place(offset, 0)
                secondPlaceableWithOffset?.let {
    
    
                    it.first.place(it.second, 0)
                }
                gradient?.place(0, 0)
            }
        }
    }
     /**
     * 渐变侧边
     */
    @Composable
    private fun GradientEdge(
        startColor: Color, endColor: Color,
    ) {
    
    
        Box(
            modifier = Modifier
                .width(10.dp)
                .fillMaxHeight()
                .background(
                    brush = Brush.horizontalGradient(
                        0f to startColor, 1f to endColor,
                    )
                )
        )
    }

    private enum class MarqueeLayers {
    
     MainText, SecondaryText, EdgesGradient }

    /**
     * 文字布局信息
     * @param textWidth 文本宽度
     * @param containerWidth 容器宽度
     */
    private data class TextLayoutInfo(val textWidth: Int, val containerWidth: Int)

call the above method

@Composable
fun TestScreen() {
    
    
    val content = "这是一段很长很长很长很长很长很长很长很长很长很长的跑马灯文字;"
    Column(modifier = Modifier.padding(top = 200.dp)) {
    
    
        MarqueeText(
            text = content,
            color = Color.Black,
            fontSize = 24.sp,
            modifier = Modifier
                .padding(start = 50.dp, end = 50.dp)
                .background(Color.White)
        )
    }
}

Implement the startup class

class test : ComponentActivity() {
    
    
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContent {
    
    
            MyApplicationTheme {
    
    
                // A surface container using the 'background' color from the theme
                Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
    
    
                     TestScreen()
                }
            }
        }
    }
}

In the xml startup file, add this kt file

<activity
            android:name=".test"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.MyApplication">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

achieve effect

insert image description here

4. Summary

The Compose framework is an efficient Android development framework. It can avoid the cumbersome, error-prone, and difficult-to-debugging of xml layout writing, and it provides a rich API for developers to use, enabling developers to spend a lot of time on process-oriented programming and result-oriented programming, reducing slice-oriented programming.

Zeng Jiancheng https://blog.csdn.net/weixin_57235263/article/details/128127787?spm=1001.2014.3001.5501

Guess you like

Origin blog.csdn.net/fjnu_se/article/details/128208046