在开发中我们通常用到固定的Tab,Tab的个数是可以动态配置的,但是不支持滑动,每个Tab均分布局并且之间被一个竖线分割开,Tab底部是一条分割线。看到如下效果如下,Tab布局、线条颜色都支持高度制定。这个Tab的难点在于首先Tab个数不固定,其次Tab竖线左右两端没有只有相邻的两个才有,而且粗细一致,最后每个Tab宽度一致。现在就通过过三种方式来实现它。下面分别介绍实现原理和步骤:
:
方法一:
方法一是把整个布局当做一个线性布局,在线性布局中根据接口返回数据个数,动态添加每个内容和竖线,然后再平分这个Tab,从而来实现,按照这个原理现在来实现。
1、创建整体线性布局和底部线条
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ll_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#dbdbdb"/>
</LinearLayout>
2、创建每个Tab的布局,这个布局可以制定
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:orientation="horizontal"
android:gravity="center">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="德国"/>
</LinearLayout>
<View
android:id="@+id/view_devide_v"
android:layout_width="1dp"
android:layout_height="40dp"
android:layout_alignParentRight="true"
android:background="#dbdbdb"/>
</RelativeLayout>
3、添加子Tab
子Tab的个数依据一般开发中依据接口的返回数据个数,这里假设4个,依据个数进行添加。
private static final String[] TAB_TITLE = {"德国","日本","法国","英国"};
LinearLayout mLlLayout = (LinearLayout) findViewById(R.id.ll_layout);
//添加布局
for (int i = 0; i < TAB_TITLE.length; i++) {
View view = View.inflate(this, R.layout.tab_item, null);
mDevideView = view.findViewById(R.id.view_devide_v);
mTvTitle = view.findViewById(R.id.tv_title);
mTvTitle.setText(TAB_TITLE[i]);
view.setOnClickListener(new MyOnClickListener(i));
//这里隐藏掉最后一个竖线
if (i == TAB_TITLE.length - 1) {
mDevideView.setVisibility(View.INVISIBLE);
}
mLlLayout.addView(view);
}
4、平分Tab
每个Tab之间的距离相等,因此可以获取到xml中设置的宽度,然后进行平分。
//均分
mLlLayout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
int measuredWidth = mLlLayout.getMeasuredWidth();
int size = measuredWidth / mLlLayout.getChildCount();
for (int i = 0; i < mLlLayout.getChildCount(); i++) {
RelativeLayout child = (RelativeLayout) mLlLayout.getChildAt(i);
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) child.getLayoutParams();
layoutParams.width = size;
child.setLayoutParams(layoutParams);
}
mLlLayout.removeOnLayoutChangeListener(this);
}
});
5、最后监听点击事件
/**
* Tab的点击事件
*/
private class MyOnClickListener implements View.OnClickListener {
private int position;
public MyOnClickListener(int position){
this.position = position;
}
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this,"点击了:"+TAB_TITLE[position],Toast.LENGTH_SHORT).show();
}
}
这样这个功能就实现了,如上图片显示结果。
方法二:
方法一虽然实现了,但是不够完美,写法上也是比较繁琐的。现在通过一种更简单的方法来实现,耦合性更低。Tab既然是一种布局,那么就能够用自定义布局来进行实现。根据这个布局的特点这里采用继承自线性布局。首先先进行绘制竖线,然后在进行添加每个View。
1、绘制线条
这里线条存在两个竖线线条、横线线条,所以创建两个画笔进行绘制。
private void init() {
//创建画笔
paintV = new Paint();
//设置颜色
paintV.setColor(colorV);
paintV.setAntiAlias(true);
paintV.setDither(true);
//设置线条粗细
paintV.setStrokeWidth(verticalLineWidth);
paintH = new Paint();
paintH.setColor(colorH);
paintH.setAntiAlias(true);
paintH.setDither(true);
paintH.setStrokeWidth(horizontalLineWidth);
}
2、获取绘制参数
绘制过程中需要测量xml中控件配置的宽高,因此需要重写OnMeasure进行测量。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
height = MeasureSpec.getSize(heightMeasureSpec);
width = MeasureSpec.getSize(widthMeasureSpec);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
3、绘制线条
在Canvas中有个drawLine方法就是绘制线条,因此使用它进行绘制,如何绘制多个竖线呢?我们加一个for循环动态改变坐标位置就可以了。
@Override
protected void onDraw(Canvas canvas) {
float size = (width + 0.5f) / number;
for (int i = 0; i < number; i++) {
canvas.drawLine(size * (i + 1), 0, size * (i + 1), height, paintV);
}
canvas.drawLine(0, height, width, height, paintH);
}
其中width为onMeasure中获取的,number为Tab个数,五个参数分别为起始X、Y坐标,终止X、Y坐标以及画笔。
4、填充数据
在绘制完毕之后就需要填充Tab内容了,这里提供一个方法添加子布局,供Activity调用,然后再请求重新绘制即可。
public void setData(List<View> viewList) {
if (viewList == null && viewList.isEmpty()) {
return;
}
this.number = viewList.size();
setOrientation(LinearLayout.HORIZONTAL);
LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, 1);
for (int i = 0; i < viewList.size(); i++) {
View view = viewList.get(i);
layoutParams.width = (width) / number;
view.setLayoutParams(layoutParams);
addView(view);
}
requestLayout();
}
5、使用
Activity:
public class MainActivity extends AppCompatActivity {
private static final String[] TAB_TITLE = {"德国", "日本", "法国", "英国"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TabLineView tabLineView = findViewById(R.id.tab_view);
ArrayList<View> views = new ArrayList<>();
for (int i = 0; i < 4; i++) {
View view = View.inflate(this, R.layout.tab_item, null);
TextView tvTitle = view.findViewById(R.id.tv_title);
tvTitle.setText(TAB_TITLE[i]);
views.add(view);
}
tabLineView.setData(views);
}
}
Tab布局:
<?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.demo.demo.MainActivity">
<com.demo.demo.TabLineView
android:id="@+id/tab_view"
android:layout_width="match_parent"
android:layout_height="40dp"/>
</LinearLayout>
效果图:
方法三:
方法一二都是自定义实现的方式,那么Android系统中有没有合适的控件呢?在做完方法二后觉得GradView应该可以,实践后果然可以,而且更简单,接下来就看看GradView方法实现吧!
1、布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#dbdbdb"
android:orientation="vertical">
<GridView
android:id="@+id/gv_gradview"
android:layout_width="match_parent"
android:horizontalSpacing="0.5dp"
android:layout_height="40dp"/>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#dbdbdb"/>
</LinearLayout>
这里需要注意的是GradView一定要设置成固定高度,不能让子View一个进行限制。这样Tab的布局就交给你GradView,不用处理其他东西。
2、Tab布局
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="@android:color/white"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="40dp"
android:gravity="center"
android:text="德国"/>
</FrameLayout>
这里没有设置竖线,把GradView背景设置成竖线颜色,再把Tab布局文件背景设置成白色,并且设置GradView每个item的间距为0.5dp,这样他们之间的间距就显示的是一条白线,这样很巧妙的实现了竖线布局。
3、使用
在Activity中三行代码就搞定。
public class MainActivity extends AppCompatActivity {
private static final String[] TAB_TITLE = {"德国", "日本", "法国", "英国"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GridView gradview = findViewById(R.id.gv_gradview);
gradview.setNumColumns(TAB_TITLE.length);
gradview.setAdapter(new MyAdapter());
}
}
MyAdapter根据上面的布局文件自己实现,这样这个功能就完成了,如下图:
至此三大方法介绍完毕,建议大家使用最后一种,简单方便。刚刚开始最后一种方法没有想到,于是自己实现了方法一,后来觉得方法一太繁琐,时间比较充足于是实现了方法二,方法二快完成的时候想起来方法三,最终项目中采用方法三实现了。