Created
June 17, 2013 23:24
-
-
Save derekbrameyer/5801412 to your computer and use it in GitHub Desktop.
Usage of tab background selector state and a ColorStateList for astuetz's PagerSlidingTabStrip
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2013 Andreas Stuetz <[email protected]> | |
* | |
* 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 android.annotation.SuppressLint; | |
import android.content.Context; | |
import android.content.res.ColorStateList; | |
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 java.util.Locale; | |
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 int selectedPosition = 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 ColorStateList tabTextColor = ColorStateList.valueOf(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.getColorStateList(1); | |
if (tabTextColor == null) { | |
tabTextColor = ColorStateList.valueOf(0xFF666666); | |
} | |
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); | |
// select the selected tab | |
for (int i = 0; i < tabsContainer.getChildCount(); i++) { | |
View tab = tabsContainer.getChildAt(i); | |
tab.setSelected(i == selectedPosition); | |
} | |
// 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); | |
} | |
selectedPosition = position; | |
invalidate(); | |
} | |
} | |
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 = ColorStateList.valueOf(textColor); | |
updateTabStyles(); | |
} | |
public void setTextColorResource(int resId) { | |
this.tabTextColor = getResources().getColorStateList(resId); | |
updateTabStyles(); | |
} | |
public ColorStateList 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]; | |
} | |
}; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment