library code migrated to our project

This commit is contained in:
MohitMali 2022-05-14 12:24:13 +05:30 committed by Kelson
parent 9f5c58eb64
commit 24a036c3d3

View File

@ -42,6 +42,13 @@ import kotlin.math.abs
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
/**
* Author DavidPacioianu
* Reference From
* https://github.com/DavidPacioianu/InkPageIndicator
* We refactor this java file to kotlin file
*/
class CustomPageIndicator @JvmOverloads constructor( class CustomPageIndicator @JvmOverloads constructor(
context: Context, context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
@ -161,7 +168,7 @@ class CustomPageIndicator @JvmOverloads constructor(
val top = paddingTop val top = paddingTop
val right = width - paddingRight val right = width - paddingRight
val bottom = height - paddingBottom val bottom = height - paddingBottom
val requiredWidth = requiredWidth val requiredWidth = getRequiredWidth()
val startLeft = left + (right - left - requiredWidth) / 2 + dotRadius val startLeft = left + (right - left - requiredWidth) / 2 + dotRadius
dotCenterX = FloatArray(pageCount) dotCenterX = FloatArray(pageCount)
for (i in 0 until pageCount) { for (i in 0 until pageCount) {
@ -180,10 +187,10 @@ class CustomPageIndicator @JvmOverloads constructor(
} else { } else {
0 0
} }
if (dotCenterX != null && dotCenterX!!.isNotEmpty() && if (dotCenterX != null && dotCenterX!!.isNotEmpty()
(moveAnimation == null || !moveAnimation!!.isStarted)
) { ) {
selectedDotX = dotCenterX!![currentPage] if (moveAnimation == null || !moveAnimation!!.isStarted)
selectedDotX = dotCenterX!![currentPage]
} }
} }
@ -198,13 +205,13 @@ class CustomPageIndicator @JvmOverloads constructor(
} }
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val desiredHeight = desiredHeight val desiredHeight = getDesiredHeight()
val height: Int = when (MeasureSpec.getMode(heightMeasureSpec)) { val height: Int = when (MeasureSpec.getMode(heightMeasureSpec)) {
MeasureSpec.EXACTLY -> MeasureSpec.getSize(heightMeasureSpec) MeasureSpec.EXACTLY -> MeasureSpec.getSize(heightMeasureSpec)
MeasureSpec.AT_MOST -> min(desiredHeight, MeasureSpec.getSize(heightMeasureSpec)) MeasureSpec.AT_MOST -> min(desiredHeight, MeasureSpec.getSize(heightMeasureSpec))
else -> desiredHeight else -> desiredHeight
} }
val desiredWidth = desiredWidth val desiredWidth = getDesiredWidth()
val width: Int = when (MeasureSpec.getMode(widthMeasureSpec)) { val width: Int = when (MeasureSpec.getMode(widthMeasureSpec)) {
MeasureSpec.EXACTLY -> MeasureSpec.getSize(widthMeasureSpec) MeasureSpec.EXACTLY -> MeasureSpec.getSize(widthMeasureSpec)
MeasureSpec.AT_MOST -> min(desiredWidth, MeasureSpec.getSize(widthMeasureSpec)) MeasureSpec.AT_MOST -> min(desiredWidth, MeasureSpec.getSize(widthMeasureSpec))
@ -214,12 +221,14 @@ class CustomPageIndicator @JvmOverloads constructor(
calculateDotPositions(width, height) calculateDotPositions(width, height)
} }
private val desiredHeight: Int private fun getDesiredHeight(): Int =
get() = paddingTop + dotDiameter + paddingBottom paddingTop + dotDiameter + paddingBottom
private val requiredWidth: Int
get() = pageCount * dotDiameter + (pageCount - 1) * gap private fun getRequiredWidth(): Int =
private val desiredWidth: Int pageCount * dotDiameter + (pageCount - 1) * gap
get() = paddingLeft + requiredWidth + paddingRight
private fun getDesiredWidth(): Int =
paddingLeft + getRequiredWidth() + paddingRight
override fun onViewAttachedToWindow(view: View) { override fun onViewAttachedToWindow(view: View) {
attachedToWindow = true attachedToWindow = true
@ -267,95 +276,27 @@ class CustomPageIndicator @JvmOverloads constructor(
dotRevealFraction: Float dotRevealFraction: Float
): Path { ): Path {
unselectedDotPath.rewind() unselectedDotPath.rewind()
if ((joiningFraction == 0f || joiningFraction == INVALID_FRACTION) && if ((joiningFraction == selectedFactor || joiningFraction == INVALID_FRACTION)
dotRevealFraction == 0f && !(page == currentPage && selectedDotInPosition)
) { ) {
if (dotRevealFraction == selectedFactor && !(page == currentPage && selectedDotInPosition)) {
// case #1 At rest // case #1 At rest
unselectedDotPath.addCircle(dotCenterX!![page], dotCenterY, dotRadius, Path.Direction.CW) unselectedDotPath.addCircle(dotCenterX!![page], dotCenterY, dotRadius, Path.Direction.CW)
}
} }
if (joiningFraction > 0f && joiningFraction <= 0.5f && retreatingJoinX1 == INVALID_FRACTION) { unselectedDotRightPath(centerX, joiningFraction, nextCenterX)
calculateDotRightPath(centerX, nextCenterX, joiningFraction) if (joiningFraction > smallUnSelectedFactor &&
} joiningFraction < unselectedFactor &&
if (joiningFraction > 0.5f && joiningFraction < 1f && retreatingJoinX1 == INVALID_FRACTION) { retreatingJoinX1 == INVALID_FRACTION
) {
// case #3 Joining neighbour, combined curved joinNeighbour(joiningFraction, centerX, nextCenterX)
// adjust the fraction so that it goes from 0.3 -> 1 to produce a more realistic 'join'
val adjustedFraction = (joiningFraction - 0.2f) * 1.25f
// start in the bottom left
unselectedDotPath.moveTo(centerX, dotBottomY)
// semi-circle to the top left
rectF[centerX - dotRadius, dotTopY, centerX + dotRadius] = dotBottomY
unselectedDotPath.arcTo(rectF, 90f, 180f, true)
// bezier to the middle top of the join
endX1 = centerX + dotRadius + gap / 2
endY1 = dotCenterY - adjustedFraction * dotRadius
controlX1 = endX1 - adjustedFraction * dotRadius
controlY1 = dotTopY
controlX2 = endX1 - (1 - adjustedFraction) * dotRadius
controlY2 = endY1
unselectedDotPath.cubicTo(
controlX1, controlY1,
controlX2, controlY2,
endX1, endY1
)
// bezier to the top right of the join
endX2 = nextCenterX
endY2 = dotTopY
controlX1 = endX1 + (1 - adjustedFraction) * dotRadius
controlY1 = endY1
controlX2 = endX1 + adjustedFraction * dotRadius
controlY2 = dotTopY
unselectedDotPath.cubicTo(
controlX1, controlY1,
controlX2, controlY2,
endX2, endY2
)
// semi-circle to the bottom right
rectF[nextCenterX - dotRadius, dotTopY, nextCenterX + dotRadius] = dotBottomY
unselectedDotPath.arcTo(rectF, 270f, 180f, true)
// bezier to the middle bottom of the join
// endX1 stays the same
endY1 = dotCenterY + adjustedFraction * dotRadius
controlX1 = endX1 + adjustedFraction * dotRadius
controlY1 = dotBottomY
controlX2 = endX1 + (1 - adjustedFraction) * dotRadius
controlY2 = endY1
unselectedDotPath.cubicTo(
controlX1, controlY1,
controlX2, controlY2,
endX1, endY1
)
// bezier back to the start point in the bottom left
endX2 = centerX
endY2 = dotBottomY
controlX1 = endX1 - (1 - adjustedFraction) * dotRadius
controlY1 = endY1
controlX2 = endX1 - adjustedFraction * dotRadius
controlY2 = endY2
unselectedDotPath.cubicTo(
controlX1, controlY1,
controlX2, controlY2,
endX2, endY2
)
} }
if (joiningFraction == 1f && retreatingJoinX1 == INVALID_FRACTION) { if (joiningFraction == 1f && retreatingJoinX1 == INVALID_FRACTION) {
// case #4 Joining neighbour, combined straight technically we could use case 3 for this // case #4 Joining neighbour, combined straight technically we could use case 3 for this
// situation as well but assume that this is an optimization rather than faffing around // situation as well but assume that this is an optimization rather than faffing around
// with beziers just to draw a rounded rect // with beziers just to draw a rounded rect
rectF[centerX - dotRadius, dotTopY, nextCenterX + dotRadius] = dotBottomY rectF[centerX - dotRadius, dotTopY, nextCenterX + dotRadius] = dotBottomY
unselectedDotPath.addRoundRect(rectF, dotRadius, dotRadius, Path.Direction.CW) unselectedDotPath.addRoundRect(rectF, dotRadius, dotRadius, Path.Direction.CW)
} }
// case #5 is handled by #getRetreatingJoinPath() // case #5 is handled by #getRetreatingJoinPath()
// this is done separately so that we can have a single retreating path spanning // this is done separately so that we can have a single retreating path spanning
// multiple dots and therefore animate it's movement smoothly // multiple dots and therefore animate it's movement smoothly
@ -370,86 +311,137 @@ class CustomPageIndicator @JvmOverloads constructor(
return unselectedDotPath return unselectedDotPath
} }
private fun calculateDotRightPath( private fun joinNeighbour(
joiningFraction: Float,
centerX: Float, centerX: Float,
nextCenterX: Float, nextCenterX: Float
joiningFraction: Float
) { ) {
// case #2 Joining neighbour, still separate // case #3 Joining neighbour, combined curved
// adjust the fraction so that it goes from 0.3 -> 1 to produce a more realistic 'join'
// start with the left dot val adjustedFraction =
unselectedDotLeftPath.rewind() (joiningFraction - zeroPointTwoFractionConst) * onePointTwoFiveFractionConst
// start in the bottom left
// start at the bottom center unselectedDotPath.moveTo(centerX, dotBottomY)
unselectedDotLeftPath.moveTo(centerX, dotBottomY) // semi-circle to the top left
// semi circle to the top center
rectF[centerX - dotRadius, dotTopY, centerX + dotRadius] = dotBottomY rectF[centerX - dotRadius, dotTopY, centerX + dotRadius] = dotBottomY
unselectedDotLeftPath.arcTo(rectF, 90f, 180f, true) unselectedDotPath.arcTo(rectF, startAngle, sweepAngle, true)
// bezier to the middle top of the join
// cubic to the right middle endX1 = centerX + dotRadius + gap / 2
endX1 = centerX + dotRadius + joiningFraction * gap endY1 = dotCenterY - adjustedFraction * dotRadius
endY1 = dotCenterY controlX1 = endX1 - adjustedFraction * dotRadius
controlX1 = centerX + halfDotRadius
controlY1 = dotTopY controlY1 = dotTopY
controlX2 = endX1 controlX2 = endX1 - (1 - adjustedFraction) * dotRadius
controlY2 = endY1 - halfDotRadius controlY2 = endY1
unselectedDotLeftPath.cubicTo( unselectedDotPath.cubicTo(
controlX1, controlY1, controlX1, controlY1, controlX2, controlY2, endX1, endY1
controlX2, controlY2,
endX1, endY1
) )
// bezier to the top right of the join
// cubic back to the bottom center endX2 = nextCenterX
endY2 = dotTopY
controlX1 = endX1 + (1 - adjustedFraction) * dotRadius
controlY1 = endY1
controlX2 = endX1 + adjustedFraction * dotRadius
controlY2 = dotTopY
unselectedDotPath.cubicTo(
controlX1, controlY1, controlX2, controlY2, endX2, endY2
)
// semi-circle to the bottom right
rectF[nextCenterX - dotRadius, dotTopY, nextCenterX + dotRadius] = dotBottomY
unselectedDotPath.arcTo(
rectF,
startAngle + sweepAngle,
sweepAngle,
true
)
// bezier to the middle bottom of the join
// endX1 stays the same
endY1 = dotCenterY + adjustedFraction * dotRadius
controlX1 = endX1 + adjustedFraction * dotRadius
controlY1 = dotBottomY
controlX2 = endX1 + (1 - adjustedFraction) * dotRadius
controlY2 = endY1
unselectedDotPath.cubicTo(
controlX1, controlY1, controlX2, controlY2, endX1, endY1
)
// bezier back to the start point in the bottom left
endX2 = centerX endX2 = centerX
endY2 = dotBottomY endY2 = dotBottomY
controlX1 = endX1 controlX1 = endX1 - (1 - adjustedFraction) * dotRadius
controlY1 = endY1 + halfDotRadius controlY1 = endY1
controlX2 = centerX + halfDotRadius controlX2 = endX1 - adjustedFraction * dotRadius
controlY2 = dotBottomY controlY2 = endY2
unselectedDotLeftPath.cubicTo( unselectedDotPath.cubicTo(
controlX1, controlY1, controlX1, controlY1, controlX2, controlY2, endX2, endY2
controlX2, controlY2,
endX2, endY2
) )
unselectedDotPath.addPath(unselectedDotLeftPath) }
// now do the next dot to the right private fun unselectedDotRightPath(
unselectedDotRightPath.rewind() centerX: Float,
joiningFraction: Float,
// start at the bottom center nextCenterX: Float
unselectedDotRightPath.moveTo(nextCenterX, dotBottomY) ) {
if (joiningFraction > selectedFactor &&
// semi circle to the top center joiningFraction <= smallUnSelectedFactor &&
rectF[nextCenterX - dotRadius, dotTopY, nextCenterX + dotRadius] = dotBottomY retreatingJoinX1 == INVALID_FRACTION
unselectedDotRightPath.arcTo(rectF, 90f, -180f, true) ) {
// case #2 Joining neighbour, still separate
// cubic to the left middle // start with the left dot
endX1 = nextCenterX - dotRadius - joiningFraction * gap unselectedDotLeftPath.rewind()
endY1 = dotCenterY // start at the bottom center
controlX1 = nextCenterX - halfDotRadius unselectedDotLeftPath.moveTo(centerX, dotBottomY)
controlY1 = dotTopY // semi circle to the top center
controlX2 = endX1 rectF[centerX - dotRadius, dotTopY, centerX + dotRadius] = dotBottomY
controlY2 = endY1 - halfDotRadius unselectedDotLeftPath.arcTo(rectF, startAngle, sweepAngle, true)
unselectedDotRightPath.cubicTo( // cubic to the right middle
controlX1, controlY1, endX1 = centerX + dotRadius + joiningFraction * gap
controlX2, controlY2, endY1 = dotCenterY
endX1, endY1 controlX1 = centerX + halfDotRadius
) controlY1 = dotTopY
controlX2 = endX1
// cubic back to the bottom center controlY2 = endY1 - halfDotRadius
endX2 = nextCenterX unselectedDotLeftPath.cubicTo(
endY2 = dotBottomY controlX1, controlY1, controlX2, controlY2, endX1, endY1
controlX1 = endX1 )
controlY1 = endY1 + halfDotRadius // cubic back to the bottom center
controlX2 = endX2 - halfDotRadius endX2 = centerX
controlY2 = dotBottomY endY2 = dotBottomY
unselectedDotRightPath.cubicTo( controlX1 = endX1
controlX1, controlY1, controlY1 = endY1 + halfDotRadius
controlX2, controlY2, controlX2 = centerX + halfDotRadius
endX2, endY2 controlY2 = dotBottomY
) unselectedDotLeftPath.cubicTo(
unselectedDotPath.addPath(unselectedDotRightPath) controlX1, controlY1, controlX2, controlY2, endX2, endY2
)
unselectedDotPath.addPath(unselectedDotLeftPath)
// now do the next dot to the right
unselectedDotRightPath.rewind()
// start at the bottom center
unselectedDotRightPath.moveTo(nextCenterX, dotBottomY)
// semi circle to the top center
rectF[nextCenterX - dotRadius, dotTopY, nextCenterX + dotRadius] = dotBottomY
unselectedDotRightPath.arcTo(rectF, startAngle, negativeSweepAngle, true)
// cubic to the left middle
endX1 = nextCenterX - dotRadius - joiningFraction * gap
endY1 = dotCenterY
controlX1 = nextCenterX - halfDotRadius
controlY1 = dotTopY
controlX2 = endX1
controlY2 = endY1 - halfDotRadius
unselectedDotRightPath.cubicTo(
controlX1, controlY1, controlX2, controlY2, endX1, endY1
)
// cubic back to the bottom center
endX2 = nextCenterX
endY2 = dotBottomY
controlX1 = endX1
controlY1 = endY1 + halfDotRadius
controlX2 = endX2 - halfDotRadius
controlY2 = dotBottomY
unselectedDotRightPath.cubicTo(
controlX1, controlY1, controlX2, controlY2, endX2, endY2
)
unselectedDotPath.addPath(unselectedDotRightPath)
}
} }
private val retreatingJoinPath: Path private val retreatingJoinPath: Path
@ -504,9 +496,9 @@ class CustomPageIndicator @JvmOverloads constructor(
retreatAnimation = PendingRetreatAnimator( retreatAnimation = PendingRetreatAnimator(
was, now, steps, was, now, steps,
if (now > was) if (now > was)
RightwardStartPredicate(moveTo - (moveTo - selectedDotX) * 0.25f) RightwardStartPredicate(moveTo - (moveTo - selectedDotX) * thresholdMultiplier)
else LeftwardStartPredicate( else LeftwardStartPredicate(
moveTo + (selectedDotX - moveTo) * 0.25f moveTo + (selectedDotX - moveTo) * thresholdMultiplier
) )
) )
retreatAnimation!!.addListener(object : AnimatorListenerAdapter() { retreatAnimation!!.addListener(object : AnimatorListenerAdapter() {
@ -535,8 +527,8 @@ class CustomPageIndicator @JvmOverloads constructor(
}) })
// slightly delay the start to give the joins a chance to run // slightly delay the start to give the joins a chance to run
// unless dot isn't in position yet then don't delay! // unless dot isn't in position yet then don't delay!
moveSelected.startDelay = if (selectedDotInPosition) animDuration / 4L else 0L moveSelected.startDelay = if (selectedDotInPosition) animDuration / fourLong else zeroLong
moveSelected.duration = animDuration * 3L / 4L moveSelected.duration = animDuration * threeLong / fourLong
moveSelected.interpolator = interpolator moveSelected.interpolator = interpolator
return moveSelected return moveSelected
} }
@ -728,8 +720,8 @@ class CustomPageIndicator @JvmOverloads constructor(
var currentPage = 0 var currentPage = 0
constructor(superState: Parcelable?) : super(superState) constructor(superState: Parcelable?) : super(superState)
private constructor(`in`: Parcel) : super(`in`) { private constructor(data: Parcel) : super(data) {
currentPage = `in`.readInt() currentPage = data.readInt()
} }
override fun writeToParcel(dest: Parcel, flags: Int) { override fun writeToParcel(dest: Parcel, flags: Int) {
@ -758,6 +750,18 @@ class CustomPageIndicator @JvmOverloads constructor(
// constants // constants
private const val INVALID_FRACTION = -1f private const val INVALID_FRACTION = -1f
private const val MINIMAL_REVEAL = 0.00001f private const val MINIMAL_REVEAL = 0.00001f
private const val zeroPointTwoFractionConst: Float = 0.2f
private const val onePointTwoFiveFractionConst: Float = 1.25f
private const val unselectedFactor: Float = 1f
private const val smallUnSelectedFactor: Float = 0.5f
private const val selectedFactor: Float = 0f
private const val threeLong: Long = 3L
private const val zeroLong: Long = 0L
private const val fourLong: Long = 4L
private const val thresholdMultiplier: Float = 0.25f
private const val negativeSweepAngle: Float = -180f
private const val sweepAngle: Float = 180f
private const val startAngle: Float = 90f
} }
init { init {