Jetpack Compose UI:如何创建 SearchView?

Jetpack Compose UI: How to create SearchView?

我想使用 jetpack compose 创建 SearchView,但找不到任何可以帮助我的示例。提前致谢。

只需创建组件,如果您想创建 UI 类似的组件,请使用 FlexRow。

FlexRow(crossAxisAlignment = CrossAxisAlignment.Start) {
    inflexible {
        drawImageResource(R.drawable.image_search)

    }
    expanded(1.0f) {
        SingleLineEditText(
            state,
            keyboardType = KeyboardType.Text,
            imeAction = ImeAction.Search,
            editorStyle = EditorStyle(textStyle = TextStyle(fontSize = 16.sp)),
            onImeActionPerformed = {
                onSearch(state.value.text)
            }
        )
    }
}
TextField(
   startingIcon = Icon(bitmap = searchIcon),
   placeholder = { Text(...) }
)

这是您在该图像中的 SearchView :

val (value, onValueChange) = remember { mutableStateOf("") }

    TextField(
          value = value,
          onValueChange = onValueChange,
          textStyle = TextStyle(fontSize = 17.sp),
          leadingIcon = { Icon(Icons.Filled.Search, null, tint = Color.Gray) },
                modifier = Modifier
                    .padding(10.dp)
                    .background(Color(0xFFE7F1F1), RoundedCornerShape(16.dp)),
                placeholder = { Text(text = "Bun") },
                colors = TextFieldDefaults.textFieldColors(
                    focusedIndicatorColor = Color.Transparent,
                    unfocusedIndicatorColor = Color.Transparent,
                    backgroundColor = Color.Transparent,
                    cursorColor = Color.DarkGray
                )
            )

这是一个复杂但完整的 SearchView 从头实现。结果将如下图所示,如果您不希望在 SearchView 未聚焦且为空时显示初始可组合项,您可以自定义或删除 InitialResultsSuggestions

github repository 中提供了完整的实现。 1- 使用

创建搜索状态
/**
 * Enum class with different values to set search state based on text, focus, initial state and
 * results from search.
 *
 * **InitialResults** represents the initial state before search is initiated. This represents
 * the whole screen
 *
 */
enum class SearchDisplay {
    InitialResults, Suggestions, Results, NoResults
}

2- 然后在定义搜索逻辑的地方创建 class

@Stable
class SearchState(
    query: TextFieldValue,
    focused: Boolean,
    searching: Boolean,
    suggestions: List<SuggestionModel>,
    searchResults: List<TutorialSectionModel>
) {
    var query by mutableStateOf(query)
    var focused by mutableStateOf(focused)
    var searching by mutableStateOf(searching)
    var suggestions by mutableStateOf(suggestions)
    var searchResults by mutableStateOf(searchResults)

    val searchDisplay: SearchDisplay
        get() = when {
            !focused && query.text.isEmpty() -> SearchDisplay.InitialResults
            focused && query.text.isEmpty() -> SearchDisplay.Suggestions
            searchResults.isEmpty() -> SearchDisplay.NoResults
            else -> SearchDisplay.Results
        }

    override fun toString(): String {
        return " State query: $query, focused: $focused, searching: $searching " +
                "suggestions: ${suggestions.size}, " +
                "searchResults: ${searchResults.size}, " +
                " searchDisplay: $searchDisplay"

    }
}

3- 记住状态不会在每个组合中更新,但只有当我们的搜索状态发生变化时才会更新

@Composable
fun rememberSearchState(
    query: TextFieldValue = TextFieldValue(""),
    focused: Boolean = false,
    searching: Boolean = false,
    suggestions: List<SuggestionModel> = emptyList(),
    searchResults: List<TutorialSectionModel> = emptyList()
): SearchState {
    return remember {
        SearchState(
            query = query,
            focused = focused,
            searching = searching,
            suggestions = suggestions,
            searchResults = searchResults
        )
    }
}

TutorialSectionModel 是我使用的模型它可以是通用类型 T 或您希望显示的特定类型

4- 创建一个提示,当不是 focused

时显示
@Composable
private fun SearchHint(modifier: Modifier = Modifier) {
    Row(
        verticalAlignment = Alignment.CenterVertically,
        modifier = Modifier
            .fillMaxSize()
            .then(modifier)

    ) {
        Text(
            color = Color(0xff757575),
            text = "Search a Tag or Description",
        )
    }
}

我没有使用 Icon,但如果您愿意,可以添加一个

5- 创建一个SearchTextfield可以有取消按钮,CircularProgressIndicator显示加载和BasicTextField输入

/**
 * This is a stateless TextField for searching with a Hint when query is empty,
 * and clear and loading [IconButton]s to clear query or show progress indicator when
 * a query is in progress.
 */
