自定義Tablayout——ViewPager導航控制元件_SimpleViewpagerIndicator
阿新 • • 發佈:2018-12-24
寫這個小控制元件是因為最近負責維護的一款app大改版,設計師給了一個新的ViewPager導航樣式,但找了幾個常用的導航控制元件發現都無法100%實現設計師給的效果,於是就乾脆自己動手豐衣足食了。
控制元件只有一個單獨的java類,程式碼也很簡單,放出來希望能幫到需要的人。
效果
控制元件提供了比較豐富的可配置選項,下面是兩個例子:
1.所有配置項均使用預設值(tab寬度包裹內容、indicator與文字等寬……):
2.tab寬度平分父控制元件剩餘空間、indicator與tab等寬……:
配置項
在呼叫setViewPager前,使用一系列setXXX方法進行設定即可,支援鏈式呼叫:
indicator.setExpand(true)//設定tab寬度為包裹內容還是平分父控制元件剩餘空間,預設值:false,包裹內容
.setIndicatorWrapText(false)//設定indicator是與文字等寬還是與整個tab等寬,預設值:true,與文字等寬
.setIndicatorColor(Color.parseColor("#ff3300"))//indicator顏色
.setIndicatorHeight(2)//indicator高度
.setShowUnderline(true, Color.parseColor("#dddddd"), 2 )//設定是否展示underline,預設不展示
.setShowDivider(true, Color.parseColor("#dddddd"), 10, 1)//設定是否展示分隔線,預設不展示
.setTabTextSize(16)//文字大小
.setTabTextColor(Color.parseColor("#ff999999"))//文字顏色
.setTabTypeface(null)//字型
.setTabTypefaceStyle(Typeface.NORMAL)//字型樣式:粗體、斜體等
.setTabBackgroundResId(0)//設定tab的背景
.setTabPadding(0)//設定tab的左右padding
.setSelectedTabTextSize(20)//被選中的文字大小
.setSelectedTabTextColor(Color.parseColor("#ff3300"))//被選中的文字顏色
.setSelectedTabTypeface(null)
.setSelectedTabTypefaceStyle(Typeface.BOLD)
.setScrollOffset(120);//滾動偏移量
indicator.setViewPager(viewPager);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
所有的配置項均有預設值,也就是說不進行任何設定也是可以的,效果參考上面的第一張圖。
程式碼
原始碼不多,註釋也比較詳細,所以就不多廢話了:
/**
* 使用方式:
* --在呼叫setViewPager之前使用setxxx方法進行樣式設定,支援鏈式呼叫
* --呼叫setViewPager方法將viewPager繫結到simpleViewpagerIndicator(viewPager的adapter必須實現getPageTitle方法)
* --呼叫setOnPageChangeListener來設定viewPager的頁面切換監聽
*/
public class SimpleViewpagerIndicator extends HorizontalScrollView {
//配置屬性 START-----------------------------------------------------------------------------
/*
* true:每個tab寬度為平分父控制元件剩餘空間
* false:每個tab寬度為包裹內容
*/
private boolean expand = false;
/*
* 指示器(被選中的tab下的短橫線)
*/
private boolean indicatorWrapText = true;//true:indicator與文字等長;false:indicator與整個tab等長
private int indicatorColor = Color.parseColor("#ff666666");
private int indicatorHeight = 2;//dp
/*
* 底線(指示器的背景滑軌)
*/
private boolean showUnderline = false;//是否展示底線
private int underlineColor;
private int underlineHeight;//dp
/*
* tab之間的分割線
*/
private boolean showDivider = false;//是否展示分隔線
private int dividerColor;
private int dividerPadding;//分隔線上下的padding,dp
private int dividerWidth;//分隔線寬度,dp
/*
* tab
*/
private int tabTextSize = 16;//tab字號,dp
private int tabTextColor = Color.parseColor("#ff999999");//tab字色
private Typeface tabTypeface = null;//tab字型
private int tabTypefaceStyle = Typeface.NORMAL;//tab字型樣式
private int tabBackgroundResId = 0;//每個tab的背景資源id
private int tabPadding = 24;//每個tab的左右內邊距,dp
/*
* 被選中的tab
*/
private int selectedTabTextSize = 16;//dp
private int selectedTabTextColor = Color.parseColor("#ff666666");
private Typeface selectedTabTypeface = null;
private int selectedTabTypefaceStyle = Typeface.BOLD;
/*
* scrollView整體滾動的偏移量,dp
*/
private int scrollOffset = 100;
//配置屬性 End-------------------------------------------------------------------------------
private LinearLayout.LayoutParams wrapTabLayoutParams;
private LinearLayout.LayoutParams expandTabLayoutParams;
private Paint rectPaint;
private Paint dividerPaint;
private Paint measureTextPaint;//測量文字寬度用的畫筆
private final PageListener pageListener = new PageListener();
private OnPageChangeListener userPageListener;
private LinearLayout tabsContainer;//tab的容器
private ViewPager viewPager;
private int currentPosition = 0;//viewPager當前頁面
private float currentPositionOffset = 0f;//viewPager當前頁面的偏移百分比(取值:0~1)
private int selectedPosition = 0;//viewPager當前被選中的頁面
private int tabCount;
private int lastScrollX = 0;
public SimpleViewpagerIndicator(Context context) {
this(context, null);
}
public SimpleViewpagerIndicator(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SimpleViewpagerIndicator(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setFillViewport(true);
setWillNotDraw(false);
}
public SimpleViewpagerIndicator setViewPager(ViewPager viewPager) {
this.viewPager = viewPager;
if (viewPager.getAdapter() == null) {
throw new IllegalStateException("ViewPager does not have adapter instance.");
}
viewPager.setOnPageChangeListener(pageListener);
init();
initView();
return this;
}
public SimpleViewpagerIndicator setOnPageChangeListener(OnPageChangeListener listener) {
this.userPageListener = listener;
return this;
}
private void init() {
/*
* 將dp換算為px
*/
float density = getContext().getResources().getDisplayMetrics().density;
indicatorHeight = (int) (indicatorHeight * density);
underlineHeight = (int) (underlineHeight * density);
dividerPadding = (int) (dividerPadding * density);
dividerWidth = (int) (dividerWidth * density);
tabTextSize = (int) (tabTextSize * density);
tabPadding = (int) (tabPadding * density);
selectedTabTextSize = (int) (selectedTabTextSize * density);
scrollOffset = (int) (scrollOffset * density);
/*
* 建立tab的容器(LinearLayout)
*/
tabsContainer = new LinearLayout(getContext());
tabsContainer.setOrientation(LinearLayout.HORIZONTAL);
tabsContainer.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
addView(tabsContainer);
/*
* 建立畫筆
*/
rectPaint = new Paint();
rectPaint.setAntiAlias(true);
rectPaint.setStyle(Style.FILL);
dividerPaint = new Paint();
dividerPaint.setAntiAlias(true);
dividerPaint.setStrokeWidth(dividerWidth);
measureTextPaint = new Paint();
measureTextPaint.setTextSize(selectedTabTextSize);
/*
* 建立兩個Tab的LayoutParams,一個為寬度包裹內容,一個為寬度等分父控制元件剩餘空間
*/
wrapTabLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);//寬度包裹內容
expandTabLayoutParams = new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f);//寬度等分
}
private void initView() {
//注意:currentPosition和selectedPosition的含義並不相同,它們分別在onPageScroll和onPageSelected中被賦值
//在從tab1往tab2滑動的過程中,selectedPosition會比currentPosition先由1變成2
currentPosition = viewPager.getCurrentItem();
selectedPosition = viewPager.getCurrentItem();
tabsContainer.removeAllViews();
tabCount = viewPager.getAdapter().getCount();
//建立tab並新增到tabsContainer中
for (int i = 0; i < tabCount; i++) {
addTab(i, viewPager.getAdapter().getPageTitle(i).toString());
}
//遍歷tab,設定tab文字大小和樣式
updateTextStyle();
//滾動scrollView
getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
scrollToChild(currentPosition, 0);//滾動scrollView
}
});
}
/**
* 新增tab
*/
private void addTab(final int position, String title) {
TextView tab = new TextView(getContext());
tab.setGravity(Gravity.CENTER);
tab.setSingleLine();
tab.setText(title);
if (tabBackgroundResId != 0) {
tab.setBackgroundResource(tabBackgroundResId);
}
tab.setPadding(tabPadding, 0, tabPadding, 0);
tab.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
viewPager.setCurrentItem(position);
}
});
tabsContainer.addView(tab, position, expand ? expandTabLayoutParams : wrapTabLayoutParams);
}
/**
* 遍歷tab,設定tab文字大小和樣式
*/
private void updateTextStyle() {
for (int i = 0; i < tabCount; i++) {
TextView tvTab = (TextView) tabsContainer.getChildAt(i);
if (i == selectedPosition) {//被選中的tab
tvTab.setTextSize(TypedValue.COMPLEX_UNIT_PX, selectedTabTextSize);
tvTab.setTypeface(selectedTabTypeface, selectedTabTypefaceStyle);
tvTab.setTextColor(selectedTabTextColor);
} else {//未被選中的tab
tvTab.setTextSize(TypedValue.COMPLEX_UNIT_PX, tabTextSize);
tvTab.setTypeface(tabTypeface, tabTypefaceStyle);
tvTab.setTextColor(tabTextColor);
}
}
}
/**
* 滾動scrollView
* <p>
* 注意:當普通文字字號(tabTextSize)與被選中的文字字號(selectedTabTextSize)相差過大,且tab的寬度模式為包裹內容(expand = false)時,
* 由於文字選中狀態切換時文字寬度突變,造成tab寬度突變,可能導致scrollView在滾動時出現輕微抖動。
* 因此,當普通文字字號(tabTextSize)與被選中的文字字號(selectedTabTextSize)相差過大時,應避免使tab寬度包裹內容(expand = false)。
*/
private void scrollToChild(int position, int offset) {
if (tabCount == 0) return;
//getLeft():tab相對於父控制元件,即tabsContainer的left
int newScrollX = tabsContainer.getChildAt(position).getLeft() + offset;
//附加一個偏移量,防止當前選中的tab太偏左
//可以去掉看看是什麼效果
if (position > 0 || offset > 0) {
newScrollX -= scrollOffset;
}
if (newScrollX != lastScrollX) {
lastScrollX = newScrollX;
scrollTo(newScrollX, 0);
}
}
/**
* 繪製indicator、underline和divider
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isInEditMode() || tabCount == 0) return;
final int height = getHeight();
/*
* 繪製divider
*/
if (showDivider) {
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);
}
}
/*
* 繪製underline(indicator的背景線)
*/
if (showUnderline) {
rectPaint.setColor(underlineColor);
canvas.drawRect(0, height - underlineHeight, tabsContainer.getWidth(), height, rectPaint);
}
/*
* 繪製indicator
*/
if (indicatorWrapText) {//indicator與文字等長
rectPaint.setColor(indicatorColor);
getTextLocation(currentPosition);
float lineLeft = textLocation.left;
float lineRight = textLocation.right;
if (currentPositionOffset > 0f && currentPosition < tabCount - 1) {
getTextLocation(currentPosition + 1);
final float nextLeft = textLocation.left;
final float nextRight = textLocation.right;
lineLeft = lineLeft + (nextLeft - lineLeft) * currentPositionOffset;
lineRight = lineRight + (nextRight - lineRight) * currentPositionOffset;
}
canvas.drawRect(lineLeft, height - indicatorHeight, lineRight, height, rectPaint);
} else {//indicator與tab等長
rectPaint.setColor(indicatorColor);
View currentTab = tabsContainer.getChildAt(currentPosition);
float lineLeft = currentTab.getLeft();
float lineRight = currentTab.getRight();
if (currentPositionOffset > 0f && currentPosition < tabCount - 1) {
View nextTab = tabsContainer.getChildAt(currentPosition + 1);
final float nextLeft = nextTab.getLeft();
final float nextRight = nextTab.getRight();
lineLeft = lineLeft + (nextLeft - lineLeft) * currentPositionOffset;
lineRight = lineRight + (nextRight - lineRight) * currentPositionOffset;
}
canvas.drawRect(lineLeft, height - indicatorHeight, lineRight, height, rectPaint);
}
}
/**
* 獲得指定tab中,文字的left和right
*/
private void getTextLocation(int position) {
View tab = tabsContainer.getChildAt(position);
String tabText = viewPager.getAdapter().getPageTitle(position).toString();
float textWidth = measureTextPaint.measureText(tabText);
int tabWidth = tab.getWidth();
textLocation.left = tab.getLeft() + (int) ((tabWidth - textWidth) / 2);
textLocation.right = tab.getRight() - (int) ((tabWidth - textWidth) / 2);
}
private LeftRight textLocation = new LeftRight();
class LeftRight {
int left, right;
}
private class PageListener implements OnPageChangeListener {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
currentPosition = position;
currentPositionOffset = positionOffset;
//scrollView滾動
scrollToChild(position, (int) (positionOffset * tabsContainer.getChildAt(position).getWidth()));
invalidate();//invalidate後onDraw會被呼叫,繪製indicator、divider等
if (userPageListener != null) {
userPageListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
}
}
@Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
scrollToChild(viewPager.getCurrentItem(), 0);//scrollView滾動
}
if (userPageListener != null) {
userPageListener.onPageScrollStateChanged(state);
}
}
@Override
public void onPageSelected(int position) {
selectedPosition = position;
updateTextStyle();//更新tab文字大小和樣式
if (userPageListener != null) {
userPageListener.onPageSelected(position);
}
}
}
//setter------------------------------------------------------------------------------------
......
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 368
- 369
- 370
- 371
- 372
- 373
- 374
- 375
- 376
- 377
- 378
- 379
- 380
- 381
- 382
完整程式碼和demo見:https://github.com/al4fun/SimpleViewpagerIndicator
原文:地址