欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android?手寫(xiě)RecyclerView實(shí)現(xiàn)列表加載

 更新時(shí)間:2022年08月28日 09:20:04   作者:Ghelper???????  
這篇文章主要介紹了Android?手寫(xiě)RecyclerView實(shí)現(xiàn)列表加載,涉及到列表的需求,肯定第一時(shí)間想到RecyclerView,即便是自定義View,那么RecyclerView也會(huì)是首選,為什么會(huì)選擇RecyclerView而不是ListView,主要就是RecyclerView的內(nèi)存復(fù)用機(jī)制,這也是RecyclerView的核心?

前言

我相信一點(diǎn),只要我們的產(chǎn)品中,涉及到列表的需求,肯定第一時(shí)間想到RecyclerView,即便是自定義View,那么RecyclerView也會(huì)是首選,為什么會(huì)選擇RecyclerView而不是ListView,主要就是RecyclerView的內(nèi)存復(fù)用機(jī)制,這也是RecyclerView的核心 

 當(dāng)RecyclerView展示列表信息的時(shí)候,獲取ItemView的來(lái)源有2個(gè):一個(gè)是從適配器拿,另一個(gè)是從復(fù)用池中去拿;一開(kāi)始的時(shí)候就是從復(fù)用池去拿,如果復(fù)用池中沒(méi)有,那么就從Adapter中去拿,這個(gè)時(shí)候就是通過(guò)onCreateViewHolder來(lái)創(chuàng)建一個(gè)ItemView。

1 RecyclerView的加載流程

 首先,當(dāng)加載第一屏的時(shí)候,RecyclerView會(huì)向復(fù)用池中請(qǐng)求獲取View,這個(gè)時(shí)候復(fù)用池中是空的,因此就需要我們自己創(chuàng)建的Adapter,調(diào)用onCreateViewHolder創(chuàng)建ItemView,然后onBindViewHolder綁定數(shù)據(jù),展示在列表上 

當(dāng)我們滑動(dòng)的時(shí)候第一個(gè)ItemView移出屏幕時(shí),會(huì)被放到復(fù)用池中;同時(shí),底部空出位置需要加載新的ItemView,觸發(fā)加載機(jī)制,這個(gè)時(shí)候復(fù)用池不為空,拿到復(fù)用的ItemView,調(diào)用Adapter的onBIndViewHolder方法刷新數(shù)據(jù),加載到尾部;

這里有個(gè)問(wèn)題,放在復(fù)用池的僅僅是View嗎?其實(shí)不是的,因?yàn)镽ecyclerView可以根據(jù)type類(lèi)型加載不同的ItemView,那么放在復(fù)用池中的ItemView也是根據(jù)type進(jìn)行歸類(lèi),當(dāng)復(fù)用的時(shí)候,根據(jù)type取出不同類(lèi)型的ItemView;

例如ItemView07的類(lèi)型是ImageView,那么ItemView01在復(fù)用池中的類(lèi)型是TextView,那么在加載ItemView07時(shí),從復(fù)用池中是取不到的,需要Adapter新建一個(gè)ImageView類(lèi)型的ItemView。

2 自定義RecyclerView

其實(shí)RecyclerView,我們?cè)谑褂玫臅r(shí)候,知道怎么去用它,但是內(nèi)部的原理并不清楚,而且就算是看了源碼,時(shí)間久了就很容易忘記,所以只有當(dāng)自己自定義RecyclerView之后才能真正了解其中的原理。

2.1 RecyclerView三板斧

通過(guò)第一節(jié)的加載流程,我們知道RecyclerView有3個(gè)重要的角色:RecyclerView、適配器、復(fù)用池,所以在自定義RecyclerView的時(shí)候,就需要先創(chuàng)建這3個(gè)角色;

/**
 * 自定義RecyclerView
 */
public class MyRecyclerView extends ViewGroup {
    
