Home | History | Annotate | Download | only in month
      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