之前写了一篇折叠流式布局,bug有点多,也不好改,究其原因就是写的逻辑太多,改起来不方便,毕竟主体逻辑不是自己写的,基于别人的改总是怪怪的。那么,我就想想这个东西的难点在哪?有什么简单的方法解决?
难点
我们的需求是:流式布局展示,当数量没超过两行,那么就不加入展开与收起按钮,如果超过两行但小于等于4行,在收起状态时加入展开按钮,在展开状态展示收起按钮,如果超过4行,在收起状态时加入展开按钮,在展开状态最大4行的最后展示收起按钮。
折叠一个流式布局,在于加入一个子view的时候,要提前知道折叠的位置。
比如流式布局在折叠状态时,加入一个子view后,我们要知道他有没有超过两行,超过了,我们需要知道第二行的最后一个index是多少,然后在这个位置插入向下按钮。
再比如,流式布局不在折叠状态时,加入一个子view,我们要判断他是否在1行到4行之间,如果在,那么他后面一定要加一个向上按钮,因为他是展开的,一定要有一个收起按钮。如果超过4行,那么我们需要知道第4行最后一个按钮。
当然开发中还发现一个问题,那就是加入一个子view后当前刚好是展开状态的第4行,那么加入收起按钮的时候,我们需要判断当前剩余的宽度够不够我们加入向上按钮,够的话,我们index插入 位置直接返回所有子view的大小,如果不能,那么我们返回所有所有子view的大小 -1 。因为如果我们还返回所有子view的大小的话,就会排到第5行。
思路
怎么提前知道要插入的位置呢?前一篇文章是用一个view,在onMeasure里面写了一大堆逻辑去写。现在换一个思路,我们做两个view,装在一个布局里面,一个view(A)是专门用于计算插入位置,另一个view(B)是专门展示数据。当A加入所有的子view后,我们能很快的知道我们需要的index,加入这个index为7,那么在B里面我们就只要装0到6的子view,最后7就变成收起或展开按钮。A就是我们的替身。缺点就是如果子view很大很大,那么就会超市或者很慢。
代码
包裹两个子view的布局FlowContentLayout
public class FlowContentLayout extends RelativeLayout {
private FlowLayout mBackFlowLayout;
private int mLastIndex = 0;
private FlowLayout mFontFlowLayout;
private List<String> list = new ArrayList<>();
private View upView;
private View downView;
public FlowContentLayout(Context context) {
this(context,null);
}
public FlowContentLayout(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public FlowContentLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
inflate(context, R.layout.flow_content_layout,this);
upView = LayoutInflater.from(context).inflate(R.layout.view_item_fold_up, this, false);
upView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
mBackFlowLayout.setFoldState(true);
mFontFlowLayout.setFoldState(true);
refreshViews();
}
});
downView = LayoutInflater.from(context).inflate(R.layout.view_item_fold_down, this, false);
downView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
mBackFlowLayout.setFoldState(false);
mFontFlowLayout.setFoldState(false);
refreshViews();
}
});
mBackFlowLayout = findViewById(R.id.mFlowLayout);
mBackFlowLayout.setFlowContentLayout(this);
mFontFlowLayout = findViewById(R.id.mFontFlowLayout);
mFontFlowLayout.setUpFoldView(upView);
mFontFlowLayout.setDownFoldView(downView);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mBackFlowLayout.setFlowContentLayout(null);
}
/**
* 这里把隐藏的幕后计算布局加入view先计算
* @param list
*/
public void addViews(@NotNull List<String> list) {
mLastIndex = 0;
this.list.clear();
this.list.addAll(list);
mBackFlowLayout.addViews(list);
}
/**
* 相同的数据重新刷新
*/
private void refreshViews(){
if(list != null && list.size() > 0){
mLastIndex = 0;
mBackFlowLayout.addViews(list);
}
}
/**
* 幕后布局计算后的最大折叠位置
* @param foldState
* @param index
* @param flag 是否需要加入向上或者向下按钮
* @param lineWidthUsed
*/
public void foldIndex(boolean foldState, int index, boolean flag, int lineWidthUsed) {
if(mLastIndex != index){//防止多次调用
mLastIndex = index;
//添加外部真正的布局
if(flag){
List<String> list = new ArrayList<>();
for (int x = 0; x < index; x++) {
list.add(FlowContentLayout.this.list.get(x));
}
list.add("@@");
mFontFlowLayout.addViews(list);
}else{
List<String> list = new ArrayList<>();
for (int x = 0; x < FlowContentLayout.this.list.size(); x++) {
list.add(FlowContentLayout.this.list.get(x));
}
mFontFlowLayout.addViews(list);
}
}
}
public int getUpViewWidth() {
if(upView != null){
return Utils.getViewWidth(upView);
}
return 0;
}
/**
* 删除全部后转态恢复
*/
public void releaseState(){
mBackFlowLayout.setFoldState(true);
mFontFlowLayout.setFoldState(true);
}
}
流式布局FlowLayout
public class FlowLayout extends ViewGroup {
/**
* 水平距离
*/
private int mHorizontalSpacing = Utils.dp2px(8f);
private static final int MAX_LINE = 3;//从0开始计数
private static final int MIN_LINE = 1;//从0开始计数
private FlowContentLayout mFlowContentLayout;
private boolean foldState = true;
private View upFoldView;
private View downFoldView;
private int mWidth;
private int textViewHeight;
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);
}
public void setFlowContentLayout(FlowContentLayout mFlowContentLayout) {
this.mFlowContentLayout = mFlowContentLayout;
}
public void setFoldState(boolean foldState) {
this.foldState = foldState;
}
public void setUpFoldView(View upFoldView) {
this.upFoldView = upFoldView;
}
public void setDownFoldView(View downFoldView) {
this.downFoldView = downFoldView;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = getWidth();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取mode 和 size
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int layoutWidth = widthSize - getPaddingLeft() - getPaddingRight();
//判断如果布局宽度抛去左右padding小于0,也不能处理了
if (layoutWidth <= 0) {
return;
}
//这里默认宽高默认值默认把左右,上下padding加上
int width = getPaddingLeft() + getPaddingRight();
int height = getPaddingTop() + getPaddingBottom();
//初始一行的宽度
int lineWidth = 0;
//初始一行的高度
int lineHeight = 0;
//测量子View
measureChildren(widthMeasureSpec, heightMeasureSpec);
int[] wh = null;
int childWidth, childHeight;
//行数
int line = 0;
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View view = getChildAt(i);
//这里需要先判断子view是否被设置了GONE
if (view.getVisibility() == GONE) {
continue;
}
childWidth = view.getMeasuredWidth();
childHeight = view.getMeasuredHeight();
//第一行
if (i == 0) {
lineWidth = getPaddingLeft() + getPaddingRight() + childWidth;
lineHeight = childHeight;
} else {
//判断是否需要换行
//换行
if (lineWidth + mHorizontalSpacing + childWidth > widthSize) {
line++;//行数增加
// 取最大的宽度
width = Math.max(lineWidth, width);
//重新开启新行,开始记录
lineWidth = getPaddingLeft() + getPaddingRight() + childWidth;
//叠加当前高度,
height += lineHeight;
//开启记录下一行的高度
lineHeight = childHeight;
if(mFlowContentLayout != null){
if(foldState && line > MIN_LINE){
callBack(foldState,i-1, true,lineWidth);
break;
}else if(!foldState && line > MAX_LINE){
callBack(foldState,i-1, true,lineWidth);
break;
}
}
}
//不换行
else {
lineWidth = lineWidth + mHorizontalSpacing + childWidth;
lineHeight = Math.max(lineHeight, childHeight);
}
}
// 如果是最后一个,则将当前记录的最大宽度和当前lineWidth做比较
if (i == count - 1) {
width = Math.max(width, lineWidth);
height += lineHeight;
}
}
//根据计算的值重新设置
if(mFlowContentLayout == null){
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width,
heightMode == MeasureSpec.EXACTLY ? heightSize : height);
}else{
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width,
0);
}
if(foldState && (line >= 0 && line <= MIN_LINE)){
callBack(foldState,getChildCount(),false,lineWidth);
}
if(!foldState && (line >= 0 && line <= MAX_LINE)){
if(mFlowContentLayout != null){
int upViewWidth = mFlowContentLayout.getUpViewWidth() + mHorizontalSpacing;
if(lineWidth > (mWidth - upViewWidth) && line == MAX_LINE){
callBack(foldState,getChildCount() - 1,true,lineWidth);
}else{
callBack(foldState,getChildCount(),true,lineWidth);
}
}else{
callBack(foldState,getChildCount(),true,lineWidth);
}
}
}
/**
* 超过最大数的回调
* @param foldState
* @param index 最大数的位置。
* @param b
* @param lineWidthUsed
*/
private void callBack(boolean foldState, int index, boolean b, int lineWidthUsed) {
if(mFlowContentLayout != null){
mFlowContentLayout.foldIndex(foldState,index,b,lineWidthUsed);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int layoutWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
if (layoutWidth <= 0) {
return;
}
int childWidth, childHeight;
//需要加上top padding
int top = getPaddingTop();
final int[] wh = getMaxWidthHeight();
int lineHeight = 0;
int line = 0;
//左对齐
//左侧需要先加上左边的padding
int left = getPaddingLeft();
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View view = getChildAt(i);
//这里一样判断下显示状态
if (view.getVisibility() == GONE) {
continue;
}
//自适宽高
childWidth = view.getMeasuredWidth();
childHeight = view.getMeasuredHeight();
//第一行开始摆放
if (i == 0) {
view.layout(left, top, left + childWidth, top + childHeight);
lineHeight = childHeight;
} else {
//判断是否需要换行
if (left + mHorizontalSpacing + childWidth > layoutWidth + getPaddingLeft()) {
line++;
//重新起行
left = getPaddingLeft();
top = top + lineHeight;
lineHeight = childHeight;
} else {
left = left + mHorizontalSpacing;
lineHeight = Math.max(lineHeight, childHeight);
}
view.layout(left, top, left + childWidth, top + childHeight);
}
//累加left
left += childWidth;
}
}
/**
* 取最大的子view的宽度和高度
*
* @return
*/
private int[] getMaxWidthHeight() {
int maxWidth = 0;
int maxHeight = 0;
for (int i = 0, count = getChildCount(); i < count; i++) {
final View view = getChildAt(i);
if (view.getVisibility() == GONE) {
continue;
}
maxWidth = Math.max(maxWidth, view.getMeasuredWidth());
maxHeight = Math.max(maxHeight, view.getMeasuredHeight());
}
return new int[]{maxWidth, maxHeight};
}
public void addViews(List<String> list){
removeAllViews();
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
for (int x = 0; x< list.size(); x++) {
String s = list.get(x);
if(TextUtils.equals("@@",s)){
if(foldState){
if(downFoldView != null){
Utils.removeFromParent(downFoldView);
addView(downFoldView,layoutParams);
}
}else{
if(upFoldView != null){
Utils.removeFromParent(upFoldView);
addView(upFoldView,layoutParams);
}
}
}else{
addTextView(s,layoutParams);
}
}
}
private void addTextView(String s,LinearLayout.LayoutParams layoutParams){
LinearLayout linearLayout = new LinearLayout(getContext());
linearLayout.setPadding(0,Utils.dp2px(8f),0,0);
linearLayout.setLayoutParams(layoutParams);
TextView tv = new TextView(getContext());
tv.setPadding(Utils.dp2px(12f), Utils.dp2px(8f), Utils.dp2px(12f), Utils.dp2px(8f));
tv.setText(s);
tv.setSingleLine();
tv.setTextSize(TypedValue.COMPLEX_UNIT_SP,12);
tv.setTextColor(getResources().getColor(R.color.ff666666));
tv.setEllipsize(TextUtils.TruncateAt.END);
tv.setBackgroundResource(R.drawable.search_tag_bg);
linearLayout.addView(tv,new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));
addView(linearLayout,layoutParams);
textViewHeight = Utils.getViewHeight(tv);
}
}
最后activity里面只要往里面加入String集合就行
mFlowContentLayout?.addViews(list)
当需要清空所有数据,重新加入数据时不止String集合需要清空,也需要调用FlowContentLayout的releaseState方法还原他的收起展开状态。
flow_content_layout布局代码填一下
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.laiyifen.search2.flowLayout.FlowLayout
android:id="@+id/mFlowLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="invisible"/>
<com.laiyifen.search2.flowLayout.FlowLayout
android:id="@+id/mFontFlowLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>
对了在实际运用中,我把这个布局放在了列表的头部,导致会调用他的detach方法,导致不能回调,所以注释掉下面的方法
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mBackFlowLayout.setFlowContentLayout(null);
}
自己独立封装,在activity销毁时自己调用释放。
网友评论