SmoothTabLayout--支持tab字体渐变的TabLayout

SmoothTabLayout–支持tab字体渐变的TabLayout

✍ Author by NamCooper

本文github地址  SmoothTabLayout–支持tab字体渐变的TabLayout

一、简介

1、在Android开发中,利用TabLayout或者第三方开源的类TabLayout控件实现Fragment切换或者ViewPager切换的情况可以说非常多,90%的应用都会使用到。而这些类TabLayout控件大同小异,以github上的一款star比较多的为例,效果如下:

2、本文实现的tab控件没有这么多的功能(这些功能,网上的实现有很多,再写也只是重复造轮胎),只提供一种可以使tab内的字体可以随着背景滑块渐变的效果,效果图如下:

二、控件分析

1、tab分层
  • 第一层:将黑色字体的tab作为最底层,因为在滑块移动的过程中,会将黑色字体逐渐遮挡。
  • 第二层:将红色滑块作为第二层,这个很好理解
  • 第三层:将白色字体的tab作为第三层,不同的是这一层附着于第二层内部,从而可以使白色字体始终包裹在红色滑块内,二不会超出。
2、图层/动画的图形分析

三、实现逻辑

1、创建自定义控件,继承RelativeLayout
  • 简单的步骤,不再细说
2、设置与ViewPager关联的方法,并增加底部tab
 public void setViewPager(final ViewPager viewPager) {
        bot = new LinearLayout(mContext);
        RelativeLayout.LayoutParams botParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        bot.setLayoutParams(botParams);
        this.addView(bot);
        PagerAdapter adapter = viewPager.getAdapter();
        final int tabCount = adapter.getCount();
        bot.setWeightSum(tabCount);
        //添加底部tab
        for (int i = 0; i < tabCount; i++) {
            String title = adapter.getPageTitle(i).toString();
            TextView view = new TextView(mContext);
            view.setText(title);
            view.setTextSize(textSize);
            view.setGravity(Gravity.CENTER);
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT);
            params.weight = 1;
            view.setLayoutParams(params);
            bot.addView(view);
            final int clickPos = i;
            view.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    viewPager.setCurrentItem(clickPos);
                }
            });
        }
}

      代码很简单,就是讲一个按照weight等分,宽高都填充父控件的LineraLayout添加到RelativeLayout中,实现后添加到布局,在activity中调用:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.namcooper.widgetdemos.activity.SmoothTabLayoutActivity">

    <com.namcooper.widgetdemos.widget.SmoothTabLayout
        android:id="@+id/tab"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:background="#0f0"/>

    <android.support.v4.view.ViewPager
        android:id="@+id/vp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />
</LinearLayout>
public class SmoothTabLayoutActivity extends AppCompatActivity {

    private SmoothTabLayout tabs;
    private ViewPager vp;
    private String[] titles = {"林黛玉", "董小宛", "柳如是", "李玉环"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_smooth_tab_layout);

        tabs = (SmoothTabLayout) findViewById(R.id.tab);
        vp = (ViewPager) findViewById(R.id.vp);

        vp.setAdapter(new PagerAdapter() {
            @Override
            public int getCount() {
                return titles.length;
            }

            @Override
            public boolean isViewFromObject(View view, Object object) {
                return view == object;
            }

            @Override
            public Object instantiateItem(ViewGroup container, int position) {
                TextView view = new TextView(container.getContext());
                view.setText("这是第" + position + "个界面");
                container.addView(view);
                return view;
            }

            @Override
            public void destroyItem(ViewGroup container, int position, Object object) {
                container.removeView((View) object);
            }

            @Override
            public CharSequence getPageTitle(int position) {
                return titles[position];
            }
        });

        tabs.setViewPager(vp);
    }
}

      效果如下:


3、添加滑块,并和ViewPager产生联动
public void setViewPager(final ViewPager viewPager) {
        ......
        ......
        ......
        //添加滑块
        final int screenWidth = getScreenWidth();
        int barWidth = (int) (screenWidth * 1f / tabCount);
        bar = new RelativeLayout(mContext);
        bar.setBackgroundColor(Color.RED);
        RelativeLayout.LayoutParams barParams = new LayoutParams(barWidth , ViewGroup.LayoutParams.MATCH_PARENT);
        bar.setLayoutParams(barParams);
        this.addView(bar);
        
        //控制滑块的滑动
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                float dia = paddingLeftAndRight * 1f / 2 + screenWidth * 1f * position / tabCount + positionOffset * screenWidth * 1f / tabCount;
                if (positionOffset % 1 != 0) {
                    //滑块移动
                    bar.setTranslationX(dia);
                }
            }

            @Override
            public void onPageSelected(int position) {
                bar.setTranslationX(screenWidth * 1f * position / tabCount);
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });
}

