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 未聚焦且为空时显示初始可组合项,您可以自定义或删除 InitialResults
或 Suggestions
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 -> {
}
}
}
}
我想使用 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 未聚焦且为空时显示初始可组合项,您可以自定义或删除 InitialResults
或 Suggestions
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 -> {
}
}
}
}