不管是在移動端的App,仍是在前端的網頁,咱們常常會看到下面這種標籤的列表效果: 前端
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
int maxWidth = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
int contentHeight = 0; //記錄內容的高度
int lineWidth = 0; //記錄行的寬度
int maxLineWidth = 0; //記錄最寬的行寬
int maxItemHeight = 0; //記錄一行中item高度最大的高度
boolean begin = true; //是不是行的開頭
//循環測量item並計算控件的內容寬高
for (int i = 0; i < count; i++) {
View view = getChildAt(i);
measureChild(view, widthMeasureSpec, heightMeasureSpec);
if(!begin) {
lineWidth += mWordMargin;
}else {
begin = false;
}
//當前行顯示不下item時換行。
if (maxWidth <= lineWidth + view.getMeasuredWidth()) {
contentHeight += mLineMargin;
contentHeight += maxItemHeight;
maxItemHeight = 0;
maxLineWidth = Math.max(maxLineWidth, lineWidth);
lineWidth = 0;
begin = true;
}
maxItemHeight = Math.max(maxItemHeight, view.getMeasuredHeight());
lineWidth += view.getMeasuredWidth();
}
contentHeight += maxItemHeight;
maxLineWidth = Math.max(maxLineWidth, lineWidth);
//測量控件的最終寬高
setMeasuredDimension(measureWidth(widthMeasureSpec,maxLineWidth),
measureHeight(heightMeasureSpec, contentHeight));
}
//測量控件的寬
private int measureWidth(int measureSpec, int contentWidth) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = contentWidth + getPaddingLeft() + getPaddingRight();
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
//這一句是爲了支持minWidth屬性。
result = Math.max(result, getSuggestedMinimumWidth());
return result;
}
//測量控件的高
private int measureHeight(int measureSpec, int contentHeight) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = contentHeight + getPaddingTop() + getPaddingBottom();
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
//這一句是爲了支持minHeight屬性。
result = Math.max(result, getSuggestedMinimumHeight());
return result;
}
複製代碼
標籤的擺放:java
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int x = getPaddingLeft();
int y = getPaddingTop();
int contentWidth = right - left;
int maxItemHeight = 0;
int count = getChildCount();
//循環擺放item
for (int i = 0; i < count; i++) {
View view = getChildAt(i);
//當前行顯示不下item時換行。
if (contentWidth < x + view.getMeasuredWidth() + getPaddingRight()) {
x = getPaddingLeft();
y += mLineMargin;
y += maxItemHeight;
maxItemHeight = 0;
}
view.layout(x, y, x + view.getMeasuredWidth(), y + view.getMeasuredHeight());
x += view.getMeasuredWidth();
x += mWordMargin;
maxItemHeight = Math.max(maxItemHeight, view.getMeasuredHeight());
}
}
複製代碼
onMeasure和onLayout的實現代碼基本是同樣的,不一樣的只是一個是測量寬高,一個是擺放位置而已。實現起來很是的簡單。 以上是LabelsView的核心代碼,LabelsView除了實現了item的測量和擺放之外,還提供了一系列的方法讓使用者能夠方便設置標籤的樣式(包括標籤被選中的樣式)和標籤點擊、選中的監聽等。下面LabelsView的使用介紹。android
一、引入依賴 在Project的build.gradle在添加如下代碼git
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
複製代碼
在Module的build.gradle在添加如下代碼github
dependencies {
compile 'com.github.donkingliang:LabelsView:1.4.1'
}
複製代碼
二、編寫佈局:數組
<com.donkingliang.labels.LabelsView xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/labels" android:layout_width="match_parent" android:layout_height="wrap_content" app:labelBackground="@drawable/label_bg" //標籤的背景 app:labelTextColor="@drawable/label_text_color" //標籤的字體顏色 能夠是一個顏色值 app:labelTextSize="14sp" //標籤的字體大小 app:labelTextPaddingBottom="5dp" //標籤的上下左右邊距 app:labelTextPaddingLeft="10dp" app:labelTextPaddingRight="10dp" app:labelTextPaddingTop="5dp" app:lineMargin="10dp" //行與行的距離 app:wordMargin="10dp" //標籤與標籤的距離 app:selectType="SINGLE" //標籤的選擇類型 有單選(可反選)、單選(不可反選)、多選、不可選四種類型 app:maxSelect="5" /> //標籤的最大選擇數量,只有多選的時候纔有用,0爲不限數量
複製代碼
這裏有兩個地方須要說明一下:bash
1)標籤的正常樣式和選中樣式是經過drawable來實現的。好比下面兩個drawable。微信
<!-- 標籤的背景 label_bg -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 標籤選中時的背景 -->
<item android:state_selected="true">
<shape>
<stroke android:width="2dp" android:color="#fb435b" />
<corners android:radius="8dp" />
<solid android:color="@android:color/white" />
</shape>
</item>
<!-- 標籤的正常背景 -->
<item>
<shape>
<stroke android:width="2dp" android:color="#656565" />
<corners android:radius="8dp" />
<solid android:color="@android:color/white" />
</shape>
</item>
</selector>
複製代碼
<!-- 標籤的文字顏色 label_text_color -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 標籤選中時的文字顏色 -->
<item android:color="#fb435b" android:state_selected="true" />
<!-- 標籤的正常文字顏色 -->
<item android:color="#2d2b2b" />
</selector>
複製代碼
TextView的textColor屬性除了能夠設置一個顏色值之外,也能夠經過資源來設置的,這一點不少同窗都不知道。微信開發
2)標籤的選擇類型有四種:app
NONE :標籤不可選中,也不響應選中事件監聽,這是默認值。
SINGLE:單選(可反選)。這種模式下,能夠一個也不選。
SINGLE_IRREVOCABLY:單選(不可反選)。這種模式下,有且只有一個是選中的。默認是第一個。
MULTI:多選,能夠經過設置maxSelect限定選擇的最大數量,0爲不限數量。maxSelect只有在多選的時候纔有效。多選模式下能夠設置一些標籤爲必選項。必選項的標籤默認選中,且不能取消。
三、設置標籤:
labelsView = (LabelsView) findViewById(labels);
ArrayList<String> label = new ArrayList<>();
label.add("Android");
label.add("IOS");
label.add("前端");
label.add("後臺");
label.add("微信開發");
label.add("遊戲開發");
labelsView.setLabels(label); //直接設置一個字符串數組就能夠了。
//LabelsView能夠設置任何類型的數據,而不只僅是String。
ArrayList<TestBean> testList = new ArrayList<>();
testList.add(new TestBean("Android",1));
testList.add(new TestBean("IOS",2));
testList.add(new TestBean("前端",3));
testList.add(new TestBean("後臺",4));
testList.add(new TestBean("微信開發",5));
testList.add(new TestBean("遊戲開發",6));
labelsView.setLabels(testList, new LabelsView.LabelTextProvider<TestBean>() {
@Override
public CharSequence getLabelText(TextView label, int position, TestBean data) {
//根據data和position返回label須要顯示的數據。
return data.getName();
}
});
複製代碼
四、設置事件監聽:(若是須要的話)
//標籤的點擊監聽
labelsView.setOnLabelClickListener(new LabelsView.OnLabelClickListener() {
@Override
public void onLabelClick(TextView label, Object data, int position) {
//label是被點擊的標籤,data是標籤所對應的數據,position是標籤的位置。
}
});
//標籤的選中監聽
labelsView.setOnLabelSelectChangeListener(new LabelsView.OnLabelSelectChangeListener() {
@Override
public void onLabelSelectChange(TextView label, Object data, boolean isSelect, int position) {
//label是被選中的標籤,data是標籤所對應的數據,isSelect是是否選中,position是標籤的位置。
}
});
複製代碼
五、經常使用方法
//設置選中標籤。
//positions是個可變類型,表示被選中的標籤的位置。
//比喻labelsView.setSelects(1,2,5);選中第1,3,5個標籤。若是是單選的話,只有第一個參數有效。
public void setSelects(int... positions);
public void setSelects(List<Integer> positions); //獲取選中的標籤(返回的是全部選中的標籤的位置)。返回的是一個Integer的數組,表示被選中的標籤的下標。若是沒有選中,數組的size等於0。 public ArrayList<Integer> getSelectLabels();
//獲取選中的label(返回的是全部選中的標籤的數據)。若是沒有選中,數組的size等於0。T表示標籤的數據類型。
public <T> List<T> getSelectLabelDatas();
//取消全部選中的標籤。
public void clearAllSelect();
//設置標籤的選擇類型,有NONE、SINGLE、SINGLE_IRREVOCABLY和MULTI四種類型。
public void setSelectType(SelectType selectType);
//設置最大的選擇數量,只有selectType等於MULTI是有效。
public void setMaxSelect(int maxSelect);
//設置必選項,只有在多項模式下,這個方法纔有效
public void setCompulsorys(int... positions) public void setCompulsorys(List<Integer> positions) //清空必選項,只有在多項模式下,這個方法纔有效 public void clearCompulsorys() //設置標籤背景 public void setLabelBackgroundResource(int resId);
//設置標籤的文字顏色
public void setLabelTextColor(int color);
public void setLabelTextColor(ColorStateList color);
//設置標籤的文字大小(單位是px)
public void setLabelTextSize(float size);
//設置標籤內邊距
public void setLabelTextPadding(int left, int top, int right, int bottom);
//設置行間隔
public void setLineMargin(int margin);
//設置標籤的間隔
public void setWordMargin(int margin);
複製代碼
全部的set方法都有對應的get方法,這裏就不說了。
效果圖:
最後給出該控件在GitHub中的地址,歡迎你們訪問和使用。 github.com/donkinglian…
文章已同步到個人簡書