Add Android PagerSlidingTabStrip library, an Interactive paging indicator widget
authorDa Risk <da_risk@beem-project.com>
Wed, 15 May 2013 11:35:03 +0200
changeset 1038 ae7bea323864
parent 1037 a1f3a20c87f8
child 1039 2f83df2983db
Add Android PagerSlidingTabStrip library, an Interactive paging indicator widget This library can be found at https://github.com/astuetz/PagerSlidingTabStrip/ and we currently use the v1.0 tag
res/drawable/background_tab.xml
res/values/attrs_pagersliding.xml
res/values/colors_pagersliding.xml
src/com/astuetz/viewpager/extensions/PagerSlidingTabStrip.java
src/com/astuetz/viewpager/extensions/package-info.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/res/drawable/background_tab.xml	Wed May 15 11:35:03 2013 +0200
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android" android:exitFadeDuration="@android:integer/config_shortAnimTime">
+
+    <item android:state_pressed="true" android:drawable="@color/background_tab_pressed" />
+    <item android:state_focused="true" android:drawable="@color/background_tab_pressed"/>
+    <item android:drawable="@android:color/transparent"/>
+
+</selector>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/res/values/attrs_pagersliding.xml	Wed May 15 11:35:03 2013 +0200
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <!-- Resources needed by PagerSlidingTabStrip -->
+    <declare-styleable name="PagerSlidingTabStrip">
+        <attr name="indicatorColor" format="color" />
+        <attr name="underlineColor" format="color" />
+        <attr name="dividerColor" format="color" />
+        <attr name="indicatorHeight" format="dimension" />
+        <attr name="underlineHeight" format="dimension" />
+        <attr name="dividerPadding" format="dimension" />
+        <attr name="tabPaddingLeftRight" format="dimension" />
+        <attr name="scrollOffset" format="dimension" />
+        <attr name="tabBackground" format="reference" />
+        <attr name="shouldExpand" format="boolean" />
+        <attr name="textAllCaps" format="boolean" />
+    </declare-styleable>
+
+</resources>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/res/values/colors_pagersliding.xml	Wed May 15 11:35:03 2013 +0200
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+	<!-- Color resources needed by PagerSlidingTabStrip -->
+	<color name="background_tab_pressed">#6633B5E5</color>
+</resources>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/astuetz/viewpager/extensions/PagerSlidingTabStrip.java	Wed May 15 11:35:03 2013 +0200
@@ -0,0 +1,618 @@
+/*
+ * Copyright (C) 2013 Andreas Stuetz <andreas.stuetz@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.astuetz.viewpager.extensions;
+
+import java.util.Locale;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Typeface;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.v4.view.ViewPager;
+import android.support.v4.view.ViewPager.OnPageChangeListener;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.widget.HorizontalScrollView;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.beem.project.beem.R;
+
+public class PagerSlidingTabStrip extends HorizontalScrollView {
+
+	public interface IconTabProvider {
+		public int getPageIconResId(int position);
+	}
+
+	// @formatter:off
+	private static final int[] ATTRS = new int[] {
+		android.R.attr.textSize,
+		android.R.attr.textColor
+    };
+	// @formatter:on
+
+	private LinearLayout.LayoutParams defaultTabLayoutParams;
+	private LinearLayout.LayoutParams expandedTabLayoutParams;
+
+	private final PageListener pageListener = new PageListener();
+	public OnPageChangeListener delegatePageListener;
+
+	private LinearLayout tabsContainer;
+	private ViewPager pager;
+
+	private int tabCount;
+
+	private int currentPosition = 0;
+	private float currentPositionOffset = 0f;
+
+	private Paint rectPaint;
+	private Paint dividerPaint;
+
+	private boolean checkedTabWidths = false;
+
+	private int indicatorColor = 0xFF666666;
+	private int underlineColor = 0x1A000000;
+	private int dividerColor = 0x1A000000;
+
+	private boolean shouldExpand = false;
+	private boolean textAllCaps = true;
+
+	private int scrollOffset = 52;
+	private int indicatorHeight = 8;
+	private int underlineHeight = 2;
+	private int dividerPadding = 12;
+	private int tabPadding = 24;
+	private int dividerWidth = 1;
+
+	private int tabTextSize = 12;
+	private int tabTextColor = 0xFF666666;
+	private Typeface tabTypeface = null;
+	private int tabTypefaceStyle = Typeface.BOLD;
+
+	private int lastScrollX = 0;
+
+	private int tabBackgroundResId = R.drawable.background_tab;
+
+	private Locale locale;
+
+	public PagerSlidingTabStrip(Context context) {
+		this(context, null);
+	}
+
+	public PagerSlidingTabStrip(Context context, AttributeSet attrs) {
+		this(context, attrs, 0);
+	}
+
+	public PagerSlidingTabStrip(Context context, AttributeSet attrs, int defStyle) {
+		super(context, attrs, defStyle);
+
+		setFillViewport(true);
+		setWillNotDraw(false);
+
+		tabsContainer = new LinearLayout(context);
+		tabsContainer.setOrientation(LinearLayout.HORIZONTAL);
+		tabsContainer.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+		addView(tabsContainer);
+
+		DisplayMetrics dm = getResources().getDisplayMetrics();
+
+		scrollOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, scrollOffset, dm);
+		indicatorHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, indicatorHeight, dm);
+		underlineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, underlineHeight, dm);
+		dividerPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dividerPadding, dm);
+		tabPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, tabPadding, dm);
+		dividerWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dividerWidth, dm);
+		tabTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, tabTextSize, dm);
+
+		// get system attrs (android:textSize and android:textColor)
+
+		TypedArray a = context.obtainStyledAttributes(attrs, ATTRS);
+
+		tabTextSize = a.getDimensionPixelSize(0, tabTextSize);
+		tabTextColor = a.getColor(1, tabTextColor);
+
+		a.recycle();
+
+		// get custom attrs
+
+		a = context.obtainStyledAttributes(attrs, R.styleable.PagerSlidingTabStrip);
+
+		indicatorColor = a.getColor(R.styleable.PagerSlidingTabStrip_indicatorColor, indicatorColor);
+		underlineColor = a.getColor(R.styleable.PagerSlidingTabStrip_underlineColor, underlineColor);
+		dividerColor = a.getColor(R.styleable.PagerSlidingTabStrip_dividerColor, dividerColor);
+		indicatorHeight = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_indicatorHeight, indicatorHeight);
+		underlineHeight = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_underlineHeight, underlineHeight);
+		dividerPadding = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_dividerPadding, dividerPadding);
+		tabPadding = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_tabPaddingLeftRight, tabPadding);
+		tabBackgroundResId = a.getResourceId(R.styleable.PagerSlidingTabStrip_tabBackground, tabBackgroundResId);
+		shouldExpand = a.getBoolean(R.styleable.PagerSlidingTabStrip_shouldExpand, shouldExpand);
+		scrollOffset = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_scrollOffset, scrollOffset);
+		textAllCaps = a.getBoolean(R.styleable.PagerSlidingTabStrip_textAllCaps, textAllCaps);
+
+		a.recycle();
+
+		rectPaint = new Paint();
+		rectPaint.setAntiAlias(true);
+		rectPaint.setStyle(Style.FILL);
+
+		dividerPaint = new Paint();
+		dividerPaint.setAntiAlias(true);
+		dividerPaint.setStrokeWidth(dividerWidth);
+
+		defaultTabLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
+		expandedTabLayoutParams = new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f);
+
+		if (locale == null) {
+			locale = getResources().getConfiguration().locale;
+		}
+	}
+
+	public void setViewPager(ViewPager pager) {
+		this.pager = pager;
+
+		if (pager.getAdapter() == null) {
+			throw new IllegalStateException("ViewPager does not have adapter instance.");
+		}
+
+		pager.setOnPageChangeListener(pageListener);
+
+		notifyDataSetChanged();
+	}
+
+	public void setOnPageChangeListener(OnPageChangeListener listener) {
+		this.delegatePageListener = listener;
+	}
+
+	public void notifyDataSetChanged() {
+
+		tabsContainer.removeAllViews();
+
+		tabCount = pager.getAdapter().getCount();
+
+		for (int i = 0; i < tabCount; i++) {
+
+			if (pager.getAdapter() instanceof IconTabProvider) {
+				addIconTab(i, ((IconTabProvider) pager.getAdapter()).getPageIconResId(i));
+			} else {
+				addTextTab(i, pager.getAdapter().getPageTitle(i).toString());
+			}
+
+		}
+
+		updateTabStyles();
+
+		checkedTabWidths = false;
+
+		getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+
+			@SuppressWarnings("deprecation")
+			@SuppressLint("NewApi")
+			@Override
+			public void onGlobalLayout() {
+
+				if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+					getViewTreeObserver().removeGlobalOnLayoutListener(this);
+				} else {
+					getViewTreeObserver().removeOnGlobalLayoutListener(this);
+				}
+
+				currentPosition = pager.getCurrentItem();
+				scrollToChild(currentPosition, 0);
+			}
+		});
+
+	}
+
+	private void addTextTab(final int position, String title) {
+
+		TextView tab = new TextView(getContext());
+		tab.setText(title);
+		tab.setFocusable(true);
+		tab.setGravity(Gravity.CENTER);
+		tab.setSingleLine();
+
+		tab.setOnClickListener(new OnClickListener() {
+			@Override
+			public void onClick(View v) {
+				pager.setCurrentItem(position);
+			}
+		});
+
+		tabsContainer.addView(tab);
+
+	}
+
+	private void addIconTab(final int position, int resId) {
+
+		ImageButton tab = new ImageButton(getContext());
+		tab.setFocusable(true);
+		tab.setImageResource(resId);
+
+		tab.setOnClickListener(new OnClickListener() {
+			@Override
+			public void onClick(View v) {
+				pager.setCurrentItem(position);
+			}
+		});
+
+		tabsContainer.addView(tab);
+
+	}
+
+	private void updateTabStyles() {
+
+		for (int i = 0; i < tabCount; i++) {
+
+			View v = tabsContainer.getChildAt(i);
+
+			v.setLayoutParams(defaultTabLayoutParams);
+			v.setBackgroundResource(tabBackgroundResId);
+			if (shouldExpand) {
+				v.setPadding(0, 0, 0, 0);
+			} else {
+				v.setPadding(tabPadding, 0, tabPadding, 0);
+			}
+
+			if (v instanceof TextView) {
+
+				TextView tab = (TextView) v;
+				tab.setTextSize(TypedValue.COMPLEX_UNIT_PX, tabTextSize);
+				tab.setTypeface(tabTypeface, tabTypefaceStyle);
+				tab.setTextColor(tabTextColor);
+
+				// setAllCaps() is only available from API 14, so the upper case is made manually if we are on a
+				// pre-ICS-build
+				if (textAllCaps) {
+					if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+						tab.setAllCaps(true);
+					} else {
+						tab.setText(tab.getText().toString().toUpperCase(locale));
+					}
+				}
+			}
+		}
+
+	}
+
+	@Override
+	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+		if (!shouldExpand || MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED) {
+			return;
+		}
+
+		int myWidth = getMeasuredWidth();
+		int childWidth = 0;
+		for (int i = 0; i < tabCount; i++) {
+			childWidth += tabsContainer.getChildAt(i).getMeasuredWidth();
+		}
+
+		if (!checkedTabWidths && childWidth > 0 && myWidth > 0) {
+
+			if (childWidth <= myWidth) {
+				for (int i = 0; i < tabCount; i++) {
+					tabsContainer.getChildAt(i).setLayoutParams(expandedTabLayoutParams);
+				}
+			}
+
+			checkedTabWidths = true;
+		}
+	}
+
+	private void scrollToChild(int position, int offset) {
+
+		if (tabCount == 0) {
+			return;
+		}
+
+		int newScrollX = tabsContainer.getChildAt(position).getLeft() + offset;
+
+		if (position > 0 || offset > 0) {
+			newScrollX -= scrollOffset;
+		}
+
+		if (newScrollX != lastScrollX) {
+			lastScrollX = newScrollX;
+			scrollTo(newScrollX, 0);
+		}
+
+	}
+
+	@Override
+	protected void onDraw(Canvas canvas) {
+		super.onDraw(canvas);
+
+		if (isInEditMode() || tabCount == 0) {
+			return;
+		}
+
+		final int height = getHeight();
+
+		// draw indicator line
+
+		rectPaint.setColor(indicatorColor);
+
+		// default: line below current tab
+		View currentTab = tabsContainer.getChildAt(currentPosition);
+		float lineLeft = currentTab.getLeft();
+		float lineRight = currentTab.getRight();
+
+		// if there is an offset, start interpolating left and right coordinates between current and next tab
+		if (currentPositionOffset > 0f && currentPosition < tabCount - 1) {
+
+			View nextTab = tabsContainer.getChildAt(currentPosition + 1);
+			final float nextTabLeft = nextTab.getLeft();
+			final float nextTabRight = nextTab.getRight();
+
+			lineLeft = (currentPositionOffset * nextTabLeft + (1f - currentPositionOffset) * lineLeft);
+			lineRight = (currentPositionOffset * nextTabRight + (1f - currentPositionOffset) * lineRight);
+		}
+
+		canvas.drawRect(lineLeft, height - indicatorHeight, lineRight, height, rectPaint);
+
+		// draw underline
+
+		rectPaint.setColor(underlineColor);
+		canvas.drawRect(0, height - underlineHeight, tabsContainer.getWidth(), height, rectPaint);
+
+		// draw divider
+
+		dividerPaint.setColor(dividerColor);
+		for (int i = 0; i < tabCount - 1; i++) {
+			View tab = tabsContainer.getChildAt(i);
+			canvas.drawLine(tab.getRight(), dividerPadding, tab.getRight(), height - dividerPadding, dividerPaint);
+		}
+	}
+
+	private class PageListener implements OnPageChangeListener {
+
+		@Override
+		public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+
+			currentPosition = position;
+			currentPositionOffset = positionOffset;
+
+			scrollToChild(position, (int) (positionOffset * tabsContainer.getChildAt(position).getWidth()));
+
+			invalidate();
+
+			if (delegatePageListener != null) {
+				delegatePageListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
+			}
+		}
+
+		@Override
+		public void onPageScrollStateChanged(int state) {
+			if (state == ViewPager.SCROLL_STATE_IDLE) {
+				scrollToChild(pager.getCurrentItem(), 0);
+			}
+
+			if (delegatePageListener != null) {
+				delegatePageListener.onPageScrollStateChanged(state);
+			}
+		}
+
+		@Override
+		public void onPageSelected(int position) {
+			if (delegatePageListener != null) {
+				delegatePageListener.onPageSelected(position);
+			}
+		}
+
+	}
+
+	public void setIndicatorColor(int indicatorColor) {
+		this.indicatorColor = indicatorColor;
+		invalidate();
+	}
+
+	public void setIndicatorColorResource(int resId) {
+		this.indicatorColor = getResources().getColor(resId);
+		invalidate();
+	}
+
+	public int getIndicatorColor() {
+		return this.indicatorColor;
+	}
+
+	public void setIndicatorHeight(int indicatorLineHeightPx) {
+		this.indicatorHeight = indicatorLineHeightPx;
+		invalidate();
+	}
+
+	public int getIndicatorHeight() {
+		return indicatorHeight;
+	}
+
+	public void setUnderlineColor(int underlineColor) {
+		this.underlineColor = underlineColor;
+		invalidate();
+	}
+
+	public void setUnderlineColorResource(int resId) {
+		this.underlineColor = getResources().getColor(resId);
+		invalidate();
+	}
+
+	public int getUnderlineColor() {
+		return underlineColor;
+	}
+
+	public void setDividerColor(int dividerColor) {
+		this.dividerColor = dividerColor;
+		invalidate();
+	}
+
+	public void setDividerColorResource(int resId) {
+		this.dividerColor = getResources().getColor(resId);
+		invalidate();
+	}
+
+	public int getDividerColor() {
+		return dividerColor;
+	}
+
+	public void setUnderlineHeight(int underlineHeightPx) {
+		this.underlineHeight = underlineHeightPx;
+		invalidate();
+	}
+
+	public int getUnderlineHeight() {
+		return underlineHeight;
+	}
+
+	public void setDividerPadding(int dividerPaddingPx) {
+		this.dividerPadding = dividerPaddingPx;
+		invalidate();
+	}
+
+	public int getDividerPadding() {
+		return dividerPadding;
+	}
+
+	public void setScrollOffset(int scrollOffsetPx) {
+		this.scrollOffset = scrollOffsetPx;
+		invalidate();
+	}
+
+	public int getScrollOffset() {
+		return scrollOffset;
+	}
+
+	public void setShouldExpand(boolean shouldExpand) {
+		this.shouldExpand = shouldExpand;
+		requestLayout();
+	}
+
+	public boolean getShouldExpand() {
+		return shouldExpand;
+	}
+
+	public boolean isTextAllCaps() {
+		return textAllCaps;
+	}
+
+	public void setAllCaps(boolean textAllCaps) {
+		this.textAllCaps = textAllCaps;
+	}
+
+	public void setTextSize(int textSizePx) {
+		this.tabTextSize = textSizePx;
+		updateTabStyles();
+	}
+
+	public int getTextSize() {
+		return tabTextSize;
+	}
+
+	public void setTextColor(int textColor) {
+		this.tabTextColor = textColor;
+		updateTabStyles();
+	}
+
+	public void setTextColorResource(int resId) {
+		this.tabTextColor = getResources().getColor(resId);
+		updateTabStyles();
+	}
+
+	public int getTextColor() {
+		return tabTextColor;
+	}
+
+	public void setTypeface(Typeface typeface, int style) {
+		this.tabTypeface = typeface;
+		this.tabTypefaceStyle = style;
+		updateTabStyles();
+	}
+
+	public void setTabBackground(int resId) {
+		this.tabBackgroundResId = resId;
+	}
+
+	public int getTabBackground() {
+		return tabBackgroundResId;
+	}
+
+	public void setTabPaddingLeftRight(int paddingPx) {
+		this.tabPadding = paddingPx;
+		updateTabStyles();
+	}
+
+	public int getTabPaddingLeftRight() {
+		return tabPadding;
+	}
+
+	@Override
+	public void onRestoreInstanceState(Parcelable state) {
+		SavedState savedState = (SavedState) state;
+		super.onRestoreInstanceState(savedState.getSuperState());
+		currentPosition = savedState.currentPosition;
+		requestLayout();
+	}
+
+	@Override
+	public Parcelable onSaveInstanceState() {
+		Parcelable superState = super.onSaveInstanceState();
+		SavedState savedState = new SavedState(superState);
+		savedState.currentPosition = currentPosition;
+		return savedState;
+	}
+
+	static class SavedState extends BaseSavedState {
+		int currentPosition;
+
+		public SavedState(Parcelable superState) {
+			super(superState);
+		}
+
+		private SavedState(Parcel in) {
+			super(in);
+			currentPosition = in.readInt();
+		}
+
+		@Override
+		public void writeToParcel(Parcel dest, int flags) {
+			super.writeToParcel(dest, flags);
+			dest.writeInt(currentPosition);
+		}
+
+		public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
+			@Override
+			public SavedState createFromParcel(Parcel in) {
+				return new SavedState(in);
+			}
+
+			@Override
+			public SavedState[] newArray(int size) {
+				return new SavedState[size];
+			}
+		};
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/astuetz/viewpager/extensions/package-info.java	Wed May 15 11:35:03 2013 +0200
@@ -0,0 +1,10 @@
+/**
+ * This package contains an implementation of an
+ * Interactive paging indicator widget, compatible with the `ViewPager` from the 
+ * Android Support Library.
+ *
+ * It is developped by Andreas Stuetz
+ * and can be found here https://github.com/astuetz/PagerSlidingTabStrip/
+ * We use the git tag v1.0 aka 0e7f1b9a961692323dbd7300e13dd1d78ad0ae44
+ */
+package com.astuetz.viewpager.extensions;