手寫RecycleView

網(wǎng)易云課堂筆記

一篡石、手寫RecycleView的重點(diǎn)

1.View的回收池
2.如何設(shè)計(jì)適配器
3.滑動(dòng)的邊界
4.子View如何布局

二燥狰、回收池和適配器

  1. RecycleView內(nèi)部有一個(gè)回收池負(fù)責(zé)緩存滑出屏幕的View于宙,設(shè)計(jì)緩存是需要考慮到RecycleView的Item可能有多重布局樣式荚坞,如何緩存下這些View并且區(qū)分出他們的種類。

考慮到RecycleView滑出屏幕一個(gè)View就需要一個(gè)新的Item進(jìn)入的情況询张,使用 Stack作為緩存池,并且針對(duì)每一種Item的布局都分配一個(gè)Stack

在構(gòu)造方法中可以看到針對(duì)每一種View都分配了一個(gè)Stack<View>,而這些Stack<View>對(duì)象又由一個(gè)數(shù)組對(duì)象views來管理,當(dāng)然也可以由一個(gè)Map來管理

/**
 * 回收池:回收 View 根據(jù) Type 來存放回收的 View 對(duì)象
 * 選取集合需要考慮到滑動(dòng)的情況,先進(jìn)先出的特性史辙,因?yàn)?RecycleView 的一個(gè) Item 滑出屏幕后有可能會(huì)被立即取出
 */
public class Recycler {
    private Stack<View>[] views;

    public Recycler(int typeNumber) {
        views = new Stack[typeNumber];
        for (int i = 0; i < typeNumber; i++) {

            views[i] = new Stack<View>();
        }
    }

    public void put(View view, int type) {
        views[type].push(view);
    }

    public View get(int type) {
        try {
            return views[type].pop();
        } catch (Exception e) {
            return null;
        }

    }

}

  1. RecycleView 調(diào)用 Adapter的 onCreateViewHolder 創(chuàng)建一個(gè)Item,當(dāng)?shù)谝黄恋膇tem都滿時(shí)佩伤,完成第一屏的加載聊倔。當(dāng)手指滑動(dòng)時(shí),劃出屏幕的item會(huì)進(jìn)入回收池生巡,這時(shí)候屏幕加載新的item時(shí)會(huì)去回收池查看是否有item耙蔑,并且布局和新進(jìn)入的item一致,一致的話從回收池中拿出這個(gè)item進(jìn)行復(fù)用障斋,復(fù)用的方式是將這個(gè)item交給適配器(因?yàn)閿?shù)據(jù)不一致)纵潦,適配器拿到item后進(jìn)行刷新,然后再繪制到屏幕上

三垃环、適配器

RecycleView需要知道

1.一共有多少條數(shù)據(jù)要渲染
2.Item有多少個(gè)種類
3.創(chuàng)建布局
4.使用緩存的View刷新布局

參考以有的適配器模式邀层,接口如下設(shè)計(jì)

  interface Adapter {
        View onCreateViewHodler(int position, View convertView, ViewGroup parent);
        /**
         * 刷新 View 的參數(shù)
         *
         * @param position
         * @param convertView
         * @param parent
         * @return
         */
        View onBinderViewHodler(int position, View convertView, ViewGroup parent);

        //獲取指定行數(shù)的 View 類型
        int getItemViewType(int row);

        //Item的類型數(shù)量
        int getViewTypeCount();

        // 數(shù)據(jù)的數(shù)量
        int getCount();

        // 每一個(gè) Item 的高度
        public int getHeight(int index);
    }

四、RecycleView的布局

onMeasure

onMeasure 需要考慮到所有子View遂庄,sumArray就是計(jì)算出所有子View的高

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int h = 0;
        if (adapter != null) {
            // 獲取到有幾條數(shù)據(jù)
            this.rowCount = adapter.getCount();
            // 獲取到所有數(shù)據(jù)的高
            heights = new int[rowCount];
            for (int i = 0; i < heights.length; i++) {
                heights[i] = adapter.getHeight(i);
            }
        }
        // 取布局設(shè)置的高以及數(shù)據(jù)總長度的高最小的一個(gè)
        int tmpH = sumArray(heights, 0, heights.length);
        // 取最小的高度
        h = Math.min(heightSize, tmpH);
        setMeasuredDimension(widthSize, h);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

