Created
November 17, 2023 14:22
-
-
Save f2janyway/3c6532e8e96278a7fb28638bc4242e42 to your computer and use it in GitHub Desktop.
fixed background of page of horizontalPager of Jetpack Compose example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import androidx.compose.foundation.ExperimentalFoundationApi | |
| import androidx.compose.foundation.background | |
| import androidx.compose.foundation.layout.Box | |
| import androidx.compose.foundation.layout.Column | |
| import androidx.compose.foundation.layout.Row | |
| import androidx.compose.foundation.layout.Spacer | |
| import androidx.compose.foundation.layout.fillMaxSize | |
| import androidx.compose.foundation.layout.fillMaxWidth | |
| import androidx.compose.foundation.layout.height | |
| import androidx.compose.foundation.layout.padding | |
| import androidx.compose.foundation.layout.width | |
| import androidx.compose.foundation.pager.HorizontalPager | |
| import androidx.compose.foundation.pager.PagerState | |
| import androidx.compose.foundation.pager.rememberPagerState | |
| import androidx.compose.material.icons.Icons | |
| import androidx.compose.material.icons.filled.AccountBox | |
| import androidx.compose.material.icons.filled.AddCircle | |
| import androidx.compose.material.icons.filled.Build | |
| import androidx.compose.material.icons.filled.Call | |
| import androidx.compose.material.icons.filled.List | |
| import androidx.compose.material3.Icon | |
| import androidx.compose.material3.Surface | |
| import androidx.compose.material3.Text | |
| import androidx.compose.runtime.Composable | |
| import androidx.compose.runtime.mutableStateOf | |
| import androidx.compose.runtime.remember | |
| import androidx.compose.ui.Alignment | |
| import androidx.compose.ui.Modifier | |
| import androidx.compose.ui.draw.drawBehind | |
| import androidx.compose.ui.geometry.Offset | |
| import androidx.compose.ui.geometry.Rect | |
| import androidx.compose.ui.geometry.Size | |
| import androidx.compose.ui.graphics.Color | |
| import androidx.compose.ui.graphics.Outline | |
| import androidx.compose.ui.graphics.Shape | |
| import androidx.compose.ui.graphics.graphicsLayer | |
| import androidx.compose.ui.layout.LayoutCoordinates | |
| import androidx.compose.ui.layout.onGloballyPositioned | |
| import androidx.compose.ui.platform.LocalDensity | |
| import androidx.compose.ui.tooling.preview.Preview | |
| import androidx.compose.ui.unit.Density | |
| import androidx.compose.ui.unit.LayoutDirection | |
| import androidx.compose.ui.unit.dp | |
| import androidx.compose.ui.unit.sp | |
| import dagger.hilt.android.AndroidEntryPoint | |
| @OptIn(ExperimentalFoundationApi::class) | |
| @Composable | |
| fun FixedBGHorizontalPager() { | |
| val list = listOf("java", "kotlin", "python", "rust", "javascript") | |
| val listOfColor = listOf(Color.Gray, Color.Yellow, Color.LightGray, Color.Magenta, Color.Green) | |
| val listOfIcons = listOf( | |
| Icons.Default.List, | |
| Icons.Default.AccountBox, | |
| Icons.Default.AddCircle, | |
| Icons.Default.Build, | |
| Icons.Default.Call | |
| ) | |
| val pagerState = rememberPagerState { | |
| list.count() | |
| } | |
| val modifier = Modifier | |
| Column { | |
| HorizontalPager( | |
| state = pagerState, modifier = modifier | |
| .height(200.dp) | |
| .fillMaxWidth() | |
| ) { page -> | |
| Box( | |
| modifier = modifier | |
| .fillMaxSize() | |
| .background(listOfColor[page]), | |
| contentAlignment = Alignment.Center | |
| ) { | |
| Icon( | |
| imageVector = listOfIcons[page], | |
| contentDescription = null, | |
| modifier = modifier | |
| .fillMaxSize() | |
| .graphicsLayer { | |
| val pageOffset = pagerState.offsetForPage(page) | |
| translationX = size.width * pageOffset | |
| shape = ProportionalClipShape(shownSpace = pagerState.shownSpace(page)) | |
| clip = true | |
| }, | |
| tint = Color.White | |
| ) | |
| Text(text = list[page], color = Color.Black, fontSize = 30.sp) | |
| } | |
| } | |
| list.forEachIndexed { page, i -> | |
| Box( | |
| modifier = modifier | |
| .fillMaxWidth() | |
| .padding(bottom = 6.dp) | |
| ) { | |
| Text( | |
| text = "page: $page :" + | |
| "offsetForPage:${ | |
| pagerState.offsetForPage(page).toRangeStr() | |
| },\n" + | |
| "starrOffsetForPage:${ | |
| pagerState.startOffsetForPage(page).toRangeStr() | |
| }," + | |
| "endOffsetForPage:${ | |
| pagerState.endOffsetForPage(page).toRangeStr() | |
| },\n" + | |
| "showSpace:${ | |
| pagerState.shownSpace(page).toRangeStr() | |
| }" | |
| ) | |
| } | |
| } | |
| } | |
| } | |
| class ProportionalClipShape(val shownSpace: Float) : Shape { | |
| override fun createOutline( | |
| size: Size, | |
| layoutDirection: LayoutDirection, | |
| density: Density | |
| ): Outline { | |
| val clipSize = when { | |
| shownSpace < 0 -> size.copy(width = size.width * -shownSpace) | |
| shownSpace > 0 && shownSpace < 1 -> size.copy(width = size.width * shownSpace) | |
| else -> size | |
| } | |
| val offset = when { | |
| shownSpace < 0 -> Offset(0f, 0f) | |
| shownSpace > 0 && shownSpace < 1 -> Offset(size.width * (1 - shownSpace), 0f) | |
| else -> Offset.Zero | |
| } | |
| return Outline.Rectangle(rect = Rect(offset,clipSize)) | |
| } | |
| } | |
| fun Float.toRangeStr(count: Int = 5): String { | |
| if ("$this".length > count) { | |
| return "$this".substring(0, count) | |
| } else { | |
| return "$this" | |
| } | |
| } | |
| //https://www.sinasamaki.com/pager-animations/ | |
| // ACTUAL OFFSET | |
| @OptIn(ExperimentalFoundationApi::class) | |
| fun PagerState.offsetForPage(page: Int) = (currentPage - page) + currentPageOffsetFraction | |
| // OFFSET ONLY FROM THE LEFT | |
| @OptIn(ExperimentalFoundationApi::class) | |
| fun PagerState.startOffsetForPage(page: Int): Float { | |
| return offsetForPage(page).coerceAtLeast(0f) | |
| } | |
| // OFFSET ONLY FROM THE RIGHT | |
| @OptIn(ExperimentalFoundationApi::class) | |
| fun PagerState.endOffsetForPage(page: Int): Float { | |
| return offsetForPage(page).coerceAtMost(0f) | |
| } | |
| /** | |
| * | |
| * This function considered only fillMaxWidth of HorizontalPager | |
| * 왼쪽이 보이면 음수 :Left (value < 0 && value > -1) or (value < 0 && abs(value) < 1) | |
| * 오른쪽에 보이면 양수 :Right (value > 0 && value < 1) | |
| * 안보이면 0 :Invisiable | |
| * 전체 다보이면 1 :Settle (centered) | |
| * */ | |
| @OptIn(ExperimentalFoundationApi::class) | |
| fun PagerState.shownSpace(page: Int): Float { | |
| val offsetForPage = offsetForPage(page) | |
| return when { | |
| 0f == offsetForPage -> 1f | |
| 1 > kotlin.math.abs(offsetForPage) && offsetForPage > 0 -> -(1 - offsetForPage) | |
| 1 > kotlin.math.abs(offsetForPage) && offsetForPage < 0 -> (1 + offsetForPage) | |
| else -> 0f | |
| } | |
| } | |
| @Preview | |
| @Composable | |
| private fun Pre() { | |
| FixedBGHorizontalPager() | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment