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