自定义LabelView热门标签

现在各大主流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<List<View>> mAllViews = new ArrayList<>();
/**
 * 每一行的高度
 */
List<Integer> 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<View> 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:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="5dp"
    android:background="@drawable/tv_bg"
    android:textColor="#5bc4ed"
    android:text="helloworld">

</TextView>

activity_main.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.bll.flowlayoutdemo.LabelView
        android:id="@+id/flow_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#E5E5F5">


    </com.bll.flowlayoutdemo.LabelView>

</LinearLayout>

以上是自定义view的完整代码,希望对各位有所帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值