Skip to content

Commit dee774b

Browse files
committed
Merge branch 'pull/2'
2 parents 037decf + 3c015e1 commit dee774b

6 files changed

Lines changed: 117 additions & 15 deletions

File tree

app/src/main/java/me/ibrahimsn/taskprogress/MainActivity.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class MainActivity : AppCompatActivity() {
1818

1919
val buttonToday = findViewById<Button>(R.id.buttonToday)
2020
val buttonNextWeek = findViewById<Button>(R.id.buttonNextWeek)
21+
val buttonToggleScrolling = findViewById<Button>(R.id.buttonToggleScrolling)
2122

2223
taskProgressView.onTaskClickListener = {
2324
Log.d("###", "On task click: $it")
@@ -109,5 +110,11 @@ class MainActivity : AppCompatActivity() {
109110
add(Calendar.WEEK_OF_MONTH, 1)
110111
})
111112
}
113+
114+
buttonToggleScrolling.setOnClickListener {
115+
taskProgressView.scrollingEnabled = !taskProgressView.scrollingEnabled
116+
val text = if (taskProgressView.scrollingEnabled) "Disable scrolling" else "Enable scrolling"
117+
buttonToggleScrolling.text = text
118+
}
112119
}
113120
}

app/src/main/res/layout/activity_main.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
android:layout_width="match_parent"
1212
android:layout_height="200dp"
1313
android:layout_marginTop="40dp"
14+
app:scrollingEnabled="true"
1415
app:rangeLength="7"
1516
app:sidePadding="26dp"
1617
app:taskLineWidth="10dp"
@@ -54,6 +55,21 @@
5455
android:textSize="13sp"
5556
android:textAllCaps="false"/>
5657

58+
<Button
59+
android:id="@+id/buttonToggleScrolling"
60+
android:layout_width="wrap_content"
61+
android:layout_height="wrap_content"
62+
android:layout_marginHorizontal="8dp"
63+
android:layout_marginVertical="12dp"
64+
android:minWidth="0dp"
65+
android:minHeight="0dp"
66+
android:paddingHorizontal="12dp"
67+
android:paddingVertical="8dp"
68+
android:text="Disable scrolling"
69+
android:textAllCaps="false"
70+
android:textColor="#424242"
71+
android:textSize="13sp" />
72+
5773
</LinearLayout>
5874

5975