@Composable
fun SearchTextField(
    query: TextFieldValue,
    onQueryChange: (TextFieldValue) -> Unit,
    onSearchFocusChange: (Boolean) -> Unit,
    onClearQuery: () -> Unit,
    searching: Boolean,
    focused: Boolean,
    modifier: Modifier = Modifier
) {

    val focusRequester = remember { FocusRequester() }

    Surface(
        modifier = modifier
            .then(
                Modifier
                    .height(56.dp)
                    .padding(
                        top = 8.dp,
                        bottom = 8.dp,
                        start = if (!focused) 16.dp else 0.dp,
                        end = 16.dp
                    )
            ),
        color = Color(0xffF5F5F5),
        shape = RoundedCornerShape(percent = 50),
    ) {

        CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
            Box(
                contentAlignment = Alignment.CenterStart,
                modifier = modifier
            ) {

                if (query.text.isEmpty()) {
                    SearchHint(modifier.padding(start = 24.dp, end = 8.dp))
                }

                Row(verticalAlignment = Alignment.CenterVertically) {
                    BasicTextField(
                        value = query,
                        onValueChange = onQueryChange,
                        modifier = Modifier
                            .fillMaxHeight()
                            .weight(1f)
                            .onFocusChanged {
                                onSearchFocusChange(it.isFocused)
                            }
                            .focusRequester(focusRequester)
                            .padding(top = 9.dp, bottom = 8.dp, start = 24.dp, end = 8.dp),
                        singleLine = true
                    )

                    when {
                        searching -> {
                            CircularProgressIndicator(
                                modifier = Modifier
                                    .padding(horizontal = 6.dp)
                                    .size(36.dp)
                            )
                        }
                        query.text.isNotEmpty() -> {
                            IconButton(onClick = onClearQuery) {
                                Icon(imageVector = Icons.Filled.Cancel, contentDescription = null)
                            }
                        }
                    }
                }
            }
        }

    }
}

您可以删除 CircularProgressBar 或将图标添加到包含 BasicTextField

Row

6- SearchBar 带有 SearchTextField 上方和后退箭头 return 后退功能。 AnimatedVisibility 确保当我们在 SearchTextField 中聚焦 BasicTextField 时箭头是动画的,它也可以与图标一起用作放大镜。

@ExperimentalAnimationApi
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun SearchBar(
    query: TextFieldValue,
    onQueryChange: (TextFieldValue) -> Unit,
    onSearchFocusChange: (Boolean) -> Unit,
    onClearQuery: () -> Unit,
    onBack: ()-> Unit,
    searching: Boolean,
    focused: Boolean,
    modifier: Modifier = Modifier
) {

    val focusManager = LocalFocusManager.current
    val keyboardController = LocalSoftwareKeyboardController.current

    Row(
        modifier = modifier.fillMaxWidth(),
        verticalAlignment = Alignment.CenterVertically
    ) {

        AnimatedVisibility(visible = focused) {
            // Back button
            IconButton(
                modifier = Modifier.padding(start =2.dp),
                onClick = {
                focusManager.clearFocus()
                keyboardController?.hide()
                    onBack()
            }) {
                Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null)
            }
        }

        SearchTextField(
            query,
            onQueryChange,
            onSearchFocusChange,
            onClearQuery,
            searching,
            focused,
            modifier.weight(1f)
        )
    }
}

7- 要使用 SearchBar,请创建一个 rememberSearchState 并将状态更新为 此处使用列是因为屏幕的其余部分是根据 SearchState

更新的

LaunchedEffect 或在ViewModel 中设置mutableState 可用于设置查询结果或searching 状态字段以显示加载

fun HomeScreen(
    modifier: Modifier = Modifier,
    viewModel: HomeViewModel,
    navigateToTutorial: (String) -> Unit,
    state: SearchState = rememberSearchState()
) {


    Column(
        modifier = modifier.fillMaxSize()
    ) {
            
        SearchBar(
            query = state.query,
            onQueryChange = { state.query = it },
            onSearchFocusChange = { state.focused = it },
            onClearQuery = { state.query = TextFieldValue("") },
            onBack = { state.query = TextFieldValue("") },
            searching = state.searching,
            focused = state.focused,
            modifier = modifier
        )

        LaunchedEffect(state.query.text) {
            state.searching = true
            delay(100)
            state.searchResults = viewModel.getTutorials(state.query.text)
            state.searching = false
        }

        when (state.searchDisplay) {
            SearchDisplay.InitialResults -> {

            }
            SearchDisplay.NoResults -> {

            }

            SearchDisplay.Suggestions -> {

            }

            SearchDisplay.Results -> {
 
            }
        }
    }
}