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

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

API surface

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

  • isRefreshing – логическое значение, указывающее, выполняется ли обновление.
  • onRefresh – лямбда-функция, которая выполняется, когда пользователь инициирует обновление.
  • 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-функции и анимации.В этом фрагменте кода показано, как создать полностью настраиваемый индикатор в реализации функции "Потянуть, чтобы обновить":

@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(
                    painter = rememberVectorPainter(AppIcons.CloudDownload),
                    contentDescription = "Refresh",
                    modifier = Modifier
                        .size(18.dp)
                        .graphicsLayer {
                            val progress = distanceFraction()
                            this.alpha = progress
                            this.scaleX = progress
                            this.scaleY = progress
                        }
                )
            }
        }
    }
}

Важные сведения о коде

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

Результат

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

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

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