1. 程式人生 > >自定義控制元件之固定Tab

自定義控制元件之固定Tab

在開發中我們通常用到固定的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根據上面的佈局檔案自己實現,這樣這個功能就完成了,如下圖:


至此三大方法介紹完畢,建議大家使用最後一種,簡單方便。剛剛開始最後一種方法沒有想到,於是自己實現了方法一,後來覺得方法一太繁瑣,時間比較充足於是實現了方法二,方法二快完成的時候想起來方法三,最終專案中採用方法三實現了。