Skip to content

Instantly share code, notes, and snippets.

@naman14
Last active September 21, 2020 18:42
Show Gist options
  • Save naman14/c047a04dbce6781c2018bc2c341f43b0 to your computer and use it in GitHub Desktop.
Save naman14/c047a04dbce6781c2018bc2c341f43b0 to your computer and use it in GitHub Desktop.
BharatPe native loading views and animation in Android and Web
package com.bharatpe.nativeloader
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import android.view.animation.DecelerateInterpolator
import android.view.animation.LinearInterpolator
class BPLoadingView : View {
private var loadingPaint: Paint? = null
private var logoPaint: Paint? = null
private var bounds: RectF? = null
private var size = 0
private var indeterminateSweep = 0f
private var indeterminateRotateOffset = 0f
private var startAngle = 90f
private var startAngleRotate: ValueAnimator? = null
private var progressAnimator: ValueAnimator? = null
private var indeterminateAnimator: AnimatorSet? = null
private var initialDraw = true
private val thickness = 25
private val animSteps = 2
private val animDuration = 2000
constructor(context: Context?) : super(context) {
init(null, 0)
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
init(attrs, 0)
}
constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) {
init(attrs, defStyle)
}
private fun init(attrs: AttributeSet?, defStyle: Int) {
loadingPaint = Paint(Paint.ANTI_ALIAS_FLAG)
logoPaint = Paint(Paint.ANTI_ALIAS_FLAG)
updatePaint()
bounds = RectF()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val xPad = paddingLeft + paddingRight
val yPad = paddingTop + paddingBottom
val width = measuredWidth - xPad
val height = measuredHeight - yPad
size = if (width < height) width else height
setMeasuredDimension(size + xPad, size + yPad)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
size = if (w < h) w else h
updateBounds()
}
private fun updateBounds() {
val paddingLeft = paddingLeft
val paddingTop = paddingTop
bounds!![paddingLeft + thickness.toFloat(),
paddingTop + thickness.toFloat(), size - paddingLeft - thickness.toFloat()] =
size - paddingTop - thickness.toFloat()
}
private fun updatePaint() {
loadingPaint!!.color = Color.parseColor("#a5ebfa")
loadingPaint!!.style = Paint.Style.STROKE
loadingPaint!!.strokeWidth = thickness - 2.toFloat()
loadingPaint!!.strokeCap = Paint.Cap.ROUND
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// logo will only be drawn once in initial draw
drawBPLogo(canvas)
// draw the indeterminate progress bar
canvas.drawArc(
bounds!!,
startAngle + indeterminateRotateOffset,
indeterminateSweep,
false,
loadingPaint!!
)
}
private fun drawBPLogo(canvas: Canvas) {
if (!initialDraw) return
// draw outer circle in the logo
logoPaint!!.style = Paint.Style.STROKE
logoPaint!!.strokeWidth = thickness.toFloat()
logoPaint!!.color = Color.parseColor("#4cabc5")
canvas.drawCircle(
bounds!!.centerX(),
bounds!!.centerY(),
(bounds!!.right - bounds!!.left) / 2,
logoPaint!!
)
val logoFlagWidth = bounds!!.right - bounds!!.left - 2 * thickness - 50
val logoFlagHeight = 30f
val logoFlagDividerHeight = 30f
var logoStartX = (bounds!!.right - bounds!!.left) / 2 - logoFlagWidth / 2 + thickness
var logoStartY = (bounds!!.bottom - bounds!!.top) / 2 - logoFlagHeight + thickness - 5
// draw the top flag bar of the logo
var path = Path()
path.moveTo(logoStartX, logoStartY)
path.lineTo(logoStartX, logoStartY + logoFlagHeight)
path.lineTo(logoStartX + logoFlagWidth, logoStartY + logoFlagHeight / 2)
path.lineTo(logoStartX + logoFlagWidth, logoStartY - logoFlagHeight / 2)
path.lineTo(logoStartX, logoStartY)
logoPaint!!.style = Paint.Style.FILL
logoPaint!!.color = Color.parseColor("#DC7464")
canvas.drawPath(path, logoPaint!!)
logoStartX = (bounds!!.right - bounds!!.left) / 2 - logoFlagWidth / 2 + thickness
logoStartY =
(bounds!!.bottom - bounds!!.top) / 2 - logoFlagHeight + logoFlagHeight + logoFlagDividerHeight + thickness - 5
// draw the bottom flag bar of the logo
path = Path()
path.moveTo(logoStartX, logoStartY)
path.lineTo(logoStartX, logoStartY + logoFlagHeight)
path.lineTo(logoStartX + logoFlagWidth, logoStartY + logoFlagHeight / 2)
path.lineTo(logoStartX + logoFlagWidth, logoStartY - logoFlagHeight / 2)
path.lineTo(logoStartX, logoStartY)
logoPaint!!.style = Paint.Style.FILL
logoPaint!!.color = Color.parseColor("#2f6f6e")
canvas.drawPath(path, logoPaint!!)
}
fun startAnimation() {
resetAnimation()
}
fun resetAnimation() {
initialDraw = true
if (startAngleRotate != null && startAngleRotate!!.isRunning) startAngleRotate!!.cancel()
if (progressAnimator != null && progressAnimator!!.isRunning) progressAnimator!!.cancel()
if (indeterminateAnimator != null && indeterminateAnimator!!.isRunning) indeterminateAnimator!!.cancel()
indeterminateSweep = INDETERMINANT_MIN_SWEEP
indeterminateAnimator = AnimatorSet()
var prevSet: AnimatorSet? = null
var nextSet: AnimatorSet
for (k in 0 until animSteps) {
nextSet = createIndeterminateAnimator(k.toFloat())
val builder = indeterminateAnimator!!.play(nextSet)
if (prevSet != null) builder.after(prevSet)
prevSet = nextSet
}
indeterminateAnimator!!.addListener(object : AnimatorListenerAdapter() {
var wasCancelled = false
override fun onAnimationCancel(animation: Animator) {
wasCancelled = true
}
override fun onAnimationEnd(animation: Animator) {
if (!wasCancelled) resetAnimation()
}
})
indeterminateAnimator!!.start()
}
fun stopAnimation() {
initialDraw = true
if (startAngleRotate != null) {
startAngleRotate!!.cancel()
startAngleRotate = null
}
if (progressAnimator != null) {
progressAnimator!!.cancel()
progressAnimator = null
}
if (indeterminateAnimator != null) {
indeterminateAnimator!!.cancel()
indeterminateAnimator = null
}
}
private fun createIndeterminateAnimator(step: Float): AnimatorSet {
val maxSweep = 360f * (animSteps - 1) / animSteps + INDETERMINANT_MIN_SWEEP
val start = -90f + step * (maxSweep - INDETERMINANT_MIN_SWEEP)
// Extending the front of the arc
val frontEndExtend = ValueAnimator.ofFloat(INDETERMINANT_MIN_SWEEP, maxSweep)
frontEndExtend.duration = animDuration / animSteps / 2.toLong()
frontEndExtend.interpolator = DecelerateInterpolator(1F)
frontEndExtend.addUpdateListener { animation ->
indeterminateSweep = animation.animatedValue as Float
invalidate()
}
// Overall rotation
val rotateAnimator1 =
ValueAnimator.ofFloat(step * 720f / animSteps, (step + .5f) * 720f / animSteps)
rotateAnimator1.duration = animDuration / animSteps / 2.toLong()
rotateAnimator1.interpolator = LinearInterpolator()
rotateAnimator1.addUpdateListener { animation ->
indeterminateRotateOffset = animation.animatedValue as Float
}
// Retracting the back end of the arc
val backEndRetract =
ValueAnimator.ofFloat(start, start + maxSweep - INDETERMINANT_MIN_SWEEP)
backEndRetract.duration = animDuration / animSteps / 2.toLong()
backEndRetract.interpolator = DecelerateInterpolator(1F)
backEndRetract.addUpdateListener { animation ->
startAngle = animation.animatedValue as Float
indeterminateSweep = maxSweep - startAngle + start
invalidate()
}
// More overall rotation
val rotateAnimator2 =
ValueAnimator.ofFloat((step + .5f) * 720f / animSteps, (step + 1) * 720f / animSteps)
rotateAnimator2.duration = animDuration / animSteps / 2.toLong()
rotateAnimator2.interpolator = LinearInterpolator()
rotateAnimator2.addUpdateListener { animation ->
indeterminateRotateOffset = animation.animatedValue as Float
}
val set = AnimatorSet()
set.play(frontEndExtend).with(rotateAnimator1)
set.play(backEndRetract).with(rotateAnimator2).after(rotateAnimator1)
return set
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
startAnimation()
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
stopAnimation()
}
override fun setVisibility(visibility: Int) {
val currentVisibility = getVisibility()
super.setVisibility(visibility)
if (visibility != currentVisibility) {
if (visibility == VISIBLE) {
resetAnimation()
} else if (visibility == GONE || visibility == INVISIBLE) {
stopAnimation()
}
}
}
companion object {
private const val INDETERMINANT_MIN_SWEEP = 15f
}
}
@naman14
Copy link
Author

naman14 commented Sep 21, 2020

web implementation (pure css) - https://codepen.io/naman14/pen/WNwYyyy

bploadingview

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