GridLayoutManager.java revision 77b17ebed6d1f8488b150b25e2b6754971480cae
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 android.content.Context;
17import android.graphics.PointF;
18import android.graphics.Rect;
19import android.os.Bundle;
20import android.os.Parcel;
21import android.os.Parcelable;
22import android.support.v4.util.CircularIntArray;
23import android.support.v4.view.ViewCompat;
24import android.support.v7.widget.LinearSmoothScroller;
25import android.support.v7.widget.RecyclerView;
26import android.support.v7.widget.RecyclerView.Recycler;
27import android.support.v7.widget.RecyclerView.State;
28import android.support.v17.leanback.os.TraceHelper;
29
30import static android.support.v7.widget.RecyclerView.NO_ID;
31import static android.support.v7.widget.RecyclerView.NO_POSITION;
32import static android.support.v7.widget.RecyclerView.HORIZONTAL;
33import static android.support.v7.widget.RecyclerView.VERTICAL;
34
35import android.util.AttributeSet;
36import android.util.Log;
37import android.view.FocusFinder;
38import android.view.Gravity;
39import android.view.View;
40import android.view.ViewParent;
41import android.view.View.MeasureSpec;
42import android.view.ViewGroup.MarginLayoutParams;
43import android.view.ViewGroup;
44
45import java.io.PrintWriter;
46import java.io.StringWriter;
47import java.util.ArrayList;
48import java.util.List;
49
50final class GridLayoutManager extends RecyclerView.LayoutManager {
51
52     /*
53      * LayoutParams for {@link HorizontalGridView} and {@link VerticalGridView}.
54      * The class currently does two internal jobs:
55      * - Saves optical bounds insets.
56      * - Caches focus align view center.
57      */
58    static class LayoutParams extends RecyclerView.LayoutParams {
59
60        // The view is saved only during animation.
61        private View mView;
62
63        // For placement
64        private int mLeftInset;
65        private int mTopInset;
66        private int mRightInset;
67        private int mBottomInset;
68
69        // For alignment
70        private int mAlignX;
71        private int mAlignY;
72
73        public LayoutParams(Context c, AttributeSet attrs) {
74            super(c, attrs);
75        }
76
77        public LayoutParams(int width, int height) {
78            super(width, height);
79        }
80
81        public LayoutParams(MarginLayoutParams source) {
82            super(source);
83        }
84
85        public LayoutParams(ViewGroup.LayoutParams source) {
86            super(source);
87        }
88
89        public LayoutParams(RecyclerView.LayoutParams source) {
90            super(source);
91        }
92
93        public LayoutParams(LayoutParams source) {
94            super(source);
95        }
96
97        int getAlignX() {
98            return mAlignX;
99        }
100
101        int getAlignY() {
102            return mAlignY;
103        }
104
105        int getOpticalLeft(View view) {
106            return view.getLeft() + mLeftInset;
107        }
108
109        int getOpticalTop(View view) {
110            return view.getTop() + mTopInset;
111        }
112
113        int getOpticalRight(View view) {
114            return view.getRight() - mRightInset;
115        }
116
117        int getOpticalBottom(View view) {
118            return view.getBottom() - mBottomInset;
119        }
120
121        int getOpticalWidth(View view) {
122            return view.getWidth() - mLeftInset - mRightInset;
123        }
124
125        int getOpticalHeight(View view) {
126            return view.getHeight() - mTopInset - mBottomInset;
127        }
128
129        int getOpticalLeftInset() {
130            return mLeftInset;
131        }
132
133        int getOpticalRightInset() {
134            return mRightInset;
135        }
136
137        int getOpticalTopInset() {
138            return mTopInset;
139        }
140
141        int getOpticalBottomInset() {
142            return mBottomInset;
143        }
144
145        void setAlignX(int alignX) {
146            mAlignX = alignX;
147        }
148
149        void setAlignY(int alignY) {
150            mAlignY = alignY;
151        }
152
153        void setOpticalInsets(int leftInset, int topInset, int rightInset, int bottomInset) {
154            mLeftInset = leftInset;
155            mTopInset = topInset;
156            mRightInset = rightInset;
157            mBottomInset = bottomInset;
158        }
159
160        private void invalidateItemDecoration() {
161            ViewParent parent = mView.getParent();
162            if (parent instanceof RecyclerView) {
163                // TODO: we only need invalidate parent if it has ItemDecoration
164                ((RecyclerView) parent).invalidate();
165            }
166        }
167    }
168
169    /**
170     * Base class which scrolls to selected view in onStop().
171     */
172    abstract class GridLinearSmoothScroller extends LinearSmoothScroller {
173        GridLinearSmoothScroller() {
174            super(mBaseGridView.getContext());
175        }
176
177        @Override
178        protected void onStop() {
179            // onTargetFound() may not be called if we hit the "wall" first.
180            View targetView = findViewByPosition(getTargetPosition());
181            if (hasFocus() && targetView != null) {
182                mInSelection = true;
183                targetView.requestFocus();
184                mInSelection = false;
185            }
186            if (needsDispatchChildSelectedOnStop()) {
187                dispatchChildSelected();
188            }
189            super.onStop();
190        }
191
192        boolean needsDispatchChildSelectedOnStop() {
193            return true;
194        }
195
196        @Override
197        protected void onTargetFound(View targetView,
198                RecyclerView.State state, Action action) {
199            if (getScrollPosition(targetView, sTwoInts)) {
200                int dx, dy;
201                if (mOrientation == HORIZONTAL) {
202                    dx = sTwoInts[0];
203                    dy = sTwoInts[1];
204                } else {
205                    dx = sTwoInts[1];
206                    dy = sTwoInts[0];
207                }
208                final int distance = (int) Math.sqrt(dx * dx + dy * dy);
209                final int time = calculateTimeForDeceleration(distance);
210                action.update(dx, dy, time, mDecelerateInterpolator);
211            }
212        }
213    }
214
215    /**
216     * The SmoothScroller that remembers pending DPAD keys and consume pending keys
217     * during scroll.
218     */
219    final class PendingMoveSmoothScroller extends GridLinearSmoothScroller {
220        // -2 is a target position that LinearSmoothScroller can never find until
221        // consumePendingMovesXXX() sets real targetPosition.
222        final static int TARGET_UNDEFINED = -2;
223        // whether the grid is staggered.
224        private final boolean mStaggeredGrid;
225        // Number of pending movements on primary direction, negative if PREV_ITEM.
226        private int mPendingMoves;
227
228        PendingMoveSmoothScroller(int initialPendingMoves, boolean staggeredGrid) {
229            mPendingMoves = initialPendingMoves;
230            mStaggeredGrid = staggeredGrid;
231            setTargetPosition(TARGET_UNDEFINED);
232        }
233
234        void increasePendingMoves() {
235            if (mPendingMoves < MAX_PENDING_MOVES) {
236                mPendingMoves++;
237                if (mPendingMoves == 0) {
238                    dispatchChildSelected();
239                }
240            }
241        }
242
243        void decreasePendingMoves() {
244            if (mPendingMoves > -MAX_PENDING_MOVES) {
245                mPendingMoves--;
246                if (mPendingMoves == 0) {
247                    dispatchChildSelected();
248                }
249            }
250        }
251
252        /**
253         * Called before laid out an item when non-staggered grid can handle pending movements
254         * by skipping "mNumRows" per movement;  staggered grid will have to wait the item
255         * has been laid out in consumePendingMovesAfterLayout().
256         */
257        void consumePendingMovesBeforeLayout() {
258            if (mStaggeredGrid || mPendingMoves == 0) {
259                return;
260            }
261            View newSelected = null;
262            int startPos = mPendingMoves > 0 ? mFocusPosition + mNumRows :
263                    mFocusPosition - mNumRows;
264            for (int pos = startPos; mPendingMoves != 0;
265                    pos = mPendingMoves > 0 ? pos + mNumRows: pos - mNumRows) {
266                View v = findViewByPosition(pos);
267                if (v == null) {
268                    break;
269                }
270                if (!canScrollTo(v)) {
271                    continue;
272                }
273                newSelected = v;
274                mFocusPosition = pos;
275                if (mPendingMoves > 0) {
276                    mPendingMoves--;
277                } else {
278                    mPendingMoves++;
279                }
280            }
281            if (newSelected != null && hasFocus()) {
282                mInSelection = true;
283                newSelected.requestFocus();
284                mInSelection = false;
285            }
286        }
287
288        /**
289         * Called after laid out an item.  Staggered grid should find view on same
290         * Row and consume pending movements.
291         */
292        void consumePendingMovesAfterLayout() {
293            if (mStaggeredGrid && mPendingMoves != 0) {
294                // consume pending moves, focus to item on the same row.
295                final int focusedRow = mGrid != null && mFocusPosition != NO_POSITION ?
296                        mGrid.getLocation(mFocusPosition).row : NO_POSITION;
297                View newSelected = null;
298                for (int i = 0, count = getChildCount(); i < count && mPendingMoves != 0; i++) {
299                    int index = mPendingMoves > 0 ? i : count - 1 - i;
300                    final View child = getChildAt(index);
301                    if (!canScrollTo(child)) {
302                        continue;
303                    }
304                    int position = getPositionByIndex(index);
305                    Grid.Location loc = mGrid.getLocation(position);
306                    if (focusedRow == NO_POSITION || (loc != null && loc.row == focusedRow)) {
307                        if (mFocusPosition == NO_POSITION) {
308                            mFocusPosition = position;
309                            newSelected = child;
310                        } else if ((mPendingMoves > 0 && position > mFocusPosition)
311                                || (mPendingMoves < 0 && position < mFocusPosition)) {
312                            mFocusPosition = position;
313                            if (mPendingMoves > 0) {
314                                mPendingMoves--;
315                            } else {
316                                mPendingMoves++;
317                            }
318                            newSelected = child;
319                        }
320                    }
321                }
322                if (newSelected != null && hasFocus()) {
323                    mInSelection = true;
324                    newSelected.requestFocus();
325                    mInSelection = false;
326                }
327            }
328            if (mPendingMoves == 0 || (mPendingMoves > 0 && hasCreatedLastItem())
329                    || (mPendingMoves < 0 && hasCreatedFirstItem())) {
330                setTargetPosition(mFocusPosition);
331                stop();
332            }
333        }
334
335        @Override
336        protected void updateActionForInterimTarget(Action action) {
337            if (mPendingMoves == 0) {
338                return;
339            }
340            super.updateActionForInterimTarget(action);
341        }
342
343        @Override
344        public PointF computeScrollVectorForPosition(int targetPosition) {
345            if (mPendingMoves == 0) {
346                return null;
347            }
348            int direction = (mReverseFlowPrimary ? mPendingMoves > 0 : mPendingMoves < 0) ?
349                    -1 : 1;
350            if (mOrientation == HORIZONTAL) {
351                return new PointF(direction, 0);
352            } else {
353                return new PointF(0, direction);
354            }
355        }
356
357        @Override
358        boolean needsDispatchChildSelectedOnStop() {
359            return mPendingMoves != 0;
360        }
361
362        @Override
363        protected void onStop() {
364            super.onStop();
365            // if we hit wall,  need clear the remaining pending moves.
366            mPendingMoves = 0;
367            mPendingMoveSmoothScroller = null;
368            View v = findViewByPosition(getTargetPosition());
369            if (v != null) scrollToView(v, true);
370        }
371    };
372
373    private static final String TAG = "GridLayoutManager";
374    private static final boolean DEBUG = false;
375    private static final boolean TRACE = false;
376
377    // maximum pending movement in one direction.
378    private final static int MAX_PENDING_MOVES = 10;
379
380    private String getTag() {
381        return TAG + ":" + mBaseGridView.getId();
382    }
383
384    private final BaseGridView mBaseGridView;
385
386    /**
387     * Note on conventions in the presence of RTL layout directions:
388     * Many properties and method names reference entities related to the
389     * beginnings and ends of things.  In the presence of RTL flows,
390     * it may not be clear whether this is intended to reference a
391     * quantity that changes direction in RTL cases, or a quantity that
392     * does not.  Here are the conventions in use:
393     *
394     * start/end: coordinate quantities - do reverse
395     * (optical) left/right: coordinate quantities - do not reverse
396     * low/high: coordinate quantities - do not reverse
397     * min/max: coordinate quantities - do not reverse
398     * scroll offset - coordinate quantities - do not reverse
399     * first/last: positional indices - do not reverse
400     * front/end: positional indices - do not reverse
401     * prepend/append: related to positional indices - do not reverse
402     *
403     * Note that although quantities do not reverse in RTL flows, their
404     * relationship does.  In LTR flows, the first positional index is
405     * leftmost; in RTL flows, it is rightmost.  Thus, anywhere that
406     * positional quantities are mapped onto coordinate quantities,
407     * the flow must be checked and the logic reversed.
408     */
409
410    /**
411     * The orientation of a "row".
412     */
413    private int mOrientation = HORIZONTAL;
414
415    private RecyclerView.State mState;
416    private RecyclerView.Recycler mRecycler;
417
418    private boolean mInLayout = false;
419    private boolean mInFastRelayout;
420    private boolean mInSelection = false;
421
422    private OnChildSelectedListener mChildSelectedListener = null;
423
424    private OnChildLaidOutListener mChildLaidOutListener = null;
425
426    /**
427     * The focused position, it's not the currently visually aligned position
428     * but it is the final position that we intend to focus on. If there are
429     * multiple setSelection() called, mFocusPosition saves last value.
430     */
431    private int mFocusPosition = NO_POSITION;
432
433    /**
434     * LinearSmoothScroller that consume pending DPAD movements.
435     */
436    private PendingMoveSmoothScroller mPendingMoveSmoothScroller;
437
438    /**
439     * The offset to be applied to mFocusPosition, due to adapter change, on the next
440     * layout.  Set to Integer.MIN_VALUE means item was removed.
441     * TODO:  This is somewhat duplication of RecyclerView getOldPosition() which is
442     * unfortunately cleared after prelayout.
443     */
444    private int mFocusPositionOffset = 0;
445
446    /**
447     * Force a full layout under certain situations.  E.g. Rows change, jump to invisible child.
448     */
449    private boolean mForceFullLayout;
450
451    /**
452     * True if layout is enabled.
453     */
454    private boolean mLayoutEnabled = true;
455
456    /**
457     * override child visibility
458     */
459    private int mChildVisibility = -1;
460
461    /**
462     * The scroll offsets of the viewport relative to the entire view.
463     */
464    private int mScrollOffsetPrimary;
465    private int mScrollOffsetSecondary;
466
467    /**
468     * User-specified row height/column width.  Can be WRAP_CONTENT.
469     */
470    private int mRowSizeSecondaryRequested;
471
472    /**
473     * The fixed size of each grid item in the secondary direction. This corresponds to
474     * the row height, equal for all rows. Grid items may have variable length
475     * in the primary direction.
476     */
477    private int mFixedRowSizeSecondary;
478
479    /**
480     * Tracks the secondary size of each row.
481     */
482    private int[] mRowSizeSecondary;
483
484    /**
485     * Flag controlling whether the current/next layout should
486     * be updating the secondary size of rows.
487     */
488    private boolean mRowSecondarySizeRefresh;
489
490    /**
491     * The maximum measured size of the view.
492     */
493    private int mMaxSizeSecondary;
494
495    /**
496     * Margin between items.
497     */
498    private int mHorizontalMargin;
499    /**
500     * Margin between items vertically.
501     */
502    private int mVerticalMargin;
503    /**
504     * Margin in main direction.
505     */
506    private int mMarginPrimary;
507    /**
508     * Margin in second direction.
509     */
510    private int mMarginSecondary;
511    /**
512     * How to position child in secondary direction.
513     */
514    private int mGravity = Gravity.START | Gravity.TOP;
515    /**
516     * The number of rows in the grid.
517     */
518    private int mNumRows;
519    /**
520     * Number of rows requested, can be 0 to be determined by parent size and
521     * rowHeight.
522     */
523    private int mNumRowsRequested = 1;
524
525    /**
526     * Saves grid information of each view.
527     */
528    Grid mGrid;
529
530    /**
531     * Focus Scroll strategy.
532     */
533    private int mFocusScrollStrategy = BaseGridView.FOCUS_SCROLL_ALIGNED;
534    /**
535     * Defines how item view is aligned in the window.
536     */
537    private final WindowAlignment mWindowAlignment = new WindowAlignment();
538
539    /**
540     * Defines how item view is aligned.
541     */
542    private final ItemAlignment mItemAlignment = new ItemAlignment();
543
544    /**
545     * Dimensions of the view, width or height depending on orientation.
546     */
547    private int mSizePrimary;
548
549    /**
550     *  Allow DPAD key to navigate out at the front of the View (where position = 0),
551     *  default is false.
552     */
553    private boolean mFocusOutFront;
554
555    /**
556     * Allow DPAD key to navigate out at the end of the view, default is false.
557     */
558    private boolean mFocusOutEnd;
559
560    /**
561     * True if focus search is disabled.
562     */
563    private boolean mFocusSearchDisabled;
564
565    /**
566     * True if prune child,  might be disabled during transition.
567     */
568    private boolean mPruneChild = true;
569
570    /**
571     * True if scroll content,  might be disabled during transition.
572     */
573    private boolean mScrollEnabled = true;
574
575    /**
576     * Temporary variable: an int array of length=2.
577     */
578    private static int[] sTwoInts = new int[2];
579
580    /**
581     * Set to true for RTL layout in horizontal orientation
582     */
583    private boolean mReverseFlowPrimary = false;
584
585    /**
586     * Set to true for RTL layout in vertical orientation
587     */
588    private boolean mReverseFlowSecondary = false;
589
590    /**
591     * Temporaries used for measuring.
592     */
593    private int[] mMeasuredDimension = new int[2];
594
595    final ViewsStateBundle mChildrenStates = new ViewsStateBundle();
596
597    public GridLayoutManager(BaseGridView baseGridView) {
598        mBaseGridView = baseGridView;
599    }
600
601    public void setOrientation(int orientation) {
602        if (orientation != HORIZONTAL && orientation != VERTICAL) {
603            if (DEBUG) Log.v(getTag(), "invalid orientation: " + orientation);
604            return;
605        }
606
607        mOrientation = orientation;
608        mWindowAlignment.setOrientation(orientation);
609        mItemAlignment.setOrientation(orientation);
610        mForceFullLayout = true;
611    }
612
613    public void onRtlPropertiesChanged(int layoutDirection) {
614        if (mOrientation == HORIZONTAL) {
615            mReverseFlowPrimary = layoutDirection == View.LAYOUT_DIRECTION_RTL;
616            mReverseFlowSecondary = false;
617        } else {
618            mReverseFlowSecondary = layoutDirection == View.LAYOUT_DIRECTION_RTL;
619            mReverseFlowPrimary = false;
620        }
621        mWindowAlignment.horizontal.setReversedFlow(layoutDirection == View.LAYOUT_DIRECTION_RTL);
622    }
623
624    public int getFocusScrollStrategy() {
625        return mFocusScrollStrategy;
626    }
627
628    public void setFocusScrollStrategy(int focusScrollStrategy) {
629        mFocusScrollStrategy = focusScrollStrategy;
630    }
631
632    public void setWindowAlignment(int windowAlignment) {
633        mWindowAlignment.mainAxis().setWindowAlignment(windowAlignment);
634    }
635
636    public int getWindowAlignment() {
637        return mWindowAlignment.mainAxis().getWindowAlignment();
638    }
639
640    public void setWindowAlignmentOffset(int alignmentOffset) {
641        mWindowAlignment.mainAxis().setWindowAlignmentOffset(alignmentOffset);
642    }
643
644    public int getWindowAlignmentOffset() {
645        return mWindowAlignment.mainAxis().getWindowAlignmentOffset();
646    }
647
648    public void setWindowAlignmentOffsetPercent(float offsetPercent) {
649        mWindowAlignment.mainAxis().setWindowAlignmentOffsetPercent(offsetPercent);
650    }
651
652    public float getWindowAlignmentOffsetPercent() {
653        return mWindowAlignment.mainAxis().getWindowAlignmentOffsetPercent();
654    }
655
656    public void setItemAlignmentOffset(int alignmentOffset) {
657        mItemAlignment.mainAxis().setItemAlignmentOffset(alignmentOffset);
658        updateChildAlignments();
659    }
660
661    public int getItemAlignmentOffset() {
662        return mItemAlignment.mainAxis().getItemAlignmentOffset();
663    }
664
665    public void setItemAlignmentOffsetWithPadding(boolean withPadding) {
666        mItemAlignment.mainAxis().setItemAlignmentOffsetWithPadding(withPadding);
667        updateChildAlignments();
668    }
669
670    public boolean isItemAlignmentOffsetWithPadding() {
671        return mItemAlignment.mainAxis().isItemAlignmentOffsetWithPadding();
672    }
673
674    public void setItemAlignmentOffsetPercent(float offsetPercent) {
675        mItemAlignment.mainAxis().setItemAlignmentOffsetPercent(offsetPercent);
676        updateChildAlignments();
677    }
678
679    public float getItemAlignmentOffsetPercent() {
680        return mItemAlignment.mainAxis().getItemAlignmentOffsetPercent();
681    }
682
683    public void setItemAlignmentViewId(int viewId) {
684        mItemAlignment.mainAxis().setItemAlignmentViewId(viewId);
685        updateChildAlignments();
686    }
687
688    public int getItemAlignmentViewId() {
689        return mItemAlignment.mainAxis().getItemAlignmentViewId();
690    }
691
692    public void setFocusOutAllowed(boolean throughFront, boolean throughEnd) {
693        mFocusOutFront = throughFront;
694        mFocusOutEnd = throughEnd;
695    }
696
697    public void setNumRows(int numRows) {
698        if (numRows < 0) throw new IllegalArgumentException();
699        mNumRowsRequested = numRows;
700        mForceFullLayout = true;
701    }
702
703    /**
704     * Set the row height. May be WRAP_CONTENT, or a size in pixels.
705     */
706    public void setRowHeight(int height) {
707        if (height >= 0 || height == ViewGroup.LayoutParams.WRAP_CONTENT) {
708            mRowSizeSecondaryRequested = height;
709        } else {
710            throw new IllegalArgumentException("Invalid row height: " + height);
711        }
712    }
713
714    public void setItemMargin(int margin) {
715        mVerticalMargin = mHorizontalMargin = margin;
716        mMarginPrimary = mMarginSecondary = margin;
717    }
718
719    public void setVerticalMargin(int margin) {
720        if (mOrientation == HORIZONTAL) {
721            mMarginSecondary = mVerticalMargin = margin;
722        } else {
723            mMarginPrimary = mVerticalMargin = margin;
724        }
725    }
726
727    public void setHorizontalMargin(int margin) {
728        if (mOrientation == HORIZONTAL) {
729            mMarginPrimary = mHorizontalMargin = margin;
730        } else {
731            mMarginSecondary = mHorizontalMargin = margin;
732        }
733    }
734
735    public int getVerticalMargin() {
736        return mVerticalMargin;
737    }
738
739    public int getHorizontalMargin() {
740        return mHorizontalMargin;
741    }
742
743    public void setGravity(int gravity) {
744        mGravity = gravity;
745    }
746
747    protected boolean hasDoneFirstLayout() {
748        return mGrid != null;
749    }
750
751    public void setOnChildSelectedListener(OnChildSelectedListener listener) {
752        mChildSelectedListener = listener;
753    }
754
755    void setOnChildLaidOutListener(OnChildLaidOutListener listener) {
756        mChildLaidOutListener = listener;
757    }
758
759    private int getPositionByView(View view) {
760        if (view == null) {
761            return NO_POSITION;
762        }
763        LayoutParams params = (LayoutParams) view.getLayoutParams();
764        if (params == null || params.isItemRemoved()) {
765            // when item is removed, the position value can be any value.
766            return NO_POSITION;
767        }
768        return params.getViewPosition();
769    }
770
771    private int getPositionByIndex(int index) {
772        return getPositionByView(getChildAt(index));
773    }
774
775    private void dispatchChildSelected() {
776        if (mChildSelectedListener == null) {
777            return;
778        }
779
780        if (TRACE) TraceHelper.beginSection("onChildSelected");
781        View view = mFocusPosition == NO_POSITION ? null : findViewByPosition(mFocusPosition);
782        if (view != null) {
783            RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(view);
784            mChildSelectedListener.onChildSelected(mBaseGridView, view, mFocusPosition,
785                    vh == null? NO_ID: vh.getItemId());
786        } else {
787            mChildSelectedListener.onChildSelected(mBaseGridView, null, NO_POSITION, NO_ID);
788        }
789        if (TRACE) TraceHelper.endSection();
790
791        // Children may request layout when a child selection event occurs (such as a change of
792        // padding on the current and previously selected rows).
793        // If in layout, a child requesting layout may have been laid out before the selection
794        // callback.
795        // If it was not, the child will be laid out after the selection callback.
796        // If so, the layout request will be honoured though the view system will emit a double-
797        // layout warning.
798        // If not in layout, we may be scrolling in which case the child layout request will be
799        // eaten by recyclerview.  Post a requestLayout.
800        if (!mInLayout && !mBaseGridView.isLayoutRequested()) {
801            int childCount = getChildCount();
802            for (int i = 0; i < childCount; i++) {
803                if (getChildAt(i).isLayoutRequested()) {
804                    forceRequestLayout();
805                    break;
806                }
807            }
808        }
809    }
810
811    @Override
812    public boolean canScrollHorizontally() {
813        // We can scroll horizontally if we have horizontal orientation, or if
814        // we are vertical and have more than one column.
815        return mOrientation == HORIZONTAL || mNumRows > 1;
816    }
817
818    @Override
819    public boolean canScrollVertically() {
820        // We can scroll vertically if we have vertical orientation, or if we
821        // are horizontal and have more than one row.
822        return mOrientation == VERTICAL || mNumRows > 1;
823    }
824
825    @Override
826    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
827        return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
828                ViewGroup.LayoutParams.WRAP_CONTENT);
829    }
830
831    @Override
832    public RecyclerView.LayoutParams generateLayoutParams(Context context, AttributeSet attrs) {
833        return new LayoutParams(context, attrs);
834    }
835
836    @Override
837    public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
838        if (lp instanceof LayoutParams) {
839            return new LayoutParams((LayoutParams) lp);
840        } else if (lp instanceof RecyclerView.LayoutParams) {
841            return new LayoutParams((RecyclerView.LayoutParams) lp);
842        } else if (lp instanceof MarginLayoutParams) {
843            return new LayoutParams((MarginLayoutParams) lp);
844        } else {
845            return new LayoutParams(lp);
846        }
847    }
848
849    protected View getViewForPosition(int position) {
850        return mRecycler.getViewForPosition(position);
851    }
852
853    final int getOpticalLeft(View v) {
854        return ((LayoutParams) v.getLayoutParams()).getOpticalLeft(v);
855    }
856
857    final int getOpticalRight(View v) {
858        return ((LayoutParams) v.getLayoutParams()).getOpticalRight(v);
859    }
860
861    final int getOpticalTop(View v) {
862        return ((LayoutParams) v.getLayoutParams()).getOpticalTop(v);
863    }
864
865    final int getOpticalBottom(View v) {
866        return ((LayoutParams) v.getLayoutParams()).getOpticalBottom(v);
867    }
868
869    private int getViewMin(View v) {
870        return (mOrientation == HORIZONTAL) ? getOpticalLeft(v) : getOpticalTop(v);
871    }
872
873    private int getViewMax(View v) {
874        return (mOrientation == HORIZONTAL) ? getOpticalRight(v) : getOpticalBottom(v);
875    }
876
877    private int getViewCenter(View view) {
878        return (mOrientation == HORIZONTAL) ? getViewCenterX(view) : getViewCenterY(view);
879    }
880
881    private int getViewCenterSecondary(View view) {
882        return (mOrientation == HORIZONTAL) ? getViewCenterY(view) : getViewCenterX(view);
883    }
884
885    private int getViewCenterX(View v) {
886        LayoutParams p = (LayoutParams) v.getLayoutParams();
887        return p.getOpticalLeft(v) + p.getAlignX();
888    }
889
890    private int getViewCenterY(View v) {
891        LayoutParams p = (LayoutParams) v.getLayoutParams();
892        return p.getOpticalTop(v) + p.getAlignY();
893    }
894
895    /**
896     * Save Recycler and State for convenience.  Must be paired with leaveContext().
897     */
898    private void saveContext(Recycler recycler, State state) {
899        if (mRecycler != null || mState != null) {
900            Log.e(TAG, "Recycler information was not released, bug!");
901        }
902        mRecycler = recycler;
903        mState = state;
904    }
905
906    /**
907     * Discard saved Recycler and State.
908     */
909    private void leaveContext() {
910        mRecycler = null;
911        mState = null;
912    }
913
914    /**
915     * Re-initialize data structures for a data change or handling invisible
916     * selection. The method tries its best to preserve position information so
917     * that staggered grid looks same before and after re-initialize.
918     * @return true if can fastRelayout()
919     */
920    private boolean layoutInit() {
921        if (!mState.didStructureChange() && !mForceFullLayout && mGrid != null) {
922            updateScrollController();
923            updateScrollSecondAxis();
924            mGrid.setMargin(mMarginPrimary);
925            return true;
926        } else {
927            mForceFullLayout = false;
928            boolean focusViewWasInTree = mGrid != null && mFocusPosition >= 0
929                    && mFocusPosition >= mGrid.getFirstVisibleIndex()
930                    && mFocusPosition <= mGrid.getLastVisibleIndex();
931            int firstVisibleIndex = focusViewWasInTree ? mGrid.getFirstVisibleIndex() : 0;
932            final int newItemCount = mState.getItemCount();
933            if (newItemCount == 0) {
934                mFocusPosition = NO_POSITION;
935            } else if (mFocusPosition >= newItemCount) {
936                mFocusPosition = newItemCount - 1;
937            } else if (mFocusPosition == NO_POSITION && newItemCount > 0) {
938                // if focus position is never set before,  initialize it to 0
939                mFocusPosition = 0;
940            }
941
942            if (mGrid == null || mNumRows != mGrid.getNumRows() ||
943                    mReverseFlowPrimary != mGrid.isReversedFlow()) {
944                mGrid = Grid.createStaggeredMultipleRows(mNumRows);
945                mGrid.setProvider(mGridProvider);
946                mGrid.setReversedFlow(mReverseFlowPrimary);
947            }
948            initScrollController();
949            updateScrollSecondAxis();
950            mGrid.setMargin(mMarginPrimary);
951            detachAndScrapAttachedViews(mRecycler);
952            mGrid.resetVisibleIndex();
953            if (mFocusPosition == NO_POSITION) {
954                mBaseGridView.clearFocus();
955            }
956            mWindowAlignment.mainAxis().invalidateScrollMin();
957            mWindowAlignment.mainAxis().invalidateScrollMax();
958            if (focusViewWasInTree && firstVisibleIndex <= mFocusPosition) {
959                // if focusView was in tree, we will add item from first visible item
960                mGrid.setStart(firstVisibleIndex);
961            } else {
962                // if focusView was not in tree, it's probably because focus position jumped
963                // far away from visible range,  so use mFocusPosition as start
964                mGrid.setStart(mFocusPosition);
965            }
966            return false;
967        }
968    }
969
970    private int getRowSizeSecondary(int rowIndex) {
971        if (mFixedRowSizeSecondary != 0) {
972            return mFixedRowSizeSecondary;
973        }
974        if (mRowSizeSecondary == null) {
975            return 0;
976        }
977        return mRowSizeSecondary[rowIndex];
978    }
979
980    private int getRowStartSecondary(int rowIndex) {
981        int start = 0;
982        // Iterate from left to right, which is a different index traversal
983        // in RTL flow
984        if (mReverseFlowSecondary) {
985            for (int i = mNumRows-1; i > rowIndex; i--) {
986                start += getRowSizeSecondary(i) + mMarginSecondary;
987            }
988        } else {
989            for (int i = 0; i < rowIndex; i++) {
990                start += getRowSizeSecondary(i) + mMarginSecondary;
991            }
992        }
993        return start;
994    }
995
996    private int getSizeSecondary() {
997        int rightmostIndex = mReverseFlowSecondary ? 0 : mNumRows - 1;
998        return getRowStartSecondary(rightmostIndex) + getRowSizeSecondary(rightmostIndex);
999    }
1000
1001    private void measureScrapChild(int position, int widthSpec, int heightSpec,
1002            int[] measuredDimension) {
1003        View view = mRecycler.getViewForPosition(position);
1004        if (view != null) {
1005            LayoutParams p = (LayoutParams) view.getLayoutParams();
1006            int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
1007                    getPaddingLeft() + getPaddingRight(), p.width);
1008            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
1009                    getPaddingTop() + getPaddingBottom(), p.height);
1010            view.measure(childWidthSpec, childHeightSpec);
1011            measuredDimension[0] = view.getMeasuredWidth();
1012            measuredDimension[1] = view.getMeasuredHeight();
1013            mRecycler.recycleView(view);
1014        }
1015    }
1016
1017    private boolean processRowSizeSecondary(boolean measure) {
1018        if (mFixedRowSizeSecondary != 0) {
1019            return false;
1020        }
1021
1022        if (TRACE) TraceHelper.beginSection("processRowSizeSecondary");
1023        CircularIntArray[] rows = mGrid == null ? null : mGrid.getItemPositionsInRows();
1024        boolean changed = false;
1025        int scrapChildWidth = -1;
1026        int scrapChildHeight = -1;
1027
1028        for (int rowIndex = 0; rowIndex < mNumRows; rowIndex++) {
1029            CircularIntArray row = rows == null ? null : rows[rowIndex];
1030            final int rowItemsPairCount = row == null ? 0 : row.size();
1031            int rowSize = -1;
1032            for (int rowItemPairIndex = 0; rowItemPairIndex < rowItemsPairCount;
1033                    rowItemPairIndex += 2) {
1034                final int rowIndexStart = row.get(rowItemPairIndex);
1035                final int rowIndexEnd = row.get(rowItemPairIndex + 1);
1036                for (int i = rowIndexStart; i <= rowIndexEnd; i++) {
1037                    final View view = findViewByPosition(i);
1038                    if (view == null) {
1039                        continue;
1040                    }
1041                    if (measure && view.isLayoutRequested()) {
1042                        measureChild(view);
1043                    }
1044                    final int secondarySize = mOrientation == HORIZONTAL ?
1045                            view.getMeasuredHeight() : view.getMeasuredWidth();
1046                    if (secondarySize > rowSize) {
1047                        rowSize = secondarySize;
1048                    }
1049                }
1050            }
1051
1052            final int itemCount = mState.getItemCount();
1053            if (measure && rowSize < 0 && itemCount > 0) {
1054                if (scrapChildWidth < 0 && scrapChildHeight < 0) {
1055                    int position;
1056                    if (mFocusPosition == NO_POSITION) {
1057                        position = 0;
1058                    } else if (mFocusPosition >= itemCount) {
1059                        position = itemCount - 1;
1060                    } else {
1061                        position = mFocusPosition;
1062                    }
1063                    measureScrapChild(position,
1064                            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
1065                            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
1066                            mMeasuredDimension);
1067                    scrapChildWidth = mMeasuredDimension[0];
1068                    scrapChildHeight = mMeasuredDimension[1];
1069                    if (DEBUG) Log.v(TAG, "measured scrap child: " + scrapChildWidth +
1070                            " " + scrapChildHeight);
1071                }
1072                rowSize = mOrientation == HORIZONTAL ? scrapChildHeight : scrapChildWidth;
1073            }
1074            if (rowSize < 0) {
1075                rowSize = 0;
1076            }
1077            if (mRowSizeSecondary[rowIndex] != rowSize) {
1078                if (DEBUG) Log.v(getTag(), "row size secondary changed: " + mRowSizeSecondary[rowIndex] +
1079                        ", " + rowSize);
1080                mRowSizeSecondary[rowIndex] = rowSize;
1081                changed = true;
1082            }
1083        }
1084
1085        if (TRACE) TraceHelper.endSection();
1086        return changed;
1087    }
1088
1089    /**
1090     * Checks if we need to update row secondary sizes.
1091     */
1092    private void updateRowSecondarySizeRefresh() {
1093        mRowSecondarySizeRefresh = processRowSizeSecondary(false);
1094        if (mRowSecondarySizeRefresh) {
1095            if (DEBUG) Log.v(getTag(), "mRowSecondarySizeRefresh now set");
1096            forceRequestLayout();
1097        }
1098    }
1099
1100    private void forceRequestLayout() {
1101        if (DEBUG) Log.v(getTag(), "forceRequestLayout");
1102        // RecyclerView prevents us from requesting layout in many cases
1103        // (during layout, during scroll, etc.)
1104        // For secondary row size wrap_content support we currently need a
1105        // second layout pass to update the measured size after having measured
1106        // and added child views in layoutChildren.
1107        // Force the second layout by posting a delayed runnable.
1108        // TODO: investigate allowing a second layout pass,
1109        // or move child add/measure logic to the measure phase.
1110        ViewCompat.postOnAnimation(mBaseGridView, mRequestLayoutRunnable);
1111    }
1112
1113    private final Runnable mRequestLayoutRunnable = new Runnable() {
1114        @Override
1115        public void run() {
1116            if (DEBUG) Log.v(getTag(), "request Layout from runnable");
1117            requestLayout();
1118        }
1119     };
1120
1121    @Override
1122    public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
1123        saveContext(recycler, state);
1124
1125        int sizePrimary, sizeSecondary, modeSecondary, paddingSecondary;
1126        int measuredSizeSecondary;
1127        if (mOrientation == HORIZONTAL) {
1128            sizePrimary = MeasureSpec.getSize(widthSpec);
1129            sizeSecondary = MeasureSpec.getSize(heightSpec);
1130            modeSecondary = MeasureSpec.getMode(heightSpec);
1131            paddingSecondary = getPaddingTop() + getPaddingBottom();
1132        } else {
1133            sizeSecondary = MeasureSpec.getSize(widthSpec);
1134            sizePrimary = MeasureSpec.getSize(heightSpec);
1135            modeSecondary = MeasureSpec.getMode(widthSpec);
1136            paddingSecondary = getPaddingLeft() + getPaddingRight();
1137        }
1138        if (DEBUG) Log.v(getTag(), "onMeasure widthSpec " + Integer.toHexString(widthSpec) +
1139                " heightSpec " + Integer.toHexString(heightSpec) +
1140                " modeSecondary " + Integer.toHexString(modeSecondary) +
1141                " sizeSecondary " + sizeSecondary + " " + this);
1142
1143        mMaxSizeSecondary = sizeSecondary;
1144
1145        if (mRowSizeSecondaryRequested == ViewGroup.LayoutParams.WRAP_CONTENT) {
1146            mNumRows = mNumRowsRequested == 0 ? 1 : mNumRowsRequested;
1147            mFixedRowSizeSecondary = 0;
1148
1149            if (mRowSizeSecondary == null || mRowSizeSecondary.length != mNumRows) {
1150                mRowSizeSecondary = new int[mNumRows];
1151            }
1152
1153            // Measure all current children and update cached row heights
1154            processRowSizeSecondary(true);
1155
1156            switch (modeSecondary) {
1157            case MeasureSpec.UNSPECIFIED:
1158                measuredSizeSecondary = getSizeSecondary() + paddingSecondary;
1159                break;
1160            case MeasureSpec.AT_MOST:
1161                measuredSizeSecondary = Math.min(getSizeSecondary() + paddingSecondary,
1162                        mMaxSizeSecondary);
1163                break;
1164            case MeasureSpec.EXACTLY:
1165                measuredSizeSecondary = mMaxSizeSecondary;
1166                break;
1167            default:
1168                throw new IllegalStateException("wrong spec");
1169            }
1170
1171        } else {
1172            switch (modeSecondary) {
1173            case MeasureSpec.UNSPECIFIED:
1174                if (mRowSizeSecondaryRequested == 0) {
1175                    if (mOrientation == HORIZONTAL) {
1176                        throw new IllegalStateException("Must specify rowHeight or view height");
1177                    } else {
1178                        throw new IllegalStateException("Must specify columnWidth or view width");
1179                    }
1180                }
1181                mFixedRowSizeSecondary = mRowSizeSecondaryRequested;
1182                mNumRows = mNumRowsRequested == 0 ? 1 : mNumRowsRequested;
1183                measuredSizeSecondary = mFixedRowSizeSecondary * mNumRows + mMarginSecondary
1184                    * (mNumRows - 1) + paddingSecondary;
1185                break;
1186            case MeasureSpec.AT_MOST:
1187            case MeasureSpec.EXACTLY:
1188                if (mNumRowsRequested == 0 && mRowSizeSecondaryRequested == 0) {
1189                    mNumRows = 1;
1190                    mFixedRowSizeSecondary = sizeSecondary - paddingSecondary;
1191                } else if (mNumRowsRequested == 0) {
1192                    mFixedRowSizeSecondary = mRowSizeSecondaryRequested;
1193                    mNumRows = (sizeSecondary + mMarginSecondary)
1194                        / (mRowSizeSecondaryRequested + mMarginSecondary);
1195                } else if (mRowSizeSecondaryRequested == 0) {
1196                    mNumRows = mNumRowsRequested;
1197                    mFixedRowSizeSecondary = (sizeSecondary - paddingSecondary - mMarginSecondary
1198                            * (mNumRows - 1)) / mNumRows;
1199                } else {
1200                    mNumRows = mNumRowsRequested;
1201                    mFixedRowSizeSecondary = mRowSizeSecondaryRequested;
1202                }
1203                measuredSizeSecondary = sizeSecondary;
1204                if (modeSecondary == MeasureSpec.AT_MOST) {
1205                    int childrenSize = mFixedRowSizeSecondary * mNumRows + mMarginSecondary
1206                        * (mNumRows - 1) + paddingSecondary;
1207                    if (childrenSize < measuredSizeSecondary) {
1208                        measuredSizeSecondary = childrenSize;
1209                    }
1210                }
1211                break;
1212            default:
1213                throw new IllegalStateException("wrong spec");
1214            }
1215        }
1216        if (mOrientation == HORIZONTAL) {
1217            setMeasuredDimension(sizePrimary, measuredSizeSecondary);
1218        } else {
1219            setMeasuredDimension(measuredSizeSecondary, sizePrimary);
1220        }
1221        if (DEBUG) {
1222            Log.v(getTag(), "onMeasure sizePrimary " + sizePrimary +
1223                    " measuredSizeSecondary " + measuredSizeSecondary +
1224                    " mFixedRowSizeSecondary " + mFixedRowSizeSecondary +
1225                    " mNumRows " + mNumRows);
1226        }
1227        leaveContext();
1228    }
1229
1230    private void measureChild(View child) {
1231        if (TRACE) TraceHelper.beginSection("measureChild");
1232        final ViewGroup.LayoutParams lp = child.getLayoutParams();
1233        final int secondarySpec = (mRowSizeSecondaryRequested == ViewGroup.LayoutParams.WRAP_CONTENT) ?
1234                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) :
1235                MeasureSpec.makeMeasureSpec(mFixedRowSizeSecondary, MeasureSpec.EXACTLY);
1236        int widthSpec, heightSpec;
1237
1238        if (mOrientation == HORIZONTAL) {
1239            widthSpec = ViewGroup.getChildMeasureSpec(
1240                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
1241                    0, lp.width);
1242            heightSpec = ViewGroup.getChildMeasureSpec(secondarySpec, 0, lp.height);
1243        } else {
1244            heightSpec = ViewGroup.getChildMeasureSpec(
1245                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
1246                    0, lp.height);
1247            widthSpec = ViewGroup.getChildMeasureSpec(secondarySpec, 0, lp.width);
1248        }
1249        child.measure(widthSpec, heightSpec);
1250        if (DEBUG) Log.v(getTag(), "measureChild secondarySpec " + Integer.toHexString(secondarySpec) +
1251                " widthSpec " + Integer.toHexString(widthSpec) +
1252                " heightSpec " + Integer.toHexString(heightSpec) +
1253                " measuredWidth " + child.getMeasuredWidth() +
1254                " measuredHeight " + child.getMeasuredHeight());
1255        if (DEBUG) Log.v(getTag(), "child lp width " + lp.width + " height " + lp.height);
1256        if (TRACE) TraceHelper.endSection();
1257    }
1258
1259    private Grid.Provider mGridProvider = new Grid.Provider() {
1260
1261        @Override
1262        public int getCount() {
1263            return mState.getItemCount();
1264        }
1265
1266        @Override
1267        public int createItem(int index, boolean append, Object[] item) {
1268            if (TRACE) TraceHelper.beginSection("createItem");
1269            if (TRACE) TraceHelper.beginSection("getview");
1270            View v = getViewForPosition(index);
1271            if (TRACE) TraceHelper.endSection();
1272            // See recyclerView docs:  we don't need re-add scraped view if it was removed.
1273            if (!((RecyclerView.LayoutParams) v.getLayoutParams()).isItemRemoved()) {
1274                if (TRACE) TraceHelper.beginSection("addView");
1275                if (append) {
1276                    addView(v);
1277                } else {
1278                    addView(v, 0);
1279                }
1280                if (TRACE) TraceHelper.endSection();
1281                if (mChildVisibility != -1) {
1282                    v.setVisibility(mChildVisibility);
1283                }
1284
1285                if (mPendingMoveSmoothScroller != null) {
1286                    mPendingMoveSmoothScroller.consumePendingMovesBeforeLayout();
1287                }
1288                if (!mInLayout) {
1289                    // when we are appending item during scroll pass and the item's position
1290                    // matches the mFocusPosition,  we should signal a childSelected event.
1291                    // However if we are still running PendingMoveSmoothScroller,  we defer and
1292                    // signal the event in PendingMoveSmoothScroller.onStop().  This can
1293                    // avoid lots of childSelected events during a long smooth scrolling and
1294                    // increase performance.
1295                    if (index == mFocusPosition && (mPendingMoveSmoothScroller == null
1296                            || mPendingMoveSmoothScroller.mPendingMoves == 0)) {
1297                        dispatchChildSelected();
1298                    }
1299                } else {
1300                    // during layout pass, we notify change for non fastRelayout case.
1301                    // fastRelayout will dispatch event at end of onLayoutChildren()
1302                    if (!mInFastRelayout && index == mFocusPosition) {
1303                        dispatchChildSelected();
1304                    }
1305                }
1306                measureChild(v);
1307            }
1308            item[0] = v;
1309            return mOrientation == HORIZONTAL ? v.getMeasuredWidth() : v.getMeasuredHeight();
1310        }
1311
1312        @Override
1313        public void addItem(Object item, int index, int length, int rowIndex, int edge) {
1314            View v = (View) item;
1315            int start, end;
1316            if (edge == Integer.MIN_VALUE || edge == Integer.MAX_VALUE) {
1317                edge = !mGrid.isReversedFlow() ? mWindowAlignment.mainAxis().getPaddingLow()
1318                        : mWindowAlignment.mainAxis().getSize()
1319                                - mWindowAlignment.mainAxis().getPaddingHigh();
1320            }
1321            boolean edgeIsMin = !mGrid.isReversedFlow();
1322            if (edgeIsMin) {
1323                start = edge;
1324                end = edge + length;
1325            } else {
1326                start = edge - length;
1327                end = edge;
1328            }
1329            int startSecondary = getRowStartSecondary(rowIndex) - mScrollOffsetSecondary;
1330            mChildrenStates.loadView(v, index);
1331            layoutChild(rowIndex, v, start, end, startSecondary);
1332            if (DEBUG) {
1333                Log.d(getTag(), "addView " + index + " " + v);
1334            }
1335            if (TRACE) TraceHelper.endSection();
1336
1337            if (index == mGrid.getFirstVisibleIndex()) {
1338                if (!mGrid.isReversedFlow()) {
1339                    updateScrollMin();
1340                } else {
1341                    updateScrollMax();
1342                }
1343            }
1344            if (index == mGrid.getLastVisibleIndex()) {
1345                if (!mGrid.isReversedFlow()) {
1346                    updateScrollMax();
1347                } else {
1348                    updateScrollMin();
1349                }
1350            }
1351            if (!mInLayout && mPendingMoveSmoothScroller != null) {
1352                mPendingMoveSmoothScroller.consumePendingMovesAfterLayout();
1353            }
1354            if (mChildLaidOutListener != null) {
1355                RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(v);
1356                mChildLaidOutListener.onChildLaidOut(mBaseGridView, v, index,
1357                        vh == null ? NO_ID : vh.getItemId());
1358            }
1359        }
1360
1361        @Override
1362        public void removeItem(int index) {
1363            if (TRACE) TraceHelper.beginSection("removeItem");
1364            View v = findViewByPosition(index);
1365            if (mInLayout) {
1366                detachAndScrapView(v, mRecycler);
1367            } else {
1368                removeAndRecycleView(v, mRecycler);
1369            }
1370            if (TRACE) TraceHelper.endSection();
1371        }
1372
1373        @Override
1374        public int getEdge(int index) {
1375            if (mReverseFlowPrimary) {
1376                return getViewMax(findViewByPosition(index));
1377            } else {
1378                return getViewMin(findViewByPosition(index));
1379            }
1380        }
1381
1382        @Override
1383        public int getSize(int index) {
1384            final View v = findViewByPosition(index);
1385            return mOrientation == HORIZONTAL ? v.getMeasuredWidth() : v.getMeasuredHeight();
1386        }
1387    };
1388
1389    private void layoutChild(int rowIndex, View v, int start, int end, int startSecondary) {
1390        if (TRACE) TraceHelper.beginSection("layoutChild");
1391        int sizeSecondary = mOrientation == HORIZONTAL ? v.getMeasuredHeight()
1392                : v.getMeasuredWidth();
1393        if (mFixedRowSizeSecondary > 0) {
1394            sizeSecondary = Math.min(sizeSecondary, mFixedRowSizeSecondary);
1395        }
1396        final int verticalGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1397        final int horizontalGravity = (mReverseFlowPrimary || mReverseFlowSecondary) ?
1398                Gravity.getAbsoluteGravity(mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK, View.LAYOUT_DIRECTION_RTL) :
1399                mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
1400        if (mOrientation == HORIZONTAL && verticalGravity == Gravity.TOP
1401                || mOrientation == VERTICAL && horizontalGravity == Gravity.LEFT) {
1402            // do nothing
1403        } else if (mOrientation == HORIZONTAL && verticalGravity == Gravity.BOTTOM
1404                || mOrientation == VERTICAL && horizontalGravity == Gravity.RIGHT) {
1405            startSecondary += getRowSizeSecondary(rowIndex) - sizeSecondary;
1406        } else if (mOrientation == HORIZONTAL && verticalGravity == Gravity.CENTER_VERTICAL
1407                || mOrientation == VERTICAL && horizontalGravity == Gravity.CENTER_HORIZONTAL) {
1408            startSecondary += (getRowSizeSecondary(rowIndex) - sizeSecondary) / 2;
1409        }
1410        int left, top, right, bottom;
1411        if (mOrientation == HORIZONTAL) {
1412            left = start;
1413            top = startSecondary;
1414            right = end;
1415            bottom = startSecondary + sizeSecondary;
1416        } else {
1417            top = start;
1418            left = startSecondary;
1419            bottom = end;
1420            right = startSecondary + sizeSecondary;
1421        }
1422        v.layout(left, top, right, bottom);
1423        updateChildOpticalInsets(v, left, top, right, bottom);
1424        updateChildAlignments(v);
1425        if (TRACE) TraceHelper.endSection();
1426    }
1427
1428    private void updateChildOpticalInsets(View v, int left, int top, int right, int bottom) {
1429        LayoutParams p = (LayoutParams) v.getLayoutParams();
1430        p.setOpticalInsets(left - v.getLeft(), top - v.getTop(),
1431                v.getRight() - right, v.getBottom() - bottom);
1432    }
1433
1434    private void updateChildAlignments(View v) {
1435        LayoutParams p = (LayoutParams) v.getLayoutParams();
1436        p.setAlignX(mItemAlignment.horizontal.getAlignmentPosition(v));
1437        p.setAlignY(mItemAlignment.vertical.getAlignmentPosition(v));
1438    }
1439
1440    private void updateChildAlignments() {
1441        for (int i = 0, c = getChildCount(); i < c; i++) {
1442            updateChildAlignments(getChildAt(i));
1443        }
1444    }
1445
1446    private void removeInvisibleViewsAtEnd() {
1447        if (mPruneChild) {
1448            mGrid.removeInvisibleItemsAtEnd(mFocusPosition,
1449                    mReverseFlowPrimary ? 0 : mSizePrimary);
1450        }
1451    }
1452
1453    private void removeInvisibleViewsAtFront() {
1454        if (mPruneChild) {
1455            mGrid.removeInvisibleItemsAtFront(mFocusPosition,
1456                    mReverseFlowPrimary ? mSizePrimary : 0);
1457        }
1458    }
1459
1460    private boolean appendOneColumnVisibleItems() {
1461        return mGrid.appendOneColumnVisibleItems();
1462    }
1463
1464    private boolean prependOneColumnVisibleItems() {
1465        return mGrid.prependOneColumnVisibleItems();
1466    }
1467
1468    private void appendVisibleItems() {
1469        mGrid.appendVisibleItems(mReverseFlowPrimary ? 0 : mSizePrimary);
1470    }
1471
1472    private void prependVisibleItems() {
1473        mGrid.prependVisibleItems(mReverseFlowPrimary ? mSizePrimary : 0);
1474    }
1475
1476    /**
1477     * Fast layout when there is no structure change, adapter change, etc.
1478     * It will layout all views was layout requested or updated, until hit a view
1479     * with different size,  then it break and detachAndScrap all views after that.
1480     */
1481    private void fastRelayout() {
1482        boolean invalidateAfter = false;
1483        final int childCount = getChildCount();
1484        int position = -1;
1485        for (int index = 0; index < childCount; index++) {
1486            View view = getChildAt(index);
1487            position = getPositionByIndex(index);
1488            Grid.Location location = mGrid.getLocation(position);
1489            if (location == null) {
1490                if (DEBUG) Log.w(getTag(), "fastRelayout(): no Location at " + position);
1491                invalidateAfter = true;
1492                break;
1493            }
1494
1495            int startSecondary = getRowStartSecondary(location.row) - mScrollOffsetSecondary;
1496            int primarySize, end;
1497            int start = getViewMin(view);
1498            int oldPrimarySize = (mOrientation == HORIZONTAL) ?
1499                    view.getMeasuredWidth() :
1500                    view.getMeasuredHeight();
1501
1502            LayoutParams lp = (LayoutParams) view.getLayoutParams();
1503            if (lp.viewNeedsUpdate()) {
1504                int viewIndex = mBaseGridView.indexOfChild(view);
1505                detachAndScrapView(view, mRecycler);
1506                view = getViewForPosition(position);
1507                addView(view, viewIndex);
1508            }
1509
1510            if (view.isLayoutRequested()) {
1511                measureChild(view);
1512            }
1513            if (mOrientation == HORIZONTAL) {
1514                primarySize = view.getMeasuredWidth();
1515                end = start + primarySize;
1516            } else {
1517                primarySize = view.getMeasuredHeight();
1518                end = start + primarySize;
1519            }
1520            layoutChild(location.row, view, start, end, startSecondary);
1521            if (oldPrimarySize != primarySize) {
1522                // size changed invalidate remaining Locations
1523                if (DEBUG) Log.d(getTag(), "fastRelayout: view size changed at " + position);
1524                invalidateAfter = true;
1525                break;
1526            }
1527        }
1528        if (invalidateAfter) {
1529            final int savedLastPos = mGrid.getLastVisibleIndex();
1530            mGrid.invalidateItemsAfter(position);
1531            if (mPruneChild) {
1532                // in regular prune child mode, we just append items up to edge limit
1533                appendVisibleItems();
1534            } else {
1535                // prune disabled(e.g. in RowsFragment transition): append all removed items
1536                while (mGrid.appendOneColumnVisibleItems()
1537                        && mGrid.getLastVisibleIndex() < savedLastPos);
1538            }
1539        }
1540        updateScrollMin();
1541        updateScrollMax();
1542        updateScrollSecondAxis();
1543    }
1544
1545    public void removeAndRecycleAllViews(RecyclerView.Recycler recycler) {
1546        if (TRACE) TraceHelper.beginSection("removeAndRecycleAllViews");
1547        if (DEBUG) Log.v(TAG, "removeAndRecycleAllViews " + getChildCount());
1548        for (int i = getChildCount() - 1; i >= 0; i--) {
1549            removeAndRecycleViewAt(i, recycler);
1550        }
1551        if (TRACE) TraceHelper.endSection();
1552    }
1553
1554    // Lays out items based on the current scroll position
1555    @Override
1556    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
1557        if (DEBUG) {
1558            Log.v(getTag(), "layoutChildren start numRows " + mNumRows + " mScrollOffsetSecondary "
1559                    + mScrollOffsetSecondary + " mScrollOffsetPrimary " + mScrollOffsetPrimary
1560                    + " inPreLayout " + state.isPreLayout()
1561                    + " didStructureChange " + state.didStructureChange()
1562                    + " mForceFullLayout " + mForceFullLayout);
1563            Log.v(getTag(), "width " + getWidth() + " height " + getHeight());
1564        }
1565
1566        if (mNumRows == 0) {
1567            // haven't done measure yet
1568            return;
1569        }
1570        final int itemCount = state.getItemCount();
1571        if (itemCount < 0) {
1572            return;
1573        }
1574
1575        if (!mLayoutEnabled) {
1576            discardLayoutInfo();
1577            removeAndRecycleAllViews(recycler);
1578            return;
1579        }
1580        mInLayout = true;
1581
1582        final boolean scrollToFocus = !isSmoothScrolling()
1583                && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED;
1584        if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE) {
1585            mFocusPosition = mFocusPosition + mFocusPositionOffset;
1586            mFocusPositionOffset = 0;
1587        }
1588        saveContext(recycler, state);
1589
1590        // Track the old focus view so we can adjust our system scroll position
1591        // so that any scroll animations happening now will remain valid.
1592        // We must use same delta in Pre Layout (if prelayout exists) and second layout.
1593        // So we cache the deltas in PreLayout and use it in second layout.
1594        int delta = 0, deltaSecondary = 0;
1595        if (mFocusPosition != NO_POSITION && scrollToFocus
1596                && mBaseGridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
1597            // FIXME: we should get the remaining scroll animation offset from RecyclerView
1598            View focusView = findViewByPosition(mFocusPosition);
1599            if (focusView != null) {
1600                delta = mWindowAlignment.mainAxis().getSystemScrollPos(mScrollOffsetPrimary
1601                        + getViewCenter(focusView), false, false) - mScrollOffsetPrimary;
1602                deltaSecondary = mWindowAlignment.secondAxis().getSystemScrollPos(
1603                        mScrollOffsetSecondary + getViewCenterSecondary(focusView),
1604                        false, false) - mScrollOffsetSecondary;
1605            }
1606        }
1607
1608        boolean hadFocus = mBaseGridView.hasFocus();
1609        int savedFocusPos = mFocusPosition;
1610        if (mInFastRelayout = layoutInit()) {
1611            fastRelayout();
1612            View focusView = findViewByPosition(mFocusPosition);
1613            if (scrollToFocus) {
1614                scrollToView(focusView, false);
1615            }
1616            if (focusView != null && hadFocus) {
1617                focusView.requestFocus();
1618            }
1619        } else {
1620            if (mFocusPosition != NO_POSITION) {
1621                // appends items till focus position.
1622                while (appendOneColumnVisibleItems()
1623                        && findViewByPosition(mFocusPosition) == null) ;
1624            }
1625            // multiple rounds: scrollToView of first round may drag first/last child into
1626            // "visible window" and we update scrollMin/scrollMax then run second scrollToView
1627            int oldFirstVisible;
1628            int oldLastVisible;
1629            do {
1630                updateScrollMin();
1631                updateScrollMax();
1632                oldFirstVisible = mGrid.getFirstVisibleIndex();
1633                oldLastVisible = mGrid.getLastVisibleIndex();
1634                View focusView = findViewByPosition(mFocusPosition);
1635                // we need force to initialize the child view's position
1636                scrollToView(focusView, false);
1637                if (focusView != null && hadFocus) {
1638                    focusView.requestFocus();
1639                }
1640                appendVisibleItems();
1641                prependVisibleItems();
1642                removeInvisibleViewsAtFront();
1643                removeInvisibleViewsAtEnd();
1644            } while (mGrid.getFirstVisibleIndex() != oldFirstVisible ||
1645                    mGrid.getLastVisibleIndex() != oldLastVisible);
1646        }
1647
1648        if (scrollToFocus) {
1649            scrollDirectionPrimary(-delta);
1650            scrollDirectionSecondary(-deltaSecondary);
1651        }
1652        appendVisibleItems();
1653        prependVisibleItems();
1654        removeInvisibleViewsAtFront();
1655        removeInvisibleViewsAtEnd();
1656
1657        if (DEBUG) {
1658            StringWriter sw = new StringWriter();
1659            PrintWriter pw = new PrintWriter(sw);
1660            mGrid.debugPrint(pw);
1661            Log.d(getTag(), sw.toString());
1662        }
1663
1664        if (mRowSecondarySizeRefresh) {
1665            mRowSecondarySizeRefresh = false;
1666        } else {
1667            updateRowSecondarySizeRefresh();
1668        }
1669
1670        // For non fastRelayout we notify selection change in createItem().
1671        // For fastRelayout, only dispatch event when focus position changes.
1672        if (mInFastRelayout && mFocusPosition != savedFocusPos) {
1673            dispatchChildSelected();
1674        }
1675
1676        mInLayout = false;
1677        leaveContext();
1678        if (DEBUG) Log.v(getTag(), "layoutChildren end");
1679    }
1680
1681    private void offsetChildrenSecondary(int increment) {
1682        final int childCount = getChildCount();
1683        if (mOrientation == HORIZONTAL) {
1684            for (int i = 0; i < childCount; i++) {
1685                getChildAt(i).offsetTopAndBottom(increment);
1686            }
1687        } else {
1688            for (int i = 0; i < childCount; i++) {
1689                getChildAt(i).offsetLeftAndRight(increment);
1690            }
1691        }
1692    }
1693
1694    private void offsetChildrenPrimary(int increment) {
1695        final int childCount = getChildCount();
1696        if (mOrientation == VERTICAL) {
1697            for (int i = 0; i < childCount; i++) {
1698                getChildAt(i).offsetTopAndBottom(increment);
1699            }
1700        } else {
1701            for (int i = 0; i < childCount; i++) {
1702                getChildAt(i).offsetLeftAndRight(increment);
1703            }
1704        }
1705    }
1706
1707    @Override
1708    public int scrollHorizontallyBy(int dx, Recycler recycler, RecyclerView.State state) {
1709        if (DEBUG) Log.v(getTag(), "scrollHorizontallyBy " + dx);
1710        if (!mLayoutEnabled || !hasDoneFirstLayout()) {
1711            return 0;
1712        }
1713        saveContext(recycler, state);
1714        int result;
1715        if (mOrientation == HORIZONTAL) {
1716            result = scrollDirectionPrimary(dx);
1717        } else {
1718            result = scrollDirectionSecondary(dx);
1719        }
1720        leaveContext();
1721        return result;
1722    }
1723
1724    @Override
1725    public int scrollVerticallyBy(int dy, Recycler recycler, RecyclerView.State state) {
1726        if (DEBUG) Log.v(getTag(), "scrollVerticallyBy " + dy);
1727        if (!mLayoutEnabled || !hasDoneFirstLayout()) {
1728            return 0;
1729        }
1730        saveContext(recycler, state);
1731        int result;
1732        if (mOrientation == VERTICAL) {
1733            result = scrollDirectionPrimary(dy);
1734        } else {
1735            result = scrollDirectionSecondary(dy);
1736        }
1737        leaveContext();
1738        return result;
1739    }
1740
1741    // scroll in main direction may add/prune views
1742    private int scrollDirectionPrimary(int da) {
1743        if (TRACE) TraceHelper.beginSection("scrollPrimary");
1744        boolean isMaxUnknown = false, isMinUnknown = false;
1745        int minScroll = 0, maxScroll = 0;
1746        if (da > 0) {
1747            isMaxUnknown = mWindowAlignment.mainAxis().isMaxUnknown();
1748            if (!isMaxUnknown) {
1749                maxScroll = mWindowAlignment.mainAxis().getMaxScroll();
1750                if (mScrollOffsetPrimary + da > maxScroll) {
1751                    da = maxScroll - mScrollOffsetPrimary;
1752                }
1753            }
1754        } else if (da < 0) {
1755            isMinUnknown = mWindowAlignment.mainAxis().isMinUnknown();
1756            if (!isMinUnknown) {
1757                minScroll = mWindowAlignment.mainAxis().getMinScroll();
1758                if (mScrollOffsetPrimary + da < minScroll) {
1759                    da = minScroll - mScrollOffsetPrimary;
1760                }
1761            }
1762        }
1763        if (da == 0) {
1764            if (TRACE) TraceHelper.endSection();
1765            return 0;
1766        }
1767        offsetChildrenPrimary(-da);
1768        mScrollOffsetPrimary += da;
1769        if (mInLayout) {
1770            if (TRACE) TraceHelper.endSection();
1771            return da;
1772        }
1773
1774        int childCount = getChildCount();
1775        boolean updated;
1776
1777        if (mReverseFlowPrimary ? da > 0 : da < 0) {
1778            prependVisibleItems();
1779        } else {
1780            appendVisibleItems();
1781        }
1782        updated = getChildCount() > childCount;
1783        childCount = getChildCount();
1784
1785        if (TRACE) TraceHelper.beginSection("remove");
1786        if (mReverseFlowPrimary ? da > 0 : da < 0) {
1787            removeInvisibleViewsAtEnd();
1788        } else {
1789            removeInvisibleViewsAtFront();
1790        }
1791        if (TRACE) TraceHelper.endSection();
1792        updated |= getChildCount() < childCount;
1793        if (updated) {
1794            updateRowSecondarySizeRefresh();
1795        }
1796
1797        mBaseGridView.invalidate();
1798        if (TRACE) TraceHelper.endSection();
1799        return da;
1800    }
1801
1802    // scroll in second direction will not add/prune views
1803    private int scrollDirectionSecondary(int dy) {
1804        if (dy == 0) {
1805            return 0;
1806        }
1807        offsetChildrenSecondary(-dy);
1808        mScrollOffsetSecondary += dy;
1809        mBaseGridView.invalidate();
1810        return dy;
1811    }
1812
1813    private void updateScrollMax() {
1814        int highVisiblePos = (!mReverseFlowPrimary) ? mGrid.getLastVisibleIndex()
1815                : mGrid.getFirstVisibleIndex();
1816        int highMaxPos = (!mReverseFlowPrimary) ? mState.getItemCount() - 1 : 0;
1817        if (highVisiblePos < 0) {
1818            return;
1819        }
1820        final boolean highAvailable = highVisiblePos == highMaxPos;
1821        final boolean maxUnknown = mWindowAlignment.mainAxis().isMaxUnknown();
1822        if (!highAvailable && maxUnknown) {
1823            return;
1824        }
1825        int maxEdge = mGrid.findRowMax(true, sTwoInts) + mScrollOffsetPrimary;
1826        int rowIndex = sTwoInts[0];
1827        int pos = sTwoInts[1];
1828        int savedMaxEdge = mWindowAlignment.mainAxis().getMaxEdge();
1829        mWindowAlignment.mainAxis().setMaxEdge(maxEdge);
1830        int maxScroll = getPrimarySystemScrollPosition(findViewByPosition(pos));
1831        mWindowAlignment.mainAxis().setMaxEdge(savedMaxEdge);
1832
1833        if (highAvailable) {
1834            mWindowAlignment.mainAxis().setMaxEdge(maxEdge);
1835            mWindowAlignment.mainAxis().setMaxScroll(maxScroll);
1836            if (DEBUG) Log.v(getTag(), "updating scroll maxEdge to " + maxEdge +
1837                    " scrollMax to " + maxScroll);
1838        } else {
1839            // the maxScroll for currently last visible item is larger,
1840            // so we must invalidate the max scroll value.
1841            if (maxScroll > mWindowAlignment.mainAxis().getMaxScroll()) {
1842                mWindowAlignment.mainAxis().invalidateScrollMax();
1843                if (DEBUG) Log.v(getTag(), "Invalidate scrollMax since it should be "
1844                        + "greater than " + maxScroll);
1845            }
1846        }
1847    }
1848
1849    private void updateScrollMin() {
1850        int lowVisiblePos = (!mReverseFlowPrimary) ? mGrid.getFirstVisibleIndex()
1851                : mGrid.getLastVisibleIndex();
1852        int lowMinPos = (!mReverseFlowPrimary) ? 0 : mState.getItemCount() - 1;
1853        if (lowVisiblePos < 0) {
1854            return;
1855        }
1856        final boolean lowAvailable = lowVisiblePos == lowMinPos;
1857        final boolean minUnknown = mWindowAlignment.mainAxis().isMinUnknown();
1858        if (!lowAvailable && minUnknown) {
1859            return;
1860        }
1861        int minEdge = mGrid.findRowMin(false, sTwoInts) + mScrollOffsetPrimary;
1862        int rowIndex = sTwoInts[0];
1863        int pos = sTwoInts[1];
1864        int savedMinEdge = mWindowAlignment.mainAxis().getMinEdge();
1865        mWindowAlignment.mainAxis().setMinEdge(minEdge);
1866        int minScroll = getPrimarySystemScrollPosition(findViewByPosition(pos));
1867        mWindowAlignment.mainAxis().setMinEdge(savedMinEdge);
1868
1869        if (lowAvailable) {
1870            mWindowAlignment.mainAxis().setMinEdge(minEdge);
1871            mWindowAlignment.mainAxis().setMinScroll(minScroll);
1872            if (DEBUG) Log.v(getTag(), "updating scroll minEdge to " + minEdge +
1873                    " scrollMin to " + minScroll);
1874        } else {
1875            // the minScroll for currently first visible item is smaller,
1876            // so we must invalidate the min scroll value.
1877            if (minScroll < mWindowAlignment.mainAxis().getMinScroll()) {
1878                mWindowAlignment.mainAxis().invalidateScrollMin();
1879                if (DEBUG) Log.v(getTag(), "Invalidate scrollMin, since it should be "
1880                        + "less than " + minScroll);
1881            }
1882        }
1883    }
1884
1885    private void updateScrollSecondAxis() {
1886        mWindowAlignment.secondAxis().setMinEdge(0);
1887        mWindowAlignment.secondAxis().setMaxEdge(getSizeSecondary());
1888    }
1889
1890    private void initScrollController() {
1891        mWindowAlignment.reset();
1892        mWindowAlignment.horizontal.setSize(getWidth());
1893        mWindowAlignment.vertical.setSize(getHeight());
1894        mWindowAlignment.horizontal.setPadding(getPaddingLeft(), getPaddingRight());
1895        mWindowAlignment.vertical.setPadding(getPaddingTop(), getPaddingBottom());
1896        mSizePrimary = mWindowAlignment.mainAxis().getSize();
1897        mScrollOffsetPrimary = -mWindowAlignment.mainAxis().getPaddingLow();
1898        mScrollOffsetSecondary = -mWindowAlignment.secondAxis().getPaddingLow();
1899
1900        if (DEBUG) {
1901            Log.v(getTag(), "initScrollController mSizePrimary " + mSizePrimary
1902                    + " mWindowAlignment " + mWindowAlignment
1903                    + " mScrollOffsetPrimary " + mScrollOffsetPrimary);
1904        }
1905    }
1906
1907    private void updateScrollController() {
1908        // mScrollOffsetPrimary and mScrollOffsetSecondary includes the padding.
1909        // e.g. when topPadding is 16 for horizontal grid view,  the initial
1910        // mScrollOffsetSecondary is -16.  fastRelayout() put views based on offsets(not padding),
1911        // when padding changes to 20,  we also need update mScrollOffsetSecondary to -20 before
1912        // fastRelayout() is performed
1913        int paddingPrimaryDiff, paddingSecondaryDiff;
1914        if (mOrientation == HORIZONTAL) {
1915            paddingPrimaryDiff = getPaddingLeft() - mWindowAlignment.horizontal.getPaddingLow();
1916            paddingSecondaryDiff = getPaddingTop() - mWindowAlignment.vertical.getPaddingLow();
1917        } else {
1918            paddingPrimaryDiff = getPaddingTop() - mWindowAlignment.vertical.getPaddingLow();
1919            paddingSecondaryDiff = getPaddingLeft() - mWindowAlignment.horizontal.getPaddingLow();
1920        }
1921        mScrollOffsetPrimary -= paddingPrimaryDiff;
1922        mScrollOffsetSecondary -= paddingSecondaryDiff;
1923
1924        mWindowAlignment.horizontal.setSize(getWidth());
1925        mWindowAlignment.vertical.setSize(getHeight());
1926        mWindowAlignment.horizontal.setPadding(getPaddingLeft(), getPaddingRight());
1927        mWindowAlignment.vertical.setPadding(getPaddingTop(), getPaddingBottom());
1928        mSizePrimary = mWindowAlignment.mainAxis().getSize();
1929
1930        if (DEBUG) {
1931            Log.v(getTag(), "updateScrollController mSizePrimary " + mSizePrimary
1932                    + " mWindowAlignment " + mWindowAlignment
1933                    + " mScrollOffsetPrimary " + mScrollOffsetPrimary);
1934        }
1935    }
1936
1937    public void setSelection(RecyclerView parent, int position) {
1938        setSelection(parent, position, false);
1939    }
1940
1941    public void setSelectionSmooth(RecyclerView parent, int position) {
1942        setSelection(parent, position, true);
1943    }
1944
1945    public int getSelection() {
1946        return mFocusPosition;
1947    }
1948
1949    public void setSelection(RecyclerView parent, int position, boolean smooth) {
1950        if (mFocusPosition != position && position != NO_POSITION) {
1951            scrollToSelection(parent, position, smooth);
1952        }
1953    }
1954
1955    private void scrollToSelection(RecyclerView parent, int position, boolean smooth) {
1956        if (TRACE) TraceHelper.beginSection("scrollToSelection");
1957        View view = findViewByPosition(position);
1958        if (view != null) {
1959            mInSelection = true;
1960            scrollToView(view, smooth);
1961            mInSelection = false;
1962        } else {
1963            mFocusPosition = position;
1964            mFocusPositionOffset = 0;
1965            if (!mLayoutEnabled) {
1966                return;
1967            }
1968            if (smooth) {
1969                if (!hasDoneFirstLayout()) {
1970                    Log.w(getTag(), "setSelectionSmooth should " +
1971                            "not be called before first layout pass");
1972                    return;
1973                }
1974                startPositionSmoothScroller(position);
1975            } else {
1976                mForceFullLayout = true;
1977                parent.requestLayout();
1978            }
1979        }
1980        if (TRACE) TraceHelper.endSection();
1981    }
1982
1983    void startPositionSmoothScroller(int position) {
1984        LinearSmoothScroller linearSmoothScroller = new GridLinearSmoothScroller() {
1985            @Override
1986            public PointF computeScrollVectorForPosition(int targetPosition) {
1987                if (getChildCount() == 0) {
1988                    return null;
1989                }
1990                final int firstChildPos = getPosition(getChildAt(0));
1991                // TODO We should be able to deduce direction from bounds of current and target
1992                // focus, rather than making assumptions about positions and directionality
1993                final boolean isStart = mReverseFlowPrimary ? targetPosition > firstChildPos
1994                        : targetPosition < firstChildPos;
1995                final int direction = isStart ? -1 : 1;
1996                if (mOrientation == HORIZONTAL) {
1997                    return new PointF(direction, 0);
1998                } else {
1999                    return new PointF(0, direction);
2000                }
2001            }
2002
2003        };
2004        linearSmoothScroller.setTargetPosition(position);
2005        startSmoothScroll(linearSmoothScroller);
2006    }
2007
2008    private void processPendingMovement(boolean forward) {
2009        if (forward ? hasCreatedLastItem() : hasCreatedFirstItem()) {
2010            return;
2011        }
2012        if (mPendingMoveSmoothScroller == null) {
2013            // Stop existing scroller and create a new PendingMoveSmoothScroller.
2014            mBaseGridView.stopScroll();
2015            PendingMoveSmoothScroller linearSmoothScroller = new PendingMoveSmoothScroller(
2016                    forward ? 1 : -1, mNumRows > 1);
2017            mFocusPositionOffset = 0;
2018            startSmoothScroll(linearSmoothScroller);
2019            if (linearSmoothScroller.isRunning()) {
2020                mPendingMoveSmoothScroller = linearSmoothScroller;
2021            }
2022        } else {
2023            if (forward) {
2024                mPendingMoveSmoothScroller.increasePendingMoves();
2025            } else {
2026                mPendingMoveSmoothScroller.decreasePendingMoves();
2027            }
2028        }
2029    }
2030
2031    @Override
2032    public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
2033        if (DEBUG) Log.v(getTag(), "onItemsAdded positionStart "
2034                + positionStart + " itemCount " + itemCount);
2035        if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE) {
2036            int pos = mFocusPosition + mFocusPositionOffset;
2037            if (positionStart <= pos) {
2038                mFocusPositionOffset += itemCount;
2039            }
2040        }
2041        mChildrenStates.clear();
2042    }
2043
2044    @Override
2045    public void onItemsChanged(RecyclerView recyclerView) {
2046        if (DEBUG) Log.v(getTag(), "onItemsChanged");
2047        mFocusPositionOffset = 0;
2048        mChildrenStates.clear();
2049    }
2050
2051    @Override
2052    public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
2053        if (DEBUG) Log.v(getTag(), "onItemsRemoved positionStart "
2054                + positionStart + " itemCount " + itemCount);
2055        if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE) {
2056            int pos = mFocusPosition + mFocusPositionOffset;
2057            if (positionStart <= pos) {
2058                if (positionStart + itemCount > pos) {
2059                    // stop updating offset after the focus item was removed
2060                    mFocusPositionOffset = Integer.MIN_VALUE;
2061                } else {
2062                    mFocusPositionOffset -= itemCount;
2063                }
2064            }
2065        }
2066        mChildrenStates.clear();
2067    }
2068
2069    @Override
2070    public void onItemsMoved(RecyclerView recyclerView, int fromPosition, int toPosition,
2071            int itemCount) {
2072        if (DEBUG) Log.v(getTag(), "onItemsMoved fromPosition "
2073                + fromPosition + " toPosition " + toPosition);
2074        if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE) {
2075            int pos = mFocusPosition + mFocusPositionOffset;
2076            if (fromPosition <= pos && pos < fromPosition + itemCount) {
2077                // moved items include focused position
2078                mFocusPositionOffset += toPosition - fromPosition;
2079            } else if (fromPosition < pos && toPosition > pos - itemCount) {
2080                // move items before focus position to after focused position
2081                mFocusPositionOffset -= itemCount;
2082            } else if (fromPosition > pos && toPosition < pos) {
2083                // move items after focus position to before focused position
2084                mFocusPositionOffset += itemCount;
2085            }
2086        }
2087        mChildrenStates.clear();
2088    }
2089
2090    @Override
2091    public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
2092        if (DEBUG) Log.v(getTag(), "onItemsUpdated positionStart "
2093                + positionStart + " itemCount " + itemCount);
2094        for (int i = positionStart, end = positionStart + itemCount; i < end; i++) {
2095            mChildrenStates.remove(i);
2096        }
2097    }
2098
2099    @Override
2100    public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) {
2101        if (mFocusSearchDisabled) {
2102            return true;
2103        }
2104        if (getPositionByView(child) == NO_POSITION) {
2105            // This shouldn't happen, but in case it does be sure not to attempt a
2106            // scroll to a view whose item has been removed.
2107            return true;
2108        }
2109        if (!mInLayout && !mInSelection) {
2110            scrollToView(child, true);
2111        }
2112        return true;
2113    }
2114
2115    @Override
2116    public boolean requestChildRectangleOnScreen(RecyclerView parent, View view, Rect rect,
2117            boolean immediate) {
2118        if (DEBUG) Log.v(getTag(), "requestChildRectangleOnScreen " + view + " " + rect);
2119        return false;
2120    }
2121
2122    int getScrollOffsetX() {
2123        return mOrientation == HORIZONTAL ? mScrollOffsetPrimary : mScrollOffsetSecondary;
2124    }
2125
2126    int getScrollOffsetY() {
2127        return mOrientation == HORIZONTAL ? mScrollOffsetSecondary : mScrollOffsetPrimary;
2128    }
2129
2130    public void getViewSelectedOffsets(View view, int[] offsets) {
2131        if (mOrientation == HORIZONTAL) {
2132            offsets[0] = getPrimarySystemScrollPosition(view) - mScrollOffsetPrimary;
2133            offsets[1] = getSecondarySystemScrollPosition(view) - mScrollOffsetSecondary;
2134        } else {
2135            offsets[1] = getPrimarySystemScrollPosition(view) - mScrollOffsetPrimary;
2136            offsets[0] = getSecondarySystemScrollPosition(view) - mScrollOffsetSecondary;
2137        }
2138    }
2139
2140    private int getPrimarySystemScrollPosition(View view) {
2141        final int viewCenterPrimary = mScrollOffsetPrimary + getViewCenter(view);
2142        final int viewMin = getViewMin(view);
2143        final int viewMax = getViewMax(view);
2144        // TODO: change to use State object in onRequestChildFocus()
2145        boolean isMin, isMax;
2146        if (!mReverseFlowPrimary) {
2147            isMin = mGrid.getFirstVisibleIndex() == 0;
2148            isMax = mGrid.getLastVisibleIndex() == (mState == null ?
2149                    getItemCount() : mState.getItemCount()) - 1;
2150        } else {
2151            isMax = mGrid.getFirstVisibleIndex() == 0;
2152            isMin = mGrid.getLastVisibleIndex() == (mState == null ?
2153                    getItemCount() : mState.getItemCount()) - 1;
2154        }
2155        for (int i = getChildCount() - 1; (isMin || isMax) && i >= 0; i--) {
2156            View v = getChildAt(i);
2157            if (v == view || v == null) {
2158                continue;
2159            }
2160            if (isMin && getViewMin(v) < viewMin) {
2161                isMin = false;
2162            }
2163            if (isMax && getViewMax(v) > viewMax) {
2164                isMax = false;
2165            }
2166        }
2167        return mWindowAlignment.mainAxis().getSystemScrollPos(viewCenterPrimary, isMin, isMax);
2168    }
2169
2170    private int getSecondarySystemScrollPosition(View view) {
2171        int viewCenterSecondary = mScrollOffsetSecondary + getViewCenterSecondary(view);
2172        int pos = getPositionByView(view);
2173        Grid.Location location = mGrid.getLocation(pos);
2174        final int row = location.row;
2175        final boolean isMin, isMax;
2176        if (!mReverseFlowSecondary) {
2177            isMin = row == 0;
2178            isMax = row == mGrid.getNumRows() - 1;
2179        } else {
2180            isMax = row == 0;
2181            isMin = row == mGrid.getNumRows() - 1;
2182        }
2183        return mWindowAlignment.secondAxis().getSystemScrollPos(viewCenterSecondary, isMin, isMax);
2184    }
2185
2186    /**
2187     * Scroll to a given child view and change mFocusPosition.
2188     */
2189    private void scrollToView(View view, boolean smooth) {
2190        int newFocusPosition = getPositionByView(view);
2191        if (newFocusPosition != mFocusPosition) {
2192            mFocusPosition = newFocusPosition;
2193            mFocusPositionOffset = 0;
2194            if (!mInLayout) {
2195                dispatchChildSelected();
2196            }
2197            if (mBaseGridView.isChildrenDrawingOrderEnabledInternal()) {
2198                mBaseGridView.invalidate();
2199            }
2200        }
2201        if (view == null) {
2202            return;
2203        }
2204        if (!view.hasFocus() && mBaseGridView.hasFocus()) {
2205            // transfer focus to the child if it does not have focus yet (e.g. triggered
2206            // by setSelection())
2207            view.requestFocus();
2208        }
2209        if (!mScrollEnabled && smooth) {
2210            return;
2211        }
2212        if (getScrollPosition(view, sTwoInts)) {
2213            scrollGrid(sTwoInts[0], sTwoInts[1], smooth);
2214        }
2215    }
2216
2217    private boolean getScrollPosition(View view, int[] deltas) {
2218        switch (mFocusScrollStrategy) {
2219        case BaseGridView.FOCUS_SCROLL_ALIGNED:
2220        default:
2221            return getAlignedPosition(view, deltas);
2222        case BaseGridView.FOCUS_SCROLL_ITEM:
2223        case BaseGridView.FOCUS_SCROLL_PAGE:
2224            return getNoneAlignedPosition(view, deltas);
2225        }
2226    }
2227
2228    private boolean getNoneAlignedPosition(View view, int[] deltas) {
2229        int pos = getPositionByView(view);
2230        int viewMin = getViewMin(view);
2231        int viewMax = getViewMax(view);
2232        // we either align "firstView" to left/top padding edge
2233        // or align "lastView" to right/bottom padding edge
2234        View firstView = null;
2235        View lastView = null;
2236        int paddingLow = mWindowAlignment.mainAxis().getPaddingLow();
2237        int clientSize = mWindowAlignment.mainAxis().getClientSize();
2238        final int row = mGrid.getRowIndex(pos);
2239        if (viewMin < paddingLow) {
2240            // view enters low padding area:
2241            firstView = view;
2242            if (mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_PAGE) {
2243                // scroll one "page" left/top,
2244                // align first visible item of the "page" at the low padding edge.
2245                while (prependOneColumnVisibleItems()) {
2246                    CircularIntArray positions =
2247                            mGrid.getItemPositionsInRows(mGrid.getFirstVisibleIndex(), pos)[row];
2248                    firstView = findViewByPosition(positions.get(0));
2249                    if (viewMax - getViewMin(firstView) > clientSize) {
2250                        if (positions.size() > 2) {
2251                            firstView = findViewByPosition(positions.get(2));
2252                        }
2253                        break;
2254                    }
2255                }
2256            }
2257        } else if (viewMax > clientSize + paddingLow) {
2258            // view enters high padding area:
2259            if (mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_PAGE) {
2260                // scroll whole one page right/bottom, align view at the low padding edge.
2261                firstView = view;
2262                do {
2263                    CircularIntArray positions =
2264                            mGrid.getItemPositionsInRows(pos, mGrid.getLastVisibleIndex())[row];
2265                    lastView = findViewByPosition(positions.get(positions.size() - 1));
2266                    if (getViewMax(lastView) - viewMin > clientSize) {
2267                        lastView = null;
2268                        break;
2269                    }
2270                } while (appendOneColumnVisibleItems());
2271                if (lastView != null) {
2272                    // however if we reached end,  we should align last view.
2273                    firstView = null;
2274                }
2275            } else {
2276                lastView = view;
2277            }
2278        }
2279        int scrollPrimary = 0;
2280        int scrollSecondary = 0;
2281        if (firstView != null) {
2282            scrollPrimary = getViewMin(firstView) - paddingLow;
2283        } else if (lastView != null) {
2284            scrollPrimary = getViewMax(lastView) - (paddingLow + clientSize);
2285        }
2286        View secondaryAlignedView;
2287        if (firstView != null) {
2288            secondaryAlignedView = firstView;
2289        } else if (lastView != null) {
2290            secondaryAlignedView = lastView;
2291        } else {
2292            secondaryAlignedView = view;
2293        }
2294        scrollSecondary = getSecondarySystemScrollPosition(secondaryAlignedView);
2295        scrollSecondary -= mScrollOffsetSecondary;
2296        if (scrollPrimary != 0 || scrollSecondary != 0) {
2297            deltas[0] = scrollPrimary;
2298            deltas[1] = scrollSecondary;
2299            return true;
2300        }
2301        return false;
2302    }
2303
2304    private boolean getAlignedPosition(View view, int[] deltas) {
2305        int scrollPrimary = getPrimarySystemScrollPosition(view);
2306        int scrollSecondary = getSecondarySystemScrollPosition(view);
2307        if (DEBUG) {
2308            Log.v(getTag(), "getAlignedPosition " + scrollPrimary + " " + scrollSecondary
2309                    + " " + mWindowAlignment);
2310            Log.v(getTag(), "getAlignedPosition " + mScrollOffsetPrimary + " " + mScrollOffsetSecondary);
2311        }
2312        scrollPrimary -= mScrollOffsetPrimary;
2313        scrollSecondary -= mScrollOffsetSecondary;
2314        if (scrollPrimary != 0 || scrollSecondary != 0) {
2315            deltas[0] = scrollPrimary;
2316            deltas[1] = scrollSecondary;
2317            return true;
2318        }
2319        return false;
2320    }
2321
2322    private void scrollGrid(int scrollPrimary, int scrollSecondary, boolean smooth) {
2323        if (mInLayout) {
2324            scrollDirectionPrimary(scrollPrimary);
2325            scrollDirectionSecondary(scrollSecondary);
2326        } else {
2327            int scrollX;
2328            int scrollY;
2329            if (mOrientation == HORIZONTAL) {
2330                scrollX = scrollPrimary;
2331                scrollY = scrollSecondary;
2332            } else {
2333                scrollX = scrollSecondary;
2334                scrollY = scrollPrimary;
2335            }
2336            if (smooth) {
2337                mBaseGridView.smoothScrollBy(scrollX, scrollY);
2338            } else {
2339                mBaseGridView.scrollBy(scrollX, scrollY);
2340            }
2341        }
2342    }
2343
2344    public void setPruneChild(boolean pruneChild) {
2345        if (mPruneChild != pruneChild) {
2346            mPruneChild = pruneChild;
2347            if (mPruneChild) {
2348                requestLayout();
2349            }
2350        }
2351    }
2352
2353    public boolean getPruneChild() {
2354        return mPruneChild;
2355    }
2356
2357    public void setScrollEnabled(boolean scrollEnabled) {
2358        if (mScrollEnabled != scrollEnabled) {
2359            mScrollEnabled = scrollEnabled;
2360            if (mScrollEnabled && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED
2361                    && mFocusPosition != NO_POSITION) {
2362                scrollToSelection(mBaseGridView, mFocusPosition, true);
2363            }
2364        }
2365    }
2366
2367    public boolean isScrollEnabled() {
2368        return mScrollEnabled;
2369    }
2370
2371    private int findImmediateChildIndex(View view) {
2372        while (view != null && view != mBaseGridView) {
2373            int index = mBaseGridView.indexOfChild(view);
2374            if (index >= 0) {
2375                return index;
2376            }
2377            view = (View) view.getParent();
2378        }
2379        return NO_POSITION;
2380    }
2381
2382    void setFocusSearchDisabled(boolean disabled) {
2383        mFocusSearchDisabled = disabled;
2384    }
2385
2386    boolean isFocusSearchDisabled() {
2387        return mFocusSearchDisabled;
2388    }
2389
2390    @Override
2391    public View onInterceptFocusSearch(View focused, int direction) {
2392        if (mFocusSearchDisabled) {
2393            return focused;
2394        }
2395        return null;
2396    }
2397
2398    boolean hasPreviousViewInSameRow(int pos) {
2399        if (mGrid == null || pos == NO_POSITION || mGrid.getFirstVisibleIndex() < 0) {
2400            return false;
2401        }
2402        if (mGrid.getFirstVisibleIndex() > 0) {
2403            return true;
2404        }
2405        final int focusedRow = mGrid.getLocation(pos).row;
2406        for (int i = getChildCount() - 1; i >= 0; i--) {
2407            int position = getPositionByIndex(i);
2408            Grid.Location loc = mGrid.getLocation(position);
2409            if (loc != null && loc.row == focusedRow) {
2410                if (position < pos) {
2411                    return true;
2412                }
2413            }
2414        }
2415        return false;
2416    }
2417
2418    @Override
2419    public boolean onAddFocusables(RecyclerView recyclerView,
2420            ArrayList<View> views, int direction, int focusableMode) {
2421        if (mFocusSearchDisabled) {
2422            return true;
2423        }
2424        // If this viewgroup or one of its children currently has focus then we
2425        // consider our children for focus searching in main direction on the same row.
2426        // If this viewgroup has no focus and using focus align, we want the system
2427        // to ignore our children and pass focus to the viewgroup, which will pass
2428        // focus on to its children appropriately.
2429        // If this viewgroup has no focus and not using focus align, we want to
2430        // consider the child that does not overlap with padding area.
2431        if (recyclerView.hasFocus()) {
2432            final int movement = getMovement(direction);
2433            if (movement != PREV_ITEM && movement != NEXT_ITEM) {
2434                // Move on secondary direction uses default addFocusables().
2435                return false;
2436            }
2437            if (mPendingMoveSmoothScroller != null) {
2438                // don't find next focusable if has pending movement.
2439                return true;
2440            }
2441            final View focused = recyclerView.findFocus();
2442            final int focusedPos = getPositionByIndex(findImmediateChildIndex(focused));
2443            // Add focusables of focused item.
2444            if (focusedPos != NO_POSITION) {
2445                findViewByPosition(focusedPos).addFocusables(views,  direction, focusableMode);
2446            }
2447            final int focusedRow = mGrid != null && focusedPos != NO_POSITION ?
2448                    mGrid.getLocation(focusedPos).row : NO_POSITION;
2449            // Add focusables of next neighbor of same row on the focus search direction.
2450            if (mGrid != null) {
2451                final int focusableCount = views.size();
2452                for (int i = 0, count = getChildCount(); i < count; i++) {
2453                    int index = movement == NEXT_ITEM ? i : count - 1 - i;
2454                    final View child = getChildAt(index);
2455                    if (child.getVisibility() != View.VISIBLE || !child.hasFocusable()) {
2456                        continue;
2457                    }
2458                    int position = getPositionByIndex(index);
2459                    Grid.Location loc = mGrid.getLocation(position);
2460                    if (focusedRow == NO_POSITION || (loc != null && loc.row == focusedRow)) {
2461                        if (focusedPos == NO_POSITION ||
2462                                (movement == NEXT_ITEM && position > focusedPos)
2463                                || (movement == PREV_ITEM && position < focusedPos)) {
2464                            child.addFocusables(views,  direction, focusableMode);
2465                            if (views.size() > focusableCount) {
2466                                break;
2467                            }
2468                        }
2469                    }
2470                }
2471            }
2472        } else {
2473            if (mFocusScrollStrategy != BaseGridView.FOCUS_SCROLL_ALIGNED) {
2474                // adding views not overlapping padding area to avoid scrolling in gaining focus
2475                int left = mWindowAlignment.mainAxis().getPaddingLow();
2476                int right = mWindowAlignment.mainAxis().getClientSize() + left;
2477                int focusableCount = views.size();
2478                for (int i = 0, count = getChildCount(); i < count; i++) {
2479                    View child = getChildAt(i);
2480                    if (child.getVisibility() == View.VISIBLE) {
2481                        if (getViewMin(child) >= left && getViewMax(child) <= right) {
2482                            child.addFocusables(views, direction, focusableMode);
2483                        }
2484                    }
2485                }
2486                // if we cannot find any, then just add all children.
2487                if (views.size() == focusableCount) {
2488                    for (int i = 0, count = getChildCount(); i < count; i++) {
2489                        View child = getChildAt(i);
2490                        if (child.getVisibility() == View.VISIBLE) {
2491                            child.addFocusables(views, direction, focusableMode);
2492                        }
2493                    }
2494                    if (views.size() != focusableCount) {
2495                        return true;
2496                    }
2497                } else {
2498                    return true;
2499                }
2500                // if still cannot find any, fall through and add itself
2501            }
2502            if (recyclerView.isFocusable()) {
2503                views.add(recyclerView);
2504            }
2505        }
2506        return true;
2507    }
2508
2509    private boolean hasCreatedLastItem() {
2510        int count = mState.getItemCount();
2511        return count == 0 || findViewByPosition(count - 1) != null;
2512    }
2513
2514    private boolean hasCreatedFirstItem() {
2515        int count = mState.getItemCount();
2516        return count == 0 || findViewByPosition(0) != null;
2517    }
2518
2519    boolean canScrollTo(View view) {
2520        return view.getVisibility() == View.VISIBLE && (!hasFocus() || view.hasFocusable());
2521    }
2522
2523    @Override
2524    public View onFocusSearchFailed(View focused, int direction, Recycler recycler,
2525            RecyclerView.State state) {
2526        if (DEBUG) Log.v(getTag(), "onFocusSearchFailed direction " + direction);
2527
2528        View view = null;
2529        int movement = getMovement(direction);
2530        final boolean isScroll = mBaseGridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE;
2531        saveContext(recycler, state);
2532        if (movement == NEXT_ITEM) {
2533            if (isScroll || !mFocusOutEnd) {
2534                view = focused;
2535            }
2536            if (mScrollEnabled && !hasCreatedLastItem()) {
2537                processPendingMovement(true);
2538                view = focused;
2539            }
2540        } else if (movement == PREV_ITEM) {
2541            if (isScroll || !mFocusOutFront) {
2542                view = focused;
2543            }
2544            if (mScrollEnabled && !hasCreatedFirstItem()) {
2545                processPendingMovement(false);
2546                view = focused;
2547            }
2548        }
2549        leaveContext();
2550        if (DEBUG) Log.v(getTag(), "onFocusSearchFailed returning view " + view);
2551        return view;
2552    }
2553
2554    boolean gridOnRequestFocusInDescendants(RecyclerView recyclerView, int direction,
2555            Rect previouslyFocusedRect) {
2556        switch (mFocusScrollStrategy) {
2557        case BaseGridView.FOCUS_SCROLL_ALIGNED:
2558        default:
2559            return gridOnRequestFocusInDescendantsAligned(recyclerView,
2560                    direction, previouslyFocusedRect);
2561        case BaseGridView.FOCUS_SCROLL_PAGE:
2562        case BaseGridView.FOCUS_SCROLL_ITEM:
2563            return gridOnRequestFocusInDescendantsUnaligned(recyclerView,
2564                    direction, previouslyFocusedRect);
2565        }
2566    }
2567
2568    private boolean gridOnRequestFocusInDescendantsAligned(RecyclerView recyclerView,
2569            int direction, Rect previouslyFocusedRect) {
2570        View view = findViewByPosition(mFocusPosition);
2571        if (view != null) {
2572            boolean result = view.requestFocus(direction, previouslyFocusedRect);
2573            if (!result && DEBUG) {
2574                Log.w(getTag(), "failed to request focus on " + view);
2575            }
2576            return result;
2577        }
2578        return false;
2579    }
2580
2581    private boolean gridOnRequestFocusInDescendantsUnaligned(RecyclerView recyclerView,
2582            int direction, Rect previouslyFocusedRect) {
2583        // focus to view not overlapping padding area to avoid scrolling in gaining focus
2584        int index;
2585        int increment;
2586        int end;
2587        int count = getChildCount();
2588        if ((direction & View.FOCUS_FORWARD) != 0) {
2589            index = 0;
2590            increment = 1;
2591            end = count;
2592        } else {
2593            index = count - 1;
2594            increment = -1;
2595            end = -1;
2596        }
2597        int left = mWindowAlignment.mainAxis().getPaddingLow();
2598        int right = mWindowAlignment.mainAxis().getClientSize() + left;
2599        for (int i = index; i != end; i += increment) {
2600            View child = getChildAt(i);
2601            if (child.getVisibility() == View.VISIBLE) {
2602                if (getViewMin(child) >= left && getViewMax(child) <= right) {
2603                    if (child.requestFocus(direction, previouslyFocusedRect)) {
2604                        return true;
2605                    }
2606                }
2607            }
2608        }
2609        return false;
2610    }
2611
2612    private final static int PREV_ITEM = 0;
2613    private final static int NEXT_ITEM = 1;
2614    private final static int PREV_ROW = 2;
2615    private final static int NEXT_ROW = 3;
2616
2617    private int getMovement(int direction) {
2618        int movement = View.FOCUS_LEFT;
2619
2620        if (mOrientation == HORIZONTAL) {
2621            switch(direction) {
2622                case View.FOCUS_LEFT:
2623                    movement = (!mReverseFlowPrimary) ? PREV_ITEM : NEXT_ITEM;
2624                    break;
2625                case View.FOCUS_RIGHT:
2626                    movement = (!mReverseFlowPrimary) ? NEXT_ITEM : PREV_ITEM;
2627                    break;
2628                case View.FOCUS_UP:
2629                    movement = PREV_ROW;
2630                    break;
2631                case View.FOCUS_DOWN:
2632                    movement = NEXT_ROW;
2633                    break;
2634            }
2635         } else if (mOrientation == VERTICAL) {
2636             switch(direction) {
2637                 case View.FOCUS_LEFT:
2638                     movement = (!mReverseFlowPrimary) ? PREV_ROW : NEXT_ROW;
2639                     break;
2640                 case View.FOCUS_RIGHT:
2641                     movement = (!mReverseFlowPrimary) ? NEXT_ROW : PREV_ROW;
2642                     break;
2643                 case View.FOCUS_UP:
2644                     movement = PREV_ITEM;
2645                     break;
2646                 case View.FOCUS_DOWN:
2647                     movement = NEXT_ITEM;
2648                     break;
2649             }
2650         }
2651
2652        return movement;
2653    }
2654
2655    int getChildDrawingOrder(RecyclerView recyclerView, int childCount, int i) {
2656        View view = findViewByPosition(mFocusPosition);
2657        if (view == null) {
2658            return i;
2659        }
2660        int focusIndex = recyclerView.indexOfChild(view);
2661        // supposely 0 1 2 3 4 5 6 7 8 9, 4 is the center item
2662        // drawing order is 0 1 2 3 9 8 7 6 5 4
2663        if (i < focusIndex) {
2664            return i;
2665        } else if (i < childCount - 1) {
2666            return focusIndex + childCount - 1 - i;
2667        } else {
2668            return focusIndex;
2669        }
2670    }
2671
2672    @Override
2673    public void onAdapterChanged(RecyclerView.Adapter oldAdapter,
2674            RecyclerView.Adapter newAdapter) {
2675        if (DEBUG) Log.v(getTag(), "onAdapterChanged to " + newAdapter);
2676        if (oldAdapter != null) {
2677            discardLayoutInfo();
2678            mFocusPosition = NO_POSITION;
2679            mFocusPositionOffset = 0;
2680            mChildrenStates.clear();
2681        }
2682        super.onAdapterChanged(oldAdapter, newAdapter);
2683    }
2684
2685    private void discardLayoutInfo() {
2686        mGrid = null;
2687        mRowSizeSecondary = null;
2688        mRowSecondarySizeRefresh = false;
2689    }
2690
2691    public void setLayoutEnabled(boolean layoutEnabled) {
2692        if (mLayoutEnabled != layoutEnabled) {
2693            mLayoutEnabled = layoutEnabled;
2694            requestLayout();
2695        }
2696    }
2697
2698    void setChildrenVisibility(int visiblity) {
2699        mChildVisibility = visiblity;
2700        if (mChildVisibility != -1) {
2701            int count = getChildCount();
2702            for (int i= 0; i < count; i++) {
2703                getChildAt(i).setVisibility(mChildVisibility);
2704            }
2705        }
2706    }
2707
2708    final static class SavedState implements Parcelable {
2709
2710        int index; // index inside adapter of the current view
2711        Bundle childStates = Bundle.EMPTY;
2712
2713        @Override
2714        public void writeToParcel(Parcel out, int flags) {
2715            out.writeInt(index);
2716            out.writeBundle(childStates);
2717        }
2718
2719        @SuppressWarnings("hiding")
2720        public static final Parcelable.Creator<SavedState> CREATOR =
2721                new Parcelable.Creator<SavedState>() {
2722                    @Override
2723                    public SavedState createFromParcel(Parcel in) {
2724                        return new SavedState(in);
2725                    }
2726
2727                    @Override
2728                    public SavedState[] newArray(int size) {
2729                        return new SavedState[size];
2730                    }
2731                };
2732
2733        @Override
2734        public int describeContents() {
2735            return 0;
2736        }
2737
2738        SavedState(Parcel in) {
2739            index = in.readInt();
2740            childStates = in.readBundle(GridLayoutManager.class.getClassLoader());
2741        }
2742
2743        SavedState() {
2744        }
2745    }
2746
2747    @Override
2748    public Parcelable onSaveInstanceState() {
2749        if (DEBUG) Log.v(getTag(), "onSaveInstanceState getSelection() " + getSelection());
2750        SavedState ss = new SavedState();
2751        for (int i = 0, count = getChildCount(); i < count; i++) {
2752            View view = getChildAt(i);
2753            int position = getPositionByView(view);
2754            if (position != NO_POSITION) {
2755                mChildrenStates.saveOnScreenView(view, position);
2756            }
2757        }
2758        ss.index = getSelection();
2759        ss.childStates = mChildrenStates.saveAsBundle();
2760        return ss;
2761    }
2762
2763    void onChildRecycled(RecyclerView.ViewHolder holder) {
2764        final int position = holder.getAdapterPosition();
2765        if (position != NO_POSITION) {
2766            mChildrenStates.saveOffscreenView(holder.itemView, position);
2767        }
2768    }
2769
2770    @Override
2771    public void onRestoreInstanceState(Parcelable state) {
2772        if (!(state instanceof SavedState)) {
2773            return;
2774        }
2775        SavedState loadingState = (SavedState)state;
2776        mFocusPosition = loadingState.index;
2777        mFocusPositionOffset = 0;
2778        mChildrenStates.loadFromBundle(loadingState.childStates);
2779        mForceFullLayout = true;
2780        requestLayout();
2781        if (DEBUG) Log.v(getTag(), "onRestoreInstanceState mFocusPosition " + mFocusPosition);
2782    }
2783}
2784