lib/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ dependencies {
3838
implementation 'androidx.core:core-ktx:1.3.2'
3939
implementation 'androidx.appcompat:appcompat:1.2.0'
4040
implementation 'com.google.android.material:material:1.2.1'
41+
implementation 'androidx.dynamicanimation:dynamicanimation:1.0.0'
4142
testImplementation 'junit:junit:4.+'
4243
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
4344
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

lib/src/main/java/me/ibrahimsn/lib/Task.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package me.ibrahimsn.lib
22

33
import androidx.annotation.ColorInt
4+
import androidx.annotation.IntRange
45
import java.util.*
56

67
data class Task(
78
val id: Int,
89
val startDate: Calendar,
910
val endDate: Calendar,
10-
val progress: Int,
11+
@IntRange(from = 0, to = 100) val progress: Int,
1112
val category: Int,
1213
@ColorInt val color: Int,
1314
)

lib/src/main/java/me/ibrahimsn/lib/TaskProgressView.kt

Lines changed: 90 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import android.annotation.SuppressLint
55
import android.content.Context
66
import android.graphics.*
77
import android.util.AttributeSet
8+
import android.view.GestureDetector
89
import android.view.MotionEvent
910
import android.view.View
1011
import android.view.animation.OvershootInterpolator
12+
import androidx.dynamicanimation.animation.FlingAnimation
13+
import androidx.dynamicanimation.animation.FloatPropertyCompat
1114
import java.util.*
12-
import kotlin.math.abs
1315

1416
class TaskProgressView @JvmOverloads constructor(
1517
context: Context,
@@ -37,6 +39,8 @@ class TaskProgressView @JvmOverloads constructor(
3739

3840
private var head = Calendar.getInstance()
3941

42+
private val flingAnimation = FlingAnimation(this, HEAD_TRANSLATION)
43+
4044
// Global Getters
4145
private val rangeSize get() = (width - sidePadding * 2) / (rangeLength - 1)
4246

@@ -50,7 +54,66 @@ class TaskProgressView @JvmOverloads constructor(
5054
interpolator = OvershootInterpolator()
5155
}
5256

57+
// Gestures
58+
private val gestureListener = object : GestureDetector.SimpleOnGestureListener() {
59+
override fun onDown(event: MotionEvent) = true
60+
61+
override fun onSingleTapUp(event: MotionEvent): Boolean {
62+
for (task in tasks) {
63+
if (getTaskRect(task).contains(event.x, event.y)) {
64+
onTaskClickListener?.invoke(task)
65+
focusRange(task.startDate)
66+
return true
67+
}
68+
}
69+
return false
70+
}
71+
72+
override fun onScroll(
73+
event1: MotionEvent,
74+
event2: MotionEvent,
75+
distanceX: Float,
76+
distanceY: Float
77+
): Boolean {
78+
if (scrollingEnabled) {
79+
val distanceFactor = distanceX / (width - sidePadding * 2)
80+
currentTimeInMillis += ((viewEndTime - viewStartTime) * distanceFactor).toLong()
81+
return true
82+
}
83+
return false
84+
}
85+
86+
override fun onFling(
87+
event1: MotionEvent,
88+
event2: MotionEvent,
89+
velocityX: Float,
90+
velocityY: Float
91+
): Boolean {
92+
if (scrollingEnabled) {
93+
animator.cancel()
94+
val velocityFactor = velocityX / (width - sidePadding * 2)
95+
flingAnimation.apply {
96+
cancel()
97+
setStartVelocity(-velocityFactor * (viewEndTime - viewStartTime))
98+
start()
99+
}
100+
return true
101+
}
102+
return false
103+
}
104+
}
105+
106+
private val gestureDetector = GestureDetector(context, gestureListener)
107+
53108
// Core Attributes
109+
var scrollingEnabled: Boolean = true
110+
set(value) {
111+
field = value
112+
if (!value) {
113+
flingAnimation.cancel()
114+
}
115+
}
116+
54117
var sidePadding: Float
55118
get() = _sidePadding
56119
set(value) {
@@ -101,6 +164,15 @@ class TaskProgressView @JvmOverloads constructor(
101164
invalidate()
102165
}
103166

167+
private var currentTimeInMillis: Long
168+
get() = head.timeInMillis
169+
set(value) {
170+
animator.cancel()
171+
flingAnimation.cancel()
172+
head.timeInMillis = value
173+
invalidate()
174+
}
175+
104176
// Listeners
105177
var onTaskClickListener: ((Task) -> Unit)? = null
106178

@@ -148,6 +220,11 @@ class TaskProgressView @JvmOverloads constructor(
148220

149221
try {
150222
with(typedArray) {
223+
scrollingEnabled = getBoolean(
224+
R.styleable.TaskProgressView_scrollingEnabled,
225+
scrollingEnabled
226+
)
227+
151228
sidePadding = getDimension(
152229
R.styleable.TaskProgressView_sidePadding,
153230
sidePadding
@@ -268,6 +345,7 @@ class TaskProgressView @JvmOverloads constructor(
268345

269346
fun focusRange(head: Calendar) {
270347
post {
348+
flingAnimation.cancel()
271349
animator.setFloatValues(
272350
this.head.timeInMillis.toFloat(),
273351
head.timeInMillis.toFloat()
@@ -282,18 +360,7 @@ class TaskProgressView @JvmOverloads constructor(
282360
}
283361

284362
@SuppressLint("ClickableViewAccessibility")
285-
override fun onTouchEvent(event: MotionEvent): Boolean {
286-
if (event.action == MotionEvent.ACTION_UP && abs(event.downTime - event.eventTime) < TOUCH_EVENT_DURATION) {
287-
for (task in tasks) {
288-
if (getTaskRect(task).contains(event.x, event.y)) {
289-
onTaskClickListener?.invoke(task)
290-
focusRange(task.startDate)
291-
return true
292-
}
293-
}
294-
}
295-
return true
296-
}
363+
override fun onTouchEvent(event: MotionEvent) = gestureDetector.onTouchEvent(event)
297364

298365
override fun onAttachedToWindow() {
299366
super.onAttachedToWindow()
@@ -304,6 +371,7 @@ class TaskProgressView @JvmOverloads constructor(
304371
super.onDetachedFromWindow()
305372
animator.removeUpdateListener(animatorUpdateListener)
306373
animator.cancel()
374+
flingAnimation.cancel()
307375
}
308376

309377
private fun Canvas.drawTextCentred(text: String, cx: Float, cy: Float, paint: Paint) {
@@ -313,13 +381,21 @@ class TaskProgressView @JvmOverloads constructor(
313381

314382
companion object {
315383
private const val DAY_MULTIPLIER = 86400000 // 1000 * 60 * 60 * 24
316-
private const val TOUCH_EVENT_DURATION = 500
317384
private const val TASK_STROKE_WIDTH = 2f
318385
private const val ANIMATION_DURATION = 500L
319386
private const val TASK_LINE_WIDTH = 25f
320387

321388
private val DEFAULT_DATE_TEXT_COLOR = Color.parseColor("#5e5e5e")
322389
private val DEFAULT_DATE_LINE_COLOR = Color.parseColor("#f1f1f1")
323390
private val DEFAULT_TASK_BACKGROUND_COLOR = Color.parseColor("#eaeaea")
391+
392+
private val HEAD_TRANSLATION = object : FloatPropertyCompat<TaskProgressView>("HeadTranslation") {
393+
override fun getValue(view: TaskProgressView): Float = view.currentTimeInMillis.toFloat()
394+
395+
override fun setValue(view: TaskProgressView, value: Float) {
396+
view.head.timeInMillis = value.toLong()
397+
view.invalidate()
398+
}
399+
}
324400
}
325401
}

lib/src/main/res/values/attrs.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<resources>
33

44
<declare-styleable name="TaskProgressView">
5+
<attr name="scrollingEnabled" format="boolean" />
56
<attr name="sidePadding" format="dimension" />
67
<attr name="rangeLength" format="integer" />
78
<attr name="taskDateMargin" format="dimension" />

0 commit comments

Comments
 (0)