自定义瀑布流控件WaterfallFlowLayout

效果如下图所示:
自定义瀑布流控件WaterfallFlowLayout_第1张图片
实现效果:每个子项的宽度相同高度可能不同的布局
实现思路:
1.由于项宽度相同,则高度需要根据图片比例进行计算得出
2.用一个数组来存储每一列的总高度,将子控件添加到总高度最小的那一列的后面
3.通过onMeasure()方法计算出控件的高度,控件的高度为所有列高中最大的值,并自定义LayoutParams类,将子控件的left,top,right,bottom封装到layoutParams参数中,以便在onLayout方法中获取参数对子控件进行布局,最后通过setMeasuredDimension()方法来设置控件测量大小
4.在onLayout()方法中对子控件进行布局

一.自定义控件代码

public class WaterfallFlowLayout extends ViewGroup {
    private int column = 4;//显示的列数
    private int horizontalSpace=10;//列间距
    private int verticalSpace=10;//行间距
    private int childWidth;//子控件的宽度
    private int[] columnHeight=new int[column];//存储每一列的高度
    public WaterfallFlowLayout(Context context) {
        this(context,null);
    }

    public WaterfallFlowLayout(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public WaterfallFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray=context.obtainStyledAttributes(attrs,R.styleable.WaterfallFlowLayout);
        column=typedArray.getInteger(R.styleable.WaterfallFlowLayout_column,3);
        horizontalSpace=typedArray.getDimensionPixelSize(R.styleable.WaterfallFlowLayout_horizontalSpace,0);
        verticalSpace=typedArray.getDimensionPixelSize(R.styleable.WaterfallFlowLayout_verticalSpace,0);
        typedArray.recycle();
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MyLayoutParams(getContext(),attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        //测量之后的宽度高度
        int newWidth = 0;
        int newHeight = 0;

        if (widthMode == MeasureSpec.EXACTLY) {
            newWidth = widthSize;
            newHeight = heightSize;
        } else {
            //根据子控件的高度宽度测量出自身的宽高
            measureChildren(widthMeasureSpec,heightMeasureSpec);
            //计算子控件的宽度
            childWidth = (widthSize-(column-1)*horizontalSpace) / column;
            int childNum = getChildCount();
            if (childNum < column) {
                newWidth = childWidth * childNum;
            } else {
                newWidth = widthSize;
            }
            for (int i = 0; i < childNum; i++) {
                View childView = getChildAt(i);
                //根据子控件的宽度计算出子控件应该显示的高度(需要等比例缩放)
                int width=childView.getMeasuredWidth();
                int height=childView.getMeasuredHeight();
                float scale= (float) (height*1.0 / width);
                int childHeight = (int) (childWidth *scale);
                //获取所有列当中高度最小的位置,放置子控件
                int column=getMinHeight();
                MyLayoutParams params= (MyLayoutParams) childView.getLayoutParams();
                params.left=column*(childWidth+horizontalSpace);
                params.top=columnHeight[column]+verticalSpace;
                params.right=params.left+childWidth;
                params.bottom=params.top+childHeight;
                //对添加的列的高度进行累加
                columnHeight[column]+=childHeight+verticalSpace;
            }
            newHeight=getMaxHeight();//获取整个列中最大值
        }
        setMeasuredDimension(newWidth, newHeight);
    }
    //获取所有行中列高度最小的位置
    private int getMinHeight(){
        int minColumn=0;
        for(int i=1;iif(columnHeight[i]return minColumn;
    }
    //获取最大的高度
    private int getMaxHeight(){
        int maxHeight=columnHeight[0];
        for(int i=1;iif(columnHeight[i]>maxHeight){
                maxHeight=columnHeight[i];
            }
        }
        return maxHeight;
    }
    private void clearTop() {
        for (int i = 0; i < columnHeight.length; i++) {
            columnHeight[i] = 0;
        }
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //对子控件进行布局
        int childNum=getChildCount();
        clearTop();
        for(int i=0;ipublic static class MyLayoutParams extends ViewGroup.LayoutParams{
        public int left;
        public int top;
        public int right ;
        public int bottom ;
        public MyLayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
        }
    }

二.布局中使用

<ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.example.qdq.uidemo.WaterfallFlowLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            app:horizontalSpace="5dp"
            app:verticalSpace="5dp">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:scaleType="centerCrop"
                android:src="@drawable/one" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:scaleType="centerCrop"
                android:src="@drawable/two" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:scaleType="centerCrop"
                android:src="@drawable/one" />
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:scaleType="centerCrop"
                android:src="@drawable/one" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:scaleType="centerCrop"
                android:src="@drawable/one" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:scaleType="centerCrop"
                android:src="@drawable/one" />
        com.example.qdq.uidemo.WaterfallFlowLayout>
    ScrollView>

三自定义属性文件代码


<resources>
    <declare-styleable name="WaterfallFlowLayout">
        <attr name="column" format="integer"/>
        <attr name="horizontalSpace" format="dimension"/>
        <attr name="verticalSpace" format="dimension"/>
    declare-styleable>
resources>

注意:本例中计算比例存在问题,当图片宽高大于屏幕宽高时,通过getMeasuredWidth()获取的宽度为屏幕宽度并非图片真实宽度,获取的高度也一样,导致比例计算不正确,导致显示异常

你可能感兴趣的:(View相关知识)