1. 程式人生 > >Android RecyclerView載入複雜佈局

Android RecyclerView載入複雜佈局

用一個RecyclerView實現多種複雜佈局,複用機制要儲存

1.jpg
  • 我們先看看兩種記憶體消耗的情況


    gaollg0.GIF
  • 第一種是NestedScrollView ,可以看到瞬間記憶體就增加,然後不停的滑動,載入越來越多的內容,記憶體消耗越來越大,沒用複用機制的缺點。
gaollg1.GIF
  • 第二種是隻用一個RecyclerView的情況,記憶體就不會繼續上漲了,即使後面再增加內容,記憶體也是相對穩定。
  • 比如像這樣的佈局要求,如果用一個NestedScrollView裡面巢狀多個RecyclerView,就能輕鬆解決,但是隨著越用越久,上滑繼續載入,越滑動,消耗的記憶體越多,而且,一進到這個介面,就開始載入全部的圖片,有的使用者不需要看到最底下的東西,就導致消耗流量。網速慢的情況下,下面的圖片內容也在載入,搶佔上面的網路資源,就會下面有的圖片,比上面先出現,這樣體驗不好。如果你試試後臺給你1000個item,下一子加載出來,小米6都會吃不消的。

  • 因為NestedScrollView巢狀的缺點,導致我換一種寫法,用一個RecyclerView,多種item,來實現這種佈局,一是複用了,記憶體消耗就小了,然後你滑到哪裡,就載入到哪,流量消耗也好一些.

  • 先看NestedScrollView實現功能
 <android.support.v4.widget.NestedScrollView
        android:id="@+id/nestedScrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
>
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <include layout="@layout/item_head"/> <include layout="@layout/item_message_1"/> <android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView_image" android:layout_width="match_parent" android:layout_height="wrap_content"/>
<android.support.v7.widget.RecyclerView android:id="@+id/recyclerView_product" android:layout_width="match_parent" android:layout_height="wrap_content"/> <View android:layout_width="match_parent" android:layout_height="20dp" android:background="@color/line_color"/> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView_other" android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout> </android.support.v4.widget.NestedScrollView>
  1. 這是NestedScrollView是佈局xml,我用的是25.3.1版本,就不會有無法計算裡面RecyclerView的高度問題。
//設定滑動慣性
recyclerViewProduct.setNestedScrollingEnabled(false);
  1. 要記得RecyclerView要設定恢復滑動慣性,不然上下滑動沒有慣性了。
       //GridLayoutManager,垂直方向
        gridLayoutManagerProduct = new GridLayoutManager(this, 3, GridLayoutManager.VERTICAL, false);
       //設定頭部佔3個,其他佔1個
        gridLayoutManagerProduct.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup()
        {
            @Override
            public int getSpanSize(int position)
            {
                if (productAdapter.getItemViewType(position) == ProductAdapter.ITEM_HEAD)
                {
                    return 3;
                } else
                {
                    return 1;
                }
            }
        });
  1. 使用網格佈局的話,就用setSpanSizeLookup這個 方法,這個不是說每行3個item,你也能寫6,然後分組頭佔6份,他就佔滿整行寬度了,item寫2份,這樣的話,就能每行3個item。這個自己去試一試就懂了,就是return 多少,佔幾份。
        //滑動監聽
        nestedScrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener()
        {
            @Override
            public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY)
            {
                View view = v.getChildAt(0);
                if (view.getHeight() == scrollY + v.getHeight())
                {
                    //載入更多、增加資料
                    otherAdapter.addData(DataOther.getOtherList());
                }
            }
        });

