现在各大主流app都有搜索功能,提到搜索不得不提热门标签,热门标签样式基本一致,今天带大家实现一个自定义的热门标签view.
先上效果图:
自动判断当前行是否可显示完整,如果不完整自动换行显示。
1.创建LabelView继承ViewGroup
2.复写三个构造方法
public LabelView(Context context) {
this(context, null);
}
public LabelView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LabelView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
3.我们需要在onMeasure中完成子view的测量
我们都知道,子View的测量大小是由父控件的MeasureSpec和子View的LayoutParams一起决定,所以分为两种情况考虑:
1.当父View为wrap_content时,对应MeasureSpec.AT_MOST,需要我们自己计算子view尺寸。
2.当子View为match_parent或者具体值,对应MeasureSpec.EXACTLY,子View尺寸为父控件大小。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
//wrap_content
int width = 0;
int height = 0;
//记录每一行的宽度和高度
int lineWidth = 0;
int lineHeight = 0;
int cCount = getChildCount();
for (int i = 0; i < cCount; i++) {
//获取子view
View child = getChildAt(i);
//测量子View的宽和高
measureChild(child, widthMeasureSpec, heightMeasureSpec);
//得到子View的layoutParams
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//子View占据的宽度
int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
//子View占据的高度
int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight()) {
//对比得到最大的宽度
width = Math.max(width, lineWidth);
//重置lineWidth
lineWidth = childWidth;
//记录行高
height += lineHeight;
//重置lineHeight
lineHeight = childHeight;
} else {
//叠加行宽
lineWidth += childWidth;
//得到当前行最大高度
lineHeight = Math.max(lineHeight, childHeight);
}
//最后一个控件
if (i == cCount - 1) {
width = Math.max(lineWidth, width);
height += lineHeight;
}
}
setMeasuredDimension(
modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width + getPaddingLeft() + getPaddingRight(),
modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop() + getPaddingBottom());
}
4.重写onLayout方法
先得到行数以及每行的view的数量,然后循环为子view设置layout方法,对子view进行布局。
代码中已经考虑到了Margin和Padding属性,可以直接使用。
/**
* 存储所有的子view,里面的list表示每行的view,外面的list表示行数
*/
List> mAllViews = new ArrayList<>();
/**
* 每一行的高度
*/
List mLineHeight = new ArrayList<>();
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mAllViews.clear();
mLineHeight.clear();
//当前ViewGroup的宽度
int width = getWidth();
int lineWidth = 0;
int lineHeight = 0;
List lineViews = new ArrayList<>();
int cCount = getChildCount();
for (int i = 0; i < cCount; i++) {
View child = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
//需要换行
if (lineWidth + childWidth + lp.leftMargin + lp.rightMargin > width - getPaddingLeft() - getPaddingRight()) {
//记录lineHeight
mLineHeight.add(lineHeight);
//记录当前行的Views
mAllViews.add(lineViews);
//重置行和宽
lineWidth = 0;
lineHeight = childHeight + lp.topMargin + lp.bottomMargin;
//重置lineViews集合
lineViews = new ArrayList<>();
}
lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
lineHeight = Math.max(lineHeight, childHeight + lp.topMargin + lp.bottomMargin);
lineViews.add(child);
}
//处理最后一行
mLineHeight.add(lineHeight);
mAllViews.add(lineViews);
//设置子View的位置
int left = getPaddingLeft();
int top = getPaddingTop();
//行数
int lineNum = mAllViews.size();
for (int i = 0; i < lineNum; i++) {
//每行的view
lineViews = mAllViews.get(i);
//每行的高度
lineHeight = mLineHeight.get(i);
for (int j = 0; j < lineViews.size(); j++) {
View child = lineViews.get(j);
if (child.getVisibility() == 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();
//为子view布局
child.layout(lc, tc, rc, bc);
left += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
}
left = getPaddingLeft();
top += lineHeight;
}
}
5.选择合适的LayoutParams,因为该view只涉及到边距,所以我们选择MarginLayoutParams,重写generateLayoutParams方法,生成MarginLayoutParams;
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
OK,以上是LabelView的完整代码。
下面是MainActivity里面的代码
public class MainActivity extends AppCompatActivity { private String[] mVals = new String[]{ "android", "button", "LinearLayout", "android world", "button", "LinearLayout image", "android studio", "button", "Hello world", "android", "textView", "LinearLayout", "android", "button imageView", "LinearLayout", "android", "button", "LinearLayout" }; private LabelView mFlowLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mFlowLayout = findViewById(R.id.flow_layout); LayoutInflater mInflater = LayoutInflater.from(this); for (int i = 0; i < mVals.length; i++) { TextView tv = (TextView) mInflater.inflate(R.layout.tv, mFlowLayout, false); tv.setText(mVals[i]); mFlowLayout.addView(tv); } } }
涉及到的布局
tv.xml:
activity_main.xml:
以上是自定义view的完整代码,希望对各位有所帮助。