自定義控制元件之固定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根據上面的佈局檔案自己實現,這樣這個功能就完成了,如下圖:
至此三大方法介紹完畢,建議大家使用最後一種,簡單方便。剛剛開始最後一種方法沒有想到,於是自己實現了方法一,後來覺得方法一太繁瑣,時間比較充足於是實現了方法二,方法二快完成的時候想起來方法三,最終專案中採用方法三實現了。