android RecyclerView實現流失佈局增加自定義佈局管理器(FlowLayoutManager)

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<Row> lineRows = new ArrayList<>();

    //保存所有的Item的上下左右的偏移量信息
    private SparseArray<Rect> 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<Item> 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<Item> 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<Item> 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<Item> 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 List<String>mList=new ArrayList<>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initData();
    }

    private void initData() {
        List<String>list=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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章