    public MyRecyclerView(Context context) {
        super(context);
    }
    public MyRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return super.onInterceptTouchEvent(ev);
    }
    @Override
    public void scrollBy(int x, int y) {
        super.scrollBy(x, y);

    }
    interface Adapter<VH extends ViewHolder>{
        VH onCreateViewHolder(ViewGroup parent,int viewType);
        void onBindViewHolder(VH holder,int position);
        int getItemCount();
        int getItemViewType(int position);
    }
}
/**
 * 復(fù)用池
 */
public class MyRecyclerViewPool {
}
/**
 * Rv的ViewHolder
 */
public class ViewHolder {

    private View itemView;

    public ViewHolder(View itemView) {
        this.itemView = itemView;
    }
}

真正在應(yīng)用層使用到的就是MyRecyclerView,通過(guò)設(shè)置Adapter實(shí)現(xiàn)View的展示

2.2 初始化工作

從加載流程中,我們可以看到,RecyclerView是協(xié)調(diào)Adapter和復(fù)用池的關(guān)系,因此在RecyclerView內(nèi)部是持有這兩個(gè)對(duì)象的引用的。

//持有Adapter和復(fù)用池的引用
private Adapter mAdapter;
private MyRecyclerViewPool myRecyclerViewPool;
//Rv的寬高
private int mWidth;
private int mHeight;
//itemView的高度
private int[] heights;

那么這些變量的初始化,是在哪里做的呢?首先肯定不是在構(gòu)造方法中做的,我們?cè)谑褂肁dapter的時(shí)候,會(huì)調(diào)用setAdapter,其實(shí)就是在這個(gè)時(shí)候,進(jìn)行初始化的操作。

public void setAdapter(Adapter mAdapter) {
    this.mAdapter = mAdapter;
    this.needLayout = true;
    //刷新頁(yè)面
    requestLayout();
}

/**
 * 對(duì)子View進(jìn)行位置計(jì)算擺放
 * @param changed
 * @param l
 * @param t
 * @param r
 * @param b
 */
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    if(changed || needLayout){
        needLayout = false;
        mWidth = r - l;
        mHeight = b - t;
    }
}

每次調(diào)用setAdapter的時(shí)候,都會(huì)調(diào)用requestLayout刷新重新布局,這個(gè)時(shí)候會(huì)調(diào)用onLayout,因?yàn)閛nLayout的調(diào)用很頻繁非常耗性能,因此我們通知設(shè)置一個(gè)標(biāo)志位needLayout,只有當(dāng)需要刷新的時(shí)候,才能刷新重新擺放子View

2.3 ItemView的獲取與擺放

其實(shí)在RecyclerView當(dāng)中,是對(duì)每個(gè)子View進(jìn)行了測(cè)量,得到了它們的寬高,然后根據(jù)每個(gè)ItemView的高度擺放,這里我們就寫(xiě)死了高度是200,僅做測(cè)試使用,后續(xù)優(yōu)化。

那么在擺放的時(shí)候,比如我們有200條數(shù)據(jù),肯定不會(huì)把200條數(shù)據(jù)全部加載進(jìn)來(lái),默認(rèn)就展示一屏的數(shù)據(jù),所以需要判斷如果最后一個(gè)ItemView的bottom超過(guò)了屏幕的高度,就停止加載。

 @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    if(changed || needLayout){
        needLayout = false;
        if(mAdapter != null){
            mWidth = r - l;
            mHeight = b - t;

            //計(jì)算每個(gè)ItemView的寬高,然后擺放位置
            rowCount = mAdapter.getItemCount();
            //這里假定每個(gè)ItemView的高度為200,實(shí)際Rv是需要測(cè)量每個(gè)ItemView的高度
            heights = new int[rowCount];
            for (int i = 0; i < rowCount; i++) {
                heights[i] = 200;
            }
            //擺放 -- 滿(mǎn)第一屏就停止擺放
            for (int i = 0; i < rowCount; i++) {
                bottom = top + heights[i];
                //獲取View
                ViewHolder holder = getItemView(i,0,top,mWidth,bottom);
                viewHolders.add(holder);
                //第二個(gè)top就是第一個(gè)的bottom
                top = bottom;
            }

        }
    }
}

