diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index 9a55c2d..fdf8d99 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/banner/build.gradle b/banner/build.gradle
index 663300a..1ae97d0 100644
--- a/banner/build.gradle
+++ b/banner/build.gradle
@@ -17,7 +17,7 @@ afterEvaluate {
// You can then customize attributes of the publication as shown below.
groupId = 'com.zj.banner'
artifactId = 'banner'
- version = '2.5.0'
+ version = '2.5.2'
}
// // Creates a Maven publication called “debug”.
// debug(MavenPublication) {
@@ -71,8 +71,4 @@ dependencies {
implementation 'io.coil-kt:coil-compose:2.4.0'
- def accompanist_version = "0.31.5-beta"
- api "com.google.accompanist:accompanist-pager:$accompanist_version"
- api "com.google.accompanist:accompanist-pager-indicators:$accompanist_version"
-
}
\ No newline at end of file
diff --git a/banner/src/main/java/com/zj/banner/BannerPager.kt b/banner/src/main/java/com/zj/banner/BannerPager.kt
index 685de46..8a53a1f 100644
--- a/banner/src/main/java/com/zj/banner/BannerPager.kt
+++ b/banner/src/main/java/com/zj/banner/BannerPager.kt
@@ -1,10 +1,16 @@
+@file:OptIn(ExperimentalFoundationApi::class)
+
package com.zj.banner
import android.util.Log
+import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.PagerState
+import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
@@ -12,10 +18,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
-import com.google.accompanist.pager.*
import com.zj.banner.model.BaseBannerBean
import com.zj.banner.ui.BannerCard
import com.zj.banner.ui.config.BannerConfig
+import com.zj.banner.utils.HorizontalPagerIndicator
+import com.zj.banner.utils.VerticalPagerIndicator
import kotlinx.coroutines.launch
import java.util.*
import kotlin.math.absoluteValue
@@ -32,7 +39,6 @@ private const val TAG = "BannerPager"
* @param indicatorGravity Banner 指示器位置,直接使用 Alignment 即可进行设定
* @param onBannerClick Banner 点击事件的回调
*/
-@OptIn(ExperimentalPagerApi::class)
@Composable
fun BannerPager(
modifier: Modifier = Modifier,
@@ -46,7 +52,12 @@ fun BannerPager(
throw NullPointerException("items is not null")
}
- val pagerState = rememberPagerState()
+ val pagerState = rememberPagerState(
+ initialPage = 0,
+ initialPageOffsetFraction = 0f
+ ) {
+ items.size
+ }
if (config.repeat) {
StartBanner(pagerState, config.intervalTime)
@@ -54,50 +65,55 @@ fun BannerPager(
Box(modifier = modifier.height(config.bannerHeight)) {
HorizontalPager(
- count = items.size,
+ modifier = Modifier,
state = pagerState,
- itemSpacing = config.itemSpacing,
- contentPadding = config.contentPadding
- ) { page ->
- val item = items[page]
- BannerCard(
- bean = item,
- modifier = Modifier
- .graphicsLayer {
- // Calculate the absolute offset for the current page from the
- // scroll position. We use the absolute value which allows us to mirror
- // any effects for both directions
- val pageOffset = calculateCurrentOffsetForPage(page).absoluteValue
+ key = { items[it].data?:it },
+ pageContent = { page ->
+ val item = items[page]
- // We animate the scaleX + scaleY, between 85% and 100%
- lerp(
- start = 0.85f,
- stop = 1f,
- fraction = 1f - pageOffset.coerceIn(0f, 1f)
- ).also { scale ->
- scaleX = scale
- scaleY = scale
- }
+ BannerCard(
+ bean = item,
+ modifier = Modifier
+ .graphicsLayer {
+ // Calculate the absolute offset for the current page from the
+ // scroll position. We use the absolute value which allows us to mirror
+ // any effects for both directions
+ val offset =
+ (pagerState.currentPage - page) + pagerState.currentPageOffsetFraction
+ val pageOffset = offset.absoluteValue
+
+ // We animate the scaleX + scaleY, between 85% and 100%
+ lerp(
+ start = 0.85f,
+ stop = 1f,
+ fraction = 1f - pageOffset.coerceIn(0f, 1f)
+ ).also { scale ->
+ scaleX = scale
+ scaleY = scale
+ }
- // We animate the alpha, between 50% and 100%
- alpha = lerp(
- start = 0.5f,
- stop = 1f,
- fraction = 1f - pageOffset.coerceIn(0f, 1f)
- )
- }
- .fillMaxSize()
- .padding(config.bannerImagePadding),
- shape = config.shape,
- contentScale = config.contentScale
- ) {
- Log.d(TAG, "item is :${item.javaClass}")
- onBannerClick(item)
+ // We animate the alpha, between 50% and 100%
+ alpha = lerp(
+ start = 0.5f,
+ stop = 1f,
+ fraction = 1f - pageOffset.coerceIn(0f, 1f)
+ )
+ }
+ .fillMaxSize()
+ .padding(config.bannerImagePadding),
+ shape = config.shape,
+ contentScale = config.contentScale
+ ) {
+ Log.d(TAG, "item is :${item.javaClass}")
+ onBannerClick(item)
+ }
}
- }
+ )
+
if (indicatorIsVertical) {
VerticalPagerIndicator(
pagerState = pagerState,
+ pageCount = items.size,
modifier = Modifier
.align(indicatorGravity)
.padding(16.dp),
@@ -105,6 +121,7 @@ fun BannerPager(
} else {
HorizontalPagerIndicator(
pagerState = pagerState,
+ pageCount = items.size,
modifier = Modifier
.align(indicatorGravity)
.padding(16.dp),
@@ -114,8 +131,6 @@ fun BannerPager(
}
}
-
-@ExperimentalPagerApi
@Composable
fun StartBanner(pagerState: PagerState, intervalTime: Long) {
val coroutineScope = rememberCoroutineScope()
diff --git a/banner/src/main/java/com/zj/banner/utils/PagerIndicator.kt b/banner/src/main/java/com/zj/banner/utils/PagerIndicator.kt
new file mode 100644
index 0000000..6a7d90f
--- /dev/null
+++ b/banner/src/main/java/com/zj/banner/utils/PagerIndicator.kt
@@ -0,0 +1,282 @@
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package com.zj.banner.utils
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.pager.PagerState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.ContentAlpha
+import androidx.compose.material.LocalContentAlpha
+import androidx.compose.material.LocalContentColor
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import kotlin.math.absoluteValue
+import kotlin.math.sign
+
+
+/**
+ * A horizontally laid out indicator for a [androidx.compose.foundation.pager.HorizontalPager] or
+ * [androidx.compose.foundation.pager.VerticalPager], representing
+ * the currently active page and total pages drawn using a [Shape].
+ *
+ * This element allows the setting of the [indicatorShape], which defines how the
+ * indicator is visually represented.
+ *
+ * @param pagerState A [androidx.compose.foundation.pager.PagerState] object of your
+ * [androidx.compose.foundation.pager.VerticalPager] or
+ * [androidx.compose.foundation.pager.HorizontalPager]to be used to observe the list's state.
+ * @param modifier the modifier to apply to this layout.
+ * @param pageCount the size of indicators should be displayed.
+ * If you are implementing a looping pager with a much larger [pageCount]
+ * than indicators should displayed, e.g. [Int.MAX_VALUE], specify you real size in this param.
+ * @param pageIndexMapping describe how to get the position of active indicator by the giving page
+ * from [androidx.compose.foundation.pager.PagerState.currentPage].
+ * @param activeColor the color of the active Page indicator
+ * @param inactiveColor the color of page indicators that are inactive. This defaults to
+ * [activeColor] with the alpha component set to the [ContentAlpha.disabled].
+ * @param indicatorWidth the width of each indicator in [Dp].
+ * @param indicatorHeight the height of each indicator in [Dp]. Defaults to [indicatorWidth].
+ * @param spacing the spacing between each indicator in [Dp].
+ * @param indicatorShape the shape representing each indicator. This defaults to [CircleShape].
+ */
+@Composable
+fun HorizontalPagerIndicator(
+ pagerState: PagerState,
+ pageCount: Int,
+ modifier: Modifier = Modifier,
+ pageIndexMapping: (Int) -> Int = { it },
+ activeColor: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
+ inactiveColor: Color = activeColor.copy(ContentAlpha.disabled),
+ indicatorWidth: Dp = 8.dp,
+ indicatorHeight: Dp = indicatorWidth,
+ spacing: Dp = indicatorWidth,
+ indicatorShape: Shape = CircleShape,
+) {
+ val stateBridge = remember(pagerState) {
+ object : PagerStateBridge {
+ override val currentPage: Int
+ get() = pagerState.currentPage
+ override val currentPageOffset: Float
+ get() = pagerState.currentPageOffsetFraction
+ }
+ }
+
+ HorizontalPagerIndicator(
+ pagerState = stateBridge,
+ pageCount = pageCount,
+ modifier = modifier,
+ pageIndexMapping = pageIndexMapping,
+ activeColor = activeColor,
+ inactiveColor = inactiveColor,
+ indicatorHeight = indicatorHeight,
+ indicatorWidth = indicatorWidth,
+ spacing = spacing,
+ indicatorShape = indicatorShape
+ )
+}
+
+@Composable
+private fun HorizontalPagerIndicator(
+ pagerState: PagerStateBridge,
+ pageCount: Int,
+ modifier: Modifier = Modifier,
+ pageIndexMapping: (Int) -> Int = { it },
+ activeColor: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
+ inactiveColor: Color = activeColor.copy(ContentAlpha.disabled),
+ indicatorWidth: Dp = 8.dp,
+ indicatorHeight: Dp = indicatorWidth,
+ spacing: Dp = indicatorWidth,
+ indicatorShape: Shape = CircleShape,
+) {
+
+ val indicatorWidthPx = LocalDensity.current.run { indicatorWidth.roundToPx() }
+ val spacingPx = LocalDensity.current.run { spacing.roundToPx() }
+
+ Box(
+ modifier = modifier,
+ contentAlignment = Alignment.CenterStart
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(spacing),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ val indicatorModifier = Modifier
+ .size(width = indicatorWidth, height = indicatorHeight)
+ .background(color = inactiveColor, shape = indicatorShape)
+
+ repeat(pageCount) {
+ Box(indicatorModifier)
+ }
+ }
+
+ Box(
+ Modifier
+ .offset {
+ val position = pageIndexMapping(pagerState.currentPage)
+ val offset = pagerState.currentPageOffset
+ val next = pageIndexMapping(pagerState.currentPage + offset.sign.toInt())
+ val scrollPosition = ((next - position) * offset.absoluteValue + position)
+ .coerceIn(
+ 0f,
+ (pageCount - 1)
+ .coerceAtLeast(0)
+ .toFloat()
+ )
+
+ IntOffset(
+ x = ((spacingPx + indicatorWidthPx) * scrollPosition).toInt(),
+ y = 0
+ )
+ }
+ .size(width = indicatorWidth, height = indicatorHeight)
+ .then(
+ if (pageCount > 0) Modifier.background(
+ color = activeColor,
+ shape = indicatorShape,
+ )
+ else Modifier
+ )
+ )
+ }
+}
+
+/**
+ * A vertically laid out indicator for a [androidx.compose.foundation.pager.VerticalPager] or
+ * [androidx.compose.foundation.pager.HorizontalPager], representing
+ * the currently active page and total pages drawn using a [Shape].
+ *
+ * This element allows the setting of the [indicatorShape], which defines how the
+ * indicator is visually represented.
+ *
+ * @param pagerState A [androidx.compose.foundation.pager.PagerState] object of your
+ * [androidx.compose.foundation.pager.VerticalPager] or
+ * [androidx.compose.foundation.pager.HorizontalPager]to be used to observe the list's state.
+ * @param modifier the modifier to apply to this layout.
+ * @param pageCount the size of indicators should be displayed. If you are implementing a looping
+ * pager with a much larger [pageCount] than indicators should displayed, e.g. [Int.MAX_VALUE],
+ * specify you real size in this param.
+ * @param pageIndexMapping describe how to get the position of active indicator by the giving page
+ * from [androidx.compose.foundation.pager.PagerState.currentPage].
+ * @param activeColor the color of the active Page indicator
+ * @param inactiveColor the color of page indicators that are inactive. This defaults to
+ * [activeColor] with the alpha component set to the [ContentAlpha.disabled].
+ * @param indicatorHeight the height of each indicator in [Dp].
+ * @param indicatorWidth the width of each indicator in [Dp]. Defaults to [indicatorHeight].
+ * @param spacing the spacing between each indicator in [Dp].
+ * @param indicatorShape the shape representing each indicator. This defaults to [CircleShape].
+ */
+@Composable
+fun VerticalPagerIndicator(
+ pagerState: PagerState,
+ pageCount: Int,
+ modifier: Modifier = Modifier,
+ pageIndexMapping: (Int) -> Int = { it },
+ activeColor: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
+ inactiveColor: Color = activeColor.copy(ContentAlpha.disabled),
+ indicatorHeight: Dp = 8.dp,
+ indicatorWidth: Dp = indicatorHeight,
+ spacing: Dp = indicatorHeight,
+ indicatorShape: Shape = CircleShape,
+) {
+ val stateBridge = remember(pagerState) {
+ object : PagerStateBridge {
+ override val currentPage: Int
+ get() = pagerState.currentPage
+ override val currentPageOffset: Float
+ get() = pagerState.currentPageOffsetFraction
+ }
+ }
+
+ VerticalPagerIndicator(
+ pagerState = stateBridge,
+ pageCount = pageCount,
+ modifier = modifier,
+ pageIndexMapping = pageIndexMapping,
+ activeColor = activeColor,
+ inactiveColor = inactiveColor,
+ indicatorHeight = indicatorHeight,
+ indicatorWidth = indicatorWidth,
+ spacing = spacing,
+ indicatorShape = indicatorShape
+ )
+}
+
+@Composable
+private fun VerticalPagerIndicator(
+ pagerState: PagerStateBridge,
+ pageCount: Int,
+ modifier: Modifier = Modifier,
+ pageIndexMapping: (Int) -> Int = { it },
+ activeColor: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
+ inactiveColor: Color = activeColor.copy(ContentAlpha.disabled),
+ indicatorHeight: Dp = 8.dp,
+ indicatorWidth: Dp = indicatorHeight,
+ spacing: Dp = indicatorHeight,
+ indicatorShape: Shape = CircleShape,
+) {
+
+ val indicatorHeightPx = LocalDensity.current.run { indicatorHeight.roundToPx() }
+ val spacingPx = LocalDensity.current.run { spacing.roundToPx() }
+
+ Box(
+ modifier = modifier,
+ contentAlignment = Alignment.TopCenter
+ ) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(spacing),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ val indicatorModifier = Modifier
+ .size(width = indicatorWidth, height = indicatorHeight)
+ .background(color = inactiveColor, shape = indicatorShape)
+
+ repeat(pageCount) {
+ Box(indicatorModifier)
+ }
+ }
+
+ Box(
+ Modifier
+ .offset {
+ val position = pageIndexMapping(pagerState.currentPage)
+ val offset = pagerState.currentPageOffset
+ val next = pageIndexMapping(pagerState.currentPage + offset.sign.toInt())
+ val scrollPosition = ((next - position) * offset.absoluteValue + position)
+ .coerceIn(
+ 0f,
+ (pageCount - 1)
+ .coerceAtLeast(0)
+ .toFloat()
+ )
+
+ IntOffset(
+ x = 0,
+ y = ((spacingPx + indicatorHeightPx) * scrollPosition).toInt(),
+ )
+ }
+ .size(width = indicatorWidth, height = indicatorHeight)
+ .then(
+ if (pageCount > 0) Modifier.background(
+ color = activeColor,
+ shape = indicatorShape,
+ )
+ else Modifier
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/banner/src/main/java/com/zj/banner/utils/PagerTab.kt b/banner/src/main/java/com/zj/banner/utils/PagerTab.kt
new file mode 100644
index 0000000..1522e75
--- /dev/null
+++ b/banner/src/main/java/com/zj/banner/utils/PagerTab.kt
@@ -0,0 +1,83 @@
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package com.zj.banner.utils
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.material.ScrollableTabRow
+import androidx.compose.material.TabPosition
+import androidx.compose.material.TabRow
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.lerp
+
+
+/**
+ * This indicator syncs up a [TabRow] or [ScrollableTabRow] tab indicator with a
+ * [androidx.compose.foundation.pager.HorizontalPager] or
+ * [androidx.compose.foundation.pager.VerticalPager].
+ */
+fun Modifier.pagerTabIndicatorOffset(
+ pagerState: androidx.compose.foundation.pager.PagerState,
+ tabPositions: List,
+ pageIndexMapping: (Int) -> Int = { it },
+): Modifier {
+ val stateBridge = object : PagerStateBridge {
+ override val currentPage: Int
+ get() = pagerState.currentPage
+ override val currentPageOffset: Float
+ get() = pagerState.currentPageOffsetFraction
+ }
+
+ return pagerTabIndicatorOffset(stateBridge, tabPositions, pageIndexMapping)
+}
+
+private fun Modifier.pagerTabIndicatorOffset(
+ pagerState: PagerStateBridge,
+ tabPositions: List,
+ pageIndexMapping: (Int) -> Int = { it },
+): Modifier = layout { measurable, constraints ->
+ if (tabPositions.isEmpty()) {
+ // If there are no pages, nothing to show
+ layout(constraints.maxWidth, 0) {}
+ } else {
+ val currentPage = minOf(tabPositions.lastIndex, pageIndexMapping(pagerState.currentPage))
+ val currentTab = tabPositions[currentPage]
+ val previousTab = tabPositions.getOrNull(currentPage - 1)
+ val nextTab = tabPositions.getOrNull(currentPage + 1)
+ val fraction = pagerState.currentPageOffset
+ val indicatorWidth = if (fraction > 0 && nextTab != null) {
+ lerp(currentTab.width, nextTab.width, fraction).roundToPx()
+ } else if (fraction < 0 && previousTab != null) {
+ lerp(currentTab.width, previousTab.width, -fraction).roundToPx()
+ } else {
+ currentTab.width.roundToPx()
+ }
+ val indicatorOffset = if (fraction > 0 && nextTab != null) {
+ lerp(currentTab.left, nextTab.left, fraction).roundToPx()
+ } else if (fraction < 0 && previousTab != null) {
+ lerp(currentTab.left, previousTab.left, -fraction).roundToPx()
+ } else {
+ currentTab.left.roundToPx()
+ }
+ val placeable = measurable.measure(
+ Constraints(
+ minWidth = indicatorWidth,
+ maxWidth = indicatorWidth,
+ minHeight = 0,
+ maxHeight = constraints.maxHeight
+ )
+ )
+ layout(constraints.maxWidth, maxOf(placeable.height, constraints.minHeight)) {
+ placeable.placeRelative(
+ indicatorOffset,
+ maxOf(constraints.minHeight - placeable.height, 0)
+ )
+ }
+ }
+}
+
+internal interface PagerStateBridge {
+ val currentPage: Int
+ val currentPageOffset: Float
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 2371b8b..5b52081 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,8 +2,8 @@
buildscript {
ext {
compose_version = '1.5.0-beta03'
- compose_compiler_version = '1.4.8'
- kotlin_version = '1.8.22'
+ compose_compiler_version = '1.5.0'
+ kotlin_version = '1.9.0'
}
repositories {
google()