自定义流式布局FlowLayout

描述:
每一行摆放多个子View时,因为每个View的宽度和高度可能不同,需要在摆放之时按实际剩余宽度布置子View的摆放,如果当前行不够空间摆放,则需要新创建一行摆放它,而父容器FlowLayout的高度需要计算出所有行Line的高度之和来确定.
效果图:
这里写图片描述
实现要点:

  1. onMeasure()方法中计算出每一行的最大可用宽度
//1,计算宽高
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int maxWidth = width - getPaddingLeft() - getPaddingRight();//最大可用宽度

2, 定义个管理类Line: a,用于管理新进来的子View是否可添加进当前行canAddView(),如果可以添加,则添加进集合addView(),同时记录已使用了的宽度usedWidth,及当前最高的子View的高度mHeight(用于确定该行Line的高度); b,布局最终添加进集合的每一个子View在当前行的摆放位置onLayout();

public class Line {
        private int usedWidth;
        private int paddingWidth;
        private int mMaxWidth;
        private List<View> mViews = new ArrayList<>();
        private int mHeight;
        public Line(int paddingWidth, int maxWidth) {
            this.paddingWidth = paddingWidth;
            mMaxWidth = maxWidth;
        }
        public boolean canAddView(View child) {
            if (mViews.size() == 0) {
                return true;
            }
            return usedWidth + child.getMeasuredWidth() + paddingWidth <= mMaxWidth;
        }
        public void addView(View child) {
            int childWidth = child.getMeasuredWidth();
            if (mViews.size() > 0) {
                usedWidth += paddingWidth;    //当添加进一个子View后,就需要加上每个子View间的间距了
            }
            mViews.add(child);
            usedWidth += childWidth;
            mHeight = mHeight > child.getMeasuredHeight() ? mHeight : child.getMeasuredHeight();    //用于确定每一行的高度
        }
    .......

}

3, 创建成员变量mCurrentLine记录当前行Line的引用(相当于指针),及集合mLines用于存储每一个new出来的Line, 在onMeasure()方法中遍历每一个孩子,循环判断mCurrentLine是否为空,如果为空就创建Line,并addView(child),如果不为空,则分两种情况判断:mCurrentLine.canAddView(child)返回为true,则直接添加;返回为false,需要重新 new Line(),同时当前行的引用mCurrentLine指向新创建的Line,再addView(child),同时添加进集合mLines中 , 每次添加完成后,再测量一下孩子,给孩子的宽高赋值:

//3,测量孩子,并添加到Line中
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            //将孩子添加到Line中
            if (mCurrentLine == null) {
                mCurrentLine = new Line(getResources().getDimensionPixelSize(R.dimen.flow_line_padding), maxWidth);
                mCurrentLine.addView(child);
                mLines.add(mCurrentLine);
            } else {
                if (mCurrentLine.canAddView(child)) {
                    mCurrentLine.addView(child);
                } else {
                    mCurrentLine = new Line(getResources().getDimensionPixelSize(R.dimen.flow_line_padding), maxWidth);
                    mCurrentLine.addView(child);
                    mLines.add(mCurrentLine);
                }
            }
            //测量孩子
            measureChild(child, widthMeasureSpec, heightMeasureSpec);    //对孩子的宽高不做限制,
        }

4, 测量自己
宽度使用期望的宽度width, 高度需要遍历mLines集合,累加每一Line的mHeight:

//4,测量自己
        int measuredHeight = getPaddingTop() + getPaddingBottom();//将高度的内边距计算在内
        for (int i = 0; i < mLines.size(); i++) {
            measuredHeight += mLines.get(i).mHeight + mVerticalSpace;    //将每一行的垂直间距计算在内
        }
        setMeasuredDimension(width, measuredHeight);

5, 根据 left 和 top 布局每一行,及每一行的中的孩子的位置:

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int top = t + getPaddingTop();    //将内边距计算在内
        int left = l + getPaddingLeft();
        for (int i = 0; i < mLines.size(); i++) {
            Line line = mLines.get(i);
            line.onLayout(left, top);
            top += line.mHeight + mVerticalSpace;    //每循环一次将top增加line.mHeight 及 每行的垂直间距 的高度
        }
    }
 Line类中的onLayout,需要根据已使用的宽度usedWidth计算出最终剩余使用不了的宽度, 将这部分宽度按照mViews.size()的个数(即每一行的孩子个数)平均分配到每一个child的宽度上去,调用child.measure()方法按照指定的宽度重新测量后,再对孩子布局位置child.layout(); 
 public void onLayout(int l, int t) {
            int left = l;
            for (int i = 0; i < mViews.size(); i++) {
                View child = mViews.get(i);
                //计算出平均添加到每个child上的多余宽度
                int avg = (mMaxWidth - usedWidth) / mViews.size();
                //按照指定的值重新测量孩子
                child.measure(MeasureSpec.makeMeasureSpec(child.getMeasuredWidth() + avg,MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(),MeasureSpec.EXACTLY));
                int right = left + child.getMeasuredWidth();
                int bottom = t + child.getMeasuredHeight();
                child.layout(left, t, right, bottom);
                left = right + paddingWidth;
            }
        }

6, 因为onMeasure()方法会被重复调用多次, 所以需要在onMeasure()方法每次被调用之前将集合清空, 不然会出现大片空白的区域:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //因为会多次调用measure()方法,所以方法开始前先清除child
        mLines.clear();
        mCurrentLine = null;
     .......
}

效果图实现代码

FlowLayout flowLayout = (FlowLayout) findViewById(R.id.flowlayout);
            mRandom = new Random();
            for (int i = 0; i < mDatas.length; i++) {
                    TextView view = new TextView(this);
                    view.setText(mDatas[i]);
                    //创建正常情况下背景
                    GradientDrawable gradientDrawable = new GradientDrawable();
                    gradientDrawable.setCornerRadius(getResources().getDimensionPixelSize(R.dimen.flow_gradient_radius));
                    int a = 255;
                    int r = 150 + mRandom.nextInt(100);
                    int g= 150 + mRandom.nextInt(100);
                    int b= 150 + mRandom.nextInt(100);
                    gradientDrawable.setColor(Color.argb(a, r, g, b));
                    //创建被点击时的背景
                    GradientDrawable gradientDrawable2 = new GradientDrawable();
                    gradientDrawable2.setCornerRadius(getResources().getDimensionPixelSize(R.dimen.flow_gradient_radius));
                    gradientDrawable2.setColor(Color.GRAY);
                    //设置图片选择器
                    StateListDrawable stateListDrawable = new StateListDrawable();
                    stateListDrawable.addState(new int[]{android.R.attr.state_pressed},gradientDrawable2);
                    stateListDrawable.addState(new int[]{},gradientDrawable);

                    view .setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                    Toast.makeText(MainActivity.this, ((TextView) v).getText(), Toast.LENGTH_SHORT).show();
                            }
                    });
                    //将背景设置到TextView中
                    view.setBackgroundDrawable(stateListDrawable);
                    view.setTextColor(Color.WHITE);
                    view.setPadding(5, 5, 5, 5);
                    view.setGravity(Gravity.CENTER);
                    view.setTextSize(14);

                    flowLayout.addView(view);//触发重绘
            }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值