1.效果如如下
2.自定义的布局管理器FlowLayoutManager
package com.zw.flowlayoutdemo;
import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
/**
* @Description:流失布局
*/
public class FlowLayoutManager extends RecyclerView.LayoutManager {
private static final String TAG = FlowLayoutManager.class.getSimpleName();
final FlowLayoutManager self = this;
protected int width, height;
private int left, top, right;
//最大容器的宽度
private int usedMaxWidth;
//竖直方向上的偏移量
private int verticalScrollOffset = 0;
public int getTotalHeight() {
return totalHeight;
}
//计算显示的内容的高度
protected int totalHeight = 0;
private Row row = new Row();
private List lineRows = new ArrayList<>();
//保存所有的Item的上下左右的偏移量信息
private SparseArray allItemFrames = new SparseArray<>();
public FlowLayoutManager() {
//设置主动测量规则,适应recyclerView高度为wrap_content
setAutoMeasureEnabled(true);
}
//每个item的定义
public class Item {
int useHeight;
View view;
public void setRect(Rect rect) {
this.rect = rect;
}
Rect rect;
public Item(int useHeight, View view, Rect rect) {
this.useHeight = useHeight;
this.view = view;
this.rect = rect;
}
}
//行信息的定义
public class Row {
public void setCuTop(float cuTop) {
this.cuTop = cuTop;
}
public void setMaxHeight(float maxHeight) {
this.maxHeight = maxHeight;
}
//每一行的头部坐标
float cuTop;
//每一行需要占据的最大高度
float maxHeight;
//每一行存储的item
List- views = new ArrayList<>();
public void addViews(Item view) {
views.add(view);
}
}
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
//该方法主要用来获取每一个item在屏幕上占据的位置
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
Log.d(TAG, "onLayoutChildren");
totalHeight = 0;
int cuLineTop = top;
//当前行使用的宽度
int cuLineWidth = 0;
int itemLeft;
int itemTop;
int maxHeightItem = 0;
row = new Row();
lineRows.clear();
allItemFrames.clear();
removeAllViews();
if (getItemCount() == 0) {
detachAndScrapAttachedViews(recycler);
verticalScrollOffset = 0;
return;
}
if (getChildCount() == 0 && state.isPreLayout()) {
return;
}
//onLayoutChildren方法在RecyclerView 初始化时 会执行两遍
detachAndScrapAttachedViews(recycler);
if (getChildCount() == 0) {
width = getWidth();
height = getHeight();
left = getPaddingLeft();
right = getPaddingRight();
top = getPaddingTop();
usedMaxWidth = width - left - right;
}
for (int i = 0; i < getItemCount(); i++) {
Log.d(TAG, "index:" + i);
View childAt = recycler.getViewForPosition(i);
if (View.GONE == childAt.getVisibility()) {
continue;
}
measureChildWithMargins(childAt, 0, 0);
int childWidth = getDecoratedMeasuredWidth(childAt);
int childHeight = getDecoratedMeasuredHeight(childAt);
int childUseWidth = childWidth;
int childUseHeight = childHeight;
//如果加上当前的item还小于最大的宽度的话就增加,否则就换行
if (cuLineWidth + childUseWidth <= usedMaxWidth) {
itemLeft = left + cuLineWidth;
itemTop = cuLineTop;
Rect frame = allItemFrames.get(i);
if (frame == null) {
frame = new Rect();
}
frame.set(itemLeft, itemTop, itemLeft + childWidth, itemTop + childHeight);
allItemFrames.put(i, frame);
cuLineWidth += childUseWidth;
maxHeightItem = Math.max(maxHeightItem, childUseHeight);
row.addViews(new Item(childUseHeight, childAt, frame));
row.setCuTop(cuLineTop);
row.setMaxHeight(maxHeightItem);
} else {
//换行
formatAboveRow();
cuLineTop += maxHeightItem;
totalHeight += maxHeightItem;
itemTop = cuLineTop;
itemLeft = left;
Rect frame = allItemFrames.get(i);
if (frame == null) {
frame = new Rect();
}
frame.set(itemLeft, itemTop, itemLeft + childWidth, itemTop + childHeight);
allItemFrames.put(i, frame);
cuLineWidth = childUseWidth;
maxHeightItem = childUseHeight;
row.addViews(new Item(childUseHeight, childAt, frame));
row.setCuTop(cuLineTop);
row.setMaxHeight(maxHeightItem);
}
//不要忘了最后一行进行刷新下布局
if (i == getItemCount() - 1) {
formatAboveRow();
totalHeight += maxHeightItem;
}
}
totalHeight = Math.max(totalHeight, getVerticalSpace());
Log.d(TAG, "onLayoutChildren totalHeight:" + totalHeight);
fillLayout(recycler, state);
}
//对出现在屏幕上的item进行展示,超出屏幕的item回收到缓存中
private void fillLayout(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (state.isPreLayout() || getItemCount() == 0) { // 跳过preLayout,preLayout主要用于支持动画
return;
}
// 当前scroll offset状态下的显示区域
Rect displayFrame = new Rect(getPaddingLeft(), getPaddingTop() + verticalScrollOffset,
getWidth() - getPaddingRight(), verticalScrollOffset + (getHeight() - getPaddingBottom()));
//对所有的行信息进行遍历
for (int j = 0; j < lineRows.size(); j++) {
Row row = lineRows.get(j);
float lineTop = row.cuTop;
float lineBottom = lineTop + row.maxHeight;
//如果该行在屏幕中,进行放置item
// if (lineTop < displayFrame.bottom && displayFrame.top < lineBottom) {
List
- views = row.views;
for (int i = 0; i < views.size(); i++) {
View scrap = views.get(i).view;
measureChildWithMargins(scrap, 0, 0);
addView(scrap);
Rect frame = views.get(i).rect;
//将这个item布局出来
layoutDecoratedWithMargins(scrap,
frame.left,
frame.top - verticalScrollOffset,
frame.right,
frame.bottom - verticalScrollOffset);
}
// } else {
// //将不在屏幕中的item放到缓存中
// List
- views = row.views;
// for (int i = 0; i < views.size(); i++) {
// View scrap = views.get(i).view;
// removeAndRecycleView(scrap, recycler);
// }
// }
}
}
/**
* 计算每一行没有居中的viewgroup,让居中显示
*/
private void formatAboveRow() {
List
- views = row.views;
for (int i = 0; i < views.size(); i++) {
Item item = views.get(i);
View view = item.view;
int position = getPosition(view);
//如果该item的位置不在该行中间位置的话,进行重新放置
if (allItemFrames.get(position).top < row.cuTop + (row.maxHeight - views.get(i).useHeight) / 2) {
Rect frame = allItemFrames.get(position);
if (frame == null) {
frame = new Rect();
}
frame.set(allItemFrames.get(position).left, (int) (row.cuTop + (row.maxHeight - views.get(i).useHeight) / 2),
allItemFrames.get(position).right, (int) (row.cuTop + (row.maxHeight - views.get(i).useHeight) / 2 + getDecoratedMeasuredHeight(view)));
allItemFrames.put(position, frame);
item.setRect(frame);
views.set(i, item);
}
}
row.views = views;
lineRows.add(row);
row = new Row();
}
/**
* 竖直方向需要滑动的条件
*
* @return
*/
@Override
public boolean canScrollVertically() {
return true;
}
//监听竖直方向滑动的偏移量
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) {
Log.d("TAG", "totalHeight:" + totalHeight);
//实际要滑动的距离
int travel = dy;
//如果滑动到最顶部
if (verticalScrollOffset + dy < 0) {//限制滑动到顶部之后,不让继续向上滑动了
travel = -verticalScrollOffset;//verticalScrollOffset=0
} else if (verticalScrollOffset + dy > totalHeight - getVerticalSpace()) {//如果滑动到最底部
travel = totalHeight - getVerticalSpace() - verticalScrollOffset;//verticalScrollOffset=totalHeight - getVerticalSpace()
}
//将竖直方向的偏移量+travel
verticalScrollOffset += travel;
// 平移容器内的item
offsetChildrenVertical(-travel);
fillLayout(recycler, state);
return travel;
}
private int getVerticalSpace() {
return self.getHeight() - self.getPaddingBottom() - self.getPaddingTop();
}
public int getHorizontalSpace() {
return self.getWidth() - self.getPaddingLeft() - self.getPaddingRight();
}
}
3.主界面
package com.zw.flowlayoutdemo;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private RecyclerView mRecyclerView;
private EditText mEditText;
private Button mBtnAdd;
private MyAdapter myAdapter;
private ListmList=new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
}
private void initData() {
Listlist=new ArrayList<>();
String [] str=new String[]{"我喜欢你","你可以的","我们为什么不换一种方式","我爱你哟","你用了什么东西"};
for (int i = 0; i <5 ; i++) {
list.add(str[i]);
}
mList.clear();
mList.addAll(list);
myAdapter.notifyDataSetChanged();
}
private void initView() {
mRecyclerView=findViewById(R.id.rv_main);
mBtnAdd=findViewById(R.id.btn_add);
mBtnAdd.setOnClickListener(this);
mEditText=findViewById(R.id.editText);
myAdapter=new MyAdapter(this,mList);
FlowLayoutManager flowLayoutManager=new FlowLayoutManager();
mRecyclerView.addItemDecoration(new SpaceItemDecoration(10));
mRecyclerView.setLayoutManager(flowLayoutManager);
mRecyclerView.setAdapter(myAdapter);
mEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
checkName(s,mEditText);
}
});
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_add:
addItem();
break;
}
}
private void addItem() {
String txt=mEditText.getText().toString();
if (txt==null || txt.length()==0 || txt.equals("")) {
return;
} else {
mRecyclerView.setVisibility(View.VISIBLE);
//是否有相同的元素
if (mList.contains(txt)) {
Toast.makeText(this, "已存在相同的词", Toast.LENGTH_LONG).show();
return;
} else {
mList.add(txt);
}
myAdapter.notifyDataSetChanged();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mRecyclerView.scrollToPosition(myAdapter.getItemCount() - 1);
}
}, 2000);
mEditText.setText("");
}
}
/**
* 检查输入的名称的合法性
*
* @param editable
* @param editText
*/
private void checkName(Editable editable, EditText editText) {
String inputStr = editable.toString().trim();
String str = stringFilter(inputStr);
if (!inputStr.equals(str)) {
editText.setText(str);
editText.setSelection(editText.getText().toString().length());
return;
}
int length = getTextLength(inputStr);
if (length > 20) {
String newStr = getStringWithSubLength(inputStr, 20);
editText.setText(newStr);
editText.setSelection(editText.getText().toString().length());
}
}
/**
* 只允许字母、汉字
* @param str
* @return
* @throws PatternSyntaxException
*/
public String stringFilter(String str) throws PatternSyntaxException {
// 只允许字母、汉字
String regEx = "[^a-zA-Z\\u4E00-\\u9FA5]";
Pattern p = Pattern.compile(regEx);
Matcher m = p.matcher(str);
return m.replaceAll("").trim();
}
/**
* 获取字符数量 汉字占2个,英文占一个
*
* @param text
* @return
*/
public int getTextLength(String text) {
int length = 0;
for (int i = 0; i < text.length(); i++) {
if (text.charAt(i) > 255) {
length += 2;
} else {
length++;
}
}
return length;
}
public String getStringWithSubLength(String source, int maxCharLength) {
if (TextUtils.isEmpty(source)) {
return "";
}
if (getTextLength(source) <= maxCharLength) {
return source;
}
char[] childrens = source.toCharArray();
for (int i = 0; i < childrens.length; i++) {
StringBuilder builder = new StringBuilder();
for (int j = 0; j <= i; j++) {
builder.append(childrens[j]);
}
if (getTextLength(builder.toString()) > maxCharLength) {
return builder.replace(builder.length() - 1, builder.length(), "").toString();
}
}
return "";
}
}
4.参考资料:https://github.com/xiangcman/LayoutManager-FlowLayout/blob/master/library/src/main/java/com/library/flowlayout/FlowLayoutManager.java
5.源码地址:https://download.csdn.net/download/zhang106209/11649507