Android 实现FlowLayout流式布局(热门标签)
先上效果图:
接着看代码实现:
public class FlowLayout extends ViewGroup {
protected DataSetObserver mDataSetObserver;
private FlowBaseAdapter mAdapter;
private final List<List> mAllChildViews;
private final List 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 lineViews = new ArrayList();
for (int i = 0; i (widthSize - getPaddingRight() - getPaddingLeft())) {
// 换行
height += lineHeight;
lineWidth = childLineWidth;
// 添加子View到集合
mAllChildViews.add(lineViews);
mLineHeights.add(lineHeight);
lineViews = new ArrayList();
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 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;
}
}
@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();
}
protected final void resetLayout() {
this.removeAllViews();
int counts = mAdapter.getCounts();
mAdapter.addViewToList(this);
ArrayList 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 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 extends FlowBaseAdapter {
private List mDatas;
private int mLayoutId;
private Context mContext;
public CommonFlowAdapter(Context context, List 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 mViews;
private View mConvertView;
public FlowHolder(Context context, ViewGroup parent, int layoutId) {
this.mViews = new SparseArray();
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 getView(int viewId) {
View view = mViews.get(viewId);
if (view == null) {
view = mConvertView.findViewById(viewId);
mViews.put(viewId, view);
}
return (T) view;
}
public FlowHolder setOnClickListener(int viewId,
OnClickListener clickListener) {
getView(viewId).setOnClickListener(clickListener);
return this;
}
public FlowHolder setItemClick(OnClickListener clickListener) {
mConvertView.setOnClickListener(clickListener);
return this;
}
public View getConvertView() {
return mConvertView;
}
}
}
FlowLayout 讲到这里就完结了,如果对你有帮助,那就帮我点个赞吧。
作者:长安三日
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341