美文网首页优秀案例
可以自定义指示器形状的TabLayout

可以自定义指示器形状的TabLayout

作者: 风雪守候 | 来源:发表于2017-08-31 17:30 被阅读113次

系统的TabLayout对于指示器写的比较死,没有提供相关的方法来修改来达到自己的需求。所以通过继承HorizontalScrollView来实现一个自定义的View(其实还可以通过继承系统的TabLayout,将系统的指示器隐藏掉,自己去实现指示器的显示和移动)。
直接看代码

TabLayout.java
package com.example.contentprovider;

import android.animation.Animator;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class TabLayout extends HorizontalScrollView implements View.OnClickListener,
        ViewPager.OnPageChangeListener, Animator.AnimatorListener {
    private static final int TRANSLATION_DURATION = 250;
    private static final int SCALE_DURATION = 100;
    private RelativeLayout mContent;
    private LinearLayout mTabContainer;
    private ImageView mIndicator;

    //当tab在屏幕居中显示时的X坐标
    private int mTabCenterLocationX;
    //当前view左右滚动的最大距离
    private int mViewScrollMaxDistance;

    //tab的相关参数
    private int mTabWidth;
    private int mTabTextSize;
    private int mTabTextPaddingTop;
    private int mTabTextPaddingLeft;
    private int mTabTextPaddingRight;
    private int mTabTextPaddingBottom;
    private int mTabBackgroundResId;
    private ColorStateList mTabTextColorSelector;
    private int mTabSelectedPos;

    //指示器相关的参数
    private int mIndicatorWidth;
    private int mIndicatorHeight;
    private int mIndicatorImageId;
    private int mIndicatorMarginBottom;

    //tab选中的监听事件
    private OnTabSelectedListener mListener;

    //显示的数据源
    private String[] mItems;

    //关联的viewpager
    private ViewPager mViewPager;

    public TabLayout(Context context) {
        super(context);
        init();
    }

    public TabLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    //初始化所有的view
    private void init() {
        setHorizontalScrollBarEnabled(false);
        setOverScrollMode(OVER_SCROLL_NEVER);
        setBackgroundColor(Color.GREEN);

        initParam();

        mContent = new RelativeLayout(getContext());
        mTabContainer = new LinearLayout(getContext());
        mIndicator = new ImageView(getContext());

        mTabContainer.setOrientation(LinearLayout.HORIZONTAL);

        mContent.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
        mTabContainer.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));


        mContent.addView(mTabContainer);
        mContent.addView(mIndicator);

        addView(mContent);
    }

    //初始化默认的参数
    private void initParam() {
        mTabTextSize = (int)getResources().getDimension(R.dimen.tab_layout_tab_text_default_text_size);
        mTabTextPaddingLeft = (int)getResources().getDimension(R.dimen.tab_layout_tab_text_default_padding_left);
        mTabTextPaddingRight = (int)getResources().getDimension(R.dimen.tab_layout_tab_text_default_padding_right);
        mIndicatorWidth = (int)getResources().getDimension(R.dimen.tab_layout_indicator_default_width);
        mIndicatorHeight = (int)getResources().getDimension(R.dimen.tab_layout_indicator_default_height);

        mIndicatorMarginBottom = (int)getResources().getDimension(R.dimen.tab_layout_indicator_default_margin_bottom);
        mIndicatorImageId = R.drawable.tab_layout_indicator_default_image;
    }

    //tab的点击事件
    @Override
    public void onClick(View v) {
        int index = (int)v.getTag();
        if(index != mTabSelectedPos) {
            setTabSelected(index);
            tabCenterScroll();
            updateIndicatorLocation(TRANSLATION_DURATION);
        }
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    //viewPager的page选中事件
    @Override
    public void onPageSelected(int position) {
        if(position != mTabSelectedPos) {
            setTabSelected(position);
            tabCenterScroll();
            updateIndicatorLocation(TRANSLATION_DURATION);
        }
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }

    @Override
    public void onAnimationStart(Animator animation) {

    }

    @Override
    public void onAnimationEnd(Animator animation) {
        mIndicator.animate().scaleX(1f).setDuration(SCALE_DURATION).start();
    }

    @Override
    public void onAnimationCancel(Animator animation) {

    }

    @Override
    public void onAnimationRepeat(Animator animation) {

    }

    //初始化view滚动是需要的一些参数
    private void initScrollParam() {
        DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
        mTabCenterLocationX = (metrics.widthPixels - mTabWidth) >> 1;
        int viewWidth = 0;
        if(mItems != null && mItems.length > 0 || mTabWidth != 0) {
            viewWidth = mItems.length * mTabWidth;
        }
        mViewScrollMaxDistance = viewWidth - metrics.widthPixels > 0 ? viewWidth - metrics.widthPixels : 0;
    }

    //更新整个view
    private void updateView() {
        updateTabView();
        initScrollParam();
        updateIndicatorView();
        setIndicatorMarginBottom();
        //不延时没有效果
        postDelayed(new Runnable() {
            @Override
            public void run() {
                tabCenterScroll();
            }
        }, 200);
        updateIndicatorLocation(0);
    }

    //更新下方指示器view
    private void updateIndicatorView() {
        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(mIndicatorWidth, mIndicatorHeight);
        layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
        mIndicator.setLayoutParams(layoutParams);
        mIndicator.setScaleType(ImageView.ScaleType.CENTER_CROP);
        mIndicator.setImageResource(mIndicatorImageId);
    }

    //更新指示器的位置(
    private void updateIndicatorLocation(final int duration) {
        int[] startLocation = new int[2];
        int[] endLocation = new int[2];
        mIndicator.getLocationInWindow(startLocation);
        mTabContainer.getChildAt(mTabSelectedPos).getLocationInWindow(endLocation);
        float distance = endLocation[0] - startLocation[0] + (mTabWidth - mIndicatorWidth) / 2;
        mIndicator.animate()
                .translationXBy(distance)
                .setDuration(duration)
                .start();
        mIndicator.animate()
                .scaleX(2f)
                .setDuration(SCALE_DURATION)
                .setListener(this)
                .start();
    }

    //设置指示器和底部的间距
    private void setIndicatorMarginBottom() {
        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)mIndicator.getLayoutParams();
        layoutParams.setMargins(0, 0, 0, mIndicatorMarginBottom);
    }

    //将选中的tab尽量的居中显示
    private void tabCenterScroll() {
        int tabCurrLocationX = mTabSelectedPos * mTabWidth;
        int scrollDistance = mTabCenterLocationX - tabCurrLocationX;
        int[] viewCurrentLocation = new int[2];
        getLocationOnScreen(viewCurrentLocation);
        int viewScrollToX = viewCurrentLocation[0] - scrollDistance;
        if(viewScrollToX < 0) {
            viewScrollToX = 0;
        } else if(viewScrollToX > mViewScrollMaxDistance) {
            viewScrollToX = mViewScrollMaxDistance;
        }
        smoothScrollTo(viewScrollToX, viewCurrentLocation[1]);
    }

    //更新所有的Tab
    private void updateTabView() {
        if(mViewPager != null) {
            relevanceViewPager();
        }
        if(mItems == null || mItems.length == 0) return;
        initTabWidth();
        addNewTabs();
        setTabPadding();
        setTabTextColor();
        setTabTextSize();
        setTabBackground();
        setTabSelected(mTabSelectedPos);
    }

    //关联viewPager
    private void relevanceViewPager() {
        PagerAdapter adapter = mViewPager.getAdapter();
        if(adapter == null) {
            throw new NullPointerException("viewPager adapter is null");
        }
        int length = adapter.getCount();
        mItems = new String[length];
        for(int i = 0; i < length; i++) {
            mItems[i] = adapter.getPageTitle(i).toString();
        }
        mViewPager.addOnPageChangeListener(this);
        mViewPager.getAdapter().registerDataSetObserver(new DataSetObserver() {
            @Override
            public void onChanged() {
                updateView();
            }
        });
    }

    //初始化Tab的宽度
    private void initTabWidth() {
        mTabWidth = 0;
        TextView view = new TextView(getContext());
        view.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
        view.setPadding(mTabTextPaddingLeft, mTabTextPaddingTop, mTabTextPaddingRight, mTabTextPaddingBottom);
        if(mTabBackgroundResId != 0) {
            view.setBackgroundResource(mTabBackgroundResId);
        }
        for(String item : mItems) {
            view.setText(item);
            view.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTabTextSize);
            view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
            int width = view.getMeasuredWidth();
            if(width > mTabWidth) {
                mTabWidth = width;
            }
        }
    }

    //添加新的tab
    private void addNewTabs() {
        mTabContainer.removeAllViews();
        int index = 0;
        for(String item : mItems) {
            TextView view = new TextView(getContext());
            view.setText(item);
            view.setGravity(Gravity.CENTER);
            view.setTag(index);
            view.setLayoutParams(new LinearLayout.LayoutParams(mTabWidth, ViewGroup.LayoutParams.MATCH_PARENT));
            view.setOnClickListener(this);
            mTabContainer.addView(view);
            index++;
        }
    }

    //设置所有tab的文字大小
    private void setTabTextSize() {
        int tabNum = mTabContainer.getChildCount();
        for(int i = 0; i < tabNum; i++) {
            TextView view = (TextView)mTabContainer.getChildAt(i);
            view.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTabTextSize);
        }
    }

    //设置所有tab的内边距
    private void setTabPadding() {
        int tabNum = mTabContainer.getChildCount();
        for(int i = 0; i < tabNum; i++) {
            View view = mTabContainer.getChildAt(i);
            view.setPadding(mTabTextPaddingLeft, mTabTextPaddingTop, mTabTextPaddingRight, mTabTextPaddingBottom);
        }
    }

    //设置所有tab的字体颜色
    private void setTabTextColor() {
        int tabNum = mTabContainer.getChildCount();
        if(mTabTextColorSelector != null) {
            for(int i = 0; i < tabNum; i++) {
                TextView view = (TextView)mTabContainer.getChildAt(i);
                view.setTextColor(mTabTextColorSelector);
            }
        } else {
            mTabTextColorSelector = getResources().getColorStateList(R.color.tab_layout_tab_text_color);
            for(int i = 0; i < tabNum; i++) {
                TextView view = (TextView)mTabContainer.getChildAt(i);
                view.setTextColor(mTabTextColorSelector);
            }
        }
    }

    //设置tab选中
    private void setTabSelected(int pos) {
        if(pos >= mTabContainer.getChildCount()) {
            throw new IllegalArgumentException("the index of crossing the line; max index:" + (mTabContainer.getChildCount() - 1) + " you index;" + pos);
        }
        mTabContainer.getChildAt(mTabSelectedPos).setSelected(false);
        mTabContainer.getChildAt(pos).setSelected(true);
        mTabSelectedPos = pos;
        if(mViewPager != null) {
            mViewPager.setCurrentItem(mTabSelectedPos);
        }
        if(mListener != null) {
            mListener.onTabSelected(mTabContainer.getChildAt(mTabSelectedPos), mTabSelectedPos);
        }
    }

    //设置所有tab的背景
    private void setTabBackground() {
        if(mTabBackgroundResId != 0) {
            for(int i = 0; i < mTabContainer.getChildCount(); i++) {
                View view = mTabContainer.getChildAt(i);
                view.setBackgroundResource(mTabBackgroundResId);
            }
        } else {
            for(int i = 0; i < mTabContainer.getChildCount(); i++) {
                View view = mTabContainer.getChildAt(i);
                int[] attrs = new int[]{
                        android.R.attr.selectableItemBackground
                };
                TypedArray ta = getContext().obtainStyledAttributes(attrs);
                Drawable drawable = ta.getDrawable(0);
                ta.recycle();
                view.setBackground(drawable);
            }
        }
    }

    /**
     * 设置数据源(关联ViewPager和该方法二选一)
     *
     * @param itemData 数据源
     */
    public void setItemData(String[] itemData) {
        mItems = itemData;
        updateView();
    }

    /**
     * 设置选中的tab
     *
     * @param position 对应的位置
     */
    public void setSelection(int position) {
        setTabSelected(position);
        tabCenterScroll();
        updateIndicatorLocation(TRANSLATION_DURATION);
    }

    /**
     * 设置指示器的图片文件
     *
     * @param imageResId 图片文件的资源ID
     */
    public void setIndicatorImageRes(int imageResId) {
        mIndicatorImageId = imageResId;
        updateIndicatorView();
    }

    /**
     * 设置指示器的下边距
     *
     * @param marginBottom 下边距
     */
    public void setIndicatorMarginBottom(int marginBottom) {
        mIndicatorMarginBottom = marginBottom;
        setIndicatorMarginBottom();
    }

    /**
     * 设置tab的内边距
     *
     * @param left   左边
     * @param top    顶部
     * @param right  右边
     * @param bottom 底部
     */
    public void setTabPadding(int left, int top, int right, int bottom) {
        mTabTextPaddingLeft = left;
        mTabTextPaddingTop = top;
        mTabTextPaddingRight = right;
        mTabTextPaddingBottom = bottom;
        setTabPadding();
    }

    /**
     * 设置tab的文字大小
     *
     * @param textSize 字体大小(单位px)
     */
    public void setTabTextSize(int textSize) {
        mTabTextSize = textSize;
        setTabTextSize();
    }

    /**
     * 设置tab选中时的监听
     *
     * @param listener 回调方法
     */
    public void setOnTabSelectedListener(OnTabSelectedListener listener) {
        mListener = listener;
    }

    /**
     * 设置tab字体的颜色(需要一个选择器来区分选中和未选中)
     *
     * @param colorStateList 颜色选择器
     */
    public void setTextColor(ColorStateList colorStateList) {
        mTabTextColorSelector = colorStateList;
        setTabTextColor();
    }

    /**
     * 设置tab的背景
     *
     * @param resId 背景的资源id
     */
    public void setTabBackground(int resId) {
        mTabBackgroundResId = resId;
        setTabBackground();
    }

    /**
     * 关联viewPager,需实现ViewPager的Adapter中getPageTitle方法
     *
     * @param view 要关联的viewPager
     */
    public void setWithViewPager(ViewPager view) {
        mViewPager = view;
        updateView();
    }

    public interface OnTabSelectedListener {
        void onTabSelected(View view, int position);
    }
}

一些资源文件

values\dimen.xml
<dimen name="tab_layout_tab_text_default_text_size">18sp</dimen>
<dimen name="tab_layout_tab_text_default_padding_left">18dp</dimen>
<dimen name="tab_layout_tab_text_default_padding_right">18dp</dimen>
<dimen name="tab_layout_indicator_default_margin_bottom">10dp</dimen>
<dimen name="tab_layout_indicator_default_width">13dp</dimen>
<dimen name="tab_layout_indicator_default_height">4dp</dimen>
drawable\tab_layout_indicator_default_image.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="#cc00ff"/>
    <corners android:radius="6dp"/>
</shape>
效果图:(指示器的样式可以自己定义)

新增:
推荐一个GitHub上的TabLayout的开源项目:
https://github.com/H07000223/FlycoTabLayout

相关文章

网友评论

    本文标题:可以自定义指示器形状的TabLayout

    本文链接:https://www.haomeiwen.com/subject/wxnqjxtx.html