先上效果圖:
接着看代碼實現:
public class FlowLayout extends ViewGroup {
protected DataSetObserver mDataSetObserver;
private FlowBaseAdapter mAdapter;
/**
* 所有的子view按行存儲
*/
private final List<List<View>> mAllChildViews;
/**
* 所有行高
*/
private final List<Integer> mLineHeights;
public FlowLayout(Context context) {
this(context, null);
}
public FlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mAllChildViews = new ArrayList<>();
mLineHeights = new ArrayList<>();
mDataSetObserver = new DataSetObserver() {
@Override
public void onChanged() {
resetLayout();
}
};
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 獲取XML設置的大小和測量模式
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
if (modeWidth == MeasureSpec.AT_MOST) {
throw new RuntimeException("FlowLayout: layout_width must not be set to wrap_content !!!");
}
int height = getPaddingTop() + getPaddingBottom();
// 行寬
int lineWidth = 0;
// 行高
int lineHeight = 0;
int childCount = getChildCount();
mAllChildViews.clear();
mLineHeights.clear();
List<View> lineViews = new ArrayList<>();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams lp = (MarginLayoutParams) childView
.getLayoutParams();
int childLineWidth = childView.getMeasuredWidth() + lp.leftMargin
+ lp.rightMargin;
int childLineHeight = childView.getMeasuredHeight() + lp.topMargin
+ lp.bottomMargin;
// 考慮padding
if (childLineWidth + lineWidth > (widthSize - getPaddingRight() - getPaddingLeft())) {
// 換行
height += lineHeight;
lineWidth = childLineWidth;
// 添加子View到集合
mAllChildViews.add(lineViews);
mLineHeights.add(lineHeight);
lineViews = new ArrayList<View>();
lineViews.add(childView);
} else {
// 不換行
lineHeight = Math.max(childLineHeight, lineHeight);
lineWidth += childLineWidth;
lineViews.add(childView);
}
//添加最後一行
if (i == childCount - 1) {
height += lineHeight;
mLineHeights.add(lineHeight);
mAllChildViews.add(lineViews);
}
}
setMeasuredDimension(widthSize,
modeHeight == MeasureSpec.AT_MOST ? height : heightSize);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 設置子View的位置
int left = getPaddingLeft();
int top = getPaddingTop();
// 行數
int lineNumber = mAllChildViews.size();
for (int i = 0; i < lineNumber; i++) {
List<View> lineViews = mAllChildViews.get(i);
int lineHeight = mLineHeights.get(i);
for (int j = 0; j < lineViews.size(); j++) {
View child = lineViews.get(j);
if (child.getVisibility() == View.GONE) {
continue;
}
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
int lc = left + lp.leftMargin;
int tc = top + lp.topMargin;
int rc = lc + child.getMeasuredWidth();
int bc = tc + child.getMeasuredHeight();
child.layout(lc, tc, rc, bc);
left += child.getMeasuredWidth() + lp.rightMargin
+ lp.leftMargin;
}
left = getPaddingLeft();
top += lineHeight;
}
}
/**
* 重寫generateLayoutParams()
*
* @param attrs attrs
* @return MarginLayoutParams
*/
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
public void setAdapter(FlowBaseAdapter adapter) {
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
mAdapter = null;
}
if (adapter == null) {
throw new NullPointerException("adapter is null");
}
this.mAdapter = adapter;
mAdapter.registerDataSetObserver(mDataSetObserver);
resetLayout();
}
/**
* 重新Layout子View
*/
protected final void resetLayout() {
this.removeAllViews();
int counts = mAdapter.getCounts();
mAdapter.addViewToList(this);
ArrayList<View> views = mAdapter.getViewList();
for (int i = 0; i < counts; i++) {
this.addView(views.get(i));
}
}
}
查看上述代碼得知:在構造函數中初始化一些必要的對象,這個後面再講。
然後在 onMeasure(int widthMeasureSpec, int heightMeasureSpec) 函數中利用 MeasureSpec 獲取Xml中設置的數值模式,進行測量並根據 MeasureSpec 的數值模式決定使用測量數值還是計算數值。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 獲取XML設置的大小和測量模式
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
//省略部分代碼
setMeasuredDimension(widthSize,
modeHeight == MeasureSpec.AT_MOST ? height : heightSize);
}
測量完成後重寫 onLayout(boolean changed, int l, int t, int r, int b) 函數,佈局子 View。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 設置子View的位置
int left = getPaddingLeft();
int top = getPaddingTop();
// 行數
int lineNumber = mAllChildViews.size();
for (int i = 0; i < lineNumber; i++) {
List<View> lineViews = mAllChildViews.get(i);
int lineHeight = mLineHeights.get(i);
for (int j = 0; j < lineViews.size(); j++) {
View child = lineViews.get(j);
if (child.getVisibility() == View.GONE) {
continue;
}
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
int lc = left + lp.leftMargin;
int tc = top + lp.topMargin;
int rc = lc + child.getMeasuredWidth();
int bc = tc + child.getMeasuredHeight();
child.layout(lc, tc, rc, bc);
left += child.getMeasuredWidth() + lp.rightMargin
+ lp.leftMargin;
}
left = getPaddingLeft();
top += lineHeight;
}
}
那麼 FlowLayout 中子View 的 MarginLayoutParams 是如何獲取的呢?
重寫 generateLayoutParams(AttributeSet attrs) 函數。
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
關於 generateLayoutParams(AttributeSet attrs) 有不瞭解的請看我另一篇博客:generateLayoutParams() 方法的作用
前面說了,在構造函數中初始化一些必要的對象:
protected DataSetObserver mDataSetObserver; private FlowBaseAdapter mAdapter;
這兩個是用來通知 FlowLayout 控件數據更新相關的對象。
FlowLayout Adapter封裝:
public abstract class CommonFlowAdapter<T> extends FlowBaseAdapter {
private List<T> mDatas;
private int mLayoutId;
private Context mContext;
public CommonFlowAdapter(Context context, List<T> datas, int layoutId) {
this.mContext = context;
this.mDatas = datas;
this.mLayoutId = layoutId;
}
@Override
public int getCounts() {
return mDatas.size();
}
@Override
public View getView(int position, ViewGroup parent) {
FlowHolder holder = new FlowHolder(mContext, parent, mLayoutId);
convert(holder, mDatas.get(position), position);
return holder.getConvertView();
}
public abstract void convert(FlowHolder holder, T item, int position);
public class FlowHolder {
private SparseArray<View> mViews;
private View mConvertView;
public FlowHolder(Context context, ViewGroup parent, int layoutId) {
this.mViews = new SparseArray<View>();
mConvertView = LayoutInflater.from(context).inflate(layoutId,
parent, false);
}
public FlowHolder setText(int viewId, CharSequence text) {
TextView tv = getView(viewId);
tv.setText(text);
return this;
}
public <T extends View> T getView(int viewId) {
View view = mViews.get(viewId);
if (view == null) {
view = mConvertView.findViewById(viewId);
mViews.put(viewId, view);
}
return (T) view;
}
/**
* 設置點擊事件
*
* @return
*/
public FlowHolder setOnClickListener(int viewId,
OnClickListener clickListener) {
getView(viewId).setOnClickListener(clickListener);
return this;
}
/**
* 設置條目的點擊事件
*
* @return
*/
public FlowHolder setItemClick(OnClickListener clickListener) {
mConvertView.setOnClickListener(clickListener);
return this;
}
public View getConvertView() {
return mConvertView;
}
}
}
FlowLayout 講到這裏就完結了,如果對你有幫助,那就幫我點個贊吧。