我們先拿到之前的圖,確定下子View的位置 

 其實(shí)每個(gè)子View的left都是0,right都是RecyclerView的寬度,變量就是top和bottom,其實(shí)從第2個(gè)ItemView開(kāi)始,top都是上一個(gè)ItemView的bottom,那么bottom就是 top + ItemView的高度

在確定了子View的位置參數(shù)之后,就可以獲取子View來(lái)進(jìn)行擺放,其實(shí)在應(yīng)用層是對(duì)子View做了一層包裝 --- ViewHolder,因此這里獲取到的也是ViewHolder。

private ViewHolder getItemView(int row,int left, int top, int right, int bottom) {

    ViewHolder viewHolder = obtainViewHolder(row,right - left,bottom - top);
    viewHolder.itemView.layout(left,top,right,bottom);
    return viewHolder;
}

private ViewHolder obtainViewHolder(int row, int width, int height) {

    ViewHolder viewHolder = null;
    //首先從復(fù)用池中查找

    //如果找不到,那么就通過(guò)適配器生成
    if(mAdapter !=null){
        viewHolder = mAdapter.onCreateViewHolder(this,mAdapter.getItemViewType(row));
    }
    return viewHolder;
}

通過(guò)調(diào)用obtainViewHolder來(lái)獲取ViewHolder對(duì)象,其實(shí)是分2步的,首先 是從緩存池中去拿,在第一節(jié)加載流程中提及到,緩存池中不只是存了一個(gè)ItemView的布局,而是通過(guò)type標(biāo)注了ItemView,所以從緩存池中需要根據(jù)type來(lái)獲取,如果沒(méi)有獲取到,那么就調(diào)用Adapter的onCreateViewHolder獲取,這種避免了每個(gè)ItemView都通過(guò)onCreateViewHolder創(chuàng)建,浪費(fèi)系統(tǒng)資源;

在拿到了ViewHolder之后,調(diào)用根布局ItemView的layout方法進(jìn)行位置擺放。

2.4 復(fù)用池

前面我們提到,在復(fù)用池中不僅僅是緩存了一個(gè)布局,而是每個(gè)type都對(duì)應(yīng)一組回收的Holder,所以在復(fù)用池中存在一個(gè)容器存儲(chǔ)ViewHolder

/**
 * 復(fù)用池
 */
public class MyRecyclerViewPool {

    static class scrapData{
        List<ViewHolder> viewHolders = new ArrayList<>();
    }

    private SparseArray<scrapData> array = new SparseArray<>();

    /**
     * 從緩存中獲取ViewHolder
     * @param type ViewHolder的類(lèi)型,用戶(hù)自己設(shè)置
     * @return ViewHolder
     */
    public ViewHolder getRecyclerView(int type){

    }

    /**
     * 將ViewHolder放入緩存池中
     * @param holder
     */
    public void putRecyclerView(ViewHolder holder){

    }
}

當(dāng)RecyclerView觸發(fā)加載機(jī)制的時(shí)候,首先會(huì)從緩存池中取出對(duì)應(yīng)type的ViewHolder;當(dāng)ItemView移出屏幕之后,相應(yīng)的ViewHolder會(huì)被放在緩存池中,因此存在對(duì)應(yīng)的2個(gè)方法,添加及獲取

/**
 * 從緩存中獲取ViewHolder
 *
 * @param type ViewHolder的類(lèi)型,用戶(hù)自己設(shè)置
 * @return ViewHolder
 */
public static ViewHolder getRecyclerView(int type) {
    //首先判斷type
    if (array.get(type) != null && !array.get(type).viewHolders.isEmpty()) {

        //將最后一個(gè)ViewHolder從列表中移除
        List<ViewHolder> scrapData = array.get(type).viewHolders;
        for (int i = scrapData.size() - 1; i >= 0; i--) {
            return scrapData.remove(i);
        }
    }
    return null;
}

/**
 * 將ViewHolder放入緩存池中
 *
 * @param holder
 */
public static void putRecyclerView(ViewHolder holder) {

    int key = holder.getItemViewType();
    //獲取集合
    List<ViewHolder> viewHolders = getScrapData(key).viewHolders;
    viewHolders.add(holder);
}

