纵向循环缓慢滚动图片
工具类:
class SrcLoopScrollFrameLayout : FrameLayout {companion object {/*** 滚动方向*/@IntDef(OUT_SLIDE_TOP, OUT_SLIDE_BOTTOM, OUT_SLIDE_LEFT, OUT_SLIDE_RIGHT)@Retention(AnnotationRetention.SOURCE)annotation class ScrollOrientation/*** 滚动方向常量* 0:往上滚出* 1:往下滚出* 2:往左滚出* 3:往右滚出*/const val OUT_SLIDE_TOP = 0const val OUT_SLIDE_BOTTOM = 1const val OUT_SLIDE_LEFT = 2const val OUT_SLIDE_RIGHT = 3/*** 重绘间隔时间*/private const val DEFAULT_DRAW_INTERVALS_TIME = 5L}/*** 间隔时间内平移距离*/private var mPanDistance = 0f/*** 间隔时间内平移增距*/private var mIntervalIncreaseDistance = 0.5f/*** 填满当前view所需bitmap个数*/private var mBitmapCount = 0/*** 是否开始滚动*/private var mIsScroll = false/*** 滚动方向,默认往上滚出*/@ScrollOrientationprivate var mScrollOrientation = OUT_SLIDE_TOP/*** 遮罩层颜色*/@ColorIntprivate var mMaskLayerColor = 0private var mDrawable: Drawable? = nullprivate var mSrcBitmap: Bitmap? = nullprivate lateinit var mPaint: Paintprivate lateinit var mMatrix: Matrixconstructor(context: Context) : super(context) {initView(context, null, 0)}constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {initView(context, attrs, 0)}constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context,attrs,defStyleAttr) {initView(context, attrs, defStyleAttr)}private fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {val array = context.theme.obtainStyledAttributes(attrs,R.styleable.SrcLoopScrollFrameLayout,defStyleAttr,0)val speed = array.getInteger(R.styleable.SrcLoopScrollFrameLayout_speed, 3)mScrollOrientation =array.getInteger(R.styleable.SrcLoopScrollFrameLayout_scrollOrientation, OUT_SLIDE_TOP)mIntervalIncreaseDistance *= speedmDrawable = array.getDrawable(R.styleable.SrcLoopScrollFrameLayout_src)mIsScroll = array.getBoolean(R.styleable.SrcLoopScrollFrameLayout_isScroll, true)mMaskLayerColor =array.getColor(R.styleable.SrcLoopScrollFrameLayout_maskLayerColor, Color.TRANSPARENT)array.recycle()setWillNotDraw(false)mPaint = Paint()mMatrix = Matrix()}override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {super.onSizeChanged(w, h, oldw, oldh)if (mDrawable == null || mDrawable !is BitmapDrawable) {return}if (visibility == GONE) {return}if (w == 0 || h == 0) {return}if (mSrcBitmap == null) {val bitmap = (mDrawable as BitmapDrawable).bitmap//调整色彩模式进行质量压缩val compressBitmap = bitmap.copy(Bitmap.Config.RGB_565, true)//缩放 BitmapmSrcBitmap = scaleBitmap(compressBitmap)//计算至少需要几个 bitmap 才能填满当前 viewwhen (mScrollOrientation) {OUT_SLIDE_TOP, OUT_SLIDE_BOTTOM -> {mBitmapCount = measuredHeight / mSrcBitmap!!.height + 1}OUT_SLIDE_LEFT, OUT_SLIDE_RIGHT -> {mBitmapCount = measuredWidth / mSrcBitmap!!.width + 1}}if (!compressBitmap.isRecycled) {compressBitmap.isRecycledSystem.gc()}}}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)if (mSrcBitmap == null) {return}val length = if (scrollOrientationIsVertical()) mSrcBitmap!!.height else mSrcBitmap!!.widthif (length + mPanDistance != 0f) {//第一张图片未完全滚出屏幕mMatrix.reset()when (mScrollOrientation) {OUT_SLIDE_TOP -> {mMatrix.postTranslate(0f, mPanDistance)}OUT_SLIDE_BOTTOM -> {mMatrix.postTranslate(0f, measuredHeight - length - mPanDistance)}OUT_SLIDE_LEFT -> {mMatrix.postTranslate(mPanDistance, 0f)}OUT_SLIDE_RIGHT -> {mMatrix.postTranslate(measuredWidth - length - mPanDistance, 0f)}}canvas.drawBitmap(mSrcBitmap!!, mMatrix, mPaint)}if (length + mPanDistance < (if (scrollOrientationIsVertical()) measuredHeight else measuredWidth)) {//用于补充留白的图片出现在屏幕for (i in 0 until mBitmapCount) {mMatrix.reset()when (mScrollOrientation) {OUT_SLIDE_TOP -> {mMatrix.postTranslate(0f, (i + 1) * length + mPanDistance)}OUT_SLIDE_BOTTOM -> {mMatrix.postTranslate(0f, measuredHeight - (i + 2) * length - mPanDistance)}OUT_SLIDE_LEFT -> {mMatrix.postTranslate((i + 1) * length + mPanDistance, 0f)}OUT_SLIDE_RIGHT -> {mMatrix.postTranslate(measuredWidth - (i + 2) * length - mPanDistance, 0f)}}canvas.drawBitmap(mSrcBitmap!!, mMatrix, mPaint)}}//绘制遮罩层if (mMaskLayerColor != Color.TRANSPARENT) {canvas.drawColor(mMaskLayerColor)}//延时重绘实现滚动效果if (mIsScroll) {handler.postDelayed(mRedrawRunnable, DEFAULT_DRAW_INTERVALS_TIME)}}/*** 重绘*/private val mRedrawRunnable = Runnable {val length = if (scrollOrientationIsVertical()) mSrcBitmap!!.height else mSrcBitmap!!.widthif (length + mPanDistance <= 0) {//第一张已完全滚出屏幕,重置平移距离mPanDistance = 0f}mPanDistance -= mIntervalIncreaseDistanceinvalidate()}/*** 设置背景图 bitmap* 通过该方法设置的背景图,当 屏幕翻转/暗黑模式切换 等涉及到 activity 重构的情况出现时,需要在 activity 重构后重新设置背景图* @srcBitmap 背景图* @isMassReduce 是否进行质量压缩(通过调整色彩模式实现,默认进行压缩)*/fun setSrcBitmap(srcBitmap: Bitmap, isMassReduce: Boolean = true) {val oldScrollStatus = mIsScrollif (oldScrollStatus) {stopScroll()}val compressBitmap: Bitmap =if (isMassReduce && srcBitmap.config != Bitmap.Config.RGB_565) {//调整色彩模式进行质量压缩srcBitmap.copy(Bitmap.Config.RGB_565, true)} else {srcBitmap}//按当前View宽度比例缩放 BitmapmSrcBitmap = scaleBitmap(compressBitmap)//计算至少需要几个 bitmap 才能填满当前 viewmBitmapCount =if (scrollOrientationIsVertical()) measuredHeight / mSrcBitmap!!.height + 1 else measuredWidth / mSrcBitmap!!.width + 1if (!srcBitmap.isRecycled) {srcBitmap.isRecycledSystem.gc()}if (!compressBitmap.isRecycled) {compressBitmap.isRecycledSystem.gc()}if (oldScrollStatus) {startScroll()}}/*** 开始滚动*/fun startScroll() {if (mSrcBitmap != null && mIsScroll) {return}mIsScroll = truehandler.postDelayed(mRedrawRunnable, DEFAULT_DRAW_INTERVALS_TIME)}/*** 停止滚动*/fun stopScroll() {if (!mIsScroll) {return}mIsScroll = falsehandler.removeCallbacks(mRedrawRunnable)}/*** 设置滚动方向*/fun setScrollOrientation(@ScrollOrientation scrollOrientation: Int) {mPanDistance = 0fmScrollOrientation = scrollOrientationif (mSrcBitmap != null) {if (mDrawable != null && mDrawable is BitmapDrawable) {val bitmap = (mDrawable as BitmapDrawable).bitmapif (!bitmap.isRecycled) {setSrcBitmap(bitmap)return}}setSrcBitmap(mSrcBitmap!!)}}/*** 切换滚动方向*/fun changeScrollOrientation() {mPanDistance = 0fif (mScrollOrientation == OUT_SLIDE_RIGHT) {mScrollOrientation = OUT_SLIDE_TOP} else {mScrollOrientation++}if (mSrcBitmap != null) {if (mDrawable != null && mDrawable is BitmapDrawable) {val bitmap = (mDrawable as BitmapDrawable).bitmapif (!bitmap.isRecycled) {setSrcBitmap(bitmap)return}}setSrcBitmap(mSrcBitmap!!)}}/*** 判断是否为竖直滚动*/private fun scrollOrientationIsVertical(): Boolean {return mScrollOrientation == OUT_SLIDE_TOP || mScrollOrientation == OUT_SLIDE_BOTTOM}/*** 缩放 Bitmap*/private fun scaleBitmap(originBitmap: Bitmap): Bitmap? {val width = originBitmap.widthval height = originBitmap.heightval newHeight: Intval newWidth: Intif (scrollOrientationIsVertical()) {newWidth = measuredWidthnewHeight = newWidth * height / width} else {newHeight = measuredHeightnewWidth = newHeight * width / height}return Bitmap.createScaledBitmap(originBitmap, newWidth, newHeight, true)}
}
styles.xml
<declare-styleable name="SrcLoopScrollFrameLayout"><attr name="src" /><attr name="maskLayerColor" /><attr name="isScroll" /><attr name="speed" /><attr name="scrollOrientation" /></declare-styleable>
使用:
<SrcLoopScrollFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/white"app:layout_constraintTop_toTopOf="parent"app:scrollOrientation="toTop"app:src="@drawable/img_gui_bg">//正常布局代码块</SrcLoopScrollFrameLayout>