{
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/viewpagerindicator/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/viewpagerindicator/src/main/java/me/caiying/library/viewpagerindicator/BadgeView.java:
--------------------------------------------------------------------------------
1 | package me.caiying.library.viewpagerindicator;
2 |
3 |
4 | import android.content.Context;
5 | import android.content.res.Resources;
6 | import android.graphics.Color;
7 | import android.graphics.Typeface;
8 | import android.graphics.drawable.Drawable;
9 | import android.graphics.drawable.ShapeDrawable;
10 | import android.graphics.drawable.shapes.RoundRectShape;
11 | import android.text.TextUtils;
12 | import android.util.AttributeSet;
13 | import android.util.TypedValue;
14 | import android.view.Gravity;
15 | import android.view.View;
16 | import android.view.ViewGroup;
17 | import android.view.ViewGroup.LayoutParams;
18 | import android.view.ViewParent;
19 | import android.view.animation.AccelerateInterpolator;
20 | import android.view.animation.AlphaAnimation;
21 | import android.view.animation.Animation;
22 | import android.view.animation.DecelerateInterpolator;
23 | import android.widget.FrameLayout;
24 | import android.widget.ImageView;
25 | import android.widget.TabWidget;
26 | import android.widget.TextView;
27 |
28 |
29 | /**
30 | * A simple text label view that can be applied as a "badge" to any given {@link android.view.View}.
31 | * This class is intended to be instantiated at runtime rather than included in XML layouts.
32 | */
33 | public class BadgeView extends TextView {
34 |
35 | public static final int POSITION_TOP_LEFT = 1;
36 | public static final int POSITION_TOP_RIGHT = 2;
37 | public static final int POSITION_BOTTOM_LEFT = 3;
38 | public static final int POSITION_BOTTOM_RIGHT = 4;
39 | public static final int POSITION_CENTER = 5;
40 |
41 | private static final int DEFAULT_MARGIN_DIP = 5;
42 | private static final int DEFAULT_LR_PADDING_DIP = 5;
43 | private static final int DEFAULT_CORNER_RADIUS_DIP = 8;
44 | private static final int DEFAULT_POSITION = POSITION_TOP_RIGHT;
45 | private static final int DEFAULT_BADGE_COLOR = Color.parseColor("#ff0000");
46 | private static final int DEFAULT_TEXT_COLOR = Color.WHITE;
47 |
48 | private static Animation fadeIn;
49 | private static Animation fadeOut;
50 |
51 | private Context context;
52 | private View target;
53 | private ImageView mNoneTipsView;
54 |
55 | private int badgePosition;
56 | private int badgeMarginH;
57 | private int badgeMarginV;
58 | private int badgeColor;
59 |
60 | private boolean isShown;
61 |
62 | private Drawable badgeBg;
63 |
64 | private int targetTabIndex;
65 |
66 | public BadgeView(Context context) {
67 | this(context, (AttributeSet) null, android.R.attr.textViewStyle);
68 | }
69 |
70 | public BadgeView(Context context, AttributeSet attrs) {
71 | this(context, attrs, android.R.attr.textViewStyle);
72 | }
73 |
74 | /**
75 | * Constructor -
76 | *
77 | * create a new BadgeView instance attached to a target {@link android.view.View}.
78 | *
79 | * @param context context for this view.
80 | * @param target the View to attach the badge to.
81 | */
82 | public BadgeView(Context context, View target) {
83 | this(context, null, android.R.attr.textViewStyle, target, 0);
84 | }
85 |
86 | /**
87 | * Constructor -
88 | *
89 | * create a new BadgeView instance attached to a target {@link android.widget.TabWidget}
90 | * tab at a given index.
91 | *
92 | * @param context context for this view.
93 | * @param target the TabWidget to attach the badge to.
94 | * @param index the position of the tab within the target.
95 | */
96 | public BadgeView(Context context, TabWidget target, int index) {
97 | this(context, null, android.R.attr.textViewStyle, target, index);
98 | }
99 |
100 | public BadgeView(Context context, AttributeSet attrs, int defStyle) {
101 | this(context, attrs, defStyle, null, 0);
102 | }
103 |
104 | public BadgeView(Context context, AttributeSet attrs, int defStyle, View target, int tabIndex) {
105 | super(context, attrs, defStyle);
106 | init(context, target, tabIndex);
107 | }
108 |
109 | private void init(Context context, View target, int tabIndex) {
110 | setTextSize(12f / getResources().getConfiguration().fontScale);
111 | this.context = context;
112 | this.target = target;
113 | this.targetTabIndex = tabIndex;
114 |
115 | // apply defaults
116 | badgePosition = DEFAULT_POSITION;
117 | badgeMarginH = dipToPixels(DEFAULT_MARGIN_DIP);
118 | badgeMarginV = badgeMarginH;
119 | badgeColor = DEFAULT_BADGE_COLOR;
120 |
121 | setTypeface(Typeface.DEFAULT_BOLD);
122 | int paddingPixels = dipToPixels(DEFAULT_LR_PADDING_DIP);
123 | setPadding(paddingPixels, 0, paddingPixels, 0);
124 | setTextColor(DEFAULT_TEXT_COLOR);
125 |
126 | fadeIn = new AlphaAnimation(0, 1);
127 | fadeIn.setInterpolator(new DecelerateInterpolator());
128 | fadeIn.setDuration(200);
129 |
130 | fadeOut = new AlphaAnimation(1, 0);
131 | fadeOut.setInterpolator(new AccelerateInterpolator());
132 | fadeOut.setDuration(200);
133 |
134 | isShown = false;
135 |
136 | if (this.target != null) {
137 | applyTo(this.target);
138 | } else {
139 | show();
140 | }
141 |
142 | }
143 |
144 | private void applyTo(View target) {
145 |
146 | LayoutParams lp = target.getLayoutParams();
147 | ViewParent parent = target.getParent();
148 | FrameLayout container = new FrameLayout(context);
149 |
150 | if (target instanceof TabWidget) {
151 |
152 | // setPhoto target to the relevant tab child container
153 | target = ((TabWidget) target).getChildTabViewAt(targetTabIndex);
154 | this.target = target;
155 |
156 | ((ViewGroup) target).addView(container,
157 | new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
158 |
159 | this.setVisibility(View.INVISIBLE);
160 | container.addView(this);
161 |
162 | mNoneTipsView = new ImageView(getContext());
163 | mNoneTipsView.setImageDrawable(getResources().getDrawable(R.drawable.mark_none_tips));
164 | container.addView(mNoneTipsView, new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
165 | mNoneTipsView.setVisibility(View.INVISIBLE);
166 |
167 | } else {
168 |
169 | // TODO verify that parent is indeed a ViewGroup
170 | ViewGroup group = (ViewGroup) parent;
171 | int index = group.indexOfChild(target);
172 |
173 | group.removeView(target);
174 | group.addView(container, index, lp);
175 |
176 | container.addView(target);
177 |
178 | this.setVisibility(View.INVISIBLE);
179 | container.addView(this);
180 |
181 | mNoneTipsView = new ImageView(getContext());
182 | mNoneTipsView.setImageDrawable(getResources().getDrawable(R.drawable.mark_none_tips));
183 | container.addView(mNoneTipsView, new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
184 | mNoneTipsView.setVisibility(View.INVISIBLE);
185 |
186 | group.invalidate();
187 |
188 | }
189 |
190 | }
191 |
192 | /**
193 | * Make the badge visible in the UI.
194 | */
195 | public void show() {
196 | show(false, null);
197 | }
198 |
199 | /**
200 | * Make the badge visible in the UI.
201 | *
202 | * @param animate flag to apply the default fade-in animation.
203 | */
204 | public void show(boolean animate) {
205 | show(animate, fadeIn);
206 | }
207 |
208 | /**
209 | * Make the badge visible in the UI.
210 | *
211 | * @param anim Animation to apply to the view when made visible.
212 | */
213 | public void show(Animation anim) {
214 | show(true, anim);
215 | }
216 |
217 | /**
218 | * Make the badge non-visible in the UI.
219 | */
220 | public void hide() {
221 | hide(false, null);
222 | }
223 |
224 | /**
225 | * Make the badge non-visible in the UI.
226 | *
227 | * @param animate flag to apply the default fade-out animation.
228 | */
229 | public void hide(boolean animate) {
230 | hide(animate, fadeOut);
231 | }
232 |
233 | /**
234 | * Make the badge non-visible in the UI.
235 | *
236 | * @param anim Animation to apply to the view when made non-visible.
237 | */
238 | public void hide(Animation anim) {
239 | hide(true, anim);
240 | }
241 |
242 | /**
243 | * Toggle the badge visibility in the UI.
244 | */
245 | public void toggle() {
246 | toggle(false, null, null);
247 | }
248 |
249 | /**
250 | * Toggle the badge visibility in the UI.
251 | *
252 | * @param animate flag to apply the default fade-in/out animation.
253 | */
254 | public void toggle(boolean animate) {
255 | toggle(animate, fadeIn, fadeOut);
256 | }
257 |
258 | /**
259 | * Toggle the badge visibility in the UI.
260 | *
261 | * @param animIn Animation to apply to the view when made visible.
262 | * @param animOut Animation to apply to the view when made non-visible.
263 | */
264 | public void toggle(Animation animIn, Animation animOut) {
265 | toggle(true, animIn, animOut);
266 | }
267 |
268 | private void show(boolean animate, Animation anim) {
269 | if (getBackground() == null) {
270 | if (badgeBg == null) {
271 | badgeBg = getDefaultBackground();
272 | }
273 | setBackgroundDrawable(badgeBg);
274 | }
275 | applyLayoutParams();
276 |
277 | if (TextUtils.isEmpty(getText())) {
278 | if (animate) {
279 | mNoneTipsView.startAnimation(anim);
280 | }
281 | this.setVisibility(View.GONE);
282 | mNoneTipsView.setVisibility(View.VISIBLE);
283 | } else {
284 | if (animate) {
285 | this.startAnimation(anim);
286 | }
287 | this.setVisibility(View.VISIBLE);
288 | mNoneTipsView.setVisibility(View.GONE);
289 | }
290 | isShown = true;
291 | }
292 |
293 | private void hide(boolean animate, Animation anim) {
294 | this.setVisibility(View.INVISIBLE);
295 | if (TextUtils.isEmpty(getText())) {
296 | mNoneTipsView.setVisibility(INVISIBLE);
297 | }
298 | if (animate) {
299 | this.startAnimation(anim);
300 | mNoneTipsView.startAnimation(anim);
301 | }
302 | isShown = false;
303 | }
304 |
305 | private void toggle(boolean animate, Animation animIn, Animation animOut) {
306 | if (isShown) {
307 | hide(animate && (animOut != null), animOut);
308 | } else {
309 | show(animate && (animIn != null), animIn);
310 | }
311 | }
312 |
313 | /**
314 | * Increment the numeric badge label. If the current badge label cannot be converted to
315 | * an integer value, its label will be setPhoto to "0".
316 | *
317 | * @param offset the increment offset.
318 | */
319 | public int increment(int offset) {
320 | CharSequence txt = getText();
321 | int i;
322 | if (txt != null) {
323 | try {
324 | i = Integer.parseInt(txt.toString());
325 | } catch (NumberFormatException e) {
326 | i = 0;
327 | }
328 | } else {
329 | i = 0;
330 | }
331 | i = i + offset;
332 | setText(String.valueOf(i));
333 | return i;
334 | }
335 |
336 | /**
337 | * Decrement the numeric badge label. If the current badge label cannot be converted to
338 | * an integer value, its label will be setPhoto to "0".
339 | *
340 | * @param offset the decrement offset.
341 | */
342 | public int decrement(int offset) {
343 | return increment(-offset);
344 | }
345 |
346 | private ShapeDrawable getDefaultBackground() {
347 |
348 | int r = dipToPixels(DEFAULT_CORNER_RADIUS_DIP);
349 | float[] outerR = new float[]{r, r, r, r, r, r, r, r};
350 |
351 | RoundRectShape rr = new RoundRectShape(outerR, null, null);
352 | ShapeDrawable drawable = new ShapeDrawable(rr);
353 | drawable.getPaint().setColor(badgeColor);
354 |
355 | return drawable;
356 |
357 | }
358 |
359 | private void applyLayoutParams() {
360 |
361 | FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
362 |
363 | int offsetH = badgeMarginH;
364 | int offsetV = badgeMarginV;
365 | if (TextUtils.isEmpty(getText())) {
366 | offsetH = badgeMarginH + Utils.dp2px(getContext(), DEFAULT_MARGIN_DIP * 2);
367 | offsetV = badgeMarginV + Utils.dp2px(getContext(), DEFAULT_MARGIN_DIP / 2);
368 | }
369 | switch (badgePosition) {
370 | case POSITION_TOP_LEFT:
371 | lp.gravity = Gravity.LEFT | Gravity.TOP;
372 | lp.setMargins(offsetH, offsetV, 0, 0);
373 | break;
374 | case POSITION_TOP_RIGHT:
375 | lp.gravity = Gravity.RIGHT | Gravity.TOP;
376 | lp.setMargins(0, offsetV, offsetH, 0);
377 | break;
378 | case POSITION_BOTTOM_LEFT:
379 | lp.gravity = Gravity.LEFT | Gravity.BOTTOM;
380 | lp.setMargins(offsetH, 0, 0, offsetV);
381 | break;
382 | case POSITION_BOTTOM_RIGHT:
383 | lp.gravity = Gravity.RIGHT | Gravity.BOTTOM;
384 | lp.setMargins(0, 0, offsetH, offsetV);
385 | break;
386 | case POSITION_CENTER:
387 | lp.gravity = Gravity.CENTER;
388 | lp.setMargins(0, 0, 0, 0);
389 | break;
390 | default:
391 | break;
392 | }
393 |
394 | if (TextUtils.isEmpty(getText())) {
395 | mNoneTipsView.setLayoutParams(lp);
396 | } else {
397 | setLayoutParams(lp);
398 | }
399 | }
400 |
401 | /**
402 | * Returns the target View this badge has been attached to.
403 | */
404 | public View getTarget() {
405 | return target;
406 | }
407 |
408 | /**
409 | * Is this badge currently visible in the UI?
410 | */
411 | @Override
412 | public boolean isShown() {
413 | return isShown;
414 | }
415 |
416 | /**
417 | * Returns the positioning of this badge.
418 | *
419 | * one of POSITION_TOP_LEFT, POSITION_TOP_RIGHT, POSITION_BOTTOM_LEFT, POSITION_BOTTOM_RIGHT, POSTION_CENTER.
420 | */
421 | public int getBadgePosition() {
422 | return badgePosition;
423 | }
424 |
425 | /**
426 | * Set the positioning of this badge.
427 | *
428 | * @param layoutPosition one of POSITION_TOP_LEFT, POSITION_TOP_RIGHT, POSITION_BOTTOM_LEFT, POSITION_BOTTOM_RIGHT, POSTION_CENTER.
429 | */
430 | public void setBadgePosition(int layoutPosition) {
431 | this.badgePosition = layoutPosition;
432 | }
433 |
434 | /**
435 | * Returns the horizontal margin from the target View that is applied to this badge.
436 | */
437 | public int getHorizontalBadgeMargin() {
438 | return badgeMarginH;
439 | }
440 |
441 | /**
442 | * Returns the vertical margin from the target View that is applied to this badge.
443 | */
444 | public int getVerticalBadgeMargin() {
445 | return badgeMarginV;
446 | }
447 |
448 | /**
449 | * Set the horizontal/vertical margin from the target View that is applied to this badge.
450 | *
451 | * @param badgeMargin the margin in pixels.
452 | */
453 | public void setBadgeMargin(int badgeMargin) {
454 | this.badgeMarginH = badgeMargin;
455 | this.badgeMarginV = badgeMargin;
456 | }
457 |
458 | /**
459 | * Set the horizontal/vertical margin from the target View that is applied to this badge.
460 | *
461 | * @param horizontal margin in pixels.
462 | * @param vertical margin in pixels.
463 | */
464 | public void setBadgeMargin(int horizontal, int vertical) {
465 | this.badgeMarginH = horizontal;
466 | this.badgeMarginV = vertical;
467 | }
468 |
469 | /**
470 | * Returns the color value of the badge background.
471 | */
472 | public int getBadgeBackgroundColor() {
473 | return badgeColor;
474 | }
475 |
476 | /**
477 | * Set the color value of the badge background.
478 | *
479 | * @param badgeColor the badge background color.
480 | */
481 | public void setBadgeBackgroundColor(int badgeColor) {
482 | this.badgeColor = badgeColor;
483 | badgeBg = getDefaultBackground();
484 | }
485 |
486 | private int dipToPixels(int dip) {
487 | Resources r = getResources();
488 | float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, r.getDisplayMetrics());
489 | return (int) px;
490 | }
491 |
492 | }
--------------------------------------------------------------------------------
/viewpagerindicator/src/main/java/me/caiying/library/viewpagerindicator/BasicViewPagerIndicator.java:
--------------------------------------------------------------------------------
1 | package me.caiying.library.viewpagerindicator;
2 |
3 | import android.support.v4.view.ViewPager;
4 |
5 | /**
6 | * A PageIndicator is responsible to show an visual indicator on the total views
7 | * number and the current visible view.
8 | */
9 | public interface BasicViewPagerIndicator extends ViewPager.OnPageChangeListener {
10 | /**
11 | * Bind the indicator to a ViewPager.
12 | *
13 | * @param view
14 | */
15 | void setViewPager(ViewPager view);
16 |
17 | /**
18 | * Bind the indicator to a ViewPager.
19 | *
20 | * @param view
21 | * @param initialPosition
22 | */
23 | void setViewPager(ViewPager view, int initialPosition);
24 |
25 | /**
26 | * Set the current page of both the ViewPager and indicator.
27 | *
28 | * This must be used if you need to set the page before
29 | * the views are drawn on screen (e.g., default start page).
30 | *
31 | * @param item
32 | */
33 | void setCurrentItem(int item);
34 |
35 | /**
36 | * Set a page change listener which will receive forwarded events.
37 | *
38 | * @param listener
39 | */
40 | void setOnPageChangeListener(ViewPager.OnPageChangeListener listener);
41 |
42 | /**
43 | * Notify the indicator that the fragment list has changed.
44 | */
45 | void notifyDataSetChanged();
46 | }
--------------------------------------------------------------------------------
/viewpagerindicator/src/main/java/me/caiying/library/viewpagerindicator/CirclePagerIndicator.java:
--------------------------------------------------------------------------------
1 | package me.caiying.library.viewpagerindicator;
2 |
3 | /**
4 | * Created by caiying on 06/02/2016.
5 | */
6 | import android.content.Context;
7 | import android.graphics.Canvas;
8 | import android.graphics.Color;
9 | import android.graphics.Paint;
10 | import android.graphics.Paint.Style;
11 | import android.os.Parcel;
12 | import android.os.Parcelable;
13 | import android.support.v4.view.MotionEventCompat;
14 | import android.support.v4.view.ViewConfigurationCompat;
15 | import android.support.v4.view.ViewPager;
16 | import android.util.AttributeSet;
17 | import android.view.MotionEvent;
18 | import android.view.View;
19 | import android.view.ViewConfiguration;
20 |
21 | import static android.graphics.Paint.ANTI_ALIAS_FLAG;
22 | import static android.widget.LinearLayout.HORIZONTAL;
23 | import static android.widget.LinearLayout.VERTICAL;
24 |
25 | /**
26 | * Draws circles (one for each view). The current view position is filled and
27 | * others are only stroked.
28 | */
29 | public class CirclePagerIndicator extends View implements BasicViewPagerIndicator {
30 | private static final int INVALID_POINTER = -1;
31 |
32 | private float mRadius;
33 | private final Paint mPaintPageFill = new Paint(ANTI_ALIAS_FLAG);
34 | private final Paint mPaintStroke = new Paint(ANTI_ALIAS_FLAG);
35 | private final Paint mPaintFill = new Paint(ANTI_ALIAS_FLAG);
36 | private ViewPager mViewPager;
37 | private ViewPager.OnPageChangeListener mListener;
38 | private int mCurrentPage;
39 | private int mSnapPage;
40 | private float mPageOffset;
41 | private int mScrollState;
42 | private int mOrientation;
43 | private boolean mCentered;
44 | private boolean mSnap = true;
45 |
46 | private int mTouchSlop;
47 | private float mLastMotionX = -1;
48 | private int mActivePointerId = INVALID_POINTER;
49 | private boolean mIsDragging;
50 |
51 |
52 | public CirclePagerIndicator(Context context) {
53 | super(context);
54 | init();
55 | }
56 |
57 | public CirclePagerIndicator(Context context, AttributeSet attrs) {
58 | super(context, attrs);
59 | init();
60 | }
61 |
62 | public CirclePagerIndicator(Context context, AttributeSet attrs, int defStyle) {
63 | super(context, attrs, defStyle);
64 | init();
65 | }
66 |
67 | private void init() {
68 | if (isInEditMode()) return;
69 |
70 | //Load defaults from resources
71 | final int defaultPageColor = Color.parseColor("#e9e9e9");
72 | final int defaultFillColor = Color.parseColor("#9b9b9b");
73 | final int defaultOrientation = HORIZONTAL;
74 | final int defaultStrokeColor = Color.parseColor("#e9e9e9");
75 | final float defaultStrokeWidth = Utils.dp2px(getContext(), 1);
76 | final float defaultRadius = Utils.dp2px(getContext(), 2);
77 | final boolean defaultCentered = true;
78 |
79 | //Retrieve styles attributes
80 | mCentered = defaultCentered;
81 | mOrientation = defaultOrientation;
82 | mPaintPageFill.setStyle(Style.FILL);
83 | mPaintPageFill.setColor(defaultPageColor);
84 | mPaintStroke.setStyle(Style.STROKE);
85 | mPaintStroke.setColor(defaultStrokeColor);
86 | mPaintStroke.setStrokeWidth(defaultStrokeWidth);
87 | mPaintFill.setStyle(Style.FILL);
88 | mPaintFill.setColor(defaultFillColor);
89 | mRadius = defaultRadius;
90 |
91 | final ViewConfiguration configuration = ViewConfiguration.get(getContext());
92 | mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
93 | }
94 |
95 | public void setCentered(boolean centered) {
96 | mCentered = centered;
97 | invalidate();
98 | }
99 |
100 | public boolean isCentered() {
101 | return mCentered;
102 | }
103 |
104 | public void setPageColor(int pageColor) {
105 | mPaintPageFill.setColor(pageColor);
106 | invalidate();
107 | }
108 |
109 | public int getPageColor() {
110 | return mPaintPageFill.getColor();
111 | }
112 |
113 | public void setFillColor(int fillColor) {
114 | mPaintFill.setColor(fillColor);
115 | invalidate();
116 | }
117 |
118 | public int getFillColor() {
119 | return mPaintFill.getColor();
120 | }
121 |
122 | public void setOrientation(int orientation) {
123 | switch (orientation) {
124 | case HORIZONTAL:
125 | case VERTICAL:
126 | mOrientation = orientation;
127 | requestLayout();
128 | break;
129 |
130 | default:
131 | throw new IllegalArgumentException("Orientation must be either HORIZONTAL or VERTICAL.");
132 | }
133 | }
134 |
135 | public int getOrientation() {
136 | return mOrientation;
137 | }
138 |
139 | public void setStrokeColor(int strokeColor) {
140 | mPaintStroke.setColor(strokeColor);
141 | invalidate();
142 | }
143 |
144 | public int getStrokeColor() {
145 | return mPaintStroke.getColor();
146 | }
147 |
148 | public void setStrokeWidth(float strokeWidth) {
149 | mPaintStroke.setStrokeWidth(strokeWidth);
150 | invalidate();
151 | }
152 |
153 | public float getStrokeWidth() {
154 | return mPaintStroke.getStrokeWidth();
155 | }
156 |
157 | public void setRadius(float radius) {
158 | mRadius = radius;
159 | invalidate();
160 | }
161 |
162 | public float getRadius() {
163 | return mRadius;
164 | }
165 |
166 | public void setSnap(boolean snap) {
167 | mSnap = snap;
168 | invalidate();
169 | }
170 |
171 | public boolean isSnap() {
172 | return mSnap;
173 | }
174 |
175 | @Override
176 | protected void onDraw(Canvas canvas) {
177 | super.onDraw(canvas);
178 |
179 | if (mViewPager == null) {
180 | return;
181 | }
182 | final int count = mViewPager.getAdapter().getCount();
183 | if (count == 0) {
184 | return;
185 | }
186 |
187 | if (mCurrentPage >= count) {
188 | setCurrentItem(count - 1);
189 | return;
190 | }
191 |
192 | int longSize;
193 | int longPaddingBefore;
194 | int longPaddingAfter;
195 | int shortPaddingBefore;
196 | if (mOrientation == HORIZONTAL) {
197 | longSize = getWidth();
198 | longPaddingBefore = getPaddingLeft();
199 | longPaddingAfter = getPaddingRight();
200 | shortPaddingBefore = getPaddingTop();
201 | } else {
202 | longSize = getHeight();
203 | longPaddingBefore = getPaddingTop();
204 | longPaddingAfter = getPaddingBottom();
205 | shortPaddingBefore = getPaddingLeft();
206 | }
207 |
208 | final float threeRadius = mRadius * 3;
209 | final float shortOffset = shortPaddingBefore + mRadius;
210 | float longOffset = longPaddingBefore + mRadius;
211 | if (mCentered) {
212 | longOffset += ((longSize - longPaddingBefore - longPaddingAfter) / 2.0f) - ((count * threeRadius) / 2.0f);
213 | }
214 |
215 | float dX;
216 | float dY;
217 |
218 | float pageFillRadius = mRadius;
219 | if (mPaintStroke.getStrokeWidth() > 0) {
220 | pageFillRadius -= mPaintStroke.getStrokeWidth() / 2.0f;
221 | }
222 |
223 | //Draw stroked circles
224 | for (int iLoop = 0; iLoop < count; iLoop++) {
225 | float drawLong = longOffset + (iLoop * threeRadius);
226 | if (mOrientation == HORIZONTAL) {
227 | dX = drawLong;
228 | dY = shortOffset;
229 | } else {
230 | dX = shortOffset;
231 | dY = drawLong;
232 | }
233 | // Only paint fill if not completely transparent
234 | if (mPaintPageFill.getAlpha() > 0) {
235 | canvas.drawCircle(dX, dY, pageFillRadius, mPaintPageFill);
236 | }
237 |
238 | // Only paint stroke if a stroke width was non-zero
239 | if (pageFillRadius != mRadius) {
240 | canvas.drawCircle(dX, dY, mRadius, mPaintStroke);
241 | }
242 | }
243 |
244 | //Draw the filled circle according to the current scroll
245 | float cx = (mSnap ? mSnapPage : mCurrentPage) * threeRadius;
246 | if (!mSnap) {
247 | cx += mPageOffset * threeRadius;
248 | }
249 | if (mOrientation == HORIZONTAL) {
250 | dX = longOffset + cx;
251 | dY = shortOffset;
252 | } else {
253 | dX = shortOffset;
254 | dY = longOffset + cx;
255 | }
256 | canvas.drawCircle(dX, dY, mRadius, mPaintFill);
257 | }
258 |
259 | public boolean onTouchEvent(android.view.MotionEvent ev) {
260 | if (super.onTouchEvent(ev)) {
261 | return true;
262 | }
263 | if ((mViewPager == null) || (mViewPager.getAdapter().getCount() == 0)) {
264 | return false;
265 | }
266 |
267 | final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
268 | switch (action) {
269 | case MotionEvent.ACTION_DOWN:
270 | mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
271 | mLastMotionX = ev.getX();
272 | break;
273 |
274 | case MotionEvent.ACTION_MOVE: {
275 | final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
276 | final float x = MotionEventCompat.getX(ev, activePointerIndex);
277 | final float deltaX = x - mLastMotionX;
278 |
279 | if (!mIsDragging) {
280 | if (Math.abs(deltaX) > mTouchSlop) {
281 | mIsDragging = true;
282 | }
283 | }
284 |
285 | if (mIsDragging) {
286 | mLastMotionX = x;
287 | if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag()) {
288 | mViewPager.fakeDragBy(deltaX);
289 | }
290 | }
291 |
292 | break;
293 | }
294 |
295 | case MotionEvent.ACTION_CANCEL:
296 | case MotionEvent.ACTION_UP:
297 | if (!mIsDragging) {
298 | final int count = mViewPager.getAdapter().getCount();
299 | final int width = getWidth();
300 | final float halfWidth = width / 2f;
301 | final float sixthWidth = width / 6f;
302 |
303 | if ((mCurrentPage > 0) && (ev.getX() < halfWidth - sixthWidth)) {
304 | if (action != MotionEvent.ACTION_CANCEL) {
305 | mViewPager.setCurrentItem(mCurrentPage - 1);
306 | }
307 | return true;
308 | } else if ((mCurrentPage < count - 1) && (ev.getX() > halfWidth + sixthWidth)) {
309 | if (action != MotionEvent.ACTION_CANCEL) {
310 | mViewPager.setCurrentItem(mCurrentPage + 1);
311 | }
312 | return true;
313 | }
314 | }
315 |
316 | mIsDragging = false;
317 | mActivePointerId = INVALID_POINTER;
318 | try {
319 | if (mViewPager.isFakeDragging()) mViewPager.endFakeDrag();
320 | } catch (Exception e) {
321 | e.printStackTrace();
322 | }
323 | break;
324 |
325 | case MotionEventCompat.ACTION_POINTER_DOWN: {
326 | final int index = MotionEventCompat.getActionIndex(ev);
327 | mLastMotionX = MotionEventCompat.getX(ev, index);
328 | mActivePointerId = MotionEventCompat.getPointerId(ev, index);
329 | break;
330 | }
331 |
332 | case MotionEventCompat.ACTION_POINTER_UP:
333 | final int pointerIndex = MotionEventCompat.getActionIndex(ev);
334 | final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
335 | if (pointerId == mActivePointerId) {
336 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
337 | mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
338 | }
339 | mLastMotionX = MotionEventCompat.getX(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId));
340 | break;
341 | }
342 |
343 | return true;
344 | }
345 |
346 | @Override
347 | public void setViewPager(ViewPager view) {
348 | if (mViewPager == view) {
349 | return;
350 | }
351 | if (mViewPager != null) {
352 | mViewPager.setOnPageChangeListener(null);
353 | }
354 | if (view.getAdapter() == null) {
355 | throw new IllegalStateException("ViewPager does not have adapter instance.");
356 | }
357 | mViewPager = view;
358 | mViewPager.setOnPageChangeListener(this);
359 | invalidate();
360 | }
361 |
362 | @Override
363 | public void setViewPager(ViewPager view, int initialPosition) {
364 | setViewPager(view);
365 | setCurrentItem(initialPosition);
366 | }
367 |
368 | @Override
369 | public void setCurrentItem(int item) {
370 | if (mViewPager == null) {
371 | throw new IllegalStateException("ViewPager has not been bound.");
372 | }
373 | mViewPager.setCurrentItem(item);
374 | mCurrentPage = item;
375 | invalidate();
376 | }
377 |
378 | @Override
379 | public void notifyDataSetChanged() {
380 | invalidate();
381 | }
382 |
383 | @Override
384 | public void onPageScrollStateChanged(int state) {
385 | mScrollState = state;
386 |
387 | if (mListener != null) {
388 | mListener.onPageScrollStateChanged(state);
389 | }
390 | }
391 |
392 | @Override
393 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
394 | mCurrentPage = position;
395 | mPageOffset = positionOffset;
396 | invalidate();
397 |
398 | if (mListener != null) {
399 | mListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
400 | }
401 | }
402 |
403 | @Override
404 | public void onPageSelected(int position) {
405 | if (mSnap || mScrollState == ViewPager.SCROLL_STATE_IDLE) {
406 | mCurrentPage = position;
407 | mSnapPage = position;
408 | invalidate();
409 | }
410 |
411 | if (mListener != null) {
412 | mListener.onPageSelected(position);
413 | }
414 | }
415 |
416 | @Override
417 | public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
418 | mListener = listener;
419 | }
420 |
421 | /*
422 | * (non-Javadoc)
423 | *
424 | * @see android.view.View#onMeasure(int, int)
425 | */
426 | @Override
427 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
428 | if (mOrientation == HORIZONTAL) {
429 | setMeasuredDimension(measureLong(widthMeasureSpec), measureShort(heightMeasureSpec));
430 | } else {
431 | setMeasuredDimension(measureShort(widthMeasureSpec), measureLong(heightMeasureSpec));
432 | }
433 | }
434 |
435 | /**
436 | * Determines the width of this view
437 | *
438 | * @param measureSpec
439 | * A measureSpec packed into an int
440 | * @return The width of the view, honoring constraints from measureSpec
441 | */
442 | private int measureLong(int measureSpec) {
443 | int result;
444 | int specMode = MeasureSpec.getMode(measureSpec);
445 | int specSize = MeasureSpec.getSize(measureSpec);
446 |
447 | if ((specMode == MeasureSpec.EXACTLY) || (mViewPager == null)) {
448 | //We were told how big to be
449 | result = specSize;
450 | } else {
451 | //Calculate the width according the views count
452 | final int count = mViewPager.getAdapter().getCount();
453 | result = (int)(getPaddingLeft() + getPaddingRight()
454 | + (count * 2 * mRadius) + (count - 1) * mRadius + 1);
455 | //Respect AT_MOST value if that was what is called for by measureSpec
456 | if (specMode == MeasureSpec.AT_MOST) {
457 | result = Math.min(result, specSize);
458 | }
459 | }
460 | return result;
461 | }
462 |
463 | /**
464 | * Determines the height of this view
465 | *
466 | * @param measureSpec
467 | * A measureSpec packed into an int
468 | * @return The height of the view, honoring constraints from measureSpec
469 | */
470 | private int measureShort(int measureSpec) {
471 | int result;
472 | int specMode = MeasureSpec.getMode(measureSpec);
473 | int specSize = MeasureSpec.getSize(measureSpec);
474 |
475 | if (specMode == MeasureSpec.EXACTLY) {
476 | //We were told how big to be
477 | result = specSize;
478 | } else {
479 | //Measure the height
480 | result = (int)(2 * mRadius + getPaddingTop() + getPaddingBottom() + 1);
481 | //Respect AT_MOST value if that was what is called for by measureSpec
482 | if (specMode == MeasureSpec.AT_MOST) {
483 | result = Math.min(result, specSize);
484 | }
485 | }
486 | return result;
487 | }
488 |
489 | @Override
490 | public void onRestoreInstanceState(Parcelable state) {
491 | SavedState savedState = (SavedState)state;
492 | super.onRestoreInstanceState(savedState.getSuperState());
493 | mCurrentPage = savedState.currentPage;
494 | mSnapPage = savedState.currentPage;
495 | requestLayout();
496 | }
497 |
498 | @Override
499 | public Parcelable onSaveInstanceState() {
500 | Parcelable superState = super.onSaveInstanceState();
501 | SavedState savedState = new SavedState(superState);
502 | savedState.currentPage = mCurrentPage;
503 | return savedState;
504 | }
505 |
506 | static class SavedState extends BaseSavedState {
507 | int currentPage;
508 |
509 | public SavedState(Parcelable superState) {
510 | super(superState);
511 | }
512 |
513 | private SavedState(Parcel in) {
514 | super(in);
515 | currentPage = in.readInt();
516 | }
517 |
518 | @Override
519 | public void writeToParcel(Parcel dest, int flags) {
520 | super.writeToParcel(dest, flags);
521 | dest.writeInt(currentPage);
522 | }
523 |
524 | @SuppressWarnings("UnusedDeclaration")
525 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
526 | @Override
527 | public SavedState createFromParcel(Parcel in) {
528 | return new SavedState(in);
529 | }
530 |
531 | @Override
532 | public SavedState[] newArray(int size) {
533 | return new SavedState[size];
534 | }
535 | };
536 | }
537 | }
538 |
--------------------------------------------------------------------------------
/viewpagerindicator/src/main/java/me/caiying/library/viewpagerindicator/FadingTabPagerIndicator.java:
--------------------------------------------------------------------------------
1 |
2 | package me.caiying.library.viewpagerindicator;
3 |
4 | import android.annotation.TargetApi;
5 | import android.content.Context;
6 | import android.os.Build;
7 | import android.support.v4.view.ViewPager;
8 | import android.support.v4.view.ViewPager.OnPageChangeListener;
9 | import android.text.TextUtils;
10 | import android.util.AttributeSet;
11 | import android.util.TypedValue;
12 | import android.view.Gravity;
13 | import android.view.View;
14 | import android.widget.FrameLayout;
15 | import android.widget.ImageView;
16 | import android.widget.LinearLayout;
17 | import android.widget.TextView;
18 |
19 | public class FadingTabPagerIndicator extends LinearLayout {
20 | private ViewPager mViewPager;
21 | private PageListener pageListener = new PageListener();
22 | private LinearLayout.LayoutParams tabLayoutParams;
23 | private int iconHeight = 28; // dp
24 | private int iconWidth = 30; // dp
25 | private int textSize = 12; // sp
26 | private int paddingOffset = 4; // dp
27 |
28 | public FadingTabPagerIndicator(Context context) {
29 | super(context);
30 | init();
31 | }
32 |
33 | public FadingTabPagerIndicator(Context context, AttributeSet attrs) {
34 | super(context, attrs);
35 | init();
36 | }
37 |
38 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
39 | public FadingTabPagerIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
40 | super(context, attrs, defStyleAttr);
41 | init();
42 | }
43 |
44 | private void init() {
45 | setOrientation(LinearLayout.HORIZONTAL);
46 | tabLayoutParams = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1.0f);
47 | tabLayoutParams.topMargin = Utils.dp2px(getContext(), paddingOffset);
48 | tabLayoutParams.bottomMargin = Utils.dp2px(getContext(), paddingOffset);
49 | iconHeight = (int) TypedValue.applyDimension(
50 | TypedValue.COMPLEX_UNIT_DIP, iconHeight, getResources().getDisplayMetrics());
51 | iconWidth = (int) TypedValue.applyDimension(
52 | TypedValue.COMPLEX_UNIT_DIP, iconWidth, getResources().getDisplayMetrics());
53 | }
54 |
55 | public void setViewPager(ViewPager pager) {
56 | this.mViewPager = pager;
57 |
58 | if (pager.getAdapter() == null) {
59 | throw new IllegalStateException("ViewPager does not have adapter instance.");
60 | }
61 |
62 | pager.setOnPageChangeListener(pageListener);
63 |
64 | notifyDataSetChanged();
65 | }
66 |
67 | public void setBadge(int index, int number) {
68 | if (number > 0) {
69 | BadgeView badgeView = ((TabView) getChildAt(index)).getBadgeView();
70 | badgeView.setText(String.valueOf(number));
71 | badgeView.show();
72 | } else {
73 | setNoneBadge(index);
74 | }
75 | }
76 |
77 | public void setBadge(int index, String number) {
78 | if (!TextUtils.isEmpty(number)) {
79 | BadgeView badgeView = ((TabView) getChildAt(index)).getBadgeView();
80 | badgeView.setText(number);
81 | badgeView.show();
82 | } else {
83 | setNoneBadge(index);
84 | }
85 | }
86 |
87 | public void setNoneBadge(int index) {
88 | BadgeView badgeView = ((TabView) getChildAt(index)).getBadgeView();
89 | badgeView.setText("");
90 | badgeView.show();
91 | }
92 |
93 | public void hideBadge(int index) {
94 | ((TabView) getChildAt(index)).getBadgeView().hide();
95 | }
96 |
97 | public void setCurrentItem(int item) {
98 | mViewPager.setCurrentItem(item, false);
99 | tabSelect(item);
100 | }
101 |
102 | public void notifyDataSetChanged() {
103 | removeAllViews();
104 |
105 | int tabCount = mViewPager.getAdapter().getCount();
106 |
107 | FadingTab tabs = (FadingTab) mViewPager.getAdapter();
108 | for (int i = 0; i < tabCount; i++) {
109 | addTab(i, mViewPager.getAdapter().getPageTitle(i).toString(),
110 | tabs.getTabNormalIconResId(i), tabs.getTabSelectIconResId(i),
111 | tabs.getTabNormalTextColor(i), tabs.getTabSelectTextColor(i));
112 | }
113 |
114 | setCurrentItem(mViewPager.getCurrentItem());
115 | }
116 |
117 | private void addTab(final int position, String text, int normalResId, int selectResId,
118 | int textNormalColorResId, int textSelectColorResId) {
119 | TabView tabView = new TabView(getContext(), text, normalResId, selectResId,
120 | textNormalColorResId, textSelectColorResId);
121 | tabView.setOnClickListener(new View.OnClickListener() {
122 | @Override
123 | public void onClick(View v) {
124 | setCurrentItem(position);
125 | }
126 | });
127 | addView(tabView, position, tabLayoutParams);
128 | }
129 |
130 | private void tabSelect(int index) {
131 | final int tabCount = getChildCount();
132 | for (int i = 0; i < tabCount; i++) {
133 | getChildAt(i).setSelected(i == index);
134 | }
135 | }
136 |
137 | private class FadingImageView extends FrameLayout {
138 | private ImageView mNormalImage;
139 | private ImageView mSelectImage;
140 |
141 | public FadingImageView(Context context, int normalResId, int selectResId) {
142 | super(context);
143 | mNormalImage = new ImageView(context);
144 | mSelectImage = new ImageView(context);
145 | mNormalImage.setImageResource(normalResId);
146 | mSelectImage.setImageResource(selectResId);
147 | mNormalImage.setAlpha(1.0f);
148 | mSelectImage.setAlpha(0.0f);
149 | addView(mNormalImage, 0, new LayoutParams(iconWidth, iconHeight, Gravity.CENTER));
150 | addView(mSelectImage, 1, new LayoutParams(iconWidth, iconHeight, Gravity.CENTER));
151 | }
152 |
153 | @Override
154 | public void setSelected(boolean selected) {
155 | super.setSelected(selected);
156 | setAlpha(selected ? 1.0f : 0.0f);
157 | }
158 |
159 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
160 | @Override
161 | public void setAlpha(float alpha) {
162 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
163 | mNormalImage.setAlpha(1 - alpha);
164 | mSelectImage.setAlpha(alpha);
165 | } else {
166 | mNormalImage.setAlpha((int) (1 - alpha));
167 | mSelectImage.setAlpha((int) alpha);
168 | }
169 | }
170 | }
171 |
172 | private class FadingTextView extends FrameLayout {
173 | private TextView mNormalTextView;
174 | private TextView mSelectTextView;
175 |
176 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
177 | public FadingTextView(Context context, String text, int normalColor, int selectColor) {
178 | super(context);
179 | mNormalTextView = new TextView(context);
180 | mSelectTextView = new TextView(context);
181 | mNormalTextView.setTextColor(normalColor);
182 | mSelectTextView.setTextColor(selectColor);
183 | mNormalTextView.setAlpha(1.0f);
184 | mSelectTextView.setAlpha(0.0f);
185 | mNormalTextView.setText(text);
186 | mSelectTextView.setText(text);
187 | mNormalTextView.setTextSize(textSize);
188 | mSelectTextView.setTextSize(textSize);
189 | addView(mNormalTextView, 0, new LayoutParams(
190 | LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, Gravity.CENTER));
191 | addView(mSelectTextView, 1, new LayoutParams(
192 | LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, Gravity.CENTER));
193 | }
194 |
195 | @Override
196 | public void setSelected(boolean selected) {
197 | super.setSelected(selected);
198 | setAlpha(selected ? 1.0f : 0.0f);
199 | }
200 |
201 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
202 | @Override
203 | public void setAlpha(float alpha) {
204 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
205 | mNormalTextView.setAlpha(1 - alpha);
206 | mSelectTextView.setAlpha(alpha);
207 | } else {
208 | mNormalTextView.setAlpha((int) (1 - alpha));
209 | mSelectTextView.setAlpha((int) alpha);
210 | }
211 | }
212 | }
213 |
214 | private class TabView extends LinearLayout {
215 | private FadingImageView fadingImageView;
216 | private FadingTextView fadingTextView;
217 | private BadgeView badgeView;
218 |
219 | public TabView(Context context, String text, int normalResId, int selectResId,
220 | int textNormalColorResId, int textSelectColorResId) {
221 | super(context);
222 | setOrientation(VERTICAL);
223 | setGravity(Gravity.CENTER);
224 |
225 | LayoutParams wpLayoutParams =
226 | new LayoutParams(Utils.dp2px(getContext(), 58), Utils.dp2px(getContext(), 28));
227 | LinearLayout wrapperLayout = new LinearLayout(getContext());
228 | wrapperLayout.setGravity(Gravity.CENTER);
229 | addView(wrapperLayout, wpLayoutParams);
230 |
231 | LayoutParams ivLayoutParams =
232 | new LayoutParams(Utils.dp2px(getContext(), 32), Utils.dp2px(getContext(), 28));
233 | fadingImageView = new FadingImageView(context, normalResId, selectResId);
234 | wrapperLayout.addView(fadingImageView, ivLayoutParams);
235 |
236 | LayoutParams tvLayoutParams =
237 | new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
238 | fadingTextView = new FadingTextView(context, text, textNormalColorResId, textSelectColorResId);
239 | addView(fadingTextView, tvLayoutParams);
240 |
241 | badgeView = new BadgeView(getContext(), wrapperLayout);
242 | badgeView.setBadgePosition(BadgeView.POSITION_TOP_RIGHT);
243 | badgeView.setBadgeMargin(0, 0);
244 | }
245 |
246 | public BadgeView getBadgeView() {
247 | return badgeView;
248 | }
249 |
250 | public FadingImageView getFadingImageView() {
251 | return fadingImageView;
252 | }
253 |
254 | public FadingTextView getFadingTextView() {
255 | return fadingTextView;
256 | }
257 | }
258 |
259 | private class PageListener implements OnPageChangeListener {
260 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
261 | @Override
262 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
263 | ((TabView) getChildAt(position)).getFadingImageView().setAlpha(1.0f - positionOffset);
264 | ((TabView) getChildAt(position)).getFadingTextView().setAlpha(1.0f - positionOffset);
265 | if (position + 1 <= getChildCount() - 1) {
266 | ((TabView) getChildAt(position + 1)).getFadingImageView().setAlpha(positionOffset);
267 | ((TabView) getChildAt(position + 1)).getFadingTextView().setAlpha(positionOffset);
268 | }
269 | }
270 |
271 | @Override
272 | public void onPageScrollStateChanged(int state) {
273 | }
274 |
275 | @Override
276 | public void onPageSelected(int position) {
277 | tabSelect(position);
278 | }
279 | }
280 |
281 | public interface FadingTab {
282 |
283 | int getTabNormalIconResId(int position);
284 |
285 | int getTabSelectIconResId(int position);
286 |
287 | int getTabNormalTextColor(int position);
288 |
289 | int getTabSelectTextColor(int position);
290 | }
291 | }
292 |
293 |
--------------------------------------------------------------------------------
/viewpagerindicator/src/main/java/me/caiying/library/viewpagerindicator/LinePagerIndicator.java:
--------------------------------------------------------------------------------
1 | package me.caiying.library.viewpagerindicator;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.graphics.Paint.Style;
8 | import android.support.v4.view.ViewPager;
9 | import android.support.v4.view.ViewPager.OnPageChangeListener;
10 | import android.util.AttributeSet;
11 | import android.util.DisplayMetrics;
12 | import android.util.TypedValue;
13 | import android.view.Gravity;
14 | import android.view.View;
15 | import android.widget.HorizontalScrollView;
16 | import android.widget.LinearLayout;
17 | import android.widget.RelativeLayout;
18 | import android.widget.TextView;
19 |
20 | public class LinePagerIndicator extends HorizontalScrollView {
21 |
22 | public interface OnTabSelectedListener {
23 |
24 | void onTabSelected(int position);
25 | }
26 |
27 | public OnPageChangeListener mOnPageChangeListener;
28 | private OnTabSelectedListener mTabSelectedListener;
29 |
30 | private LinearLayout mTabsContainer;
31 | private ViewPager mPager;
32 |
33 | private int tabCount;
34 | private int currentPosition = 0;
35 | private float currentPositionOffset = 0f;
36 |
37 | private Paint linePaint;
38 | private Paint diviPaint;
39 |
40 | private int indicatorColor = 0xFF52B800;
41 | private int underlineColor = 0x1A000000;
42 | private int dividerColor = 0x1A000000;
43 | private int textSelectedColor = 0xFF52B800;
44 | private int textUnselectColor = 0xFF808080;
45 |
46 | private boolean enableExpand = true;
47 | private boolean enableDivider = false;
48 | private boolean indicatorOnTop = false;
49 | private boolean viewPagerScrollWithAnimation = true;
50 |
51 | private int tabTextSize = 16;
52 | private int scrollOffset = 52;
53 | private float indicatorHeight = 1.5f;
54 | private float underlineHeight = 1.0f;
55 | private int dividerPadding = 12;
56 | private int tabPadding = 24;
57 | private int dividerWidth = 1;
58 | private int lastScrollX = 0;
59 |
60 | public LinePagerIndicator(Context context) {
61 | this(context, null);
62 | }
63 |
64 | public LinePagerIndicator(Context context, AttributeSet attrs) {
65 | this(context, attrs, 0);
66 | }
67 |
68 | public LinePagerIndicator(Context context, AttributeSet attrs, int defStyle) {
69 | super(context, attrs, defStyle);
70 |
71 | setFillViewport(true);
72 | setWillNotDraw(false);
73 |
74 | DisplayMetrics dm = getResources().getDisplayMetrics();
75 |
76 | mTabsContainer = new LinearLayout(context);
77 | mTabsContainer.setOrientation(LinearLayout.HORIZONTAL);
78 | LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
79 | params.height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, dm);
80 | mTabsContainer.setLayoutParams(params);
81 | addView(mTabsContainer);
82 |
83 | scrollOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, scrollOffset, dm);
84 | dividerPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dividerPadding, dm);
85 | tabPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, tabPadding, dm);
86 | dividerWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dividerWidth, dm);
87 | indicatorHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, indicatorHeight, dm);
88 | underlineHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, underlineHeight, dm);
89 |
90 | linePaint = new Paint();
91 | linePaint.setAntiAlias(true);
92 | linePaint.setStyle(Style.FILL);
93 |
94 | diviPaint = new Paint();
95 | diviPaint.setAntiAlias(true);
96 | diviPaint.setStrokeWidth(dividerWidth);
97 | }
98 |
99 | @Override
100 | protected void onDraw(Canvas canvas) {
101 | super.onDraw(canvas);
102 |
103 | if (isInEditMode() || tabCount == 0) {
104 | return;
105 | }
106 |
107 | final int height = getHeight();
108 |
109 | linePaint.setColor(underlineColor);
110 | if(indicatorOnTop) {
111 | canvas.drawRect(0, 0, mTabsContainer.getWidth(), underlineHeight, linePaint);
112 | } else {
113 | canvas.drawRect(0, height - underlineHeight, mTabsContainer.getWidth(), height, linePaint);
114 | }
115 |
116 | linePaint.setColor(indicatorColor);
117 |
118 | View currentTab = mTabsContainer.getChildAt(currentPosition);
119 | float lineLeft = currentTab.getLeft();
120 | float lineRight = currentTab.getRight();
121 |
122 | if (currentPositionOffset > 0f && currentPosition < tabCount - 1) {
123 | View nextTab = mTabsContainer.getChildAt(currentPosition + 1);
124 | final float nextTabLeft = nextTab.getLeft();
125 | final float nextTabRight = nextTab.getRight();
126 |
127 | lineLeft = (currentPositionOffset * nextTabLeft + (1f - currentPositionOffset) * lineLeft);
128 | lineRight = (currentPositionOffset * nextTabRight + (1f - currentPositionOffset) * lineRight);
129 | }
130 |
131 | if(indicatorOnTop) {
132 | canvas.drawRect(lineLeft, 0, lineRight, indicatorHeight, linePaint);
133 | } else {
134 | canvas.drawRect(lineLeft, height - indicatorHeight, lineRight, height, linePaint);
135 | }
136 |
137 | if(enableDivider) {
138 | diviPaint.setColor(dividerColor);
139 | for (int i = 0; i < tabCount - 1; i++) {
140 | View tab = mTabsContainer.getChildAt(i);
141 | canvas.drawLine(tab.getRight(), dividerPadding, tab.getRight(), height - dividerPadding, diviPaint);
142 | }
143 | }
144 | }
145 |
146 | public void setViewPager(ViewPager pager) {
147 | this.mPager = pager;
148 |
149 | if (pager.getAdapter() == null) {
150 | throw new IllegalStateException("ViewPager does not have adapter instance.");
151 | }
152 |
153 | pager.setOnPageChangeListener(new PageListener());
154 |
155 | notifyDataSetChanged();
156 | }
157 |
158 | public void setOnPageChangeListener(OnPageChangeListener listener) {
159 | this.mOnPageChangeListener = listener;
160 | }
161 |
162 | public void setOnTabReselectedListener(OnTabSelectedListener listener) {
163 | mTabSelectedListener = listener;
164 | }
165 |
166 | public void notifyDataSetChanged() {
167 |
168 | mTabsContainer.removeAllViews();
169 |
170 | tabCount = mPager.getAdapter().getCount();
171 |
172 | for (int i = 0; i < tabCount; i++) {
173 | addTab(i, mPager.getAdapter().getPageTitle(i).toString());
174 | }
175 |
176 | updateTabStyles();
177 | }
178 |
179 | private class TabView extends RelativeLayout {
180 | private TextView mTabText;
181 |
182 | public TabView(Context context) {
183 | super(context);
184 | init();
185 | }
186 |
187 | public TabView(Context context, AttributeSet attrs) {
188 | super(context, attrs);
189 | init();
190 | }
191 |
192 | public TabView(Context context, AttributeSet attrs, int defStyle) {
193 | super(context, attrs, defStyle);
194 | init();
195 | }
196 |
197 | private void init() {
198 | mTabText = new TextView(getContext());
199 | mTabText.setTextAppearance(getContext(), android.R.style.TextAppearance_Medium);
200 | mTabText.setTextSize(tabTextSize / getResources().getConfiguration().fontScale);
201 | mTabText.setSingleLine(true);
202 | mTabText.setGravity(Gravity.CENTER);
203 | LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
204 | this.addView(mTabText, params);
205 |
206 | }
207 |
208 | public TextView getTextView() {
209 | return mTabText;
210 | }
211 | }
212 |
213 | private void addTab(final int position, String title) {
214 | TabView tab = new TabView(getContext());
215 | tab.getTextView().setText(title);
216 | tab.setFocusable(true);
217 | tab.setOnClickListener(new OnClickListener() {
218 | @Override
219 | public void onClick(View v) {
220 | final int oldSelected = mPager.getCurrentItem();
221 | if (oldSelected != position && mTabSelectedListener != null) {
222 | mTabSelectedListener.onTabSelected(position);
223 | }
224 |
225 | mPager.setCurrentItem(position, viewPagerScrollWithAnimation);
226 | }
227 | });
228 |
229 | if(!enableExpand) {
230 | tab.setPadding(tabPadding, 0, tabPadding, 0);
231 | }
232 | mTabsContainer.addView(tab, position, enableExpand ?
233 | new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f) :
234 | new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
235 | }
236 |
237 | public void setTabText(int position, String text) {
238 | if (position < 0 || position > (mTabsContainer.getChildCount() - 1))
239 | throw new RuntimeException("tabs does not have this position.");
240 |
241 | View tab = mTabsContainer.getChildAt(position);
242 | if (tab instanceof TextView) {
243 | ((TextView) tab).setText(text);
244 | }
245 | }
246 |
247 | public boolean isIndicatorOnTop() {
248 | return indicatorOnTop;
249 | }
250 |
251 | public void setIndicatorOnTop(boolean indicatorOnTop) {
252 | this.indicatorOnTop = indicatorOnTop;
253 | }
254 |
255 | public boolean isEnableExpand() {
256 | return enableExpand;
257 | }
258 |
259 | public void setEnableExpand(boolean enableExpand) {
260 | this.enableExpand = enableExpand;
261 | }
262 |
263 | public boolean isEnableDivider() {
264 | return enableDivider;
265 | }
266 |
267 | public void setEnableDivider(boolean enableDivider) {
268 | this.enableDivider = enableDivider;
269 | }
270 |
271 | public void setViewPagerScrollWithAnimation(boolean enable) {
272 | this.viewPagerScrollWithAnimation = enable;
273 | }
274 |
275 | public boolean getViewPagerScrollWithAnimation() {
276 | return this.viewPagerScrollWithAnimation;
277 | }
278 |
279 | public void setCurrentItem(int item) {
280 | mPager.setCurrentItem(item, viewPagerScrollWithAnimation);
281 | }
282 |
283 | private void tabSelect(int index) {
284 | final int tabCount = mTabsContainer.getChildCount();
285 | for (int i = 0; i < tabCount; i++) {
286 | final View child = mTabsContainer.getChildAt(i);
287 | final boolean isSelected = (i == index);
288 | child.setSelected(isSelected);
289 | if(isSelected) {
290 | ((TabView) child).getTextView().setTextColor(textSelectedColor);
291 | } else {
292 | ((TabView) child).getTextView().setTextColor(textUnselectColor);
293 | }
294 | }
295 | }
296 |
297 | private void updateTabStyles() {
298 | for (int i = 0; i < tabCount; i++) {
299 | View v = mTabsContainer.getChildAt(i);
300 | v.setBackgroundColor(Color.TRANSPARENT);
301 | }
302 | tabSelect(mPager.getCurrentItem());
303 | }
304 |
305 | private void scrollToChild(int position, int offset) {
306 | if (tabCount == 0) {
307 | return;
308 | }
309 |
310 | int newScrollX = mTabsContainer.getChildAt(position).getLeft() + offset;
311 |
312 | if (position > 0 || offset > 0) {
313 | newScrollX -= scrollOffset;
314 | }
315 |
316 | if (newScrollX != lastScrollX) {
317 | lastScrollX = newScrollX;
318 | scrollTo(newScrollX, 0);
319 | }
320 | }
321 |
322 | private class PageListener implements OnPageChangeListener {
323 |
324 | @Override
325 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
326 | currentPosition = position;
327 | currentPositionOffset = positionOffset;
328 |
329 | scrollToChild(position, (int) (positionOffset * mTabsContainer.getChildAt(position).getWidth()));
330 |
331 | invalidate();
332 |
333 | if (mOnPageChangeListener != null) {
334 | mOnPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
335 | }
336 | }
337 |
338 | @Override
339 | public void onPageScrollStateChanged(int state) {
340 | if (state == ViewPager.SCROLL_STATE_IDLE) {
341 | scrollToChild(mPager.getCurrentItem(), 0);
342 | }
343 |
344 | if (mOnPageChangeListener != null) {
345 | mOnPageChangeListener.onPageScrollStateChanged(state);
346 | }
347 | }
348 |
349 | @Override
350 | public void onPageSelected(int position) {
351 | tabSelect(position);
352 |
353 | if (mOnPageChangeListener != null) {
354 | mOnPageChangeListener.onPageSelected(position);
355 | }
356 | }
357 | }
358 | }
--------------------------------------------------------------------------------
/viewpagerindicator/src/main/java/me/caiying/library/viewpagerindicator/Utils.java:
--------------------------------------------------------------------------------
1 | package me.caiying.library.viewpagerindicator;
2 |
3 | import android.content.Context;
4 |
5 | /**
6 | * Created by caiying on 06/02/2016.
7 | */
8 | public class Utils {
9 | public static int dp2px(Context context, float dpValue) {
10 | final float scale = context.getResources().getDisplayMetrics().density;
11 | return (int) (dpValue * scale + 0.5f);
12 | }
13 |
14 | public static int px2dp(Context context, float pxValue) {
15 | final float scale = context.getResources().getDisplayMetrics().density;
16 | return (int) (pxValue / scale + 0.5f);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/viewpagerindicator/src/main/res/drawable/mark_none_tips.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/viewpagerindicator/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ViewPagerIndicator
3 |
4 |
--------------------------------------------------------------------------------
/viewpagerindicator/src/test/java/me/caiying/library/viewpagerindicator/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package me.caiying.library.viewpagerindicator;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/viewpagerindicator/viewpagerindicator.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | generateDebugSources
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------