Android自定义控件-流式布局(FlowLayout)

流布局,相信大家都知道,就是依次展示,每行展示的个数不定,此行不能展示出末尾的那个item,自动换行,我简单写了一个,下面是我的效果图,大家看一下,是否是自己要找的效果
Android自定义控件-流式布局(FlowLayout)_第1张图片
下面贴出我的自定义flowLayout布局

/**
 * 打造一款具有滑动,并且不同高度的view会居中显示的流式布局
 */

public class FlowLayout extends ViewGroup implements FlowNotification {

    private static final String TAG = "yaya";
    //主要用来处理滑动用的,正直才可以滑动的
    protected int verticalOffset;

    protected int width, height;

    private LineDes row = new LineDes();

    protected int totalHeight;

    //用来存储每一行的高度,主要让子类去实现几行的流式布局
    protected SparseArray<Float> heightLines = new SparseArray<>();

    private FlowAdapter flowAdapter;

    private boolean isLineCenter;

    private static final boolean default_line_center = false;

    @Override
    public void onChange() {
        if (flowAdapter != null) {
            addItemView(flowAdapter);
        }
    }

    //用来存储信息的
    private class LineDes {
        //当前行的顶点坐标
        float cuLineTop;
        //行高度以最高的view为基准
        int lineHeight;
        //一行收集到的item
        List<ItemView> views = new ArrayList<>();

        //用完了该行的信息后进行清除的操作
        public void clearLineDes() {
            if (views.size() > 0) {
                views.clear();
            }
            lineHeight = 0;
            cuLineTop = 0;
        }
    }

    private class ItemView {
        View view;
        int heightArea;

        public ItemView(View view, int heightArea) {
            this.view = view;
            this.heightArea = heightArea;
        }
    }

    public FlowLayout(Context context) {
        this(context, null);
    }

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

    public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.FlowLayout);
        isLineCenter = array.getBoolean(R.styleable.FlowLayout_is_line_center, default_line_center);
        array.recycle();
        initArgus(context, attrs);
    }

    protected void initArgus(Context context, AttributeSet attrs) {
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
        if (widthMode == MeasureSpec.EXACTLY) {
            width = measureWidth;
        } else {
            //以实际屏宽为标准
            width = getContext().getResources().getDisplayMetrics().widthPixels;
        }

        Log.d(TAG, "屏高:" + getContext().getResources().getDisplayMetrics().heightPixels);

        int left = getPaddingLeft();
        int right = getPaddingRight();
        int top = getPaddingTop();
        int bottom = getPaddingBottom();
        int count = getChildCount();
        //整个view占据的高度
        totalHeight = 0;
        //当前行的高度
        int lineHeight = 0;
        int start = left;
        //当前行的起点,这个是换行的依据
        int cuLineStart = start;
        int end = width - right;
        int lineIndex = 0;
        //水平方向可用的宽度,主要考虑内边距
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();
            MarginLayoutParams marginLayoutParams = (MarginLayoutParams) child.getLayoutParams();
            int childWidthArea = childWidth + marginLayoutParams.leftMargin + marginLayoutParams.rightMargin;
            int childHeightArea = childHeight + marginLayoutParams.topMargin + marginLayoutParams.bottomMargin;
            //如果当前行的起点再加上当前child的宽度还小于end坐标时
            if (cuLineStart + childWidthArea <= end) {
                cuLineStart += childWidthArea;
                lineHeight = Math.max(lineHeight, childHeightArea);
                heightLines.put(lineIndex, (float) lineHeight);
            } else {//换行了
                lineIndex++;
                cuLineStart = start;
                //换行的时候需要加上上一行的行高
                totalHeight += lineHeight;
                lineHeight = childHeightArea;
                heightLines.put(lineIndex, (float) lineHeight);
                cuLineStart += childWidthArea;
            }
            if (i == getChildCount() - 1) {
                totalHeight += lineHeight;
            }
        }
        totalHeight = totalHeight + top + bottom;

        calculateHeight(heightMode, measureHeight, totalHeight);
    }

    private void calculateHeight(int heightMode, int measureHeight, int totalHeight) {
        if (heightMode == MeasureSpec.EXACTLY) {
            height = measureHeight;
            Log.d(TAG, "规则的");
        } else {
            //以实际屏高为标准
            Log.d(TAG, "不规则的");//这里就去
            height = totalHeight;
        }

        Log.d(TAG, "totalHeight:" + totalHeight);
        Log.d(TAG, "height:" + height);
        Log.d(TAG, "verticalOffset:" + verticalOffset);
        setMeasuredDimension(width, height);
    }

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

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int left = getPaddingLeft();
        int right = getPaddingRight();
        //左边的起始坐标
        int start = left;
        int cuLineStart = start;
        int end = width - right;
        int cuLineTop = getPaddingTop();
        row.cuLineTop = cuLineTop;
        //当前行的高度
        int lineHeight = 0;
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();
            MarginLayoutParams marginLayoutParams = (MarginLayoutParams) child.getLayoutParams();
            int childWidthArea = childWidth + marginLayoutParams.leftMargin + marginLayoutParams.rightMargin;
            int childHeightArea = childHeight + marginLayoutParams.topMargin + marginLayoutParams.bottomMargin;
            if (cuLineStart + childWidthArea <= end) {
                child.layout(cuLineStart, cuLineTop, cuLineStart + childWidth, cuLineTop + childHeight);
                cuLineStart += childWidthArea;
                lineHeight = Math.max(lineHeight, childHeightArea);
                //当前行的高度保存的是该行最高的高度
                row.lineHeight = lineHeight;
                row.views.add(new ItemView(child, childHeightArea));
            } else {//换行
                //对上一行没居中显示的item进行修正
                if (isLineCenter) {
                    formatAboveLine();
                }
                cuLineStart = start;
                cuLineTop += lineHeight;
                child.layout(cuLineStart, cuLineTop, cuLineStart + childWidth, cuLineTop + childHeight);
                cuLineStart += childWidthArea;
                lineHeight = childHeightArea;
                //换行的item也需要加到行信息中
                row.lineHeight = lineHeight;
                row.views.add(new ItemView(child, childHeightArea));
                row.cuLineTop = cuLineTop;
            }
            if (i == getChildCount() - 1) {
                if (isLineCenter) {
                    formatAboveLine();
                }
            }
        }
    }

    private void formatAboveLine() {
        List<ItemView> views = row.views;
        for (int i = 0; i < views.size(); i++) {
            View view = views.get(i).view;
            ItemView itemView = views.get(i);
            //如果是当前行的高度大于了该view的高度话,此时需要重新放该view了
            if (itemView.heightArea < row.lineHeight) {
                float viewTop = row.cuLineTop + (row.lineHeight - view.getMeasuredHeight()) / 2;
                view.layout(view.getLeft(), (int) viewTop, view.getRight(),
                        (int) viewTop + view.getMeasuredHeight());
            }
        }
        row.clearLineDes();
    }

    //采用适配器模式
    public void setAdapter(FlowAdapter flowAdapter) {
        if (flowAdapter == null) {
            return;
        }
        this.flowAdapter = flowAdapter;
        flowAdapter.setFlowNotification(this);
        addItemView(flowAdapter);
    }

    private void addItemView(FlowAdapter flowAdapter) {
        removeAllViews();
        for (int i = 0; i < flowAdapter.getCount(); i++) {
            View view = View.inflate(getContext(), flowAdapter.generateLayout(i), null);
            flowAdapter.getView(i, view);
            addView(view, new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
        }
    }
}

