自定义控件之固定Tab

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yoonerloop/article/details/81143821

在开发中我们通常用到固定的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根据上面的布局文件自己实现,这样这个功能就完成了,如下图:


至此三大方法介绍完毕,建议大家使用最后一种,简单方便。刚刚开始最后一种方法没有想到,于是自己实现了方法一,后来觉得方法一太繁琐,时间比较充足于是实现了方法二,方法二快完成的时候想起来方法三,最终项目中采用方法三实现了。

 

猜你喜欢

转载自blog.csdn.net/yoonerloop/article/details/81143821