android自定义ViewGroup---设计简单的FlowLayout搜索条目容器

马上周末了,好开心!

今天手头事情不多,撸一个自定义的viewgroup吧,简单实现瀑布流效果的热门搜索条目:
最终效果如下:


android自定义ViewGroup---设计简单的FlowLayout搜索条目容器_第1张图片

如果对自定义viewgroup的一些基本概念不是很熟,可以先看看大神的文章,写的很细致:
自定义viewgroup入门——Hongyang大神



下面就是我们这个viewgroup的实现步骤了:

  • 首先,写一个ViewGroup的子类,重写generateLayoutParams(),这一步是为我们的ViewGroup指定一个LayoutParams:

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);//直接使用系统的MarginLayoutParams
    }
  • 接下来,爸爸要根据儿子们的需求买地了。就是onMeasure(),需要量测viewgroup的尺寸了

计算原理:
- 把每一个child的尺寸的width累加,累加之前先计算,如果加上这个
child的width会超过系统量测的最大width,就换行;

- 每一行的行高是取这一行的所有儿子中最高的;

- 最后的viewgroup的宽取所有行宽最大的,高是所有的行高累加起来;
@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(widthMeasureSpec);

        int width = 0;//如果设置width为wrap_content时的width
        int height = 0;//同理

        int lineWidth = 0;//记录每一行的宽度, width最后取最大的值
        int lineHeight = 0;//记录每一行的高度,height不断累加

        //遍历每一个view,计算容器的总尺寸
        for (int i = 0; i < getChildCount(); i++) {

            View childView = getChildAt(i);
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
            MarginLayoutParams childParams = (MarginLayoutParams) childView.getLayoutParams();

            int childWidth = childView.getMeasuredWidth() + childParams.leftMargin + childParams.rightMargin;
            int childHeight = childView.getMeasuredHeight() + childParams.topMargin + childParams.bottomMargin;

            if (lineWidth + childWidth > widthSize) {//如果当前控件和当前这一行的宽度之和大于widthSize,换行
                width = Math.max(lineWidth, childWidth);
                lineWidth = childWidth;//重新记录lineWidth,初始为childWidth
                height += childHeight;
                lineHeight = childHeight;
            } else {//不需要换行时
                lineWidth += childWidth;
                lineHeight = Math.max(lineHeight, childHeight);
            }
            if (i == getChildCount() - 1) {
                width = Math.max(width, lineWidth);
                height += lineHeight;
            }
        }
        //如果是wrap_content,就用计算的值,否则就用系统量测的值,至于另外的UNSPECIFIED先不考虑,毕竟用的少
        setMeasuredDimension((MeasureSpec.EXACTLY == widthMode) ? widthSize : width,
                (MeasureSpec.EXACTLY == heightMode) ? heightSize : height);
    }
  • onLayout(),为每一个儿子划好地产,就是分配它们的绘制区域

    • 这一步的思路基本和onMeasure()中一样,但是定义两个list,mLines里面有所有的行,每一行的list里又存储了里面的child
private ArrayList> mLines = new ArrayList<>();//二级List,里面有所有的view
    private ArrayList mLineHeight = new ArrayList<>();//记录每一行的行高
      for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();
            MarginLayoutParams childParams = (MarginLayoutParams) child.getLayoutParams();
            //先处理需要换行的情况
            if (lineWidth + childWidth + childParams.leftMargin + childParams.rightMargin > width) {
                mLineHeight.add(lineHeight);
                mLines.add(lineViews);
                lineWidth = 0;
                lineViews = new ArrayList<>();
            }
            //不用换行时,累加
            lineWidth = childWidth + childParams.leftMargin + childParams.rightMargin + lineWidth;
            lineHeight = Math.max(lineHeight, childParams.topMargin + childParams.bottomMargin + childHeight);
            lineViews.add(child);
        }
        //记录最后一行
        mLineHeight.add(lineHeight);
        mLines.add(lineViews);
 - 再根据mLines进行两次for循环,为每一个child定位
        int left = 0, top = 0;
        //根据行数和每行的view个数遍历所有view
        for (int i = 0; i < mLines.size(); i++) {
            lineViews = mLines.get(i);
            lineHeight = mLineHeight.get(i);
            //遍历当前行所有的view
            for (int j = 0; j < lineViews.size(); j++) {
                View childView = lineViews.get(j);
                if (childView.getVisibility() == GONE) continue;
                MarginLayoutParams childParams = (MarginLayoutParams) childView.getLayoutParams();
                int childLeft = left + childParams.leftMargin;
                int childTop = top + childParams.topMargin;
                int childBottom = childTop + childView.getMeasuredHeight();
                int childRight = childLeft + childView.getMeasuredWidth();
                childView.layout(childLeft, childTop, childRight, childBottom);
                left += childView.getMeasuredWidth() + childParams.leftMargin + childParams.rightMargin;
            }
            left = 0;
            top += lineHeight;
        }
  • viewgroup的子类的基本方法实现完成,先在xml中引用一下



    
        

        
        
    

跑一下效果如下,基本可以实现了(为了方便观察效果,textview中加了点shape属性)


android自定义ViewGroup---设计简单的FlowLayout搜索条目容器_第2张图片
  • 那么接下来就比较简单了,实现类似RadioGroup的功能,将childView上的文字加到edittext上就行了

    • 先定义一个接口:
    public interface OnChildSelectedListener{
        void onChildSelected(String content,View child);
    }
 - 在我们自定义的子类中,得到这个接口的实例
private OnChildSelectedListener mOnChildSelectedListener;

    public void setOnChildSelectedListener(OnChildSelectedListener onChildSelectedListener) {
        this.mOnChildSelectedListener = onChildSelectedListener;
    }
 - 回到onLayout()中,在第一个for循环里,加上点击事件:
            final int finalI = i;
            child.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (v.getTag() == null) v.setTag(true);
                    //selectedIndex : 当前选中的child坐标,全局变量
                    if (selectedIndex != finalI) {//本次点击view不是上次选中的view
                        if (selectedIndex != -1) {
                            //将上一个被选中的child选中效果拿掉
                            getChildAt(selectedIndex).setBackground(getResources().getDrawable(R.drawable.tv_bg));
                        }
                        v.setBackground(getResources().getDrawable(R.drawable.tv_bg_selcected));//设定选中效果
                        selectedIndex = finalI;
                        TextView tv = (TextView) v;
                        childSelcted = (String) tv.getText();
                    } else {//点击的是已经选中的view,取消选中效果
                        selectedIndex = -1;
                        v.setBackground(getResources().getDrawable(R.drawable.tv_bg));
                        childSelcted = null;
                    }
                        //把当前child上的文字传过去
                        mOnChildSelectedListener.onChildSelected(childSelcted,v);
                }
            });
 - 来到activity中,实现这个接口,并将被选中的文字设置到edittext中:
flowLayout.setOnChildSelectedListener(new FlowLayout.OnChildSelectedListener() {
            @Override
            public void onChildSelected(String content, View child) {
                editText.setText(content);
            }
        });

搞定。

老规矩,github上的代码,点击查看

有什么bug,请及时指出,大家一起来解决 :)

你可能感兴趣的:(android自定义ViewGroup---设计简单的FlowLayout搜索条目容器)