其他
打造一个Compose版本的Banner效果吧
https://juejin.cn/user/3913917127985240/posts
https://github.com/android/compose-samples/tree/1630f6b35ac9e25fb3cd3a64208d7c9afaaaedc5/Jetcaster
上面是 Jetcaster 项目的地址,下面再贴下 Pager 的地址。
https://github.com/android/compose-samples/blob/1630f6b35ac9e25fb3cd3a64208d7c9afaaaedc5/Jetcaster/app/src/main/java/com/example/jetcaster/util/Pager.kt#L130
repositories {
...
maven { url 'https://jitpack.io' }
}
}
implementation 'com.github.zhujiang521:Banner:Tag'
}
override val data: Any? = null
) : BaseBannerBean()
BannerBean(
"https://wanandroid.com/blogimgs/8a0131ac-05b7-4b6c-a8d0-f438678834ba.png",
),
BannerBean(
"https://www.wanandroid.com/blogimgs/62c1bd68-b5f3-4a3c-a649-7ca8c7dfabe6.png",
),
BannerBean(
"https://www.wanandroid.com/blogimgs/50c115c2-cf6c-4802-aa7b-a4334de444cd.png",
),
BannerBean(
"https://www.wanandroid.com/blogimgs/90c6cc12-742e-4c9f-b318-b912f163b8d0.png",
),
)
BannerPager(
items = items
) { item ->
Toast.makeText(context, "item:$item", Toast.LENGTH_SHORT).show()
Log.e(TAG, "Notes: 哈哈哈:$item")
}
fun <T : BaseBannerBean> BannerPager(
items: List<T> = arrayListOf(),
repeat: Boolean = true,
config: BannerConfig = BannerConfig(),
indicator: Indicator = CircleIndicator(),
onBannerClick: (T) -> Unit
)
items:数据源,可以看到数据源使用到了泛型,这里为什么使用泛型下面再说,这里只要知道必须是继承自 BaseBannerBean 就可以了 repeat:是否允许自动轮播 config:Banner 的一些配置参数 indicator:Banner 指示器,你可以使用默认的 CircleIndicator 或 NumberIndicator ,也可以自定义,仅需要继承 Indicator 即可。 onBannerClick:Banner 的点击事件,会回调回一个泛型参数供大家使用
val mTimerTask: TimerTask = object : TimerTask() {
override fun run() {
viewModelScope.launch {
if (pagerState.currentPage == pagerState.maxPage) {
pagerState.currentPage = 0
} else {
pagerState.currentPage++
}
pagerState.fling(-1f)
}
}
}
timer?.schedule(mTimerTask, 5000, 3000)
* Banner Model 的基类
*/
abstract class BaseBannerBean {
// 图片资源 可以是:url、文件路径或者是 drawable id
abstract val data: Any?
}
state = pagerState,
modifier = Modifier.fillMaxWidth().height(config.bannerHeight)
) {
val item = items[page]
BannerCard(
bean = item,
modifier = Modifier.fillMaxSize().padding(config.bannerImagePadding),
shape = config.shape
) {
Log.d(TAG, "item is :${item.javaClass}")
onBannerClick(item)
}
}
* Banner 图片展示卡片
*
* @param bean banner Model
* @param modifier
*/
@Composable
fun <T : BaseBannerBean> BannerCard(
bean: T,
modifier: Modifier = Modifier,
shape: Shape = RoundedCornerShape(10.dp),
onBannerClick: () -> Unit,
) {
if (bean.data == null) {
throw NullPointerException("Url or imgRes or filePath must have a not for empty.")
}
Card(
shape = shape,
modifier = modifier
) {
val imgModifier = Modifier.clickable(onClick = onBannerClick)
when (bean.data) {
is String -> {
val img = bean.data as String
if (img.contains("https://") || img.contains("http://")) {
Log.d(TAG, "PostCardPopular: 加载网络图片")
CoilImage(
data = img,
contentDescription = null,
modifier = imgModifier
)
} else {
Log.d(TAG, "PostCardPopular: 加载本地图片")
val bitmap = BitmapFactory.decodeFile(img)
Image(
modifier = imgModifier,
painter = BitmapPainter(bitmap.asImageBitmap()),
contentDescription = "",
contentScale = ContentScale.Crop
)
}
}
is Int -> {
Log.d(TAG, "PostCardPopular: 加载本地资源图片")
Image(
modifier = imgModifier,
painter = painterResource(bean.data as Int),
contentDescription = "",
contentScale = ContentScale.Crop
)
}
else -> {
throw IllegalArgumentException("参数类型不符合要求,只能是:url、文件路径或者是 drawable id")
}
}
}
}
当图片资源为图片路径的时候,需要通过 BitmapFactory 先转成 Bitmap ,然后还需要把 Bitmap 通过 Bitmap 的扩展方法 asImageBitmap 转成 ImageBitmap ,这样才能在 Compose 的 Image 中使用。 网络图片的加载这里用到了一个三方加载库 coil ,具体是什么就不过多介绍了,大家可以去百度下,在 Kotlin 中还是推荐使用 coil ,当然 Glide 也支持了 Compose ,大家可以随便使用。
本地图片
BannerBean(R.drawable.banner1),
BannerBean(R.drawable.banner2),
BannerBean(R.drawable.banner3),
BannerBean(R.drawable.banner4),
)
BannerPager(
modifier = Modifier.padding(top = 10.dp),
items = items2,
indicator = CircleIndicator(gravity = BannerGravity.BottomLeft)
) { item ->
Toast.makeText(context, "item:$item", Toast.LENGTH_SHORT).show()
}
// banner 高度
var bannerHeight: Dp = 210.dp,
// banner 图片距离四周的 padding 值
var bannerImagePadding: Dp = 8.dp,
// banner 图片的 shape
var shape: Shape = RoundedCornerShape(10.dp),
// banner 切换间隔时间
var intervalTime: Long = 3000
)
items = items2,
indicator = CircleIndicator(gravity = BannerGravity.BottomLeft),
config = BannerConfig(
bannerHeight = 250.dp,
bannerImagePadding = 0.dp,
shape = RoundedCornerShape(0),
intervalTime = 1000
)
) { item ->
Toast.makeText(context, "item:$item", Toast.LENGTH_SHORT).show()
}
* 指示器基类,如果需要自定义指示器,需要继承此类,并实现 [DrawIndicator] 方法
* 别忘了在重写方法上添加 @Composable 注解
*/
abstract class Indicator {
abstract var gravity: Int
@Composable
abstract fun DrawIndicator(pagerState: PagerState)
}
* BannerGravity 设置指示器位置
*/
object BannerGravity {
// 底部居中
const val BottomCenter = 0
// 底部左边
const val BottomLeft = 2
// 底部右边
const val BottomRight = 3
}
圆形指示器
* 圆形指示器 eg:。。. 。。
*
* @param indicatorColor 指示器默认颜色
* @param selectIndicatorColor 指示器选中颜色
* @param indicatorDistance 指示器之间的距离
* @param indicatorSize 指示器默认圆大小
* @param selectIndicatorSize 指示器选中圆大小
* @param gravity 指示器位置
*/
class CircleIndicator(
var indicatorColor: Color = Color(30, 30, 33, 90),
var selectIndicatorColor: Color = Color.Green,
var indicatorDistance: Int = 50,
var indicatorSize: Float = 10f,
var selectIndicatorSize: Float = 13f,
override var gravity: Int = BottomCenter,
) : Indicator() {
@Composable
override fun DrawIndicator(pagerState: PagerState) {
for (pageIndex in 0..pagerState.maxPage) {
Canvas(modifier = Modifier.fillMaxSize()) {
val canvasWidth = size.width
val canvasHeight = size.height
val color: Color
val inSize: Float
if (pageIndex == pagerState.currentPage) {
color = selectIndicatorColor
inSize = selectIndicatorSize
} else {
color = indicatorColor
inSize = indicatorSize
}
val start = when (gravity) {
BottomCenter -> {
val width = canvasWidth - pagerState.maxPage * indicatorDistance
width / 2
}
BottomLeft -> {
100f
}
BottomRight -> {
canvasWidth - pagerState.maxPage * indicatorDistance - 100f
}
else -> 100f
}
drawCircle(
color,
inSize,
center = Offset(start + pageIndex * indicatorDistance, canvasHeight)
)
}
}
}
}
modifier = Modifier.padding(top = 10.dp),
items = items2,
indicator = CircleIndicator(gravity = BannerGravity.BottomRight)
) { item ->
Toast.makeText(context, "item:$item", Toast.LENGTH_SHORT).show()
}
数字指示器
* 数字指示器,显示数字的指示器 eg: 1/5
*
* @param backgroundColor 数字指示器背景颜色
* @param numberColor 数字的颜色
* @param circleSize 背景圆的半径
* @param fontSize 页码文字大小
* @param gravity 指示器位置
*/
class NumberIndicator(
var backgroundColor: Color = Color(30, 30, 33, 90),
var numberColor: Color = Color.White,
var circleSize: Dp = 35.dp,
var fontSize: TextUnit = 15.sp,
override var gravity: Int = BottomRight,
) : Indicator() {
@Composable
override fun DrawIndicator(pagerState: PagerState) {
val alignment: Alignment = when (gravity) {
BannerGravity.BottomCenter -> {
Alignment.BottomCenter
}
BannerGravity.BottomLeft -> {
Alignment.BottomStart
}
BottomRight -> {
Alignment.BottomEnd
}
else -> Alignment.BottomEnd
}
Box(modifier = Modifier.fillMaxSize().padding(10.dp), contentAlignment = alignment) {
Box(
modifier = Modifier.size(circleSize).clip(CircleShape)
.background(color = backgroundColor),
contentAlignment = Alignment.Center
) {
Text(
"${pagerState.currentPage + 1}/${pagerState.maxPage + 1}",
color = numberColor,
fontSize = fontSize
)
}
}
}
}
https://github.com/zhujiang521/Banner