FlowLayout的子项展示时的样式,在res下的values下创建一个attrs.xml项,内容如下


<resources>
    <declare-styleable name="FlowLayout">
        <attr name="is_line_center" format="boolean" />
    declare-styleable>
    <declare-styleable name="LineFlowLayout">
        <attr name="flow_line_count" format="integer" />
    declare-styleable>

resources>

同时与FlowLayout对应的adapter我也封装了一下,代码如下


public abstract class FlowAdapter<T extends Object> {
    protected Context context;

    private List<T> list;

    private FlowNotification flowNotification;

    public void setFlowNotification(FlowNotification flowNotification) {
        this.flowNotification = flowNotification;
    }

    public FlowAdapter(Context context, List<T> list) {
        this.context = context;
        this.list = list;
    }

    public void setList(List<T> list) {
        this.list.clear();
        this.list.addAll(list);
    }

    public int getCount() {
        return list.size();
    }

    /**
     * 子类需要的item布局
     *
     * @return
     */
    public abstract int generateLayout(int position);

    public void getView(int position, View parent) {
        getView(list.get(position), parent,position);
    }

    public abstract void getView(T o,View parent,int position) ;

    public void notifyDataChanged() {
        if (flowNotification != null) {
            flowNotification.onChange();
        }
    }

//    protected abstract void getView(T o, View parent);
}

还需要一个接口提供notifyDataChanged,代码如下


public interface FlowNotification {
    void onChange();
}

子布局flow_item.xml


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

    <TextView
        android:id="@+id/flow_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="8dp"
        android:gravity="center"
        android:paddingBottom="5dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingTop="5dp"
        android:textColor="#333"
        android:textSize="14sp"
        android:background="@drawable/flow_item"/>

LinearLayout>

自定义子布局的背景,flow_item.xml

<shape android:shape="rectangle"
    xmlns:android="http://schemas.android.com/apk/res/android" >
    <corners android:radius="8dp"/>
    <solid android:color="@color/white"/>
    <stroke android:color="#cccccc" android:width="1dp"/>
shape>

布局应用,activity的布局;


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="cn.xu.test.FlowLayoutActivity">

    <cn.xu.test.view.FlowLayout
        android:id="@+id/flowlayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

LinearLayout>

activity里用于数据展示的adapter,代码如下:

public class FlowLayoutAdapter extends FlowAdapter<String> {

    public FlowLayoutAdapter(Context context, List<String> list) {
        super(context, list);
    }

    @Override
    public int generateLayout(int position) {
        return R.layout.flow_item;
    }

    @Override
    public void getView(String o, View parent, int position) {
        final TextView text = parent.findViewById(R.id.flow_text);
        text.setText(o);
    }
}

activity具体操作,代码如下:

public class FlowLayoutActivity extends AppCompatActivity {

    FlowLayout flowLayout;
    FlowLayoutAdapter flowLayoutAdapter;
    List<String> list;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_flow_layout);
        flowLayout = findViewById(R.id.flowlayout);
        initData();
        flowLayoutAdapter = new FlowLayoutAdapter(this, list);
        flowLayout.setAdapter(flowLayoutAdapter);
    }

    /**
     * 添加数据
     * 
     */
    private void initData() {
        list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            if (i % 2 == 0) {
                list.add(i + "是偶数");
            } else {
                if (i % 3 == 0)
                    list.add(i + "是3的倍数");
                else {
                    list.add("10以内既不是偶数也不是3的倍数的值:" + i);
                }
            }
        }
    }
}

好,到了这里就完成了我上面展示的UI,如有哪里不妥,大家可以留下想法,共同学习,共同进步

你可能感兴趣的:(Android自定义控件-流式布局(FlowLayout))