IRcyclerView從會用到會寫(二)

IRcyclerView從會用到會寫系列的第二篇,上一篇我們講了怎么使用IRrecyclerView,這一節(jié)我們來看看,IRecyclerView到底是怎樣實現(xiàn)效果的。

首先,你得要有一個猜想

首先,我們會很疑惑,感覺IRecyclerView用起來和recyclerview沒什么區(qū)別,就只是setAdapter變成了setIAdapter。那么,我們就從setIAdapter開始看起。

點進去,我們看見的setIAdapter的方法,以及現(xiàn)在可以看到IRecyclerView是繼承自RecyclerView的,所以擁有recyclerView的所有方法。

    public void setIAdapter(Adapter adapter) {
        this.ensureRefreshHeaderContainer();
        this.ensureHeaderViewContainer();
        this.ensureFooterViewContainer();
        this.ensureLoadMoreFooterContainer();
        this.setAdapter(new WrapperAdapter(adapter, this.mRefreshHeaderContainer, this.mHeaderViewContainer, this.mFooterViewContainer, this.mLoadMoreFooterContainer));
    }

噫!!上面的都是些什么鬼!最后還是調(diào)用了setAdapter,只不過卻傳了一個我們不知道的參數(shù)。而且上面的名字很熟悉,明顯就是我們的刷新item,頭結(jié)點,尾節(jié)點,和加載item。
嘛嘛,我們開始猜想,他是不是其實已經(jīng)早就已經(jīng)將我們所需要的四個動態(tài)item早就加載了list里面,但是返回給我們的就只是我們設(shè)置的參數(shù)。

我們隨便點進去一個會發(fā)現(xiàn),噫!這不就是一個空的viewlayout嘛。

    private void ensureRefreshHeaderContainer() {
        if(this.mRefreshHeaderContainer == null) {
            this.mRefreshHeaderContainer = new RefreshHeaderLayout(this.getContext());
            this.mRefreshHeaderContainer.setLayoutParams(new LayoutParams(-1, 0));
        }
    }

再看看他們的定義

    private RefreshHeaderLayout mRefreshHeaderContainer;
    private FrameLayout mLoadMoreFooterContainer;
    private LinearLayout mHeaderViewContainer;
    private LinearLayout mFooterViewContainer;

就更加堅定了我們的之前的猜想,這些就是viewLayout。但是他們卻傳給了一個不知姓名的WrapperAdapter,所以為什么我們設(shè)置adapter的時候,卻和往常的一樣,沒有因為多加了幾個item而是item的順序發(fā)生了錯亂, 所以 ,這個WrapperAdapter一定有鬼,而且他最終肯定也是一個adapter,所噶,那我們進去看看這個WrapperAdapter到底是什么東西。

    public WrapperAdapter(Adapter adapter, RefreshHeaderLayout refreshHeaderContainer, LinearLayout headerContainer, LinearLayout footerContainer, FrameLayout loadMoreFooterContainer) {
        this.mAdapter = adapter;
        this.mRefreshHeaderContainer = refreshHeaderContainer;
        this.mHeaderContainer = headerContainer;
        this.mFooterContainer = footerContainer;
        this.mLoadMoreFooterContainer = loadMoreFooterContainer;
        this.mAdapter.registerAdapterDataObserver(this.mObserver);
    }

點進去我們就看見了WrapperAdapter的構(gòu)造函數(shù), 前面5行不用解釋了吧,就是對象變量的初始化。重點是最后一個,我滴天?。?!registerAdapterDataObserver()這個是什么東東,而且還傳入了一個自定義的mOberserver。

好了!到了這里我們就又要科普一下了!這個registerAdapterDataObserver是個什么東東。讓我們看一下官方的說法:
Register a new observer to listen for data changes.
嘛嘛!什么意思呢!就是說注冊一個新的監(jiān)聽者,去監(jiān)聽data的變化。我們大家都知道,當(dāng)我們使用recyclerview的過程中,如果數(shù)據(jù)變化了,我們就要使用notifydatachange去改變recycleview中的item的數(shù)量以及順序。而具體要怎么變,其實也就是通過adapter中的observer接收到的消息,然后具體在改變。
OK!科普結(jié)束。

