SearchBar | Item Selected Change App Bar | Jetpack Compose

Tempo de leitura: 3 minutes
  1. Barra de pesquisa padrão
  2. Item selecionado Barra de pesquisa Ocultar mostrar Barra de seleção

O Jetpack Compose é uma biblioteca moderna de IU para desenvolvimento Android que permite criar interfaces de usuário de maneira declarativa e programática. Algumas informações relevantes sobre Jetpack Compose incluem:

  1. Funções Compostas: O Jetpack Compose utiliza funções que podem ser compostas, permitindo definir a interface do usuário (IU) do aplicativo de maneira programática.
  2. Estado em Compose: O estado em um aplicativo é qualquer valor que pode mudar ao longo do tempo. Isso pode variar desde um banco de dados do Room até informações na interface do usuário, permitindo interações dinâmicas.
  3. Bottom App Bar: Em contextos de IU, como o Bottom App Bar, é possível criar um recurso de menu com os itens desejados e configurar a ActionBar utilizando este recurso.
  4. SearchBar com Jetpack Compose: Para criar uma barra de pesquisa, pode-se utilizar o composável SearchBar oferecido pelo Material 3 do Jetpack Compose. Essa barra flutuante permite que os usuários insiram palavras-chave ou frases para obter informações relevantes.
  5. App Bars em Jetpack Compose: As App Bars podem conter um título, itens de ação principais e determinados itens de navegação. É possível personalizá-la conforme as necessidades do aplicativo, incluindo a alteração com base em seleções específicas.

Abaixo você terá um exemplo pratico disto.

//SearchBar.kt
@Composable
fun SearchBar(
    searchBarHeight: Dp,
    searchBarOffsetHeightPx: Float,
    isVisible: Boolean,
    value: String,
    onChange: (newValue: String) -> Unit
) {
    val searchBarFloatHeight = with(LocalDensity.current) { searchBarHeight.roundToPx().toFloat() }

    AnimatedVisibility(
        isVisible,
        enter = slideIn(tween(200, easing = LinearOutSlowInEasing)) {
            IntOffset(0, -searchBarFloatHeight.roundToInt())
        },
        exit = slideOut(tween(200, easing = FastOutSlowInEasing)) {
            IntOffset(0, -searchBarFloatHeight.roundToInt())
        },
    ) {
        Box(
            modifier = Modifier.height(searchBarHeight).padding(20.dp, 10.dp, 20.dp, 0.dp)
                .offset { IntOffset(x = 0, y = searchBarOffsetHeightPx.roundToInt()) },
        ) {
            //HERE SEARCH BAR COMPOSE ADD
        }
    }
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun NoteSelectionScreen() {
    val searchBarHeight = 60.dp
    val searchBarHeightPx = with(LocalDensity.current) { searchBarHeight.roundToPx().toFloat() }
    val searchBarOffsetHeightPx = remember { mutableFloatStateOf(0f) }
    val lazyGridNotes = rememberLazyStaggeredGridState()

    val nestedScrollConnection = remember {
        object : NestedScrollConnection {
            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
                val delta = available.y
                val newOffset = searchBarOffsetHeightPx.floatValue + delta
                if (lazyGridNotes.canScrollForward) searchBarOffsetHeightPx.floatValue =
                    newOffset.coerceIn(-searchBarHeightPx, 0f)
                return Offset.Zero
            }
        }
    }

    Box(Modifier.fillMaxSize().nestedScroll(nestedScrollConnection)) {
        ScreenContent(lazyGridNotes, notesViewModel)
        Box(modifier = Modifier) {
            SearchBar(
                searchBarHeight,
                searchBarOffsetHeightPx.floatValue,
                true,
                //searchText,
                //changeSearchText
            )
            ActionBar(actionBarHeight, offsetX)
        }
        Box(modifier = Modifier.align(Alignment.BottomEnd).padding(35.dp)) {
            AnimatedVisibility(
                notesViewModel.screenState.value.selectedNoteId != null,
                enter = fadeIn(tween(200, easing = LinearOutSlowInEasing)),
                exit = fadeOut(tween(200, easing = FastOutSlowInEasing)),
            ) {
                //FloatingActionButton ADD
            }
        }
    }
}


@Composable
private fun ScreenContent(
    lazyGridNotes: LazyStaggeredGridState
) {
    LazyGridNotes(
        modifier = Modifier.fillMaxSize().testTag("notes_list"),
        gridState = lazyGridNotes
    ) {
          //ADD COMPOSE
      }
}

