Here, we can learn:
-
1. Compose is a declarative UI, not a traditional command UI. Instead of holding View to setXXX(), it uses functions to describe a UI state.
-
2. Compose adopts the idea of attribute refinement and behavior isolation. For example, in the demo, text is the direct attribute of text, and background is an attribute of modifier. Because Image has no text attribute, but has background attribute, compose extracts the public attribute into the modifier and binds the unique attribute to the View itself, This is an idea of attribute refinement, and the click event is in the modifier, that is, the behavior is controlled through the modifier, which is the separation of behavior and attribute. In other words, the View itself is only responsible for describing its own inherent attributes, and the modifier is responsible for describing extended attributes and behaviors. In this way, the design is more humanized and reduces the coupling in disguise.
Let's look at a function. This is Google's Android Study Jam class ending quiz, which requires the implementation of a Searchbar.
First look at the effect:
Very simple. Enter keywords and click search to list the items containing keywords. If the keywords are empty, all items will be listed. If we use the native implementation, we may need to write a RecyclerView, an Adapter, a ViewHolder... Let's look at the composition version:
1. Implement upper 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) }), ) } Copy code
The function receives an onSearch() method and passes a String; OutlinedTextField can be understood as EditText, and value is equivalent to the text attribute of EditText. trailingIcon represents the tail icon, that is, the closed X. when clicking it, we set value to an empty String, which is equivalent to clearing. keyboardOptions represents the icon of keyboard return, which we define as ImeAction.Search, that is, search; keyboardActions refers to the method executed by clicking return. We directly call onSearch(), as shown in the figure:
The red x is specified by trailingIcon, and the search is specified by keyboardOptions.
2. Implement the following list
@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)) } } } Copy code
Function receives a LiveData; Among them, LazyColumn can be understood as RecyclerView in Compose, and items is an internal lambda, which means expanding the list of contents and constructing Text. The size of contents will construct as many Text as possible.
3. Combined 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) } } Copy code
Column is equivalent to LinearLayout, and Spacer is equivalent to a blank View, which is used as a dividing line.
4 data part
The ContentsListData code is as follows:
interface ContentsListData { fun getDisplayData(): MutableLiveData<MutableList<String>> } class MainViewModel : ViewModel(), ContentsListData { // All data var totalDatas = MutableLiveData<MutableList<String>>() // Data to be displayed val displayDatas = MutableLiveData<MutableList<String>>() init { // Add test data 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()) { // If the keyword is empty, all data will be displayed totalDatas.value?.forEach { list.add(it) } } else {