Android 替身法實(shí)現(xiàn)折疊流式布局

1635156335588.gif

之前寫了一篇折疊流式布局,bug有點(diǎn)多,也不好改,究其原因就是寫的邏輯太多,改起來不方便,畢竟主體邏輯不是自己寫的,基于別人的改總是怪怪的。那么,我就想想這個(gè)東西的難點(diǎn)在哪?有什么簡(jiǎn)單的方法解決?

難點(diǎn)

我們的需求是:流式布局展示,當(dāng)數(shù)量沒超過兩行,那么就不加入展開與收起按鈕,如果超過兩行但小于等于4行,在收起狀態(tài)時(shí)加入展開按鈕,在展開狀態(tài)展示收起按鈕,如果超過4行,在收起狀態(tài)時(shí)加入展開按鈕,在展開狀態(tài)最大4行的最后展示收起按鈕。

折疊一個(gè)流式布局,在于加入一個(gè)子view的時(shí)候,要提前知道折疊的位置。

比如流式布局在折疊狀態(tài)時(shí),加入一個(gè)子view后,我們要知道他有沒有超過兩行,超過了,我們需要知道第二行的最后一個(gè)index是多少,然后在這個(gè)位置插入向下按鈕。

再比如,流式布局不在折疊狀態(tài)時(shí),加入一個(gè)子view,我們要判斷他是否在1行到4行之間,如果在,那么他后面一定要加一個(gè)向上按鈕,因?yàn)樗钦归_的,一定要有一個(gè)收起按鈕。如果超過4行,那么我們需要知道第4行最后一個(gè)按鈕。

當(dāng)然開發(fā)中還發(fā)現(xiàn)一個(gè)問題,那就是加入一個(gè)子view后當(dāng)前剛好是展開狀態(tài)的第4行,那么加入收起按鈕的時(shí)候,我們需要判斷當(dāng)前剩余的寬度夠不夠我們加入向上按鈕,夠的話,我們index插入 位置直接返回所有子view的大小,如果不能,那么我們返回所有所有子view的大小 -1 。因?yàn)槿绻覀冞€返回所有子view的大小的話,就會(huì)排到第5行。

思路

怎么提前知道要插入的位置呢?前一篇文章是用一個(gè)view,在onMeasure里面寫了一大堆邏輯去寫。現(xiàn)在換一個(gè)思路,我們做兩個(gè)view,裝在一個(gè)布局里面,一個(gè)view(A)是專門用于計(jì)算插入位置,另一個(gè)view(B)是專門展示數(shù)據(jù)。當(dāng)A加入所有的子view后,我們能很快的知道我們需要的index,加入這個(gè)index為7,那么在B里面我們就只要裝0到6的子view,最后7就變成收起或展開按鈕。A就是我們的替身。缺點(diǎn)就是如果子view很大很大,那么就會(huì)超市或者很慢。

代碼

包裹兩個(gè)子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);
   }

   /**
    * 這里把隱藏的幕后計(jì)算布局加入view先計(jì)算
    * @param list
    */
   public void addViews(@NotNull List<String> list) {
       mLastIndex = 0;
       this.list.clear();
       this.list.addAll(list);
       mBackFlowLayout.addViews(list);
   }

   /**
    * 相同的數(shù)據(jù)重新刷新
    */
   private void refreshViews(){
       if(list != null && list.size() > 0){
           mLastIndex = 0;
           mBackFlowLayout.addViews(list);
       }
   }

   /**
    * 幕后布局計(jì)算后的最大折疊位置
    * @param foldState
    * @param index
    * @param flag 是否需要加入向上或者向下按鈕
    * @param lineWidthUsed
    */
   public void foldIndex(boolean foldState, int index, boolean flag, int lineWidthUsed) {
       if(mLastIndex != index){//防止多次調(diào)用
           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;
   }

   /**
    * 刪除全部后轉(zhuǎn)態(tài)恢復(fù)
    */
   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開始計(jì)數(shù)
    private static final int MIN_LINE = 1;//從0開始計(jì)數(shù)
    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;
        }

        //這里默認(rèn)寬高默認(rèn)值默認(rèn)把左右,上下padding加上
        int width = getPaddingLeft() + getPaddingRight();
        int height = getPaddingTop() + getPaddingBottom();

        //初始一行的寬度
        int lineWidth = 0;
        //初始一行的高度
        int lineHeight = 0;

        //測(cè)量子View
        measureChildren(widthMeasureSpec, heightMeasureSpec);

        int[] wh = null;
        int childWidth, childHeight;
        //行數(shù)
        int line = 0;
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View view = getChildAt(i);
            //這里需要先判斷子view是否被設(shè)置了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++;//行數(shù)增加
                    // 取最大的寬度
                    width = Math.max(lineWidth, width);
                    //重新開啟新行,開始記錄
                    lineWidth = getPaddingLeft() + getPaddingRight() + childWidth;
                    //疊加當(dāng)前高度,
                    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);
                }
            }
            // 如果是最后一個(gè),則將當(dāng)前記錄的最大寬度和當(dāng)前l(fā)ineWidth做比較
            if (i == count - 1) {
                width = Math.max(width, lineWidth);
                height += lineHeight;
            }
        }
        //根據(jù)計(jì)算的值重新設(shè)置
        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);
            }

        }
    }

    /**
     * 超過最大數(shù)的回調(diào)
     * @param foldState
     * @param index 最大數(shù)的位置。
     * @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;
        //左對(duì)齊
        //左側(cè)需要先加上左邊的padding
        int left = getPaddingLeft();
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View view = getChildAt(i);
            //這里一樣判斷下顯示狀態(tà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)

當(dāng)需要清空所有數(shù)據(jù),重新加入數(shù)據(jù)時(shí)不止String集合需要清空,也需要調(diào)用FlowContentLayout的releaseState方法還原他的收起展開狀態(tài)。

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>

對(duì)了在實(shí)際運(yùn)用中,我把這個(gè)布局放在了列表的頭部,導(dǎo)致會(huì)調(diào)用他的detach方法,導(dǎo)致不能回調(diào),所以注釋掉下面的方法

@Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mBackFlowLayout.setFlowContentLayout(null);
    }

自己獨(dú)立封裝,在activity銷毀時(shí)自己調(diào)用釋放。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容