package com.astuetz.viewpagertabs;
import com.actionbarsherlock.R;
import android.content.Context;
import android.content.res.TypedArray;
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.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class ViewPagerTabs extends ViewGroup implements OnPageChangeListener {
@SuppressWarnings("unused")
private static final String TAG = "ViewPagerTabs";
// Scrolling direction
private enum Direction {
Left, Right, Center
}
// Length of the horizontal fading edges
private static final int SHADOW_WIDTH = 35;
// Context.
private Context mContext;
// The referenced {@link ViewPager}
private ViewPager mPager;
// The current position
private int mPosition = 0;
// The offset at which tabs are going to
// be moved, if they are outside the screen
private int mOutsideOffset = -1;
// Used for initial positioning of the tabs
private boolean isFirstMeasurement = true;
// These values will be passed to every child ({@link ViewPagerTab})
private int mBackgroundColorPressed = 0x9943797F;
private int mTextColor = 0xFF999999;
private int mTextColorCenter = 0xFF91A438;
private int mLineColor = 0x00000000;
private int mLineColorCenter = 0xFF91A438;
private int mLineHeight = 3;
// These values are needed here
private int mTabPaddingLeft = 25;
private int mTabPaddingTop = 5;
private int mTabPaddingRight = 25;
private int mTabPaddingBottom = 10;
private float mTextSize = 14;
public ViewPagerTabs(Context context) {
this(context, null);
}
public ViewPagerTabs(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ViewPagerTabs(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewPagerTabs, defStyle, 0);
mBackgroundColorPressed = a.getColor(R.styleable.ViewPagerTabs_backgroundColorPressed, mBackgroundColorPressed);
mTextColor = a.getColor(R.styleable.ViewPagerTabs_textColor, mTextColor);
mTextColorCenter = a.getColor(R.styleable.ViewPagerTabs_textColorCenter, mTextColorCenter);
mLineColorCenter = a.getColor(R.styleable.ViewPagerTabs_lineColorCenter, mLineColorCenter);
mLineHeight = a.getDimensionPixelSize(R.styleable.ViewPagerTabs_lineMeHeight, mLineHeight);
mTextSize = a.getDimension(R.styleable.ViewPagerTabs_textSize, mTextSize);
mTabPaddingLeft = a.getDimensionPixelSize(R.styleable.ViewPagerTabs_tabPaddingLeft, mTabPaddingLeft);
mTabPaddingTop = a.getDimensionPixelSize(R.styleable.ViewPagerTabs_tabPaddingTop, mTabPaddingTop);
mTabPaddingRight = a.getDimensionPixelSize(R.styleable.ViewPagerTabs_tabPaddingRight, mTabPaddingRight);
mTabPaddingBottom = a.getDimensionPixelSize(R.styleable.ViewPagerTabs_tabPaddingBottom, mTabPaddingBottom);
mOutsideOffset = a.getDimensionPixelSize(R.styleable.ViewPagerTabs_outsideMeOffset, mOutsideOffset);
a.recycle();
setHorizontalFadingEdgeEnabled(true);
setFadingEdgeLength((int) (getResources().getDisplayMetrics().density * SHADOW_WIDTH));
setWillNotDraw(false);
}
@Override
protected float getLeftFadingEdgeStrength() {
return 1.0f;
}
@Override
protected float getRightFadingEdgeStrength() {
return 1.0f;
}
/**
* Notify the view that new data is available.
*/
public void notifyDatasetChanged() {
// remove all old child views
this.removeAllViews();
// add new tabs
for (int i = 0; i < mPager.getAdapter().getCount(); i++) {
addTab(i, ((ViewPagerTabProvider) mPager.getAdapter()).getTitle(i));
}
applyStyles();
calculateNewPositions(true);
this.requestLayout();
}
/**
* Binds the {@link ViewPager} to this instance
*
* @param pager
* An instance of {@link ViewPager}
*/
public void setViewPager(ViewPager pager) {
if (!(pager.getAdapter() instanceof ViewPagerTabProvider)) {
throw new IllegalStateException("The pager's adapter has to implement ViewPagerTabProvider.");
}
this.mPager = pager;
mPager.setCurrentItem(this.mPosition);
mPager.setOnPageChangeListener(this);
notifyDatasetChanged();
}
/**
* Binds the {@link ViewPager} to this instance and sets the current position
*
* @param pager
* An instance of {@link ViewPager}
* @param position
* Initial position of the {@link ViewPager}
*/
public void setViewPager(ViewPager pager, int position) {
this.mPosition = position;
setViewPager(pager);
}
/**
* Returns the current position
*/
public int getPosition() {
return this.mPosition;
}
/**
* Adds a new {@link ViewPagerTab} to the layout
*
* @param index
* The index from the Pagers adapter
* @param title
* The title which should be used
*/
public void addTab(int index, String title) {
ViewPagerTab tab = new ViewPagerTab(mContext);
tab.setText(title);
tab.setIndex(index);
addView(tab);
tab.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mPager.setCurrentItem(((ViewPagerTab) v).getIndex());
}
});
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final View c = getChildAt(0);
if (c != null) {
final TextView tab = (TextView) c;
LayoutParams layoutParams = (LayoutParams) tab.getLayoutParams();
final int widthSpec = MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY);
final int heightSpec = MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);
tab.measure(widthSpec, heightSpec);
setMeasuredDimension(resolveSize(0, widthMeasureSpec),
resolveSize(tab.getMeasuredHeight() + this.getPaddingTop() + this.getPaddingBottom(), heightMeasureSpec));
} else {
setMeasuredDimension(resolveSize(0, widthMeasureSpec),
resolveSize(this.getPaddingTop() + this.getPaddingBottom(), heightMeasureSpec));
}
// first time measuring, set outside offset (if not set manually),
// measure children and calculate initial positions
if (isFirstMeasurement) {
isFirstMeasurement = false;
if (mOutsideOffset < 0) mOutsideOffset = getMeasuredWidth();
measureChildren();
calculateNewPositions(true);
}
}
private void measureChildren() {
final int count = this.getChildCount();
for (int i = 0; i < count; i++) {
LayoutParams layoutParams = (LayoutParams) getChildAt(i).getLayoutParams();
final int widthSpec = MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY);
final int heightSpec = MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);
getChildAt(i).measure(widthSpec, heightSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
final int center = this.getMeasuredWidth() / 2;
// At this position, the centered tab will be highlighted via
// ViewPagerTab.setCenterPercent(int percent)
final int highlightOffset = this.getMeasuredWidth() / 5;
// lay out each tab
for (int i = 0; i < count; i++) {
final ViewPagerTab tab = (ViewPagerTab) getChildAt(i);
final int tabCenter = tab.layoutPos + tab.getMeasuredWidth() / 2;
int diff = Math.abs(center - tabCenter);
if (diff <= highlightOffset) {
final int x1 = highlightOffset;
final int y = (int) 100 * diff / x1;
tab.setCenterPercent(100 - y);
} else {
tab.setCenterPercent(0);
}
tab.layout(tab.layoutPos, this.getPaddingTop(), tab.layoutPos + tab.getMeasuredWidth(), this.getPaddingTop()
+ tab.getMeasuredHeight());
}
}
/**
* This method calculates the previous, current and next position for each tab
*
* @param firstTime
* If true, all tabs will be aligned at their initial position
*/
private void calculateNewPositions(boolean firstTime) {
final int pos = mPosition;
final int count = getChildCount();
// left outside, will not come to screen
for (int i = 0; i < pos - 2; i++) {
ViewPagerTab t = (ViewPagerTab) getChildAt(i);
t.currentPos = leftOutside(t);
t.prevPos = t.currentPos;
t.nextPos = t.currentPos;
}
// left outside, may come to screen
if (pos > 1) {
ViewPagerTab t = (ViewPagerTab) getChildAt(pos - 2);
t.currentPos = leftOutside(t);
t.prevPos = t.currentPos;
t.nextPos = left(t);
}
// left
if (pos > 0) {
ViewPagerTab t = (ViewPagerTab) getChildAt(pos - 1);
t.currentPos = left(t);
t.prevPos = leftOutside(t);
t.nextPos = center(t);
}
// center
if (count > 0) {
ViewPagerTab t = (ViewPagerTab) getChildAt(pos);
t.currentPos = center(t);
t.prevPos = left(t);
t.nextPos = right(t);
}
// right
if (pos < count - 1) {
ViewPagerTab t = (ViewPagerTab) getChildAt(pos + 1);
t.currentPos = right(t);
t.prevPos = center(t);
t.nextPos = rightOutside(t);
}
// right outside, may come to screen
if (pos < count - 2) {
ViewPagerTab t = (ViewPagerTab) getChildAt(pos + 2);
t.currentPos = rightOutside(t);
t.prevPos = right(t);
t.nextPos = t.currentPos;
}
// right outside, will not come to screen
for (int i = pos + 3; i < count; i++) {
ViewPagerTab t = (ViewPagerTab) getChildAt(i);
t.currentPos = rightOutside(t);
t.prevPos = t.currentPos;
t.nextPos = t.currentPos;
}
// Prevent tabs from overlapping
// TODO: make this better?
{
ViewPagerTab leftOutside = pos > 1 ? (ViewPagerTab) getChildAt(pos - 2) : null;
ViewPagerTab left = pos > 0 ? (ViewPagerTab) getChildAt(pos - 1) : null;
ViewPagerTab center = (ViewPagerTab) getChildAt(pos);
ViewPagerTab right = pos < getChildCount() - 1 ? (ViewPagerTab) getChildAt(pos + 1) : null;
ViewPagerTab rightOutside = pos < getChildCount() - 2 ? (ViewPagerTab) getChildAt(pos + 2) : null;
if (leftOutside != null) {
if (leftOutside.nextPos + leftOutside.getMeasuredWidth() >= left.nextPos) {
leftOutside.nextPos = left.nextPos - leftOutside.getMeasuredWidth();
}
}
if (left != null) {
if (left.currentPos + left.getMeasuredWidth() >= center.currentPos) {
left.currentPos = center.currentPos - left.getMeasuredWidth();
}
if (center.nextPos <= left.nextPos + left.getMeasuredWidth()) {
center.nextPos = left.nextPos + left.getMeasuredWidth();
}
}
if (right != null) {
if (center.prevPos + center.getMeasuredWidth() >= right.prevPos) {
center.prevPos = right.prevPos - center.getMeasuredWidth();
}
if (right.currentPos <= center.currentPos + center.getMeasuredWidth()) {
right.currentPos = center.currentPos + center.getMeasuredWidth();
}
}
if (rightOutside != null) {
if (rightOutside.prevPos <= right.prevPos + right.getMeasuredWidth()) {
rightOutside.prevPos = right.prevPos + right.getMeasuredWidth();
}
}
}
// Set initial positions for each tab
if (firstTime) {
for (int i = 0; i < count; i++) {
ViewPagerTab t = (ViewPagerTab) getChildAt(i);
t.layoutPos = t.currentPos;
}
}
}
private int leftOutside(ViewPagerTab tab) {
final int width = tab.getMeasuredWidth();
return width * (-1) - mOutsideOffset;
}
private int left(ViewPagerTab tab) {
return 0 - tab.getPaddingLeft();
}
private int center(ViewPagerTab tab) {
final int width = tab.getMeasuredWidth();
return this.getMeasuredWidth() / 2 - width / 2;
}
private int right(ViewPagerTab tab) {
final int width = tab.getMeasuredWidth();
return this.getMeasuredWidth() - width + tab.getPaddingRight();
}
private int rightOutside(ViewPagerTab tab) {
return this.getMeasuredWidth() + mOutsideOffset;
}
@Override
public void onPageScrollStateChanged(int state) {
}
/**
* At this point the scrolling direction is determined and every child is
* interpolated to its previous or next position
*/
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
final int count = getChildCount();
final int currentScrollX = mPosition * (mPager.getWidth() + mPager.getPageMargin());
Direction dir = Direction.Center;
float x = 0.0f;
if (mPager.getScrollX() < currentScrollX) {
dir = Direction.Left;
x = 1 - positionOffset;
} else if (mPager.getScrollX() > currentScrollX) {
dir = Direction.Right;
x = positionOffset;
}
for (int i = 0; i < count; i++) {
ViewPagerTab tab = (ViewPagerTab) getChildAt(i);
final float y0 = tab.currentPos;
float y1 = 0.0f;
if (dir == Direction.Left)
y1 = tab.nextPos;
else if (dir == Direction.Right)
y1 = tab.prevPos;
else
y1 = tab.currentPos;
tab.layoutPos = (int) (y0 + (y1 * x - y0 * x));
}
this.requestLayout();
}
@Override
public void onPageSelected(int position) {
mPosition = position;
calculateNewPositions(false);
this.requestLayout();
}
/*
* Public property access
*/
public void setTabPaddingLeft(int padding) {
this.mTabPaddingLeft = padding;
applyStyles();
}
public void setTabPaddingTop(int padding) {
this.mTabPaddingTop = padding;
applyStyles();
}
public void setTabPaddingRight(int padding) {
this.mTabPaddingRight = padding;
applyStyles();
}
public void setTabPaddingBottom(int padding) {
this.mTabPaddingBottom = padding;
applyStyles();
}
public void setTabPadding(int left, int top, int right, int bottom) {
this.mTabPaddingLeft = left;
this.mTabPaddingTop = top;
this.mTabPaddingRight = right;
this.mTabPaddingBottom = bottom;
applyStyles();
}
public void setBackgroundColorPressed(int color) {
this.mBackgroundColorPressed = color;
applyStyles();
}
public void setTextSize(float size) {
this.mTextSize = size;
applyStyles();
}
public void setTextColor(int color) {
this.mTextColor = color;
applyStyles();
}
public void setTextColorCenter(int color) {
this.mTextColorCenter = color;
applyStyles();
}
public void setLineColorCenter(int color) {
this.mLineColorCenter = color;
applyStyles();
}
public void setLineHeight(int height) {
this.mLineHeight = height;
applyStyles();
}
public void setOutsideOffset(int offset) {
this.mOutsideOffset = offset;
applyStyles();
}
/**
*
*/
private void applyStyles() {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
ViewPagerTab tab = (ViewPagerTab) getChildAt(i);
tab.setPadding(mTabPaddingLeft, mTabPaddingTop, mTabPaddingRight, mLineHeight + mTabPaddingBottom - 4);
tab.setTextColors(mTextColor, mTextColorCenter);
tab.setLineColors(mLineColor, mLineColorCenter);
tab.setLineHeight(mLineHeight);
tab.setBackgroundColorPressed(mBackgroundColorPressed);
tab.setTextSize(mTextSize);
}
measureChildren();
calculateNewPositions(true);
this.requestLayout();
}
/**
* Call this method if the titles in the pager's adapter were changed
*/
public void refreshTitles() {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
ViewPagerTab tab = (ViewPagerTab) getChildAt(i);
tab.setText(((ViewPagerTabProvider) mPager.getAdapter()).getTitle(i));
}
measureChildren();
calculateNewPositions(true);
this.requestLayout();
}
/*
* state handling
*/
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
mPosition = savedState.position;
mBackgroundColorPressed = savedState.backgroundColorPressed;
mTextColor = savedState.textColor;
mTextColorCenter = savedState.textColorCenter;
mLineColorCenter = savedState.lineColorCenter;
mLineHeight = savedState.lineHeight;
mTabPaddingLeft = savedState.tabPaddingLeft;
mTabPaddingTop = savedState.tabPaddingTop;
mTabPaddingRight = savedState.tabPaddingRight;
mTabPaddingBottom = savedState.tabPaddingBottom;
mTextSize = savedState.textSize;
applyStyles();
}
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState savedState = new SavedState(superState);
savedState.position = mPosition;
savedState.backgroundColorPressed = mBackgroundColorPressed;
savedState.textColor = mTextColor;
savedState.textColorCenter = mTextColorCenter;
savedState.lineColorCenter = mLineColorCenter;
savedState.lineHeight = mLineHeight;
savedState.tabPaddingLeft = mTabPaddingLeft;
savedState.tabPaddingTop = mTabPaddingTop;
savedState.tabPaddingRight = mTabPaddingRight;
savedState.tabPaddingBottom = mTabPaddingBottom;
savedState.textSize = mTextSize;
return savedState;
}
/**
* This holds our state
*
*/
static class SavedState extends BaseSavedState {
int position;
int backgroundColorPressed;
int textColor;
int textColorCenter;
int lineColorCenter;
int lineHeight;
int tabPaddingLeft;
int tabPaddingTop;
int tabPaddingRight;
int tabPaddingBottom;
float textSize;
public SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
position = in.readInt();
backgroundColorPressed = in.readInt();
textColor = in.readInt();
textColorCenter = in.readInt();
lineColorCenter = in.readInt();
lineHeight = in.readInt();
tabPaddingLeft = in.readInt();
tabPaddingTop = in.readInt();
tabPaddingRight = in.readInt();
tabPaddingBottom = in.readInt();
textSize = in.readFloat();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(position);
dest.writeInt(backgroundColorPressed);
dest.writeInt(textColor);
dest.writeInt(textColorCenter);
dest.writeInt(lineColorCenter);
dest.writeInt(lineHeight);
dest.writeInt(tabPaddingLeft);
dest.writeInt(tabPaddingTop);
dest.writeInt(tabPaddingRight);
dest.writeInt(tabPaddingBottom);
dest.writeFloat(textSize);
}
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];
}
};
}
}