Compose简易入坑

7/2/2021 AndroidCompose

# 前言

Jetpack compose即将推出正式版了,作为声明式UI,将会摈弃传统的xml布局的方式,从而避免反射创建View,将会极大提高View的加载效率,这就是趋势,不可对抗,所以学起来吧。

# 基础

我们先来看个小demo,体验一下Compose声明式UI的写法,下面demo是一个简单的TextView,打印一个Hello World:

@Composable
fun TextViewDemo() {
   Text(
        text = "Hello, world",
        color = Color.White,
        modifier = Modifier
            .background(Color.Red, RoundedCornerShape(8.dp)) // 添加圆角背景
            .padding(8.dp)
            .clickable { // 点击事件
                Log.e(TAG,"click me")
            }
    )
}
1
2
3
4
5
6
7
8
9
10
11
12
13

效果如下:

示例

代码很简单,但是这是一个函数,用Composable修饰,就表示一个UI状态,只需要在MainActivity的onCreate()里面调用setContent()就可以:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent { TextViewDemo() }
    }
}
1
2
3
4
5
6

这就完事了。

到这里,我们可以了解到:

  • 1 Compose是声明式UI,不是传统的命令式UI,不是拿着View去setXXX(),而是用函数去描述一种UI状态。
  • 2 Compose采用的是属性细化和行为隔离的思想,比如demo里面,text就是Text直接的属性,而background是modifier的一个属性,因为像图片Image就没有text属性,但是却有background属性,所以Compose就将公有属性提取到Modifier中,将独有属性绑定到View自身,这就是一种属性细化的思想,而且点击事件是在Modifier中,也就是通过Modifier来控制行为,这就是行为和属性分开。换句话说,View本身只负责描述自身固有属性,Modifier负责描述扩展属性和行为,这样设计更人性化,变相降低了耦合。

# 实践

我们来看个功能,这是谷歌的Android Study Jam 结课小测验,要求实现一个Searchbar。

先看效果:

示例

很简单,输入关键词,点击搜索,就列出包含关键词的条目,关键词为空就列出全部,如果我们用原生实现,可能需要写一个RecyclerView,一个Adapter,一个ViewHolder...,我们来看Compose版本的:

# 1 实现上方SearchBar

@Composable
fun SearchBar(onSearch: (keyword: String) -> Unit) {
    var value by remember { mutableStateOf("") }
    OutlinedTextField(
        label = { Text(text = "Please input keyword") },
        value = value,
        onValueChange = { value = it },
        modifier = Modifier
            .padding(horizontal = 16.dp)
            .fillMaxWidth(),
        singleLine = true,
        textStyle = TextStyle(color = Color.DarkGray, fontWeight = FontWeight.W700),
        trailingIcon = @Composable { Image(imageVector = Icons.Filled.Clear, contentDescription = null, Modifier.clickable { value = "" }) },
        keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
        keyboardActions = KeyboardActions(onSearch = { onSearch.invoke(value) }),
    )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

函数接收一个onSearch()方法,传递一个String;OutlinedTextField可以理解为EditText,value就等价于EditText的text属性,trailingIcon表示尾部图标,就是那个关闭的X,点击它的时候,我们把value设置为空串,等价于清除,keyboardOptions表示键盘return的图标,我们定义为ImeAction.Search,也就是搜索;keyboardActions表示点击return执行的方法,我们直接调用onSearch(),如图:

示例

红色的x就是trailingIcon指定的,搜索就是keyboardOptions指定的。

# 2 实现下方列表

@Composable
fun ContentList(contentData: ContentsListData) {
    val contents by contentData.getDisplayData().observeAsState(listOf())
    LazyColumn(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.Start) {
        items(contents) { content ->
            Text(text = content, modifier = Modifier.padding(4.dp))
        }
    }
}
1
2
3
4
5
6
7
8
9

函数接收一个LiveData;其中LazyColumn可以理解为Compose中的RecyclerView,items是内部的一个lambda,表示将contents这个list展开,然后去构造Text,contents的size是多少,就会构造多少个Text。

# 3 组合UI

@Composable
fun HomeScreen() {
    Column {
        SearchBar { keyword ->
            Toast.makeText(this@MainActivity, "keyword is $keyword", Toast.LENGTH_SHORT).show()
            viewModel.filter(keyword = keyword)
        }

        Spacer(modifier = Modifier.size(8.dp))

        ContentList(contentData = viewModel)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

Column等价于LinearLayout,Spacer等价于一个空白View,用来作为分割线。

# 4 数据部分

ContentsListData代码如下:

interface ContentsListData {
    fun getDisplayData(): MutableLiveData<MutableList<String>>
}

class MainViewModel : ViewModel(), ContentsListData {
    // 所有数据
    var totalDatas = MutableLiveData<MutableList<String>>()

    // 需要展示的数据
    val displayDatas = MutableLiveData<MutableList<String>>()

    init {
        // 添加测试数据
        val totalList = mutableListOf<String>()
        for (index in 1..100) {
            totalList.add("content $index")
        }
        val displayList = mutableListOf<String>()
        totalList.forEach { displayList.add(it) }


        totalDatas.value = totalList
        displayDatas.value = displayList

    }

    fun filter(keyword: String) {

        val list: MutableList<String> = mutableListOf()
        if (keyword.isEmpty()) {
            // 如果关键词是空,就展示所有数据
            totalDatas.value?.forEach { list.add(it) }
        } else {
            // 否则只展示包含关键词的数据
            totalDatas.value?.forEach { if (it.contains(keyword)) list.add(it) }
        }
        displayDatas.postValue(list)
    }

    override fun getDisplayData(): MutableLiveData<MutableList<String>> {
        return displayDatas
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

代码也很简答,通过LiveData来实现数据的通知,从而重绘UI,

# 5 使用

class MainActivity : ComponentActivity() {

    private val TAG = "MainActivity"

    private lateinit var viewModel: MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = ViewModelProvider(viewModelStore, defaultViewModelProviderFactory).get(MainViewModel::class.java)
        setContent { HomeScreen() }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

简单的雅痞,点击获取: 完整代码 (opens new window)

Last Updated: 1/28/2022, 4:13:43 PM