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实现,也只有一个字体变化功能,源码很简单,更多功能可以根据需要增删改代码。