@@ -93,10 +93,8 @@ class ApngDrawable @VisibleForTesting internal constructor(
93
93
@IntRange(from = LOOP_INTRINSIC .toLong(), to = Int .MAX_VALUE .toLong())
94
94
var loopCount: Int = apngState.apng.loopCount
95
95
set(value) {
96
- if (value < LOOP_INTRINSIC ) {
97
- throw IllegalArgumentException (
98
- " `loopCount` must be a signed value or special values. (value = $value )"
99
- )
96
+ require(value >= LOOP_INTRINSIC ) {
97
+ " `loopCount` must be a signed value or special values. (value = $value )"
100
98
}
101
99
field = if (value == LOOP_INTRINSIC ) apngState.apng.loopCount else value
102
100
}
@@ -115,22 +113,14 @@ class ApngDrawable @VisibleForTesting internal constructor(
115
113
if (currentRepeatCountInternal > loopCount) loopCount else currentRepeatCountInternal
116
114
117
115
/* *
118
- * [currentFrameIndex] is the index to indicate which frame should show at that time.
119
- * [currentFrameIndex] is calculated with APNG meta data and elapsed time after animation
120
- * started.
121
- * [durationMillis] is the duration to animate one loop of APNG animation. [frameCount]
122
- * is number of APNG frames. For example, if one loop duration is 1000ms, image count is 10 and
123
- * elapsed time is 2100ms, the frame index should be 1 of 3rd loop.
124
- * If this image isn't infinite looping image and [animationElapsedTimeMillis] is larger than
125
- * total duration of this image's animation, returns always last frame index.
116
+ * The corresponding frame index with the elapsed time of the animation. This value indicates
117
+ * the last frame after the animation finished.
126
118
*/
127
119
val currentFrameIndex: Int
128
- get() = if (loopCount != LOOP_FOREVER && exceedsRepeatCountLimitation()) {
129
- frameCount - 1
130
- } else {
131
- val durationInSingleLoop = animationElapsedTimeMillis % durationMillis
132
- val index = frameStartTimes.indexOfFirst { durationInSingleLoop < it } - 1
133
- if (index < 0 ) frameCount - 1 else index
120
+ get() {
121
+ var progressMillisInCurrentLoop = animationElapsedTimeMillis % durationMillis
122
+ progressMillisInCurrentLoop + = if (exceedsRepeatCountLimitation()) durationMillis else 0
123
+ return calculateCurrentFrameIndex(0 , frameCount - 1 , progressMillisInCurrentLoop)
134
124
}
135
125
136
126
private val currentRepeatCountInternal: Int
@@ -345,6 +335,35 @@ class ApngDrawable @VisibleForTesting internal constructor(
345
335
bounds.set(0 , 0 , scaledWidth, scaledHeight)
346
336
}
347
337
338
+ private tailrec fun calculateCurrentFrameIndex (
339
+ lowerBoundIndex : Int ,
340
+ upperBoundIndex : Int ,
341
+ progressMillisInCurrentLoop : Long
342
+ ): Int {
343
+ val middleIndex = (lowerBoundIndex + upperBoundIndex) / 2
344
+ return when {
345
+ // Continue searching in the upper half
346
+ frameStartTimes.size > middleIndex + 1 &&
347
+ progressMillisInCurrentLoop >= frameStartTimes[middleIndex + 1 ] ->
348
+ calculateCurrentFrameIndex(
349
+ middleIndex + 1 ,
350
+ upperBoundIndex,
351
+ progressMillisInCurrentLoop
352
+ )
353
+
354
+ // Continue searching in the lower half
355
+ lowerBoundIndex != upperBoundIndex &&
356
+ progressMillisInCurrentLoop < frameStartTimes[middleIndex] ->
357
+ calculateCurrentFrameIndex(
358
+ lowerBoundIndex,
359
+ middleIndex,
360
+ progressMillisInCurrentLoop
361
+ )
362
+
363
+ else -> middleIndex
364
+ }
365
+ }
366
+
348
367
internal class ApngState (
349
368
val apng : Apng ,
350
369
/* *
0 commit comments