Android 实现FlowLayout流式布局(热门标签)
作者:互联网
先上效果图:
接着看代码实现:
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 讲到这里就完结了,如果对你有帮助,那就帮我点个赞吧。
标签:MeasureSpec,int,流式,FlowLayout,lp,child,Android,public,View 来源: https://blog.csdn.net/wjr1949/article/details/70242570