Skip to content

Commit 8860c12

Browse files
authored
Add better scrolling (alamkanak#145)
* Fix crash in sample app on APIs below 24 * Make firstVisibleDate and lastVisibleDate non-nullable * Add smooth vertical and horizontal scrolling * Fix issue in TimeColumnDrawer where the wrong hours where displayed
1 parent a5eb057 commit 8860c12

File tree

18 files changed

+517
-360
lines changed

18 files changed

+517
-360
lines changed

core/src/main/java/com/alamkanak/weekview/CalendarExtensions.kt

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,17 @@ internal inline class Millis(val millis: Int) : Duration {
2525
get() = millis
2626
}
2727

28-
internal val Calendar.hour: Int
28+
internal var Calendar.hour: Int
2929
get() = get(Calendar.HOUR_OF_DAY)
30+
set(value) {
31+
set(Calendar.HOUR_OF_DAY, value)
32+
}
3033

31-
internal val Calendar.minute: Int
34+
internal var Calendar.minute: Int
3235
get() = get(Calendar.MINUTE)
36+
set(value) {
37+
set(Calendar.MINUTE, value)
38+
}
3339

3440
internal val Calendar.dayOfWeek: Int
3541
get() = get(Calendar.DAY_OF_WEEK)
@@ -258,3 +264,13 @@ internal fun Calendar.format(
258264
val sdf = SimpleDateFormat.getDateInstance(format)
259265
return sdf.format(time)
260266
}
267+
268+
internal fun Calendar.limitBy(minTime: Calendar, maxTime: Calendar) {
269+
if (this < minTime) {
270+
hour = minTime.hour
271+
minute = 0
272+
} else if (this > maxTime) {
273+
hour = maxTime.hour
274+
minute = 0
275+
}
276+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.alamkanak.weekview
2+
3+
annotation class PublicApi

core/src/main/java/com/alamkanak/weekview/SavedState.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,22 @@ import java.util.Calendar
88
internal class SavedState : BaseSavedState {
99

1010
var numberOfVisibleDays: Int = 0
11-
var firstVisibleDate: Calendar? = null
11+
var firstVisibleDate: Calendar = today()
1212

1313
constructor(superState: Parcelable) : super(superState)
1414

1515
constructor(
1616
superState: Parcelable,
1717
numberOfVisibleDays: Int,
18-
firstVisibleDate: Calendar?
18+
firstVisibleDate: Calendar
1919
) : super(superState) {
2020
this.numberOfVisibleDays = numberOfVisibleDays
2121
this.firstVisibleDate = firstVisibleDate
2222
}
2323

2424
constructor(source: Parcel) : super(source) {
2525
numberOfVisibleDays = source.readInt()
26-
firstVisibleDate = source.readSerializable() as? Calendar
26+
firstVisibleDate = source.readSerializable() as Calendar
2727
}
2828

2929
override fun writeToParcel(out: Parcel, flags: Int) {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.alamkanak.weekview
2+
3+
import android.content.Context
4+
import android.view.MotionEvent
5+
import android.view.ScaleGestureDetector as AndroidScaleGestureDetector
6+
7+
internal class ScaleGestureDetector(
8+
context: Context,
9+
private val config: WeekViewConfigWrapper,
10+
private val valueAnimator: ValueAnimator,
11+
private val onInvalidation: () -> Unit
12+
) {
13+
14+
private val listener = object : AndroidScaleGestureDetector.OnScaleGestureListener {
15+
16+
override fun onScaleBegin(
17+
detector: AndroidScaleGestureDetector
18+
): Boolean = !valueAnimator.isRunning
19+
20+
override fun onScale(detector: AndroidScaleGestureDetector): Boolean {
21+
val hourHeight = config.hourHeight
22+
config.newHourHeight = hourHeight * detector.scaleFactor
23+
onInvalidation()
24+
return true
25+
}
26+
27+
override fun onScaleEnd(detector: AndroidScaleGestureDetector) {
28+
onInvalidation()
29+
}
30+
}
31+
32+
private val detector = AndroidScaleGestureDetector(context, listener)
33+
34+
fun onTouchEvent(event: MotionEvent) {
35+
if (!valueAnimator.isRunning) {
36+
detector.onTouchEvent(event)
37+
}
38+
}
39+
}

core/src/main/java/com/alamkanak/weekview/TimeColumnDrawer.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ internal class TimeColumnDrawer(
1515
}
1616

1717
private fun cacheTimeLabels() = with(config) {
18-
for (hour in startHour until hoursPerDay step timeColumnHoursInterval) {
19-
timeLabelCache.put(hour, dateTimeInterpreter.interpretTime(hour + minHour))
18+
for (hour in timeRange step timeColumnHoursInterval) {
19+
timeLabelCache.put(hour, dateTimeInterpreter.interpretTime(hour))
2020
}
2121
}
2222

@@ -32,8 +32,8 @@ internal class TimeColumnDrawer(
3232
val hourLines = FloatArray(hoursPerDay * 4)
3333
val hourStep = timeColumnHoursInterval
3434

35-
for (hour in startHour until hoursPerDay step hourStep) {
36-
val heightOfHour = (hourHeight * hour)
35+
for (hour in timeRange step hourStep) {
36+
val heightOfHour = hourHeight * (hour - minHour)
3737
topMargin = headerHeight + currentOrigin.y + heightOfHour
3838

3939
val isOutsideVisibleArea = topMargin > bottom
@@ -49,7 +49,9 @@ internal class TimeColumnDrawer(
4949
y += timeTextHeight / 2 + hourSeparatorPaint.strokeWidth + timeColumnPadding
5050
}
5151

52-
canvas.drawText(timeLabelCache[hour], x, y, timeTextPaint)
52+
if (hour in timeLabelCache) {
53+
canvas.drawText(timeLabelCache[hour], x, y, timeTextPaint)
54+
}
5355

5456
if (showTimeColumnHourSeparator && hour > 0) {
5557
val j = hour - 1
@@ -77,3 +79,5 @@ internal class TimeColumnDrawer(
7779
cacheTimeLabels()
7880
}
7981
}
82+
83+
private operator fun <E> SparseArray<E>.contains(key: Int): Boolean = indexOfKey(key) >= 0
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.alamkanak.weekview
2+
3+
import android.animation.Animator
4+
import android.animation.ValueAnimator as AndroidValueAnimator
5+
import android.view.animation.DecelerateInterpolator
6+
7+
internal class ValueAnimator {
8+
9+
private var valueAnimator: AndroidValueAnimator? = null
10+
11+
val isRunning: Boolean
12+
get() = valueAnimator?.isStarted ?: false
13+
14+
fun animate(
15+
fromValue: Float,
16+
toValue: Float,
17+
duration: Long = 300,
18+
onUpdate: (Float) -> Unit,
19+
onEnd: () -> Unit = {}
20+
) {
21+
valueAnimator?.cancel()
22+
23+
valueAnimator = AndroidValueAnimator.ofFloat(fromValue, toValue).apply {
24+
setDuration(duration)
25+
interpolator = DecelerateInterpolator()
26+
27+
addUpdateListener {
28+
val value = it.animatedValue as Float
29+
onUpdate(value)
30+
}
31+
32+
addEndListener { onEnd() }
33+
34+
start()
35+
}
36+
}
37+
38+
fun stop() {
39+
valueAnimator?.cancel()
40+
}
41+
}
42+
43+
private fun AndroidValueAnimator.addEndListener(listener: (AndroidValueAnimator) -> Unit) {
44+
addListener(object : Animator.AnimatorListener {
45+
override fun onAnimationStart(animator: Animator) {
46+
listener(animator as AndroidValueAnimator)
47+
}
48+
49+
override fun onAnimationEnd(animator: Animator) = Unit
50+
override fun onAnimationCancel(animator: Animator) = Unit
51+
override fun onAnimationRepeat(animator: Animator) = Unit
52+
})
53+
}

0 commit comments

Comments
 (0)