4.NestedScrollView的滑動監聽,判斷滑動到底部的方法。

  • 接下來說重點,就是使用一個RecyclerView來實現複製的佈局,主要講Adapter。
    //頭部
    public static final int ITEM_HEAD = 0;
    //資訊
    public static final int ITEM_MESSAGE = 1;
    //圖片
    public static final int ITEM_IMAGE = 2;
    //還有別型別
  1. 首先把這個佈局進行拆分,我將它分為 6 種,像“頭部”部分,還有“資訊”部分,只會出現一次的,其他的就會多出出現,
    其實之前,我是將“頭部”和“資訊”放一起,但是這個item太大了,超出2個螢幕高度,導致顯示不全,原因估計是item的高度計算問題,如果不解決問題,可以把item拆成不大於螢幕高度的大小。
    @Override
    public int getItemViewType(int position)
    {
        if (position == 0)
        {
            //確定第一個是頭部
            return ITEM_HEAD;
        } else if (position == 1)
        {
            //確定第二個是資訊
            return ITEM_MESSAGE;
        } else if (datas.get(position) instanceof BeanImage)
        {
            return ITEM_IMAGE;
        } else if (datas.get(position) instanceof BeanProduct.BeanData)
        {
            return ITEM_PRODUCT;
        } else if (datas.get(position) instanceof BeanOther)
        {
            return ITEM_OTHER;
        } else
        {
            //確定String使用者商品頭部
            return ITEM_PRODUCT_HEAD;
        }
    }
  1. 在adapter裡面,要重寫 getItemViewType 這個方法,這個方法呢,就是根據 item 的下標 position 返回對應的 ViewHolder,
    demo可以看到,“頭部”和“資訊”部分,我能確定他是出現一次,而且位置是固定的,所以,position 為 0 和 1 的時候,我就放回對應的值,其他ViewHolder 我根據對應的資料型別來放回。
   @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
    {
        if (viewType == ITEM_HEAD)
        {
            return new HeadViewHolder(LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.item_head, parent, false));
        }
        .
        .        
        //還有別的型別,省略
    }
  1. adapter必須重新的 onCreateViewHolder 這個 方法,給我們一個引數 int viewType,這個引數就是上面 getItemViewType 這個方法判斷型別後返回的值,我們根據這個值,來 create 對應的 ViewHolder。
    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView)
    {
        super.onAttachedToRecyclerView(recyclerView);
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager)
        {
            GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
            gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup()
            {
                @Override
                public int getSpanSize(int position)
                {
                    //取多個item的每行佔用個數的最小公倍數,
                    //比如item1一行2個,item2一行3個,item3一行1個,就取6
                    //他們對應的return 3,return 2,return 6 
                    if (ITEM_PRODUCT == getItemViewType(position))
                    {
                        return 2;
                    } else if (ITEM_OTHER == getItemViewType(position))
                    {
                        return 3;
                    } else
                    {
                        return 6;
                    }
                }
            });
        }
    }
  1. adapter再寫一個方法 onAttachedToRecyclerView,這個方法就能獲取到RecyclerView ,然後再獲取到 對應的LayoutManager,複製的佈局,一般都是用網格(GridLayoutManager),或者瀑布流( StaggeredGridLayoutManager)
    這個看自己的需求了,獲取到之後,再根據position對應的ViewHolder,設定他們的佔位值(我理解的)。
       //滑動監聽
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener()
        {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState)
            {
                super.onScrollStateChanged(recyclerView, newState);
                //判斷是否到底部,offset是滑動距離,Extent是控制元件高度,range是總的內容高度
                if (recyclerView.getChildCount() > 0
                        && recyclerView.computeVerticalScrollOffset() + recyclerView.computeVerticalScrollExtent()
                        >= recyclerView.computeVerticalScrollRange())
                {
                    detailAdapter.addData(DataOther.getOtherList());
                }
            }
        });
  1. 最後來個RecyclerView的滑動到底部,可靠的方法。

這個demo用原始的方法,繼承 RecyclerView.Adapter ,將方法一個個重寫,好處就是讓你理解RecyclerView,可以實現複雜的佈局複用,缺點就是寫起來,有點費時間,不過學習嘛,搞懂了再去用開源框架。
介紹一些可能用的到的
BaseRecyclerViewAdapterHelper:https://github.com/CymChad/BaseRecyclerViewAdapterHelper/wiki/%E9%A6%96%E9%A1%B5