1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.calendar.month; 18 19 import android.content.Context; 20 import android.graphics.Rect; 21 import android.os.SystemClock; 22 import android.text.format.Time; 23 import android.util.AttributeSet; 24 import android.view.MotionEvent; 25 import android.view.VelocityTracker; 26 import android.view.View; 27 import android.widget.ListView; 28 29 import com.android.calendar.Utils; 30 31 public class MonthListView extends ListView { 32 33 private static final String TAG = "MonthListView"; 34 VelocityTracker mTracker; 35 private static float mScale = 0; 36 37 // These define the behavior of the fling. Below MIN_VELOCITY_FOR_FLING, do the system fling 38 // behavior. Between MIN_VELOCITY_FOR_FLING and MULTIPLE_MONTH_VELOCITY_THRESHOLD, do one month 39 // fling. Above MULTIPLE_MONTH_VELOCITY_THRESHOLD, do multiple month flings according to the 40 // fling strength. When doing multiple month fling, the velocity is reduced by this threshold 41 // to prevent moving from one month fling to 4 months and above flings. 42 private static int MIN_VELOCITY_FOR_FLING = 1500; 43 private static int MULTIPLE_MONTH_VELOCITY_THRESHOLD = 2000; 44 private static int FLING_VELOCITY_DIVIDER = 500; 45 private static int FLING_TIME = 1000; 46 47 // disposable variable used for time calculations 48 protected Time mTempTime; 49 private long mDownActionTime; 50 private final Rect mFirstViewRect = new Rect(); 51 52 Context mListContext; 53 54 // Updates the time zone when it changes 55 private final Runnable mTimezoneUpdater = new Runnable() { 56 @Override 57 public void run() { 58 if (mTempTime != null && mListContext != null) { 59 mTempTime.timezone = 60 Utils.getTimeZone(mListContext, mTimezoneUpdater); 61 } 62 } 63 }; 64 65 public MonthListView(Context context) { 66 super(context); 67 init(context); 68 } 69 70 public MonthListView(Context context, AttributeSet attrs, int defStyle) { 71 super(context, attrs, defStyle); 72 init(context); 73 } 74 75 public MonthListView(Context context, AttributeSet attrs) { 76 super(context, attrs); 77 init(context); 78 } 79 80 private void init(Context c) { 81 mListContext = c; 82 mTracker = VelocityTracker.obtain(); 83 mTempTime = new Time(Utils.getTimeZone(c,mTimezoneUpdater)); 84 if (mScale == 0) { 85 mScale = c.getResources().getDisplayMetrics().density; 86 if (mScale != 1) { 87 MIN_VELOCITY_FOR_FLING *= mScale; 88 MULTIPLE_MONTH_VELOCITY_THRESHOLD *= mScale; 89 FLING_VELOCITY_DIVIDER *= mScale; 90 } 91 } 92 } 93 94 @Override 95 public boolean onTouchEvent(MotionEvent ev) { 96 return processEvent(ev) || super.onTouchEvent(ev); 97 } 98 99 @Override 100 public boolean onInterceptTouchEvent(MotionEvent ev) { 101 return processEvent(ev) || super.onInterceptTouchEvent(ev); 102 } 103 104 private boolean processEvent (MotionEvent ev) { 105 switch (ev.getAction() & MotionEvent.ACTION_MASK) { 106 // Since doFling sends a cancel, make sure not to process it. 107 case MotionEvent.ACTION_CANCEL: 108 return false; 109 // Start tracking movement velocity 110 case MotionEvent.ACTION_DOWN: 111 mTracker.clear(); 112 mDownActionTime = SystemClock.uptimeMillis(); 113 break; 114 // Accumulate velocity and do a custom fling when above threshold 115 case MotionEvent.ACTION_UP: 116 mTracker.addMovement(ev); 117 mTracker.computeCurrentVelocity(1000); // in pixels per second 118 float vel = mTracker.getYVelocity (); 119 if (Math.abs(vel) > MIN_VELOCITY_FOR_FLING) { 120 doFling(vel); 121 return true; 122 } 123 break; 124 default: 125 mTracker.addMovement(ev); 126 break; 127 } 128 return false; 129 } 130 131 // Do a "snap to start of month" fling 132 private void doFling(float velocityY) { 133 134 // Stop the list-view movement and take over 135 MotionEvent cancelEvent = MotionEvent.obtain(mDownActionTime, SystemClock.uptimeMillis(), 136 MotionEvent.ACTION_CANCEL, 0, 0, 0); 137 onTouchEvent(cancelEvent); 138 139 // Below the threshold, fling one month. Above the threshold , fling 140 // according to the speed of the fling. 141 int monthsToJump; 142 if (Math.abs(velocityY) < MULTIPLE_MONTH_VELOCITY_THRESHOLD) { 143 if (velocityY < 0) { 144 monthsToJump = 1; 145 } else { 146 // value here is zero and not -1 since by the time the fling is 147 // detected the list moved back one month. 148 monthsToJump = 0; 149 } 150 } else { 151 if (velocityY < 0) { 152 monthsToJump = 1 - (int) ((velocityY + MULTIPLE_MONTH_VELOCITY_THRESHOLD) 153 / FLING_VELOCITY_DIVIDER); 154 } else { 155 monthsToJump = -(int) ((velocityY - MULTIPLE_MONTH_VELOCITY_THRESHOLD) 156 / FLING_VELOCITY_DIVIDER); 157 } 158 } 159 160 // Get the day at the top right corner 161 int day = getUpperRightJulianDay(); 162 // Get the day of the first day of the next/previous month 163 // (according to scroll direction) 164 mTempTime.setJulianDay(day); 165 mTempTime.monthDay = 1; 166 mTempTime.month += monthsToJump; 167 long timeInMillis = mTempTime.normalize(false); 168 // Since each view is 7 days, round the target day up to make sure the 169 // scroll will be at least one view. 170 int scrollToDay = Time.getJulianDay(timeInMillis, mTempTime.gmtoff) 171 + ((monthsToJump > 0) ? 6 : 0); 172 173 // Since all views have the same height, scroll by pixels instead of 174 // "to position". 175 // Compensate for the top view offset from the top. 176 View firstView = getChildAt(0); 177 int firstViewHeight = firstView.getHeight(); 178 // Get visible part length 179 firstView.getLocalVisibleRect(mFirstViewRect); 180 int topViewVisiblePart = mFirstViewRect.bottom - mFirstViewRect.top; 181 int viewsToFling = (scrollToDay - day) / 7 - ((monthsToJump <= 0) ? 1 : 0); 182 int offset = (viewsToFling > 0) ? -(firstViewHeight - topViewVisiblePart 183 + SimpleDayPickerFragment.LIST_TOP_OFFSET) : (topViewVisiblePart 184 - SimpleDayPickerFragment.LIST_TOP_OFFSET); 185 // Fling 186 smoothScrollBy(viewsToFling * firstViewHeight + offset, FLING_TIME); 187 } 188 189 // Returns the julian day of the day in the upper right corner 190 private int getUpperRightJulianDay() { 191 SimpleWeekView child = (SimpleWeekView) getChildAt(0); 192 if (child == null) { 193 return -1; 194 } 195 return child.getFirstJulianDay() + SimpleDayPickerFragment.DAYS_PER_WEEK - 1; 196 } 197 } 198