Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
23410 tejbeer 1
/*
2
 * Copyright (C) 2012 Jake Wharton
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
package com.viewpagerindicator;
17
 
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.util.FloatMath;
31
import android.view.MotionEvent;
32
import android.view.View;
33
import android.view.ViewConfiguration;
34
 
35
/**
36
 * Draws a line for each page. The current page line is colored differently
37
 * than the unselected page lines.
38
 */
39
public class LinePageIndicator extends View implements PageIndicator {
40
    private static final int INVALID_POINTER = -1;
41
 
42
    private final Paint mPaintUnselected = new Paint(Paint.ANTI_ALIAS_FLAG);
43
    private final Paint mPaintSelected = new Paint(Paint.ANTI_ALIAS_FLAG);
44
    private ViewPager mViewPager;
45
    private ViewPager.OnPageChangeListener mListener;
46
    private int mCurrentPage;
47
    private boolean mCentered;
48
    private float mLineWidth;
49
    private float mGapWidth;
50
 
51
    private int mTouchSlop;
52
    private float mLastMotionX = -1;
53
    private int mActivePointerId = INVALID_POINTER;
54
    private boolean mIsDragging;
55
 
56
 
57
    public LinePageIndicator(Context context) {
58
        this(context, null);
59
    }
60
 
61
    public LinePageIndicator(Context context, AttributeSet attrs) {
62
        this(context, attrs, R.attr.vpiLinePageIndicatorStyle);
63
    }
64
 
65
    public LinePageIndicator(Context context, AttributeSet attrs, int defStyle) {
66
        super(context, attrs, defStyle);
67
        if (isInEditMode()) return;
68
 
69
        final Resources res = getResources();
70
 
71
        //Load defaults from resources
72
        final int defaultSelectedColor = res.getColor(R.color.default_line_indicator_selected_color);
73
        final int defaultUnselectedColor = res.getColor(R.color.default_line_indicator_unselected_color);
74
        final float defaultLineWidth = res.getDimension(R.dimen.default_line_indicator_line_width);
75
        final float defaultGapWidth = res.getDimension(R.dimen.default_line_indicator_gap_width);
76
        final float defaultStrokeWidth = res.getDimension(R.dimen.default_line_indicator_stroke_width);
77
        final boolean defaultCentered = res.getBoolean(R.bool.default_line_indicator_centered);
78
 
79
        //Retrieve styles attributes
80
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LinePageIndicator, defStyle, 0);
81
 
82
        mCentered = a.getBoolean(R.styleable.LinePageIndicator_centered, defaultCentered);
83
        mLineWidth = a.getDimension(R.styleable.LinePageIndicator_lineWidth, defaultLineWidth);
84
        mGapWidth = a.getDimension(R.styleable.LinePageIndicator_gapWidth, defaultGapWidth);
85
        setStrokeWidth(a.getDimension(R.styleable.LinePageIndicator_strokeWidth, defaultStrokeWidth));
86
        mPaintUnselected.setColor(a.getColor(R.styleable.LinePageIndicator_unselectedColor, defaultUnselectedColor));
87
        mPaintSelected.setColor(a.getColor(R.styleable.LinePageIndicator_selectedColor, defaultSelectedColor));
88
 
89
        Drawable background = a.getDrawable(R.styleable.LinePageIndicator_android_background);
90
        if (background != null) {
91
          setBackgroundDrawable(background);
92
        }
93
 
94
        a.recycle();
95
 
96
        final ViewConfiguration configuration = ViewConfiguration.get(context);
97
        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
98
    }
99
 
100
 
101
    public void setCentered(boolean centered) {
102
        mCentered = centered;
103
        invalidate();
104
    }
105
 
106
    public boolean isCentered() {
107
        return mCentered;
108
    }
109
 
110
    public void setUnselectedColor(int unselectedColor) {
111
        mPaintUnselected.setColor(unselectedColor);
112
        invalidate();
113
    }
114
 
115
    public int getUnselectedColor() {
116
        return mPaintUnselected.getColor();
117
    }
118
 
119
    public void setSelectedColor(int selectedColor) {
120
        mPaintSelected.setColor(selectedColor);
121
        invalidate();
122
    }
123
 
124
    public int getSelectedColor() {
125
        return mPaintSelected.getColor();
126
    }
127
 
128
    public void setLineWidth(float lineWidth) {
129
        mLineWidth = lineWidth;
130
        invalidate();
131
    }
132
 
133
    public float getLineWidth() {
134
        return mLineWidth;
135
    }
136
 
137
    public void setStrokeWidth(float lineHeight) {
138
        mPaintSelected.setStrokeWidth(lineHeight);
139
        mPaintUnselected.setStrokeWidth(lineHeight);
140
        invalidate();
141
    }
142
 
143
    public float getStrokeWidth() {
144
        return mPaintSelected.getStrokeWidth();
145
    }
146
 
147
    public void setGapWidth(float gapWidth) {
148
        mGapWidth = gapWidth;
149
        invalidate();
150
    }
151
 
