├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── fauzie │ │ └── sample │ │ └── tabsspinner │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── fauzie │ │ └── sample │ │ └── tabsspinner │ │ ├── BaseActivity.java │ │ ├── MainActivity.java │ │ ├── SpinnerActivity.java │ │ ├── TabsActivity.java │ │ └── widget │ │ ├── SlidingTabLayout.java │ │ └── SlidingTabStrip.java │ └── res │ ├── color │ ├── list_dropdown_foreground_color.xml │ └── tab_text_color.xml │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ ├── ic_launcher.png │ └── spinner_triangle.png │ ├── layout │ ├── actionbar_spinner.xml │ ├── activity_main.xml │ ├── activity_spinner.xml │ ├── activity_tabs.xml │ ├── fragment_sample.xml │ ├── spinner_item_actionbar.xml │ ├── spinner_item_dropdown.xml │ ├── tab_indicator.xml │ ├── toolbar.xml │ └── toolbar_no_bg.xml │ ├── values-v21 │ └── styles.xml │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshots ├── spinner.png └── tabs.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | build/ 16 | 17 | # Local configuration file (sdk path, etc) 18 | local.properties 19 | 20 | # Eclipse project files 21 | .classpath 22 | .project 23 | 24 | # Windows thumbnail db 25 | .DS_Store 26 | 27 | # IDEA/Android Studio project files, because 28 | # the project can be imported from settings.gradle 29 | .idea 30 | *.iml 31 | 32 | # Old-style IDEA project files 33 | *.ipr 34 | *.iws 35 | 36 | # Local IDEA workspace 37 | .idea/workspace.xml 38 | 39 | # Gradle cache 40 | .gradle 41 | 42 | # Sandbox stuff 43 | _sandbox 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android Material Tabs and Spinner Sample 2 | ============================= 3 | 4 | Works on Android 2.3.3 and later. 5 | 6 | Screenshots 7 | ----------------------------- 8 | 9 | ![Alt text](/screenshots/spinner.png?raw=true "Spinner Sample") 10 | ![Alt text](/screenshots/tabs.png?raw=true "Tabs Sample") 11 | 12 | 13 | Licence 14 | ----------------------------- 15 | 16 | The MIT License (MIT) 17 | 18 | Copyright (c) 2014 Fauzie Rofi 19 | 20 | Permission is hereby granted, free of charge, to any person obtaining a copy 21 | of this software and associated documentation files (the "Software"), to deal 22 | in the Software without restriction, including without limitation the rights 23 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 | copies of the Software, and to permit persons to whom the Software is 25 | furnished to do so, subject to the following conditions: 26 | 27 | The above copyright notice and this permission notice shall be included in all 28 | copies or substantial portions of the Software. 29 | 30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 33 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 36 | SOFTWARE. 37 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 22 5 | buildToolsVersion "22.0.1" 6 | 7 | defaultConfig { 8 | applicationId "com.fauzie.sample.tabsspinner" 9 | minSdkVersion 9 10 | targetSdkVersion 22 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | compile 'com.android.support:appcompat-v7:22.0.0' 25 | } 26 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\Master\android-sdk-windows/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/fauzie/sample/tabsspinner/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.fauzie.sample.tabsspinner; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 22 | 23 | 27 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/fauzie/sample/tabsspinner/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.fauzie.sample.tabsspinner; 2 | 3 | import android.support.v7.app.ActionBarActivity; 4 | import android.support.v7.widget.Toolbar; 5 | 6 | /** 7 | * Created by Fauzie on 11/2/2014. 8 | */ 9 | public abstract class BaseActivity extends ActionBarActivity { 10 | private Toolbar mActionBarToolbar; 11 | 12 | @Override 13 | public void setContentView(int layoutResID) { 14 | super.setContentView(layoutResID); 15 | getActionBarToolbar(); 16 | } 17 | 18 | protected Toolbar getActionBarToolbar() { 19 | if (mActionBarToolbar == null) { 20 | mActionBarToolbar = (Toolbar) findViewById(R.id.toolbar_actionbar); 21 | if (mActionBarToolbar != null) { 22 | setSupportActionBar(mActionBarToolbar); 23 | } 24 | } 25 | return mActionBarToolbar; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/fauzie/sample/tabsspinner/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.fauzie.sample.tabsspinner; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.widget.Button; 7 | import android.widget.Toast; 8 | 9 | /** 10 | * Created by Fauzie on 11/2/2014. 11 | */ 12 | public class MainActivity extends BaseActivity implements View.OnClickListener { 13 | private Button btnTabs; 14 | private Button btnSpinner; 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.activity_main); 20 | 21 | btnTabs = (Button) findViewById(R.id.button_tabs); 22 | btnSpinner = (Button) findViewById(R.id.button_spinner); 23 | 24 | btnTabs.setOnClickListener(this); 25 | btnSpinner.setOnClickListener(this); 26 | } 27 | 28 | @Override 29 | public void onClick(View view) { 30 | if (view == btnTabs) { 31 | Intent i = new Intent(this, TabsActivity.class); 32 | startActivity(i); 33 | } else if (view == btnSpinner) { 34 | Intent i = new Intent(this, SpinnerActivity.class); 35 | startActivity(i); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/fauzie/sample/tabsspinner/SpinnerActivity.java: -------------------------------------------------------------------------------- 1 | package com.fauzie.sample.tabsspinner; 2 | 3 | import android.database.DataSetObserver; 4 | import android.os.Bundle; 5 | import android.support.v7.app.ActionBar; 6 | import android.support.v7.widget.Toolbar; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.AdapterView; 11 | import android.widget.Spinner; 12 | import android.widget.SpinnerAdapter; 13 | import android.widget.TextView; 14 | import android.widget.Toast; 15 | 16 | import java.util.ArrayList; 17 | 18 | /** 19 | * Created by Fauzie on 11/2/2014. 20 | */ 21 | public class SpinnerActivity extends BaseActivity { 22 | private SampleSpinnerAdapter mSpinnerAdapter = new SampleSpinnerAdapter(); 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.activity_spinner); 28 | 29 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 30 | getSupportActionBar().setTitle(""); 31 | 32 | setUpActionBarSpinner(); 33 | } 34 | 35 | private void setUpActionBarSpinner() { 36 | Toolbar toolbar = getActionBarToolbar(); 37 | 38 | Spinner spinner = (Spinner) LayoutInflater.from(this).inflate(R.layout.actionbar_spinner, 39 | toolbar, false); 40 | ActionBar.LayoutParams lp = new ActionBar.LayoutParams( 41 | ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 42 | toolbar.addView(spinner, lp); 43 | 44 | spinner.setAdapter(mSpinnerAdapter); 45 | spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 46 | @Override 47 | public void onItemSelected(AdapterView spinner, View view, int position, long itemId) { 48 | Toast.makeText(SpinnerActivity.this, "Selected: " + mSpinnerAdapter.getItem(position).toString(), Toast.LENGTH_SHORT).show(); 49 | } 50 | 51 | @Override 52 | public void onNothingSelected(AdapterView adapterView) { 53 | } 54 | }); 55 | } 56 | 57 | private class SampleSpinnerAdapter implements SpinnerAdapter { 58 | @Override 59 | public View getDropDownView(int position, View view, ViewGroup parent) { 60 | if (view == null || !view.getTag().toString().equals("DROPDOWN")) { 61 | view = getLayoutInflater().inflate(R.layout.spinner_item_dropdown, parent, false); 62 | view.setTag("DROPDOWN"); 63 | } 64 | ((TextView) view).setText(getItem(position).toString()); 65 | return view; 66 | } 67 | 68 | @Override 69 | public View getView(int position, View view, ViewGroup parent) { 70 | if (view == null || !view.getTag().toString().equals("NON_DROPDOWN")) { 71 | view = getLayoutInflater().inflate(R.layout.spinner_item_actionbar, parent, false); 72 | view.setTag("NON_DROPDOWN"); 73 | } 74 | ((TextView) view).setText(getItem(position).toString()); 75 | return view; 76 | } 77 | 78 | @Override 79 | public void registerDataSetObserver(DataSetObserver dataSetObserver) { 80 | 81 | } 82 | 83 | @Override 84 | public void unregisterDataSetObserver(DataSetObserver dataSetObserver) { 85 | 86 | } 87 | 88 | @Override 89 | public int getCount() { 90 | return 5; 91 | } 92 | 93 | @Override 94 | public Object getItem(int i) { 95 | return "Spinner item " + (i + 1); 96 | } 97 | 98 | @Override 99 | public long getItemId(int i) { 100 | return i; 101 | } 102 | 103 | @Override 104 | public boolean hasStableIds() { 105 | return false; 106 | } 107 | 108 | @Override 109 | public int getItemViewType(int i) { 110 | return 0; 111 | } 112 | 113 | @Override 114 | public int getViewTypeCount() { 115 | return 1; 116 | } 117 | 118 | @Override 119 | public boolean isEmpty() { 120 | return false; 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /app/src/main/java/com/fauzie/sample/tabsspinner/TabsActivity.java: -------------------------------------------------------------------------------- 1 | package com.fauzie.sample.tabsspinner; 2 | 3 | import android.content.res.Resources; 4 | import android.os.Bundle; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v4.app.FragmentManager; 7 | import android.support.v4.app.FragmentPagerAdapter; 8 | import android.support.v4.view.ViewPager; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.TextView; 13 | 14 | import com.fauzie.sample.tabsspinner.widget.SlidingTabLayout; 15 | 16 | /** 17 | * Created by Fauzie on 11/2/2014. 18 | */ 19 | public class TabsActivity extends BaseActivity { 20 | ViewPager mViewPager = null; 21 | SampleViewPagerAdapter mViewPagerAdapter = null; 22 | SlidingTabLayout mSlidingTabLayout = null; 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.activity_tabs); 28 | 29 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 30 | 31 | mViewPager = (ViewPager) findViewById(R.id.view_pager); 32 | mViewPagerAdapter = new SampleViewPagerAdapter(getSupportFragmentManager()); 33 | mViewPager.setAdapter(mViewPagerAdapter); 34 | 35 | mSlidingTabLayout = (SlidingTabLayout) findViewById(R.id.sliding_tabs); 36 | mSlidingTabLayout.setCustomTabView(R.layout.tab_indicator, android.R.id.text1); 37 | 38 | Resources res = getResources(); 39 | mSlidingTabLayout.setSelectedIndicatorColors(res.getColor(R.color.tab_selected_strip)); 40 | mSlidingTabLayout.setDistributeEvenly(true); 41 | mSlidingTabLayout.setViewPager(mViewPager); 42 | } 43 | 44 | private class SampleViewPagerAdapter extends FragmentPagerAdapter { 45 | 46 | public SampleViewPagerAdapter(FragmentManager fm) { 47 | super(fm); 48 | } 49 | 50 | @Override 51 | public Fragment getItem(int position) { 52 | SampleFragment frag = new SampleFragment(); 53 | Bundle args = new Bundle(); 54 | args.putInt("pos", position); 55 | frag.setArguments(args); 56 | return frag; 57 | } 58 | 59 | @Override 60 | public int getCount() { 61 | return 10; 62 | } 63 | 64 | @Override 65 | public CharSequence getPageTitle(int position) { 66 | return "Tab item " + (position + 1); 67 | } 68 | } 69 | 70 | public static class SampleFragment extends Fragment { 71 | @Override 72 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 73 | Bundle savedInstanceState) { 74 | View v = inflater.inflate(R.layout.fragment_sample, container, false); 75 | 76 | Bundle b = getArguments(); 77 | if (b != null) { 78 | int pos = b.getInt("pos"); 79 | TextView text1 = (TextView) v.findViewById(R.id.text1); 80 | text1.setText("Tab item " + (pos + 1)); 81 | } 82 | 83 | return v; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/com/fauzie/sample/tabsspinner/widget/SlidingTabLayout.java: -------------------------------------------------------------------------------- 1 | package com.fauzie.sample.tabsspinner.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Typeface; 5 | import android.support.v4.view.PagerAdapter; 6 | import android.support.v4.view.ViewPager; 7 | import android.util.AttributeSet; 8 | import android.util.SparseArray; 9 | import android.util.TypedValue; 10 | import android.view.Gravity; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.HorizontalScrollView; 15 | import android.widget.LinearLayout; 16 | import android.widget.TextView; 17 | 18 | /** 19 | * To be used with ViewPager to provide a tab indicator component which give constant feedback as to 20 | * the user's scroll progress. 21 | *

