Last active
September 21, 2020 18:42
-
-
Save naman14/c047a04dbce6781c2018bc2c341f43b0 to your computer and use it in GitHub Desktop.
BharatPe native loading views and animation in Android and Web
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
web implementation (pure css) - https://codepen.io/naman14/pen/WNwYyyy