private int getScreenWidth() {
        WindowManager wm = (WindowManager) mContext
                .getSystemService(Context.WINDOW_SERVICE);

        return wm.getDefaultDisplay().getWidth();
}

      这一步里需要注意,我屏幕宽度按照tab的数量等分设置为滑块的宽度,高度填充了整个父控件。这里涉及了我实现的这个控件的弊端:这个控件只能在tab宽度占满屏幕,且所有tab平分屏幕宽度的需求下使用。 当然,后面的代码中会对这些进行一些优化,可以让使用者方便地控制tab的背景大小。效果如下:


4、添加第三层tab,并与滑块进行反向运动
public void setViewPager(final ViewPager viewPager) {
        .......
        .......
        .......

        //向滑块中增加上层tab
        for (int i = 0; i < tabCount; i++) {
            String title = adapter.getPageTitle(i).toString();
            TextView view = new TextView(mContext);
            view.setText(title);
            view.setTextSize(textSize);
            view.setTextColor(Color.WHITE);
            view.setGravity(Gravity.CENTER);
            //每一个tab的宽度都与滑块的宽度相同
            LinearLayout.LayoutParams topTabParams = new LinearLayout.LayoutParams(barWidth, ViewGroup.LayoutParams.MATCH_PARENT);
            view.setLayoutParams(topTabParams);
            topInner.addView(view);
        }
        top.addView(topInner);
        bar.addView(top);
        //控制滑块的滑动
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                float dia = screenWidth * 1f * position / tabCount + positionOffset * screenWidth * 1f / tabCount;
                if (positionOffset % 1 != 0) {
                    //滑块移动
                    bar.setTranslationX(dia);
                }
                //滑块内容反向移动
                topInner.setTranslationX(-dia);
            }

            @Override
            public void onPageSelected(int position) {
                bar.setTranslationX(screenWidth * 1f * position / tabCount);
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });

    }

      这里我使用了一个不能横向滑动的HorizonTalScollView,从而利用了HorizontallScrollView的子View可布置到屏幕之外的属性来讲所有顶部tab都附着于滑块内。

private class NoScrollHorizontalScrollView extends HorizontalScrollView {

        public NoScrollHorizontalScrollView(Context context) {
            super(context);
        }

        public NoScrollHorizontalScrollView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        public NoScrollHorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }

        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            return false;
        }
    }

      运行效果:


      可以发现,我们已经基本实现了想要的效果,下面贴上完整代码
4、完整代码
public class SmoothTabLayout extends RelativeLayout {
    private static final int BAR_WIDTH = 200;
    private Context mContext;
    private LinearLayout bot;
    private RelativeLayout bar;
    private NoScrollHorizontalScrollView top;
    private LinearLayout topInner;
    private int paddingTopAndBot = 0;
    private int paddingLeftAndRight = 0;
    private int textSize = 0;
    private int bgShape = R.drawable.shape_tab_item_bg;

    public SmoothTabLayout(Context context) {
        this(context, null);
    }

