Заметка
Вам необходимо иметь установленной версию Android Studio Arctic Fox и выше для использования Jetpack Compose в вашем проекте.
В этом туториале я покажу вам, как производить поиск по списку (List) непосредственно во время набора текста в текстовом поле (TextField) и отображать найденные данные в окне подробных сведений (Details Screen), т.е. речь пойдет о поиске по списку с визуальной выдачей результата.
Добавление библиотек
Необходимо добавить в файл gradle.build на уровне проекта следующее:
buildscript {
ext {
compose_version = '1.0.0-rc02'
}
// ...
}
После этого необходимо добавить следующие строки в gradle.build файл на уровне приложения:
android {
// ...
kotlinOptions {
jvmTarget = '1.8'
useIR = true
}
buildFeatures {
// ...
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
}
}
dependencies {
// ...
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling:$compose_version"
implementation "androidx.navigation:navigation-compose:2.4.0-alpha04"
implementation "androidx.activity:activity-compose:1.3.0-rc02"
// ...
}
Создание верхней строки (TOP BAR).
Перейдите в ваш рабочий файл-активити (напр. MainActivity.kt), и вне класса пропишите следующую компонуемую функцию для создания верхней строки (Top Bar):
@Composable
fun TopBar() {
TopAppBar(
title = { Text(text = stringResource(R.string.app_name), fontSize = 18.sp) },
backgroundColor = colorResource(id = R.color.colorPrimary),
contentColor = Color.White
)
}
@Preview(showBackground = true)
@Composable
fun TopBarPreview() {
TopBar()
}
Добавление верхней строки (TOP BAR) в структуру проекта (LAYOUT)
Теперь добавим верхнюю строку (Top Bar) в Scaffold layout. (Я не могу показать вам Preview так как мы добавляем непосредственно методом OnCreate)
Заметка
Scaffold Layout – это layout в Jetpack Compose (как Relative Layout или LinearLayout в XML) с готовыми «зонами» для верхней строки (Top Bar), строки в нижней части страницы/экрана (Bottom Bar), Плавающей кнопки действия) FAB button, навигации между экранами (Navigation Drawer).
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Scaffold(
topBar = { TopBar() },
backgroundColor = colorResource(id = R.color.colorPrimaryDark)
) {
/* Add code later */
}
}
}
}
Создание поиска (Search View)
Так как в Jetpack Compose нет встроенных средств поиска (SearchView), как в XML, мы создадим наш собственный поиск c использованием текстового поля (TextField), иконкой лупы в начале поля и иконкой Х в конце строки.
Иконка Х будет начнет отображаться только при наборе текста в поле.
@Composable
fun SearchView(state: MutableState) {
TextField(
value = state.value,
onValueChange = { value ->
state.value = value
},
modifier = Modifier
.fillMaxWidth(),
textStyle = TextStyle(color = Color.White, fontSize = 18.sp),
leadingIcon = {
Icon(
Icons.Default.Search,
contentDescription = "",
modifier = Modifier
.padding(15.dp)
.size(24.dp)
)
},
trailingIcon = {
if (state.value != TextFieldValue("")) {
IconButton(
onClick = {
state.value =
TextFieldValue("") // Remove text from TextField when you press the 'X' icon
}
) {
Icon(
Icons.Default.Close,
contentDescription = "",
modifier = Modifier
.padding(15.dp)
.size(24.dp)
)
}
}
},
singleLine = true,
shape = RectangleShape, // The TextFiled has rounded corners top left and right by default
colors = TextFieldDefaults.textFieldColors(
textColor = Color.White,
cursorColor = Color.White,
leadingIconColor = Color.White,
trailingIconColor = Color.White,
backgroundColor = colorResource(id = R.color.colorPrimary),
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
disabledIndicatorColor = Color.Transparent
)
)
}
@Preview(showBackground = true)
@Composable
fun SearchViewPreview() {
val textState = remember { mutableStateOf(TextFieldValue("")) }
SearchView(textState)
}
Создание списка (LIST) и элементов списка (LIST ITEM)
Перед тем, как создать Список (List) нам необходимо создать элемент списка (List Item).
Элемент списка (List Item)
Элемент списка прост по своей сущности, он содержит Текст внутри строки, и когда мы кликаем на строку, он через конструкцию onItemClick передает название страны (CountryText) на экран подробных сведений (Details Screen), который мы создадим позднее.
@Composable
fun CountryListItem(countryText: String, onItemClick: (String) -> Unit) {
Row(
modifier = Modifier
.clickable(onClick = { onItemClick(countryText) })
.background(colorResource(id = R.color.colorPrimaryDark))
.height(57.dp)
.fillMaxWidth()
.padding(PaddingValues(8.dp, 16.dp))
) {
Text(text = countryText, fontSize = 18.sp, color = Color.White)
}
}
@Preview(showBackground = true)
@Composable
fun CountryListItemPreview() {
CountryListItem(countryText = "United States 🇺🇸", onItemClick = { })
}
Список (List)
В этом примере мы получаем список стран из getListOfCountries(), и отбираем значения, которые совпадают с текстом, который набираем в строке поиска SearchView, затем сохраняем результат в массив filteredConutries.
Когда строка поиска SearchView пуста, мы видим все страны из массива countries, а когда начинаем вводить текст, начинают отображаться данные из массива filteredCountries.
@Composable
fun CountryList(navController: NavController, state: MutableState) {
val countries = getListOfCountries()
var filteredCountries: ArrayList
LazyColumn(modifier = Modifier.fillMaxWidth()) {
val searchedText = state.value.text
filteredCountries = if (searchedText.isEmpty()) {
countries
} else {
val resultList = ArrayList()
for (country in countries) {
if (country.lowercase(Locale.getDefault())
.contains(searchedText.lowercase(Locale.getDefault()))
) {
resultList.add(country)
}
}
resultList
}
items(filteredCountries) { filteredCountry ->
CountryListItem(
countryText = filteredCountry,
onItemClick = { selectedCountry ->
/* Add code later */
}
)
}
}
}
@Preview(showBackground = true)
@Composable
fun CountryListPreview() {
val navController = rememberNavController()
val textState = remember { mutableStateOf(TextFieldValue("")) }
CountryList(navController = navController, state = textState)
}
fun getListOfCountries(): ArrayList {
val isoCountryCodes = Locale.getISOCountries()
val countryListWithEmojis = ArrayList()
for (countryCode in isoCountryCodes) {
val locale = Locale("", countryCode)
val countryName = locale.displayCountry
val flagOffset = 0x1F1E6
val asciiOffset = 0x41
val firstChar = Character.codePointAt(countryCode, 0) - asciiOffset + flagOffset
val secondChar = Character.codePointAt(countryCode, 1) - asciiOffset + flagOffset
val flag =
(String(Character.toChars(firstChar)) + String(Character.toChars(secondChar)))
countryListWithEmojis.add("$countryName $flag")
}
return countryListWithEmojis
}
Связывание строки поиска SEARCHVIEW и списка LIST
После того, как мы создали строку поиска и список, настало время связать их.
Создадим новую компонующую функцию, назовем ее MainScreen с параметром navController (о навигации мы поговорим позднее). Здесь мы «Сохраним» состояние текста, который мы набираем в данный момент в текстовом поле TextField, и добавим строку поиска SearchView и Список List во колонку.
@Composable
fun MainScreen(navController: NavController) {
val textState = remember { mutableStateOf(TextFieldValue("")) }
Column {
SearchView(textState)
CountryList(navController = navController, state = textState)
}
}
@Preview(showBackground = true)
@Composable
fun MainScreenPreview() {
val navController = rememberNavController()
MainScreen(navController = navController)
}
Как вы видите, и поиск SearchView, и список стран CountryList получает значение textState и обновляются автоматически при наборе текста.
Переход к новому окну при нажатии на элемент списка
Перед тем, как приступить к навигации, разберемся с окном сведений Details Screen.
Экран подробных сведений
Создайте новый Kotlin файл, назовите его DetailsScreen.kt и вставьте следующий код:
Здесь происходит следующее: мы передаем названием страны из предыдущего окна (содержащего поисковую строку и список) и отображаем его в центре экрана.
@Composable
fun DetailsScreen(countryName: String) {
Column(
modifier = Modifier
.fillMaxSize()
.background(colorResource(id = R.color.colorPrimaryDark))
.wrapContentSize(Alignment.Center)
) {
Text(
text = countryName,
color = Color.White,
modifier = Modifier.align(Alignment.CenterHorizontally),
textAlign = TextAlign.Center,
fontSize = 22.sp
)
}
}
@Preview(showBackground = true)
@Composable
fun DetailsScreenPreview() {
DetailsScreen("United States 🇺🇸")
}
Навигация
Вернемся к MainActivity.kt, создадим новую компонуемую функцию под именем Navigation(). Внутри этой функции создадим NavHost и добавим компонуемые Homescreen и DetailScreen (с данными, которые передали).
Затем вызовем функцию Naviagtion() из Scaffold layout.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Scaffold(
topBar = { TopBar() },
backgroundColor = colorResource(id = R.color.colorPrimaryDark)
) {
Navigation()
}
}
}
}
@Composable
fun Navigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "main") {
composable("main") {
MainScreen(navController = navController)
}
composable(
"details/{countryName}",
arguments = listOf(navArgument("countryName") { type = NavType.StringType })
) { backStackEntry ->
backStackEntry.arguments?.getString("countryName")?.let { countryName ->
DetailsScreen(countryName = countryName)
}
}
}
}
Наконец, в List добавьте следующий код внутри конструкции onItemClick, чтобы иметь возможность перемещаться и передавать данные на экран сведений DetailScreen() при нажатии на элемент.
// ...
items(filteredCountries) { filteredCountry ->
CountryListItem(
countryText = filteredCountry,
onItemClick = { selectedCountry ->
navController.navigate("details/$selectedCountry") {
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
// on the back stack as users select items
popUpTo("main") {
saveState = true
}
// Avoid multiple copies of the same destination when
// reselecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true
}
}
)
}
// ...
Проект можно найти здесь.