8
8
import android .graphics .PointF ;
9
9
import android .graphics .Rect ;
10
10
import android .graphics .RectF ;
11
+ import android .graphics .Region ;
11
12
import android .graphics .Typeface ;
13
+ import android .os .Build ;
12
14
import android .support .annotation .Nullable ;
13
15
import android .support .v4 .view .GestureDetectorCompat ;
14
16
import android .support .v4 .view .ViewCompat ;
17
+ import android .support .v4 .view .animation .FastOutLinearInInterpolator ;
15
18
import android .text .Layout ;
16
19
import android .text .SpannableStringBuilder ;
17
20
import android .text .StaticLayout ;
27
30
import android .view .ScaleGestureDetector ;
28
31
import android .view .SoundEffectConstants ;
29
32
import android .view .View ;
33
+ import android .view .ViewConfiguration ;
30
34
import android .widget .OverScroller ;
31
35
32
36
import java .text .SimpleDateFormat ;
43
47
*/
44
48
public class WeekView extends View {
45
49
50
+ private enum Direction {
51
+ NONE , LEFT , RIGHT , VERTICAL
52
+ }
53
+
46
54
@ Deprecated
47
55
public static final int LENGTH_SHORT = 1 ;
48
56
@ Deprecated
@@ -72,9 +80,9 @@ public class WeekView extends View {
72
80
private Paint mEventBackgroundPaint ;
73
81
private float mHeaderColumnWidth ;
74
82
private List <EventRect > mEventRects ;
75
- private List <WeekViewEvent > mPreviousPeriodEvents ;
76
- private List <WeekViewEvent > mCurrentPeriodEvents ;
77
- private List <WeekViewEvent > mNextPeriodEvents ;
83
+ private List <? extends WeekViewEvent > mPreviousPeriodEvents ;
84
+ private List <? extends WeekViewEvent > mCurrentPeriodEvents ;
85
+ private List <? extends WeekViewEvent > mNextPeriodEvents ;
78
86
private TextPaint mEventTextPaint ;
79
87
private Paint mHeaderColumnBackgroundPaint ;
80
88
private int mFetchedPeriod = -1 ; // the middle period the calendar has fetched.
@@ -85,7 +93,8 @@ public class WeekView extends View {
85
93
private Calendar mFirstVisibleDay ;
86
94
private Calendar mLastVisibleDay ;
87
95
private int mDefaultEventColor ;
88
-
96
+ private int mMinimumFlingVelocity = 0 ;
97
+ private int mScaledTouchSlop = 0 ;
89
98
// Attributes and their default values.
90
99
private int mHourHeight = 50 ;
91
100
private int mNewHourHeight = -1 ;
@@ -151,20 +160,40 @@ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float d
151
160
if (mIsZooming )
152
161
return true ;
153
162
154
- // Calculate the direction to scroll.
155
- if (mCurrentScrollDirection == Direction .NONE ) {
156
- // allow scrolling only in one direction
157
- if (Math .abs (distanceX ) > Math .abs (distanceY )){
158
- mCurrentScrollDirection = Direction .HORIZONTAL ;
163
+ switch (mCurrentScrollDirection ) {
164
+ case NONE : {
165
+ // Allow scrolling only in one direction.
166
+ if (Math .abs (distanceX ) > Math .abs (distanceY )) {
167
+ if (distanceX > 0 ) {
168
+ mCurrentScrollDirection = Direction .LEFT ;
169
+ } else {
170
+ mCurrentScrollDirection = Direction .RIGHT ;
171
+ }
172
+ } else {
173
+ mCurrentScrollDirection = Direction .VERTICAL ;
174
+ }
175
+ break ;
159
176
}
160
- else {
161
- mCurrentScrollDirection = Direction .VERTICAL ;
177
+ case LEFT : {
178
+ // Change direction if there was enough change.
179
+ if (Math .abs (distanceX ) > Math .abs (distanceY ) && (distanceX < -mScaledTouchSlop )) {
180
+ mCurrentScrollDirection = Direction .RIGHT ;
181
+ }
182
+ break ;
183
+ }
184
+ case RIGHT : {
185
+ // Change direction if there was enough change.
186
+ if (Math .abs (distanceX ) > Math .abs (distanceY ) && (distanceX > mScaledTouchSlop )) {
187
+ mCurrentScrollDirection = Direction .LEFT ;
188
+ }
189
+ break ;
162
190
}
163
191
}
164
192
165
193
// Calculate the new origin after scroll.
166
194
switch (mCurrentScrollDirection ) {
167
- case HORIZONTAL :
195
+ case LEFT :
196
+ case RIGHT :
168
197
mCurrentOrigin .x -= distanceX * mXScrollingSpeed ;
169
198
ViewCompat .postInvalidateOnAnimation (WeekView .this );
170
199
break ;
@@ -178,14 +207,20 @@ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float d
178
207
179
208
@ Override
180
209
public boolean onFling (MotionEvent e1 , MotionEvent e2 , float velocityX , float velocityY ) {
210
+ if (mIsZooming )
211
+ return true ;
212
+
181
213
mScroller .forceFinished (true );
182
214
183
215
mCurrentFlingDirection = mCurrentScrollDirection ;
184
- if (mCurrentFlingDirection == Direction .HORIZONTAL ){
185
- mScroller .fling ((int ) mCurrentOrigin .x , (int ) mCurrentOrigin .y , (int ) (velocityX * mXScrollingSpeed ), 0 , Integer .MIN_VALUE , Integer .MAX_VALUE , (int ) -(mHourHeight * 24 + mHeaderTextHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight /2 - getHeight ()), 0 );
186
- }
187
- else if (mCurrentFlingDirection == Direction .VERTICAL ){
188
- mScroller .fling ((int ) mCurrentOrigin .x , (int ) mCurrentOrigin .y , 0 , (int ) velocityY , Integer .MIN_VALUE , Integer .MAX_VALUE , (int ) -(mHourHeight * 24 + mHeaderTextHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight /2 - getHeight ()), 0 );
216
+ switch (mCurrentFlingDirection ) {
217
+ case LEFT :
218
+ case RIGHT :
219
+ mScroller .fling ((int ) mCurrentOrigin .x , (int ) mCurrentOrigin .y , (int ) (velocityX * mXScrollingSpeed ), 0 , Integer .MIN_VALUE , Integer .MAX_VALUE , (int ) -(mHourHeight * 24 + mHeaderTextHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight / 2 - getHeight ()), 0 );
220
+ break ;
221
+ case VERTICAL :
222
+ mScroller .fling ((int ) mCurrentOrigin .x , (int ) mCurrentOrigin .y , 0 , (int ) velocityY , Integer .MIN_VALUE , Integer .MAX_VALUE , (int ) -(mHourHeight * 24 + mHeaderTextHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight /2 - getHeight ()), 0 );
223
+ break ;
189
224
}
190
225
191
226
ViewCompat .postInvalidateOnAnimation (WeekView .this );
@@ -247,10 +282,6 @@ public void onLongPress(MotionEvent e) {
247
282
}
248
283
};
249
284
250
- private enum Direction {
251
- NONE , HORIZONTAL , VERTICAL
252
- }
253
-
254
285
public WeekView (Context context ) {
255
286
this (context , null );
256
287
}
@@ -314,7 +345,10 @@ public WeekView(Context context, AttributeSet attrs, int defStyleAttr) {
314
345
private void init () {
315
346
// Scrolling initialization.
316
347
mGestureDetector = new GestureDetectorCompat (mContext , mGestureListener );
317
- mScroller = new OverScroller (mContext );
348
+ mScroller = new OverScroller (mContext , new FastOutLinearInInterpolator ());
349
+
350
+ mMinimumFlingVelocity = ViewConfiguration .get (mContext ).getScaledMinimumFlingVelocity ();
351
+ mScaledTouchSlop = ViewConfiguration .get (mContext ).getScaledTouchSlop ();
318
352
319
353
// Measure settings for time column.
320
354
mTimeTextPaint = new Paint (Paint .ANTI_ALIAS_FLAG );
@@ -438,23 +472,23 @@ private void initTextTimeWidth() {
438
472
protected void onDraw (Canvas canvas ) {
439
473
super .onDraw (canvas );
440
474
475
+ // Hide everything in the first cell (top left corner).
476
+ canvas .drawRect (0 , 0 , mTimeTextWidth + mHeaderColumnPadding * 2 , mHeaderTextHeight + mHeaderRowPadding * 2 , mHeaderBackgroundPaint );
477
+
441
478
// Draw the header row.
442
479
drawHeaderRowAndEvents (canvas );
443
480
444
481
// Draw the time column and all the axes/separators.
445
482
drawTimeColumnAndAxes (canvas );
446
-
447
- // Hide everything in the first cell (top left corner).
448
- canvas .drawRect (0 , 0 , mTimeTextWidth + mHeaderColumnPadding * 2 , mHeaderTextHeight + mHeaderRowPadding * 2 , mHeaderBackgroundPaint );
449
-
450
- // Hide anything that is in the bottom margin of the header row.
451
- canvas .drawRect (mHeaderColumnWidth , mHeaderTextHeight + mHeaderRowPadding * 2 , getWidth (), mHeaderRowPadding * 2 + mHeaderTextHeight + mHeaderMarginBottom + mTimeTextHeight /2 , mHeaderColumnBackgroundPaint );
452
483
}
453
484
454
485
private void drawTimeColumnAndAxes (Canvas canvas ) {
455
486
// Draw the background color for the header column.
456
487
canvas .drawRect (0 , mHeaderTextHeight + mHeaderRowPadding * 2 , mHeaderColumnWidth , getHeight (), mHeaderColumnBackgroundPaint );
457
488
489
+ // Clip to paint in left column only.
490
+ canvas .clipRect (0 , mHeaderTextHeight + mHeaderRowPadding * 2 , mHeaderColumnWidth , getHeight (), Region .Op .REPLACE );
491
+
458
492
for (int i = 0 ; i < 24 ; i ++) {
459
493
float top = mHeaderTextHeight + mHeaderRowPadding * 2 + mCurrentOrigin .y + mHourHeight * i + mHeaderMarginBottom ;
460
494
@@ -544,6 +578,9 @@ else if (mNewHourHeight > mMaxHourHeight)
544
578
}
545
579
}
546
580
581
+ // Clip to paint events only.
582
+ canvas .clipRect (mHeaderColumnWidth , mHeaderTextHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight /2 , getWidth (), getHeight (), Region .Op .REPLACE );
583
+
547
584
// Iterate through each day.
548
585
Calendar oldFirstVisibleDay = mFirstVisibleDay ;
549
586
mFirstVisibleDay = (Calendar ) today .clone ();
@@ -629,6 +666,10 @@ else if (day.before(today)) {
629
666
startPixel += mWidthPerDay + mColumnGap ;
630
667
}
631
668
669
+
670
+ // Clip to paint header row only.
671
+ canvas .clipRect (mHeaderColumnWidth , 0 , getWidth (), mHeaderTextHeight + mHeaderRowPadding * 2 , Region .Op .REPLACE );
672
+
632
673
// Draw the header background.
633
674
canvas .drawRect (0 , 0 , getWidth (), mHeaderTextHeight + mHeaderRowPadding * 2 , mHeaderBackgroundPaint );
634
675
@@ -845,9 +886,9 @@ private void getMoreEvents(Calendar day) {
845
886
if (mWeekViewLoader != null ){
846
887
int periodToFetch = (int ) mWeekViewLoader .toWeekViewPeriodIndex (day );
847
888
if (!isInEditMode () && (mFetchedPeriod < 0 || mFetchedPeriod != periodToFetch || mRefreshEvents )) {
848
- List <WeekViewEvent > previousPeriodEvents = null ;
849
- List <WeekViewEvent > currentPeriodEvents = null ;
850
- List <WeekViewEvent > nextPeriodEvents = null ;
889
+ List <? extends WeekViewEvent > previousPeriodEvents = null ;
890
+ List <? extends WeekViewEvent > currentPeriodEvents = null ;
891
+ List <? extends WeekViewEvent > nextPeriodEvents = null ;
851
892
852
893
if (mPreviousPeriodEvents != null && mCurrentPeriodEvents != null && mNextPeriodEvents != null ){
853
894
if (periodToFetch == mFetchedPeriod -1 ){
@@ -917,6 +958,8 @@ else if (periodToFetch == mFetchedPeriod+1){
917
958
* @param event The event to cache.
918
959
*/
919
960
private void cacheEvent (WeekViewEvent event ) {
961
+ if (event .getStartTime ().compareTo (event .getEndTime ()) >= 0 )
962
+ return ;
920
963
if (!isSameDay (event .getStartTime (), event .getEndTime ())) {
921
964
// Add first day.
922
965
Calendar endTime = (Calendar ) event .getStartTime ().clone ();
@@ -961,7 +1004,7 @@ private void cacheEvent(WeekViewEvent event) {
961
1004
* Sort and cache events.
962
1005
* @param events The events to be sorted and cached.
963
1006
*/
964
- private void sortAndCacheEvents (List <WeekViewEvent > events ) {
1007
+ private void sortAndCacheEvents (List <? extends WeekViewEvent > events ) {
965
1008
sortEvents (events );
966
1009
for (WeekViewEvent event : events ) {
967
1010
cacheEvent (event );
@@ -972,7 +1015,7 @@ private void sortAndCacheEvents(List<WeekViewEvent> events) {
972
1015
* Sorts the events in ascending order.
973
1016
* @param events The events to be sorted.
974
1017
*/
975
- private void sortEvents (List <WeekViewEvent > events ) {
1018
+ private void sortEvents (List <? extends WeekViewEvent > events ) {
976
1019
Collections .sort (events , new Comparator <WeekViewEvent >() {
977
1020
@ Override
978
1021
public int compare (WeekViewEvent event1 , WeekViewEvent event2 ) {
@@ -1636,7 +1679,7 @@ public boolean onTouchEvent(MotionEvent event) {
1636
1679
1637
1680
// Check after call of mGestureDetector, so mCurrentFlingDirection and mCurrentScrollDirection are set.
1638
1681
if (event .getAction () == MotionEvent .ACTION_UP && !mIsZooming && mCurrentFlingDirection == Direction .NONE ) {
1639
- if (mCurrentScrollDirection == Direction .HORIZONTAL ) {
1682
+ if (mCurrentScrollDirection == Direction .RIGHT || mCurrentScrollDirection == Direction . LEFT ) {
1640
1683
goToNearestOrigin ();
1641
1684
}
1642
1685
mCurrentScrollDirection = Direction .NONE ;
@@ -1646,15 +1689,29 @@ public boolean onTouchEvent(MotionEvent event) {
1646
1689
}
1647
1690
1648
1691
private void goToNearestOrigin (){
1649
- float leftDays = Math .round (mCurrentOrigin .x / (mWidthPerDay + mColumnGap ));
1692
+ double leftDays = mCurrentOrigin .x / (mWidthPerDay + mColumnGap );
1693
+
1694
+ if (mCurrentFlingDirection != Direction .NONE ) {
1695
+ // snap to nearest day
1696
+ leftDays = Math .round (leftDays );
1697
+ } else if (mCurrentScrollDirection == Direction .LEFT ) {
1698
+ // snap to last day
1699
+ leftDays = Math .floor (leftDays );
1700
+ } else if (mCurrentScrollDirection == Direction .RIGHT ) {
1701
+ // snap to next day
1702
+ leftDays = Math .ceil (leftDays );
1703
+ } else {
1704
+ // snap to nearest day
1705
+ leftDays = Math .round (leftDays );
1706
+ }
1650
1707
1651
1708
int nearestOrigin = (int ) (mCurrentOrigin .x - leftDays * (mWidthPerDay + mColumnGap ));
1652
1709
1653
1710
if (nearestOrigin != 0 ) {
1654
1711
// Stop current animation.
1655
1712
mScroller .forceFinished (true );
1656
1713
// Snap to date.
1657
- mScroller .startScroll ((int ) mCurrentOrigin .x , (int ) mCurrentOrigin .y , -nearestOrigin , 0 , 50 );
1714
+ mScroller .startScroll ((int ) mCurrentOrigin .x , (int ) mCurrentOrigin .y , -nearestOrigin , 0 , ( int ) ( Math . abs ( nearestOrigin ) / mWidthPerDay * 500 ) );
1658
1715
ViewCompat .postInvalidateOnAnimation (WeekView .this );
1659
1716
}
1660
1717
// Reset scrolling and fling direction.
@@ -1672,14 +1729,29 @@ public void computeScroll() {
1672
1729
goToNearestOrigin ();
1673
1730
}
1674
1731
} else {
1675
- if (mScroller .computeScrollOffset ()) {
1732
+ if (mCurrentFlingDirection != Direction .NONE && forceFinishScroll ()) {
1733
+ goToNearestOrigin ();
1734
+ } else if (mScroller .computeScrollOffset ()) {
1676
1735
mCurrentOrigin .y = mScroller .getCurrY ();
1677
1736
mCurrentOrigin .x = mScroller .getCurrX ();
1678
1737
ViewCompat .postInvalidateOnAnimation (this );
1679
1738
}
1680
1739
}
1681
1740
}
1682
1741
1742
+ /**
1743
+ * Check if scrolling should be stopped.
1744
+ * @return true if scrolling should be stopped before reaching the end of animation.
1745
+ */
1746
+ private boolean forceFinishScroll () {
1747
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .ICE_CREAM_SANDWICH ) {
1748
+ // current velocity only available since api 14
1749
+ return mScroller .getCurrVelocity () <= mMinimumFlingVelocity ;
1750
+ } else {
1751
+ return false ;
1752
+ }
1753
+ }
1754
+
1683
1755
1684
1756
/////////////////////////////////////////////////////////////////
1685
1757
//
0 commit comments