GridLayoutManager.java revision 5db6d414ed0329b4f5464a92d1a7f8e6d8bb1678
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14package android.support.v17.leanback.widget;
15
16import static android.support.v7.widget.RecyclerView.HORIZONTAL;
17import static android.support.v7.widget.RecyclerView.NO_ID;
18import static android.support.v7.widget.RecyclerView.NO_POSITION;
19import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
20import static android.support.v7.widget.RecyclerView.VERTICAL;
21
22import android.content.Context;
23import android.graphics.PointF;
24import android.graphics.Rect;
25import android.os.Bundle;
26import android.os.Parcel;
27import android.os.Parcelable;
28import android.support.annotation.VisibleForTesting;
29import android.support.v4.os.TraceCompat;
30import android.support.v4.util.CircularIntArray;
31import android.support.v4.view.ViewCompat;
32import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
33import android.support.v7.widget.LinearSmoothScroller;
34import android.support.v7.widget.OrientationHelper;
35import android.support.v7.widget.RecyclerView;
36import android.support.v7.widget.RecyclerView.Recycler;
37import android.support.v7.widget.RecyclerView.State;
38import android.util.AttributeSet;
39import android.util.Log;
40import android.util.SparseIntArray;
41import android.view.FocusFinder;
42import android.view.Gravity;
43import android.view.View;
44import android.view.View.MeasureSpec;
45import android.view.ViewGroup;
46import android.view.ViewGroup.MarginLayoutParams;
47import android.view.animation.AccelerateDecelerateInterpolator;
48
49import java.io.PrintWriter;
50import java.io.StringWriter;
51import java.util.ArrayList;
52import java.util.Arrays;
53import java.util.List;
54
55final class GridLayoutManager extends RecyclerView.LayoutManager {
56
57    /*
58     * LayoutParams for {@link HorizontalGridView} and {@link VerticalGridView}.
59     * The class currently does two internal jobs:
60     * - Saves optical bounds insets.
61     * - Caches focus align view center.
62     */
63    final static class LayoutParams extends RecyclerView.LayoutParams {
64
65        // For placement
66        int mLeftInset;
67        int mTopInset;
68        int mRightInset;
69        int mBottomInset;
70
71        // For alignment
72        private int mAlignX;
73        private int mAlignY;
74        private int[] mAlignMultiple;
75        private ItemAlignmentFacet mAlignmentFacet;
76
77        public LayoutParams(Context c, AttributeSet attrs) {
78            super(c, attrs);
79        }
80
81        public LayoutParams(int width, int height) {
82            super(width, height);
83        }
84
85        public LayoutParams(MarginLayoutParams source) {
86            super(source);
87        }
88
89        public LayoutParams(ViewGroup.LayoutParams source) {
90            super(source);
91        }
92
93        public LayoutParams(RecyclerView.LayoutParams source) {
94            super(source);
95        }
96
97        public LayoutParams(LayoutParams source) {
98            super(source);
99        }
100
101        int getAlignX() {
102            return mAlignX;
103        }
104
105        int getAlignY() {
106            return mAlignY;
107        }
108
109        int getOpticalLeft(View view) {
110            return view.getLeft() + mLeftInset;
111        }
112
113        int getOpticalTop(View view) {
114            return view.getTop() + mTopInset;
115        }
116
117        int getOpticalRight(View view) {
118            return view.getRight() - mRightInset;
119        }
120
121        int getOpticalBottom(View view) {
122            return view.getBottom() - mBottomInset;
123        }
124
125        int getOpticalWidth(View view) {
126            return view.getWidth() - mLeftInset - mRightInset;
127        }
128
129        int getOpticalHeight(View view) {
130            return view.getHeight() - mTopInset - mBottomInset;
131        }
132
133        int getOpticalLeftInset() {
134            return mLeftInset;
135        }
136
137        int getOpticalRightInset() {
138            return mRightInset;
139        }
140
141        int getOpticalTopInset() {
142            return mTopInset;
143        }
144
145        int getOpticalBottomInset() {
146            return mBottomInset;
147        }
148
149        void setAlignX(int alignX) {
150            mAlignX = alignX;
151        }
152
153        void setAlignY(int alignY) {
154            mAlignY = alignY;
155        }
156
157        void setItemAlignmentFacet(ItemAlignmentFacet facet) {
158            mAlignmentFacet = facet;
159        }
160
161        ItemAlignmentFacet getItemAlignmentFacet() {
162            return mAlignmentFacet;
163        }
164
165        void calculateItemAlignments(int orientation, View view) {
166            ItemAlignmentFacet.ItemAlignmentDef[] defs = mAlignmentFacet.getAlignmentDefs();
167            if (mAlignMultiple == null || mAlignMultiple.length != defs.length) {
168                mAlignMultiple = new int[defs.length];
169            }
170            for (int i = 0; i < defs.length; i++) {
171                mAlignMultiple[i] = ItemAlignmentFacetHelper
172                        .getAlignmentPosition(view, defs[i], orientation);
173            }
174            if (orientation == HORIZONTAL) {
175                mAlignX = mAlignMultiple[0];
176            } else {
177                mAlignY = mAlignMultiple[0];
178            }
179        }
180
181        int[] getAlignMultiple() {
182            return mAlignMultiple;
183        }
184
185        void setOpticalInsets(int leftInset, int topInset, int rightInset, int bottomInset) {
186            mLeftInset = leftInset;
187            mTopInset = topInset;
188            mRightInset = rightInset;
189            mBottomInset = bottomInset;
190        }
191
192    }
193
194    /**
195     * Base class which scrolls to selected view in onStop().
196     */
197    abstract class GridLinearSmoothScroller extends LinearSmoothScroller {
198        GridLinearSmoothScroller() {
199            super(mBaseGridView.getContext());
200        }
201
202        @Override
203        protected void onStop() {
204            // onTargetFound() may not be called if we hit the "wall" first or get cancelled.
205            View targetView = findViewByPosition(getTargetPosition());
206            if (targetView == null) {
207                if (getTargetPosition() >= 0) {
208                    // if smooth scroller is stopped without target, immediately jumps
209                    // to the target position.
210                    scrollToSelection(getTargetPosition(), 0, false, 0);
211                }
212                super.onStop();
213                return;
214            }
215            if (mFocusPosition != getTargetPosition()) {
216                // This should not happen since we cropped value in startPositionSmoothScroller()
217                mFocusPosition = getTargetPosition();
218            }
219            if (hasFocus()) {
220                mInSelection = true;
221                targetView.requestFocus();
222                mInSelection = false;
223            }
224            dispatchChildSelected();
225            dispatchChildSelectedAndPositioned();
226            super.onStop();
227        }
228
229        @Override
230        protected int calculateTimeForScrolling(int dx) {
231            int ms = super.calculateTimeForScrolling(dx);
232            if (mWindowAlignment.mainAxis().getSize() > 0) {
233                float minMs = (float) MIN_MS_SMOOTH_SCROLL_MAIN_SCREEN
234                        / mWindowAlignment.mainAxis().getSize() * dx;
235                if (ms < minMs) {
236                    ms = (int) minMs;
237                }
238            }
239            return ms;
240        }
241
242        @Override
243        protected void onTargetFound(View targetView,
244                RecyclerView.State state, Action action) {
245            if (getScrollPosition(targetView, null, sTwoInts)) {
246                int dx, dy;
247                if (mOrientation == HORIZONTAL) {
248                    dx = sTwoInts[0];
249                    dy = sTwoInts[1];
250                } else {
251                    dx = sTwoInts[1];
252                    dy = sTwoInts[0];
253                }
254                final int distance = (int) Math.sqrt(dx * dx + dy * dy);
255                final int time = calculateTimeForDeceleration(distance);
256                action.update(dx, dy, time, mDecelerateInterpolator);
257            }
258        }
259    }
260
261    /**
262     * The SmoothScroller that remembers pending DPAD keys and consume pending keys
263     * during scroll.
264     */
265    final class PendingMoveSmoothScroller extends GridLinearSmoothScroller {
266        // -2 is a target position that LinearSmoothScroller can never find until
267        // consumePendingMovesXXX() sets real targetPosition.
268        final static int TARGET_UNDEFINED = -2;
269        // whether the grid is staggered.
270        private final boolean mStaggeredGrid;
271        // Number of pending movements on primary direction, negative if PREV_ITEM.
272        private int mPendingMoves;
273
274        PendingMoveSmoothScroller(int initialPendingMoves, boolean staggeredGrid) {
275            mPendingMoves = initialPendingMoves;
276            mStaggeredGrid = staggeredGrid;
277            setTargetPosition(TARGET_UNDEFINED);
278        }
279
280        void increasePendingMoves() {
281            if (mPendingMoves < mMaxPendingMoves) {
282                mPendingMoves++;
283            }
284        }
285
286        void decreasePendingMoves() {
287            if (mPendingMoves > -mMaxPendingMoves) {
288                mPendingMoves--;
289            }
290        }
291
292        /**
293         * Called before laid out an item when non-staggered grid can handle pending movements
294         * by skipping "mNumRows" per movement;  staggered grid will have to wait the item
295         * has been laid out in consumePendingMovesAfterLayout().
296         */
297        void consumePendingMovesBeforeLayout() {
298            if (mStaggeredGrid || mPendingMoves == 0) {
299                return;
300            }
301            View newSelected = null;
302            int startPos = mPendingMoves > 0 ? mFocusPosition + mNumRows :
303                    mFocusPosition - mNumRows;
304            for (int pos = startPos; mPendingMoves != 0;
305                    pos = mPendingMoves > 0 ? pos + mNumRows: pos - mNumRows) {
306                View v = findViewByPosition(pos);
307                if (v == null) {
308                    break;
309                }
310                if (!canScrollTo(v)) {
311                    continue;
312                }
313                newSelected = v;
314                mFocusPosition = pos;
315                mSubFocusPosition = 0;
316                if (mPendingMoves > 0) {
317                    mPendingMoves--;
318                } else {
319                    mPendingMoves++;
320                }
321            }
322            if (newSelected != null && hasFocus()) {
323                mInSelection = true;
324                newSelected.requestFocus();
325                mInSelection = false;
326            }
327        }
328
329        /**
330         * Called after laid out an item.  Staggered grid should find view on same
331         * Row and consume pending movements.
332         */
333        void consumePendingMovesAfterLayout() {
334            if (mStaggeredGrid && mPendingMoves != 0) {
335                // consume pending moves, focus to item on the same row.
336                mPendingMoves = processSelectionMoves(true, mPendingMoves);
337            }
338            if (mPendingMoves == 0 || (mPendingMoves > 0 && hasCreatedLastItem())
339                    || (mPendingMoves < 0 && hasCreatedFirstItem())) {
340                setTargetPosition(mFocusPosition);
341                stop();
342            }
343        }
344
345        @Override
346        protected void updateActionForInterimTarget(Action action) {
347            if (mPendingMoves == 0) {
348                return;
349            }
350            super.updateActionForInterimTarget(action);
351        }
352
353        @Override
354        public PointF computeScrollVectorForPosition(int targetPosition) {
355            if (mPendingMoves == 0) {
356                return null;
357            }
358            int direction = (mReverseFlowPrimary ? mPendingMoves > 0 : mPendingMoves < 0)
359                    ? -1 : 1;
360            if (mOrientation == HORIZONTAL) {
361                return new PointF(direction, 0);
362            } else {
363                return new PointF(0, direction);
364            }
365        }
366
367        @Override
368        protected void onStop() {
369            super.onStop();
370            // if we hit wall,  need clear the remaining pending moves.
371            mPendingMoves = 0;
372            mPendingMoveSmoothScroller = null;
373            View v = findViewByPosition(getTargetPosition());
374            if (v != null) scrollToView(v, true);
375        }
376    };
377
378    private static final String TAG = "GridLayoutManager";
379    static final boolean DEBUG = false;
380    static final boolean TRACE = false;
381
382    // maximum pending movement in one direction.
383    static final int DEFAULT_MAX_PENDING_MOVES = 10;
384    int mMaxPendingMoves = DEFAULT_MAX_PENDING_MOVES;
385    // minimal milliseconds to scroll window size in major direction,  we put a cap to prevent the
386    // effect smooth scrolling too over to bind an item view then drag the item view back.
387    final static int MIN_MS_SMOOTH_SCROLL_MAIN_SCREEN = 30;
388
389    // Represents whether child views are temporarily sliding out
390    boolean mIsSlidingChildViews;
391    boolean mLayoutEatenInSliding;
392
393    String getTag() {
394        return TAG + ":" + mBaseGridView.getId();
395    }
396
397    final BaseGridView mBaseGridView;
398
399    /**
400     * Note on conventions in the presence of RTL layout directions:
401     * Many properties and method names reference entities related to the
402     * beginnings and ends of things.  In the presence of RTL flows,
403     * it may not be clear whether this is intended to reference a
404     * quantity that changes direction in RTL cases, or a quantity that
405     * does not.  Here are the conventions in use:
406     *
407     * start/end: coordinate quantities - do reverse
408     * (optical) left/right: coordinate quantities - do not reverse
409     * low/high: coordinate quantities - do not reverse
410     * min/max: coordinate quantities - do not reverse
411     * scroll offset - coordinate quantities - do not reverse
412     * first/last: positional indices - do not reverse
413     * front/end: positional indices - do not reverse
414     * prepend/append: related to positional indices - do not reverse
415     *
416     * Note that although quantities do not reverse in RTL flows, their
417     * relationship does.  In LTR flows, the first positional index is
418     * leftmost; in RTL flows, it is rightmost.  Thus, anywhere that
419     * positional quantities are mapped onto coordinate quantities,
420     * the flow must be checked and the logic reversed.
421     */
422
423    /**
424     * The orientation of a "row".
425     */
426    int mOrientation = HORIZONTAL;
427    private OrientationHelper mOrientationHelper = OrientationHelper.createHorizontalHelper(this);
428
429    RecyclerView.State mState;
430    // Suppose currently showing 4, 5, 6, 7; removing 2,3,4 will make the layoutPosition to be
431    // 2(deleted), 3, 4, 5 in prelayout pass. So when we add item in prelayout, we must subtract 2
432    // from index of Grid.createItem.
433    int mPositionDeltaInPreLayout;
434    // Extra layout space needs to fill in prelayout pass. Note we apply the extra space to both
435    // appends and prepends due to the fact leanback is doing mario scrolling: removing items to
436    // the left of focused item might need extra layout on the right.
437    int mExtraLayoutSpaceInPreLayout;
438    // mPositionToRowInPostLayout and mDisappearingPositions are temp variables in post layout.
439    final SparseIntArray mPositionToRowInPostLayout = new SparseIntArray();
440    int[] mDisappearingPositions;
441
442    RecyclerView.Recycler mRecycler;
443
444    private static final Rect sTempRect = new Rect();
445
446    boolean mInLayout;
447    private boolean mInScroll;
448    boolean mInFastRelayout;
449    /**
450     * During full layout pass, when GridView had focus: onLayoutChildren will
451     * skip non-focusable child and adjust mFocusPosition.
452     */
453    boolean mInLayoutSearchFocus;
454    boolean mInSelection = false;
455
456    private OnChildSelectedListener mChildSelectedListener = null;
457
458    private ArrayList<OnChildViewHolderSelectedListener> mChildViewHolderSelectedListeners = null;
459
460    OnChildLaidOutListener mChildLaidOutListener = null;
461
462    /**
463     * The focused position, it's not the currently visually aligned position
464     * but it is the final position that we intend to focus on. If there are
465     * multiple setSelection() called, mFocusPosition saves last value.
466     */
467    int mFocusPosition = NO_POSITION;
468
469    /**
470     * A view can have multiple alignment position,  this is the index of which
471     * alignment is used,  by default is 0.
472     */
473    int mSubFocusPosition = 0;
474
475    /**
476     * LinearSmoothScroller that consume pending DPAD movements.
477     */
478    PendingMoveSmoothScroller mPendingMoveSmoothScroller;
479
480    /**
481     * The offset to be applied to mFocusPosition, due to adapter change, on the next
482     * layout.  Set to Integer.MIN_VALUE means we should stop adding delta to mFocusPosition
483     * until next layout cycler.
484     * TODO:  This is somewhat duplication of RecyclerView getOldPosition() which is
485     * unfortunately cleared after prelayout.
486     */
487    private int mFocusPositionOffset = 0;
488
489    /**
490     * Extra pixels applied on primary direction.
491     */
492    private int mPrimaryScrollExtra;
493
494    /**
495     * Force a full layout under certain situations.  E.g. Rows change, jump to invisible child.
496     */
497    private boolean mForceFullLayout;
498
499    /**
500     * True if layout is enabled.
501     */
502    private boolean mLayoutEnabled = true;
503
504    /**
505     * override child visibility
506     */
507    int mChildVisibility = -1;
508
509    /**
510     * Pixels that scrolled in secondary forward direction. Negative value means backward.
511     * Note that we treat secondary differently than main. For the main axis, update scroll min/max
512     * based on first/last item's view location. For second axis, we don't use item's view location.
513     * We are using the {@link #getRowSizeSecondary(int)} plus mScrollOffsetSecondary. see
514     * details in {@link #updateSecondaryScrollLimits()}.
515     */
516    int mScrollOffsetSecondary;
517
518    /**
519     * User-specified row height/column width.  Can be WRAP_CONTENT.
520     */
521    private int mRowSizeSecondaryRequested;
522
523    /**
524     * The fixed size of each grid item in the secondary direction. This corresponds to
525     * the row height, equal for all rows. Grid items may have variable length
526     * in the primary direction.
527     */
528    private int mFixedRowSizeSecondary;
529
530    /**
531     * Tracks the secondary size of each row.
532     */
533    private int[] mRowSizeSecondary;
534
535    /**
536     * Flag controlling whether the current/next layout should
537     * be updating the secondary size of rows.
538     */
539    private boolean mRowSecondarySizeRefresh;
540
541    /**
542     * The maximum measured size of the view.
543     */
544    private int mMaxSizeSecondary;
545
546    /**
547     * Margin between items.
548     */
549    private int mHorizontalSpacing;
550    /**
551     * Margin between items vertically.
552     */
553    private int mVerticalSpacing;
554    /**
555     * Margin in main direction.
556     */
557    private int mSpacingPrimary;
558    /**
559     * Margin in second direction.
560     */
561    private int mSpacingSecondary;
562    /**
563     * How to position child in secondary direction.
564     */
565    private int mGravity = Gravity.START | Gravity.TOP;
566    /**
567     * The number of rows in the grid.
568     */
569    int mNumRows;
570    /**
571     * Number of rows requested, can be 0 to be determined by parent size and
572     * rowHeight.
573     */
574    private int mNumRowsRequested = 1;
575
576    /**
577     * Saves grid information of each view.
578     */
579    Grid mGrid;
580
581    /**
582     * Focus Scroll strategy.
583     */
584    private int mFocusScrollStrategy = BaseGridView.FOCUS_SCROLL_ALIGNED;
585    /**
586     * Defines how item view is aligned in the window.
587     */
588    final WindowAlignment mWindowAlignment = new WindowAlignment();
589
590    /**
591     * Defines how item view is aligned.
592     */
593    private final ItemAlignment mItemAlignment = new ItemAlignment();
594
595    /**
596     * Dimensions of the view, width or height depending on orientation.
597     */
598    private int mSizePrimary;
599
600    /**
601     * Pixels of extra space for layout item (outside the widget)
602     */
603    private int mExtraLayoutSpace;
604
605    /**
606     *  Allow DPAD key to navigate out at the front of the View (where position = 0),
607     *  default is false.
608     */
609    private boolean mFocusOutFront;
610
611    /**
612     * Allow DPAD key to navigate out at the end of the view, default is false.
613     */
614    private boolean mFocusOutEnd;
615
616    /**
617     *  Allow DPAD key to navigate out of second axis.
618     *  default is true.
619     */
620    private boolean mFocusOutSideStart = true;
621
622    /**
623     * Allow DPAD key to navigate out of second axis.
624     */
625    private boolean mFocusOutSideEnd = true;
626
627    /**
628     * True if focus search is disabled.
629     */
630    private boolean mFocusSearchDisabled;
631
632    /**
633     * True if prune child,  might be disabled during transition.
634     */
635    private boolean mPruneChild = true;
636
637    /**
638     * True if scroll content,  might be disabled during transition.
639     */
640    private boolean mScrollEnabled = true;
641
642    /**
643     * Temporary variable: an int array of length=2.
644     */
645    static int[] sTwoInts = new int[2];
646
647    /**
648     * Set to true for RTL layout in horizontal orientation
649     */
650    boolean mReverseFlowPrimary = false;
651
652    /**
653     * Set to true for RTL layout in vertical orientation
654     */
655    private boolean mReverseFlowSecondary = false;
656
657    /**
658     * Temporaries used for measuring.
659     */
660    private int[] mMeasuredDimension = new int[2];
661
662    final ViewsStateBundle mChildrenStates = new ViewsStateBundle();
663
664    /**
665     * Optional interface implemented by Adapter.
666     */
667    private FacetProviderAdapter mFacetProviderAdapter;
668
669    public GridLayoutManager(BaseGridView baseGridView) {
670        mBaseGridView = baseGridView;
671    }
672
673    public void setOrientation(int orientation) {
674        if (orientation != HORIZONTAL && orientation != VERTICAL) {
675            if (DEBUG) Log.v(getTag(), "invalid orientation: " + orientation);
676            return;
677        }
678
679        mOrientation = orientation;
680        mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation);
681        mWindowAlignment.setOrientation(orientation);
682        mItemAlignment.setOrientation(orientation);
683        mForceFullLayout = true;
684    }
685
686    public void onRtlPropertiesChanged(int layoutDirection) {
687        boolean reversePrimary, reverseSecondary;
688        if (mOrientation == HORIZONTAL) {
689            reversePrimary = layoutDirection == View.LAYOUT_DIRECTION_RTL;
690            reverseSecondary = false;
691        } else {
692            reverseSecondary = layoutDirection == View.LAYOUT_DIRECTION_RTL;
693            reversePrimary = false;
694        }
695        if (mReverseFlowPrimary == reversePrimary && mReverseFlowSecondary == reverseSecondary) {
696            return;
697        }
698        mReverseFlowPrimary = reversePrimary;
699        mReverseFlowSecondary = reverseSecondary;
700        mForceFullLayout = true;
701        mWindowAlignment.horizontal.setReversedFlow(layoutDirection == View.LAYOUT_DIRECTION_RTL);
702    }
703
704    public int getFocusScrollStrategy() {
705        return mFocusScrollStrategy;
706    }
707
708    public void setFocusScrollStrategy(int focusScrollStrategy) {
709        mFocusScrollStrategy = focusScrollStrategy;
710    }
711
712    public void setWindowAlignment(int windowAlignment) {
713        mWindowAlignment.mainAxis().setWindowAlignment(windowAlignment);
714    }
715
716    public int getWindowAlignment() {
717        return mWindowAlignment.mainAxis().getWindowAlignment();
718    }
719
720    public void setWindowAlignmentOffset(int alignmentOffset) {
721        mWindowAlignment.mainAxis().setWindowAlignmentOffset(alignmentOffset);
722    }
723
724    public int getWindowAlignmentOffset() {
725        return mWindowAlignment.mainAxis().getWindowAlignmentOffset();
726    }
727
728    public void setWindowAlignmentOffsetPercent(float offsetPercent) {
729        mWindowAlignment.mainAxis().setWindowAlignmentOffsetPercent(offsetPercent);
730    }
731
732    public float getWindowAlignmentOffsetPercent() {
733        return mWindowAlignment.mainAxis().getWindowAlignmentOffsetPercent();
734    }
735
736    public void setItemAlignmentOffset(int alignmentOffset) {
737        mItemAlignment.mainAxis().setItemAlignmentOffset(alignmentOffset);
738        updateChildAlignments();
739    }
740
741    public int getItemAlignmentOffset() {
742        return mItemAlignment.mainAxis().getItemAlignmentOffset();
743    }
744
745    public void setItemAlignmentOffsetWithPadding(boolean withPadding) {
746        mItemAlignment.mainAxis().setItemAlignmentOffsetWithPadding(withPadding);
747        updateChildAlignments();
748    }
749
750    public boolean isItemAlignmentOffsetWithPadding() {
751        return mItemAlignment.mainAxis().isItemAlignmentOffsetWithPadding();
752    }
753
754    public void setItemAlignmentOffsetPercent(float offsetPercent) {
755        mItemAlignment.mainAxis().setItemAlignmentOffsetPercent(offsetPercent);
756        updateChildAlignments();
757    }
758
759    public float getItemAlignmentOffsetPercent() {
760        return mItemAlignment.mainAxis().getItemAlignmentOffsetPercent();
761    }
762
763    public void setItemAlignmentViewId(int viewId) {
764        mItemAlignment.mainAxis().setItemAlignmentViewId(viewId);
765        updateChildAlignments();
766    }
767
768    public int getItemAlignmentViewId() {
769        return mItemAlignment.mainAxis().getItemAlignmentViewId();
770    }
771
772    public void setFocusOutAllowed(boolean throughFront, boolean throughEnd) {
773        mFocusOutFront = throughFront;
774        mFocusOutEnd = throughEnd;
775    }
776
777    public void setFocusOutSideAllowed(boolean throughStart, boolean throughEnd) {
778        mFocusOutSideStart = throughStart;
779        mFocusOutSideEnd = throughEnd;
780    }
781
782    public void setNumRows(int numRows) {
783        if (numRows < 0) throw new IllegalArgumentException();
784        mNumRowsRequested = numRows;
785    }
786
787    /**
788     * Set the row height. May be WRAP_CONTENT, or a size in pixels.
789     */
790    public void setRowHeight(int height) {
791        if (height >= 0 || height == ViewGroup.LayoutParams.WRAP_CONTENT) {
792            mRowSizeSecondaryRequested = height;
793        } else {
794            throw new IllegalArgumentException("Invalid row height: " + height);
795        }
796    }
797
798    public void setItemSpacing(int space) {
799        mVerticalSpacing = mHorizontalSpacing = space;
800        mSpacingPrimary = mSpacingSecondary = space;
801    }
802
803    public void setVerticalSpacing(int space) {
804        if (mOrientation == VERTICAL) {
805            mSpacingPrimary = mVerticalSpacing = space;
806        } else {
807            mSpacingSecondary = mVerticalSpacing = space;
808        }
809    }
810
811    public void setHorizontalSpacing(int space) {
812        if (mOrientation == HORIZONTAL) {
813            mSpacingPrimary = mHorizontalSpacing = space;
814        } else {
815            mSpacingSecondary = mHorizontalSpacing = space;
816        }
817    }
818
819    public int getVerticalSpacing() {
820        return mVerticalSpacing;
821    }
822
823    public int getHorizontalSpacing() {
824        return mHorizontalSpacing;
825    }
826
827    public void setGravity(int gravity) {
828        mGravity = gravity;
829    }
830
831    protected boolean hasDoneFirstLayout() {
832        return mGrid != null;
833    }
834
835    public void setOnChildSelectedListener(OnChildSelectedListener listener) {
836        mChildSelectedListener = listener;
837    }
838
839    public void setOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener) {
840        if (listener == null) {
841            mChildViewHolderSelectedListeners = null;
842            return;
843        }
844        if (mChildViewHolderSelectedListeners == null) {
845            mChildViewHolderSelectedListeners = new ArrayList<OnChildViewHolderSelectedListener>();
846        } else {
847            mChildViewHolderSelectedListeners.clear();
848        }
849        mChildViewHolderSelectedListeners.add(listener);
850    }
851
852    public void addOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener) {
853        if (mChildViewHolderSelectedListeners == null) {
854            mChildViewHolderSelectedListeners = new ArrayList<OnChildViewHolderSelectedListener>();
855        }
856        mChildViewHolderSelectedListeners.add(listener);
857    }
858
859    public void removeOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener
860            listener) {
861        if (mChildViewHolderSelectedListeners != null) {
862            mChildViewHolderSelectedListeners.remove(listener);
863        }
864    }
865
866    boolean hasOnChildViewHolderSelectedListener() {
867        return mChildViewHolderSelectedListeners != null
868                && mChildViewHolderSelectedListeners.size() > 0;
869    }
870
871    void fireOnChildViewHolderSelected(RecyclerView parent, RecyclerView.ViewHolder child,
872            int position, int subposition) {
873        if (mChildViewHolderSelectedListeners == null) {
874            return;
875        }
876        for (int i = mChildViewHolderSelectedListeners.size() - 1; i >= 0 ; i--) {
877            mChildViewHolderSelectedListeners.get(i).onChildViewHolderSelected(parent, child,
878                    position, subposition);
879        }
880    }
881
882    void fireOnChildViewHolderSelectedAndPositioned(RecyclerView parent, RecyclerView.ViewHolder
883            child, int position, int subposition) {
884        if (mChildViewHolderSelectedListeners == null) {
885            return;
886        }
887        for (int i = mChildViewHolderSelectedListeners.size() - 1; i >= 0 ; i--) {
888            mChildViewHolderSelectedListeners.get(i).onChildViewHolderSelectedAndPositioned(parent,
889                    child, position, subposition);
890        }
891    }
892
893    void setOnChildLaidOutListener(OnChildLaidOutListener listener) {
894        mChildLaidOutListener = listener;
895    }
896
897    private int getAdapterPositionByView(View view) {
898        if (view == null) {
899            return NO_POSITION;
900        }
901        LayoutParams params = (LayoutParams) view.getLayoutParams();
902        if (params == null || params.isItemRemoved()) {
903            // when item is removed, the position value can be any value.
904            return NO_POSITION;
905        }
906        return params.getViewAdapterPosition();
907    }
908
909    int getSubPositionByView(View view, View childView) {
910        if (view == null || childView == null) {
911            return 0;
912        }
913        final LayoutParams lp = (LayoutParams) view.getLayoutParams();
914        final ItemAlignmentFacet facet = lp.getItemAlignmentFacet();
915        if (facet != null) {
916            final ItemAlignmentFacet.ItemAlignmentDef[] defs = facet.getAlignmentDefs();
917            if (defs.length > 1) {
918                while (childView != view) {
919                    int id = childView.getId();
920                    if (id != View.NO_ID) {
921                        for (int i = 1; i < defs.length; i++) {
922                            if (defs[i].getItemAlignmentFocusViewId() == id) {
923                                return i;
924                            }
925                        }
926                    }
927                    childView = (View) childView.getParent();
928                }
929            }
930        }
931        return 0;
932    }
933
934    private int getAdapterPositionByIndex(int index) {
935        return getAdapterPositionByView(getChildAt(index));
936    }
937
938    void dispatchChildSelected() {
939        if (mChildSelectedListener == null && !hasOnChildViewHolderSelectedListener()) {
940            return;
941        }
942
943        if (TRACE) TraceCompat.beginSection("onChildSelected");
944        View view = mFocusPosition == NO_POSITION ? null : findViewByPosition(mFocusPosition);
945        if (view != null) {
946            RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(view);
947            if (mChildSelectedListener != null) {
948                mChildSelectedListener.onChildSelected(mBaseGridView, view, mFocusPosition,
949                        vh == null? NO_ID: vh.getItemId());
950            }
951            fireOnChildViewHolderSelected(mBaseGridView, vh, mFocusPosition, mSubFocusPosition);
952        } else {
953            if (mChildSelectedListener != null) {
954                mChildSelectedListener.onChildSelected(mBaseGridView, null, NO_POSITION, NO_ID);
955            }
956            fireOnChildViewHolderSelected(mBaseGridView, null, NO_POSITION, 0);
957        }
958        if (TRACE) TraceCompat.endSection();
959
960        // Children may request layout when a child selection event occurs (such as a change of
961        // padding on the current and previously selected rows).
962        // If in layout, a child requesting layout may have been laid out before the selection
963        // callback.
964        // If it was not, the child will be laid out after the selection callback.
965        // If so, the layout request will be honoured though the view system will emit a double-
966        // layout warning.
967        // If not in layout, we may be scrolling in which case the child layout request will be
968        // eaten by recyclerview.  Post a requestLayout.
969        if (!mInLayout && !mBaseGridView.isLayoutRequested()) {
970            int childCount = getChildCount();
971            for (int i = 0; i < childCount; i++) {
972                if (getChildAt(i).isLayoutRequested()) {
973                    forceRequestLayout();
974                    break;
975                }
976            }
977        }
978    }
979
980    private void dispatchChildSelectedAndPositioned() {
981        if (!hasOnChildViewHolderSelectedListener()) {
982            return;
983        }
984
985        if (TRACE) TraceCompat.beginSection("onChildSelectedAndPositioned");
986        View view = mFocusPosition == NO_POSITION ? null : findViewByPosition(mFocusPosition);
987        if (view != null) {
988            RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(view);
989            fireOnChildViewHolderSelectedAndPositioned(mBaseGridView, vh, mFocusPosition,
990                    mSubFocusPosition);
991        } else {
992            if (mChildSelectedListener != null) {
993                mChildSelectedListener.onChildSelected(mBaseGridView, null, NO_POSITION, NO_ID);
994            }
995            fireOnChildViewHolderSelectedAndPositioned(mBaseGridView, null, NO_POSITION, 0);
996        }
997        if (TRACE) TraceCompat.endSection();
998
999    }
1000
1001    @Override
1002    public boolean canScrollHorizontally() {
1003        // We can scroll horizontally if we have horizontal orientation, or if
1004        // we are vertical and have more than one column.
1005        return mOrientation == HORIZONTAL || mNumRows > 1;
1006    }
1007
1008    @Override
1009    public boolean canScrollVertically() {
1010        // We can scroll vertically if we have vertical orientation, or if we
1011        // are horizontal and have more than one row.
1012        return mOrientation == VERTICAL || mNumRows > 1;
1013    }
1014
1015    @Override
1016    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
1017        return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
1018                ViewGroup.LayoutParams.WRAP_CONTENT);
1019    }
1020
1021    @Override
1022    public RecyclerView.LayoutParams generateLayoutParams(Context context, AttributeSet attrs) {
1023        return new LayoutParams(context, attrs);
1024    }
1025
1026    @Override
1027    public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
1028        if (lp instanceof LayoutParams) {
1029            return new LayoutParams((LayoutParams) lp);
1030        } else if (lp instanceof RecyclerView.LayoutParams) {
1031            return new LayoutParams((RecyclerView.LayoutParams) lp);
1032        } else if (lp instanceof MarginLayoutParams) {
1033            return new LayoutParams((MarginLayoutParams) lp);
1034        } else {
1035            return new LayoutParams(lp);
1036        }
1037    }
1038
1039    protected View getViewForPosition(int position) {
1040        return mRecycler.getViewForPosition(position);
1041    }
1042
1043    final int getOpticalLeft(View v) {
1044        return ((LayoutParams) v.getLayoutParams()).getOpticalLeft(v);
1045    }
1046
1047    final int getOpticalRight(View v) {
1048        return ((LayoutParams) v.getLayoutParams()).getOpticalRight(v);
1049    }
1050
1051    final int getOpticalTop(View v) {
1052        return ((LayoutParams) v.getLayoutParams()).getOpticalTop(v);
1053    }
1054
1055    final int getOpticalBottom(View v) {
1056        return ((LayoutParams) v.getLayoutParams()).getOpticalBottom(v);
1057    }
1058
1059    @Override
1060    public int getDecoratedLeft(View child) {
1061        return super.getDecoratedLeft(child) + ((LayoutParams) child.getLayoutParams()).mLeftInset;
1062    }
1063
1064    @Override
1065    public int getDecoratedTop(View child) {
1066        return super.getDecoratedTop(child) + ((LayoutParams) child.getLayoutParams()).mTopInset;
1067    }
1068
1069    @Override
1070    public int getDecoratedRight(View child) {
1071        return super.getDecoratedRight(child)
1072                - ((LayoutParams) child.getLayoutParams()).mRightInset;
1073    }
1074
1075    @Override
1076    public int getDecoratedBottom(View child) {
1077        return super.getDecoratedBottom(child)
1078                - ((LayoutParams) child.getLayoutParams()).mBottomInset;
1079    }
1080
1081    @Override
1082    public void getDecoratedBoundsWithMargins(View view, Rect outBounds) {
1083        super.getDecoratedBoundsWithMargins(view, outBounds);
1084        LayoutParams params = ((LayoutParams) view.getLayoutParams());
1085        outBounds.left += params.mLeftInset;
1086        outBounds.top += params.mTopInset;
1087        outBounds.right -= params.mRightInset;
1088        outBounds.bottom -= params.mBottomInset;
1089    }
1090
1091    int getViewMin(View v) {
1092        return mOrientationHelper.getDecoratedStart(v);
1093    }
1094
1095    int getViewMax(View v) {
1096        return mOrientationHelper.getDecoratedEnd(v);
1097    }
1098
1099    int getViewPrimarySize(View view) {
1100        getDecoratedBoundsWithMargins(view, sTempRect);
1101        return mOrientation == HORIZONTAL ? sTempRect.width() : sTempRect.height();
1102    }
1103
1104    private int getViewCenter(View view) {
1105        return (mOrientation == HORIZONTAL) ? getViewCenterX(view) : getViewCenterY(view);
1106    }
1107
1108    private int getAdjustedViewCenter(View view) {
1109        if (view.hasFocus()) {
1110            View child = view.findFocus();
1111            if (child != null && child != view) {
1112                return getAdjustedPrimaryAlignedScrollDistance(getViewCenter(view), view, child);
1113            }
1114        }
1115        return getViewCenter(view);
1116    }
1117
1118    private int getViewCenterSecondary(View view) {
1119        return (mOrientation == HORIZONTAL) ? getViewCenterY(view) : getViewCenterX(view);
1120    }
1121
1122    private int getViewCenterX(View v) {
1123        LayoutParams p = (LayoutParams) v.getLayoutParams();
1124        return p.getOpticalLeft(v) + p.getAlignX();
1125    }
1126
1127    private int getViewCenterY(View v) {
1128        LayoutParams p = (LayoutParams) v.getLayoutParams();
1129        return p.getOpticalTop(v) + p.getAlignY();
1130    }
1131
1132    /**
1133     * Save Recycler and State for convenience.  Must be paired with leaveContext().
1134     */
1135    private void saveContext(Recycler recycler, State state) {
1136        if (mRecycler != null || mState != null) {
1137            Log.e(TAG, "Recycler information was not released, bug!");
1138        }
1139        mRecycler = recycler;
1140        mState = state;
1141        mPositionDeltaInPreLayout = 0;
1142        mExtraLayoutSpaceInPreLayout = 0;
1143    }
1144
1145    /**
1146     * Discard saved Recycler and State.
1147     */
1148    private void leaveContext() {
1149        mRecycler = null;
1150        mState = null;
1151        mPositionDeltaInPreLayout = 0;
1152        mExtraLayoutSpaceInPreLayout = 0;
1153    }
1154
1155    /**
1156     * Re-initialize data structures for a data change or handling invisible
1157     * selection. The method tries its best to preserve position information so
1158     * that staggered grid looks same before and after re-initialize.
1159     * @return true if can fastRelayout()
1160     */
1161    private boolean layoutInit() {
1162        final int newItemCount = mState.getItemCount();
1163        if (newItemCount == 0) {
1164            mFocusPosition = NO_POSITION;
1165            mSubFocusPosition = 0;
1166        } else if (mFocusPosition >= newItemCount) {
1167            mFocusPosition = newItemCount - 1;
1168            mSubFocusPosition = 0;
1169        } else if (mFocusPosition == NO_POSITION && newItemCount > 0) {
1170            // if focus position is never set before,  initialize it to 0
1171            mFocusPosition = 0;
1172            mSubFocusPosition = 0;
1173        }
1174        if (!mState.didStructureChange() && mGrid != null && mGrid.getFirstVisibleIndex() >= 0
1175                && !mForceFullLayout && mGrid.getNumRows() == mNumRows) {
1176            updateScrollController();
1177            updateSecondaryScrollLimits();
1178            mGrid.setSpacing(mSpacingPrimary);
1179            return true;
1180        } else {
1181            mForceFullLayout = false;
1182
1183            if (mGrid == null || mNumRows != mGrid.getNumRows()
1184                    || mReverseFlowPrimary != mGrid.isReversedFlow()) {
1185                mGrid = Grid.createGrid(mNumRows);
1186                mGrid.setProvider(mGridProvider);
1187                mGrid.setReversedFlow(mReverseFlowPrimary);
1188            }
1189            initScrollController();
1190            updateSecondaryScrollLimits();
1191            mGrid.setSpacing(mSpacingPrimary);
1192            detachAndScrapAttachedViews(mRecycler);
1193            mGrid.resetVisibleIndex();
1194            mWindowAlignment.mainAxis().invalidateScrollMin();
1195            mWindowAlignment.mainAxis().invalidateScrollMax();
1196            return false;
1197        }
1198    }
1199
1200    private int getRowSizeSecondary(int rowIndex) {
1201        if (mFixedRowSizeSecondary != 0) {
1202            return mFixedRowSizeSecondary;
1203        }
1204        if (mRowSizeSecondary == null) {
1205            return 0;
1206        }
1207        return mRowSizeSecondary[rowIndex];
1208    }
1209
1210    int getRowStartSecondary(int rowIndex) {
1211        int start = 0;
1212        // Iterate from left to right, which is a different index traversal
1213        // in RTL flow
1214        if (mReverseFlowSecondary) {
1215            for (int i = mNumRows-1; i > rowIndex; i--) {
1216                start += getRowSizeSecondary(i) + mSpacingSecondary;
1217            }
1218        } else {
1219            for (int i = 0; i < rowIndex; i++) {
1220                start += getRowSizeSecondary(i) + mSpacingSecondary;
1221            }
1222        }
1223        return start;
1224    }
1225
1226    private int getSizeSecondary() {
1227        int rightmostIndex = mReverseFlowSecondary ? 0 : mNumRows - 1;
1228        return getRowStartSecondary(rightmostIndex) + getRowSizeSecondary(rightmostIndex);
1229    }
1230
1231    int getDecoratedMeasuredWidthWithMargin(View v) {
1232        final LayoutParams lp = (LayoutParams) v.getLayoutParams();
1233        return getDecoratedMeasuredWidth(v) + lp.leftMargin + lp.rightMargin;
1234    }
1235
1236    int getDecoratedMeasuredHeightWithMargin(View v) {
1237        final LayoutParams lp = (LayoutParams) v.getLayoutParams();
1238        return getDecoratedMeasuredHeight(v) + lp.topMargin + lp.bottomMargin;
1239    }
1240
1241    private void measureScrapChild(int position, int widthSpec, int heightSpec,
1242            int[] measuredDimension) {
1243        View view = mRecycler.getViewForPosition(position);
1244        if (view != null) {
1245            final LayoutParams p = (LayoutParams) view.getLayoutParams();
1246            calculateItemDecorationsForChild(view, sTempRect);
1247            int widthUsed = p.leftMargin + p.rightMargin + sTempRect.left + sTempRect.right;
1248            int heightUsed = p.topMargin + p.bottomMargin + sTempRect.top + sTempRect.bottom;
1249
1250            int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
1251                    getPaddingLeft() + getPaddingRight() + widthUsed, p.width);
1252            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
1253                    getPaddingTop() + getPaddingBottom() + heightUsed, p.height);
1254            view.measure(childWidthSpec, childHeightSpec);
1255
1256            measuredDimension[0] = getDecoratedMeasuredWidthWithMargin(view);
1257            measuredDimension[1] = getDecoratedMeasuredHeightWithMargin(view);
1258            mRecycler.recycleView(view);
1259        }
1260    }
1261
1262    private boolean processRowSizeSecondary(boolean measure) {
1263        if (mFixedRowSizeSecondary != 0 || mRowSizeSecondary == null) {
1264            return false;
1265        }
1266
1267        if (TRACE) TraceCompat.beginSection("processRowSizeSecondary");
1268        CircularIntArray[] rows = mGrid == null ? null : mGrid.getItemPositionsInRows();
1269        boolean changed = false;
1270        int scrapChildWidth = -1;
1271        int scrapChildHeight = -1;
1272
1273        for (int rowIndex = 0; rowIndex < mNumRows; rowIndex++) {
1274            CircularIntArray row = rows == null ? null : rows[rowIndex];
1275            final int rowItemsPairCount = row == null ? 0 : row.size();
1276            int rowSize = -1;
1277            for (int rowItemPairIndex = 0; rowItemPairIndex < rowItemsPairCount;
1278                    rowItemPairIndex += 2) {
1279                final int rowIndexStart = row.get(rowItemPairIndex);
1280                final int rowIndexEnd = row.get(rowItemPairIndex + 1);
1281                for (int i = rowIndexStart; i <= rowIndexEnd; i++) {
1282                    final View view = findViewByPosition(i);
1283                    if (view == null) {
1284                        continue;
1285                    }
1286                    if (measure) {
1287                        measureChild(view);
1288                    }
1289                    final int secondarySize = mOrientation == HORIZONTAL
1290                            ? getDecoratedMeasuredHeightWithMargin(view)
1291                            : getDecoratedMeasuredWidthWithMargin(view);
1292                    if (secondarySize > rowSize) {
1293                        rowSize = secondarySize;
1294                    }
1295                }
1296            }
1297
1298            final int itemCount = mState.getItemCount();
1299            if (!mBaseGridView.hasFixedSize() && measure && rowSize < 0 && itemCount > 0) {
1300                if (scrapChildWidth < 0 && scrapChildHeight < 0) {
1301                    int position;
1302                    if (mFocusPosition == NO_POSITION) {
1303                        position = 0;
1304                    } else if (mFocusPosition >= itemCount) {
1305                        position = itemCount - 1;
1306                    } else {
1307                        position = mFocusPosition;
1308                    }
1309                    measureScrapChild(position,
1310                            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
1311                            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
1312                            mMeasuredDimension);
1313                    scrapChildWidth = mMeasuredDimension[0];
1314                    scrapChildHeight = mMeasuredDimension[1];
1315                    if (DEBUG) {
1316                        Log.v(TAG, "measured scrap child: " + scrapChildWidth + " "
1317                                + scrapChildHeight);
1318                    }
1319                }
1320                rowSize = mOrientation == HORIZONTAL ? scrapChildHeight : scrapChildWidth;
1321            }
1322            if (rowSize < 0) {
1323                rowSize = 0;
1324            }
1325            if (mRowSizeSecondary[rowIndex] != rowSize) {
1326                if (DEBUG) {
1327                    Log.v(getTag(), "row size secondary changed: " + mRowSizeSecondary[rowIndex]
1328                            + ", " + rowSize);
1329                }
1330                mRowSizeSecondary[rowIndex] = rowSize;
1331                changed = true;
1332            }
1333        }
1334
1335        if (TRACE) TraceCompat.endSection();
1336        return changed;
1337    }
1338
1339    /**
1340     * Checks if we need to update row secondary sizes.
1341     */
1342    private void updateRowSecondarySizeRefresh() {
1343        mRowSecondarySizeRefresh = processRowSizeSecondary(false);
1344        if (mRowSecondarySizeRefresh) {
1345            if (DEBUG) Log.v(getTag(), "mRowSecondarySizeRefresh now set");
1346            forceRequestLayout();
1347        }
1348    }
1349
1350    private void forceRequestLayout() {
1351        if (DEBUG) Log.v(getTag(), "forceRequestLayout");
1352        // RecyclerView prevents us from requesting layout in many cases
1353        // (during layout, during scroll, etc.)
1354        // For secondary row size wrap_content support we currently need a
1355        // second layout pass to update the measured size after having measured
1356        // and added child views in layoutChildren.
1357        // Force the second layout by posting a delayed runnable.
1358        // TODO: investigate allowing a second layout pass,
1359        // or move child add/measure logic to the measure phase.
1360        ViewCompat.postOnAnimation(mBaseGridView, mRequestLayoutRunnable);
1361    }
1362
1363    private final Runnable mRequestLayoutRunnable = new Runnable() {
1364        @Override
1365        public void run() {
1366            if (DEBUG) Log.v(getTag(), "request Layout from runnable");
1367            requestLayout();
1368        }
1369    };
1370
1371    @Override
1372    public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
1373        saveContext(recycler, state);
1374
1375        int sizePrimary, sizeSecondary, modeSecondary, paddingSecondary;
1376        int measuredSizeSecondary;
1377        if (mOrientation == HORIZONTAL) {
1378            sizePrimary = MeasureSpec.getSize(widthSpec);
1379            sizeSecondary = MeasureSpec.getSize(heightSpec);
1380            modeSecondary = MeasureSpec.getMode(heightSpec);
1381            paddingSecondary = getPaddingTop() + getPaddingBottom();
1382        } else {
1383            sizeSecondary = MeasureSpec.getSize(widthSpec);
1384            sizePrimary = MeasureSpec.getSize(heightSpec);
1385            modeSecondary = MeasureSpec.getMode(widthSpec);
1386            paddingSecondary = getPaddingLeft() + getPaddingRight();
1387        }
1388        if (DEBUG) {
1389            Log.v(getTag(), "onMeasure widthSpec " + Integer.toHexString(widthSpec)
1390                    + " heightSpec " + Integer.toHexString(heightSpec)
1391                    + " modeSecondary " + Integer.toHexString(modeSecondary)
1392                    + " sizeSecondary " + sizeSecondary + " " + this);
1393        }
1394
1395        mMaxSizeSecondary = sizeSecondary;
1396
1397        if (mRowSizeSecondaryRequested == ViewGroup.LayoutParams.WRAP_CONTENT) {
1398            mNumRows = mNumRowsRequested == 0 ? 1 : mNumRowsRequested;
1399            mFixedRowSizeSecondary = 0;
1400
1401            if (mRowSizeSecondary == null || mRowSizeSecondary.length != mNumRows) {
1402                mRowSizeSecondary = new int[mNumRows];
1403            }
1404
1405            // Measure all current children and update cached row heights
1406            processRowSizeSecondary(true);
1407
1408            switch (modeSecondary) {
1409                case MeasureSpec.UNSPECIFIED:
1410                    measuredSizeSecondary = getSizeSecondary() + paddingSecondary;
1411                    break;
1412                case MeasureSpec.AT_MOST:
1413                    measuredSizeSecondary = Math.min(getSizeSecondary() + paddingSecondary,
1414                            mMaxSizeSecondary);
1415                    break;
1416                case MeasureSpec.EXACTLY:
1417                    measuredSizeSecondary = mMaxSizeSecondary;
1418                    break;
1419                default:
1420                    throw new IllegalStateException("wrong spec");
1421            }
1422
1423        } else {
1424            switch (modeSecondary) {
1425                case MeasureSpec.UNSPECIFIED:
1426                    mFixedRowSizeSecondary = mRowSizeSecondaryRequested == 0
1427                            ? sizeSecondary - paddingSecondary : mRowSizeSecondaryRequested;
1428                    mNumRows = mNumRowsRequested == 0 ? 1 : mNumRowsRequested;
1429                    measuredSizeSecondary = mFixedRowSizeSecondary * mNumRows + mSpacingSecondary
1430                            * (mNumRows - 1) + paddingSecondary;
1431                    break;
1432                case MeasureSpec.AT_MOST:
1433                case MeasureSpec.EXACTLY:
1434                    if (mNumRowsRequested == 0 && mRowSizeSecondaryRequested == 0) {
1435                        mNumRows = 1;
1436                        mFixedRowSizeSecondary = sizeSecondary - paddingSecondary;
1437                    } else if (mNumRowsRequested == 0) {
1438                        mFixedRowSizeSecondary = mRowSizeSecondaryRequested;
1439                        mNumRows = (sizeSecondary + mSpacingSecondary)
1440                                / (mRowSizeSecondaryRequested + mSpacingSecondary);
1441                    } else if (mRowSizeSecondaryRequested == 0) {
1442                        mNumRows = mNumRowsRequested;
1443                        mFixedRowSizeSecondary = (sizeSecondary - paddingSecondary
1444                                - mSpacingSecondary * (mNumRows - 1)) / mNumRows;
1445                    } else {
1446                        mNumRows = mNumRowsRequested;
1447                        mFixedRowSizeSecondary = mRowSizeSecondaryRequested;
1448                    }
1449                    measuredSizeSecondary = sizeSecondary;
1450                    if (modeSecondary == MeasureSpec.AT_MOST) {
1451                        int childrenSize = mFixedRowSizeSecondary * mNumRows + mSpacingSecondary
1452                                * (mNumRows - 1) + paddingSecondary;
1453                        if (childrenSize < measuredSizeSecondary) {
1454                            measuredSizeSecondary = childrenSize;
1455                        }
1456                    }
1457                    break;
1458                default:
1459                    throw new IllegalStateException("wrong spec");
1460            }
1461        }
1462        if (mOrientation == HORIZONTAL) {
1463            setMeasuredDimension(sizePrimary, measuredSizeSecondary);
1464        } else {
1465            setMeasuredDimension(measuredSizeSecondary, sizePrimary);
1466        }
1467        if (DEBUG) {
1468            Log.v(getTag(), "onMeasure sizePrimary " + sizePrimary
1469                    + " measuredSizeSecondary " + measuredSizeSecondary
1470                    + " mFixedRowSizeSecondary " + mFixedRowSizeSecondary
1471                    + " mNumRows " + mNumRows);
1472        }
1473        leaveContext();
1474    }
1475
1476    void measureChild(View child) {
1477        if (TRACE) TraceCompat.beginSection("measureChild");
1478        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1479        calculateItemDecorationsForChild(child, sTempRect);
1480        int widthUsed = lp.leftMargin + lp.rightMargin + sTempRect.left + sTempRect.right;
1481        int heightUsed = lp.topMargin + lp.bottomMargin + sTempRect.top + sTempRect.bottom;
1482
1483        final int secondarySpec =
1484                (mRowSizeSecondaryRequested == ViewGroup.LayoutParams.WRAP_CONTENT)
1485                        ? MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
1486                        : MeasureSpec.makeMeasureSpec(mFixedRowSizeSecondary, MeasureSpec.EXACTLY);
1487        int widthSpec, heightSpec;
1488
1489        if (mOrientation == HORIZONTAL) {
1490            widthSpec = ViewGroup.getChildMeasureSpec(
1491                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), widthUsed, lp.width);
1492            heightSpec = ViewGroup.getChildMeasureSpec(secondarySpec, heightUsed, lp.height);
1493        } else {
1494            heightSpec = ViewGroup.getChildMeasureSpec(
1495                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), heightUsed, lp.height);
1496            widthSpec = ViewGroup.getChildMeasureSpec(secondarySpec, widthUsed, lp.width);
1497        }
1498        child.measure(widthSpec, heightSpec);
1499        if (DEBUG) {
1500            Log.v(getTag(), "measureChild secondarySpec " + Integer.toHexString(secondarySpec)
1501                    + " widthSpec " + Integer.toHexString(widthSpec)
1502                    + " heightSpec " + Integer.toHexString(heightSpec)
1503                    + " measuredWidth " + child.getMeasuredWidth()
1504                    + " measuredHeight " + child.getMeasuredHeight());
1505        }
1506        if (DEBUG) Log.v(getTag(), "child lp width " + lp.width + " height " + lp.height);
1507        if (TRACE) TraceCompat.endSection();
1508    }
1509
1510    /**
1511     * Get facet from the ViewHolder or the viewType.
1512     */
1513    <E> E getFacet(RecyclerView.ViewHolder vh, Class<? extends E> facetClass) {
1514        E facet = null;
1515        if (vh instanceof FacetProvider) {
1516            facet = (E) ((FacetProvider) vh).getFacet(facetClass);
1517        }
1518        if (facet == null && mFacetProviderAdapter != null) {
1519            FacetProvider p = mFacetProviderAdapter.getFacetProvider(vh.getItemViewType());
1520            if (p != null) {
1521                facet = (E) p.getFacet(facetClass);
1522            }
1523        }
1524        return facet;
1525    }
1526
1527    private Grid.Provider mGridProvider = new Grid.Provider() {
1528
1529        @Override
1530        public int getMinIndex() {
1531            return mPositionDeltaInPreLayout;
1532        }
1533
1534        @Override
1535        public int getCount() {
1536            return mState.getItemCount() + mPositionDeltaInPreLayout;
1537        }
1538
1539        @Override
1540        public int createItem(int index, boolean append, Object[] item, boolean disappearingItem) {
1541            if (TRACE) TraceCompat.beginSection("createItem");
1542            if (TRACE) TraceCompat.beginSection("getview");
1543            View v = getViewForPosition(index - mPositionDeltaInPreLayout);
1544            if (TRACE) TraceCompat.endSection();
1545            LayoutParams lp = (LayoutParams) v.getLayoutParams();
1546            RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(v);
1547            lp.setItemAlignmentFacet((ItemAlignmentFacet)getFacet(vh, ItemAlignmentFacet.class));
1548            // See recyclerView docs:  we don't need re-add scraped view if it was removed.
1549            if (!lp.isItemRemoved()) {
1550                if (TRACE) TraceCompat.beginSection("addView");
1551                if (disappearingItem) {
1552                    if (append) {
1553                        addDisappearingView(v);
1554                    } else {
1555                        addDisappearingView(v, 0);
1556                    }
1557                } else {
1558                    if (append) {
1559                        addView(v);
1560                    } else {
1561                        addView(v, 0);
1562                    }
1563                }
1564                if (TRACE) TraceCompat.endSection();
1565                if (mChildVisibility != -1) {
1566                    v.setVisibility(mChildVisibility);
1567                }
1568
1569                if (mPendingMoveSmoothScroller != null) {
1570                    mPendingMoveSmoothScroller.consumePendingMovesBeforeLayout();
1571                }
1572                int subindex = getSubPositionByView(v, v.findFocus());
1573                if (!mInLayout) {
1574                    // when we are appending item during scroll pass and the item's position
1575                    // matches the mFocusPosition,  we should signal a childSelected event.
1576                    // However if we are still running PendingMoveSmoothScroller,  we defer and
1577                    // signal the event in PendingMoveSmoothScroller.onStop().  This can
1578                    // avoid lots of childSelected events during a long smooth scrolling and
1579                    // increase performance.
1580                    if (index == mFocusPosition && subindex == mSubFocusPosition
1581                            && mPendingMoveSmoothScroller == null) {
1582                        dispatchChildSelected();
1583                    }
1584                } else if (!mInFastRelayout) {
1585                    // fastRelayout will dispatch event at end of onLayoutChildren().
1586                    // For full layout, two situations here:
1587                    // 1. mInLayoutSearchFocus is false, dispatchChildSelected() at mFocusPosition.
1588                    // 2. mInLayoutSearchFocus is true:  dispatchChildSelected() on first child
1589                    //    equal to or after mFocusPosition that can take focus.
1590                    if (!mInLayoutSearchFocus && index == mFocusPosition
1591                            && subindex == mSubFocusPosition) {
1592                        dispatchChildSelected();
1593                    } else if (mInLayoutSearchFocus && index >= mFocusPosition
1594                            && v.hasFocusable()) {
1595                        mFocusPosition = index;
1596                        mSubFocusPosition = subindex;
1597                        mInLayoutSearchFocus = false;
1598                        dispatchChildSelected();
1599                    }
1600                }
1601                measureChild(v);
1602            }
1603            item[0] = v;
1604            return mOrientation == HORIZONTAL ? getDecoratedMeasuredWidthWithMargin(v)
1605                    : getDecoratedMeasuredHeightWithMargin(v);
1606        }
1607
1608        @Override
1609        public void addItem(Object item, int index, int length, int rowIndex, int edge) {
1610            View v = (View) item;
1611            int start, end;
1612            if (edge == Integer.MIN_VALUE || edge == Integer.MAX_VALUE) {
1613                edge = !mGrid.isReversedFlow() ? mWindowAlignment.mainAxis().getPaddingMin()
1614                        : mWindowAlignment.mainAxis().getSize()
1615                                - mWindowAlignment.mainAxis().getPaddingMax();
1616            }
1617            boolean edgeIsMin = !mGrid.isReversedFlow();
1618            if (edgeIsMin) {
1619                start = edge;
1620                end = edge + length;
1621            } else {
1622                start = edge - length;
1623                end = edge;
1624            }
1625            int startSecondary = getRowStartSecondary(rowIndex)
1626                    + mWindowAlignment.secondAxis().getPaddingMin() - mScrollOffsetSecondary;
1627            mChildrenStates.loadView(v, index);
1628            layoutChild(rowIndex, v, start, end, startSecondary);
1629            if (DEBUG) {
1630                Log.d(getTag(), "addView " + index + " " + v);
1631            }
1632            if (TRACE) TraceCompat.endSection();
1633
1634            updateScrollLimits();
1635            if (!mInLayout && mPendingMoveSmoothScroller != null) {
1636                mPendingMoveSmoothScroller.consumePendingMovesAfterLayout();
1637            }
1638            if (mChildLaidOutListener != null) {
1639                RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(v);
1640                mChildLaidOutListener.onChildLaidOut(mBaseGridView, v, index,
1641                        vh == null ? NO_ID : vh.getItemId());
1642            }
1643        }
1644
1645        @Override
1646        public void removeItem(int index) {
1647            if (TRACE) TraceCompat.beginSection("removeItem");
1648            View v = findViewByPosition(index - mPositionDeltaInPreLayout);
1649            if (mInLayout) {
1650                detachAndScrapView(v, mRecycler);
1651            } else {
1652                removeAndRecycleView(v, mRecycler);
1653            }
1654            if (TRACE) TraceCompat.endSection();
1655        }
1656
1657        @Override
1658        public int getEdge(int index) {
1659            if (mReverseFlowPrimary) {
1660                return getViewMax(findViewByPosition(index - mPositionDeltaInPreLayout));
1661            } else {
1662                return getViewMin(findViewByPosition(index - mPositionDeltaInPreLayout));
1663            }
1664        }
1665
1666        @Override
1667        public int getSize(int index) {
1668            return getViewPrimarySize(findViewByPosition(index - mPositionDeltaInPreLayout));
1669        }
1670    };
1671
1672    void layoutChild(int rowIndex, View v, int start, int end, int startSecondary) {
1673        if (TRACE) TraceCompat.beginSection("layoutChild");
1674        int sizeSecondary = mOrientation == HORIZONTAL ? getDecoratedMeasuredHeightWithMargin(v)
1675                : getDecoratedMeasuredWidthWithMargin(v);
1676        if (mFixedRowSizeSecondary > 0) {
1677            sizeSecondary = Math.min(sizeSecondary, mFixedRowSizeSecondary);
1678        }
1679        final int verticalGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1680        final int horizontalGravity = (mReverseFlowPrimary || mReverseFlowSecondary)
1681                ? Gravity.getAbsoluteGravity(mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK,
1682                View.LAYOUT_DIRECTION_RTL)
1683                : mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
1684        if ((mOrientation == HORIZONTAL && verticalGravity == Gravity.TOP)
1685                || (mOrientation == VERTICAL && horizontalGravity == Gravity.LEFT)) {
1686            // do nothing
1687        } else if ((mOrientation == HORIZONTAL && verticalGravity == Gravity.BOTTOM)
1688                || (mOrientation == VERTICAL && horizontalGravity == Gravity.RIGHT)) {
1689            startSecondary += getRowSizeSecondary(rowIndex) - sizeSecondary;
1690        } else if ((mOrientation == HORIZONTAL && verticalGravity == Gravity.CENTER_VERTICAL)
1691                || (mOrientation == VERTICAL && horizontalGravity == Gravity.CENTER_HORIZONTAL)) {
1692            startSecondary += (getRowSizeSecondary(rowIndex) - sizeSecondary) / 2;
1693        }
1694        int left, top, right, bottom;
1695        if (mOrientation == HORIZONTAL) {
1696            left = start;
1697            top = startSecondary;
1698            right = end;
1699            bottom = startSecondary + sizeSecondary;
1700        } else {
1701            top = start;
1702            left = startSecondary;
1703            bottom = end;
1704            right = startSecondary + sizeSecondary;
1705        }
1706        LayoutParams params = (LayoutParams) v.getLayoutParams();
1707        layoutDecoratedWithMargins(v, left, top, right, bottom);
1708        // Now super.getDecoratedBoundsWithMargins() includes the extra space for optical bounds,
1709        // subtracting it from value passed in layoutDecoratedWithMargins(), we can get the optical
1710        // bounds insets.
1711        super.getDecoratedBoundsWithMargins(v, sTempRect);
1712        params.setOpticalInsets(left - sTempRect.left, top - sTempRect.top,
1713                sTempRect.right - right, sTempRect.bottom - bottom);
1714        updateChildAlignments(v);
1715        if (TRACE) TraceCompat.endSection();
1716    }
1717
1718    private void updateChildAlignments(View v) {
1719        final LayoutParams p = (LayoutParams) v.getLayoutParams();
1720        if (p.getItemAlignmentFacet() == null) {
1721            // Fallback to global settings on grid view
1722            p.setAlignX(mItemAlignment.horizontal.getAlignmentPosition(v));
1723            p.setAlignY(mItemAlignment.vertical.getAlignmentPosition(v));
1724        } else {
1725            // Use ItemAlignmentFacet defined on specific ViewHolder
1726            p.calculateItemAlignments(mOrientation, v);
1727            if (mOrientation == HORIZONTAL) {
1728                p.setAlignY(mItemAlignment.vertical.getAlignmentPosition(v));
1729            } else {
1730                p.setAlignX(mItemAlignment.horizontal.getAlignmentPosition(v));
1731            }
1732        }
1733    }
1734
1735    private void updateChildAlignments() {
1736        for (int i = 0, c = getChildCount(); i < c; i++) {
1737            updateChildAlignments(getChildAt(i));
1738        }
1739    }
1740
1741    void setExtraLayoutSpace(int extraLayoutSpace) {
1742        if (mExtraLayoutSpace == extraLayoutSpace) {
1743            return;
1744        } else if (mExtraLayoutSpace < 0) {
1745            throw new IllegalArgumentException("ExtraLayoutSpace must >= 0");
1746        }
1747        mExtraLayoutSpace = extraLayoutSpace;
1748        requestLayout();
1749    }
1750
1751    int getExtraLayoutSpace() {
1752        return mExtraLayoutSpace;
1753    }
1754
1755    private void removeInvisibleViewsAtEnd() {
1756        if (mPruneChild && !mIsSlidingChildViews) {
1757            mGrid.removeInvisibleItemsAtEnd(mFocusPosition,
1758                    mReverseFlowPrimary ? -mExtraLayoutSpace : mSizePrimary + mExtraLayoutSpace);
1759        }
1760    }
1761
1762    private void removeInvisibleViewsAtFront() {
1763        if (mPruneChild && !mIsSlidingChildViews) {
1764            mGrid.removeInvisibleItemsAtFront(mFocusPosition,
1765                    mReverseFlowPrimary ? mSizePrimary + mExtraLayoutSpace: -mExtraLayoutSpace);
1766        }
1767    }
1768
1769    private boolean appendOneColumnVisibleItems() {
1770        return mGrid.appendOneColumnVisibleItems();
1771    }
1772
1773    void slideIn() {
1774        if (mIsSlidingChildViews) {
1775            mIsSlidingChildViews = false;
1776            if (mFocusPosition >= 0) {
1777                scrollToSelection(mFocusPosition, mSubFocusPosition, true, mPrimaryScrollExtra);
1778            } else {
1779                mLayoutEatenInSliding = false;
1780                requestLayout();
1781            }
1782            if (mLayoutEatenInSliding) {
1783                mLayoutEatenInSliding = false;
1784                if (mBaseGridView.getScrollState() != SCROLL_STATE_IDLE || isSmoothScrolling()) {
1785                    mBaseGridView.addOnScrollListener(new RecyclerView.OnScrollListener() {
1786                        @Override
1787                        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
1788                            if (newState == SCROLL_STATE_IDLE) {
1789                                mBaseGridView.removeOnScrollListener(this);
1790                                requestLayout();
1791                            }
1792                        }
1793                    });
1794                } else {
1795                    requestLayout();
1796                }
1797            }
1798        }
1799    }
1800
1801    int getSlideOutDistance() {
1802        int distance;
1803        if (mOrientation == VERTICAL) {
1804            distance = -getHeight();
1805            if (getChildCount() > 0) {
1806                int top = getChildAt(0).getTop();
1807                if (top < 0) {
1808                    // scroll more if first child is above top edge
1809                    distance = distance + top;
1810                }
1811            }
1812        } else {
1813            if (mReverseFlowPrimary) {
1814                distance = getWidth();
1815                if (getChildCount() > 0) {
1816                    int start = getChildAt(0).getRight();
1817                    if (start > distance) {
1818                        // scroll more if first child is outside right edge
1819                        distance = start;
1820                    }
1821                }
1822            } else {
1823                distance = -getWidth();
1824                if (getChildCount() > 0) {
1825                    int start = getChildAt(0).getLeft();
1826                    if (start < 0) {
1827                        // scroll more if first child is out side left edge
1828                        distance = distance + start;
1829                    }
1830                }
1831            }
1832        }
1833        return distance;
1834    }
1835
1836    /**
1837     * Temporarily slide out child and block layout and scroll requests.
1838     */
1839    void slideOut() {
1840        if (mIsSlidingChildViews) {
1841            return;
1842        }
1843        mIsSlidingChildViews = true;
1844        if (getChildCount() == 0) {
1845            return;
1846        }
1847        if (mOrientation == VERTICAL) {
1848            mBaseGridView.smoothScrollBy(0, getSlideOutDistance(),
1849                    new AccelerateDecelerateInterpolator());
1850        } else {
1851            mBaseGridView.smoothScrollBy(getSlideOutDistance(), 0,
1852                    new AccelerateDecelerateInterpolator());
1853        }
1854    }
1855
1856    private boolean prependOneColumnVisibleItems() {
1857        return mGrid.prependOneColumnVisibleItems();
1858    }
1859
1860    private void appendVisibleItems() {
1861        mGrid.appendVisibleItems(mReverseFlowPrimary
1862                ? -mExtraLayoutSpace - mExtraLayoutSpaceInPreLayout
1863                : mSizePrimary + mExtraLayoutSpace + mExtraLayoutSpaceInPreLayout);
1864    }
1865
1866    private void prependVisibleItems() {
1867        mGrid.prependVisibleItems(mReverseFlowPrimary
1868                ? mSizePrimary + mExtraLayoutSpace + mExtraLayoutSpaceInPreLayout
1869                : -mExtraLayoutSpace - mExtraLayoutSpaceInPreLayout);
1870    }
1871
1872    /**
1873     * Fast layout when there is no structure change, adapter change, etc.
1874     * It will layout all views was layout requested or updated, until hit a view
1875     * with different size,  then it break and detachAndScrap all views after that.
1876     */
1877    private void fastRelayout() {
1878        boolean invalidateAfter = false;
1879        final int childCount = getChildCount();
1880        int position = -1;
1881        for (int index = 0; index < childCount; index++) {
1882            View view = getChildAt(index);
1883            position = getAdapterPositionByView(view);
1884            Grid.Location location = mGrid.getLocation(position);
1885            // position could be NO_POSITION for the views off the screen that were cached. For
1886            // these views markKnownViewsInvalid invalidates them.
1887            if (location == null || (position == NO_POSITION)) {
1888                if (DEBUG) Log.w(getTag(), "fastRelayout(): no Location at " + position);
1889                invalidateAfter = true;
1890                break;
1891            }
1892
1893            int startSecondary = getRowStartSecondary(location.row)
1894                    + mWindowAlignment.secondAxis().getPaddingMin() - mScrollOffsetSecondary;
1895            int primarySize, end;
1896            int start = getViewMin(view);
1897            int oldPrimarySize = getViewPrimarySize(view);
1898
1899            LayoutParams lp = (LayoutParams) view.getLayoutParams();
1900            if (lp.viewNeedsUpdate()) {
1901                int viewIndex = mBaseGridView.indexOfChild(view);
1902                detachAndScrapView(view, mRecycler);
1903                view = getViewForPosition(position);
1904                addView(view, viewIndex);
1905            }
1906
1907            measureChild(view);
1908            if (mOrientation == HORIZONTAL) {
1909                primarySize = getDecoratedMeasuredWidthWithMargin(view);
1910                end = start + primarySize;
1911            } else {
1912                primarySize = getDecoratedMeasuredHeightWithMargin(view);
1913                end = start + primarySize;
1914            }
1915            layoutChild(location.row, view, start, end, startSecondary);
1916            if (oldPrimarySize != primarySize) {
1917                // size changed invalidate remaining Locations
1918                if (DEBUG) Log.d(getTag(), "fastRelayout: view size changed at " + position);
1919                invalidateAfter = true;
1920                break;
1921            }
1922        }
1923        if (invalidateAfter) {
1924            final int savedLastPos = mGrid.getLastVisibleIndex();
1925            mGrid.invalidateItemsAfter(position);
1926            if (mPruneChild) {
1927                // in regular prune child mode, we just append items up to edge limit
1928                appendVisibleItems();
1929                if (mFocusPosition >= 0 && mFocusPosition <= savedLastPos) {
1930                    // make sure add focus view back:  the view might be outside edge limit
1931                    // when there is delta in onLayoutChildren().
1932                    while (mGrid.getLastVisibleIndex() < mFocusPosition) {
1933                        mGrid.appendOneColumnVisibleItems();
1934                    }
1935                }
1936            } else {
1937                // prune disabled(e.g. in RowsFragment transition): append all removed items
1938                while (mGrid.appendOneColumnVisibleItems()
1939                        && mGrid.getLastVisibleIndex() < savedLastPos);
1940            }
1941        }
1942        updateScrollLimits();
1943        updateSecondaryScrollLimits();
1944    }
1945
1946    @Override
1947    public void removeAndRecycleAllViews(RecyclerView.Recycler recycler) {
1948        if (TRACE) TraceCompat.beginSection("removeAndRecycleAllViews");
1949        if (DEBUG) Log.v(TAG, "removeAndRecycleAllViews " + getChildCount());
1950        for (int i = getChildCount() - 1; i >= 0; i--) {
1951            removeAndRecycleViewAt(i, recycler);
1952        }
1953        if (TRACE) TraceCompat.endSection();
1954    }
1955
1956    // called by onLayoutChildren, either focus to FocusPosition or declare focusViewAvailable
1957    // and scroll to the view if framework focus on it.
1958    private void focusToViewInLayout(boolean hadFocus, boolean alignToView) {
1959        View focusView = findViewByPosition(mFocusPosition);
1960        if (focusView != null && alignToView) {
1961            scrollToView(focusView, false);
1962        }
1963        if (focusView != null && hadFocus && !focusView.hasFocus()) {
1964            focusView.requestFocus();
1965        } else if (!hadFocus && !mBaseGridView.hasFocus()) {
1966            if (focusView != null && focusView.hasFocusable()) {
1967                mBaseGridView.focusableViewAvailable(focusView);
1968            } else {
1969                for (int i = 0, count = getChildCount(); i < count; i++) {
1970                    focusView = getChildAt(i);
1971                    if (focusView != null && focusView.hasFocusable()) {
1972                        mBaseGridView.focusableViewAvailable(focusView);
1973                        break;
1974                    }
1975                }
1976            }
1977            // focusViewAvailable() might focus to the view, scroll to it if that is the case.
1978            if (alignToView && focusView != null && focusView.hasFocus()) {
1979                scrollToView(focusView, false);
1980            }
1981        }
1982    }
1983
1984    @VisibleForTesting
1985    public static class OnLayoutCompleteListener {
1986        public void onLayoutCompleted(RecyclerView.State state) {
1987        }
1988    }
1989
1990    @VisibleForTesting
1991    OnLayoutCompleteListener mLayoutCompleteListener;
1992
1993    @Override
1994    public void onLayoutCompleted(State state) {
1995        if (mLayoutCompleteListener != null) {
1996            mLayoutCompleteListener.onLayoutCompleted(state);
1997        }
1998    }
1999
2000    @Override
2001    public boolean supportsPredictiveItemAnimations() {
2002        return true;
2003    }
2004
2005    void updatePositionToRowMapInPostLayout() {
2006        mPositionToRowInPostLayout.clear();
2007        final int childCount = getChildCount();
2008        for (int i = 0;  i < childCount; i++) {
2009            // Grid still maps to old positions at this point, use old position to get row infor
2010            int position = mBaseGridView.getChildViewHolder(getChildAt(i)).getOldPosition();
2011            if (position >= 0) {
2012                Grid.Location loc = mGrid.getLocation(position);
2013                if (loc != null) {
2014                    mPositionToRowInPostLayout.put(position, loc.row);
2015                }
2016            }
2017        }
2018    }
2019
2020    void fillScrapViewsInPostLayout() {
2021        List<RecyclerView.ViewHolder> scrapList = mRecycler.getScrapList();
2022        final int scrapSize = scrapList.size();
2023        if (scrapSize == 0) {
2024            return;
2025        }
2026        // initialize the int array or re-allocate the array.
2027        if (mDisappearingPositions == null  || scrapSize > mDisappearingPositions.length) {
2028            int length = mDisappearingPositions == null ? 16 : mDisappearingPositions.length;
2029            while (length < scrapSize) {
2030                length = length << 1;
2031            }
2032            mDisappearingPositions = new int[length];
2033        }
2034        int totalItems = 0;
2035        for (int i = 0; i < scrapSize; i++) {
2036            int pos = scrapList.get(i).getAdapterPosition();
2037            if (pos >= 0) {
2038                mDisappearingPositions[totalItems++] = pos;
2039            }
2040        }
2041        // totalItems now has the length of disappearing items
2042        if (totalItems > 0) {
2043            Arrays.sort(mDisappearingPositions, 0, totalItems);
2044            mGrid.fillDisappearingItems(mDisappearingPositions, totalItems,
2045                    mPositionToRowInPostLayout);
2046        }
2047        mPositionToRowInPostLayout.clear();
2048    }
2049
2050    // Lays out items based on the current scroll position
2051    @Override
2052    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
2053        if (DEBUG) {
2054            Log.v(getTag(), "layoutChildren start numRows " + mNumRows
2055                    + " inPreLayout " + state.isPreLayout()
2056                    + " didStructureChange " + state.didStructureChange()
2057                    + " mForceFullLayout " + mForceFullLayout);
2058            Log.v(getTag(), "width " + getWidth() + " height " + getHeight());
2059        }
2060
2061        if (mNumRows == 0) {
2062            // haven't done measure yet
2063            return;
2064        }
2065        final int itemCount = state.getItemCount();
2066        if (itemCount < 0) {
2067            return;
2068        }
2069
2070        if (mIsSlidingChildViews) {
2071            // if there is already children, delay the layout process until slideIn(), if it's
2072            // first time layout children: scroll them offscreen at end of onLayoutChildren()
2073            if (getChildCount() > 0) {
2074                mLayoutEatenInSliding = true;
2075                return;
2076            }
2077        }
2078        if (!mLayoutEnabled) {
2079            discardLayoutInfo();
2080            removeAndRecycleAllViews(recycler);
2081            return;
2082        }
2083        mInLayout = true;
2084
2085        saveContext(recycler, state);
2086        if (state.isPreLayout()) {
2087            int childCount = getChildCount();
2088            if (mGrid != null && childCount > 0) {
2089                int minChangedEdge = Integer.MAX_VALUE;
2090                int maxChangeEdge = Integer.MIN_VALUE;
2091                int minOldAdapterPosition = mBaseGridView.getChildViewHolder(
2092                        getChildAt(0)).getOldPosition();
2093                int maxOldAdapterPosition = mBaseGridView.getChildViewHolder(
2094                        getChildAt(childCount - 1)).getOldPosition();
2095                for (int i = 0; i < childCount; i++) {
2096                    View view = getChildAt(i);
2097                    LayoutParams lp = (LayoutParams) view.getLayoutParams();
2098                    if (i == 0) {
2099                        // first child's layout position can be smaller than index if there were
2100                        // items removed before first visible index.
2101                        mPositionDeltaInPreLayout = mGrid.getFirstVisibleIndex()
2102                                - lp.getViewLayoutPosition();
2103                    }
2104                    int newAdapterPosition = mBaseGridView.getChildAdapterPosition(view);
2105                    // if either of following happening
2106                    // 1. item itself has changed or layout parameter changed
2107                    // 2. item is losing focus
2108                    // 3. item is gaining focus
2109                    // 4. item is moved out of old adapter position range.
2110                    if (lp.isItemChanged() || lp.isItemRemoved() || view.isLayoutRequested()
2111                            || (!view.hasFocus() && mFocusPosition == lp.getViewAdapterPosition())
2112                            || (view.hasFocus() && mFocusPosition != lp.getViewAdapterPosition())
2113                            || newAdapterPosition < minOldAdapterPosition
2114                            || newAdapterPosition > maxOldAdapterPosition) {
2115                        minChangedEdge = Math.min(minChangedEdge, getViewMin(view));
2116                        maxChangeEdge = Math.max(maxChangeEdge, getViewMax(view));
2117                    }
2118                }
2119                if (maxChangeEdge > minChangedEdge) {
2120                    mExtraLayoutSpaceInPreLayout = maxChangeEdge - minChangedEdge;
2121                }
2122                // append items for mExtraLayoutSpaceInPreLayout
2123                appendVisibleItems();
2124                prependVisibleItems();
2125            }
2126            mInLayout = false;
2127            leaveContext();
2128            if (DEBUG) Log.v(getTag(), "layoutChildren end");
2129            return;
2130        }
2131
2132        // save all view's row information before detach all views
2133        if (state.willRunPredictiveAnimations()) {
2134            updatePositionToRowMapInPostLayout();
2135        }
2136        // check if we need align to mFocusPosition, this is usually true unless in smoothScrolling
2137        final boolean scrollToFocus = !isSmoothScrolling()
2138                && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED;
2139        if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE) {
2140            mFocusPosition = mFocusPosition + mFocusPositionOffset;
2141            mSubFocusPosition = 0;
2142        }
2143        mFocusPositionOffset = 0;
2144
2145        View savedFocusView = findViewByPosition(mFocusPosition);
2146        int savedFocusPos = mFocusPosition;
2147        int savedSubFocusPos = mSubFocusPosition;
2148        boolean hadFocus = mBaseGridView.hasFocus();
2149        final int firstVisibleIndex = mGrid != null ? mGrid.getFirstVisibleIndex() : NO_POSITION;
2150        final int lastVisibleIndex = mGrid != null ? mGrid.getLastVisibleIndex() : NO_POSITION;
2151        int savedViewCenter = savedFocusView == null ? 0 : getAdjustedViewCenter(savedFocusView);
2152        int savedViewCenterSecondary = savedFocusView == null ? 0 :
2153                getViewCenterSecondary(savedFocusView);
2154
2155        if (mInFastRelayout = layoutInit()) {
2156            // If grid view is empty, we will start from mFocusPosition
2157            mGrid.setStart(mFocusPosition);
2158            fastRelayout();
2159        } else {
2160            // layoutInit() has detached all views, so start from scratch
2161            mInLayoutSearchFocus = hadFocus;
2162            int startFromPosition, endPos;
2163            if (scrollToFocus && (firstVisibleIndex < 0 || mFocusPosition > lastVisibleIndex
2164                    || mFocusPosition < firstVisibleIndex)) {
2165                startFromPosition = endPos = mFocusPosition;
2166            } else {
2167                startFromPosition = firstVisibleIndex;
2168                endPos = lastVisibleIndex;
2169            }
2170
2171            mGrid.setStart(startFromPosition);
2172            if (endPos != NO_POSITION) {
2173                while (appendOneColumnVisibleItems() && findViewByPosition(endPos) == null) {
2174                    // continuously append items until endPos
2175                }
2176            }
2177        }
2178        // multiple rounds: scrollToView of first round may drag first/last child into
2179        // "visible window" and we update scrollMin/scrollMax then run second scrollToView
2180        // we must do this for fastRelayout() for the append item case
2181        int oldFirstVisible;
2182        int oldLastVisible;
2183        do {
2184            updateScrollLimits();
2185            oldFirstVisible = mGrid.getFirstVisibleIndex();
2186            oldLastVisible = mGrid.getLastVisibleIndex();
2187            if (scrollToFocus && savedFocusView != null) {
2188                // Make previous focused view stay at the original place:
2189                focusToViewInLayout(hadFocus, false);
2190                int newViewCenter = getAdjustedViewCenter(savedFocusView);
2191                int newViewCenterSecondary = getViewCenterSecondary(savedFocusView);
2192                scrollDirectionPrimary(newViewCenter - savedViewCenter);
2193                scrollDirectionSecondary(newViewCenterSecondary - savedViewCenterSecondary);
2194            } else {
2195                // focus and scroll to the view
2196                focusToViewInLayout(hadFocus, scrollToFocus);
2197            }
2198            appendVisibleItems();
2199            prependVisibleItems();
2200            removeInvisibleViewsAtFront();
2201            removeInvisibleViewsAtEnd();
2202        } while (mGrid.getFirstVisibleIndex() != oldFirstVisible
2203                || mGrid.getLastVisibleIndex() != oldLastVisible);
2204
2205        if (scrollToFocus) {
2206            // we need scroll to the new focus view
2207            View newFocusView = findViewByPosition(mFocusPosition); // must not be null
2208            View newChildFocusView = newFocusView != null && newFocusView.hasFocus()
2209                    ? newFocusView.findFocus() : null;
2210            if (newFocusView != null) {
2211                // get scroll delta of primary / secondary to the new focus view
2212                // Note that we need to multiple rounds to updateScrollLimits()
2213                int newFocusViewCenter = getAdjustedViewCenter(newFocusView);
2214                int newFocusViewCenterSecondary = getViewCenterSecondary(newFocusView);
2215                do {
2216                    updateScrollLimits();
2217                    oldFirstVisible = mGrid.getFirstVisibleIndex();
2218                    oldLastVisible = mGrid.getLastVisibleIndex();
2219                    scrollToView(newFocusView, newChildFocusView, false);
2220                    appendVisibleItems();
2221                    prependVisibleItems();
2222                    removeInvisibleViewsAtFront();
2223                    removeInvisibleViewsAtEnd();
2224                } while (mGrid.getFirstVisibleIndex() != oldFirstVisible
2225                        || mGrid.getLastVisibleIndex() != oldLastVisible);
2226                int primary = newFocusViewCenter - getAdjustedViewCenter(newFocusView);
2227                int secondary = newFocusViewCenterSecondary - getViewCenterSecondary(newFocusView);
2228                final int scrollX, scrollY;
2229                if (mOrientation == HORIZONTAL) {
2230                    scrollX = primary;
2231                    scrollY = secondary;
2232                } else {
2233                    scrollY = primary;
2234                    scrollX = secondary;
2235                }
2236                final int remainingScrollX = state.getRemainingScrollHorizontal();
2237                final int remainingScrollY = state.getRemainingScrollVertical();
2238                // check if the remaining scroll will stop at the new focus view
2239                if (remainingScrollX != scrollX || remainingScrollY != scrollY) {
2240                    if (remainingScrollX == 0 && remainingScrollY == 0) {
2241                        // if there wasnt scroll animation, we dont start animation, let
2242                        // ItemAnimation to do the move animation.
2243                    } else {
2244                        // if there was scroll animation, we will start a new scroll animation.
2245                        // after move back to current position
2246                        scrollAndAppendPrepend(-primary, -secondary);
2247                        if (scrollX != 0 || scrollY != 0) {
2248                            mBaseGridView.smoothScrollBy(scrollX, scrollY);
2249                        } else {
2250                            mBaseGridView.stopScroll();
2251                        }
2252                    }
2253                } else {
2254                    // move back to current position and let scroll animation continue
2255                    scrollAndAppendPrepend(-primary, -secondary);
2256                }
2257            }
2258        }
2259        if (state.willRunPredictiveAnimations()) {
2260            fillScrapViewsInPostLayout();
2261        }
2262
2263        if (DEBUG) {
2264            StringWriter sw = new StringWriter();
2265            PrintWriter pw = new PrintWriter(sw);
2266            mGrid.debugPrint(pw);
2267            Log.d(getTag(), sw.toString());
2268        }
2269
2270        if (mRowSecondarySizeRefresh) {
2271            mRowSecondarySizeRefresh = false;
2272        } else {
2273            updateRowSecondarySizeRefresh();
2274        }
2275
2276        // For fastRelayout, only dispatch event when focus position changes.
2277        if (mInFastRelayout && (mFocusPosition != savedFocusPos || mSubFocusPosition
2278                != savedSubFocusPos || findViewByPosition(mFocusPosition) != savedFocusView)) {
2279            dispatchChildSelected();
2280        } else if (!mInFastRelayout && mInLayoutSearchFocus) {
2281            // For full layout we dispatchChildSelected() in createItem() unless searched all
2282            // children and found none is focusable then dispatchChildSelected() here.
2283            dispatchChildSelected();
2284        }
2285        dispatchChildSelectedAndPositioned();
2286        if (mIsSlidingChildViews) {
2287            scrollDirectionPrimary(getSlideOutDistance());
2288        }
2289
2290        mInLayout = false;
2291        leaveContext();
2292        if (DEBUG) Log.v(getTag(), "layoutChildren end");
2293    }
2294
2295    void scrollAndAppendPrepend(int primary, int secondary) {
2296        scrollDirectionPrimary(primary);
2297        scrollDirectionSecondary(secondary);
2298        appendVisibleItems();
2299        prependVisibleItems();
2300        removeInvisibleViewsAtFront();
2301        removeInvisibleViewsAtEnd();
2302    }
2303
2304    private void offsetChildrenSecondary(int increment) {
2305        final int childCount = getChildCount();
2306        if (mOrientation == HORIZONTAL) {
2307            for (int i = 0; i < childCount; i++) {
2308                getChildAt(i).offsetTopAndBottom(increment);
2309            }
2310        } else {
2311            for (int i = 0; i < childCount; i++) {
2312                getChildAt(i).offsetLeftAndRight(increment);
2313            }
2314        }
2315    }
2316
2317    private void offsetChildrenPrimary(int increment) {
2318        final int childCount = getChildCount();
2319        if (mOrientation == VERTICAL) {
2320            for (int i = 0; i < childCount; i++) {
2321                getChildAt(i).offsetTopAndBottom(increment);
2322            }
2323        } else {
2324            for (int i = 0; i < childCount; i++) {
2325                getChildAt(i).offsetLeftAndRight(increment);
2326            }
2327        }
2328    }
2329
2330    @Override
2331    public int scrollHorizontallyBy(int dx, Recycler recycler, RecyclerView.State state) {
2332        if (DEBUG) Log.v(getTag(), "scrollHorizontallyBy " + dx);
2333        if (!mLayoutEnabled || !hasDoneFirstLayout()) {
2334            return 0;
2335        }
2336        saveContext(recycler, state);
2337        mInScroll = true;
2338        int result;
2339        if (mOrientation == HORIZONTAL) {
2340            result = scrollDirectionPrimary(dx);
2341        } else {
2342            result = scrollDirectionSecondary(dx);
2343        }
2344        leaveContext();
2345        mInScroll = false;
2346        return result;
2347    }
2348
2349    @Override
2350    public int scrollVerticallyBy(int dy, Recycler recycler, RecyclerView.State state) {
2351        if (DEBUG) Log.v(getTag(), "scrollVerticallyBy " + dy);
2352        if (!mLayoutEnabled || !hasDoneFirstLayout()) {
2353            return 0;
2354        }
2355        mInScroll = true;
2356        saveContext(recycler, state);
2357        int result;
2358        if (mOrientation == VERTICAL) {
2359            result = scrollDirectionPrimary(dy);
2360        } else {
2361            result = scrollDirectionSecondary(dy);
2362        }
2363        leaveContext();
2364        mInScroll = false;
2365        return result;
2366    }
2367
2368    // scroll in main direction may add/prune views
2369    private int scrollDirectionPrimary(int da) {
2370        if (TRACE) TraceCompat.beginSection("scrollPrimary");
2371        boolean isMaxUnknown = false, isMinUnknown = false;
2372        int minScroll = 0, maxScroll = 0;
2373        if (!mIsSlidingChildViews) {
2374            if (da > 0) {
2375                isMaxUnknown = mWindowAlignment.mainAxis().isMaxUnknown();
2376                if (!isMaxUnknown) {
2377                    maxScroll = mWindowAlignment.mainAxis().getMaxScroll();
2378                    if (da > maxScroll) {
2379                        da = maxScroll;
2380                    }
2381                }
2382            } else if (da < 0) {
2383                isMinUnknown = mWindowAlignment.mainAxis().isMinUnknown();
2384                if (!isMinUnknown) {
2385                    minScroll = mWindowAlignment.mainAxis().getMinScroll();
2386                    if (da < minScroll) {
2387                        da = minScroll;
2388                    }
2389                }
2390            }
2391        }
2392        if (da == 0) {
2393            if (TRACE) TraceCompat.endSection();
2394            return 0;
2395        }
2396        offsetChildrenPrimary(-da);
2397        if (mInLayout) {
2398            updateScrollLimits();
2399            if (TRACE) TraceCompat.endSection();
2400            return da;
2401        }
2402
2403        int childCount = getChildCount();
2404        boolean updated;
2405
2406        if (mReverseFlowPrimary ? da > 0 : da < 0) {
2407            prependVisibleItems();
2408        } else {
2409            appendVisibleItems();
2410        }
2411        updated = getChildCount() > childCount;
2412        childCount = getChildCount();
2413
2414        if (TRACE) TraceCompat.beginSection("remove");
2415        if (mReverseFlowPrimary ? da > 0 : da < 0) {
2416            removeInvisibleViewsAtEnd();
2417        } else {
2418            removeInvisibleViewsAtFront();
2419        }
2420        if (TRACE) TraceCompat.endSection();
2421        updated |= getChildCount() < childCount;
2422        if (updated) {
2423            updateRowSecondarySizeRefresh();
2424        }
2425
2426        mBaseGridView.invalidate();
2427        updateScrollLimits();
2428        if (TRACE) TraceCompat.endSection();
2429        return da;
2430    }
2431
2432    // scroll in second direction will not add/prune views
2433    private int scrollDirectionSecondary(int dy) {
2434        if (dy == 0) {
2435            return 0;
2436        }
2437        offsetChildrenSecondary(-dy);
2438        mScrollOffsetSecondary += dy;
2439        updateSecondaryScrollLimits();
2440        mBaseGridView.invalidate();
2441        return dy;
2442    }
2443
2444    @Override
2445    public void collectAdjacentPrefetchPositions(int dx, int dy, State state,
2446            LayoutPrefetchRegistry layoutPrefetchRegistry) {
2447        try {
2448            saveContext(null, state);
2449            int da = (mOrientation == HORIZONTAL) ? dx : dy;
2450            if (getChildCount() == 0 || da == 0) {
2451                // can't support this scroll, so don't bother prefetching
2452                return;
2453            }
2454
2455            int fromLimit = da < 0
2456                    ? -mExtraLayoutSpace
2457                    : mSizePrimary + mExtraLayoutSpace;
2458            mGrid.collectAdjacentPrefetchPositions(fromLimit, da, layoutPrefetchRegistry);
2459        } finally {
2460            leaveContext();
2461        }
2462    }
2463
2464    @Override
2465    public void collectInitialPrefetchPositions(int adapterItemCount,
2466            LayoutPrefetchRegistry layoutPrefetchRegistry) {
2467        int numToPrefetch = mBaseGridView.mInitialPrefetchItemCount;
2468        if (adapterItemCount != 0 && numToPrefetch != 0) {
2469            // prefetch items centered around mFocusPosition
2470            int initialPos = Math.max(0, Math.min(mFocusPosition - (numToPrefetch - 1)/ 2,
2471                    adapterItemCount - numToPrefetch));
2472            for (int i = initialPos; i < adapterItemCount && i < initialPos + numToPrefetch; i++) {
2473                layoutPrefetchRegistry.addPosition(i, 0);
2474            }
2475        }
2476    }
2477
2478    void updateScrollLimits() {
2479        if (mState.getItemCount() == 0) {
2480            return;
2481        }
2482        int highVisiblePos, lowVisiblePos;
2483        int highMaxPos, lowMinPos;
2484        if (!mReverseFlowPrimary) {
2485            highVisiblePos = mGrid.getLastVisibleIndex();
2486            highMaxPos = mState.getItemCount() - 1;
2487            lowVisiblePos = mGrid.getFirstVisibleIndex();
2488            lowMinPos = 0;
2489        } else {
2490            highVisiblePos = mGrid.getFirstVisibleIndex();
2491            highMaxPos = 0;
2492            lowVisiblePos = mGrid.getLastVisibleIndex();
2493            lowMinPos = mState.getItemCount() - 1;
2494        }
2495        if (highVisiblePos < 0 || lowVisiblePos < 0) {
2496            return;
2497        }
2498        final boolean highAvailable = highVisiblePos == highMaxPos;
2499        final boolean lowAvailable = lowVisiblePos == lowMinPos;
2500        if (!highAvailable && mWindowAlignment.mainAxis().isMaxUnknown()
2501                && !lowAvailable && mWindowAlignment.mainAxis().isMinUnknown()) {
2502            return;
2503        }
2504        int maxEdge, maxViewCenter;
2505        if (highAvailable) {
2506            maxEdge = mGrid.findRowMax(true, sTwoInts);
2507            View maxChild = findViewByPosition(sTwoInts[1]);
2508            maxViewCenter = getViewCenter(maxChild);
2509            final LayoutParams lp = (LayoutParams) maxChild.getLayoutParams();
2510            int[] multipleAligns = lp.getAlignMultiple();
2511            if (multipleAligns != null && multipleAligns.length > 0) {
2512                maxViewCenter += multipleAligns[multipleAligns.length - 1] - multipleAligns[0];
2513            }
2514        } else {
2515            maxEdge = Integer.MAX_VALUE;
2516            maxViewCenter = Integer.MAX_VALUE;
2517        }
2518        int minEdge, minViewCenter;
2519        if (lowAvailable) {
2520            minEdge = mGrid.findRowMin(false, sTwoInts);
2521            View minChild = findViewByPosition(sTwoInts[1]);
2522            minViewCenter = getViewCenter(minChild);
2523        } else {
2524            minEdge = Integer.MIN_VALUE;
2525            minViewCenter = Integer.MIN_VALUE;
2526        }
2527        mWindowAlignment.mainAxis().updateMinMax(minEdge, maxEdge, minViewCenter, maxViewCenter);
2528    }
2529
2530    /**
2531     * Update secondary axis's scroll min/max, should be updated in
2532     * {@link #scrollDirectionSecondary(int)}.
2533     */
2534    private void updateSecondaryScrollLimits() {
2535        WindowAlignment.Axis secondAxis = mWindowAlignment.secondAxis();
2536        int minEdge = secondAxis.getPaddingMin() - mScrollOffsetSecondary;
2537        int maxEdge = minEdge + getSizeSecondary();
2538        secondAxis.updateMinMax(minEdge, maxEdge, minEdge, maxEdge);
2539    }
2540
2541    private void initScrollController() {
2542        mWindowAlignment.reset();
2543        mWindowAlignment.horizontal.setSize(getWidth());
2544        mWindowAlignment.vertical.setSize(getHeight());
2545        mWindowAlignment.horizontal.setPadding(getPaddingLeft(), getPaddingRight());
2546        mWindowAlignment.vertical.setPadding(getPaddingTop(), getPaddingBottom());
2547        mSizePrimary = mWindowAlignment.mainAxis().getSize();
2548        mScrollOffsetSecondary = 0;
2549
2550        if (DEBUG) {
2551            Log.v(getTag(), "initScrollController mSizePrimary " + mSizePrimary
2552                    + " mWindowAlignment " + mWindowAlignment);
2553        }
2554    }
2555
2556    private void updateScrollController() {
2557        mWindowAlignment.horizontal.setSize(getWidth());
2558        mWindowAlignment.vertical.setSize(getHeight());
2559        mWindowAlignment.horizontal.setPadding(getPaddingLeft(), getPaddingRight());
2560        mWindowAlignment.vertical.setPadding(getPaddingTop(), getPaddingBottom());
2561        mSizePrimary = mWindowAlignment.mainAxis().getSize();
2562
2563        if (DEBUG) {
2564            Log.v(getTag(), "updateScrollController mSizePrimary " + mSizePrimary
2565                    + " mWindowAlignment " + mWindowAlignment);
2566        }
2567    }
2568
2569    @Override
2570    public void scrollToPosition(int position) {
2571        setSelection(position, 0, false, 0);
2572    }
2573
2574    @Override
2575    public void smoothScrollToPosition(RecyclerView recyclerView, State state,
2576            int position) {
2577        setSelection(position, 0, true, 0);
2578    }
2579
2580    public void setSelection(int position,
2581            int primaryScrollExtra) {
2582        setSelection(position, 0, false, primaryScrollExtra);
2583    }
2584
2585    public void setSelectionSmooth(int position) {
2586        setSelection(position, 0, true, 0);
2587    }
2588
2589    public void setSelectionWithSub(int position, int subposition,
2590            int primaryScrollExtra) {
2591        setSelection(position, subposition, false, primaryScrollExtra);
2592    }
2593
2594    public void setSelectionSmoothWithSub(int position, int subposition) {
2595        setSelection(position, subposition, true, 0);
2596    }
2597
2598    public int getSelection() {
2599        return mFocusPosition;
2600    }
2601
2602    public int getSubSelection() {
2603        return mSubFocusPosition;
2604    }
2605
2606    public void setSelection(int position, int subposition, boolean smooth,
2607            int primaryScrollExtra) {
2608        if ((mFocusPosition != position && position != NO_POSITION)
2609                || subposition != mSubFocusPosition || primaryScrollExtra != mPrimaryScrollExtra) {
2610            scrollToSelection(position, subposition, smooth, primaryScrollExtra);
2611        }
2612    }
2613
2614    void scrollToSelection(int position, int subposition,
2615            boolean smooth, int primaryScrollExtra) {
2616        if (TRACE) TraceCompat.beginSection("scrollToSelection");
2617        mPrimaryScrollExtra = primaryScrollExtra;
2618        View view = findViewByPosition(position);
2619        if (view != null) {
2620            mInSelection = true;
2621            scrollToView(view, smooth);
2622            mInSelection = false;
2623        } else {
2624            mFocusPosition = position;
2625            mSubFocusPosition = subposition;
2626            mFocusPositionOffset = Integer.MIN_VALUE;
2627            if (!mLayoutEnabled || mIsSlidingChildViews) {
2628                return;
2629            }
2630            if (smooth) {
2631                if (!hasDoneFirstLayout()) {
2632                    Log.w(getTag(), "setSelectionSmooth should "
2633                            + "not be called before first layout pass");
2634                    return;
2635                }
2636                position = startPositionSmoothScroller(position);
2637                if (position != mFocusPosition) {
2638                    // gets cropped by adapter size
2639                    mFocusPosition = position;
2640                    mSubFocusPosition = 0;
2641                }
2642            } else {
2643                mForceFullLayout = true;
2644                requestLayout();
2645            }
2646        }
2647        if (TRACE) TraceCompat.endSection();
2648    }
2649
2650    int startPositionSmoothScroller(int position) {
2651        LinearSmoothScroller linearSmoothScroller = new GridLinearSmoothScroller() {
2652            @Override
2653            public PointF computeScrollVectorForPosition(int targetPosition) {
2654                if (getChildCount() == 0) {
2655                    return null;
2656                }
2657                final int firstChildPos = getPosition(getChildAt(0));
2658                // TODO We should be able to deduce direction from bounds of current and target
2659                // focus, rather than making assumptions about positions and directionality
2660                final boolean isStart = mReverseFlowPrimary ? targetPosition > firstChildPos
2661                        : targetPosition < firstChildPos;
2662                final int direction = isStart ? -1 : 1;
2663                if (mOrientation == HORIZONTAL) {
2664                    return new PointF(direction, 0);
2665                } else {
2666                    return new PointF(0, direction);
2667                }
2668            }
2669
2670        };
2671        linearSmoothScroller.setTargetPosition(position);
2672        startSmoothScroll(linearSmoothScroller);
2673        return linearSmoothScroller.getTargetPosition();
2674    }
2675
2676    private void processPendingMovement(boolean forward) {
2677        if (forward ? hasCreatedLastItem() : hasCreatedFirstItem()) {
2678            return;
2679        }
2680        if (mPendingMoveSmoothScroller == null) {
2681            // Stop existing scroller and create a new PendingMoveSmoothScroller.
2682            mBaseGridView.stopScroll();
2683            PendingMoveSmoothScroller linearSmoothScroller = new PendingMoveSmoothScroller(
2684                    forward ? 1 : -1, mNumRows > 1);
2685            mFocusPositionOffset = 0;
2686            startSmoothScroll(linearSmoothScroller);
2687            if (linearSmoothScroller.isRunning()) {
2688                mPendingMoveSmoothScroller = linearSmoothScroller;
2689            }
2690        } else {
2691            if (forward) {
2692                mPendingMoveSmoothScroller.increasePendingMoves();
2693            } else {
2694                mPendingMoveSmoothScroller.decreasePendingMoves();
2695            }
2696        }
2697    }
2698
2699    @Override
2700    public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
2701        if (DEBUG) Log.v(getTag(), "onItemsAdded positionStart "
2702                + positionStart + " itemCount " + itemCount);
2703        if (mFocusPosition != NO_POSITION && mGrid != null && mGrid.getFirstVisibleIndex() >= 0
2704                && mFocusPositionOffset != Integer.MIN_VALUE) {
2705            int pos = mFocusPosition + mFocusPositionOffset;
2706            if (positionStart <= pos) {
2707                mFocusPositionOffset += itemCount;
2708            }
2709        }
2710        mChildrenStates.clear();
2711    }
2712
2713    @Override
2714    public void onItemsChanged(RecyclerView recyclerView) {
2715        if (DEBUG) Log.v(getTag(), "onItemsChanged");
2716        mFocusPositionOffset = 0;
2717        mChildrenStates.clear();
2718    }
2719
2720    @Override
2721    public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
2722        if (DEBUG) Log.v(getTag(), "onItemsRemoved positionStart "
2723                + positionStart + " itemCount " + itemCount);
2724        if (mFocusPosition != NO_POSITION  && mGrid != null && mGrid.getFirstVisibleIndex() >= 0
2725                && mFocusPositionOffset != Integer.MIN_VALUE) {
2726            int pos = mFocusPosition + mFocusPositionOffset;
2727            if (positionStart <= pos) {
2728                if (positionStart + itemCount > pos) {
2729                    // the focus item was removed
2730                    mFocusPositionOffset += positionStart - pos;
2731                } else {
2732                    mFocusPositionOffset -= itemCount;
2733                }
2734            }
2735        }
2736        mChildrenStates.clear();
2737    }
2738
2739    @Override
2740    public void onItemsMoved(RecyclerView recyclerView, int fromPosition, int toPosition,
2741            int itemCount) {
2742        if (DEBUG) Log.v(getTag(), "onItemsMoved fromPosition "
2743                + fromPosition + " toPosition " + toPosition);
2744        if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE) {
2745            int pos = mFocusPosition + mFocusPositionOffset;
2746            if (fromPosition <= pos && pos < fromPosition + itemCount) {
2747                // moved items include focused position
2748                mFocusPositionOffset += toPosition - fromPosition;
2749            } else if (fromPosition < pos && toPosition > pos - itemCount) {
2750                // move items before focus position to after focused position
2751                mFocusPositionOffset -= itemCount;
2752            } else if (fromPosition > pos && toPosition < pos) {
2753                // move items after focus position to before focused position
2754                mFocusPositionOffset += itemCount;
2755            }
2756        }
2757        mChildrenStates.clear();
2758    }
2759
2760    @Override
2761    public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
2762        if (DEBUG) Log.v(getTag(), "onItemsUpdated positionStart "
2763                + positionStart + " itemCount " + itemCount);
2764        for (int i = positionStart, end = positionStart + itemCount; i < end; i++) {
2765            mChildrenStates.remove(i);
2766        }
2767    }
2768
2769    @Override
2770    public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) {
2771        if (mFocusSearchDisabled) {
2772            return true;
2773        }
2774        if (getAdapterPositionByView(child) == NO_POSITION) {
2775            // This is could be the last view in DISAPPEARING animation.
2776            return true;
2777        }
2778        if (!mInLayout && !mInSelection && !mInScroll) {
2779            scrollToView(child, focused, true);
2780        }
2781        return true;
2782    }
2783
2784    @Override
2785    public boolean requestChildRectangleOnScreen(RecyclerView parent, View view, Rect rect,
2786            boolean immediate) {
2787        if (DEBUG) Log.v(getTag(), "requestChildRectangleOnScreen " + view + " " + rect);
2788        return false;
2789    }
2790
2791    public void getViewSelectedOffsets(View view, int[] offsets) {
2792        if (mOrientation == HORIZONTAL) {
2793            offsets[0] = getPrimaryAlignedScrollDistance(view);
2794            offsets[1] = getSecondaryScrollDistance(view);
2795        } else {
2796            offsets[1] = getPrimaryAlignedScrollDistance(view);
2797            offsets[0] = getSecondaryScrollDistance(view);
2798        }
2799    }
2800
2801    /**
2802     * Return the scroll delta on primary direction to make the view selected. If the return value
2803     * is 0, there is no need to scroll.
2804     */
2805    private int getPrimaryAlignedScrollDistance(View view) {
2806        return mWindowAlignment.mainAxis().getScroll(getViewCenter(view));
2807    }
2808
2809    /**
2810     * Get adjusted primary position for a given childView (if there is multiple ItemAlignment
2811     * defined on the view).
2812     */
2813    private int getAdjustedPrimaryAlignedScrollDistance(int scrollPrimary, View view,
2814            View childView) {
2815        int subindex = getSubPositionByView(view, childView);
2816        if (subindex != 0) {
2817            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
2818            scrollPrimary += lp.getAlignMultiple()[subindex] - lp.getAlignMultiple()[0];
2819        }
2820        return scrollPrimary;
2821    }
2822
2823    private int getSecondaryScrollDistance(View view) {
2824        int viewCenterSecondary = getViewCenterSecondary(view);
2825        return mWindowAlignment.secondAxis().getScroll(viewCenterSecondary);
2826    }
2827
2828    /**
2829     * Scroll to a given child view and change mFocusPosition. Ignored when in slideOut() state.
2830     */
2831    void scrollToView(View view, boolean smooth) {
2832        scrollToView(view, view == null ? null : view.findFocus(), smooth);
2833    }
2834
2835    /**
2836     * Scroll to a given child view and change mFocusPosition. Ignored when in slideOut() state.
2837     */
2838    private void scrollToView(View view, View childView, boolean smooth) {
2839        if (mIsSlidingChildViews) {
2840            return;
2841        }
2842        int newFocusPosition = getAdapterPositionByView(view);
2843        int newSubFocusPosition = getSubPositionByView(view, childView);
2844        if (newFocusPosition != mFocusPosition || newSubFocusPosition != mSubFocusPosition) {
2845            mFocusPosition = newFocusPosition;
2846            mSubFocusPosition = newSubFocusPosition;
2847            mFocusPositionOffset = 0;
2848            if (!mInLayout) {
2849                dispatchChildSelected();
2850            }
2851            if (mBaseGridView.isChildrenDrawingOrderEnabledInternal()) {
2852                mBaseGridView.invalidate();
2853            }
2854        }
2855        if (view == null) {
2856            return;
2857        }
2858        if (!view.hasFocus() && mBaseGridView.hasFocus()) {
2859            // transfer focus to the child if it does not have focus yet (e.g. triggered
2860            // by setSelection())
2861            view.requestFocus();
2862        }
2863        if (!mScrollEnabled && smooth) {
2864            return;
2865        }
2866        if (getScrollPosition(view, childView, sTwoInts)) {
2867            scrollGrid(sTwoInts[0], sTwoInts[1], smooth);
2868        }
2869    }
2870
2871    boolean getScrollPosition(View view, View childView, int[] deltas) {
2872        switch (mFocusScrollStrategy) {
2873            case BaseGridView.FOCUS_SCROLL_ALIGNED:
2874            default:
2875                return getAlignedPosition(view, childView, deltas);
2876            case BaseGridView.FOCUS_SCROLL_ITEM:
2877            case BaseGridView.FOCUS_SCROLL_PAGE:
2878                return getNoneAlignedPosition(view, deltas);
2879        }
2880    }
2881
2882    private boolean getNoneAlignedPosition(View view, int[] deltas) {
2883        int pos = getAdapterPositionByView(view);
2884        int viewMin = getViewMin(view);
2885        int viewMax = getViewMax(view);
2886        // we either align "firstView" to left/top padding edge
2887        // or align "lastView" to right/bottom padding edge
2888        View firstView = null;
2889        View lastView = null;
2890        int paddingMin = mWindowAlignment.mainAxis().getPaddingMin();
2891        int clientSize = mWindowAlignment.mainAxis().getClientSize();
2892        final int row = mGrid.getRowIndex(pos);
2893        if (viewMin < paddingMin) {
2894            // view enters low padding area:
2895            firstView = view;
2896            if (mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_PAGE) {
2897                // scroll one "page" left/top,
2898                // align first visible item of the "page" at the low padding edge.
2899                while (prependOneColumnVisibleItems()) {
2900                    CircularIntArray positions =
2901                            mGrid.getItemPositionsInRows(mGrid.getFirstVisibleIndex(), pos)[row];
2902                    firstView = findViewByPosition(positions.get(0));
2903                    if (viewMax - getViewMin(firstView) > clientSize) {
2904                        if (positions.size() > 2) {
2905                            firstView = findViewByPosition(positions.get(2));
2906                        }
2907                        break;
2908                    }
2909                }
2910            }
2911        } else if (viewMax > clientSize + paddingMin) {
2912            // view enters high padding area:
2913            if (mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_PAGE) {
2914                // scroll whole one page right/bottom, align view at the low padding edge.
2915                firstView = view;
2916                do {
2917                    CircularIntArray positions =
2918                            mGrid.getItemPositionsInRows(pos, mGrid.getLastVisibleIndex())[row];
2919                    lastView = findViewByPosition(positions.get(positions.size() - 1));
2920                    if (getViewMax(lastView) - viewMin > clientSize) {
2921                        lastView = null;
2922                        break;
2923                    }
2924                } while (appendOneColumnVisibleItems());
2925                if (lastView != null) {
2926                    // however if we reached end,  we should align last view.
2927                    firstView = null;
2928                }
2929            } else {
2930                lastView = view;
2931            }
2932        }
2933        int scrollPrimary = 0;
2934        int scrollSecondary = 0;
2935        if (firstView != null) {
2936            scrollPrimary = getViewMin(firstView) - paddingMin;
2937        } else if (lastView != null) {
2938            scrollPrimary = getViewMax(lastView) - (paddingMin + clientSize);
2939        }
2940        View secondaryAlignedView;
2941        if (firstView != null) {
2942            secondaryAlignedView = firstView;
2943        } else if (lastView != null) {
2944            secondaryAlignedView = lastView;
2945        } else {
2946            secondaryAlignedView = view;
2947        }
2948        scrollSecondary = getSecondaryScrollDistance(secondaryAlignedView);
2949        if (scrollPrimary != 0 || scrollSecondary != 0) {
2950            deltas[0] = scrollPrimary;
2951            deltas[1] = scrollSecondary;
2952            return true;
2953        }
2954        return false;
2955    }
2956
2957    private boolean getAlignedPosition(View view, View childView, int[] deltas) {
2958        int scrollPrimary = getPrimaryAlignedScrollDistance(view);
2959        if (childView != null) {
2960            scrollPrimary = getAdjustedPrimaryAlignedScrollDistance(scrollPrimary, view, childView);
2961        }
2962        int scrollSecondary = getSecondaryScrollDistance(view);
2963        if (DEBUG) {
2964            Log.v(getTag(), "getAlignedPosition " + scrollPrimary + " " + scrollSecondary
2965                    + " " + mPrimaryScrollExtra + " " + mWindowAlignment);
2966        }
2967        scrollPrimary += mPrimaryScrollExtra;
2968        if (scrollPrimary != 0 || scrollSecondary != 0) {
2969            deltas[0] = scrollPrimary;
2970            deltas[1] = scrollSecondary;
2971            return true;
2972        } else {
2973            deltas[0] = 0;
2974            deltas[1] = 0;
2975        }
2976        return false;
2977    }
2978
2979    private void scrollGrid(int scrollPrimary, int scrollSecondary, boolean smooth) {
2980        if (mInLayout) {
2981            scrollDirectionPrimary(scrollPrimary);
2982            scrollDirectionSecondary(scrollSecondary);
2983        } else {
2984            int scrollX;
2985            int scrollY;
2986            if (mOrientation == HORIZONTAL) {
2987                scrollX = scrollPrimary;
2988                scrollY = scrollSecondary;
2989            } else {
2990                scrollX = scrollSecondary;
2991                scrollY = scrollPrimary;
2992            }
2993            if (smooth) {
2994                mBaseGridView.smoothScrollBy(scrollX, scrollY);
2995            } else {
2996                mBaseGridView.scrollBy(scrollX, scrollY);
2997                dispatchChildSelectedAndPositioned();
2998            }
2999        }
3000    }
3001
3002    public void setPruneChild(boolean pruneChild) {
3003        if (mPruneChild != pruneChild) {
3004            mPruneChild = pruneChild;
3005            if (mPruneChild) {
3006                requestLayout();
3007            }
3008        }
3009    }
3010
3011    public boolean getPruneChild() {
3012        return mPruneChild;
3013    }
3014
3015    public void setScrollEnabled(boolean scrollEnabled) {
3016        if (mScrollEnabled != scrollEnabled) {
3017            mScrollEnabled = scrollEnabled;
3018            if (mScrollEnabled && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED
3019                    && mFocusPosition != NO_POSITION) {
3020                scrollToSelection(mFocusPosition, mSubFocusPosition,
3021                        true, mPrimaryScrollExtra);
3022            }
3023        }
3024    }
3025
3026    public boolean isScrollEnabled() {
3027        return mScrollEnabled;
3028    }
3029
3030    private int findImmediateChildIndex(View view) {
3031        if (mBaseGridView != null && view != mBaseGridView) {
3032            view = findContainingItemView(view);
3033            if (view != null) {
3034                for (int i = 0, count = getChildCount(); i < count; i++) {
3035                    if (getChildAt(i) == view) {
3036                        return i;
3037                    }
3038                }
3039            }
3040        }
3041        return NO_POSITION;
3042    }
3043
3044    void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
3045        if (gainFocus) {
3046            // if gridview.requestFocus() is called, select first focusable child.
3047            for (int i = mFocusPosition; ;i++) {
3048                View view = findViewByPosition(i);
3049                if (view == null) {
3050                    break;
3051                }
3052                if (view.getVisibility() == View.VISIBLE && view.hasFocusable()) {
3053                    view.requestFocus();
3054                    break;
3055                }
3056            }
3057        }
3058    }
3059
3060    void setFocusSearchDisabled(boolean disabled) {
3061        mFocusSearchDisabled = disabled;
3062    }
3063
3064    boolean isFocusSearchDisabled() {
3065        return mFocusSearchDisabled;
3066    }
3067
3068    @Override
3069    public View onInterceptFocusSearch(View focused, int direction) {
3070        if (mFocusSearchDisabled) {
3071            return focused;
3072        }
3073
3074        final FocusFinder ff = FocusFinder.getInstance();
3075        View result = null;
3076        if (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD) {
3077            // convert direction to absolute direction and see if we have a view there and if not
3078            // tell LayoutManager to add if it can.
3079            if (canScrollVertically()) {
3080                final int absDir =
3081                        direction == View.FOCUS_FORWARD ? View.FOCUS_DOWN : View.FOCUS_UP;
3082                result = ff.findNextFocus(mBaseGridView, focused, absDir);
3083            }
3084            if (canScrollHorizontally()) {
3085                boolean rtl = getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
3086                final int absDir = (direction == View.FOCUS_FORWARD) ^ rtl
3087                        ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
3088                result = ff.findNextFocus(mBaseGridView, focused, absDir);
3089            }
3090        } else {
3091            result = ff.findNextFocus(mBaseGridView, focused, direction);
3092        }
3093        if (result != null) {
3094            return result;
3095        }
3096
3097        if (mBaseGridView.getDescendantFocusability() == ViewGroup.FOCUS_BLOCK_DESCENDANTS) {
3098            return mBaseGridView.getParent().focusSearch(focused, direction);
3099        }
3100
3101        if (DEBUG) Log.v(getTag(), "regular focusSearch failed direction " + direction);
3102        int movement = getMovement(direction);
3103        final boolean isScroll = mBaseGridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE;
3104        if (movement == NEXT_ITEM) {
3105            if (isScroll || !mFocusOutEnd) {
3106                result = focused;
3107            }
3108            if (mScrollEnabled && !hasCreatedLastItem()) {
3109                processPendingMovement(true);
3110                result = focused;
3111            }
3112        } else if (movement == PREV_ITEM) {
3113            if (isScroll || !mFocusOutFront) {
3114                result = focused;
3115            }
3116            if (mScrollEnabled && !hasCreatedFirstItem()) {
3117                processPendingMovement(false);
3118                result = focused;
3119            }
3120        } else if (movement == NEXT_ROW) {
3121            if (isScroll || !mFocusOutSideEnd) {
3122                result = focused;
3123            }
3124        } else if (movement == PREV_ROW) {
3125            if (isScroll || !mFocusOutSideStart) {
3126                result = focused;
3127            }
3128        }
3129        if (result != null) {
3130            return result;
3131        }
3132
3133        if (DEBUG) Log.v(getTag(), "now focusSearch in parent");
3134        result = mBaseGridView.getParent().focusSearch(focused, direction);
3135        if (result != null) {
3136            return result;
3137        }
3138        return focused != null ? focused : mBaseGridView;
3139    }
3140
3141    boolean hasPreviousViewInSameRow(int pos) {
3142        if (mGrid == null || pos == NO_POSITION || mGrid.getFirstVisibleIndex() < 0) {
3143            return false;
3144        }
3145        if (mGrid.getFirstVisibleIndex() > 0) {
3146            return true;
3147        }
3148        final int focusedRow = mGrid.getLocation(pos).row;
3149        for (int i = getChildCount() - 1; i >= 0; i--) {
3150            int position = getAdapterPositionByIndex(i);
3151            Grid.Location loc = mGrid.getLocation(position);
3152            if (loc != null && loc.row == focusedRow) {
3153                if (position < pos) {
3154                    return true;
3155                }
3156            }
3157        }
3158        return false;
3159    }
3160
3161    @Override
3162    public boolean onAddFocusables(RecyclerView recyclerView,
3163            ArrayList<View> views, int direction, int focusableMode) {
3164        if (mFocusSearchDisabled) {
3165            return true;
3166        }
3167        // If this viewgroup or one of its children currently has focus then we
3168        // consider our children for focus searching in main direction on the same row.
3169        // If this viewgroup has no focus and using focus align, we want the system
3170        // to ignore our children and pass focus to the viewgroup, which will pass
3171        // focus on to its children appropriately.
3172        // If this viewgroup has no focus and not using focus align, we want to
3173        // consider the child that does not overlap with padding area.
3174        if (recyclerView.hasFocus()) {
3175            if (mPendingMoveSmoothScroller != null) {
3176                // don't find next focusable if has pending movement.
3177                return true;
3178            }
3179            final int movement = getMovement(direction);
3180            final View focused = recyclerView.findFocus();
3181            final int focusedIndex = findImmediateChildIndex(focused);
3182            final int focusedPos = getAdapterPositionByIndex(focusedIndex);
3183            // Add focusables of focused item.
3184            if (focusedPos != NO_POSITION) {
3185                findViewByPosition(focusedPos).addFocusables(views,  direction, focusableMode);
3186            }
3187            if (mGrid == null || getChildCount() == 0) {
3188                // no grid information, or no child, bail out.
3189                return true;
3190            }
3191            if ((movement == NEXT_ROW || movement == PREV_ROW) && mGrid.getNumRows() <= 1) {
3192                // For single row, cannot navigate to previous/next row.
3193                return true;
3194            }
3195            // Add focusables of neighbor depending on the focus search direction.
3196            final int focusedRow = mGrid != null && focusedPos != NO_POSITION
3197                    ? mGrid.getLocation(focusedPos).row : NO_POSITION;
3198            final int focusableCount = views.size();
3199            int inc = movement == NEXT_ITEM || movement == NEXT_ROW ? 1 : -1;
3200            int loop_end = inc > 0 ? getChildCount() - 1 : 0;
3201            int loop_start;
3202            if (focusedIndex == NO_POSITION) {
3203                loop_start = inc > 0 ? 0 : getChildCount() - 1;
3204            } else {
3205                loop_start = focusedIndex + inc;
3206            }
3207            for (int i = loop_start; inc > 0 ? i <= loop_end : i >= loop_end; i += inc) {
3208                final View child = getChildAt(i);
3209                if (child.getVisibility() != View.VISIBLE || !child.hasFocusable()) {
3210                    continue;
3211                }
3212                // if there wasn't any focusing item,  add the very first focusable
3213                // items and stop.
3214                if (focusedPos == NO_POSITION) {
3215                    child.addFocusables(views,  direction, focusableMode);
3216                    if (views.size() > focusableCount) {
3217                        break;
3218                    }
3219                    continue;
3220                }
3221                int position = getAdapterPositionByIndex(i);
3222                Grid.Location loc = mGrid.getLocation(position);
3223                if (loc == null) {
3224                    continue;
3225                }
3226                if (movement == NEXT_ITEM) {
3227                    // Add first focusable item on the same row
3228                    if (loc.row == focusedRow && position > focusedPos) {
3229                        child.addFocusables(views,  direction, focusableMode);
3230                        if (views.size() > focusableCount) {
3231                            break;
3232                        }
3233                    }
3234                } else if (movement == PREV_ITEM) {
3235                    // Add first focusable item on the same row
3236                    if (loc.row == focusedRow && position < focusedPos) {
3237                        child.addFocusables(views,  direction, focusableMode);
3238                        if (views.size() > focusableCount) {
3239                            break;
3240                        }
3241                    }
3242                } else if (movement == NEXT_ROW) {
3243                    // Add all focusable items after this item whose row index is bigger
3244                    if (loc.row == focusedRow) {
3245                        continue;
3246                    } else if (loc.row < focusedRow) {
3247                        break;
3248                    }
3249                    child.addFocusables(views,  direction, focusableMode);
3250                } else if (movement == PREV_ROW) {
3251                    // Add all focusable items before this item whose row index is smaller
3252                    if (loc.row == focusedRow) {
3253                        continue;
3254                    } else if (loc.row > focusedRow) {
3255                        break;
3256                    }
3257                    child.addFocusables(views,  direction, focusableMode);
3258                }
3259            }
3260        } else {
3261            int focusableCount = views.size();
3262            if (mFocusScrollStrategy != BaseGridView.FOCUS_SCROLL_ALIGNED) {
3263                // adding views not overlapping padding area to avoid scrolling in gaining focus
3264                int left = mWindowAlignment.mainAxis().getPaddingMin();
3265                int right = mWindowAlignment.mainAxis().getClientSize() + left;
3266                for (int i = 0, count = getChildCount(); i < count; i++) {
3267                    View child = getChildAt(i);
3268                    if (child.getVisibility() == View.VISIBLE) {
3269                        if (getViewMin(child) >= left && getViewMax(child) <= right) {
3270                            child.addFocusables(views, direction, focusableMode);
3271                        }
3272                    }
3273                }
3274                // if we cannot find any, then just add all children.
3275                if (views.size() == focusableCount) {
3276                    for (int i = 0, count = getChildCount(); i < count; i++) {
3277                        View child = getChildAt(i);
3278                        if (child.getVisibility() == View.VISIBLE) {
3279                            child.addFocusables(views, direction, focusableMode);
3280                        }
3281                    }
3282                }
3283            } else {
3284                View view = findViewByPosition(mFocusPosition);
3285                if (view != null) {
3286                    view.addFocusables(views, direction, focusableMode);
3287                }
3288            }
3289            // if still cannot find any, fall through and add itself
3290            if (views.size() != focusableCount) {
3291                return true;
3292            }
3293            if (recyclerView.isFocusable()) {
3294                views.add(recyclerView);
3295            }
3296        }
3297        return true;
3298    }
3299
3300    boolean hasCreatedLastItem() {
3301        int count = getItemCount();
3302        return count == 0 || mBaseGridView.findViewHolderForAdapterPosition(count - 1) != null;
3303    }
3304
3305    boolean hasCreatedFirstItem() {
3306        int count = getItemCount();
3307        return count == 0 || mBaseGridView.findViewHolderForAdapterPosition(0) != null;
3308    }
3309
3310    boolean canScrollTo(View view) {
3311        return view.getVisibility() == View.VISIBLE && (!hasFocus() || view.hasFocusable());
3312    }
3313
3314    boolean gridOnRequestFocusInDescendants(RecyclerView recyclerView, int direction,
3315            Rect previouslyFocusedRect) {
3316        switch (mFocusScrollStrategy) {
3317            case BaseGridView.FOCUS_SCROLL_ALIGNED:
3318            default:
3319                return gridOnRequestFocusInDescendantsAligned(recyclerView,
3320                        direction, previouslyFocusedRect);
3321            case BaseGridView.FOCUS_SCROLL_PAGE:
3322            case BaseGridView.FOCUS_SCROLL_ITEM:
3323                return gridOnRequestFocusInDescendantsUnaligned(recyclerView,
3324                        direction, previouslyFocusedRect);
3325        }
3326    }
3327
3328    private boolean gridOnRequestFocusInDescendantsAligned(RecyclerView recyclerView,
3329            int direction, Rect previouslyFocusedRect) {
3330        View view = findViewByPosition(mFocusPosition);
3331        if (view != null) {
3332            boolean result = view.requestFocus(direction, previouslyFocusedRect);
3333            if (!result && DEBUG) {
3334                Log.w(getTag(), "failed to request focus on " + view);
3335            }
3336            return result;
3337        }
3338        return false;
3339    }
3340
3341    private boolean gridOnRequestFocusInDescendantsUnaligned(RecyclerView recyclerView,
3342            int direction, Rect previouslyFocusedRect) {
3343        // focus to view not overlapping padding area to avoid scrolling in gaining focus
3344        int index;
3345        int increment;
3346        int end;
3347        int count = getChildCount();
3348        if ((direction & View.FOCUS_FORWARD) != 0) {
3349            index = 0;
3350            increment = 1;
3351            end = count;
3352        } else {
3353            index = count - 1;
3354            increment = -1;
3355            end = -1;
3356        }
3357        int left = mWindowAlignment.mainAxis().getPaddingMin();
3358        int right = mWindowAlignment.mainAxis().getClientSize() + left;
3359        for (int i = index; i != end; i += increment) {
3360            View child = getChildAt(i);
3361            if (child.getVisibility() == View.VISIBLE) {
3362                if (getViewMin(child) >= left && getViewMax(child) <= right) {
3363                    if (child.requestFocus(direction, previouslyFocusedRect)) {
3364                        return true;
3365                    }
3366                }
3367            }
3368        }
3369        return false;
3370    }
3371
3372    private final static int PREV_ITEM = 0;
3373    private final static int NEXT_ITEM = 1;
3374    private final static int PREV_ROW = 2;
3375    private final static int NEXT_ROW = 3;
3376
3377    private int getMovement(int direction) {
3378        int movement = View.FOCUS_LEFT;
3379
3380        if (mOrientation == HORIZONTAL) {
3381            switch(direction) {
3382                case View.FOCUS_LEFT:
3383                    movement = (!mReverseFlowPrimary) ? PREV_ITEM : NEXT_ITEM;
3384                    break;
3385                case View.FOCUS_RIGHT:
3386                    movement = (!mReverseFlowPrimary) ? NEXT_ITEM : PREV_ITEM;
3387                    break;
3388                case View.FOCUS_UP:
3389                    movement = PREV_ROW;
3390                    break;
3391                case View.FOCUS_DOWN:
3392                    movement = NEXT_ROW;
3393                    break;
3394            }
3395        } else if (mOrientation == VERTICAL) {
3396            switch(direction) {
3397                case View.FOCUS_LEFT:
3398                    movement = (!mReverseFlowSecondary) ? PREV_ROW : NEXT_ROW;
3399                    break;
3400                case View.FOCUS_RIGHT:
3401                    movement = (!mReverseFlowSecondary) ? NEXT_ROW : PREV_ROW;
3402                    break;
3403                case View.FOCUS_UP:
3404                    movement = PREV_ITEM;
3405                    break;
3406                case View.FOCUS_DOWN:
3407                    movement = NEXT_ITEM;
3408                    break;
3409            }
3410        }
3411
3412        return movement;
3413    }
3414
3415    int getChildDrawingOrder(RecyclerView recyclerView, int childCount, int i) {
3416        View view = findViewByPosition(mFocusPosition);
3417        if (view == null) {
3418            return i;
3419        }
3420        int focusIndex = recyclerView.indexOfChild(view);
3421        // supposely 0 1 2 3 4 5 6 7 8 9, 4 is the center item
3422        // drawing order is 0 1 2 3 9 8 7 6 5 4
3423        if (i < focusIndex) {
3424            return i;
3425        } else if (i < childCount - 1) {
3426            return focusIndex + childCount - 1 - i;
3427        } else {
3428            return focusIndex;
3429        }
3430    }
3431
3432    @Override
3433    public void onAdapterChanged(RecyclerView.Adapter oldAdapter,
3434            RecyclerView.Adapter newAdapter) {
3435        if (DEBUG) Log.v(getTag(), "onAdapterChanged to " + newAdapter);
3436        if (oldAdapter != null) {
3437            discardLayoutInfo();
3438            mFocusPosition = NO_POSITION;
3439            mFocusPositionOffset = 0;
3440            mChildrenStates.clear();
3441        }
3442        if (newAdapter instanceof FacetProviderAdapter) {
3443            mFacetProviderAdapter = (FacetProviderAdapter) newAdapter;
3444        } else {
3445            mFacetProviderAdapter = null;
3446        }
3447        super.onAdapterChanged(oldAdapter, newAdapter);
3448    }
3449
3450    private void discardLayoutInfo() {
3451        mGrid = null;
3452        mRowSizeSecondary = null;
3453        mRowSecondarySizeRefresh = false;
3454    }
3455
3456    public void setLayoutEnabled(boolean layoutEnabled) {
3457        if (mLayoutEnabled != layoutEnabled) {
3458            mLayoutEnabled = layoutEnabled;
3459            requestLayout();
3460        }
3461    }
3462
3463    void setChildrenVisibility(int visibility) {
3464        mChildVisibility = visibility;
3465        if (mChildVisibility != -1) {
3466            int count = getChildCount();
3467            for (int i= 0; i < count; i++) {
3468                getChildAt(i).setVisibility(mChildVisibility);
3469            }
3470        }
3471    }
3472
3473    final static class SavedState implements Parcelable {
3474
3475        int index; // index inside adapter of the current view
3476        Bundle childStates = Bundle.EMPTY;
3477
3478        @Override
3479        public void writeToParcel(Parcel out, int flags) {
3480            out.writeInt(index);
3481            out.writeBundle(childStates);
3482        }
3483
3484        @SuppressWarnings("hiding")
3485        public static final Parcelable.Creator<SavedState> CREATOR =
3486                new Parcelable.Creator<SavedState>() {
3487                    @Override
3488                    public SavedState createFromParcel(Parcel in) {
3489                        return new SavedState(in);
3490                    }
3491
3492                    @Override
3493                    public SavedState[] newArray(int size) {
3494                        return new SavedState[size];
3495                    }
3496                };
3497
3498        @Override
3499        public int describeContents() {
3500            return 0;
3501        }
3502
3503        SavedState(Parcel in) {
3504            index = in.readInt();
3505            childStates = in.readBundle(GridLayoutManager.class.getClassLoader());
3506        }
3507
3508        SavedState() {
3509        }
3510    }
3511
3512    @Override
3513    public Parcelable onSaveInstanceState() {
3514        if (DEBUG) Log.v(getTag(), "onSaveInstanceState getSelection() " + getSelection());
3515        SavedState ss = new SavedState();
3516        // save selected index
3517        ss.index = getSelection();
3518        // save offscreen child (state when they are recycled)
3519        Bundle bundle = mChildrenStates.saveAsBundle();
3520        // save views currently is on screen (TODO save cached views)
3521        for (int i = 0, count = getChildCount(); i < count; i++) {
3522            View view = getChildAt(i);
3523            int position = getAdapterPositionByView(view);
3524            if (position != NO_POSITION) {
3525                bundle = mChildrenStates.saveOnScreenView(bundle, view, position);
3526            }
3527        }
3528        ss.childStates = bundle;
3529        return ss;
3530    }
3531
3532    void onChildRecycled(RecyclerView.ViewHolder holder) {
3533        final int position = holder.getAdapterPosition();
3534        if (position != NO_POSITION) {
3535            mChildrenStates.saveOffscreenView(holder.itemView, position);
3536        }
3537    }
3538
3539    @Override
3540    public void onRestoreInstanceState(Parcelable state) {
3541        if (!(state instanceof SavedState)) {
3542            return;
3543        }
3544        SavedState loadingState = (SavedState)state;
3545        mFocusPosition = loadingState.index;
3546        mFocusPositionOffset = 0;
3547        mChildrenStates.loadFromBundle(loadingState.childStates);
3548        mForceFullLayout = true;
3549        requestLayout();
3550        if (DEBUG) Log.v(getTag(), "onRestoreInstanceState mFocusPosition " + mFocusPosition);
3551    }
3552
3553    @Override
3554    public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
3555            RecyclerView.State state) {
3556        if (mOrientation == HORIZONTAL && mGrid != null) {
3557            return mGrid.getNumRows();
3558        }
3559        return super.getRowCountForAccessibility(recycler, state);
3560    }
3561
3562    @Override
3563    public int getColumnCountForAccessibility(RecyclerView.Recycler recycler,
3564            RecyclerView.State state) {
3565        if (mOrientation == VERTICAL && mGrid != null) {
3566            return mGrid.getNumRows();
3567        }
3568        return super.getColumnCountForAccessibility(recycler, state);
3569    }
3570
3571    @Override
3572    public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
3573            RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
3574        ViewGroup.LayoutParams lp = host.getLayoutParams();
3575        if (mGrid == null || !(lp instanceof LayoutParams)) {
3576            return;
3577        }
3578        LayoutParams glp = (LayoutParams) lp;
3579        int position = glp.getViewAdapterPosition();
3580        int rowIndex = position >= 0 ? mGrid.getRowIndex(position) : -1;
3581        if (rowIndex < 0) {
3582            return;
3583        }
3584        int guessSpanIndex = position / mGrid.getNumRows();
3585        if (mOrientation == HORIZONTAL) {
3586            info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
3587                    rowIndex, 1, guessSpanIndex, 1, false, false));
3588        } else {
3589            info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
3590                    guessSpanIndex, 1, rowIndex, 1, false, false));
3591        }
3592    }
3593
3594    /*
3595     * Leanback widget is different than the default implementation because the "scroll" is driven
3596     * by selection change.
3597     */
3598    @Override
3599    public boolean performAccessibilityAction(Recycler recycler, State state, int action,
3600            Bundle args) {
3601        saveContext(recycler, state);
3602        switch (action) {
3603            case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD:
3604                // try to focus all the way to the last visible item on the same row.
3605                processSelectionMoves(false, -mState.getItemCount());
3606                break;
3607            case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD:
3608                processSelectionMoves(false, mState.getItemCount());
3609                break;
3610        }
3611        leaveContext();
3612        return true;
3613    }
3614
3615    /*
3616     * Move mFocusPosition multiple steps on the same row in main direction.
3617     * Stops when moves are all consumed or reach first/last visible item.
3618     * Returning remaining moves.
3619     */
3620    int processSelectionMoves(boolean preventScroll, int moves) {
3621        if (mGrid == null) {
3622            return moves;
3623        }
3624        int focusPosition = mFocusPosition;
3625        int focusedRow = focusPosition != NO_POSITION
3626                ? mGrid.getRowIndex(focusPosition) : NO_POSITION;
3627        View newSelected = null;
3628        for (int i = 0, count = getChildCount(); i < count && moves != 0; i++) {
3629            int index = moves > 0 ? i : count - 1 - i;
3630            final View child = getChildAt(index);
3631            if (!canScrollTo(child)) {
3632                continue;
3633            }
3634            int position = getAdapterPositionByIndex(index);
3635            int rowIndex = mGrid.getRowIndex(position);
3636            if (focusedRow == NO_POSITION) {
3637                focusPosition = position;
3638                newSelected = child;
3639                focusedRow = rowIndex;
3640            } else if (rowIndex == focusedRow) {
3641                if ((moves > 0 && position > focusPosition)
3642                        || (moves < 0 && position < focusPosition)) {
3643                    focusPosition = position;
3644                    newSelected = child;
3645                    if (moves > 0) {
3646                        moves--;
3647                    } else {
3648                        moves++;
3649                    }
3650                }
3651            }
3652        }
3653        if (newSelected != null) {
3654            if (preventScroll) {
3655                if (hasFocus()) {
3656                    mInSelection = true;
3657                    newSelected.requestFocus();
3658                    mInSelection = false;
3659                }
3660                mFocusPosition = focusPosition;
3661                mSubFocusPosition = 0;
3662            } else {
3663                scrollToView(newSelected, true);
3664            }
3665        }
3666        return moves;
3667    }
3668
3669    @Override
3670    public void onInitializeAccessibilityNodeInfo(Recycler recycler, State state,
3671            AccessibilityNodeInfoCompat info) {
3672        saveContext(recycler, state);
3673        if (mScrollEnabled && !hasCreatedFirstItem()) {
3674            info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
3675            info.setScrollable(true);
3676        }
3677        if (mScrollEnabled && !hasCreatedLastItem()) {
3678            info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
3679            info.setScrollable(true);
3680        }
3681        final AccessibilityNodeInfoCompat.CollectionInfoCompat collectionInfo =
3682                AccessibilityNodeInfoCompat.CollectionInfoCompat
3683                        .obtain(getRowCountForAccessibility(recycler, state),
3684                                getColumnCountForAccessibility(recycler, state),
3685                                isLayoutHierarchical(recycler, state),
3686                                getSelectionModeForAccessibility(recycler, state));
3687        info.setCollectionInfo(collectionInfo);
3688        leaveContext();
3689    }
3690}
3691