본문 바로가기

안드로이드

Android - Compose 1.3 PullRefresh

반응형

Compose 1.3 버전에서 Pull To Refresh를 위한 기능이 추가되었다.
기존에 제공하던 accompanist의 SwipeRefresh 가 정식버전으로 넘어올 거라 생각했는데,
조금 다른 형태로 새롭게 추가되었다.

 

1. 차이점

  • 새로운 컴포넌트가 있는 것이 아닌 Modifier를 사용하는 형태로 변경된다.
  • Modifier로 바뀌면서 Indicator를 뷰에 정의하지 않으면 기본값은 없다.
  • onPull, onRelease 상태를 사용할 수 있다.

2. 사용법

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun NewsScreen(viewModel: NewsViewModel = hiltViewModel()) {
    val news = viewModel.news.collectAsLazyPagingItems()

    var isRefreshing by remember { mutableStateOf(false) }
    val pullRefreshState = rememberPullRefreshState(
        refreshing = isRefreshing,
        onRefresh = {
            isRefreshing = true
            news.refresh()
        })

    val pullRefreshModifier = Modifier.pullRefresh(pullRefreshState)

    Box(modifier = pullRefreshModifier, contentAlignment = Alignment.TopCenter) {
        NewsList(news)
        PullRefreshIndicator(refreshing = isRefreshing, state = pullRefreshState)
    }

    LaunchedEffect(news.loadState.refresh) {
        if (news.loadState.refresh is LoadState.NotLoading)
            isRefreshing = false
    }
}

우선, Modifier.pullRefresh 를 통해서 원하는 컴포넌트에 적용하고, PullRefreshState를 넘겨준다.

 

pullRefresh 자체에는 refresh 여부를 저장하는 상태 값이 없기 때문에

isRefreshing과 같은 별도의 변수로 refresh 여부를 지정해주어야 한다. ( SwipeRefresh와 동일 )

 

PullRefreshState 에는

refreshThreshold ( refresh 되는 임계값 ),

refreshingOffset ( refresh 일 때 indicator의 위치 )를 설정할 수 도 있다.

 

SwipeRefresh와 는 다르게 기본 indicator 가 없기 때문에 직접 선언해주어야 한다.

안드로이드의 기본 Indicator는 PullRefreshIndicator라는 이름을 제공해준다.

 

이렇게 하면 pullRefresh 기능을 사용할 수 있다.

3. custom indicator

커스텀하게 indicator를 적용하려면 아래처럼 작성하면 된다.

@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun PokeBallIndicator(
    modifier: Modifier = Modifier,
    state: PullRefreshState,
    refreshing: Boolean
) {
    val indicatorSize = 40.dp

    Surface(
        modifier = modifier
            .size(indicatorSize)
            .pullRefreshIndicatorTransform(state, true),
        shape = CircleShape,
        elevation = if (refreshing) 16.dp else 0.dp
    ) {
        if (refreshing) {
            val transition = rememberInfiniteTransition()
            val degree by transition.animateFloat(
                initialValue = 0f, targetValue = 360f, animationSpec = infiniteRepeatable(
                    animation = tween(
                        durationMillis = 1000,
                        easing = LinearEasing
                    )
                )
            )
            PokeBall(modifier = Modifier.rotate(degree), indicatorSize)
        } else {
            PokeBall(modifier = Modifier.rotate(state.progress * 180), indicatorSize)
        }
    }
}

@Composable
private fun PokeBall(modifier: Modifier = Modifier, size: Dp) {
    Canvas(modifier = modifier) {
        ...
    }
}

위치와 pull 동작 시에 Scale 기능을 제공해주는

Modifier.pullRefreshIndicatorTransform 를 "꼭" 적용해줘야만 한다.

그래야만 현재 상태에 맞게 indicator 가 노출된다.

 

아무것도 하지 않을 때의 indicator의 위치는 컴포넌트의 밖에 존재하는데 정확하게 경계선에 있기 때문에

elevation과 같은 컴포넌트의 그리기 영역을 벗어나는 기능들은 애매하게 보이는 경우가 있다.
그러니 refresh 중일 때만 표현되도록 설정해야 한다.

 

Scale 기능은 on/off 가 가능하다.

왼쪽 Scale On / 오른쪽 Scale Off

 

 

GitHub - D000L/pokedex-compose: clean architecture pokedex app with compose.

clean architecture pokedex app with compose. Contribute to D000L/pokedex-compose development by creating an account on GitHub.

github.com

 

반응형