Skip to content

Instantly share code, notes, and snippets.

@DanteAndroid
Last active January 26, 2021 11:31
Show Gist options
  • Save DanteAndroid/d9e9f05c5555a54fef9acce00989e73e to your computer and use it in GitHub Desktop.
Save DanteAndroid/d9e9f05c5555a54fef9acce00989e73e to your computer and use it in GitHub Desktop.
ConstrainLayout that has ability to infinitely scroll background image vertically or horizontally
import android.animation.ValueAnimator
import android.content.Context
import android.util.AttributeSet
import android.view.animation.LinearInterpolator
import android.widget.ImageView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.res.ResourcesCompat
/**
* 支持横竖方向无限滚动背景图的 ConstraintLayout
* @author Dante
* 2020/12/22
*/
class InfiniteBackgroundLayout @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
private var backgroundRes: Int = 0
// 单位s
private var scrollDuration: Int = 0
private var orientation: Int = 0
private lateinit var imageStart: ImageView
private lateinit var imageEnd: ImageView
private var animator: ValueAnimator? = null
private val isScrollVertical get() = orientation == 0
init {
context.obtainStyledAttributes(attrs, R.styleable.InfiniteBackgroundLayout, defStyleAttr, 0).apply {
backgroundRes =
getResourceId(R.styleable.InfiniteBackgroundLayout_backgroundResource, 0)
scrollDuration =
getInteger(R.styleable.InfiniteBackgroundLayout_scrollDuration, 0)
orientation = getInteger(R.styleable.InfiniteBackgroundLayout_scrollOrientation, 0)
}.recycle()
if (scrollDuration == 0) {
scrollDuration = if (isScrollVertical) {
UIUtils.getScreenHeight() / 74
} else {
UIUtils.getScreenWidth() / 74
}
}
val drawable = ResourcesCompat.getDrawable(resources, backgroundRes, null)
if (drawable != null) {
imageStart = ImageView(context, attrs, defStyleAttr).apply {
id = generateViewId()
scaleType = ImageView.ScaleType.CENTER_CROP
setImageDrawable(drawable)
}
imageEnd = ImageView(context, attrs, defStyleAttr).apply {
id = generateViewId()
scaleType = ImageView.ScaleType.CENTER_CROP
setImageDrawable(drawable)
}
initView(drawable.intrinsicWidth, drawable.intrinsicHeight)
}
}
private fun initView(width: Int, height: Int) {
addView(imageStart, LayoutParams(width, height))
addView(imageEnd, LayoutParams(width, height))
ConstraintSet().also {
it.clone(this)
if (isScrollVertical) {
it.connect(
imageStart.id,
ConstraintSet.TOP,
ConstraintSet.PARENT_ID,
ConstraintSet.TOP
)
it.constrainWidth(imageStart.id, LayoutParams.MATCH_PARENT)
// it.constrainHeight(imageStart.id, LayoutParams.WRAP_CONTENT)
it.connect(imageEnd.id, ConstraintSet.TOP, imageStart.id, ConstraintSet.BOTTOM)
it.constrainWidth(imageEnd.id, LayoutParams.MATCH_PARENT)
// it.constrainHeight(imageEnd.id, LayoutParams.WRAP_CONTENT)
} else {
it.connect(
imageStart.id,
ConstraintSet.START,
ConstraintSet.PARENT_ID,
ConstraintSet.START
)
// it.constrainWidth(imageStart.id, LayoutParams.WRAP_CONTENT)
it.constrainHeight(imageStart.id, LayoutParams.MATCH_PARENT)
it.connect(imageEnd.id, ConstraintSet.START, imageStart.id, ConstraintSet.END)
// it.constrainWidth(imageEnd.id, LayoutParams.WRAP_CONTENT)
it.constrainHeight(imageEnd.id, LayoutParams.MATCH_PARENT)
}
it.applyTo(this)
}
}
override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
super.onWindowFocusChanged(hasWindowFocus)
if (!this::imageStart.isInitialized) return
animateBackground(hasWindowFocus)
}
private fun animateBackground(start: Boolean) {
if (start) {
if (animator == null) {
imageStart.post {
val translation =
if (isScrollVertical) imageStart.measuredHeight.toFloat() else imageStart.measuredWidth.toFloat()
animator = ValueAnimator.ofFloat(0f, -translation)
.setDuration(scrollDuration * 1000L)
animator!!.interpolator = LinearInterpolator()
animator!!.repeatMode = ValueAnimator.RESTART
animator!!.repeatCount = ValueAnimator.INFINITE
animator!!.addUpdateListener {
if (isScrollVertical) {
imageStart.translationY = it.animatedValue as Float
imageEnd.translationY = it.animatedValue as Float
} else {
imageStart.translationX = it.animatedValue as Float
imageEnd.translationX = it.animatedValue as Float
}
}
animator!!.start()
}
} else {
animator!!.resume()
}
} else {
animator?.pause()
}
}
fun getScreenWidth(): Int {
return resources.displayMetrics.widthPixels
}
fun getScreenHeight(): Int {
return resources.displayMetrics.heightPixels
}
}
@DanteAndroid
Copy link
Author

Add following to attrs.xml before use:

    <declare-styleable name="InfiniteBackgroundLayout">
        <attr name="backgroundResource" format="reference" />
        <attr name="scrollDuration" format="integer" />
        <attr name="scrollOrientation" format="enum">
            <enum name="vertical" value="0"/>
            <enum name="horizontal" value="1"/>
        </attr>
    </declare-styleable>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment