【技术前沿】聊聊Jetpack Compose声明式ui框架

1,179 阅读6分钟

Jepack Compose声明式ui框架

前言

  1. 2020的Google/IO大会,亮相了一个全新的 Android 原生 UI 开发框架-Jetpack Compose
  2. 与苹果的SwiftIUI一样,Jetpack Compose是一个声明式的UI框架,随着了今年安卓和苹果两大移动平台相继推出自己的UI开发框架Jetpack Compose 和SwiftIUI,标志着移动操作系统正式全面拥抱声明式 UI 开发模式
  3. 这时候就有同学问了,何为声明式UI?
  • 声明式UI的意思就是描述你想要一个什么样的UI界面, 状态变化时,界面按照先前描述的重写“渲染”即可得到状态绝对正确的界面。
  • 它不像命令一样,告诉程序一步一步该干什么,维护各种状态。如果想要了解的同学,可以详细可以查看这篇专栏 从 SwiftUI 谈声明式 UI 与类型系统
  1. 就简单说到这,下面让我们走进Jetpack Compose的世界,体验它的美好

JetPack Compose简单介绍

  • Jetpack Compose 是一个用于构建原生Android UI 的现代化工具包,它基于声明式的编程模型,因此你可以简单地描述UI的外观,而Compose则负责其余的工作-当状态发生改变时,你的UI将自动更新。
  • 由于Compose基于Kotlin构建,因此可以与Java编程语言完全互操作,并且可以直接访问所有Android和Jetpack API。它与现有的UI工具包也是完全兼容的,因此你可以混合原来的View和现在新的View,并且从一开始就使用Material和动画进行设计。

简单环境配置

以下是我在Android Sutdio4.1上进行编写demo,可以直接去Android Developers官网下载最新版本

  1. 创建一个新的kotlin project,以下是我的build.gradle文件
defaultConfig {
        applicationId "com.example.jetpackcomposedemo"
        minSdkVersion 21
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }

ps: 需要注意的是app目录下的支持的最低版本是21或者以上,否则使用jetpack compose会报错,详细可查看官方回答

  1. 使用Kotlin-Gradle插件 需要根目录下的build.gradle添加如下代码:
buildscript {
    ext.kotlin_version = "1.4.21"
    repositories {
        google()
        jcenter()
        maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.1.0"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
        maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
    }
}
  1. 加入Jetpack Compose的依赖
  • dev02 0.1.0先行版
    implementation 'androidx.ui:ui-tooling:0.1.0-dev02'
    implementation 'androidx.ui:ui-layout:0.1.0-dev02'
    implementation 'androidx.ui:ui-material:0.1.0-dev02'
  • 目前使用1.0.0-alpha10版本
 implementation 'androidx.compose.ui:ui:1.0.0-alpha10'
    // Tooling support (Previews, etc.)
    implementation 'androidx.compose.ui:ui-tooling:1.0.0-alpha10'
    // Foundation (Border, Background, Box, Image, Scroll, shapes, animations, etc.)
    implementation 'androidx.compose.foundation:foundation:1.0.0-alpha10'
    // Material Design
    implementation 'androidx.compose.material:material:1.0.0-alpha10'
    // Material design icons
    implementation 'androidx.compose.material:material-icons-core:1.0.0-alpha10'
    implementation 'androidx.compose.material:material-icons-extended:1.0.0-alpha10'
    // Integration with observables
    implementation 'androidx.compose.runtime:runtime-livedata:1.0.0-alpha10'
    implementation 'androidx.compose.runtime:runtime-rxjava2:1.0.0-alpha10'
    // UI Tests
    androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.0.0-alpha10'
  1. 至此,Jetpack Compose的准备工具就到此了,现在我们可以创建一个Hello world项目进行体验一下
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
      //初步使用JetPack进行声明式ui编程
        setContent {
            MaterialTheme {
                greeting("World")
            }
        }
   }


    @Composable
    private fun greeting(str: String) {
        Text(text = "Hello $str!")
    }
}
  • 这里定义了一个Compose函数打印Text

看看Jetpack的布局

  • 在Compose中ui布局是分层级的,元素包含在其他元素中。
  • 在Jetpack Compose中,你可以通过从其他composable函数中调composable函数来构建UI层次结构。
  1. 垂直添加多个text
  • 相对于xml是使用LinearLayout中的orientation:vertical可以实现垂直结构,在jetpack compose中,我们就需要使用到Column函数
  • 写过flutter的同学看起来是不是很眼熟?是的,跟flutter里面的Column Widget名字和功能完全一样,甚至连他们的属性都一摸一样。
 @Composable
