RecyclerViewの動きを追いたい (初回レイアウト編)

割とただのメモ書きなので、読まなくてよいです！
ただ分かっているとRecyclerViewと仲良くなれる気がします。


RecyclerViewのソースコードを見たことがありますか？
いくつかのパーツで出来ていて結構でかいですよね、、
普通に読むとつらそうです(自分には辛いです)
また、以下の様な画像がAndroid Developerにありますがちょっとこれだけだとちょっとうーんという感じです。。
https://developer.android.com/training/material/lists-cards.html
![公式の画像](https://developer.android.com/training/material/images/RecyclerView.png)



ですがRecyclerViewもただのViewGropを継承したViewのはずです。
なので、普通にカスタムViewGroupと同じようにonLayout()があります。

RecyclerViewはライブラリとして提供されているので、デバッガーでブレークポイントをつければ、出ているコードと同じ場所を実行してくれるので、読むときに必要な場所だけ読めて良いです。


今回はバージョンが23.1.0でLinearLayoutManagerで縦に並べて普通に表示する場合について説明します。


## 子Viewが作られるまで(Adapter.onCreateViewHolderが呼ばれるまで)

RecyclerView#onLayout()でRecyclerView#dispatchLayout()を呼び出しています。(関係ないですがTraceCompat便利そうですね、、)


```java
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        eatRequestLayout();
        TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
        dispatchLayout();
        TraceCompat.endSection();
        resumeRequestLayout(false);
        mFirstLayoutComplete = true;
    }
```

RecyclerView#dispatchLayout();

```java
    void dispatchLayout() {
...
        mState.mItemCount = mAdapter.getItemCount();
```
さっそくmAdapterからアイテムの数を取得しました！これはRecyclerView.Adapterでいつも実装するやつです！
まだRecyclerView#dispatchLayout()です。

```java
...
        mLayout.onLayoutChildren(mRecycler, mState);
...
```
このmLayoutはLayoutManagerです。

LinearLayoutManager#onLayoutChildren()をみていきましょう

```java
 public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {

...
        if (mAnchorInfo.mLayoutFromEnd) {
...
        } else {
//レイアウトの指定の仕方は普通LayoutFromEndにしないとおもうのでこちらです。
            fill(recycler, mLayoutState, state, false);
```
RecyclerView.LayoutStateクラスでfillするときの状態を管理するみたいです

```java
    /**
     * Helper class that keeps temporary state while {LayoutManager} is filling out the empty
     * space.
     */
    static class LayoutState {
```


LinearLayoutManager#fillを呼び出します。

```
    /**
     * The magic functions :). Fills the given layout, defined by the layoutState. This is fairly
     * independent from the rest of the {@link android.support.v7.widget.LinearLayoutManager}
     * and with little change, can be made publicly available as a helper class.
     *
     * @param recycler        Current recycler that is attached to RecyclerView
     * @param layoutState     Configuration on how we should fill out the available space.
     * @param state           Context passed by the RecyclerView to control scroll steps.
     * @param stopOnFocusable If true, filling stops in the first focusable new child
     * @return Number of pixels that it added. Useful for scoll functions.
     */
    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
...
        while (remainingSpace > 0 && layoutState.hasMore(state)) {
...
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
...
```

ここのwhileで子View一個一個を実際にレイアウトしていきます！！
remainingSpace > 0で残りの表示スペースが有るかどうかと、次のデータがあるかをlayoutState.hasMoreで聞いて、なければwhileが終わります。

LayoutManager#layoutChunk()を呼び出します。
このメソッド内でViewのアイテムのほとんどの処理を行います。

```java
    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        View view = layoutState.next(recycler);
```

LayoutState#next()

```java
        View next(RecyclerView.Recycler recycler) {
...
            final View view = recycler.getViewForPosition(mCurrentPosition);
            mCurrentPosition += mItemDirection;
            return view;
        }
```

Recycler#getViewForPosition()
分かりやすい名前ですが、これはとてもでかいメソッドです。
scrapという再利用するViewHolderを保持しておくキャッシュを駆使してViewを返そうとします。

```java
        View getViewForPosition(int position, boolean dryRun) {
...
            ViewHolder holder = null;
...
            if (holder == null) {
                holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
...
```

getScrapViewForPositionを実行しますがScrap(RecyclerViewのViewHolderのキャッシュ)がないのでholderはnullです。


Recycler#getViewForPosition()続き

```java
...
            if (holder == null) {
...
                final int type = mAdapter.getItemViewType(offsetPosition);
```


ここでAdapterからgetItemViewTypeしてViewTypeを取得します。
RecyclerViewにはViewの種類を管理する仕組みがあり、その仕組のためのメソッドです。

Recycler#getViewForPosition()続き

```
...
    holder = getRecycledViewPool().getRecycledView(type);
...
```
こちらもscrapから取り出そうとする処理のようですが、これを行った後も、まだnullです。

Recycler#getViewForPosition()続き

```java
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
```
やっとAdapterのonCreateViewHolderを呼び出すためのメソッドが来ました！

RecyclerView.Adapter#createViewHolder

```java
        public final VH createViewHolder(ViewGroup parent, int viewType) {
            TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
            final VH holder = onCreateViewHolder(parent, viewType);
            holder.mItemViewType = viewType;
            TraceCompat.endSection();
            return holder;
        }
```

これで**RecyclerView.Adapterを継承したクラスでonCreateViewHolderが呼びだされました！**
holder.mItemViewTypeを代入しています。


# 子Viewにデータをバインドするまで(Adapter.onBindViewHolderが呼ばれるまで)

Recycler#getViewForPosition()続き

```java
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                holder.mOwnerRecyclerView = RecyclerView.this;
                mAdapter.bindViewHolder(holder, offsetPosition);
```

mAdapterHelper.findPositionOffsetこちらのメソッドでpostponed？になっている数を除いてpositionが取得できて、**`RecyclerView.Adapter#bindViewHolder`を呼び出してデータをバインドします！**

# Viewの大きさを決定する

```java
            final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
            final LayoutParams rvLayoutParams;
...
                rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
                holder.itemView.setLayoutParams(rvLayoutParams);
...
            rvLayoutParams.mViewHolder = holder;
...
            return holder.itemView;
```

大体は普通にLayoutParamsを作成してセットするのみです。特別なことは多分この時点ではしていないと思います。
またここでLayoutParamsにViewHolderをセットしています。


ここでLinearLayoutManager#layoutChunk()まで戻ってきました。
`layoutState.next(recycler)`でAdapterを使ってViewを作って、データがバインドされたViewが取得されました。


```java
    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
...
        View view = layoutState.next(recycler); // ←ここまででクリア！
...
        LayoutParams params = (LayoutParams) view.getLayoutParams();
...
                addView(view);
```
これでやっとレイアウトに追加されました
LinearLayoutManager#layoutChunk内

```java
        measureChildWithMargins(view, 0, 0);
```

```java
        public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
            widthUsed += insets.left + insets.right;
            heightUsed += insets.top + insets.bottom;

            final int widthSpec = getChildMeasureSpec(getWidth(),
                    getPaddingLeft() + getPaddingRight() +
                            lp.leftMargin + lp.rightMargin + widthUsed, lp.width,
                    canScrollHorizontally());
            final int heightSpec = getChildMeasureSpec(getHeight(),
                    getPaddingTop() + getPaddingBottom() +
                            lp.topMargin + lp.bottomMargin + heightUsed, lp.height,
                    canScrollVertically());
            child.measure(widthSpec, heightSpec);
        }
```

子Viewのレイアウトの大きさがこのコードで決まります。
`RecyclerView#getItemDecorInsetsForChild`を呼び出します



```java
    Rect getItemDecorInsetsForChild(View child) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (!lp.mInsetsDirty) {
            return lp.mDecorInsets;
        }

        final Rect insets = lp.mDecorInsets;
        insets.set(0, 0, 0, 0);
        final int decorCount = mItemDecorations.size();
        for (int i = 0; i < decorCount; i++) {
            mTempRect.set(0, 0, 0, 0);
            mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
            insets.left += mTempRect.left;
            insets.top += mTempRect.top;
            insets.right += mTempRect.right;
            insets.bottom += mTempRect.bottom;
        }
        lp.mInsetsDirty = false;
        return insets;
    }
```
これで、ItemDecoratorのレイアウトを反映した子Viewの大きさの設定を行います。
これで**子Viewの大きさが決定されました**


# Viewの位置(x, y)を決定する


LinearLayoutManager#layoutChunk内

```java
        result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
        int left, top, right, bottom;
        if (mOrientation == VERTICAL) {
            if (isLayoutRTL()) {
...
            } else {
                left = getPaddingLeft();
                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
...
```
result.mConsumedはViewの高さになるメソッドで先ほど決まった大きさを利用しているようです。

OrientationHelper.getDecoratedMeasurementInOtherで先ほど大きさが決定したのを考慮して、横幅を取得して、右と左の場所を決定しました。

```java
                top = layoutState.mOffset;
                bottom = layoutState.mOffset + result.mConsumed;
```


layoutStateが持っていたmOffsetでyが決まります。ちなみにこのmOffsetはfillメソッド内でlayoutChunkが終わった後更新されています。


LinearLayoutManager#layoutChunk続き

```java
        layoutDecorated(view, left + params.leftMargin, top + params.topMargin,
                right - params.rightMargin, bottom - params.bottomMargin);
```

```java
        public void layoutDecorated(View child, int left, int top, int right, int bottom) {
            final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
            child.layout(left + insets.left, top + insets.top, right - insets.right,
                    bottom - insets.bottom);
        }
```


やっと、、**子Viewがレイアウトされました**、、

これをLinearLayoutManager#fillメソッド内で必要なViewの数、ぐるぐる回してlayoutChunkを呼ぶことで、Layoutしていっているようです。

多分ここまで読んでくれた人はいないと思いますが、お疲れ様でした

# まとめ
ViewGroup#onLayoutから始まって、
RecyclerView.Adapterからアイテム数を取得して、
RecyclerView.AdapterからViewを取得して、
LayoutStateでレイアウト中の状態を管理して、
データをBindして、addViewして、
ItemDecolatorのレイアウトを経てレイアウトされました。

LayoutManagerはレイアウト関連のところを行う。
RecyclerViewがリサイクル関連の部分を管理している。
などが役割が分かれているのは名前で分かっていましたが少し具体的に知ることができました。

ここは間違っているとか、こういう意味だとか、RecyclerView内でここのコードを読むのをおすすめするよとか、そういったことがございましたらお教えください！！