    public SmoothTabLayout(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, -1);
    }

    public SmoothTabLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        paddingTopAndBot = dp2px(10);
        paddingLeftAndRight = dp2px(10);
        textSize = 14;
    }

    public void setTextSize(int textSize) {
        this.textSize = textSize;
    }

    public void setPaddingTopAndBot(int paddingTopAndBot) {
        this.paddingTopAndBot = paddingTopAndBot;
    }

    public void setPaddingLeftAndRight(int paddingLeftAndRight) {
        this.paddingLeftAndRight = paddingLeftAndRight;
    }

    public void setBgShape(int resId) {
        this.bgShape = resId;
    }

    public void setViewPager(final ViewPager viewPager) {
        bot = new LinearLayout(mContext);
        RelativeLayout.LayoutParams botParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        bot.setLayoutParams(botParams);
        this.addView(bot);
        PagerAdapter adapter = viewPager.getAdapter();
        final int tabCount = adapter.getCount();
        bot.setWeightSum(tabCount);
        //添加底部tab
        for (int i = 0; i < tabCount; i++) {
            String title = adapter.getPageTitle(i).toString();
            TextView view = new TextView(mContext);
            view.setText(title);
            view.setTextSize(textSize);
            view.setGravity(Gravity.CENTER);
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT);
            params.weight = 1;
            view.setLayoutParams(params);
            bot.addView(view);
            final int clickPos = i;
            view.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    viewPager.setCurrentItem(clickPos);
                }
            });
        }
        //添加滑块
        final int screenWidth = getScreenWidth();
        int barWidth = (int) (screenWidth * 1f / tabCount);
        bar = new RelativeLayout(mContext);
        bar.setBackgroundResource(bgShape);
        RelativeLayout.LayoutParams barParams = new LayoutParams(barWidth - paddingLeftAndRight, ViewGroup.LayoutParams.MATCH_PARENT);
        barParams.topMargin = paddingTopAndBot;
        barParams.bottomMargin = paddingTopAndBot;
        bar.setLayoutParams(barParams);
        this.addView(bar);

        top = new NoScrollHorizontalScrollView(mContext);
        top.setHorizontalScrollBarEnabled(false);
        RelativeLayout.LayoutParams topParams = new LayoutParams(barWidth, ViewGroup.LayoutParams.MATCH_PARENT);
        top.setLayoutParams(topParams);
        HorizontalScrollView.LayoutParams params = new HorizontalScrollView.LayoutParams(barWidth, ViewGroup.LayoutParams.MATCH_PARENT);
        topInner = new LinearLayout(mContext);
        topInner.setLayoutParams(params);

        //向滑块中增加上层tab
        for (int i = 0; i < tabCount; i++) {
            String title = adapter.getPageTitle(i).toString();
            TextView view = new TextView(mContext);
            view.setText(title);
            view.setTextSize(textSize);
            view.setTextColor(Color.WHITE);
            view.setGravity(Gravity.CENTER);
            //每一个tab的宽度都与滑块的宽度相同
            LinearLayout.LayoutParams topTabParams = new LinearLayout.LayoutParams(barWidth, ViewGroup.LayoutParams.MATCH_PARENT);
            view.setLayoutParams(topTabParams);
            topInner.addView(view);
        }
        top.addView(topInner);
        bar.addView(top);
        bar.setTranslationX(paddingLeftAndRight * 1f / 2);
        //控制滑块的滑动
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                float dia = paddingLeftAndRight * 1f / 2 + screenWidth * 1f * position / tabCount + positionOffset * screenWidth * 1f / tabCount;
                if (positionOffset % 1 != 0) {
                    //滑块移动
                    bar.setTranslationX(dia);
                }
                //滑块内容反向移动
                topInner.setTranslationX(-dia);
            }

            @Override
            public void onPageSelected(int position) {
                bar.setTranslationX(screenWidth * 1f * position / tabCount);
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });

    }

    private int getScreenWidth() {
        WindowManager wm = (WindowManager) mContext
                .getSystemService(Context.WINDOW_SERVICE);

        return wm.getDefaultDisplay().getWidth();
    }

    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    private int dp2px(float dpValue) {
        return (int) (0.5f + dpValue * Resources.getSystem().getDisplayMetrics().density);
    }
    
}
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
       android:shape="rectangle">

    <solid android:color="#FF1B00"/>
    <corners android:radius="20dp"/>

</shape>

      提供了四个方法,分别是:

  • setTextSize(int textSize):设置tab的字体大小
  • setPaddingTopAndBot(int paddingTopAndBot):设置背景的上下padding
  • setPaddingLeftAndRight(int paddingLeftAndRight):设置背景的左右padding
  • setBgShape(int resId):设置背景的资源id,必须是shape的xml文件的id

本文github地址   SmoothTabLayout–支持tab字体渐变的TabLayout

四、问题探讨

  • 使用这种思路,可以继续完善进行控件大小变化的动态设置。
  • 这个控件只是进行了简单的tab实现,也只有一个字体变化功能,源码很简单,更多功能可以根据需要增删改代码。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值