2 * Copyright (C) 2012 Jake Wharton
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.viewpagerindicator;
18 import android.content.Context;
19 import android.content.res.Resources;
20 import android.content.res.TypedArray;
21 import android.graphics.Canvas;
22 import android.graphics.Paint;
23 import android.graphics.drawable.Drawable;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.support.v4.view.MotionEventCompat;
27 import android.support.v4.view.ViewConfigurationCompat;
28 import android.support.v4.view.ViewPager;
29 import android.util.AttributeSet;
30 import android.view.MotionEvent;
31 import android.view.View;
32 import android.view.ViewConfiguration;
34 import com.cyanogenmod.eleven.R;
37 * Draws a line for each page. The current page line is colored differently
38 * than the unselected page lines.
40 public class UnderlinePageIndicator extends View implements PageIndicator {
41 private static final int INVALID_POINTER = -1;
42 private static final int FADE_FRAME_MS = 30;
44 private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
46 private boolean mFades;
47 private int mFadeDelay;
48 private int mFadeLength;
51 private ViewPager mViewPager;
52 private ViewPager.OnPageChangeListener mListener;
53 private int mScrollState;
54 private int mCurrentPage;
55 private float mPositionOffset;
57 private int mTouchSlop;
58 private float mLastMotionX = -1;
59 private int mActivePointerId = INVALID_POINTER;
60 private boolean mIsDragging;
62 private final Runnable mFadeRunnable = new Runnable() {
63 @Override public void run() {
66 final int alpha = Math.max(mPaint.getAlpha() - mFadeBy, 0);
67 mPaint.setAlpha(alpha);
70 postDelayed(this, FADE_FRAME_MS);
75 public UnderlinePageIndicator(Context context) {
79 public UnderlinePageIndicator(Context context, AttributeSet attrs) {
80 this(context, attrs, R.attr.vpiUnderlinePageIndicatorStyle);
83 public UnderlinePageIndicator(Context context, AttributeSet attrs, int defStyle) {
84 super(context, attrs, defStyle);
85 if (isInEditMode()) return;
87 final Resources res = getResources();
89 //Load defaults from resources
90 final boolean defaultFades = res.getBoolean(R.bool.default_underline_indicator_fades);
91 final int defaultFadeDelay = res.getInteger(R.integer.default_underline_indicator_fade_delay);
92 final int defaultFadeLength = res.getInteger(R.integer.default_underline_indicator_fade_length);
93 final int defaultSelectedColor = res.getColor(R.color.default_underline_indicator_selected_color);
95 //Retrieve styles attributes
96 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.UnderlinePageIndicator, defStyle, 0);
98 setFades(a.getBoolean(R.styleable.UnderlinePageIndicator_fades, defaultFades));
99 setSelectedColor(a.getColor(R.styleable.UnderlinePageIndicator_selectedColor, defaultSelectedColor));
100 setFadeDelay(a.getInteger(R.styleable.UnderlinePageIndicator_fadeDelay, defaultFadeDelay));
101 setFadeLength(a.getInteger(R.styleable.UnderlinePageIndicator_fadeLength, defaultFadeLength));
103 Drawable background = a.getDrawable(R.styleable.UnderlinePageIndicator_android_background);
104 if (background != null) {
105 setBackgroundDrawable(background);
110 final ViewConfiguration configuration = ViewConfiguration.get(context);
111 mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
114 public boolean getFades() {
118 public void setFades(boolean fades) {
119 if (fades != mFades) {
124 removeCallbacks(mFadeRunnable);
125 mPaint.setAlpha(0xFF);
131 public int getFadeDelay() {
135 public void setFadeDelay(int fadeDelay) {
136 mFadeDelay = fadeDelay;
139 public int getFadeLength() {
143 public void setFadeLength(int fadeLength) {
144 mFadeLength = fadeLength;
145 mFadeBy = 0xFF / (mFadeLength / FADE_FRAME_MS);
148 public int getSelectedColor() {
149 return mPaint.getColor();
152 public void setSelectedColor(int selectedColor) {
153 mPaint.setColor(selectedColor);
158 protected void onDraw(Canvas canvas) {
159 super.onDraw(canvas);
161 if (mViewPager == null) {
164 final int count = mViewPager.getAdapter().getCount();
169 if (mCurrentPage >= count) {
170 setCurrentItem(count - 1);
174 final int paddingLeft = getPaddingLeft();
175 final float pageWidth = (getWidth() - paddingLeft - getPaddingRight()) / (1f * count);
176 final float left = paddingLeft + pageWidth * (mCurrentPage + mPositionOffset);
177 final float right = left + pageWidth;
178 final float top = getPaddingTop();
179 final float bottom = getHeight() - getPaddingBottom();
180 canvas.drawRect(left, top, right, bottom, mPaint);
183 public boolean onTouchEvent(MotionEvent ev) {
184 if (super.onTouchEvent(ev)) {
187 if ((mViewPager == null) || (mViewPager.getAdapter().getCount() == 0)) {
191 final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
193 case MotionEvent.ACTION_DOWN:
194 mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
195 mLastMotionX = ev.getX();
198 case MotionEvent.ACTION_MOVE: {
199 final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
200 final float x = MotionEventCompat.getX(ev, activePointerIndex);
201 final float deltaX = x - mLastMotionX;
204 if (Math.abs(deltaX) > mTouchSlop) {
211 if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag()) {
212 mViewPager.fakeDragBy(deltaX);
219 case MotionEvent.ACTION_CANCEL:
220 case MotionEvent.ACTION_UP:
222 final int count = mViewPager.getAdapter().getCount();
223 final int width = getWidth();
224 final float halfWidth = width / 2f;
225 final float sixthWidth = width / 6f;
227 if ((mCurrentPage > 0) && (ev.getX() < halfWidth - sixthWidth)) {
228 if (action != MotionEvent.ACTION_CANCEL) {
229 mViewPager.setCurrentItem(mCurrentPage - 1);
232 } else if ((mCurrentPage < count - 1) && (ev.getX() > halfWidth + sixthWidth)) {
233 if (action != MotionEvent.ACTION_CANCEL) {
234 mViewPager.setCurrentItem(mCurrentPage + 1);
241 mActivePointerId = INVALID_POINTER;
242 if (mViewPager.isFakeDragging()) mViewPager.endFakeDrag();
245 case MotionEventCompat.ACTION_POINTER_DOWN: {
246 final int index = MotionEventCompat.getActionIndex(ev);
247 mLastMotionX = MotionEventCompat.getX(ev, index);
248 mActivePointerId = MotionEventCompat.getPointerId(ev, index);
252 case MotionEventCompat.ACTION_POINTER_UP:
253 final int pointerIndex = MotionEventCompat.getActionIndex(ev);
254 final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
255 if (pointerId == mActivePointerId) {
256 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
257 mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
259 mLastMotionX = MotionEventCompat.getX(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId));
267 public void setViewPager(ViewPager viewPager) {
268 if (mViewPager == viewPager) {
271 if (mViewPager != null) {
272 //Clear us from the old pager.
273 mViewPager.setOnPageChangeListener(null);
275 if (viewPager.getAdapter() == null) {
276 throw new IllegalStateException("ViewPager does not have adapter instance.");
278 mViewPager = viewPager;
279 mViewPager.setOnPageChangeListener(this);
281 post(new Runnable() {
282 @Override public void run() {
291 public void setViewPager(ViewPager view, int initialPosition) {
293 setCurrentItem(initialPosition);
297 public void setCurrentItem(int item) {
298 if (mViewPager == null) {
299 throw new IllegalStateException("ViewPager has not been bound.");
301 mViewPager.setCurrentItem(item);
307 public void notifyDataSetChanged() {
312 public void onPageScrollStateChanged(int state) {
313 mScrollState = state;
315 if (mListener != null) {
316 mListener.onPageScrollStateChanged(state);
321 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
322 mCurrentPage = position;
323 mPositionOffset = positionOffset;
325 if (positionOffsetPixels > 0) {
326 removeCallbacks(mFadeRunnable);
327 mPaint.setAlpha(0xFF);
328 } else if (mScrollState != ViewPager.SCROLL_STATE_DRAGGING) {
329 postDelayed(mFadeRunnable, mFadeDelay);
334 if (mListener != null) {
335 mListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
340 public void onPageSelected(int position) {
341 if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
342 mCurrentPage = position;
347 if (mListener != null) {
348 mListener.onPageSelected(position);
353 public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
354 mListener = listener;
358 public void onRestoreInstanceState(Parcelable state) {
359 SavedState savedState = (SavedState)state;
360 super.onRestoreInstanceState(savedState.getSuperState());
361 mCurrentPage = savedState.currentPage;
366 public Parcelable onSaveInstanceState() {
367 Parcelable superState = super.onSaveInstanceState();
368 SavedState savedState = new SavedState(superState);
369 savedState.currentPage = mCurrentPage;
373 static class SavedState extends BaseSavedState {
376 public SavedState(Parcelable superState) {
380 private SavedState(Parcel in) {
382 currentPage = in.readInt();
386 public void writeToParcel(Parcel dest, int flags) {
387 super.writeToParcel(dest, flags);
388 dest.writeInt(currentPage);
391 @SuppressWarnings("UnusedDeclaration")
392 public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
394 public SavedState createFromParcel(Parcel in) {
395 return new SavedState(in);
399 public SavedState[] newArray(int size) {
400 return new SavedState[size];