Android實(shí)現(xiàn)單行標(biāo)簽流式布局
近期產(chǎn)品提了有關(guān)流式布局的新需求,要求顯示字?jǐn)?shù)不定的標(biāo)簽,如果一行顯示不完,就只顯示一行的內(nèi)容,而且還在一兩個(gè)頁(yè)面采取了多種樣式,無(wú)語(yǔ)了

自己歸類(lèi)了一下,需求有以下幾個(gè)區(qū)別
1:可選擇添加標(biāo)簽與否
2:是否有具體數(shù)量限制(比如最多顯示3個(gè))
3:是否要靠右對(duì)齊
有需求,先找一波現(xiàn)有的工具,看是不是可以直接用
參考Android流式布局實(shí)現(xiàn)熱門(mén)標(biāo)簽效果
FlowLayout原樣式:

這個(gè)和我們的需求已經(jīng)比較符合了,但是他并不能控制只顯示單行內(nèi)容
如果要實(shí)現(xiàn)這樣的布局,官方也提供了Flexbox和FlexboxLayout,但查閱文檔后發(fā)現(xiàn)他們都不支持設(shè)置單行,如果強(qiáng)行設(shè)置maxlines為1,所有子view都會(huì)被減少寬度來(lái)讓第一行擠下所有的子view
希望的樣式(第一行內(nèi)能放下多少就放多少,第二行開(kāi)始都不顯示,也不占用高度):

實(shí)際上:

