| /* | |
| * Copyright (C) 2013 The Android Open Source Project | |
| * | |
| * 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.example.android.common.view; | |
| import android.content.Context; | |
| import android.graphics.Typeface; | |
| import android.os.Build; | |
| import android.support.v4.view.PagerAdapter; | |
| import android.support.v4.view.ViewPager; | |
| import android.util.AttributeSet; | |
| import android.util.TypedValue; | |
| import android.view.Gravity; | |
| import android.view.LayoutInflater; | |
| import android.view.View; | |
| import android.widget.HorizontalScrollView; | |
| import android.widget.TextView; | |
| /** | |
| * To be used with ViewPager to provide a tab indicator component which give constant feedback as to | |
| * the user's scroll progress. | |
| * <p> | |
| * To use the component, simply add it to your view hierarchy. Then in your | |
| * {@link android.app.Activity} or {@link android.support.v4.app.Fragment} call | |
| * {@link #setViewPager(ViewPager)} providing it the ViewPager this layout is being used for. | |
| * <p> | |
| * The colors can be customized in two ways. The first and simplest is to provide an array of colors | |
| * via {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)}. The | |
| * alternative is via the {@link TabColorizer} interface which provides you complete control over | |
| * which color is used for any individual position. | |
| * <p> | |
| * The views used as tabs can be customized by calling {@link #setCustomTabView(int, int)}, | |
| * providing the layout ID of your custom layout. | |
| */ | |
| public class SlidingTabLayout extends HorizontalScrollView { | |
| /** | |
| * Allows complete control over the colors drawn in the tab layout. Set with | |
| * {@link #setCustomTabColorizer(TabColorizer)}. | |
| */ | |
| public interface TabColorizer { | |
| /** | |
| * @return return the color of the indicator used when {@code position} is selected. | |
| */ | |
| int getIndicatorColor(int position); | |
| /** | |
| * @return return the color of the divider drawn to the right of {@code position}. | |
| */ | |
| int getDividerColor(int position); | |
| } | |
| private static final int TITLE_OFFSET_DIPS = 24; | |
| private static final int TAB_VIEW_PADDING_DIPS = 16; | |
| private static final int TAB_VIEW_TEXT_SIZE_SP = 12; | |
| private int mTitleOffset; | |
| private int mTabViewLayoutId; | |
| private int mTabViewTextViewId; | |
| private ViewPager mViewPager; | |
| private ViewPager.OnPageChangeListener mViewPagerPageChangeListener; | |
| private final SlidingTabStrip mTabStrip; | |
| public SlidingTabLayout(Context context) { | |
| this(context, null); | |
| } | |
| public SlidingTabLayout(Context context, AttributeSet attrs) { | |
| this(context, attrs, 0); | |
| } | |
| public SlidingTabLayout(Context context, AttributeSet attrs, int defStyle) { | |
| super(context, attrs, defStyle); | |
| // Disable the Scroll Bar | |
| setHorizontalScrollBarEnabled(false); | |
| // Make sure that the Tab Strips fills this View | |
| setFillViewport(true); | |
| mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density); | |
| mTabStrip = new SlidingTabStrip(context); | |
| addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); | |
| } | |
| /** | |
| * Set the custom {@link TabColorizer} to be used. | |
| * | |
| * If you only require simple custmisation then you can use | |
| * {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)} to achieve | |
| * similar effects. | |
| */ | |
| public void setCustomTabColorizer(TabColorizer tabColorizer) { | |
| mTabStrip.setCustomTabColorizer(tabColorizer); | |
| } | |
| /** | |
| * Sets the colors to be used for indicating the selected tab. These colors are treated as a | |
| * circular array. Providing one color will mean that all tabs are indicated with the same color. | |
| */ | |
| public void setSelectedIndicatorColors(int... colors) { | |
| mTabStrip.setSelectedIndicatorColors(colors); | |
| } | |
| /** | |
| * Sets the colors to be used for tab dividers. These colors are treated as a circular array. | |
| * Providing one color will mean that all tabs are indicated with the same color. | |
| */ | |
| public void setDividerColors(int... colors) { | |
| mTabStrip.setDividerColors(colors); | |
| } | |
| /** | |
| * Set the {@link ViewPager.OnPageChangeListener}. When using {@link SlidingTabLayout} you are | |
| * required to set any {@link ViewPager.OnPageChangeListener} through this method. This is so | |
| * that the layout can update it's scroll position correctly. | |
| * | |
| * @see ViewPager#setOnPageChangeListener(ViewPager.OnPageChangeListener) | |
| */ | |
| public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { | |
| mViewPagerPageChangeListener = listener; | |
| } | |
| /** | |
| * Set the custom layout to be inflated for the tab views. | |
| * | |
| * @param layoutResId Layout id to be inflated | |
| * @param textViewId id of the {@link TextView} in the inflated view | |
| */ | |
| public void setCustomTabView(int layoutResId, int textViewId) { | |
| mTabViewLayoutId = layoutResId; | |
| mTabViewTextViewId = textViewId; | |
| } | |
| /** | |
| * Sets the associated view pager. Note that the assumption here is that the pager content | |
| * (number of tabs and tab titles) does not change after this call has been made. | |
| */ | |
| public void setViewPager(ViewPager viewPager) { | |
| mTabStrip.removeAllViews(); | |
| mViewPager = viewPager; | |
| if (viewPager != null) { | |
| viewPager.setOnPageChangeListener(new InternalViewPagerListener()); | |
| populateTabStrip(); | |
| } | |
| } | |
| /** | |
| * Create a default view to be used for tabs. This is called if a custom tab view is not set via | |
| * {@link #setCustomTabView(int, int)}. | |
| */ | |
| protected TextView createDefaultTabView(Context context) { | |
| TextView textView = new TextView(context); | |
| textView.setGravity(Gravity.CENTER); | |
| textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP); | |
| textView.setTypeface(Typeface.DEFAULT_BOLD); | |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { | |
| // If we're running on Honeycomb or newer, then we can use the Theme's | |
| // selectableItemBackground to ensure that the View has a pressed state | |
| TypedValue outValue = new TypedValue(); | |
| getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, | |
| outValue, true); | |
| textView.setBackgroundResource(outValue.resourceId); | |
| } | |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { | |
| // If we're running on ICS or newer, enable all-caps to match the Action Bar tab style | |
| textView.setAllCaps(true); | |
| } | |
| int padding = (int) (TAB_VIEW_PADDING_DIPS * getResources().getDisplayMetrics().density); | |
| textView.setPadding(padding, padding, padding, padding); | |
| return textView; | |
| } | |
| private void populateTabStrip() { | |
| final PagerAdapter adapter = mViewPager.getAdapter(); | |
| final View.OnClickListener tabClickListener = new TabClickListener(); | |
| for (int i = 0; i < adapter.getCount(); i++) { | |
| View tabView = null; | |
| TextView tabTitleView = null; | |
| if (mTabViewLayoutId != 0) { | |
| // If there is a custom tab view layout id set, try and inflate it | |
| tabView = LayoutInflater.from(getContext()).inflate(mTabViewLayoutId, mTabStrip, | |
| false); | |
| tabTitleView = (TextView) tabView.findViewById(mTabViewTextViewId); | |
| } | |
| if (tabView == null) { | |
| tabView = createDefaultTabView(getContext()); | |
| } | |
| if (tabTitleView == null && TextView.class.isInstance(tabView)) { | |
| tabTitleView = (TextView) tabView; | |
| } | |
| tabTitleView.setText(adapter.getPageTitle(i)); | |
| tabView.setOnClickListener(tabClickListener); | |
| mTabStrip.addView(tabView); | |
| } | |
| } | |
| @Override | |
| protected void onAttachedToWindow() { | |
| super.onAttachedToWindow(); | |
| if (mViewPager != null) { | |
| scrollToTab(mViewPager.getCurrentItem(), 0); | |
| } | |
| } | |
| private void scrollToTab(int tabIndex, int positionOffset) { | |
| final int tabStripChildCount = mTabStrip.getChildCount(); | |
| if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) { | |
| return; | |
| } | |
| View selectedChild = mTabStrip.getChildAt(tabIndex); | |
| if (selectedChild != null) { | |
| int targetScrollX = selectedChild.getLeft() + positionOffset; | |
| if (tabIndex > 0 || positionOffset > 0) { | |
| // If we're not at the first child and are mid-scroll, make sure we obey the offset | |
| targetScrollX -= mTitleOffset; | |
| } | |
| scrollTo(targetScrollX, 0); | |
| } | |
| } | |
| private class InternalViewPagerListener implements ViewPager.OnPageChangeListener { | |
| private int mScrollState; | |
| @Override | |
| public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { | |
| int tabStripChildCount = mTabStrip.getChildCount(); | |
| if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) { | |
| return; | |
| } | |
| mTabStrip.onViewPagerPageChanged(position, positionOffset); | |
| View selectedTitle = mTabStrip.getChildAt(position); | |
| int extraOffset = (selectedTitle != null) | |
| ? (int) (positionOffset * selectedTitle.getWidth()) | |
| : 0; | |
| scrollToTab(position, extraOffset); | |
| if (mViewPagerPageChangeListener != null) { | |
| mViewPagerPageChangeListener.onPageScrolled(position, positionOffset, | |
| positionOffsetPixels); | |
| } | |
| } | |
| @Override | |
| public void onPageScrollStateChanged(int state) { | |
| mScrollState = state; | |
| if (mViewPagerPageChangeListener != null) { | |
| mViewPagerPageChangeListener.onPageScrollStateChanged(state); | |
| } | |
| } | |
| @Override | |
| public void onPageSelected(int position) { | |
| if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { | |
| mTabStrip.onViewPagerPageChanged(position, 0f); | |
| scrollToTab(position, 0); | |
| } | |
| if (mViewPagerPageChangeListener != null) { | |
| mViewPagerPageChangeListener.onPageSelected(position); | |
| } | |
| } | |
| } | |
| private class TabClickListener implements View.OnClickListener { | |
| @Override | |
| public void onClick(View v) { | |
| for (int i = 0; i < mTabStrip.getChildCount(); i++) { | |
| if (v == mTabStrip.getChildAt(i)) { | |
| mViewPager.setCurrentItem(i); | |
| return; | |
| } | |
| } | |
| } | |
| } | |
| } |