1
/*
2
* Copyright (C) 2013 The Android Open Source Project
3
*
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
* you may not use this file except in compliance with the License.
6
* You may obtain a copy of the License at
7
*
8
* http://www.apache.org/licenses/LICENSE-2.0
9
*
10
* Unless required by applicable law or agreed to in writing, software
11
* distributed under the License is distributed on an "AS IS" BASIS,
12
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
* See the License for the specific language governing permissions and
14
* limitations under the License.
15
*/
16
17
package com.example.android.common.view;
18
19
import android.content.Context;
20
import android.graphics.Typeface;
21
import android.os.Build;
22
import android.support.v4.view.PagerAdapter;
23
import android.support.v4.view.ViewPager;
24
import android.util.AttributeSet;
25
import android.util.TypedValue;
26
import android.view.Gravity;
27
import android.view.LayoutInflater;
28
import android.view.View;
29
import android.widget.HorizontalScrollView;
30
import android.widget.TextView;
31
32
/**
33
* To be used with ViewPager to provide a tab indicator component which give constant feedback as to
34
* the user's scroll progress.
35
* <p>
36
* To use the component, simply add it to your view hierarchy. Then in your
37
* {@link android.app.Activity} or {@link android.support.v4.app.Fragment} call
38
* {@link #setViewPager(ViewPager)} providing it the ViewPager this layout is being used for.
39
* <p>
40
* The colors can be customized in two ways. The first and simplest is to provide an array of colors
41
* via {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)}. The
42
* alternative is via the {@link TabColorizer} interface which provides you complete control over
43
* which color is used for any individual position.
44
* <p>
45
* The views used as tabs can be customized by calling {@link #setCustomTabView(int, int)},
46
* providing the layout ID of your custom layout.
47
*/
48
public class SlidingTabLayout extends HorizontalScrollView {
49
50
/**
51
* Allows complete control over the colors drawn in the tab layout. Set with
52
* {@link #setCustomTabColorizer(TabColorizer)}.
53
*/
54
public interface TabColorizer {
55
56
/**
57
* @return return the color of the indicator used when {@code position} is selected.
58
*/
59
int getIndicatorColor(int position);
60
61
/**
62
* @return return the color of the divider drawn to the right of {@code position}.
63
*/
64
int getDividerColor(int position);
65
66
}
67
68
private static final int TITLE_OFFSET_DIPS = 24;
69
private static final int TAB_VIEW_PADDING_DIPS = 16;
70
private static final int TAB_VIEW_TEXT_SIZE_SP = 12;
71
72
private int mTitleOffset;
73
74
private int mTabViewLayoutId;
75
private int mTabViewTextViewId;
76
77
private ViewPager mViewPager;
78
private ViewPager.OnPageChangeListener mViewPagerPageChangeListener;
79
80
private final SlidingTabStrip mTabStrip;
81
82
public SlidingTabLayout(Context context) {
83
this(context, null);
84
}
85
86
public SlidingTabLayout(Context context, AttributeSet attrs) {
87
this(context, attrs, 0);
88
}
89
90
public SlidingTabLayout(Context context, AttributeSet attrs, int defStyle) {
91
super(context, attrs, defStyle);
92
93
// Disable the Scroll Bar
94
setHorizontalScrollBarEnabled(false);
95
// Make sure that the Tab Strips fills this View
96
setFillViewport(true);
97
98
mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density);
99
100
mTabStrip = new SlidingTabStrip(context);
101
addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
102
}
103
104
/**
105
* Set the custom {@link TabColorizer} to be used.
106
*
107
* If you only require simple custmisation then you can use
108
* {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)} to achieve
109
* similar effects.
110
*/
111
public void setCustomTabColorizer(TabColorizer tabColorizer) {
112
mTabStrip.setCustomTabColorizer(tabColorizer);
113
}
114
115
/**
116
* Sets the colors to be used for indicating the selected tab. These colors are treated as a
117
* circular array. Providing one color will mean that all tabs are indicated with the same color.
118
*/
119
public void setSelectedIndicatorColors(int... colors) {
120
mTabStrip.setSelectedIndicatorColors(colors);
121
}
122
123
/**
124
* Sets the colors to be used for tab dividers. These colors are treated as a circular array.
125
* Providing one color will mean that all tabs are indicated with the same color.
126
*/
127
public void setDividerColors(int... colors) {
128
mTabStrip.setDividerColors(colors);
129
}
130
131
/**
132
* Set the {@link ViewPager.OnPageChangeListener}. When using {@link SlidingTabLayout} you are
133
* required to set any {@link ViewPager.OnPageChangeListener} through this method. This is so
134
* that the layout can update it's scroll position correctly.
135
*
136
* @see ViewPager#setOnPageChangeListener(ViewPager.OnPageChangeListener)
137
*/
138
public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
139
mViewPagerPageChangeListener = listener;
140
}
141
142
/**
143
* Set the custom layout to be inflated for the tab views.
144
*
145
* @param layoutResId Layout id to be inflated
146
* @param textViewId id of the {@link TextView} in the inflated view
147
*/
148
public void setCustomTabView(int layoutResId, int textViewId) {
149
mTabViewLayoutId = layoutResId;
150
mTabViewTextViewId = textViewId;
151
}
152
153
/**
154
* Sets the associated view pager. Note that the assumption here is that the pager content
155
* (number of tabs and tab titles) does not change after this call has been made.
156
*/
157
public void setViewPager(ViewPager viewPager) {
158
mTabStrip.removeAllViews();
159
160
mViewPager = viewPager;
161
if (viewPager != null) {
162
viewPager.setOnPageChangeListener(new InternalViewPagerListener());
163
populateTabStrip();
164
}
165
}
166
167
/**
168
* Create a default view to be used for tabs. This is called if a custom tab view is not set via
169
* {@link #setCustomTabView(int, int)}.
170
*/
171
protected TextView createDefaultTabView(Context context) {
172
TextView textView = new TextView(context);
173
textView.setGravity(Gravity.CENTER);
174
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP);
175
textView.setTypeface(Typeface.DEFAULT_BOLD);
176
177
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
178
// If we're running on Honeycomb or newer, then we can use the Theme's
179
// selectableItemBackground to ensure that the View has a pressed state
180
TypedValue outValue = new TypedValue();
181
getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground,
182
outValue, true);
183
textView.setBackgroundResource(outValue.resourceId);
184
}
185
186
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
187
// If we're running on ICS or newer, enable all-caps to match the Action Bar tab style
188
textView.setAllCaps(true);
189
}
190
191
int padding = (int) (TAB_VIEW_PADDING_DIPS * getResources().getDisplayMetrics().density);
192
textView.setPadding(padding, padding, padding, padding);
193
194
return textView;
195
}
196
197
private void populateTabStrip() {
198
final PagerAdapter adapter = mViewPager.getAdapter();
199
final View.OnClickListener tabClickListener = new TabClickListener();
200
201
for (int i = 0; i < adapter.getCount(); i++) {
202
View tabView = null;
203
TextView tabTitleView = null;
204
205
if (mTabViewLayoutId != 0) {
206
// If there is a custom tab view layout id set, try and inflate it
207
tabView = LayoutInflater.from(getContext()).inflate(mTabViewLayoutId, mTabStrip,
208
false);
209
tabTitleView = (TextView) tabView.findViewById(mTabViewTextViewId);
210
}
211
212
if (tabView == null) {
213
tabView = createDefaultTabView(getContext());
214
}
215
216
if (tabTitleView == null && TextView.class.isInstance(tabView)) {
217
tabTitleView = (TextView) tabView;
218
}
219
220
tabTitleView.setText(adapter.getPageTitle(i));
221
tabView.setOnClickListener(tabClickListener);
222
223
mTabStrip.addView(tabView);
224
}
225
}
226
227
@Override
228
protected void onAttachedToWindow() {
229
super.onAttachedToWindow();
230
231
if (mViewPager != null) {
232
scrollToTab(mViewPager.getCurrentItem(), 0);
233
}
234
}
235
236
private void scrollToTab(int tabIndex, int positionOffset) {
237
final int tabStripChildCount = mTabStrip.getChildCount();
238
if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) {
239
return;
240
}
241
242
View selectedChild = mTabStrip.getChildAt(tabIndex);
243
if (selectedChild != null) {
244
int targetScrollX = selectedChild.getLeft() + positionOffset;
245
246
if (tabIndex > 0 || positionOffset > 0) {
247
// If we're not at the first child and are mid-scroll, make sure we obey the offset
248
targetScrollX -= mTitleOffset;
249
}
250
251
scrollTo(targetScrollX, 0);
252
}
253
}
254
255
private class InternalViewPagerListener implements ViewPager.OnPageChangeListener {
256
private int mScrollState;
257
258
@Override
259
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
260
int tabStripChildCount = mTabStrip.getChildCount();
261
if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
262
return;
263
}
264
265
mTabStrip.onViewPagerPageChanged(position, positionOffset);
266
267
View selectedTitle = mTabStrip.getChildAt(position);
268
int extraOffset = (selectedTitle != null)
269
? (int) (positionOffset * selectedTitle.getWidth())
270
: 0;
271
scrollToTab(position, extraOffset);
272
273
if (mViewPagerPageChangeListener != null) {
274
mViewPagerPageChangeListener.onPageScrolled(position, positionOffset,
275
positionOffsetPixels);
276
}
277
}
278
279
@Override
280
public void onPageScrollStateChanged(int state) {
281
mScrollState = state;
282
283
if (mViewPagerPageChangeListener != null) {
284
mViewPagerPageChangeListener.onPageScrollStateChanged(state);
285
}
286
}
287
288
@Override
289
public void onPageSelected(int position) {
290
if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
291
mTabStrip.onViewPagerPageChanged(position, 0f);
292
scrollToTab(position, 0);
293
}
294
295
if (mViewPagerPageChangeListener != null) {
296
mViewPagerPageChangeListener.onPageSelected(position);
297
}
298
}
299
300
}
301
302
private class TabClickListener implements View.OnClickListener {
303
@Override
304
public void onClick(View v) {
305
for (int i = 0; i < mTabStrip.getChildCount(); i++) {
306
if (v == mTabStrip.getChildAt(i)) {
307
mViewPager.setCurrentItem(i);
308
return;
309
}
310
}
311
}
312
}
313
314
}