自定义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

原文:地址

猜你喜欢

转载自blog.csdn.net/huahuaxiaolian/article/details/80832560