private static ScrapData getScrapData(int key) {
    ScrapData scrapData = array.get(key);
    if(scrapData == null){
        scrapData = new ScrapData();
        array.put(key,scrapData);
    }
    return scrapData;
}

2.5 數(shù)據(jù)更新

無(wú)論是從緩存池中拿到了緩存的ViewHolder,還是通過(guò)適配器創(chuàng)建了ViewHolder,最終都需要將ViewHolder進(jìn)行數(shù)據(jù)填充

private ViewHolder obtainViewHolder(int row, int width, int height) {

    int itemViewType = mAdapter.getItemViewType(row);
    //首先從復(fù)用池中查找
    ViewHolder viewHolder = MyRecyclerViewPool.getRecyclerView(itemViewType);
    //如果找不到,那么就通過(guò)適配器生成
    if(viewHolder == null){
        viewHolder = mAdapter.onCreateViewHolder(this,itemViewType);
    }
    //更新數(shù)據(jù)
    if (mAdapter != null) {
        mAdapter.onBindViewHolder(viewHolder, row);
        //設(shè)置ViewHOlder的類(lèi)型
        viewHolder.setItemViewType(itemViewType);

        //測(cè)量
        viewHolder.itemView.measure(
                MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
        );
        addView(viewHolder.itemView);
    }
    return viewHolder;
}

如果跟到這里,我們其實(shí)已經(jīng)完成了RecyclerView的基礎(chǔ)功能,一個(gè)首屏列表的展示

3 RecyclerView滑動(dòng)事件處理

3.1 點(diǎn)擊事件與滑動(dòng)事件

對(duì)于RecyclerView來(lái)說(shuō),我們需要的其實(shí)是對(duì)于滑動(dòng)事件的處理,對(duì)于點(diǎn)擊事件來(lái)說(shuō),通常是子View來(lái)響應(yīng),做相應(yīng)的跳轉(zhuǎn)或者其他操作,所以對(duì)于點(diǎn)擊事件和滑動(dòng)事件,RecyclerView需要做定向的處理。

那么如何區(qū)分點(diǎn)擊事件和滑動(dòng)事件?

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {

    switch (ev.getAction()){

        case MotionEvent.ACTION_MOVE:
            return true;
    }
    return false;
}

在容器中,如果碰到MOVE事件就攔截就認(rèn)為是滑動(dòng)事件,這種靠譜嗎?顯然 不是的,當(dāng)手指點(diǎn)擊到屏幕上時(shí),首先系統(tǒng)會(huì)接收到一次ACTION_DWON時(shí)間,在手指抬起之前,ACTION_DWON只會(huì)響應(yīng)一次,而且ACTION_MOVE會(huì)有無(wú)數(shù)次,因?yàn)槿梭w手指是有面積的,當(dāng)我們點(diǎn)下去肯定不是一個(gè)點(diǎn),而是一個(gè)面肯定會(huì)存在ACTION_MOVE事件,但這種我們會(huì)認(rèn)為是點(diǎn)擊事件;

所以對(duì)于滑動(dòng)事件,我們會(huì)認(rèn)為當(dāng)手指移動(dòng)一段距離之后,超出某個(gè)距離就是滑動(dòng)事件,這個(gè)最小滑動(dòng)距離通過(guò)ViewConfiguration來(lái)獲取。

private void init(Context context) {
    ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
    this.touchSlop = viewConfiguration.getScaledTouchSlop();
}

因?yàn)榱斜砦覀冋J(rèn)為是豎直方向滑動(dòng)的,所以我們需要記錄手指在豎直方向上的滑動(dòng)距離。

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    //判斷是否攔截
    boolean intercept = false;

    switch (ev.getAction()){
        case MotionEvent.ACTION_DOWN:
            mCurrentY = (int) ev.getY();
            break;

        case MotionEvent.ACTION_MOVE:
            //y值在不停改變
            int y = (int) ev.getY();
            if(Math.abs(y - mCurrentY) > touchSlop){
                //認(rèn)為是滑動(dòng)了
                intercept = true;
            }
            break;
    }
    return intercept;
}

