使用方法:
mBinding.bnBanner.setOnPageChangeListener(object : OnPageChangeListener {
val viewScrollGradientHelper = BannerScrollGradientHelper(mBinding.bannerCurrentBg, mBinding.bannerWillShowBg)
override fun onPageScrollStateChanged(p0: Int) {
}
override fun onPageScrolled(position: Int, offsetPercent: Float, offsetPx: Int) {
// offsetPercent 代表当前滑动的比例,往后滑动【从0到1区间渐渐变大】,往前滑动【从1到0区间渐渐变小】
// 开始渐变滑动
viewScrollGradientHelper.startGradientByOffsetPercent(requireContext(), position, offsetPercent, offsetPx, bean.bannerBgUrlList) {
// banner滑动到某个位置的埋点
dataTrackOnBanner(bean.list, position)
}
}
override fun onPageSelected(position: Int) {
}
})
思路:
根据banner滑动监听,滑动的比例offsetPercent (【offsetPercent】0~1,往下一张滑动从0到1变大,往上一张滑动从1到0变小) 逐渐改变透明度;两张背景图交替变化为当前图控件和将要显示的控件(为了处理图片闪烁问题,透明度瞬间改变有闪烁);
资源准备:
1.banner控件:来自三方控件
// gradle中引入
api 'com.youth.banner:banner:1.4.10'
2. 布局(嵌套到你的父布局中,显示banner的位置):
<!-- banner底部背景 -->
<FrameLayout
android:id="@+id/frame_banner"
android:layout_width="match_parent"
android:layout_height="225dp"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible">
<!-- 背景图A 当前背景图 -->
<ImageView
android:id="@+id/banner_current_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="1"
android:scaleType="centerCrop" />
<!-- 背景图B 将要展示的背景图 -->
<ImageView
android:id="@+id/banner_will_show_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0"
android:scaleType="centerCrop" />
<!-- 前景图banner -->
<com.youth.banner.Banner
android:id="@+id/bn_banner"
android:layout_width="match_parent"
android:layout_height="225dp"
android:layout_gravity="center"
app:indicator_height="3dp"
app:indicator_margin="3dp"
app:indicator_width="9dp"
app:scroll_time="1600"
tools:background="@drawable/banner_placeholder"
tools:visibility="gone" />
</FrameLayout>
3. 滑动渐变帮助类:
package com.xxx.helper
import android.content.Context
import android.widget.ImageView
import com.czb.chezhubang.base.utils.GlideUtils
import kotlin.math.roundToInt
/**
* @author: lwp
* @date: 6/22/21
* @description: banner 滑动渐变帮助类
*/
class BannerScrollGradientHelper constructor(private val mBannerCurrentBg: ImageView, private val mBannerWillShowBg: ImageView) {
/**
* 上次记录的偏移量百分比(0f~1f)取值
*/
private var preOffsetPercent: Float = 0f
/**
* 上次加载最终位置图的index,用来去重同样值的回调
*/
private var preLoadFinalImagePosition = -1
/**
* 真正的当前显示的图的ImageView控件(根据当前透明度计算,透明度为1的为当前图的ImageView)
*/
private var currentImageView: ImageView? = null
/**
* 真正的将要显示的下张图的ImageView控件((根据最终停止滑动的最后一次回调的透明度计算,透明度为1的为下张显示图的ImageView)
*/
private var willShowImageView: ImageView? = null
/**
* banner滚动方向 true是下一张,false是上一张
*/
private var bannerWillScrollToNext = true
/**
* 判断banner的onScroll回调里是否设置了方向的值,用来去除重复赋值
*/
private var isJudgeScrollAndSetScrollNextValue = false
/**
* 上次滚动中加载图的位置index
*/
private var preLoadImagePositionOnScrolling = -1
/**
* @param context 上下文
* @param position 位置
* @param offsetPercent 滑动的偏移百分比(0~1之间)
* @param offsetPx 滑动偏移的像素
* @param imageUrlList 渐变的list URL
* @param onScrollFinishListener 滚动完毕回调
*/
fun startGradientByOffsetPercent(context: Context, position: Int,
offsetPercent: Float,
offsetPx: Int,
imageUrlList: MutableList<String>,
onScrollFinishListener: () -> Unit) {
// 滑动的图的数组长度
val imageUrlListSize = imageUrlList.size
if (currentImageView == null) {
currentImageView = getCurrentImageView()
}
if (willShowImageView == null) {
willShowImageView = getWillShowImageView()
}
if (offsetPercent > 0f && preOffsetPercent > 0) {
// 获取将要滑动的方向
if (!isJudgeScrollAndSetScrollNextValue) {
isJudgeScrollAndSetScrollNextValue = true
bannerWillScrollToNext = offsetPercent - preOffsetPercent > 0
}
// 判断滑动方向
if (bannerWillScrollToNext) {
if (preLoadImagePositionOnScrolling != position) {
preLoadImagePositionOnScrolling = position
/**
* 当往后一张滑动时候,position的值是当前张的值
*/
GlideUtils.loadImage(context, currentImageView, imageUrlList[position % imageUrlListSize])
// 下一个
GlideUtils.loadImage(context, willShowImageView, imageUrlList[(position + 1) % imageUrlListSize])
}
// 逐渐改变透明度,根据滑动偏移量比例
// 当前page渐渐透明
currentImageView?.alpha = 1 - offsetPercent
// 下一张page渐渐显示
willShowImageView?.alpha = offsetPercent
} else {
// preLoadImagePositionOnScrolling:去重,避免重复执行
if (preLoadImagePositionOnScrolling != position) {
preLoadImagePositionOnScrolling = position
/**
* 当往前一张滑动时候,position的值是上一张的值
*/
// 当前图
GlideUtils.loadImage(context, currentImageView, imageUrlList[(position + 1) % imageUrlListSize])
// 上一张
GlideUtils.loadImage(context, willShowImageView, imageUrlList[position % imageUrlListSize])
}
// 逐渐改变透明度,根据滑动偏移量比例
currentImageView?.alpha = offsetPercent
willShowImageView?.alpha = 1 - offsetPercent
}
}
// 记录上次的滑动百分比,大于0表示开始滑动了
preOffsetPercent = offsetPercent
// offsetPercent和offsetPx 等于0代表最后一次滑动回调;preLoadFinalImagePosition去重,避免重复执行
if (offsetPercent == 0f && offsetPx == 0 && position != preLoadFinalImagePosition) {
preLoadFinalImagePosition = position
// 最后一次滑动
onTheLastScrollCallback(context, imageUrlList, position)
// 滑动完成回调
onScrollFinishListener?.invoke()
}
}
/**
* 最后的滑动回调
*/
private fun onTheLastScrollCallback(context: Context, imageUrlList: MutableList<String>, position: Int) {
// 展示最终的图片
showFinalImage(context, imageUrlList, position)
}
/**
* 展示最终的图片
*/
private fun showFinalImage(context: Context, imageUrlList: MutableList<String>, position: Int) {
// 设置最终的透明度,四舍五入,接近0的透明度为0,接近1的透明度为1,两个控件分别对应其中的一种情况,不会同时为0或1
mBannerWillShowBg.alpha = mBannerWillShowBg.alpha.roundToInt() * 1f
mBannerCurrentBg.alpha = mBannerCurrentBg.alpha.roundToInt() * 1f
val willShowAlpha = mBannerWillShowBg.alpha
// 展示alpha为1的控件,避免自动滑动的时候闪烁
GlideUtils.loadImage(context, if (willShowAlpha == 1f) {
mBannerWillShowBg
} else {
mBannerCurrentBg
}, imageUrlList[position.let {
if (it >= imageUrlList.size) {
0
} else {
it
}
}])
// 透明度为0的设置为空图资源
if (willShowAlpha == 0f) {
mBannerWillShowBg
} else {
mBannerCurrentBg
}.setImageResource(0)
// 重置一些记录变量
resetRecordData()
}
/**
* 重置记录的数据
*/
private fun resetRecordData() {
isJudgeScrollAndSetScrollNextValue = false
preLoadImagePositionOnScrolling = -1
currentImageView = null
willShowImageView = null
}
/**
* @return 将返回透明度alpha为1的控件
*/
private fun getCurrentImageView(): ImageView {
return if (mBannerCurrentBg.alpha == 1f) {
mBannerCurrentBg
} else {
mBannerWillShowBg
}
}
/**
* @return 将返回透明度alpha为0的控件
*/
private fun getWillShowImageView(): ImageView {
return if (mBannerCurrentBg.alpha == 0f) {
mBannerCurrentBg
} else {
mBannerWillShowBg
}
}
}