onLayout中需要計(jì)算每一個(gè)子View的位置,這里上一個(gè)Item底部的位置就是下一個(gè)Item的Top

......
  for (int i = 0; i < rowCount && top < height; i++) {
                    right = width;
                    bottom = top + heights[i];
                    // 生成一個(gè)View
                    View view = makeAndStep(i, 0, top, right, bottom);
                    viewList.add(view);
                    // 下一個(gè) view 的 top 是上一個(gè) View 的 bottom
                    top = bottom;//循環(huán)擺放
                }
....
 private View makeAndStep(int row, int left, int top, int right, int bottom) {
        View view = obtainView(row, right - left, bottom - top);
        view.layout(left, top, right, bottom);
        return view;
    }

五寥院、如何處理滑動(dòng)事件

需要監(jiān)聽手指按下的事件和移動(dòng)的事件,當(dāng)移動(dòng)的距離大于滑動(dòng)最小距離時(shí)認(rèn)為是一次滑動(dòng)事件

1.通過ViewConfiguration獲取系統(tǒng)設(shè)定的最小滑動(dòng)距離
2.onIntercept用來判斷是否攔截事件,處理滑動(dòng)事件是在onTouchEvent中涛目。

   ViewConfiguration configuration = ViewConfiguration.get(context);
   this.touchSlop = configuration.getScaledTouchSlop();
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercept = false;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                currentY = (int) event.getRawY();
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                // 當(dāng)手指按下的位值 比在 Y 方向移動(dòng)的距離大于最小滑動(dòng)的距離秸谢,我們攔截這個(gè)事件
                int y2 = Math.abs(currentY - (int) event.getRawY());
                if (y2 > touchSlop) {
                    intercept = true;
                }
            }
        }
        return intercept;
    }

onTouchEvent 中去計(jì)算滑動(dòng)的距離,并執(zhí)行滑動(dòng)

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE: {
                //                移動(dòng)的距離   y方向
                int y2 = (int) event.getRawY();
                //         //            上滑正  下滑負(fù)
                int diffY = currentY - y2;
                //                畫布移動(dòng)  并不影響子控件的位置
                scrollBy(0, diffY);
            }
        }
        return super.onTouchEvent(event);
    }

六、從換從中獲取View并且刷新布局

Item可以直接創(chuàng)建霹肝,也可以從緩存中獲取估蹄,直接創(chuàng)建的話調(diào)用onCreateViewHolder創(chuàng)建View并加入到緩存中,從緩存中獲取到的View通過onBinderViewHolder刷新數(shù)據(jù)沫换,給View設(shè)置一個(gè)Tag可以通過這個(gè)Tag來區(qū)分Item的布局類型

   private View obtainView(int row, int width, int height) {
        //    獲取到這一行 View 的類型
        int itemType = adapter.getItemViewType(row);
        //    根據(jù)類型去 緩存池中獲取
        View reclyView = recycler.get(itemType);
        View view = null;
        // 如果回收池里沒有 View 使用 onCreateViewHolder 創(chuàng)建一個(gè)
        if (reclyView == null) {
            view = adapter.onCreateViewHodler(row, reclyView, this);
            if (view == null) {
                throw new RuntimeException("onCreateViewHodler  必須填充布局");
            }
        } else {
            // 否則使用onBinderView
            view = adapter.onBinderViewHodler(row, reclyView, this);
        }
        // 給View 一個(gè) Tag
        view.setTag(R.id.tag_type_view, itemType);
        view.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY)
                , MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
        addView(view, 0);
        return view;
    }

七臭蚁、處理滑動(dòng)事件

1.首先要判斷是否超出了滑動(dòng)的邊界 即滑動(dòng)到最后一個(gè)View仍然上滑或者第一個(gè)VIew仍然下滑

