OSDN Git Service

Eleven: lots of ui tweaks (fonts, padding, grid layout) and cancel tasks smarter
[android-x86/packages-apps-Eleven.git] / src / com / viewpagerindicator / TitlePageIndicator.java
1 /*
2  * Copyright (C) 2011 Jake Wharton
3  * Copyright (C) 2011 Patrik Akerfeldt
4  * Copyright (C) 2011 Francisco Figueiredo Jr.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 package com.viewpagerindicator;
19
20 import android.content.Context;
21 import android.content.res.Resources;
22 import android.content.res.TypedArray;
23 import android.graphics.Canvas;
24 import android.graphics.Paint;
25 import android.graphics.Path;
26 import android.graphics.Rect;
27 import android.graphics.Typeface;
28 import android.graphics.drawable.Drawable;
29 import android.os.Parcel;
30 import android.os.Parcelable;
31 import android.support.v4.view.MotionEventCompat;
32 import android.support.v4.view.ViewConfigurationCompat;
33 import android.support.v4.view.ViewPager;
34 import android.util.AttributeSet;
35 import android.view.MotionEvent;
36 import android.view.View;
37 import android.view.ViewConfiguration;
38
39 import com.cyngn.eleven.R;
40
41 import java.util.ArrayList;
42
43 /**
44  * A TitlePageIndicator is a PageIndicator which displays the title of left view
45  * (if exist), the title of the current select view (centered) and the title of
46  * the right view (if exist). When the user scrolls the ViewPager then titles are
47  * also scrolled.
48  */
49 public class TitlePageIndicator extends View implements PageIndicator {
50     /**
51      * Percentage indicating what percentage of the screen width away from
52      * center should the underline be fully faded. A value of 0.25 means that
53      * halfway between the center of the screen and an edge.
54      */
55     private static final float SELECTION_FADE_PERCENTAGE = 0.25f;
56
57     /**
58      * Percentage indicating what percentage of the screen width away from
59      * center should the selected text bold turn off. A value of 0.05 means
60      * that 10% between the center and an edge.
61      */
62     private static final float BOLD_FADE_PERCENTAGE = 0.05f;
63
64     /**
65      * Title text used when no title is provided by the adapter.
66      */
67     private static final String EMPTY_TITLE = "";
68
69     /**
70      * Interface for a callback when the center item has been clicked.
71      */
72     public interface OnCenterItemClickListener {
73         /**
74          * Callback when the center item has been clicked.
75          *
76          * @param position Position of the current center item.
77          */
78         void onCenterItemClick(int position);
79     }
80
81     public enum IndicatorStyle {
82         None(0), Triangle(1), Underline(2);
83
84         public final int value;
85
86         private IndicatorStyle(int value) {
87             this.value = value;
88         }
89
90         public static IndicatorStyle fromValue(int value) {
91             for (IndicatorStyle style : IndicatorStyle.values()) {
92                 if (style.value == value) {
93                     return style;
94                 }
95             }
96             return null;
97         }
98     }
99
100     public enum LinePosition {
101         Bottom(0), Top(1);
102
103         public final int value;
104
105         private LinePosition(int value) {
106             this.value = value;
107         }
108
109         public static LinePosition fromValue(int value) {
110             for (LinePosition position : LinePosition.values()) {
111                 if (position.value == value) {
112                     return position;
113                 }
114             }
115             return null;
116         }
117     }
118
119     private ViewPager mViewPager;
120     private ViewPager.OnPageChangeListener mListener;
121     private int mCurrentPage = -1;
122     private float mPageOffset;
123     private int mScrollState;
124     private final Paint mPaintText = new Paint();
125     private boolean mBoldText;
126     private boolean mBoldAll;
127     private int mColorText;
128     private int mColorSelected;
129     private Path mPath = new Path();
130     private final Rect mBounds = new Rect();
131     private final Paint mPaintFooterLine = new Paint();
132     private IndicatorStyle mFooterIndicatorStyle;
133     private LinePosition mLinePosition;
134     private final Paint mPaintFooterIndicator = new Paint();
135     private float mFooterIndicatorHeight;
136     private float mFooterIndicatorUnderlinePadding;
137     private float mFooterPadding;
138     private float mTitlePadding;
139     private float mTopPadding;
140     /** Left and right side padding for not active view titles. */
141     private float mClipPadding;
142     private float mFooterLineHeight;
143
144     private static final int INVALID_POINTER = -1;
145
146     private int mTouchSlop;
147     private float mLastMotionX = -1;
148     private int mActivePointerId = INVALID_POINTER;
149     private boolean mIsDragging;
150
151     private OnCenterItemClickListener mCenterItemClickListener;
152
153
154     public TitlePageIndicator(Context context) {
155         this(context, null);
156     }
157
158     public TitlePageIndicator(Context context, AttributeSet attrs) {
159         this(context, attrs, R.attr.vpiTitlePageIndicatorStyle);
160     }
161
162     public TitlePageIndicator(Context context, AttributeSet attrs, int defStyle) {
163         super(context, attrs, defStyle);
164         if (isInEditMode()) return;
165
166         //Load defaults from resources
167         final Resources res = getResources();
168         final int defaultFooterColor = res.getColor(R.color.default_title_indicator_footer_color);
169         final float defaultFooterLineHeight = res.getDimension(R.dimen.default_title_indicator_footer_line_height);
170         final int defaultFooterIndicatorStyle = res.getInteger(R.integer.default_title_indicator_footer_indicator_style);
171         final float defaultFooterIndicatorHeight = res.getDimension(R.dimen.default_title_indicator_footer_indicator_height);
172         final float defaultFooterIndicatorUnderlinePadding = res.getDimension(R.dimen.default_title_indicator_footer_indicator_underline_padding);
173         final float defaultFooterPadding = res.getDimension(R.dimen.default_title_indicator_footer_padding);
174         final int defaultLinePosition = res.getInteger(R.integer.default_title_indicator_line_position);
175         final int defaultSelectedColor = res.getColor(R.color.default_title_indicator_selected_color);
176         final boolean defaultSelectedBold = res.getBoolean(R.bool.default_title_indicator_selected_bold);
177         final boolean defaultBoldAll = res.getBoolean(R.bool.default_title_indicator_bold_all);
178         final int defaultTextColor = res.getColor(R.color.default_title_indicator_text_color);
179         final float defaultTextSize = res.getDimension(R.dimen.default_title_indicator_text_size);
180         final float defaultTitlePadding = res.getDimension(R.dimen.default_title_indicator_title_padding);
181         final float defaultClipPadding = res.getDimension(R.dimen.default_title_indicator_clip_padding);
182         final float defaultTopPadding = res.getDimension(R.dimen.default_title_indicator_top_padding);
183
184         //Retrieve styles attributes
185         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TitlePageIndicator, defStyle, 0);
186
187         //Retrieve the colors to be used for this view and apply them.
188         mFooterLineHeight = a.getDimension(R.styleable.TitlePageIndicator_footerLineHeight, defaultFooterLineHeight);
189         mFooterIndicatorStyle = IndicatorStyle.fromValue(a.getInteger(R.styleable.TitlePageIndicator_footerIndicatorStyle, defaultFooterIndicatorStyle));
190         mFooterIndicatorHeight = a.getDimension(R.styleable.TitlePageIndicator_footerIndicatorHeight, defaultFooterIndicatorHeight);
191         mFooterIndicatorUnderlinePadding = a.getDimension(R.styleable.TitlePageIndicator_footerIndicatorUnderlinePadding, defaultFooterIndicatorUnderlinePadding);
192         mFooterPadding = a.getDimension(R.styleable.TitlePageIndicator_footerPadding, defaultFooterPadding);
193         mLinePosition = LinePosition.fromValue(a.getInteger(R.styleable.TitlePageIndicator_linePosition, defaultLinePosition));
194         mTopPadding = a.getDimension(R.styleable.TitlePageIndicator_topPadding, defaultTopPadding);
195         mTitlePadding = a.getDimension(R.styleable.TitlePageIndicator_titlePadding, defaultTitlePadding);
196         mClipPadding = a.getDimension(R.styleable.TitlePageIndicator_clipPadding, defaultClipPadding);
197         mColorSelected = a.getColor(R.styleable.TitlePageIndicator_selectedColor, defaultSelectedColor);
198         mColorText = a.getColor(R.styleable.TitlePageIndicator_android_textColor, defaultTextColor);
199         mBoldText = a.getBoolean(R.styleable.TitlePageIndicator_selectedBold, defaultSelectedBold);
200         mBoldAll = a.getBoolean(R.styleable.TitlePageIndicator_boldAll, defaultBoldAll);
201
202         final float textSize = a.getDimension(R.styleable.TitlePageIndicator_android_textSize, defaultTextSize);
203         final int footerColor = a.getColor(R.styleable.TitlePageIndicator_footerColor, defaultFooterColor);
204         mPaintText.setTextSize(textSize);
205         mPaintText.setAntiAlias(true);
206         mPaintFooterLine.setStyle(Paint.Style.FILL_AND_STROKE);
207         mPaintFooterLine.setStrokeWidth(mFooterLineHeight);
208         mPaintFooterLine.setColor(footerColor);
209         mPaintFooterIndicator.setStyle(Paint.Style.FILL_AND_STROKE);
210         mPaintFooterIndicator.setColor(footerColor);
211
212         Drawable background = a.getDrawable(R.styleable.TitlePageIndicator_android_background);
213         if (background != null) {
214           setBackgroundDrawable(background);
215         }
216
217         a.recycle();
218
219         final ViewConfiguration configuration = ViewConfiguration.get(context);
220         mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
221     }
222
223
224     public int getFooterColor() {
225         return mPaintFooterLine.getColor();
226     }
227
228     public void setFooterColor(int footerColor) {
229         mPaintFooterLine.setColor(footerColor);
230         mPaintFooterIndicator.setColor(footerColor);
231         invalidate();
232     }
233
234     public float getFooterLineHeight() {
235         return mFooterLineHeight;
236     }
237
238     public void setFooterLineHeight(float footerLineHeight) {
239         mFooterLineHeight = footerLineHeight;
240         mPaintFooterLine.setStrokeWidth(mFooterLineHeight);
241         invalidate();
242     }
243
244     public float getFooterIndicatorHeight() {
245         return mFooterIndicatorHeight;
246     }
247
248     public void setFooterIndicatorHeight(float footerTriangleHeight) {
249         mFooterIndicatorHeight = footerTriangleHeight;
250         invalidate();
251     }
252
253     public float getFooterIndicatorPadding() {
254         return mFooterPadding;
255     }
256
257     public void setFooterIndicatorPadding(float footerIndicatorPadding) {
258         mFooterPadding = footerIndicatorPadding;
259         invalidate();
260     }
261
262     public IndicatorStyle getFooterIndicatorStyle() {
263         return mFooterIndicatorStyle;
264     }
265
266     public void setFooterIndicatorStyle(IndicatorStyle indicatorStyle) {
267         mFooterIndicatorStyle = indicatorStyle;
268         invalidate();
269     }
270
271     public LinePosition getLinePosition() {
272         return mLinePosition;
273     }
274
275     public void setLinePosition(LinePosition linePosition) {
276         mLinePosition = linePosition;
277         invalidate();
278     }
279
280     public int getSelectedColor() {
281         return mColorSelected;
282     }
283
284     public void setSelectedColor(int selectedColor) {
285         mColorSelected = selectedColor;
286         invalidate();
287     }
288
289     public boolean isSelectedBold() {
290         return mBoldText;
291     }
292
293     public void setSelectedBold(boolean selectedBold) {
294         mBoldText = selectedBold;
295         invalidate();
296     }
297
298     public boolean isBoldAll() {
299         return mBoldAll;
300     }
301
302     public void setBoldAll(boolean boldAll) {
303         mBoldAll = boldAll;
304         invalidate();
305     }
306
307     public int getTextColor() {
308         return mColorText;
309     }
310
311     public void setTextColor(int textColor) {
312         mPaintText.setColor(textColor);
313         mColorText = textColor;
314         invalidate();
315     }
316
317     public float getTextSize() {
318         return mPaintText.getTextSize();
319     }
320
321     public void setTextSize(float textSize) {
322         mPaintText.setTextSize(textSize);
323         invalidate();
324     }
325
326     public float getTitlePadding() {
327         return this.mTitlePadding;
328     }
329
330     public void setTitlePadding(float titlePadding) {
331         mTitlePadding = titlePadding;
332         invalidate();
333     }
334
335     public float getTopPadding() {
336         return this.mTopPadding;
337     }
338
339     public void setTopPadding(float topPadding) {
340         mTopPadding = topPadding;
341         invalidate();
342     }
343
344     public float getClipPadding() {
345         return this.mClipPadding;
346     }
347
348     public void setClipPadding(float clipPadding) {
349         mClipPadding = clipPadding;
350         invalidate();
351     }
352
353     public void setTypeface(Typeface typeface) {
354         mPaintText.setTypeface(typeface);
355         invalidate();
356     }
357
358     public Typeface getTypeface() {
359         return mPaintText.getTypeface();
360     }
361
362     /*
363      * (non-Javadoc)
364      *
365      * @see android.view.View#onDraw(android.graphics.Canvas)
366      */
367     @Override
368     protected void onDraw(Canvas canvas) {
369         super.onDraw(canvas);
370
371         if (mViewPager == null) {
372             return;
373         }
374         final int count = mViewPager.getAdapter().getCount();
375         if (count == 0) {
376             return;
377         }
378
379         // mCurrentPage is -1 on first start and after orientation changed. If so, retrieve the correct index from viewpager.
380         if (mCurrentPage == -1 && mViewPager != null) {
381             mCurrentPage = mViewPager.getCurrentItem();
382         }
383
384         //Calculate views bounds
385         ArrayList<Rect> bounds = calculateAllBounds(mPaintText);
386         final int boundsSize = bounds.size();
387
388         //Make sure we're on a page that still exists
389         if (mCurrentPage >= boundsSize) {
390             setCurrentItem(boundsSize - 1);
391             return;
392         }
393
394         final int countMinusOne = count - 1;
395         final float halfWidth = getWidth() / 2f;
396         final int left = getLeft();
397         final float leftClip = left + mClipPadding;
398         final int width = getWidth();
399         int height = getHeight();
400         final int right = left + width;
401         final float rightClip = right - mClipPadding;
402
403         int page = mCurrentPage;
404         float offsetPercent;
405         if (mPageOffset <= 0.5) {
406             offsetPercent = mPageOffset;
407         } else {
408             page += 1;
409             offsetPercent = 1 - mPageOffset;
410         }
411         final boolean currentSelected = (offsetPercent <= SELECTION_FADE_PERCENTAGE);
412         final boolean currentBold = (offsetPercent <= BOLD_FADE_PERCENTAGE);
413         final float selectedPercent = (SELECTION_FADE_PERCENTAGE - offsetPercent) / SELECTION_FADE_PERCENTAGE;
414
415         //Verify if the current view must be clipped to the screen
416         Rect curPageBound = bounds.get(mCurrentPage);
417         float curPageWidth = curPageBound.right - curPageBound.left;
418         if (curPageBound.left < leftClip) {
419             //Try to clip to the screen (left side)
420             clipViewOnTheLeft(curPageBound, curPageWidth, left);
421         }
422         if (curPageBound.right > rightClip) {
423             //Try to clip to the screen (right side)
424             clipViewOnTheRight(curPageBound, curPageWidth, right);
425         }
426
427         //Left views starting from the current position
428         if (mCurrentPage > 0) {
429             for (int i = mCurrentPage - 1; i >= 0; i--) {
430                 Rect bound = bounds.get(i);
431                 //Is left side is outside the screen
432                 if (bound.left < leftClip) {
433                     int w = bound.right - bound.left;
434                     //Try to clip to the screen (left side)
435                     clipViewOnTheLeft(bound, w, left);
436                     //Except if there's an intersection with the right view
437                     Rect rightBound = bounds.get(i + 1);
438                     //Intersection
439                     if (bound.right + mTitlePadding > rightBound.left) {
440                         bound.left = (int) (rightBound.left - w - mTitlePadding);
441                         bound.right = bound.left + w;
442                     }
443                 }
444             }
445         }
446         //Right views starting from the current position
447         if (mCurrentPage < countMinusOne) {
448             for (int i = mCurrentPage + 1 ; i < count; i++) {
449                 Rect bound = bounds.get(i);
450                 //If right side is outside the screen
451                 if (bound.right > rightClip) {
452                     int w = bound.right - bound.left;
453                     //Try to clip to the screen (right side)
454                     clipViewOnTheRight(bound, w, right);
455                     //Except if there's an intersection with the left view
456                     Rect leftBound = bounds.get(i - 1);
457                     //Intersection
458                     if (bound.left - mTitlePadding < leftBound.right) {
459                         bound.left = (int) (leftBound.right + mTitlePadding);
460                         bound.right = bound.left + w;
461                     }
462                 }
463             }
464         }
465
466         //Now draw views
467         int colorTextAlpha = mColorText >>> 24;
468         for (int i = 0; i < count; i++) {
469             //Get the title
470             Rect bound = bounds.get(i);
471             //Only if one side is visible
472             if ((bound.left > left && bound.left < right) || (bound.right > left && bound.right < right)) {
473                 final boolean currentPage = (i == page);
474                 final CharSequence pageTitle = getTitle(i);
475
476                 //Only set bold if we are within bounds
477                 mPaintText.setFakeBoldText(mBoldAll || (currentPage && currentBold && mBoldText));
478
479                 //Draw text as unselected
480                 mPaintText.setColor(mColorText);
481                 if(currentPage && currentSelected && !mBoldAll) {
482                     //Fade out/in unselected text as the selected text fades in/out
483                     mPaintText.setAlpha(colorTextAlpha - (int)(colorTextAlpha * selectedPercent));
484                 }
485
486                 //Except if there's an intersection with the right view
487                 if (i < boundsSize - 1)  {
488                     Rect rightBound = bounds.get(i + 1);
489                     //Intersection
490                     if (bound.right + mTitlePadding > rightBound.left) {
491                         int w = bound.right - bound.left;
492                         bound.left = (int) (rightBound.left - w - mTitlePadding);
493                         bound.right = bound.left + w;
494                     }
495                 }
496                 canvas.drawText(pageTitle, 0, pageTitle.length(), bound.left, bound.bottom + mTopPadding, mPaintText);
497
498                 //If we are within the selected bounds draw the selected text
499                 if (currentPage && currentSelected) {
500                     mPaintText.setColor(mColorSelected);
501                     mPaintText.setAlpha((int)((mColorSelected >>> 24) * selectedPercent));
502                     canvas.drawText(pageTitle, 0, pageTitle.length(), bound.left, bound.bottom + mTopPadding, mPaintText);
503                 }
504             }
505         }
506
507         //If we want the line on the top change height to zero and invert the line height to trick the drawing code
508         float footerLineHeight = mFooterLineHeight;
509         float footerIndicatorLineHeight = mFooterIndicatorHeight;
510         if (mLinePosition == LinePosition.Top) {
511             height = 0;
512             footerLineHeight = -footerLineHeight;
513             footerIndicatorLineHeight = -footerIndicatorLineHeight;
514         }
515
516         //Draw the footer line
517         mPath.reset();
518         mPath.moveTo(0, height - footerLineHeight / 2f);
519         mPath.lineTo(width, height - footerLineHeight / 2f);
520         mPath.close();
521         canvas.drawPath(mPath, mPaintFooterLine);
522
523         float heightMinusLine = height - footerLineHeight;
524         switch (mFooterIndicatorStyle) {
525             case Triangle:
526                 mPath.reset();
527                 mPath.moveTo(halfWidth, heightMinusLine - footerIndicatorLineHeight);
528                 mPath.lineTo(halfWidth + footerIndicatorLineHeight, heightMinusLine);
529                 mPath.lineTo(halfWidth - footerIndicatorLineHeight, heightMinusLine);
530                 mPath.close();
531                 canvas.drawPath(mPath, mPaintFooterIndicator);
532                 break;
533
534             case Underline:
535                 if (!currentSelected || page >= boundsSize) {
536                     break;
537                 }
538
539                 Rect underlineBounds = bounds.get(page);
540                 final float rightPlusPadding = underlineBounds.right + mFooterIndicatorUnderlinePadding;
541                 final float leftMinusPadding = underlineBounds.left - mFooterIndicatorUnderlinePadding;
542                 final float heightMinusLineMinusIndicator = heightMinusLine - footerIndicatorLineHeight;
543
544                 mPath.reset();
545                 mPath.moveTo(leftMinusPadding, heightMinusLine);
546                 mPath.lineTo(rightPlusPadding, heightMinusLine);
547                 mPath.lineTo(rightPlusPadding, heightMinusLineMinusIndicator);
548                 mPath.lineTo(leftMinusPadding, heightMinusLineMinusIndicator);
549                 mPath.close();
550
551                 mPaintFooterIndicator.setAlpha((int)(0xFF * selectedPercent));
552                 canvas.drawPath(mPath, mPaintFooterIndicator);
553                 mPaintFooterIndicator.setAlpha(0xFF);
554                 break;
555         }
556     }
557
558     public boolean onTouchEvent(android.view.MotionEvent ev) {
559         if (super.onTouchEvent(ev)) {
560             return true;
561         }
562         if ((mViewPager == null) || (mViewPager.getAdapter().getCount() == 0)) {
563             return false;
564         }
565
566         final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
567         switch (action) {
568             case MotionEvent.ACTION_DOWN:
569                 mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
570                 mLastMotionX = ev.getX();
571                 break;
572
573             case MotionEvent.ACTION_MOVE: {
574                 final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
575                 final float x = MotionEventCompat.getX(ev, activePointerIndex);
576                 final float deltaX = x - mLastMotionX;
577
578                 if (!mIsDragging) {
579                     if (Math.abs(deltaX) > mTouchSlop) {
580                         mIsDragging = true;
581                     }
582                 }
583
584                 if (mIsDragging) {
585                     mLastMotionX = x;
586                     if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag()) {
587                         mViewPager.fakeDragBy(deltaX);
588                     }
589                 }
590
591                 break;
592             }
593
594             case MotionEvent.ACTION_CANCEL:
595             case MotionEvent.ACTION_UP:
596                 if (!mIsDragging) {
597                     final int count = mViewPager.getAdapter().getCount();
598                     final int width = getWidth();
599                     final float halfWidth = width / 2f;
600                     final float sixthWidth = width / 6f;
601                     final float leftThird = halfWidth - sixthWidth;
602                     final float rightThird = halfWidth + sixthWidth;
603                     final float eventX = ev.getX();
604
605                     if (eventX < leftThird) {
606                         if (mCurrentPage > 0) {
607                             if (action != MotionEvent.ACTION_CANCEL) {
608                                 mViewPager.setCurrentItem(mCurrentPage - 1);
609                             }
610                             return true;
611                         }
612                     } else if (eventX > rightThird) {
613                         if (mCurrentPage < count - 1) {
614                             if (action != MotionEvent.ACTION_CANCEL) {
615                                 mViewPager.setCurrentItem(mCurrentPage + 1);
616                             }
617                             return true;
618                         }
619                     } else {
620                         //Middle third
621                         if (mCenterItemClickListener != null && action != MotionEvent.ACTION_CANCEL) {
622                             mCenterItemClickListener.onCenterItemClick(mCurrentPage);
623                         }
624                     }
625                 }
626
627                 mIsDragging = false;
628                 mActivePointerId = INVALID_POINTER;
629                 if (mViewPager.isFakeDragging()) mViewPager.endFakeDrag();
630                 break;
631
632             case MotionEventCompat.ACTION_POINTER_DOWN: {
633                 final int index = MotionEventCompat.getActionIndex(ev);
634                 mLastMotionX = MotionEventCompat.getX(ev, index);
635                 mActivePointerId = MotionEventCompat.getPointerId(ev, index);
636                 break;
637             }
638
639             case MotionEventCompat.ACTION_POINTER_UP:
640                 final int pointerIndex = MotionEventCompat.getActionIndex(ev);
641                 final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
642                 if (pointerId == mActivePointerId) {
643                     final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
644                     mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
645                 }
646                 mLastMotionX = MotionEventCompat.getX(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId));
647                 break;
648         }
649
650         return true;
651     }
652
653     /**
654      * Set bounds for the right textView including clip padding.
655      *
656      * @param curViewBound
657      *            current bounds.
658      * @param curViewWidth
659      *            width of the view.
660      */
661     private void clipViewOnTheRight(Rect curViewBound, float curViewWidth, int right) {
662         curViewBound.right = (int) (right - mClipPadding);
663         curViewBound.left = (int) (curViewBound.right - curViewWidth);
664     }
665
666     /**
667      * Set bounds for the left textView including clip padding.
668      *
669      * @param curViewBound
670      *            current bounds.
671      * @param curViewWidth
672      *            width of the view.
673      */
674     private void clipViewOnTheLeft(Rect curViewBound, float curViewWidth, int left) {
675         curViewBound.left = (int) (left + mClipPadding);
676         curViewBound.right = (int) (mClipPadding + curViewWidth);
677     }
678
679     /**
680      * Calculate views bounds and scroll them according to the current index
681      *
682      * @param paint
683      * @return
684      */
685     private ArrayList<Rect> calculateAllBounds(Paint paint) {
686         ArrayList<Rect> list = new ArrayList<Rect>();
687         //For each views (If no values then add a fake one)
688         final int count = mViewPager.getAdapter().getCount();
689         final int width = getWidth();
690         final int halfWidth = width / 2;
691         for (int i = 0; i < count; i++) {
692             Rect bounds = calcBounds(i, paint);
693             int w = bounds.right - bounds.left;
694             int h = bounds.bottom - bounds.top;
695             bounds.left = (int)(halfWidth - (w / 2f) + ((i - mCurrentPage - mPageOffset) * width));
696             bounds.right = bounds.left + w;
697             bounds.top = 0;
698             bounds.bottom = h;
699             list.add(bounds);
700         }
701
702         return list;
703     }
704
705     /**
706      * Calculate the bounds for a view's title
707      *
708      * @param index
709      * @param paint
710      * @return
711      */
712     private Rect calcBounds(int index, Paint paint) {
713         //Calculate the text bounds
714         Rect bounds = new Rect();
715         CharSequence title = getTitle(index);
716         bounds.right = (int) paint.measureText(title, 0, title.length());
717         bounds.bottom = (int) (paint.descent() - paint.ascent());
718         return bounds;
719     }
720
721     @Override
722     public void setViewPager(ViewPager view) {
723         if (mViewPager == view) {
724             return;
725         }
726         if (mViewPager != null) {
727             mViewPager.setOnPageChangeListener(null);
728         }
729         if (view.getAdapter() == null) {
730             throw new IllegalStateException("ViewPager does not have adapter instance.");
731         }
732         mViewPager = view;
733         mViewPager.setOnPageChangeListener(this);
734         invalidate();
735     }
736
737     @Override
738     public void setViewPager(ViewPager view, int initialPosition) {
739         setViewPager(view);
740         setCurrentItem(initialPosition);
741     }
742
743     @Override
744     public void notifyDataSetChanged() {
745         invalidate();
746     }
747
748     /**
749      * Set a callback listener for the center item click.
750      *
751      * @param listener Callback instance.
752      */
753     public void setOnCenterItemClickListener(OnCenterItemClickListener listener) {
754         mCenterItemClickListener = listener;
755     }
756
757     @Override
758     public void setCurrentItem(int item) {
759         if (mViewPager == null) {
760             throw new IllegalStateException("ViewPager has not been bound.");
761         }
762         mViewPager.setCurrentItem(item);
763         mCurrentPage = item;
764         invalidate();
765     }
766
767     @Override
768     public void onPageScrollStateChanged(int state) {
769         mScrollState = state;
770
771         if (mListener != null) {
772             mListener.onPageScrollStateChanged(state);
773         }
774     }
775
776     @Override
777     public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
778         mCurrentPage = position;
779         mPageOffset = positionOffset;
780         invalidate();
781
782         if (mListener != null) {
783             mListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
784         }
785     }
786
787     @Override
788     public void onPageSelected(int position) {
789         if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
790             mCurrentPage = position;
791             invalidate();
792         }
793
794         if (mListener != null) {
795             mListener.onPageSelected(position);
796         }
797     }
798
799     @Override
800     public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
801         mListener = listener;
802     }
803
804     @Override
805     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
806         //Measure our width in whatever mode specified
807         final int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
808
809         //Determine our height
810         float height;
811         final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
812         if (heightSpecMode == MeasureSpec.EXACTLY) {
813             //We were told how big to be
814             height = MeasureSpec.getSize(heightMeasureSpec);
815         } else {
816             //Calculate the text bounds
817             mBounds.setEmpty();
818             mBounds.bottom = (int) (mPaintText.descent() - mPaintText.ascent());
819             height = mBounds.bottom - mBounds.top + mFooterLineHeight + mFooterPadding + mTopPadding;
820             if (mFooterIndicatorStyle != IndicatorStyle.None) {
821                 height += mFooterIndicatorHeight;
822             }
823         }
824         final int measuredHeight = (int)height;
825
826         setMeasuredDimension(measuredWidth, measuredHeight);
827     }
828
829     @Override
830     public void onRestoreInstanceState(Parcelable state) {
831         SavedState savedState = (SavedState)state;
832         super.onRestoreInstanceState(savedState.getSuperState());
833         mCurrentPage = savedState.currentPage;
834         requestLayout();
835     }
836
837     @Override
838     public Parcelable onSaveInstanceState() {
839         Parcelable superState = super.onSaveInstanceState();
840         SavedState savedState = new SavedState(superState);
841         savedState.currentPage = mCurrentPage;
842         return savedState;
843     }
844
845     static class SavedState extends BaseSavedState {
846         int currentPage;
847
848         public SavedState(Parcelable superState) {
849             super(superState);
850         }
851
852         private SavedState(Parcel in) {
853             super(in);
854             currentPage = in.readInt();
855         }
856
857         @Override
858         public void writeToParcel(Parcel dest, int flags) {
859             super.writeToParcel(dest, flags);
860             dest.writeInt(currentPage);
861         }
862
863         @SuppressWarnings("UnusedDeclaration")
864         public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
865             @Override
866             public SavedState createFromParcel(Parcel in) {
867                 return new SavedState(in);
868             }
869
870             @Override
871             public SavedState[] newArray(int size) {
872                 return new SavedState[size];
873             }
874         };
875     }
876
877     private CharSequence getTitle(int i) {
878         CharSequence title = mViewPager.getAdapter().getPageTitle(i);
879         if (title == null) {
880             title = EMPTY_TITLE;
881         }
882         return title;
883     }
884 }