那我們就去看看mObserver是怎樣響應(yīng)這個data變化的消息事件。


image.png

OK,我們可以看見,我們新建的adapterdataobserver里面重寫了所有的方法。
這里我們打開幾個重點的,有代表性的函數(shù)。

        public void onItemRangeChanged(int positionStart, int itemCount) {
            WrapperAdapter.this.notifyItemRangeChanged(positionStart + 2, itemCount);
        }

        public void onItemRangeInserted(int positionStart, int itemCount) {
            WrapperAdapter.this.notifyItemRangeInserted(positionStart + 2, itemCount);
        }

我們發(fā)現(xiàn),噫?。槭裁丛诟聰?shù)據(jù)的時候,要向后移動兩個位置呢??咦咦?。?!這部又進一步證明我們的猜想嘛,因為那兩個是refreshview和handView,而他們本來就已經(jīng)先建好了的,只是需要動態(tài)的設(shè)置才能顯示出來。

然后我們繼續(xù)往下翻,哇塞,心里的石頭落地,這不就是印證了我們的猜想。根據(jù)不同的類型,創(chuàng)建不同的itemview。

image.png
    public void onBindViewHolder(ViewHolder holder, int position) {
        if(1 < position && position < this.mAdapter.getItemCount() + 2) {
            this.mAdapter.onBindViewHolder(holder, position - 2);
        }
    }

所以我們的猜想是正確的,irecyclerview內(nèi)部自己實現(xiàn)了adapter,已經(jīng)將我們可能需要到的四個view已經(jīng)加了進去。只是暫時讓我們看不見而已。既然我們已經(jīng)知道這一點了,那么,開始下一步,我什么我們看不見他們!

為什么我們看不見他們

讓我們回到IRecyclerView文件中,看看那itemlayout都是怎么定義的。

    private void ensureRefreshHeaderContainer() {
        if (mRefreshHeaderContainer == null) {
            mRefreshHeaderContainer = new RefreshHeaderLayout(getContext());
            mRefreshHeaderContainer.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0));
        }
    }

    private void ensureLoadMoreFooterContainer() {
        if (mLoadMoreFooterContainer == null) {
            mLoadMoreFooterContainer = new FrameLayout(getContext());
            mLoadMoreFooterContainer.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        }
    }

看到了吧,在初始化他的時候,我們給他的高度為0,或者,我們給他們的高度為自適應(yīng),而開始的時候,他們里面都是沒有子view的,所以我們看不見他們,他們開始時候的高度為0。

itemView的動態(tài)改變?不存在的

其實到這里,我們已經(jīng)能夠知道了頭節(jié)點和尾節(jié)點的實現(xiàn)方式,然而我們現(xiàn)在關(guān)心的是下拉刷新的效果以及,上拉加載更多的頁面的變化。

首先,讓我們想一下,如果是你,想要實現(xiàn)下拉刷新,你會怎么做,一般的想法就是當(dāng)我們的listview已經(jīng)處于在頂端的時候,你還在繼續(xù)下拉的話,那么,我們的refreshView是不是就應(yīng)該出現(xiàn)了,所以說,你肯定需要檢測到我們下拉的手勢,所以,我們需要重寫onTouchEvent方法。

所以,繼續(xù)看源碼,我們果然發(fā)現(xiàn)了他在確實是重寫了onTouchEvent方法。并且,里面的東西還挺多,我們挑重點的來說。

case MotionEvent.ACTION_MOVE: {
                ...
                final int dx = x - mLastTouchX;
                final int dy = y - mLastTouchY;

                //保證 存在 開啟 存在刷新頁面 手指滑動 保證在最頭上
                final boolean triggerCondition = isEnabled() && mRefreshEnabled && mRefreshHeaderView != null && isFingerDragging() && canTriggerRefresh();
                if (triggerCondition) {
                    ...
                    //下拉
                    if (dy > 0 && mStatus == STATUS_DEFAULT) {
                      ...
                    }//上拉
                    else if (dy < 0) {
                        ...
                    }

                    //下拉或者是放松狀態(tài)
                    if (mStatus == STATUS_SWIPING_TO_REFRESH || mStatus == STATUS_RELEASE_TO_REFRESH) {
                        ...
                        fingerMove(dy);//變化的Y值
                        ...
                    }
                }
            }
            break;