可以看到這些view的寬度都被嚴(yán)重壓縮了,即使設(shè)置了padding也是沒(méi)有用的。正好項(xiàng)目本身使用了FlowLayout,就在他的基礎(chǔ)上進(jìn)行修改。
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.RequiresApi;
import java.util.ArrayList;
import java.util.List;
public class SingleLineFlowLayout extends ViewGroup {
private static final String TAG = "FlowLayout";
public int position=-1;
public boolean alignRight;
public boolean countMore;
private List<List<View>> mLineViews = new ArrayList<List<View>>();
private List<Integer> mLineHeight = new ArrayList<Integer>();
public SingleLineFlowLayout(Context context) {
super(context);
}
public SingleLineFlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SingleLineFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public SingleLineFlowLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
/**
* 測(cè)量所有子View大小,確定ViewGroup的寬高
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//由于onMeasure會(huì)執(zhí)行多次,避免重復(fù)的計(jì)算控件個(gè)數(shù)和高度,這里需要進(jìn)行清空操作
mLineViews.clear();
mLineHeight.clear();
//獲取測(cè)量的模式和尺寸大小
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec) + getPaddingTop() + getPaddingBottom();
//記錄ViewGroup真實(shí)的測(cè)量寬高
int viewGroupWidth = widthSize;
int viewGroupHeight = getPaddingTop() + getPaddingBottom();
if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {
viewGroupWidth = widthSize;
viewGroupHeight = heightSize;
} else {
//當(dāng)前所占的寬高
int currentLineWidth = 0;
int currentLineHeight = 0;
int lastChildWidth=0;
int doubleLastChildWidth=0;
//用來(lái)存儲(chǔ)每一行上的子View
List<View> lineView = new ArrayList<View>();
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
//對(duì)子View進(jìn)行測(cè)量
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) childView.getLayoutParams();
int childViewWidth = childView.getMeasuredWidth() + marginLayoutParams.leftMargin + marginLayoutParams.rightMargin;
int childViewHeight = childView.getMeasuredHeight() + marginLayoutParams.topMargin + marginLayoutParams.bottomMargin;
if (currentLineWidth + childViewWidth > widthSize) {
//從當(dāng)前位置開(kāi)始,刪除view,保留最后一個(gè)moreView
String tag=(String) childView.getTag();
if (tag!=null&&tag.equals("More")){
currentLineHeight = Math.max(currentLineHeight, childViewHeight);
}else {
removeViews(i,getChildCount()-1-i);
childView = getChildAt(i);
tag=(String) childView.getTag();
if (tag==null||!tag.equals("More")){
currentLineHeight = Math.max(currentLineHeight, childViewHeight);
}else {
//對(duì)子View進(jìn)行測(cè)量
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
marginLayoutParams = (MarginLayoutParams) childView.getLayoutParams();
childViewWidth = childView.getMeasuredWidth() + marginLayoutParams.leftMargin + marginLayoutParams.rightMargin;
childViewHeight = childView.getMeasuredHeight() + marginLayoutParams.topMargin + marginLayoutParams.bottomMargin;
if (currentLineWidth + childViewWidth > widthSize) {
//lineView.remove(i-1);
lineView.remove(getChildAt(i-1));
removeViewAt(i-1);
currentLineWidth=currentLineWidth-lastChildWidth;
}
if (i-1>0&¤tLineWidth + childViewWidth > widthSize){
currentLineWidth=currentLineWidth-doubleLastChildWidth-marginLayoutParams.leftMargin - marginLayoutParams.rightMargin;
lineView.remove(getChildAt(i-2));
removeViewAt(i-2);
}
currentLineWidth += childViewWidth;
currentLineHeight = Math.max(currentLineHeight, childViewHeight);
//添加行對(duì)象里的子View
if (alignRight){
int rightMargin=marginLayoutParams.rightMargin;
marginLayoutParams.setMargins(marginLayoutParams.leftMargin,marginLayoutParams.topMargin,0,marginLayoutParams.bottomMargin);
childView.setLayoutParams(marginLayoutParams);
marginLayoutParams=(MarginLayoutParams) getChildAt(0).getLayoutParams();
marginLayoutParams.setMargins(marginLayoutParams.leftMargin+widthSize-currentLineWidth+rightMargin,marginLayoutParams.topMargin,marginLayoutParams.rightMargin,marginLayoutParams.bottomMargin);
getChildAt(0).setLayoutParams(marginLayoutParams);
}else {
marginLayoutParams.setMargins(widthSize-currentLineWidth,marginLayoutParams.topMargin,0,marginLayoutParams.bottomMargin);
childView.setLayoutParams(marginLayoutParams);
}
lineView.add(childView);
}
}
} else {
//當(dāng)前行寬+子View+左右外邊距<=ViewGroup的寬度,不換行
if (i>1){
doubleLastChildWidth=lastChildWidth;
}
lastChildWidth=childViewWidth;
currentLineWidth += childViewWidth;
currentLineHeight = Math.max(currentLineHeight, childViewHeight);
//添加行對(duì)象里的子View
String tag=(String) childView.getTag();
if (tag!=null&&tag.equals("More")){
if (countMore){
lineView.add(childView);
}
}else {
lineView.add(childView);
}
}
if (i >= getChildCount() - 1) {
//最后一個(gè)子View的時(shí)候
//添加行對(duì)象
if (alignRight){
int rightMargin=marginLayoutParams.rightMargin;
marginLayoutParams.setMargins(marginLayoutParams.leftMargin,marginLayoutParams.topMargin,0,marginLayoutParams.bottomMargin);
childView.setLayoutParams(marginLayoutParams);
marginLayoutParams=(MarginLayoutParams) getChildAt(0).getLayoutParams();
marginLayoutParams.setMargins(marginLayoutParams.leftMargin+widthSize-currentLineWidth+rightMargin,marginLayoutParams.topMargin,marginLayoutParams.rightMargin,marginLayoutParams.bottomMargin);
getChildAt(0).setLayoutParams(marginLayoutParams);
}
mLineViews.add(lineView);
viewGroupWidth = Math.max(childViewWidth, widthSize);
viewGroupHeight = viewGroupHeight+currentLineHeight;
//添加行高
mLineHeight.add(currentLineHeight);
}
}
}
setMeasuredDimension(viewGroupWidth, viewGroupHeight);
}
/**
* 設(shè)置ViewGroup里子View的具體位置
*
* @param changed
* @param l
* @param t
* @param r
* @param b
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int left = getPaddingLeft();
int top = getPaddingTop();
//一共有幾行
int lines = mLineViews.size();
for (int i = 0; i < lines; i++) {
//每行行高
int lineHeight = mLineHeight.get(i);
//行內(nèi)有幾個(gè)子View
List<View> viewList = mLineViews.get(i);
int views = viewList.size();
for (int j = 0; j < views; j++) {
View view = viewList.get(j);
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
int vl = left + (j == 0 ? 0 : marginLayoutParams.leftMargin);
if (alignRight){
vl = left + marginLayoutParams.leftMargin;
}
int vt = top + marginLayoutParams.topMargin;
int vr = vl + view.getMeasuredWidth();
int vb = vt + view.getMeasuredHeight();
view.layout(vl, vt, vr, vb);
if (alignRight){
left += view.getMeasuredWidth() + marginLayoutParams.leftMargin + marginLayoutParams.rightMargin;
}else {
left += view.getMeasuredWidth() + (j == 0 ? 0 : marginLayoutParams.leftMargin) + marginLayoutParams.rightMargin;
}
}
left = getPaddingLeft();
top += lineHeight;
}
}
/**
* 指定ViewGroup的LayoutParams
*
* @param attrs
* @return
*/
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
}
使用方法:
1:使用addView()將所有的標(biāo)簽都添加到布局中
2:如果需要顯示代表更多的標(biāo)簽,在添加完所有標(biāo)簽后,再添加對(duì)應(yīng)的view,并且設(shè)置view.setTag("More")即可(該View會(huì)自動(dòng)靠右對(duì)齊,并且會(huì)檢測(cè)是否能放得下該標(biāo)簽,如果放不下的話會(huì)依次嘗試兩次從后往前刪除子view后能否放得下,如果遇到特殊情況需要?jiǎng)h除更多的子view的話,可以自己修改代碼)
3:如果需要所有的view都右對(duì)齊,要在addView()前設(shè)置布局的alignRight=true
4:如果需要超過(guò)某個(gè)數(shù)量后,即使可以單行顯示,也要添加更多標(biāo)簽的話,要在addView()前設(shè)置布局的countMore=true,使用addView()時(shí)自己控制添加的數(shù)量,并在超過(guò)數(shù)量的時(shí)候添加對(duì)應(yīng)的view,并且設(shè)置view.setTag("More")
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android數(shù)據(jù)持久化之SQLite數(shù)據(jù)庫(kù)用法分析
這篇文章主要介紹了Android數(shù)據(jù)持久化之SQLite數(shù)據(jù)庫(kù)用法,結(jié)合實(shí)例形式分析了SQLite概念、功能、相關(guān)操作類(lèi)與使用技巧,需要的朋友可以參考下2017-05-05
android實(shí)現(xiàn)添加耳機(jī)狀態(tài)圖標(biāo)的方法
這篇文章主要介紹了android實(shí)現(xiàn)添加耳機(jī)狀態(tài)圖標(biāo)的方法,較為詳細(xì)的分析了Android實(shí)現(xiàn)添加耳機(jī)圖標(biāo)的原理與相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10
Android中比較兩個(gè)圖片是否一致的問(wèn)題
這篇文章主要介紹了Android中比較兩個(gè)圖片是否一致的問(wèn)題,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10
微信小程序首頁(yè)數(shù)據(jù)初始化失敗的解決方法
這篇文章主要介紹了微信小程序首頁(yè)數(shù)據(jù)初始化失敗的解決方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-01-01
Android自定義ViewGroup實(shí)現(xiàn)受邊界限制的滾動(dòng)操作(3)
這篇文章主要為大家詳細(xì)介紹了Android自定義ViewGroup實(shí)現(xiàn)受邊界限制的滾動(dòng)操作,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
autojs模仿QQ長(zhǎng)按彈窗菜單實(shí)現(xiàn)示例
這篇文章主要為大家介紹了autojs模仿QQ長(zhǎng)按彈窗菜單實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
Android自定義可拖拽的懸浮按鈕DragFloatingActionButton
這篇文章主要介紹了Android自定義可拖拽的懸浮按鈕DragFloatingActionButton,需要的朋友可以參考下2017-06-06
簡(jiǎn)單實(shí)現(xiàn)Android倒計(jì)時(shí)效果
這篇文章主要教大家如何簡(jiǎn)單的實(shí)現(xiàn)Android倒計(jì)時(shí)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10
Android多線程斷點(diǎn)續(xù)傳下載示例詳解
這篇文章主要為大家詳細(xì)介紹了Android多線程斷點(diǎn)續(xù)傳下載示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11

