最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

android - How to enable swipe gesture to open ModalNavigationDrawer when using a full-screen HorizontalPager in Jetpack Compose?

programmeradmin0浏览0评论

I'm using Jetpack Compose and I am trying to enable opening a ModalNavigationDrawer via a swipe gesture (from left to right) similar to how Twitter does it. I have a HorizontalPager that occupies the entire screen, and while I can open the drawer by tapping an icon in the Top Bar and swiping from it, swiping from inside the HorizontalPgaer doesn't trigger the drawer to open.

I expect the ModalNavigationDrawer to open when swiping from left to right on the first page (index = 0) of the HorizontalPager, while still allowing horizontal swipes between pages on the other pages.

My code is the following one:


@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TestNavigationDrawer() {
    val drawerState = rememberDrawerState(DrawerValue.Closed)
    val pagerState = rememberPagerState(initialPage = 0) { 2 }
    val scope = rememberCoroutineScope()

    ModalNavigationDrawer(
        drawerState = drawerState,
        drawerContent = {
            Column(
                Modifier.fillMaxSize().padding(end = 64.dp)
                    .background(MaterialTheme.colorScheme.surface)
                    .systemBarsPadding().systemBarsPadding()
            ) {
                Text("Navigation Drawer")
            }
        }
    ) {

        Scaffold(topBar = {
            CenterAlignedTopAppBar(
                title = { Text("Top bar") },
                navigationIcon = {
                    IconButton(onClick = {
                        scope.launch {
                            drawerState.open()
                        }
                    }
                    ) {
                        Icon(imageVector = Icons.Default.Info, contentDescription = null)
                    }
                })
        }) { innerPadding ->
            Box(modifier = Modifier.fillMaxSize().padding(innerPadding)) {
                HorizontalPager(modifier = Modifier.fillMaxSize(), state = pagerState) { page ->
                    when (page) {
                        0 -> Text("Page 0")
                        1 -> Text("Page 1")
                    }
                }
            }
        }
    }
}

This is a gif with the current behavior of my code. I can open the drawer by tapping the icon and swiping from the Top Bar.

I'm using Jetpack Compose and I am trying to enable opening a ModalNavigationDrawer via a swipe gesture (from left to right) similar to how Twitter does it. I have a HorizontalPager that occupies the entire screen, and while I can open the drawer by tapping an icon in the Top Bar and swiping from it, swiping from inside the HorizontalPgaer doesn't trigger the drawer to open.

I expect the ModalNavigationDrawer to open when swiping from left to right on the first page (index = 0) of the HorizontalPager, while still allowing horizontal swipes between pages on the other pages.

My code is the following one:


@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TestNavigationDrawer() {
    val drawerState = rememberDrawerState(DrawerValue.Closed)
    val pagerState = rememberPagerState(initialPage = 0) { 2 }
    val scope = rememberCoroutineScope()

    ModalNavigationDrawer(
        drawerState = drawerState,
        drawerContent = {
            Column(
                Modifier.fillMaxSize().padding(end = 64.dp)
                    .background(MaterialTheme.colorScheme.surface)
                    .systemBarsPadding().systemBarsPadding()
            ) {
                Text("Navigation Drawer")
            }
        }
    ) {

        Scaffold(topBar = {
            CenterAlignedTopAppBar(
                title = { Text("Top bar") },
                navigationIcon = {
                    IconButton(onClick = {
                        scope.launch {
                            drawerState.open()
                        }
                    }
                    ) {
                        Icon(imageVector = Icons.Default.Info, contentDescription = null)
                    }
                })
        }) { innerPadding ->
            Box(modifier = Modifier.fillMaxSize().padding(innerPadding)) {
                HorizontalPager(modifier = Modifier.fillMaxSize(), state = pagerState) { page ->
                    when (page) {
                        0 -> Text("Page 0")
                        1 -> Text("Page 1")
                    }
                }
            }
        }
    }
}

This is a gif with the current behavior of my code. I can open the drawer by tapping the icon and swiping from the Top Bar.

Share Improve this question asked Feb 5 at 11:27 Danfb__Danfb__ 1711 silver badge6 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 1

You can do it by using a gesture that does not consume which does not prevent neither scroll of ModalDrawer nor HorizontalPager such as

