1. 程式人生 > >自定義Tablayout——ViewPager導航控制元件_SimpleViewpagerIndicator

自定義Tablayout——ViewPager導航控制元件_SimpleViewpagerIndicator

寫這個小控制元件是因為最近負責維護的一款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

原文:地址