android技术

Android 替身法实现折叠流式布局

2021-10-25  本文已影响0人  坑逼的严
1635156335588.gif

之前写了一篇折叠流式布局,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销毁时自己调用释放。

上一篇下一篇

猜你喜欢

热点阅读