152
    public float getGapWidth() {
153
        return mGapWidth;
154
    }
155
 
156
    @Override
157
    protected void onDraw(Canvas canvas) {
158
        super.onDraw(canvas);
159
 
160
        if (mViewPager == null) {
161
            return;
162
        }
163
        final int count = mViewPager.getAdapter().getCount();
164
        if (count == 0) {
165
            return;
166
        }
167
 
168
        if (mCurrentPage >= count) {
169
            setCurrentItem(count - 1);
170
            return;
171
        }
172
 
173
        final float lineWidthAndGap = mLineWidth + mGapWidth;
174
        final float indicatorWidth = (count * lineWidthAndGap) - mGapWidth;
175
        final float paddingTop = getPaddingTop();
176
        final float paddingLeft = getPaddingLeft();
177
        final float paddingRight = getPaddingRight();
178
 
179
        float verticalOffset = paddingTop + ((getHeight() - paddingTop - getPaddingBottom()) / 2.0f);
180
        float horizontalOffset = paddingLeft;
181
        if (mCentered) {
182
            horizontalOffset += ((getWidth() - paddingLeft - paddingRight) / 2.0f) - (indicatorWidth / 2.0f);
183
        }
184
 
185
        //Draw stroked circles
186
        for (int i = 0; i < count; i++) {
187
            float dx1 = horizontalOffset + (i * lineWidthAndGap);
188
            float dx2 = dx1 + mLineWidth;
189
            canvas.drawLine(dx1, verticalOffset, dx2, verticalOffset, (i == mCurrentPage) ? mPaintSelected : mPaintUnselected);
190
        }
191
    }
192
 
193
    public boolean onTouchEvent(android.view.MotionEvent ev) {
194
        if (super.onTouchEvent(ev)) {
195
            return true;
196
        }
197
        if ((mViewPager == null) || (mViewPager.getAdapter().getCount() == 0)) {
198
            return false;
199
        }
200
 
201
        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
202
        switch (action) {
203
            case MotionEvent.ACTION_DOWN:
204
                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
205
                mLastMotionX = ev.getX();
206
                break;
207
 
208
            case MotionEvent.ACTION_MOVE: {
209
                final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
210
                final float x = MotionEventCompat.getX(ev, activePointerIndex);
211
                final float deltaX = x - mLastMotionX;
212
 
213
                if (!mIsDragging) {
214
                    if (Math.abs(deltaX) > mTouchSlop) {
215
                        mIsDragging = true;
216
                    }
217
                }
218
 
219
                if (mIsDragging) {
220
                    mLastMotionX = x;
221
                    if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag()) {
222
                        mViewPager.fakeDragBy(deltaX);
223
                    }
224
                }
225
 
226
                break;
227
            }
228
 
229
            case MotionEvent.ACTION_CANCEL:
230
            case MotionEvent.ACTION_UP:
231
                if (!mIsDragging) {
232
                    final int count = mViewPager.getAdapter().getCount();
233
                    final int width = getWidth();
234
                    final float halfWidth = width / 2f;
235
                    final float sixthWidth = width / 6f;
236
 
237
                    if ((mCurrentPage > 0) && (ev.getX() < halfWidth - sixthWidth)) {
238
                        if (action != MotionEvent.ACTION_CANCEL) {
239
                            mViewPager.setCurrentItem(mCurrentPage - 1);
240
                        }
241
                        return true;
242
                    } else if ((mCurrentPage < count - 1) && (ev.getX() > halfWidth + sixthWidth)) {
243
                        if (action != MotionEvent.ACTION_CANCEL) {
244
                            mViewPager.setCurrentItem(mCurrentPage + 1);
245
                        }
246
                        return true;
247
                    }
248
                }
249
 
250
                mIsDragging = false;
251
                mActivePointerId = INVALID_POINTER;
252
                if (mViewPager.isFakeDragging()) mViewPager.endFakeDrag();
253
                break;
254
 
255
            case MotionEventCompat.ACTION_POINTER_DOWN: {
256
                final int index = MotionEventCompat.getActionIndex(ev);
257
                mLastMotionX = MotionEventCompat.getX(ev, index);
258
                mActivePointerId = MotionEventCompat.getPointerId(ev, index);
259
                break;
260
            }
261
 
262
            case MotionEventCompat.ACTION_POINTER_UP:
263
                final int pointerIndex = MotionEventCompat.getActionIndex(ev);
264
                final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
265
                if (pointerId == mActivePointerId) {
266
                    final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
267
                    mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
268
                }
269
                mLastMotionX = MotionEventCompat.getX(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId));
270
                break;
271
        }
272
 
273
        return true;
274
    }
275
 
276
    @Override
277
    public void setViewPager(ViewPager viewPager) {
278
        if (mViewPager == viewPager) {
279
            return;
280
        }
281
        if (mViewPager != null) {
282
            //Clear us from the old pager.
283
            mViewPager.setOnPageChangeListener(null);
284
        }
285
        if (viewPager.getAdapter() == null) {
286
            throw new IllegalStateException("ViewPager does not have adapter instance.");
287
        }
288
        mViewPager = viewPager;
289
        mViewPager.setOnPageChangeListener(this);
290
        invalidate();
291
    }
