Потяните, чтобы обновить

Компонент "Потяните, чтобы обновить" позволяет пользователям обновить данные, потянув вниз в начале контента приложения.

Поверхность API

Используйте composable-функцию PullToRefreshBox, чтобы реализовать функцию "потянуть, чтобы обновить", которая действует как контейнер для контента с возможностью прокрутки. Поведение и внешний вид обновлений определяют следующие ключевые параметры:

  • isRefreshing – логическое значение, указывающее, выполняется ли обновление.
  • onRefresh – функция Lambda, которая выполняется, когда пользователь инициирует обновление.
  • indicator: позволяет настроить индикатор, который система рисует при обновлении страницы с помощью жеста "тянуть вниз".

Простой пример

Ниже приведен фрагмент кода, в котором показано, как использовать PullToRefreshBox:

@Composable
fun PullToRefreshBasicSample(
    items: List<String>,
    isRefreshing: Boolean,
    onRefresh: () -> Unit,
    modifier: Modifier = Modifier
) {
    PullToRefreshBox(
        isRefreshing = isRefreshing,
        onRefresh = onRefresh,
        modifier = modifier
    ) {
        LazyColumn(Modifier.fillMaxSize()) {
            items(items) {
                ListItem({ Text(text = it) })
            }
        }
    }
}

Ключевые моменты

  • PullToRefreshBox содержит LazyColumn, который отображает список строк.
  • Для параметра PullToRefreshBox требуются параметры isRefreshing и onRefresh.
  • Контент внутри блока PullToRefreshBox представляет собой контент с прокруткой.

Результат

В этом видео показана базовая реализация функции "Потяните, чтобы обновить" на основе приведенного выше кода:

Рисунок 1. Базовая реализация функции "Потяните, чтобы обновить" в списке элементов.

Расширенный пример: как настроить цвет индикатора

@Composable
fun PullToRefreshCustomStyleSample(
    items: List<String>,
    isRefreshing: Boolean,
    onRefresh: () -> Unit,
    modifier: Modifier = Modifier
) {
    val state = rememberPullToRefreshState()

    PullToRefreshBox(
        isRefreshing = isRefreshing,
        onRefresh = onRefresh,
        modifier = modifier,
        state = state,
        indicator = {
            Indicator(
                modifier = Modifier.align(Alignment.TopCenter),
                isRefreshing = isRefreshing,
                containerColor = MaterialTheme.colorScheme.primaryContainer,
                color = MaterialTheme.colorScheme.onPrimaryContainer,
                state = state
            )
        },
    ) {
        LazyColumn(Modifier.fillMaxSize()) {
            items(items) {
                ListItem({ Text(text = it) })
            }
        }
    }
}

Ключевые моменты

  • Цвет индикатора настраивается с помощью свойств containerColor и color в параметре indicator.
  • rememberPullToRefreshState() управляет состоянием действия обновления. Этот статус используется вместе с параметром indicator.

Результат

В этом видео показано, как реализовать функцию "Потяните, чтобы обновить" с цветным индикатором:

Рисунок 2. Реализация функции "обновить, потянув вниз" с собственным стилем.

Расширенный пример: как создать полностью настраиваемый индикатор

Вы можете создавать сложные индикаторы, используя существующие компоненты и анимацию.В этом фрагменте кода показано, как создать полностью настраиваемый индикатор для функции "Потяните, чтобы обновить":

@Composable
fun PullToRefreshCustomIndicatorSample(
    items: List<String>,
    isRefreshing: Boolean,
    onRefresh: () -> Unit,
    modifier: Modifier = Modifier
) {
    val state = rememberPullToRefreshState()

    PullToRefreshBox(
        isRefreshing = isRefreshing,
        onRefresh = onRefresh,
        modifier = modifier,
        state = state,
        indicator = {
            MyCustomIndicator(
                state = state,
                isRefreshing = isRefreshing,
                modifier = Modifier.align(Alignment.TopCenter)
            )
        }
    ) {
        LazyColumn(Modifier.fillMaxSize()) {
            items(items) {
                ListItem({ Text(text = it) })
            }
        }
    }
}

// ...
@Composable
fun MyCustomIndicator(
    state: PullToRefreshState,
    isRefreshing: Boolean,
    modifier: Modifier = Modifier,
) {
    Box(
        modifier = modifier.pullToRefresh(
            state = state,
            isRefreshing = isRefreshing,
            threshold = PositionalThreshold,
            onRefresh = {

            }
        ),
        contentAlignment = Alignment.Center
    ) {
        Crossfade(
            targetState = isRefreshing,
            animationSpec = tween(durationMillis = CROSSFADE_DURATION_MILLIS),
            modifier = Modifier.align(Alignment.Center)
        ) { refreshing ->
            if (refreshing) {
                CircularProgressIndicator(Modifier.size(SPINNER_SIZE))
            } else {
                val distanceFraction = { state.distanceFraction.coerceIn(0f, 1f) }
                Icon(
                    imageVector = Icons.Filled.CloudDownload,
                    contentDescription = "Refresh",
                    modifier = Modifier
                        .size(18.dp)
                        .graphicsLayer {
                            val progress = distanceFraction()
                            this.alpha = progress
                            this.scaleX = progress
                            this.scaleY = progress
                        }
                )
            }
        }
    }
}

Ключевые моменты

  • В предыдущем фрагменте использовался код Indicator, предоставленный библиотекой. Этот фрагмент кода создает собственный компонент индикатора с названием MyCustomIndicator. В этом компоненте модификатор pullToRefreshIndicator отвечает за позиционирование и запуск обновления.
  • Как и в предыдущем фрагменте, в этом примере извлекается экземпляр PullToRefreshState, поэтому вы можете передать один и тот же экземпляр и в PullToRefreshBox, и в pullToRefreshModifier.
  • В примере используются цвет контейнера и пороговое значение позиции из класса PullToRefreshDefaults. Таким образом вы сможете использовать поведение и стиль по умолчанию из библиотеки Material, настраивая только те элементы, которые вам нужны.
  • MyCustomIndicator использует Crossfade для перехода между значком облака и CircularProgressIndicator. Значок облака увеличивается по мере того, как пользователь тянет экран вниз, и превращается в значок CircularProgressIndicator, когда начинается обновление.
    • targetState использует isRefreshing, чтобы определить, какое состояние отображать (значок облака или кольцевой индикатор прогресса).
    • animationSpec определяет анимацию tween для перехода с заданной продолжительностью CROSSFADE_DURATION_MILLIS.
    • state.distanceFraction – насколько пользователь потянул вниз, от 0f (не потянул) до 1f (потянул полностью).
    • Модификатор graphicsLayer изменяет масштаб и прозрачность.

Результат

В этом видео показан пользовательский индикатор из приведенного выше кода:

Рисунок 3. Реализация функции "Потяните, чтобы обновить" со специальным индикатором.

Дополнительные ресурсы