我們通過(guò)intercept標(biāo)志位,來(lái)判斷當(dāng)前是否在進(jìn)行滑動(dòng),如果滑動(dòng)的距離超出了touchSlop,那么就將事件攔截,在onTouchEvent中消費(fèi)這個(gè)事件。

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_MOVE: {
            //判斷滑動(dòng)的方向
            int diff = (int) (mCurrentY - event.getRawY());
            if(Math.abs(diff) > touchSlop){
                Log.e(TAG,"diff --- "+diff);
                scrollBy(0, diff);
                mCurrentY = (int) event.getRawY();
            }
            break;
        }
    }
    return super.onTouchEvent(event);
}

3.2 scrollBy和scrollTo

在onTouchEvent中,我們使用了scrollBy進(jìn)行滑動(dòng),那么scrollBy和scrollTo有什么區(qū)別,那就根據(jù)Android的坐標(biāo)系開(kāi)始說(shuō)起 

 scrollBy滑動(dòng),其實(shí)是滑動(dòng)的偏移量,相對(duì)于上一次View所在的位置,例如上圖中,View上滑,偏移量就是(200 - 100 = 100),所以調(diào)用scrollBy(0,100)就是向上滑動(dòng),反之就是上下滑動(dòng);

scrollTo滑動(dòng),滑動(dòng)的是絕對(duì)距離,例如上圖中,View上滑,那么需要傳入詳細(xì)的坐標(biāo)scrollTo(200,100),下滑scrollTo(200,300),其實(shí)scrollBy內(nèi)部調(diào)用也是調(diào)用的scrollTo,所以偏移量就是用來(lái)計(jì)算絕對(duì)位置的。

3.3 滑動(dòng)帶來(lái)的View回收

當(dāng)滑動(dòng)屏幕的時(shí)候,有一部分View會(huì)被滑出到屏幕外,那么就涉及到了View的回收和View的重新擺放。

首先分析向上滑動(dòng)的操作,首先我們用scrollY來(lái)標(biāo)記,屏幕中第一個(gè)子View左上角距離屏幕左上角的距離,默認(rèn)就是0.

@Override
public void scrollBy(int x, int y) {
    super.scrollBy(x, y);
    scrollY += y;

    if (scrollY > 0) {
        Log.e(TAG, "上滑");
        //防止一次滑動(dòng)多個(gè)子View出去
        while (scrollY > heights[firstRow]) {
            //被移除,放入回收池
            if (!viewHolders.isEmpty()) {
                removeView(viewHolders.remove(0));
            }
            scrollY -= heights[firstRow];
            firstRow++;
        }

    } else {
        Log.e(TAG, "下滑");
    }
}

在這里插入圖片描述

 當(dāng)ItemView1移出屏幕之后,因?yàn)樯匣瑂crollY > 0,所以scrollY肯定會(huì)超過(guò)Itemiew 的高度,這里有個(gè)情況就是,如果一次滑出去多個(gè)ItemView,那么高度肯定是超過(guò)單個(gè)ItemView的高度,這里用firstRow來(lái)標(biāo)記,當(dāng)前子View在數(shù)據(jù)集合中的位置,所以這里使用的是while循環(huán)。

/**
 * 移除ViewHolder,放入回收池
 *
 * @param holder
 */
private void removeView(ViewHolder holder) {
    MyRecyclerViewPool.putRecyclerView(holder);
    //系統(tǒng)方法,從RecyclerView中移除這個(gè)View
    removeView(holder.itemView);
    viewHolders.remove(holder);
}

如果滑出去多個(gè)子View,那么就循環(huán)從viewHolders(當(dāng)前屏幕展示的View的集合)中移除,移除的ViewHolder就被放在了回收池中,然后從當(dāng)前屏幕中移除;

3.4 加載機(jī)制

既然有移除,那么就會(huì)有新增,當(dāng)?shù)撞砍霈F(xiàn)空缺的時(shí)候,就會(huì)觸發(fā)加載機(jī)制,那么每次移除一個(gè)元素,都會(huì)有一個(gè)元素添加進(jìn)來(lái)嗎?其實(shí)不然 

 像ItemView1移除之后,最底部的ItemView還沒(méi)有完全展示出來(lái),其實(shí)是沒(méi)有觸發(fā)加載的,那么什么時(shí)候觸發(fā)加載呢?