在move事件中,看見,通過每次對比dy,得到是否是上劃還是下劃,然后添加不同的狀態(tài),最后調(diào)用fingerMove。還有一個重點就是triggerCondition,他是決定該事件能否開始我們下拉刷新的操作。我們看看最后兩個條件的函數(shù)。

    //判斷當(dāng)前是否是 拖 這個狀態(tài)
    private boolean isFingerDragging() {
        return getScrollState() == SCROLL_STATE_DRAGGING;
    }
    //當(dāng)前是否已經(jīng)到達recyclerView的頂部
    public boolean canTriggerRefresh() {
        final Adapter adapter = getAdapter();
        if (adapter == null || adapter.getItemCount() <= 0) {
            return true;
        }
        View firstChild = getChildAt(0);
        int position = getChildLayoutPosition(firstChild);
        if (position == 0) {
            if (firstChild.getTop() == mRefreshHeaderContainer.getTop()) {
                return true;
            }
        }
        return false;
    }

秒懂對不對?。?!再來看看fingerMove是什么操作。因為fingerMov最終調(diào)用了move。move如下:

    private void move(int dy) {
        if (dy != 0) {
            int height = mRefreshHeaderContainer.getMeasuredHeight() + dy;
            setRefreshHeaderContainerHeight(height);
            mRefreshTrigger.onMove(false, false, height);
        }
    }

好的 我相信你已經(jīng)也懂了對不對。
最后是setRefreshHeaderContainerHeight,他就動態(tài)改變了refreshView的高度。也就是我們下拉時候,把refreshView拉出來的操作。

    private void setRefreshHeaderContainerHeight(int height) {
        mRefreshHeaderContainer.getLayoutParams().height = height;
        mRefreshHeaderContainer.requestLayout();
    }

最后回到我們的ontouchEvent,它里面還有兩個事件:

            case MotionEvent.ACTION_UP: {
                onFingerUpStartAnimating();
            }
            break;

            case MotionEvent.ACTION_CANCEL: {
                onFingerUpStartAnimating();
            }

當(dāng)觸摸事件終止和觸摸事件手指抬起的時候,開始動畫效果。
嘛嘛!我們已經(jīng)知道,在我們拉完了之后,頁面向上彈起,其實也就是動畫效果實現(xiàn)了。

    private void onFingerUpStartAnimating() {
        if (mStatus == STATUS_RELEASE_TO_REFRESH) {
            startScrollReleaseStatusToRefreshingStatus();
        } else if (mStatus == STATUS_SWIPING_TO_REFRESH) {
            startScrollSwipingToRefreshStatusToDefaultStatus();
        }
    }

到這里 ,我想大家也已經(jīng)明白了IRecyclerView大致的道理。
1.重寫了recyclerView,將其多增加了四個節(jié)點,作為我們后期動態(tài)改變的itemview
2.在touchEvent中,定了我們特定的事件,實現(xiàn)下拉效果的refreshView的動態(tài)改變
3.在手指抬起,或者觸摸事件終止的時候,通過動畫,實現(xiàn)頁面的反彈回去。

好的?。?!接下來我們開始重復(fù)造輪子,自己仿照這IRecyclerView的方式,自己寫一個IRecyclerViewCopy,并在內(nèi)部增加幾個常見的示例,不用每次使用的時候,還要自己寫頁面動畫效果。那么今天就這樣咯?。hhhhhhh

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,881評論 25 709
  • 雨葵_5264閱讀 1,202評論 0 0
  • 多久都沒有舊地重游了 午夜夢回的思緒 每每在一棵樹下打斷 我停下來冥想的時候 樹又老去了三載 寫在葉子上的年輪 斷...
    T騎士閱讀 216評論 2 4
  • 360云盤宣布個人用戶服務(wù)要下臺,目前說是2017年4月或12月,這再次引起我們對云盤的關(guān)注。云時代里,云盤能發(fā)揮...
    小斌PPT閱讀 2,196評論 6 4

友情鏈接更多精彩內(nèi)容