2.如果上滑的高度已經(jīng)大于當(dāng)前可見的第一個(gè)Item的距離,就移除這個(gè)Item,使用while循環(huán)是因?yàn)榉乐褂脩粢淮涡曰龆鄠€(gè)View 所以使用
3.加入一個(gè)Item的條件是新加入Item后所有顯示出的Item是否會(huì)超出高度

 @Override
    public void scrollBy(int x, int y) {
        //        scrollY表示 第一個(gè)可見Item的左上頂點(diǎn) 距離屏幕的左上頂點(diǎn)的距離
        scrollY += y;
        // 判斷是否達(dá)到了極限條件 數(shù)據(jù)的最頂端和數(shù)據(jù)的最低端
        scrollY = scrollBounds(scrollY);
        //        scrolly
        if (scrollY > 0) {
            /**
             * 當(dāng)用戶滑動(dòng)特別快的時(shí)候 可能一下子滑出去3,4個(gè) View 所以要不斷去判斷 scrollY 是否比當(dāng)前
             * 第一個(gè) View 的 heights 只內(nèi)讯赏,如果不在繼續(xù)移除垮兑,知道 scrollY 在 當(dāng)前 第一個(gè) item的高度范圍內(nèi)
             *
             */
            //              上滑正  下滑負(fù)  邊界值
            while (scrollY > heights[firstRow]) {
                //      1 上滑移除  2 上劃加載  3下滑移除  4 下滑加載
                removeView(viewList.remove(0));
                // 因?yàn)橛脩艨赡芑瑒?dòng)的很快,可能一次性滑出了好幾個(gè)View漱挎,所以用這個(gè)方式來
                // 計(jì)算一次性滑出了幾個(gè) View
                scrollY -= heights[firstRow];
                firstRow++;
            }
            // 是否添加一個(gè) View: 數(shù)據(jù)高度減去-scrollY的值
            while (getFillHeight() < height) {
                int addLast = firstRow + viewList.size();
                View view = obtainView(addLast, width, heights[addLast]);
                viewList.add(viewList.size(), view);
            }
            // 下滑添加
        } else if (scrollY < 0) {
            //            4 下滑加載
            while (scrollY < 0) {
                int firstAddRow = firstRow - 1;
                View view = obtainView(firstAddRow, width, heights[firstAddRow]);
                // 因?yàn)槭窍禄虞d系枪,緩存永遠(yuǎn)在第一個(gè)位置
                viewList.add(0, view);
                firstRow--;
                scrollY += heights[firstRow + 1];
            }
            //   總和高度 - 滑出屏幕的高度 scrollY - 最后一個(gè) item 的高度 就等于 View 的高度
            while (sumArray(heights, firstRow, viewList.size()) - scrollY - heights[firstRow + viewList.size() - 1]
                    >= height) {
                removeView(viewList.remove(viewList.size() - 1));
            }

        } else {
        }
        // 重新擺放子View的位置
        repositionViews();
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市磕谅,隨后出現(xiàn)的幾起案子私爷,更是在濱河造成了極大的恐慌雾棺,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件当犯,死亡現(xiàn)場(chǎng)離奇詭異垢村,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)嚎卫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門嘉栓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拓诸,你說我怎么就攤上這事侵佃。” “怎么了奠支?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵馋辈,是天一觀的道長。 經(jīng)常有香客問我倍谜,道長迈螟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任尔崔,我火速辦了婚禮答毫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘季春。我一直安慰自己洗搂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布载弄。 她就那樣靜靜地躺著耘拇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪宇攻。 梳的紋絲不亂的頭發(fā)上惫叛,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音逞刷,去河邊找鬼挣棕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛亲桥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播固耘,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼题篷,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了厅目?” 一聲冷哼從身側(cè)響起番枚,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤法严,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后葫笼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體深啤,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年路星,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了溯街。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡洋丐,死狀恐怖呈昔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情友绝,我是刑警寧澤堤尾,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站迁客,受9級(jí)特大地震影響郭宝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜掷漱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一粘室、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧切威,春花似錦育特、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至喳魏,卻和暖如春棉浸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背刺彩。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國打工迷郑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人创倔。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓嗡害,卻偏偏與公主長得像,于是被迫代替她去往敵國和親畦攘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子霸妹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

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