22 | * To use the component, simply add it to your view hierarchy. Then in your 23 | * {@link android.app.Activity} or {@link android.support.v4.app.Fragment} call 24 | * {@link #setViewPager(ViewPager)} providing it the ViewPager this layout is being used for. 25 | *

26 | * The colors can be customized in two ways. The first and simplest is to provide an array of colors 27 | * via {@link #setSelectedIndicatorColors(int...)}. The 28 | * alternative is via the {@link TabColorizer} interface which provides you complete control over 29 | * which color is used for any individual position. 30 | *

31 | * The views used as tabs can be customized by calling {@link #setCustomTabView(int, int)}, 32 | * providing the layout ID of your custom layout. 33 | */ 34 | public class SlidingTabLayout extends HorizontalScrollView { 35 | /** 36 | * Allows complete control over the colors drawn in the tab layout. Set with 37 | * {@link #setCustomTabColorizer(TabColorizer)}. 38 | */ 39 | public interface TabColorizer { 40 | 41 | /** 42 | * @return return the color of the indicator used when {@code position} is selected. 43 | */ 44 | int getIndicatorColor(int position); 45 | 46 | } 47 | 48 | private static final int TITLE_OFFSET_DIPS = 24; 49 | private static final int TAB_VIEW_PADDING_DIPS = 16; 50 | private static final int TAB_VIEW_TEXT_SIZE_SP = 12; 51 | 52 | private int mTitleOffset; 53 | 54 | private int mTabViewLayoutId; 55 | private int mTabViewTextViewId; 56 | private boolean mDistributeEvenly; 57 | 58 | private ViewPager mViewPager; 59 | private SparseArray mContentDescriptions = new SparseArray(); 60 | private ViewPager.OnPageChangeListener mViewPagerPageChangeListener; 61 | 62 | private final SlidingTabStrip mTabStrip; 63 | 64 | public SlidingTabLayout(Context context) { 65 | this(context, null); 66 | } 67 | 68 | public SlidingTabLayout(Context context, AttributeSet attrs) { 69 | this(context, attrs, 0); 70 | } 71 | 72 | public SlidingTabLayout(Context context, AttributeSet attrs, int defStyle) { 73 | super(context, attrs, defStyle); 74 | 75 | // Disable the Scroll Bar 76 | setHorizontalScrollBarEnabled(false); 77 | // Make sure that the Tab Strips fills this View 78 | setFillViewport(true); 79 | 80 | mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density); 81 | 82 | mTabStrip = new SlidingTabStrip(context); 83 | addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 84 | } 85 | 86 | /** 87 | * Set the custom {@link TabColorizer} to be used. 88 | * 89 | * If you only require simple custmisation then you can use 90 | * {@link #setSelectedIndicatorColors(int...)} to achieve 91 | * similar effects. 92 | */ 93 | public void setCustomTabColorizer(TabColorizer tabColorizer) { 94 | mTabStrip.setCustomTabColorizer(tabColorizer); 95 | } 96 | 97 | public void setDistributeEvenly(boolean distributeEvenly) { 98 | mDistributeEvenly = distributeEvenly; 99 | } 100 | 101 | /** 102 | * Sets the colors to be used for indicating the selected tab. These colors are treated as a 103 | * circular array. Providing one color will mean that all tabs are indicated with the same color. 104 | */ 105 | public void setSelectedIndicatorColors(int... colors) { 106 | mTabStrip.setSelectedIndicatorColors(colors); 107 | } 108 | 109 | /** 110 | * Set the {@link ViewPager.OnPageChangeListener}. When using {@link SlidingTabLayout} you are 111 | * required to set any {@link ViewPager.OnPageChangeListener} through this method. This is so 112 | * that the layout can update it's scroll position correctly. 113 | * 114 | * @see ViewPager#setOnPageChangeListener(ViewPager.OnPageChangeListener) 115 | */ 116 | public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { 117 | mViewPagerPageChangeListener = listener; 118 | } 119 | 120 | /** 121 | * Set the custom layout to be inflated for the tab views. 122 | * 123 | * @param layoutResId Layout id to be inflated 124 | * @param textViewId id of the {@link TextView} in the inflated view 125 | */ 126 | public void setCustomTabView(int layoutResId, int textViewId) { 127 | mTabViewLayoutId = layoutResId; 128 | mTabViewTextViewId = textViewId; 129 | } 130 | 131 | /** 132 | * Sets the associated view pager. Note that the assumption here is that the pager content 133 | * (number of tabs and tab titles) does not change after this call has been made. 134 | */ 135 | public void setViewPager(ViewPager viewPager) { 136 | mTabStrip.removeAllViews(); 137 | 138 | mViewPager = viewPager; 139 | if (viewPager != null) { 140 | viewPager.setOnPageChangeListener(new InternalViewPagerListener()); 141 | populateTabStrip(); 142 | } 143 | } 144 | 145 | /** 146 | * Create a default view to be used for tabs. This is called if a custom tab view is not set via 147 | * {@link #setCustomTabView(int, int)}. 148 | */ 149 | protected TextView createDefaultTabView(Context context) { 150 | TextView textView = new TextView(context); 151 | textView.setGravity(Gravity.CENTER); 152 | textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP); 153 | textView.setTypeface(Typeface.DEFAULT_BOLD); 154 | textView.setLayoutParams(new LinearLayout.LayoutParams( 155 | ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 156 | 157 | TypedValue outValue = new TypedValue(); 158 | getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, 159 | outValue, true); 160 | textView.setBackgroundResource(outValue.resourceId); 161 | textView.setAllCaps(true); 162 | 163 | int padding = (int) (TAB_VIEW_PADDING_DIPS * getResources().getDisplayMetrics().density); 164 | textView.setPadding(padding, padding, padding, padding); 165 | 166 | return textView; 167 | } 168 | 169 | private void populateTabStrip() { 170 | final PagerAdapter adapter = mViewPager.getAdapter(); 171 | final View.OnClickListener tabClickListener = new TabClickListener(); 172 | 173 | for (int i = 0; i < adapter.getCount(); i++) { 174 | View tabView = null; 175 | TextView tabTitleView = null; 176 | 177 | if (mTabViewLayoutId != 0) { 178 | // If there is a custom tab view layout id set, try and inflate it 179 | tabView = LayoutInflater.from(getContext()).inflate(mTabViewLayoutId, mTabStrip, 180 | false); 181 | tabTitleView = (TextView) tabView.findViewById(mTabViewTextViewId); 182 | } 183 | 184 | if (tabView == null) { 185 | tabView = createDefaultTabView(getContext()); 186 | } 187 | 188 | if (tabTitleView == null && TextView.class.isInstance(tabView)) { 189 | tabTitleView = (TextView) tabView; 190 | } 191 | 192 | if (mDistributeEvenly) { 193 | LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) tabView.getLayoutParams(); 194 | lp.width = 0; 195 | lp.weight = 1; 196 | } 197 | 198 | tabTitleView.setText(adapter.getPageTitle(i)); 199 | tabView.setOnClickListener(tabClickListener); 200 | String desc = mContentDescriptions.get(i, null); 201 | if (desc != null) { 202 | tabView.setContentDescription(desc); 203 | } 204 | 205 | mTabStrip.addView(tabView); 206 | if (i == mViewPager.getCurrentItem()) { 207 | tabView.setSelected(true); 208 | } 209 | } 210 | } 211 | 212 | public void setContentDescription(int i, String desc) { 213 | mContentDescriptions.put(i, desc); 214 | } 215 | 216 | @Override 217 | protected void onAttachedToWindow() { 218 | super.onAttachedToWindow(); 219 | 220 | if (mViewPager != null) { 221 | scrollToTab(mViewPager.getCurrentItem(), 0); 222 | } 223 | } 224 | 225 | private void scrollToTab(int tabIndex, int positionOffset) { 226 | final int tabStripChildCount = mTabStrip.getChildCount(); 227 | if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) { 228 | return; 229 | } 230 | 231 | View selectedChild = mTabStrip.getChildAt(tabIndex); 232 | if (selectedChild != null) { 233 | int targetScrollX = selectedChild.getLeft() + positionOffset; 234 | 235 | if (tabIndex > 0 || positionOffset > 0) { 236 | // If we're not at the first child and are mid-scroll, make sure we obey the offset 237 | targetScrollX -= mTitleOffset; 238 | } 239 | 240 | scrollTo(targetScrollX, 0); 241 | } 242 | } 243 | 244 | private class InternalViewPagerListener implements ViewPager.OnPageChangeListener { 245 | private int mScrollState; 246 | 247 | @Override 248 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 249 | int tabStripChildCount = mTabStrip.getChildCount(); 250 | if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) { 251 | return; 252 | } 253 | 254 | mTabStrip.onViewPagerPageChanged(position, positionOffset); 255 | 256 | View selectedTitle = mTabStrip.getChildAt(position); 257 | int extraOffset = (selectedTitle != null) 258 | ? (int) (positionOffset * selectedTitle.getWidth()) 259 | : 0; 260 | scrollToTab(position, extraOffset); 261 | 262 | if (mViewPagerPageChangeListener != null) { 263 | mViewPagerPageChangeListener.onPageScrolled(position, positionOffset, 264 | positionOffsetPixels); 265 | } 266 | } 267 | 268 | @Override 269 | public void onPageScrollStateChanged(int state) { 270 | mScrollState = state; 271 | 272 | if (mViewPagerPageChangeListener != null) { 273 | mViewPagerPageChangeListener.onPageScrollStateChanged(state); 274 | } 275 | } 276 | 277 | @Override 278 | public void onPageSelected(int position) { 279 | if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { 280 | mTabStrip.onViewPagerPageChanged(position, 0f); 281 | scrollToTab(position, 0); 282 | } 283 | for (int i = 0; i < mTabStrip.getChildCount(); i++) { 284 | mTabStrip.getChildAt(i).setSelected(position == i); 285 | } 286 | if (mViewPagerPageChangeListener != null) { 287 | mViewPagerPageChangeListener.onPageSelected(position); 288 | } 289 | } 290 | 291 | } 292 | 293 | private class TabClickListener implements View.OnClickListener { 294 | @Override 295 | public void onClick(View v) { 296 | for (int i = 0; i < mTabStrip.getChildCount(); i++) { 297 | if (v == mTabStrip.getChildAt(i)) { 298 | mViewPager.setCurrentItem(i); 299 | return; 300 | } 301 | } 302 | } 303 | } 304 | 305 | } -------------------------------------------------------------------------------- /app/src/main/java/com/fauzie/sample/tabsspinner/widget/SlidingTabStrip.java: -------------------------------------------------------------------------------- 1 | package com.fauzie.sample.tabsspinner.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.util.AttributeSet; 8 | import android.util.TypedValue; 9 | import android.view.View; 10 | import android.widget.LinearLayout; 11 | 12 | class SlidingTabStrip extends LinearLayout { 13 | 14 | private static final int DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS = 0; 15 | private static final byte DEFAULT_BOTTOM_BORDER_COLOR_ALPHA = 0x26; 16 | private static final int SELECTED_INDICATOR_THICKNESS_DIPS = 3; 17 | private static final int DEFAULT_SELECTED_INDICATOR_COLOR = 0xFF33B5E5; 18 | 19 | private final int mBottomBorderThickness; 20 | private final Paint mBottomBorderPaint; 21 | 22 | private final int mSelectedIndicatorThickness; 23 | private final Paint mSelectedIndicatorPaint; 24 | 25 | private final int mDefaultBottomBorderColor; 26 | 27 | private int mSelectedPosition; 28 | private float mSelectionOffset; 29 | 30 | private SlidingTabLayout.TabColorizer mCustomTabColorizer; 31 | private final SimpleTabColorizer mDefaultTabColorizer; 32 | 33 | SlidingTabStrip(Context context) { 34 | this(context, null); 35 | } 36 | 37 | SlidingTabStrip(Context context, AttributeSet attrs) { 38 | super(context, attrs); 39 | setWillNotDraw(false); 40 | 41 | final float density = getResources().getDisplayMetrics().density; 42 | 43 | TypedValue outValue = new TypedValue(); 44 | context.getTheme().resolveAttribute(android.R.attr.colorForeground, outValue, true); 45 | final int themeForegroundColor = outValue.data; 46 | 47 | mDefaultBottomBorderColor = setColorAlpha(themeForegroundColor, 48 | DEFAULT_BOTTOM_BORDER_COLOR_ALPHA); 49 | 50 | mDefaultTabColorizer = new SimpleTabColorizer(); 51 | mDefaultTabColorizer.setIndicatorColors(DEFAULT_SELECTED_INDICATOR_COLOR); 52 | 53 | mBottomBorderThickness = (int) (DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS * density); 54 | mBottomBorderPaint = new Paint(); 55 | mBottomBorderPaint.setColor(mDefaultBottomBorderColor); 56 | 57 | mSelectedIndicatorThickness = (int) (SELECTED_INDICATOR_THICKNESS_DIPS * density); 58 | mSelectedIndicatorPaint = new Paint(); 59 | } 60 | 61 | void setCustomTabColorizer(SlidingTabLayout.TabColorizer customTabColorizer) { 62 | mCustomTabColorizer = customTabColorizer; 63 | invalidate(); 64 | } 65 | 66 | void setSelectedIndicatorColors(int... colors) { 67 | // Make sure that the custom colorizer is removed 68 | mCustomTabColorizer = null; 69 | mDefaultTabColorizer.setIndicatorColors(colors); 70 | invalidate(); 71 | } 72 | 73 | void onViewPagerPageChanged(int position, float positionOffset) { 74 | mSelectedPosition = position; 75 | mSelectionOffset = positionOffset; 76 | invalidate(); 77 | } 78 | 79 | @Override 80 | protected void onDraw(Canvas canvas) { 81 | final int height = getHeight(); 82 | final int childCount = getChildCount(); 83 | final SlidingTabLayout.TabColorizer tabColorizer = mCustomTabColorizer != null 84 | ? mCustomTabColorizer 85 | : mDefaultTabColorizer; 86 | 87 | // Thick colored underline below the current selection 88 | if (childCount > 0) { 89 | View selectedTitle = getChildAt(mSelectedPosition); 90 | int left = selectedTitle.getLeft(); 91 | int right = selectedTitle.getRight(); 92 | int color = tabColorizer.getIndicatorColor(mSelectedPosition); 93 | 94 | if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) { 95 | int nextColor = tabColorizer.getIndicatorColor(mSelectedPosition + 1); 96 | if (color != nextColor) { 97 | color = blendColors(nextColor, color, mSelectionOffset); 98 | } 99 | 100 | // Draw the selection partway between the tabs 101 | View nextTitle = getChildAt(mSelectedPosition + 1); 102 | left = (int) (mSelectionOffset * nextTitle.getLeft() + 103 | (1.0f - mSelectionOffset) * left); 104 | right = (int) (mSelectionOffset * nextTitle.getRight() + 105 | (1.0f - mSelectionOffset) * right); 106 | } 107 | 108 | mSelectedIndicatorPaint.setColor(color); 109 | 110 | canvas.drawRect(left, height - mSelectedIndicatorThickness, right, 111 | height, mSelectedIndicatorPaint); 112 | } 113 | 114 | // Thin underline along the entire bottom edge 115 | canvas.drawRect(0, height - mBottomBorderThickness, getWidth(), height, mBottomBorderPaint); 116 | } 117 | 118 | /** 119 | * Set the alpha value of the {@code color} to be the given {@code alpha} value. 120 | */ 121 | private static int setColorAlpha(int color, byte alpha) { 122 | return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color)); 123 | } 124 | 125 | /** 126 | * Blend {@code color1} and {@code color2} using the given ratio. 127 | * 128 | * @param ratio of which to blend. 1.0 will return {@code color1}, 0.5 will give an even blend, 129 | * 0.0 will return {@code color2}. 130 | */ 131 | private static int blendColors(int color1, int color2, float ratio) { 132 | final float inverseRation = 1f - ratio; 133 | float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation); 134 | float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation); 135 | float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation); 136 | return Color.rgb((int) r, (int) g, (int) b); 137 | } 138 | 139 | private static class SimpleTabColorizer implements SlidingTabLayout.TabColorizer { 140 | private int[] mIndicatorColors; 141 | 142 | @Override 143 | public final int getIndicatorColor(int position) { 144 | return mIndicatorColors[position % mIndicatorColors.length]; 145 | } 146 | 147 | void setIndicatorColors(int... colors) { 148 | mIndicatorColors = colors; 149 | } 150 | } 151 | } -------------------------------------------------------------------------------- /app/src/main/res/color/list_dropdown_foreground_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/color/tab_text_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fauzie811/android-tabsandspinner-sample/7b5dcc1e86457a60f65b587e12514feba38c4992/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fauzie811/android-tabsandspinner-sample/7b5dcc1e86457a60f65b587e12514feba38c4992/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fauzie811/android-tabsandspinner-sample/7b5dcc1e86457a60f65b587e12514feba38c4992/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fauzie811/android-tabsandspinner-sample/7b5dcc1e86457a60f65b587e12514feba38c4992/app/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/spinner_triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fauzie811/android-tabsandspinner-sample/7b5dcc1e86457a60f65b587e12514feba38c4992/app/src/main/res/drawable-xxhdpi/spinner_triangle.png -------------------------------------------------------------------------------- /app/src/main/res/layout/actionbar_spinner.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 9 | 15 | 16 |