在當(dāng)前屏幕中展示的View其實(shí)是在緩存中的,那么只要計(jì)算緩存中全部ItemView的高度跟屏幕的高度比較,如果不足就需要填充。

 //如果小于屏幕的高度
 while (getRealHeight(firstRow) <= mHeight) {
     //觸發(fā)加載機(jī)制
     int addIndex = firstRow + viewHolders.size();
     ViewHolder viewHolder = obtainViewHolder(addIndex, mWidth, heights[addIndex]);
     viewHolders.add(viewHolders.size(), viewHolder);
     Log.e(TAG,"添加一個(gè)View");
 }
/**
 * 獲取實(shí)際展示的高度
 *
 * @param firstIndex
 * @return
 */
private int getRealHeight(int firstIndex) {
    return getSumArray(firstRow, viewHolders.size()) - scrollY;
}

private int getSumArray(int firstIndex, int count) {
    int totalHeight = 0;
    count+= firstIndex;
    for (int i = firstIndex; i < count; i++) {
        totalHeight += heights[i];
    }
    return totalHeight;
}

這樣其實(shí)就實(shí)現(xiàn)了,一個(gè)View移除屏幕之后,會(huì)有一個(gè)新的View添加進(jìn)來(lái)

/**
 * 重新擺放View
 */
private void repositionViews() {
    int left = 0;
    int top = -scrollY;
    int right = mWidth;
    int bottom = 0;

    int index = firstRow;

    for (int i = 0; i < viewHolders.size(); i++) {
        bottom = top + heights[index++];
        viewHolders.get(i).itemView.layout(left,top,right,bottom);
        top = bottom;
    }
}

當(dāng)然新的View只要添加進(jìn)來(lái),就需要對(duì)他進(jìn)行重新擺放,這樣上滑就實(shí)現(xiàn)了(只有上滑哦) 

3.5 RecyclerView下滑處理

在此之前,我們處理了上滑的事件,頂部的View移出,下部分的View添加進(jìn)來(lái),那么下滑正好相反。 

 那么下滑添加View的時(shí)機(jī)是什么呢?就是scrollY小于0的時(shí)候,會(huì)有新的View添加進(jìn)來(lái)

//下滑頂部添加View
while (scrollY < 0) {

    //獲取ViewHolder
    ViewHolder viewHolder = obtainViewHolder(firstRow - 1, mWidth, heights[firstRow - 1]);
    //放到屏幕緩存ViewHolder最頂部的位置
    viewHolders.add(0, viewHolder);
    firstRow--;
    //當(dāng)頂部ItemView完全加進(jìn)來(lái)之后,需要改變scrollY的值
    scrollY += heights[firstRow];
}

此時(shí)需要將添加的View,放在屏幕展示View緩存的首位,然后firstRow需要-1;

那么當(dāng)新的View添加進(jìn)來(lái)之后,底部View需要移除,那么移除的時(shí)機(jī)是什么呢?先把尾部最后一個(gè)View的高度拋開(kāi),繼續(xù)往下滑動(dòng),如果當(dāng)前屏幕展示的View的高度超過(guò)了屏幕高度,那么就需要移除

//底部移除View
while (!viewHolders.isEmpty() &&
        getRealHeight(firstRow) - viewHolders.get(viewHolders.size() - 1).itemView.getHeight() >= mHeight) {
    //需要移除
    removeView(viewHolders.remove(viewHolders.size() - 1));
}

3.6 邊界問(wèn)題

當(dāng)我們上滑或者下滑的時(shí)候,firstRow都在遞增或者遞減,但是firstRow肯定是有邊界的,例如滑到最上端的時(shí)候,firstRow最小就是0,如果再-1,那么就會(huì)數(shù)組越界,最下端也有邊界,那就是數(shù)組的最大長(zhǎng)度。

/**
 * @param scrollY
 * @param firstRow
 */