fun NewsStory() {
    Column {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  1. 添加一些样式(官方)
  • 以上是beta02版本的jetpack compose的写法
 @Composable
    fun newsStory() {
        Column(
            crossAxisSize = LayoutSize.Expand,
            modifier = Spacing(16.dp)
        ) {
            Text("A day in Shark Fin Cove")
            Text("Davenport, California")
            Text("December 2018")
        }
    }
  • crossAxisSize: 指定Column组件(注:Compose中,所有的组件都是composable函数,文中的组件都是指代composable函数)在水平方向的大小,设置crossAxisSize为LayoutSize.Expand即表示Column宽度应为其父组件允许的最大宽度,相当于传统布局中的match_parant ,还有一个值为LayoutSize.Wrap,看名字就知道,包裹内容,相当于传统布局中的wrap_content。
  • modifier:使你可以进行其他格式更改。在这种情况下,我们将应用一个Spacing修改器,该设置将Cloumn与周围的视图产生间距。
  • 以下是1.0.0版本的jetpack compose的写法
@Composable
fun NewsStory() {
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  1. 添加一张图片
  @Composable
    private fun addImage() {
        val image = +imageResource(R.mipmap.header)
        // 放在容器中,设置大小
        Container(expanded = true, height = 180.dp) {
            //显示图片
            DrawImage(image)
        }

        // 添加垂直方向间距20dp
        HeightSpacer(height = 20.dp)
    }
  • expanded : 指定Container的大小,默认是false(Container的大小是子组件的大小,相当于wrap_content),如果将它设置为true,就指定Container的大小为父控件所允许的最大size, 相当于match_parent。
  • height : 设置Container容器的高度,height属性的优先级高于expanded,因此会覆盖expanded,如上面的例子,设置height为180dp,也就是容器宽为父控件宽度,高为180dp

简单使用Material Design设计

  1. 添加shape的样式
 setContent {
            val image = +imageResource(R.mipmap.header)
            Column(
                crossAxisSize = LayoutSize.Expand,
                modifier = Spacing(16.dp)
            ) {
                //放在容器中,设置大小
                Container(expanded = true,height = 180.dp) {
                    Clip(shape = RoundedCornerShape(10.dp)) {
                        //显示图片
                        DrawImage(image)
                    }
                }
            }
  • 这里给图片加入圆角10dp
  1. 设置下文字样式
                Text("Hello World")
                Text("我来体验下JetPack Compose,JetPack Compose的复用性很强,可以抽取很多公共组件",
                    maxLines = 2,
                    overflow = TextOverflow.Ellipsis,
                    style = +themeTextStyle { h5 }
                )

  • 设置了maxLines 和overflow 之后,超出部分就截断处理了,不会影响到整个布局样式。
  1. 设置按钮
       drawerButton(
                    R.drawable.ic_shape_button.also { },
                    label = "Home",
                    isSelected = true
                ) {
                }
  1. 完整demo代码如下所示
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //初步使用JetPack进行声明式ui编程
        setContent {
            val image = +imageResource(R.mipmap.header)
            Column(
                crossAxisSize = LayoutSize.Expand,
                modifier = Spacing(16.dp)
            ) {
                //放在容器中,设置大小
                Container(expanded = true, height = 180.dp) {
                    Clip(shape = RoundedCornerShape(10.dp)) {
                        //显示图片
                        DrawImage(image)
                    }
                }

                //添加垂直方向间距为20dp
                HeightSpacer(20.dp)

                Text("Hello World")
                Text(
                    "我来体验下JetPack Compose,JetPack Compose的复用性很强,可以抽取很多公共组件",
                    maxLines = 2,
                    overflow = TextOverflow.Ellipsis
//                    style = (+themeTextStyle { h2 }).withOpacity(0.1f)
                )


                HeightSpacer(20.dp)

                drawerButton(
                    R.drawable.ic_shape_button.also { },
                    label = "Home",
                    isSelected = true
                ) {
                }

            }


        }
    }


    @Preview
    @Composable
    private fun newsTory(string: String) {
        Text("Hello $string")
    }


    /**
     * 添加一张图片
     */
    @Composable
    private fun addImage() {
        val image = +imageResource(R.mipmap.header)
        // 放在容器中,设置大小
        Container(expanded = true, height = 180.dp) {
            //显示图片
            DrawImage(image)
        }

        // 添加垂直方向间距20dp
        HeightSpacer(height = 20.dp)
    }

    @Composable
    private fun drawerButton(
        @DrawableRes icon: Int,
        label: String,
        isSelected: Boolean,
        action: () -> Unit
    ) {
        val textIconColor = if (isSelected) {
            +themeColor { primary }
        } else {
            (+themeColor { onSurface }).copy(alpha = 0.6f)
        }
        val backgroundColor = if (isSelected) {
            (+themeColor { primary }).copy(alpha = 0.12f)
        } else {
            +themeColor { surface }
        }

        Padding(left = 8.dp, top = 8.dp, right = 8.dp) {
            Surface(
                color = backgroundColor,
                shape = RoundedCornerShape(4.dp)
            ) {
                Button(onClick = action, style = TextButtonStyle()) {
                    Row(
                        mainAxisSize = LayoutSize.Expand,
                        crossAxisAlignment = CrossAxisAlignment.Center
                    ) {
                        WidthSpacer(16.dp)
                        Text(
                            text = label,
                            style = (+themeTextStyle { body2 }).copy(
                                color = textIconColor
                            )
                        )
                    }
                }
            }
        }
    }


}

总结

  • 同学我尝试体验了一下JetPack Compose,目前还存在很多问题,还不能现在将其用于商业项目中,但是这并不能妨碍我们学习和体验它,声明式 UI 框架近年来飞速发展,React 为声明式 UI 奠定了坚实基础并。
  • Flutter 的发布将声明式 UI 的思想成功带到移动端开发领域,Apple和Google 分别先后发布了自己的声明式UI框架SwiftUI 和 Jetpack Compose , 以后,原生UI布局,声明式可能将会是主流。
  • 具体效果可查看Android官方文档JetPack Compose教程
  • 欢迎各位同学大佬们一起学习讨论