blob: ffba8f6bd16d39558d3dbb29627231ae7dd911c8 [file] [log] [blame]
Alexandre Lisione5b66022013-10-30 11:34:15 -04001/*
2 * Copyright (C) 2013 Andreas Stuetz <andreas.stuetz@gmail.com>
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
Alexandre Lisiona8b78722013-12-13 10:18:33 -050017package org.sflphone.views;
18
Alexandre Lisione5b66022013-10-30 11:34:15 -040019import java.util.Locale;
20
21import org.sflphone.R;
22
23import android.annotation.SuppressLint;
24import android.content.Context;
25import android.content.res.TypedArray;
26import android.graphics.Canvas;
27import android.graphics.Paint;
28import android.graphics.Paint.Style;
29import android.graphics.Typeface;
30import android.os.Build;
31import android.os.Parcel;
32import android.os.Parcelable;
33import android.support.v4.view.ViewPager;
34import android.support.v4.view.ViewPager.OnPageChangeListener;
35import android.util.AttributeSet;
36import android.util.DisplayMetrics;
37import android.util.TypedValue;
38import android.view.Gravity;
39import android.view.View;
40import android.view.ViewTreeObserver.OnGlobalLayoutListener;
41import android.widget.HorizontalScrollView;
42import android.widget.ImageButton;
43import android.widget.LinearLayout;
44import android.widget.TextView;
45
46public class PagerSlidingTabStrip extends HorizontalScrollView {
47
48 public interface IconTabProvider {
49 public int getPageIconResId(int position);
50 }
51
52 // @formatter:off
53 private static final int[] ATTRS = new int[] { android.R.attr.textSize, android.R.attr.textColor };
54 // @formatter:on
55
56 private LinearLayout.LayoutParams defaultTabLayoutParams;
57 private LinearLayout.LayoutParams expandedTabLayoutParams;
58
59 private final PageListener pageListener = new PageListener();
60 public OnPageChangeListener delegatePageListener;
61
62 private LinearLayout tabsContainer;
63 private ViewPager pager;
64
65 private int tabCount;
66
67 private int currentPosition = 0;
68 private float currentPositionOffset = 0f;
69
70 private Paint rectPaint;
71 private Paint dividerPaint;
72
73 private boolean checkedTabWidths = false;
74
75 private int indicatorColor = 0xFF666666;
76 private int underlineColor = 0x1A000000;
77 private int dividerColor = 0x1A000000;
78
79 private boolean shouldExpand = false;
80 private boolean textAllCaps = true;
81
82 private int scrollOffset = 52;
83 private int indicatorHeight = 8;
84 private int underlineHeight = 2;
85 private int dividerPadding = 12;
86 private int tabPadding = 24;
87 private int dividerWidth = 1;
88
89 private int tabTextSize = 12;
90 private int tabTextColor = 0xFF666666;
91 private Typeface tabTypeface = null;
92 private int tabTypefaceStyle = Typeface.BOLD;
93
94 private int lastScrollX = 0;
95
96 private int tabBackgroundResId = R.drawable.background_tabs;
97
98 private Locale locale;
99
100 public PagerSlidingTabStrip(Context context) {
101 this(context, null);
102 }
103
104 public PagerSlidingTabStrip(Context context, AttributeSet attrs) {
105 this(context, attrs, 0);
106 }
107
108 public PagerSlidingTabStrip(Context context, AttributeSet attrs, int defStyle) {
109 super(context, attrs, defStyle);
110
111 setFillViewport(true);
112 setWillNotDraw(false);
113
114 tabsContainer = new LinearLayout(context);
115 tabsContainer.setOrientation(LinearLayout.HORIZONTAL);
116 tabsContainer.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
117 addView(tabsContainer);
118
119 DisplayMetrics dm = getResources().getDisplayMetrics();
120
121 scrollOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, scrollOffset, dm);
122 indicatorHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, indicatorHeight, dm);
123 underlineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, underlineHeight, dm);
124 dividerPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dividerPadding, dm);
125 tabPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, tabPadding, dm);
126 dividerWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dividerWidth, dm);
127 tabTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, tabTextSize, dm);
128
129 // get system attrs (android:textSize and android:textColor)
130
131 TypedArray a = context.obtainStyledAttributes(attrs, ATTRS);
132
133 tabTextSize = a.getDimensionPixelSize(0, tabTextSize);
134 tabTextColor = a.getColor(1, tabTextColor);
135
136 a.recycle();
137
138 // get custom attrs
139
140 a = context.obtainStyledAttributes(attrs, R.styleable.PagerSlidingTabStrip);
141
142 indicatorColor = a.getColor(R.styleable.PagerSlidingTabStrip_indicatorColor, indicatorColor);
143 underlineColor = a.getColor(R.styleable.PagerSlidingTabStrip_underlineColor, underlineColor);
144 dividerColor = a.getColor(R.styleable.PagerSlidingTabStrip_dividerColor, dividerColor);
145 indicatorHeight = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_indicatorHeight, indicatorHeight);
146 underlineHeight = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_underlineHeight, underlineHeight);
147 dividerPadding = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_dividerPadding, dividerPadding);
148 tabPadding = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_tabPaddingLeftRight, tabPadding);
149 tabBackgroundResId = a.getResourceId(R.styleable.PagerSlidingTabStrip_tabBackground, tabBackgroundResId);
150 shouldExpand = a.getBoolean(R.styleable.PagerSlidingTabStrip_shouldExpand, shouldExpand);
151 scrollOffset = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_scrollOffset, scrollOffset);
152 textAllCaps = a.getBoolean(R.styleable.PagerSlidingTabStrip_textAllCaps, textAllCaps);
153
154 a.recycle();
155
156 rectPaint = new Paint();
157 rectPaint.setAntiAlias(true);
158 rectPaint.setStyle(Style.FILL);
159
160 dividerPaint = new Paint();
161 dividerPaint.setAntiAlias(true);
162 dividerPaint.setStrokeWidth(dividerWidth);
163
164 defaultTabLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
165 expandedTabLayoutParams = new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f);
166
167 if (locale == null) {
168 locale = getResources().getConfiguration().locale;
169 }
170 }
171
172 public void setViewPager(ViewPager pager) {
173 this.pager = pager;
174
175 if (pager.getAdapter() == null) {
176 throw new IllegalStateException("ViewPager does not have adapter instance.");
177 }
178
179 pager.setOnPageChangeListener(pageListener);
180
181 notifyDataSetChanged();
182 }
183
184 public void setOnPageChangeListener(OnPageChangeListener listener) {
185 this.delegatePageListener = listener;
186 }
187
188 public void notifyDataSetChanged() {
189
190 tabsContainer.removeAllViews();
191
192 tabCount = pager.getAdapter().getCount();
193
194 for (int i = 0; i < tabCount; i++) {
195
196 if (pager.getAdapter() instanceof IconTabProvider) {
197 addIconTab(i, ((IconTabProvider) pager.getAdapter()).getPageIconResId(i));
198 } else {
199 addTextTab(i, pager.getAdapter().getPageTitle(i).toString());
200 }
201
202 }
203
204 updateTabStyles();
205
206 checkedTabWidths = false;
207
208 getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
209
210 @SuppressWarnings("deprecation")
211 @SuppressLint("NewApi")
212 @Override
213 public void onGlobalLayout() {
214
215 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
216 getViewTreeObserver().removeGlobalOnLayoutListener(this);
217 } else {
218 getViewTreeObserver().removeOnGlobalLayoutListener(this);
219 }
220
221 currentPosition = pager.getCurrentItem();
222 scrollToChild(currentPosition, 0);
223 }
224 });
225
226 }
227
228 private void addTextTab(final int position, String title) {
229
230 TextView tab = new TextView(getContext());
231 tab.setText(title);
232 tab.setFocusable(true);
233 tab.setGravity(Gravity.CENTER);
234 tab.setSingleLine();
235
236 tab.setOnClickListener(new OnClickListener() {
237 @Override
238 public void onClick(View v) {
Alexandre Lisiona8b78722013-12-13 10:18:33 -0500239
Alexandre Lisione5b66022013-10-30 11:34:15 -0400240 pager.setCurrentItem(position);
241 }
242 });
243
244 tabsContainer.addView(tab);
245
246 }
247
248 private void addIconTab(final int position, int resId) {
249
250 ImageButton tab = new ImageButton(getContext());
251 tab.setFocusable(true);
252 tab.setImageResource(resId);
253
254 tab.setOnClickListener(new OnClickListener() {
255 @Override
256 public void onClick(View v) {
257 pager.setCurrentItem(position);
258 }
259 });
260
261 tabsContainer.addView(tab);
262
263 }
264
265 private void updateTabStyles() {
266
267 for (int i = 0; i < tabCount; i++) {
268
269 View v = tabsContainer.getChildAt(i);
270
271 v.setLayoutParams(defaultTabLayoutParams);
272 v.setBackgroundResource(tabBackgroundResId);
273 if (shouldExpand) {
274 v.setPadding(0, 0, 0, 0);
275 } else {
276 v.setPadding(tabPadding, 0, tabPadding, 0);
277 }
278
279 if (v instanceof TextView) {
280
281 TextView tab = (TextView) v;
282 tab.setTextSize(TypedValue.COMPLEX_UNIT_PX, tabTextSize);
283 tab.setTypeface(tabTypeface, tabTypefaceStyle);
284 tab.setTextColor(tabTextColor);
285
286 // setAllCaps() is only available from API 14, so the upper case is made manually if we are on a
287 // pre-ICS-build
288 if (textAllCaps) {
289 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
290 tab.setAllCaps(true);
291 } else {
292 tab.setText(tab.getText().toString().toUpperCase(locale));
293 }
294 }
295 }
296 }
297
298 }
299
300 @Override
301 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
302 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
303
304 if (!shouldExpand || MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED) {
305 return;
306 }
307
308 int myWidth = getMeasuredWidth();
309 int childWidth = 0;
310 for (int i = 0; i < tabCount; i++) {
311 childWidth += tabsContainer.getChildAt(i).getMeasuredWidth();
312 }
313
314 if (!checkedTabWidths && childWidth > 0 && myWidth > 0) {
315
316 if (childWidth <= myWidth) {
317 for (int i = 0; i < tabCount; i++) {
318 tabsContainer.getChildAt(i).setLayoutParams(expandedTabLayoutParams);
319 }
320 }
321
322 checkedTabWidths = true;
323 }
324 }
325
326 private void scrollToChild(int position, int offset) {
327
328 if (tabCount == 0) {
329 return;
330 }
331
332 int newScrollX = tabsContainer.getChildAt(position).getLeft() + offset;
333
334 if (position > 0 || offset > 0) {
335 newScrollX -= scrollOffset;
336 }
337
338 if (newScrollX != lastScrollX) {
339 lastScrollX = newScrollX;
340 scrollTo(newScrollX, 0);
341 }
342
343 }
344
345 @Override
346 protected void onDraw(Canvas canvas) {
347 super.onDraw(canvas);
348
349 if (isInEditMode() || tabCount == 0) {
350 return;
351 }
352
353 final int height = getHeight();
354
355 // draw indicator line
356
357 rectPaint.setColor(indicatorColor);
358
359 // default: line below current tab
360 View currentTab = tabsContainer.getChildAt(currentPosition);
361 float lineLeft = currentTab.getLeft();
362 float lineRight = currentTab.getRight();
363
364 // if there is an offset, start interpolating left and right coordinates between current and next tab
365 if (currentPositionOffset > 0f && currentPosition < tabCount - 1) {
366
367 View nextTab = tabsContainer.getChildAt(currentPosition + 1);
368 final float nextTabLeft = nextTab.getLeft();
369 final float nextTabRight = nextTab.getRight();
370
371 lineLeft = (currentPositionOffset * nextTabLeft + (1f - currentPositionOffset) * lineLeft);
372 lineRight = (currentPositionOffset * nextTabRight + (1f - currentPositionOffset) * lineRight);
373 }
374
375 canvas.drawRect(lineLeft, height - indicatorHeight, lineRight, height, rectPaint);
376
377 // draw underline
378
379 rectPaint.setColor(underlineColor);
380 canvas.drawRect(0, height - underlineHeight, tabsContainer.getWidth(), height, rectPaint);
381
382 // draw divider
383
384 dividerPaint.setColor(dividerColor);
385 for (int i = 0; i < tabCount - 1; i++) {
386 View tab = tabsContainer.getChildAt(i);
387 canvas.drawLine(tab.getRight(), dividerPadding, tab.getRight(), height - dividerPadding, dividerPaint);
388 }
389 }
390
391 private class PageListener implements OnPageChangeListener {
392
393 @Override
394 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
395
396 currentPosition = position;
397 currentPositionOffset = positionOffset;
398
399 scrollToChild(position, (int) (positionOffset * tabsContainer.getChildAt(position).getWidth()));
400
401 invalidate();
402
403 if (delegatePageListener != null) {
404 delegatePageListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
405 }
406 }
407
408 @Override
409 public void onPageScrollStateChanged(int state) {
410 if (state == ViewPager.SCROLL_STATE_IDLE) {
411 scrollToChild(pager.getCurrentItem(), 0);
412 }
413
414 if (delegatePageListener != null) {
415 delegatePageListener.onPageScrollStateChanged(state);
416 }
417 }
418
419 @Override
420 public void onPageSelected(int position) {
421 if (delegatePageListener != null) {
422 delegatePageListener.onPageSelected(position);
423 }
424 }
425
426 }
427
428 public void setIndicatorColor(int indicatorColor) {
429 this.indicatorColor = indicatorColor;
430 invalidate();
431 }
432
433 public void setIndicatorColorResource(int resId) {
434 this.indicatorColor = getResources().getColor(resId);
435 invalidate();
436 }
437
438 public int getIndicatorColor() {
439 return this.indicatorColor;
440 }
441
442 public void setIndicatorHeight(int indicatorLineHeightPx) {
443 this.indicatorHeight = indicatorLineHeightPx;
444 invalidate();
445 }
446
447 public int getIndicatorHeight() {
448 return indicatorHeight;
449 }
450
451 public void setUnderlineColor(int underlineColor) {
452 this.underlineColor = underlineColor;
453 invalidate();
454 }
455
456 public void setUnderlineColorResource(int resId) {
457 this.underlineColor = getResources().getColor(resId);
458 invalidate();
459 }
460
461 public int getUnderlineColor() {
462 return underlineColor;
463 }
464
465 public void setDividerColor(int dividerColor) {
466 this.dividerColor = dividerColor;
467 invalidate();
468 }
469
470 public void setDividerColorResource(int resId) {
471 this.dividerColor = getResources().getColor(resId);
472 invalidate();
473 }
474
475 public int getDividerColor() {
476 return dividerColor;
477 }
478
479 public void setUnderlineHeight(int underlineHeightPx) {
480 this.underlineHeight = underlineHeightPx;
481 invalidate();
482 }
483
484 public int getUnderlineHeight() {
485 return underlineHeight;
486 }
487
488 public void setDividerPadding(int dividerPaddingPx) {
489 this.dividerPadding = dividerPaddingPx;
490 invalidate();
491 }
492
493 public int getDividerPadding() {
494 return dividerPadding;
495 }
496
497 public void setScrollOffset(int scrollOffsetPx) {
498 this.scrollOffset = scrollOffsetPx;
499 invalidate();
500 }
501
502 public int getScrollOffset() {
503 return scrollOffset;
504 }
505
506 public void setShouldExpand(boolean shouldExpand) {
507 this.shouldExpand = shouldExpand;
508 requestLayout();
509 }
510
511 public boolean getShouldExpand() {
512 return shouldExpand;
513 }
514
515 public boolean isTextAllCaps() {
516 return textAllCaps;
517 }
518
519 public void setAllCaps(boolean textAllCaps) {
520 this.textAllCaps = textAllCaps;
521 }
522
523 public void setTextSize(int textSizePx) {
524 this.tabTextSize = textSizePx;
525 updateTabStyles();
526 }
527
528 public int getTextSize() {
529 return tabTextSize;
530 }
531
532 public void setTextColor(int textColor) {
533 this.tabTextColor = textColor;
534 updateTabStyles();
535 }
536
537 public void setTextColorResource(int resId) {
538 this.tabTextColor = getResources().getColor(resId);
539 updateTabStyles();
540 }
541
542 public int getTextColor() {
543 return tabTextColor;
544 }
545
546 public void setTypeface(Typeface typeface, int style) {
547 this.tabTypeface = typeface;
548 this.tabTypefaceStyle = style;
549 updateTabStyles();
550 }
551
552 public void setTabBackground(int resId) {
553 this.tabBackgroundResId = resId;
554 }
555
556 public int getTabBackground() {
557 return tabBackgroundResId;
558 }
559
560 public void setTabPaddingLeftRight(int paddingPx) {
561 this.tabPadding = paddingPx;
562 updateTabStyles();
563 }
564
565 public int getTabPaddingLeftRight() {
566 return tabPadding;
567 }
568
569 @Override
570 public void onRestoreInstanceState(Parcelable state) {
571 SavedState savedState = (SavedState) state;
572 super.onRestoreInstanceState(savedState.getSuperState());
573 currentPosition = savedState.currentPosition;
574 requestLayout();
575 }
576
577 @Override
578 public Parcelable onSaveInstanceState() {
579 Parcelable superState = super.onSaveInstanceState();
580 SavedState savedState = new SavedState(superState);
581 savedState.currentPosition = currentPosition;
582 return savedState;
583 }
584
585 static class SavedState extends BaseSavedState {
586 int currentPosition;
587
588 public SavedState(Parcelable superState) {
589 super(superState);
590 }
591
592 private SavedState(Parcel in) {
593 super(in);
594 currentPosition = in.readInt();
595 }
596
597 @Override
598 public void writeToParcel(Parcel dest, int flags) {
599 super.writeToParcel(dest, flags);
600 dest.writeInt(currentPosition);
601 }
602
603 public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
604 @Override
605 public SavedState createFromParcel(Parcel in) {
606 return new SavedState(in);
607 }
608
609 @Override
610 public SavedState[] newArray(int size) {
611 return new SavedState[size];
612 }
613 };
614 }
615
616}