//val selectedNotes: Set<ObjectId> = emptySet()

@Composable
private fun ActionBar(
    actionBarHeight: Dp, offsetX: Animatable<Float, AnimationVector1D>
) {
    val systemUiController = rememberSystemUiController()
    val backgroundColor by animateColorAsState(
        if (selectedNotes.isNotEmpty()) CustomTheme.colors.secondaryBackground else CustomTheme.colors.mainBackground,
        animationSpec = tween(200), label = "",
    )
    SideEffect {
        systemUiController.setStatusBarColor(backgroundColor)
    }

    val topAppBarElevation = if (offsetX.value.roundToInt() < -actionBarHeight.value.roundToInt()) 0.dp else 2.dp
    Box(
        modifier = Modifier.height(actionBarHeight).offset { IntOffset(x = 0, y = offsetX.value.roundToInt()) },
    ) {
        CustomTopAppBar(
            modifier = Modifier.fillMaxWidth(),
            elevation = topAppBarElevation,
            backgroundColor = CustomTheme.colors.secondaryBackground,
            text = selectedNotes.count().toString(),
            navigation = TopAppBarItem(
                isActive = false,
                icon = Icons.Default.Close,
                iconDescription = R.string.GoBack,
                onClick = {}
            ),
            items = listOf(
                TopAppBarItem(
                    isActive = false,
                    icon = Icons.Outlined.PushPin,
                    iconDescription = R.string.AttachNote,
                    onClick = {}
                ),
                TopAppBarItem(
                    isActive = false,
                    icon = Icons.Outlined.NotificationAdd,
                    iconDescription = R.string.AddToNotification,
                    onClick = {}
                ),
                TopAppBarItem(
                    isActive = false,
                    icon = Icons.Outlined.Archive,
                    iconDescription = R.string.AddToArchive,
                    onClick = {}
                ),
                TopAppBarItem(
                    isActive = false,
                    icon = Icons.Outlined.Delete,
                    iconDescription = R.string.AddToBasket,
                    onClick = {}
                ),
            )
        )
    }
}
//TopActionBar.kt
data class TopAppBarItem(
    val isActive: Boolean = false,
    val icon: ImageVector,
    @StringRes
    val iconDescription: Int,
    val modifier: Modifier = Modifier,
    val onClick: () -> Unit
)

@Composable
fun CustomTopAppBar(
    modifier: Modifier,
    elevation: Dp = 0.dp,
    text: String = "",
    backgroundColor: Color = CustomTheme.colors.secondaryBackground,
    contentColor: Color = CustomTheme.colors.textSecondary,
    navigation: TopAppBarItem,
    items: List<TopAppBarItem>
) {
    TopAppBar(
        elevation = elevation,
        contentColor = contentColor,
        backgroundColor = backgroundColor,
        modifier = modifier,
        title = {
            Spacer(modifier = Modifier.padding(6.dp, 0.dp, 0.dp, 0.dp))
            Text(
                text,
                overflow = TextOverflow.Ellipsis,
                style = MaterialTheme.typography.body1.copy(fontSize = 18.sp),
                color = CustomTheme.colors.textSecondary,
                maxLines = 1,
            )
        },
        navigationIcon = {
            Spacer(modifier = Modifier.padding(6.dp, 0.dp, 0.dp, 0.dp))
            IconButton(
                onClick = navigation.onClick,
                modifier = navigation.modifier.size(40.dp).clip(CircleShape).padding(0.dp),
            ) {
                Icon(
                    imageVector = navigation.icon,
                    contentDescription = stringResource(navigation.iconDescription),
                    tint = if (navigation.isActive) CustomTheme.colors.text else CustomTheme.colors.textSecondary,
                )
            }
        },
        actions = {
            for (item in items) {
                IconButton(
                    onClick = item.onClick,
                    modifier = item.modifier.size(40.dp).clip(CircleShape).padding(0.dp),
                ) {
                    Icon(
                        imageVector = item.icon,
                        contentDescription = stringResource(item.iconDescription),
                        tint = if (item.isActive) CustomTheme.colors.text else CustomTheme.colors.textSecondary,
                    )
                }
                Spacer(modifier = Modifier.padding(6.dp, 0.dp, 0.dp, 0.dp))
            }
        })
}

E