流布局,相信大家都知道,就是依次展示,每行展示的个数不定,此行不能展示出末尾的那个item,自动换行,我简单写了一个,下面是我的效果图,大家看一下,是否是自己要找的效果
下面贴出我的自定义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,如有哪里不妥,大家可以留下想法,共同学习,共同进步