前言
實際項目中需要實作一個 熱門搜尋 的欄目,類似下圖:
由于 子項(子view) 中的文字是可變的,一行能顯示的 子項 的個數也無法确定。需要支援自動換行和計算位置。

�# 使用開源類庫SimpleFlowLayout
我自己寫了個 自定義view ,繼承自viewGroup, 來實作它,托管到github開源平台。
名稱:SimpleFlowLayout
位址:https://github.com/vir56k/SimpleFlowLayout
特點:可以不斷添加多個子view,計算位置,自動換行。 類似html中的div标簽
适用: 熱門标簽
複制
實作思路
要實作 自定義的viewgroup,需要:
- 繼承自 ViewGroup
-
實作 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
這個方法用于測量 自己(自定義view)本身需要的寬度和高度
-
實作 protected void onLayout(boolean changed, int l, int t, int r, int b)
這個方法用于指定如何擺放 子view 的位置。
實作代碼
package zhangyf.vir56k.flowframelayout;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
/**
* name: android 簡單的流布局自定義view
* 作者:張雲飛vir
* 特點:可以不斷添加多個子view,計算位置,自動換行。
* 适用: 熱門标簽
* Created by zhangyunfei on 15/12/4.
*/
public class SimpleFlowLayout extends ViewGroup {
public SimpleFlowLayout(Context context) {
super(context);
}
public SimpleFlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SimpleFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMax = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMax = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthNeed = 0;
int heightNeed = 0;
int x = 0;
int y = 0;
int currentLineHeight = 0;
View child;
for (int i = 0; i < getChildCount(); i++) {
child = getChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
child.measure(widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();//獲得子view的 外邊距
//測算子view寬度,本行這句代碼有問題,不能計算子view的自動換行 int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
//使用viewGroup的measureChildWithMargins測算寬度,在這個方法裡處理了 LayoutParams的match_parent等方式的處理
int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
if (x + childWidth > widthMax) {//換行處理,本行高度和x軸都清零,y軸下移(加上上次的行高)
y += currentLineHeight;
currentLineHeight = 0;
x = 0;
}
x += childWidth;
currentLineHeight = Math.max(currentLineHeight, childHeight);
widthNeed = Math.max(widthNeed, x);//加入了這個 子view後,留下最大寬度
heightNeed = Math.max(heightNeed, y + currentLineHeight);//對比上次的,留下最大的高度
}
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthMax : widthNeed,
heightMode == MeasureSpec.EXACTLY ? heightMax : heightNeed);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int widthMax = getWidth();
int x, y;
x = 0;
y = 0;
View child;
int left = 0;
int top = 0;
int currentLineHeight = 0;
for (int i = 0; i < getChildCount(); i++) {
child = getChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
if (x + childWidth > widthMax) {//換行處理
y += currentLineHeight;
x = 0;
currentLineHeight = 0;
}
left = x + lp.leftMargin;
top = y + lp.topMargin;
//定位子view的位置
child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
x += childWidth;
currentLineHeight = Math.max(currentLineHeight, childHeight);
}
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
}
複制