fun Modifier.customTouch(
    pass: PointerEventPass = PointerEventPass.Main,
    onDown: (pointer: PointerInputChange) -> Unit,
    onMove: (changes: List<PointerInputChange>) -> Unit,
    onUp: () -> Unit,
) = this.then(
    Modifier.pointerInput(pass) {
        awaitEachGesture {
            val down = awaitFirstDown(pass = pass, requireUnconsumed = false)
            onDown(down)
            do {
                val event: PointerEvent = awaitPointerEvent(
                    pass = pass
                )

                onMove(event.changes)

            } while (event.changes.any { it.pressed })
            onUp()
        }
    }
)

Since we don't consume this gesture will never prevent HorizontalPager from scrolling.

Inside onMove check if page is zero and swiped to right to start ModalNavigationDrawer scrolling by disabling gesture of HorizontalPager as

.customTouch(
    onDown = {},
    onMove = { changeList ->
        changeList.firstOrNull()?.let {
            if (scrollEnabled && (it.position.x - it.previousPosition.x) > 0f
                && pagerState.currentPage == 0
            ) {
                scrollEnabled = false
            }
        }
    },

    onUp = {
        scrollEnabled = true
    }
)

Full code

@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun TestNavigationDrawer() {
    val drawerState = rememberDrawerState(DrawerValue.Closed)
    val pagerState = rememberPagerState(initialPage = 0) { 2 }
    val scope = rememberCoroutineScope()

    ModalNavigationDrawer(
        drawerState = drawerState,
        drawerContent = {
            Column(
                Modifier.fillMaxSize().padding(end = 64.dp)
                    .background(MaterialTheme.colorScheme.surface)
                    .systemBarsPadding().systemBarsPadding()
            ) {
                Text("Navigation Drawer")
            }
        }
    ) {

        var scrollEnabled by remember {
            mutableStateOf(true)
        }

        Scaffold(topBar = {
            CenterAlignedTopAppBar(
                title = { Text("Top bar") },
                navigationIcon = {
                    IconButton(onClick = {
                        scope.launch {
                            drawerState.open()
                        }
                    }
                    ) {
                        Icon(imageVector = Icons.Default.Info, contentDescription = null)
                    }
                })
        }) { innerPadding ->
            Box(
                modifier = Modifier
                    .fillMaxSize()
                    .padding(innerPadding)
                    .customTouch(
                        onDown = {},
                        onMove = { changeList ->
                            changeList.firstOrNull()?.let {
                                if (scrollEnabled && (it.position.x - it.previousPosition.x) > 0f
                                    && pagerState.currentPage == 0
                                ) {
                                    scrollEnabled = false
                                }
                            }
                        },

                        onUp = {
                            scrollEnabled = true
                        }
                    )
            ) {

//                Box(
//                    modifier = Modifier.fillMaxSize(),
//                    contentAlignment = Alignment.Center
//                ) {
//                    Text("Page 0", fontSize = 40.sp, fontWeight = FontWeight.Bold)
//                }

                HorizontalPager(
                    modifier = Modifier
                        .fillMaxSize(),
                    state = pagerState,
                    userScrollEnabled = scrollEnabled
                ) { page ->
                    Box(
                        modifier = Modifier.fillMaxSize(),
                        contentAlignment = Alignment.Center
                    ) {
                        when (page) {
                            0 -> Text("Page 0", fontSize = 40.sp, fontWeight = FontWeight.Bold)
                            1 -> Text("Page 1", fontSize = 32.sp, fontWeight = FontWeight.Bold)
                        }
                    }
                }
            }
        }
    }
}

However, ModalNavigationDrawer you won't get the same experience as in Twitter because of two things.

1- twitter component animates to pointer position after slope threshold is passed which is not possible with ModalNavigationDrawer.

2- Also it either requires fast swipe or swipe to start from very close to start of screen to move properly when you release your finger. This is independent of HorizontalPager or gesture i posted, you can remove gesture and only try with Box to see how it behaves.

To have exact behavior you need to write a custom Composable or gesture instead of ModalNavigationDrawer while using the gesture and disable HorizontalPager scroll logic.

Give some padding from start in HorizontalPager, it will work fine

No need to do anything extra

code :

HorizontalPager(modifier = Modifier
.fillMaxSize()
.padding(start = 0.5.dp),
state = pagerState
) { page ->
      when (page) {
         0 -> Text("Page 0")
         1 -> Text("Page 1")
      }
  }

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论