private void scrollBounds(int scrollY, int firstRow) {

    if (scrollY > 0) {
        //上滑
        if (getSumArray(firstRow, heights.length - firstRow) - scrollY > mHeight) {
            this.scrollY = scrollY;
        } else {
            this.scrollY = getSumArray(firstRow, heights.length - firstRow) - mHeight;
        }
    } else {
        //下滑
        this.scrollY = Math.max(scrollY, -getSumArray(0, firstRow));
    }
}

首先看下滑,這個(gè)時(shí)候firstRow > 0,這個(gè)時(shí)候getSumArray的值是逐漸減小的,等到最頂部,也就是滑到firstRow = 0的時(shí)候,這個(gè)時(shí)候getSumArray = 0,那么再往下滑其實(shí)還是能滑的,這個(gè)時(shí)候我們需要做限制,取scrollY 和 getSumArray的最大值,如果一致下滑,getSumArray一致都是0,然后scrollY < 0,最終scrollY = 0,不會(huì)再執(zhí)行下滑的操作了。

接下來(lái)看上滑,正常情況下,如果200條數(shù)據(jù),那么當(dāng)firstRow = 10的時(shí)候,剩下190個(gè)ItemView的高度(減去上滑的高度)肯定是高于屏幕高度的,那么一直滑,當(dāng)發(fā)現(xiàn)剩余的ItemView的高度不足以占滿(mǎn)整個(gè)屏幕的時(shí)候,就是沒(méi)有數(shù)據(jù)了,這個(gè)時(shí)候,其實(shí)就可以把scrollY設(shè)置為0,不能再繼續(xù)滑動(dòng)了。

 @Override
 public void scrollBy(int x, int y) {
//        super.scrollBy(x, y);

     scrollY += y;
     scrollBounds(scrollY, firstRow);


     if (scrollY > 0) {
         Log.e(TAG, "上滑");
         //防止一次滑動(dòng)多個(gè)子View出去
         while (scrollY > heights[firstRow]) {
             //被移除,放入回收池
             if (!viewHolders.isEmpty()) {
                 removeView(viewHolders.remove(0));
             }
             scrollY -= heights[firstRow];
             firstRow++;
             Log.e("scrollBy", "scrollBy 移除一個(gè)View size =="+viewHolders.size());
         }

         //如果小于屏幕的高度
         while (getRealHeight(firstRow) < mHeight) {
             //觸發(fā)加載機(jī)制
             int addIndex = firstRow + viewHolders.size();
             ViewHolder viewHolder = obtainViewHolder(addIndex, mWidth, heights[addIndex]);
             viewHolders.add(viewHolders.size(), viewHolder);
             Log.e("scrollBy", "scrollBy 添加一個(gè)View size=="+viewHolders.size());
         }
         //重新擺放
         repositionViews();

     } else {
         Log.e(TAG, "下滑");

         //底部移除View
         while (!viewHolders.isEmpty() &&
                 getRealHeight(firstRow) - viewHolders.get(viewHolders.size() - 1).itemView.getHeight() >= mHeight) {
             //需要移除
             removeView(viewHolders.remove(viewHolders.size() - 1));
         }

         //下滑頂部添加View
         while (scrollY < 0) {

             //獲取ViewHolder
             ViewHolder viewHolder = obtainViewHolder(firstRow - 1, mWidth, heights[firstRow - 1]);
             //放到屏幕緩存ViewHolder最頂部的位置
             viewHolders.add(0, viewHolder);
             firstRow--;
             //當(dāng)頂部ItemView完全加進(jìn)來(lái)之后,需要改變scrollY的值
             scrollY += heights[firstRow];
         }
     }
 }

OK,這其實(shí)跟RecyclerView的源碼相比,簡(jiǎn)直就是一個(gè)窮人版的RecyclerView,但是其中的思想我們是可以借鑒的,尤其是回收池的思想,在開(kāi)發(fā)中是可以借鑒的,下面展示的就是最后的成果 

到此這篇關(guān)于Android 手寫(xiě)RecyclerView實(shí)現(xiàn)列表加載的文章就介紹到這了,更多相關(guān)Android RecyclerView 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論