292
 
293
    @Override
294
    public void setViewPager(ViewPager view, int initialPosition) {
295
        setViewPager(view);
296
        setCurrentItem(initialPosition);
297
    }
298
 
299
    @Override
300
    public void setCurrentItem(int item) {
301
        if (mViewPager == null) {
302
            throw new IllegalStateException("ViewPager has not been bound.");
303
        }
304
        mViewPager.setCurrentItem(item);
305
        mCurrentPage = item;
306
        invalidate();
307
    }
308
 
309
    @Override
310
    public void notifyDataSetChanged() {
311
        invalidate();
312
    }
313
 
314
    @Override
315
    public void onPageScrollStateChanged(int state) {
316
        if (mListener != null) {
317
            mListener.onPageScrollStateChanged(state);
318
        }
319
    }
320
 
321
    @Override
322
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
323
        if (mListener != null) {
324
            mListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
325
        }
326
    }
327
 
328
    @Override
329
    public void onPageSelected(int position) {
330
        mCurrentPage = position;
331
        invalidate();
332
 
333
        if (mListener != null) {
334
            mListener.onPageSelected(position);
335
        }
336
    }
337
 
338
    @Override
339
    public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
340
        mListener = listener;
341
    }
342
 
343
    @Override
344
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
345
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
346
    }
347
 
348
    /**
349
     * Determines the width of this view
350
     *
351
     * @param measureSpec
352
     *            A measureSpec packed into an int
353
     * @return The width of the view, honoring constraints from measureSpec
354
     */
355
    private int measureWidth(int measureSpec) {
356
        float result;
357
        int specMode = MeasureSpec.getMode(measureSpec);
358
        int specSize = MeasureSpec.getSize(measureSpec);
359
 
360
        if ((specMode == MeasureSpec.EXACTLY) || (mViewPager == null)) {
361
            //We were told how big to be
362
            result = specSize;
363
        } else {
364
            //Calculate the width according the views count
365
            final int count = mViewPager.getAdapter().getCount();
366
            result = getPaddingLeft() + getPaddingRight() + (count * mLineWidth) + ((count - 1) * mGapWidth);
367
            //Respect AT_MOST value if that was what is called for by measureSpec
368
            if (specMode == MeasureSpec.AT_MOST) {
369
                result = Math.min(result, specSize);
370
            }
371
        }
372
        return (int)FloatMath.ceil(result);
373
    }
374
 
375
    /**
376
     * Determines the height of this view
377
     *
378
     * @param measureSpec
379
     *            A measureSpec packed into an int
380
     * @return The height of the view, honoring constraints from measureSpec
381
     */
382
    private int measureHeight(int measureSpec) {
383
        float result;
384
        int specMode = MeasureSpec.getMode(measureSpec);
385
        int specSize = MeasureSpec.getSize(measureSpec);
386
 
387
        if (specMode == MeasureSpec.EXACTLY) {
388
            //We were told how big to be
389
            result = specSize;
390
        } else {
391
            //Measure the height
392
            result = mPaintSelected.getStrokeWidth() + getPaddingTop() + getPaddingBottom();
393
            //Respect AT_MOST value if that was what is called for by measureSpec
394
            if (specMode == MeasureSpec.AT_MOST) {
395
                result = Math.min(result, specSize);
396
            }
397
        }
398
        return (int)FloatMath.ceil(result);
399
    }
400
 
401
    @Override
402
    public void onRestoreInstanceState(Parcelable state) {
403
        SavedState savedState = (SavedState)state;
404
        super.onRestoreInstanceState(savedState.getSuperState());
405
        mCurrentPage = savedState.currentPage;
406
        requestLayout();
407
    }
408
 
409
    @Override
410
    public Parcelable onSaveInstanceState() {
411
        Parcelable superState = super.onSaveInstanceState();
412
        SavedState savedState = new SavedState(superState);
413
        savedState.currentPage = mCurrentPage;
414
        return savedState;
415
    }
416
 
417
    static class SavedState extends BaseSavedState {
418
        int currentPage;
419
 
420
        public SavedState(Parcelable superState) {
421
            super(superState);
422
        }
423
 
424
        private SavedState(Parcel in) {
425
            super(in);
426
            currentPage = in.readInt();
427
        }
428
 
429
        @Override
430
        public void writeToParcel(Parcel dest, int flags) {
431
            super.writeToParcel(dest, flags);
432
            dest.writeInt(currentPage);
433
        }
434
 
435
        @SuppressWarnings("UnusedDeclaration")
436
        public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
437
            @Override
438
            public SavedState createFromParcel(Parcel in) {
439
                return new SavedState(in);
440
            }
441
 
442
            @Override
443
            public SavedState[] newArray(int size) {
444
                return new SavedState[size];
445
            }
446
        };
447
    }
448
}