自定义流式布局FlowLayout

来源:互联网 发布:企业数据备份管理制度 编辑:程序博客网 时间:2024/04/29 03:08

描述:
每一行摆放多个子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
原创粉丝点击