165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane/*
265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Copyright (C) 2014 The Android Open Source Project
365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *
465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Licensed under the Apache License, Version 2.0 (the "License");
565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * you may not use this file except in compliance with the License.
665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * You may obtain a copy of the License at
765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *
865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *      http://www.apache.org/licenses/LICENSE-2.0
965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *
1065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Unless required by applicable law or agreed to in writing, software
1165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * distributed under the License is distributed on an "AS IS" BASIS,
1265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * See the License for the specific language governing permissions and
1465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * limitations under the License.
1565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane */
1665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
1765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lanepackage com.android.tv.settings.widget;
1865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
1965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.animation.Animator;
2065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.animation.AnimatorInflater;
2165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.animation.ObjectAnimator;
2265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.content.Context;
2365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.content.res.TypedArray;
2465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.database.DataSetObserver;
2565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.graphics.Rect;
2665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.os.Bundle;
2765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.os.Parcel;
2865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.os.Parcelable;
2965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.util.AttributeSet;
3065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.util.Log;
3165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.view.FocusFinder;
3265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.view.KeyEvent;
3365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.view.SoundEffectConstants;
3465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.view.View;
3565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.view.ViewGroup;
3665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.view.ViewParent;
376e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantlerimport android.view.accessibility.AccessibilityEvent;
3865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.widget.Adapter;
396e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantlerimport android.widget.AdapterView;
4065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
4165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport com.android.tv.settings.R;
4265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
4365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport java.util.ArrayList;
4465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport java.util.List;
4565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
4665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane/**
4765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * A scrollable AdapterView, similar to {@link android.widget.Gallery}. Features include:
4865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * <p>
4965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Supports "expandable" views by supplying a Adapter that implements
5065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * {@link ScrollAdapter#getExpandAdapter()}. Generally you could see two expanded views at most: one
5165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * fade in, one fade out.
5265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * <p>
5365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Supports {@link #HORIZONTAL} and {@link #VERTICAL} set by {@link #setOrientation(int)}.
5465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * So you could have a vertical ScrollAdapterView with a nested expanding Horizontal ScrollAdapterView.
5565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * <p>
5665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Supports Grid view style, see {@link #setGridSetting(int)}.
5765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * <p>
5865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Supports Different strategies of scrolling viewport, see
5965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * {@link ScrollController#SCROLL_CENTER_IN_MIDDLE},
6065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * {@link ScrollController#SCROLL_CENTER_FIXED}, and
6165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * {@link ScrollController#SCROLL_CENTER_FIXED_PERCENT}.
6265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Also take a look of {@link #adjustSystemScrollPos()} for better understanding how Center
6365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * is translated to android View scroll position.
6465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * <p>
6565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Expandable items animation is based on distance to the center. Motivation behind not using two
6665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * time based animations for focusing/onfocusing is that in a fast scroll, there is no better way to
6765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * synchronize these two animations with scroller animation; so you will end up with situation that
6865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * scale animated item cannot be kept in the center because scroll animation is too fast/too slow.
6965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * By using distance to the scroll center, the animation of focus/unfocus will be accurately synced
7065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * with scroller animation. {@link #setLowItemTransform(Animator)} transforms items that are left or
7165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * up to scroll center position; {@link #setHighItemTransform(Animator)} transforms items that are
7265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * right or down to the scroll center position. It's recommended to use xml resource ref
7365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * "highItemTransform" and "lowItemTransform" attributes to load the animation from xml. The
7465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * animation duration which android by default is a duration of milliseconds is interpreted as dip
7565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * to the center. Here is an example that scales the center item to "1.2" of original size, any item
7665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * far from 60dip to scroll center has normal scale (scale = 1):
7765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * <pre>{@code
7865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * <set xmlns:android="http://schemas.android.com/apk/res/android" >
7965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *   <objectAnimator
8065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *       android:startOffset="0"
8165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *       android:duration="60"
8265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *       android:valueFrom="1.2"
8365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *       android:valueTo="1"
8465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *       android:valueType="floatType"
8565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *       android:propertyName="scaleX" />
8665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *   <objectAnimator
8765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *       android:startOffset="0"
8865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *       android:duration="60"
8965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *       android:valueFrom="1.2"
9065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *       android:valueTo="1"
9165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *       android:valueType="floatType"
9265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *       android:propertyName="scaleY"/>
9365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * </set>
9465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * } </pre>
9565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * When using an animation that expands the selected item room has to be made in the view for
9665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * the scale animation. To accomplish this set right/left and/or top/bottom padding values
9765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * for the ScrollAdapterView and also set its clipToPadding value to false. Another option is
9865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * to include padding in the item view itself.
9965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * <p>
10065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Expanded items animation uses "normal" animation: duration is duration. Use xml attribute
10165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * expandedItemInAnim and expandedItemOutAnim for animation. A best practice is specify startOffset
10265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * for expandedItemInAnim to avoid showing half loaded expanded items during a fast scroll of
10365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * expandable items.
10465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane */
10565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lanepublic final class ScrollAdapterView extends AdapterView<Adapter> {
10665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
10765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** Callback interface for changing state of selected item */
10865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static interface OnItemChangeListener {
10965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        /**
11065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane         * In contrast to standard onFocusChange, the event is fired only when scrolling stops
11165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane         * @param view the view focusing to
11265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane         * @param position index in ScrollAdapter
11365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane         * @param targetCenter final center position of view to the left edge of ScrollAdapterView
11465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane         */
11565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void onItemSelected(View view, int position, int targetCenter);
11665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
11765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
11865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
11965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Callback interface when there is scrolling happened, this function is called before
12065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * applying transformations ({@link ScrollAdapterTransform}).  This listener can be a
12165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * replacement of {@link ScrollAdapterTransform}.  The difference is that this listener
12265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * is called once when scroll position changes, {@link ScrollAdapterTransform} is called
12365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * on each child view.
12465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
12565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static interface OnScrollListener {
12665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        /**
12765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane         * @param view the view focusing to
12865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane         * @param position index in ScrollAdapter
12965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane         * @param mainPosition position in the main axis 0(inclusive) ~ 1(exclusive)
13065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane         * @param secondPosition position in the second axis 0(inclusive) ~ 1(exclusive)
13165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane         */
13265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void onScrolled(View view, int position, float mainPosition, float secondPosition);
13365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
13465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
13565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final String TAG = "ScrollAdapterView";
13665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
13765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final boolean DBG = false;
13865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final boolean DEBUG_FOCUS = false;
13965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
14065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final int MAX_RECYCLED_VIEWS = 10;
14165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final int MAX_RECYCLED_EXPANDED_VIEWS = 3;
14265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
14365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    // search range for stable id, see {@link #heuristicGetPersistentIndex()}
14465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final int SEARCH_ID_RANGE = 30;
14565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
14665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
14765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * {@link ScrollAdapterView} fills horizontally
14865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
14965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static final int HORIZONTAL = 0;
15065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
15165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
15265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * {@link ScrollAdapterView} fills vertically
15365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
15465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static final int VERTICAL = 1;
15565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
15665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** calculate number of items on second axis by "parentSize / childSize" */
15765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static final int GRID_SETTING_AUTO = 0;
15865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** single item on second axis (i.e. not a grid view) */
15965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static final int GRID_SETTING_SINGLE = 1;
16065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
16165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int mOrientation = HORIZONTAL;
16265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
16365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** saved measuredSpec to pass to child views */
16465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int mMeasuredSpec = -1;
16565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
16665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** the Adapter used to create views */
16765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private ScrollAdapter mAdapter;
16865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private ScrollAdapterCustomSize mAdapterCustomSize;
16965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private ScrollAdapterCustomAlign mAdapterCustomAlign;
17065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int mSelectedSize;
17165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
17265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    // flag that we have made initial selection during refreshing ScrollAdapterView
17365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private boolean mMadeInitialSelection = false;
17465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
17565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** allow animate expanded size change when Scroller is stopped */
17665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private boolean mAnimateLayoutChange = true;
17765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
17865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static class RecycledViews {
17965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        List<View>[] mViews;
1806e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler        final int mMaxRecycledViews;
18165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ScrollAdapterBase mAdapter;
18265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
18365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        RecycledViews(int max) {
18465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mMaxRecycledViews = max;
18565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
18665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
18765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        void updateAdapter(ScrollAdapterBase adapter) {
18865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (adapter != null) {
18965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int typeCount = adapter.getViewTypeCount();
19065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (mViews == null || typeCount != mViews.length) {
19165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mViews = new List[typeCount];
19265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    for (int i = 0; i < typeCount; i++) {
1936e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler                        mViews[i] = new ArrayList<>();
19465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
19565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
19665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
19765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mAdapter = adapter;
19865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
19965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
20065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        void recycleView(View child, int type) {
20165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mAdapter != null) {
20265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mAdapter.viewRemoved(child);
20365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
20465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mViews != null && type >=0 && type < mViews.length
20565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    && mViews[type].size() < mMaxRecycledViews) {
20665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mViews[type].add(child);
20765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
20865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
20965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
21065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View getView(int type) {
21165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mViews != null && type >= 0 && type < mViews.length) {
21265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                List<View> array = mViews[type];
21365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return array.size() > 0 ? array.remove(array.size() - 1) : null;
21465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
21565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return null;
21665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
21765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
21865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
2196e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler    private final RecycledViews mRecycleViews = new RecycledViews(MAX_RECYCLED_VIEWS);
22065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
2216e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler    private final RecycledViews mRecycleExpandedViews =
2226e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler            new RecycledViews(MAX_RECYCLED_EXPANDED_VIEWS);
22365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
22465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** exclusive index of view on the left */
22565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int mLeftIndex;
22665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** exclusive index of view on the right */
22765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int mRightIndex;
22865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
22965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** space between two items */
23065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int mSpace;
23165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int mSpaceLow;
23265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int mSpaceHigh;
23365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
23465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int mGridSetting = GRID_SETTING_SINGLE;
23565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** effective number of items on 2nd axis, calculated in {@link #onMeasure} */
23665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int mItemsOnOffAxis;
23765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
23865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** maintains the scroller information */
2396e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler    private final ScrollController mScroll;
24065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
2416e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler    private final ArrayList<OnItemChangeListener> mOnItemChangeListeners = new ArrayList<>();
2426e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler    private final ArrayList<OnScrollListener> mOnScrollListeners = new ArrayList<>();
24365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
24465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private final static boolean DEFAULT_NAVIGATE_OUT_ALLOWED = true;
24565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private final static boolean DEFAULT_NAVIGATE_OUT_OF_OFF_AXIS_ALLOWED = true;
24665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
24765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private final static boolean DEFAULT_NAVIGATE_IN_ANIMATION_ALLOWED = true;
24865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
24965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    final class ExpandableChildStates extends ViewsStateBundle {
25065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ExpandableChildStates() {
25165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            super(SAVE_NO_CHILD, 0);
25265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
25365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
25465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        protected void saveVisibleViewsUnchecked() {
25565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            for (int i = firstExpandableIndex(), last = lastExpandableIndex(); i < last; i++) {
25665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                saveViewUnchecked(getChildAt(i), getAdapterIndex(i));
25765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
25865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
25965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
26065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    final class ExpandedChildStates extends ViewsStateBundle {
26165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ExpandedChildStates() {
26265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            super(SAVE_LIMITED_CHILD, SAVE_LIMITED_CHILD_DEFAULT_VALUE);
26365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
26465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
26565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        protected void saveVisibleViewsUnchecked() {
26665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            for (int i = 0, size = mExpandedViews.size(); i < size; i++) {
26765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                ExpandedView v = mExpandedViews.get(i);
26865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                saveViewUnchecked(v.expandedView, v.index);
26965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
27065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
27165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
27265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
27365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static class ChildViewHolder {
2746e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler        final int mItemViewType;
27565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int mMaxSize; // max size in mainAxis of the same offaxis
27665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int mExtraSpaceLow; // extra space added before the view
27765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        float mLocationInParent; // temp variable used in animating expanded view size change
27865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        float mLocation; // temp variable used in animating expanded view size change
27965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int mScrollCenter; // cached scroll center
28065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
28165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ChildViewHolder(int t) {
28265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mItemViewType = t;
28365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
28465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
28565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
28665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
28765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * set in {@link #onRestoreInstanceState(Parcelable)} which triggers a re-layout
28865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * and ScrollAdapterView restores states in {@link #onLayout}
28965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
29065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private AdapterViewState mLoadingState;
29165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
29265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** saves all expandable child states */
29365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    final private ExpandableChildStates mExpandableChildStates = new ExpandableChildStates();
29465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
29565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** saves all expanded child states */
29665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    final private ExpandedChildStates mExpandedChildStates = new ExpandedChildStates();
29765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
29865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private ScrollAdapterTransform mItemTransform;
29965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
30065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** flag for data changed, {@link #onLayout} will cleaning the whole view */
30165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private boolean mDataSetChangedFlag;
30265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
30365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    // current selected view adapter index, this is the final position to scroll to
30465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int mSelectedIndex;
30565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
30665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static class ScrollInfo {
30765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int index;
30865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        long id;
30965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        float mainPos;
31065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        float secondPos;
31165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int viewLocation;
31265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ScrollInfo() {
31365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            clear();
31465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
31565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        boolean isValid() {
31665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return index >= 0;
31765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
31865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        void clear() {
31965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            index = -1;
32065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            id = INVALID_ROW_ID;
32165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
32265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        void copyFrom(ScrollInfo other) {
32365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            index = other.index;
32465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            id = other.id;
32565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mainPos = other.mainPos;
32665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            secondPos = other.secondPos;
32765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            viewLocation = other.viewLocation;
32865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
32965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
33065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
33165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    // positions that current scrolled to
33265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private final ScrollInfo mCurScroll = new ScrollInfo();
33365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int mItemSelected = -1;
33465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
33565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int mPendingSelection = -1;
33665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private float mPendingScrollPosition = 0f;
33765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
33865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private final ScrollInfo mScrollBeforeReset = new ScrollInfo();
33965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
34065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private boolean mScrollTaskRunning;
34165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
34265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private ScrollAdapterBase mExpandAdapter;
34365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
34465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** used for measuring the size of {@link ScrollAdapterView} */
34565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int mScrapWidth;
34665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int mScrapHeight;
34765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
34865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** Animator for showing expanded item */
34965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private Animator mExpandedItemInAnim = null;
35065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
35165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** Animator for hiding expanded item */
35265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private Animator mExpandedItemOutAnim = null;
35365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
35465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private boolean mNavigateOutOfOffAxisAllowed = DEFAULT_NAVIGATE_OUT_OF_OFF_AXIS_ALLOWED;
35565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private boolean mNavigateOutAllowed = DEFAULT_NAVIGATE_OUT_ALLOWED;
35665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
35765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private boolean mNavigateInAnimationAllowed = DEFAULT_NAVIGATE_IN_ANIMATION_ALLOWED;
35865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
35965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
36065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * internal structure maintaining status of expanded views
36165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
36265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    final class ExpandedView {
36365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private static final int ANIM_DURATION = 450;
36465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ExpandedView(View v, int i, int t) {
36565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            expandedView = v;
36665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            index = i;
36765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            viewType = t;
36865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
36965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
3706e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler        final int index; // "Adapter index" of the expandable view
3716e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler        final int viewType;
3726e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler        final View expandedView; // expanded view
37365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        float progress = 0f; // 0 ~ 1, indication if it's expanding or shrinking
37465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        Animator grow_anim;
37565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        Animator shrink_anim;
37665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
37765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        Animator createFadeInAnimator() {
37865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mExpandedItemInAnim == null) {
37965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                expandedView.setAlpha(0);
38065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                ObjectAnimator anim1 = ObjectAnimator.ofFloat(null, "alpha", 1);
38165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                anim1.setStartDelay(ANIM_DURATION / 2);
38265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                anim1.setDuration(ANIM_DURATION * 2);
38365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return anim1;
38465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
38565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return mExpandedItemInAnim.clone();
38665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
38765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
38865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
38965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        Animator createFadeOutAnimator() {
39065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mExpandedItemOutAnim == null) {
39165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                ObjectAnimator anim1 = ObjectAnimator.ofFloat(null, "alpha", 0);
39265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                anim1.setDuration(ANIM_DURATION);
39365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return anim1;
39465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
39565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return mExpandedItemOutAnim.clone();
39665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
39765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
39865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
39965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        void setProgress(float p) {
40065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            boolean growing = p > progress;
40165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            boolean shrinking = p < progress;
40265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            progress = p;
40365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (growing) {
40465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (shrink_anim != null) {
40565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    shrink_anim.cancel();
40665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    shrink_anim = null;
40765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
40865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (grow_anim == null) {
40965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    grow_anim = createFadeInAnimator();
41065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    grow_anim.setTarget(expandedView);
41165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    grow_anim.start();
41265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
41365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (!mAnimateLayoutChange) {
41465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    grow_anim.end();
41565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
41665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else if (shrinking) {
41765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (grow_anim != null) {
41865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    grow_anim.cancel();
41965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    grow_anim = null;
42065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
42165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (shrink_anim == null) {
42265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    shrink_anim = createFadeOutAnimator();
42365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    shrink_anim.setTarget(expandedView);
42465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    shrink_anim.start();
42565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
42665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (!mAnimateLayoutChange) {
42765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    shrink_anim.end();
42865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
42965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
43065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
43165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
43265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        void close() {
43365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (shrink_anim != null) {
43465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                shrink_anim.cancel();
43565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                shrink_anim = null;
43665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
43765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (grow_anim != null) {
43865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                grow_anim.cancel();
43965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                grow_anim = null;
44065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
44165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
44265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
44365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
44465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** list of ExpandedView structure */
4456e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler    private final ArrayList<ExpandedView> mExpandedViews = new ArrayList<>(4);
44665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
44765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** no scrolling */
44865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final int NO_SCROLL = 0;
44965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** scrolling and centering a known focused view */
45065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final int SCROLL_AND_CENTER_FOCUS = 3;
45165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
45265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
45365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * internal state machine for scrolling, typical scenario: <br>
45465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * DPAD up/down is pressed: -> {@link #SCROLL_AND_CENTER_FOCUS} -> {@link #NO_SCROLL} <br>
45565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
45665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int mScrollerState;
45765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
4586e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler    final Rect mTempRect = new Rect(); // temp variable used in UI thread
45965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
46065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    // Controls whether or not sounds should be played when scrolling/clicking
46165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private boolean mPlaySoundEffects = true;
46265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
46365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public ScrollAdapterView(Context context, AttributeSet attrs) {
46465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        super(context, attrs);
46565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScroll = new ScrollController(getContext());
46665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setChildrenDrawingOrderEnabled(true);
46765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setSoundEffectsEnabled(true);
46865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setWillNotDraw(true);
46965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        initFromAttributes(context, attrs);
47065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        reset();
47165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
47265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
47365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void initFromAttributes(Context context, AttributeSet attrs) {
47465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ScrollAdapterView);
47565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
47665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setOrientation(a.getInt(R.styleable.ScrollAdapterView_orientation, HORIZONTAL));
47765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
47865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScroll.setScrollItemAlign(a.getInt(R.styleable.ScrollAdapterView_scrollItemAlign,
47965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                ScrollController.SCROLL_ITEM_ALIGN_CENTER));
48065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
48165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setGridSetting(a.getInt(R.styleable.ScrollAdapterView_gridSetting, 1));
48265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
48365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (a.hasValue(R.styleable.ScrollAdapterView_lowItemTransform)) {
48465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            setLowItemTransform(AnimatorInflater.loadAnimator(getContext(),
48565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    a.getResourceId(R.styleable.ScrollAdapterView_lowItemTransform, -1)));
48665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
48765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
48865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (a.hasValue(R.styleable.ScrollAdapterView_highItemTransform)) {
48965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            setHighItemTransform(AnimatorInflater.loadAnimator(getContext(),
49065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    a.getResourceId(R.styleable.ScrollAdapterView_highItemTransform, -1)));
49165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
49265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
49365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (a.hasValue(R.styleable.ScrollAdapterView_expandedItemInAnim)) {
49465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mExpandedItemInAnim = AnimatorInflater.loadAnimator(getContext(),
49565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    a.getResourceId(R.styleable.ScrollAdapterView_expandedItemInAnim, -1));
49665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
49765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
49865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (a.hasValue(R.styleable.ScrollAdapterView_expandedItemOutAnim)) {
49965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mExpandedItemOutAnim = AnimatorInflater.loadAnimator(getContext(),
50065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    a.getResourceId(R.styleable.ScrollAdapterView_expandedItemOutAnim, -1));
50165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
50265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
50365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setSpace(a.getDimensionPixelSize(R.styleable.ScrollAdapterView_space, 0));
50465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
50565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setSelectedTakesMoreSpace(a.getBoolean(
50665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                R.styleable.ScrollAdapterView_selectedTakesMoreSpace, false));
50765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
50865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setSelectedSize(a.getDimensionPixelSize(
50965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                R.styleable.ScrollAdapterView_selectedSize, 0));
51065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
51165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setScrollCenterStrategy(a.getInt(R.styleable.ScrollAdapterView_scrollCenterStrategy, 0));
51265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
51365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setScrollCenterOffset(a.getDimensionPixelSize(
51465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                R.styleable.ScrollAdapterView_scrollCenterOffset, 0));
51565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
51665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setScrollCenterOffsetPercent(a.getInt(
51765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                R.styleable.ScrollAdapterView_scrollCenterOffsetPercent, 0));
51865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
51965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setNavigateOutAllowed(a.getBoolean(
52065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                R.styleable.ScrollAdapterView_navigateOutAllowed, DEFAULT_NAVIGATE_OUT_ALLOWED));
52165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
52265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setNavigateOutOfOffAxisAllowed(a.getBoolean(
52365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                R.styleable.ScrollAdapterView_navigateOutOfOffAxisAllowed,
52465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                DEFAULT_NAVIGATE_OUT_OF_OFF_AXIS_ALLOWED));
52565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
52665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setNavigateInAnimationAllowed(a.getBoolean(
52765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                R.styleable.ScrollAdapterView_navigateInAnimationAllowed,
52865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                DEFAULT_NAVIGATE_IN_ANIMATION_ALLOWED));
52965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
53065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScroll.lerper().setDivisor(a.getFloat(
53165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                R.styleable.ScrollAdapterView_lerperDivisor, Lerper.DEFAULT_DIVISOR));
53265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
53365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        a.recycle();
53465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
53565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
53665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setOrientation(int orientation) {
53765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mOrientation = orientation;
53865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScroll.setOrientation(orientation);
53965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
54065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
54165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public int getOrientation() {
54265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mOrientation;
54365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
54465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
54565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @SuppressWarnings("unchecked")
54665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void reset() {
54765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScrollBeforeReset.copyFrom(mCurScroll);
54865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mLeftIndex = -1;
54965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mRightIndex = 0;
55065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mDataSetChangedFlag = false;
55165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (int i = 0, c = mExpandedViews.size(); i < c; i++) {
55265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            ExpandedView v = mExpandedViews.get(i);
55365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            v.close();
55465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            removeViewInLayout(v.expandedView);
55565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mRecycleExpandedViews.recycleView(v.expandedView, v.viewType);
55665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
55765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mExpandedViews.clear();
55865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (int i = getChildCount() - 1; i >= 0; i--) {
55965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View child = getChildAt(i);
56065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            removeViewInLayout(child);
56165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            recycleExpandableView(child);
56265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
56365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mRecycleViews.updateAdapter(mAdapter);
56465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mRecycleExpandedViews.updateAdapter(mExpandAdapter);
56565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mSelectedIndex = -1;
56665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mCurScroll.clear();
56765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mMadeInitialSelection = false;
56865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
56965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
57065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** find the view that containing scrollCenter or the next view */
57165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int findViewIndexContainingScrollCenter(int scrollCenter, int scrollCenterOffAxis,
57265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            boolean findNext) {
57365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        final int lastExpandable = lastExpandableIndex();
57465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (int i = firstExpandableIndex(); i < lastExpandable; i ++) {
57565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View view = getChildAt(i);
57665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int centerOffAxis = getCenterInOffAxis(view);
57765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int viewSizeOffAxis;
57865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mOrientation == HORIZONTAL) {
57965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                viewSizeOffAxis = view.getHeight();
58065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
58165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                viewSizeOffAxis = view.getWidth();
58265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
58365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int centerMain = getScrollCenter(view);
58465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (hasScrollPosition(centerMain, getSize(view), scrollCenter)
58565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    && (mItemsOnOffAxis == 1 ||  hasScrollPositionSecondAxis(
58665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            scrollCenterOffAxis, viewSizeOffAxis, centerOffAxis))) {
58765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (findNext) {
58865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (mScroll.isMainAxisMovingForward() && centerMain < scrollCenter) {
58965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        if (i + mItemsOnOffAxis < lastExpandableIndex()) {
59065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            i = i + mItemsOnOffAxis;
59165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        }
59265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    } else if (!mScroll.isMainAxisMovingForward() && centerMain > scrollCenter) {
59365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        if (i - mItemsOnOffAxis >= firstExpandableIndex()) {
59465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            i = i - mItemsOnOffAxis;
59565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        }
59665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
59765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (mItemsOnOffAxis == 1) {
59865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        // don't look in second axis if it's not grid
59965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    } else if (mScroll.isSecondAxisMovingForward() &&
60065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            centerOffAxis < scrollCenterOffAxis) {
60165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        if (i + 1 < lastExpandableIndex()) {
60265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            i += 1;
60365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        }
60465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    } else if (!mScroll.isSecondAxisMovingForward() &&
60565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            centerOffAxis < scrollCenterOffAxis) {
60665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        if (i - 1 >= firstExpandableIndex()) {
60765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            i -= 1;
60865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        }
60965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
61065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
61165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return i;
61265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
61365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
61465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return -1;
61565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
61665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
61765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int findViewIndexContainingScrollCenter() {
61865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return findViewIndexContainingScrollCenter(mScroll.mainAxis().getScrollCenter(),
61965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mScroll.secondAxis().getScrollCenter(), false);
62065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
62165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
62265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
62365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public int getFirstVisiblePosition() {
62465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int first = firstExpandableIndex();
62565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return lastExpandableIndex() == first ? -1 : getAdapterIndex(first);
62665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
62765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
62865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
62965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public int getLastVisiblePosition() {
63065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int last = lastExpandableIndex();
63165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return firstExpandableIndex() == last ? -1 : getAdapterIndex(last - 1);
63265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
63365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
63465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
63565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setSelection(int position) {
63665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setSelectionInternal(position, 0f, true);
63765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
63865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
63965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setSelection(int position, float offset) {
64065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setSelectionInternal(position, offset, true);
64165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
64265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
64365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public int getCurrentAnimationDuration() {
64465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mScroll.getCurrentAnimationDuration();
64565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
64665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
64765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setSelectionSmooth(int index) {
64865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setSelectionSmooth(index, 0);
64965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
65065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
65165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** set selection using animation with a given duration, use 0 duration for auto  */
65265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setSelectionSmooth(int index, int duration) {
65365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int currentExpandableIndex = indexOfChild(getSelectedView());
65465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (currentExpandableIndex < 0) {
65565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
65665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
65765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int adapterIndex = getAdapterIndex(currentExpandableIndex);
65865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (index == adapterIndex) {
65965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
66065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
66165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        boolean isGrowing = index > adapterIndex;
66265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View nextTop = null;
66365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (isGrowing) {
66465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            do {
66565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (index < getAdapterIndex(lastExpandableIndex())) {
66665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    nextTop = getChildAt(expandableIndexFromAdapterIndex(index));
66765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    break;
66865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
66965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } while (fillOneRightChildView(false));
67065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
67165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            do {
67265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (index >= getAdapterIndex(firstExpandableIndex())) {
67365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    nextTop = getChildAt(expandableIndexFromAdapterIndex(index));
67465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    break;
67565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
67665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } while (fillOneLeftChildView(false));
67765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
67865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (nextTop == null) {
67965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
68065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
68165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int direction = isGrowing ?
68265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                (mOrientation == HORIZONTAL ? View.FOCUS_RIGHT : View.FOCUS_DOWN) :
68365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                (mOrientation == HORIZONTAL ? View.FOCUS_LEFT : View.FOCUS_UP);
68465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        scrollAndFocusTo(nextTop, direction, false, duration, false);
68565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
68665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
68765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void fireDataSetChanged() {
68865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // set flag and trigger a scroll task
68965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mDataSetChangedFlag = true;
69065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        scheduleScrollTask();
69165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
69265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
6936e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler    private final DataSetObserver mDataObserver = new DataSetObserver() {
69465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
69565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
69665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void onChanged() {
69765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            fireDataSetChanged();
69865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
69965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
70065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
70165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void onInvalidated() {
70265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            fireDataSetChanged();
70365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
70465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
70565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    };
70665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
70765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
70865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public Adapter getAdapter() {
70965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mAdapter;
71065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
71165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
71265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
71365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Adapter must be an implementation of {@link ScrollAdapter}.
71465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
71565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
71665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setAdapter(Adapter adapter) {
71765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mAdapter != null) {
71865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mAdapter.unregisterDataSetObserver(mDataObserver);
71965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
72065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mAdapter = (ScrollAdapter) adapter;
72165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mExpandAdapter = mAdapter.getExpandAdapter();
72265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mAdapter.registerDataSetObserver(mDataObserver);
72365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mAdapterCustomSize = adapter instanceof ScrollAdapterCustomSize ?
72465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                (ScrollAdapterCustomSize) adapter : null;
72565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mAdapterCustomAlign = adapter instanceof ScrollAdapterCustomAlign ?
72665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                (ScrollAdapterCustomAlign) adapter : null;
72765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mMeasuredSpec = -1;
72865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mLoadingState = null;
72965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mPendingSelection = -1;
73065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mExpandableChildStates.clear();
73165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mExpandedChildStates.clear();
73265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mCurScroll.clear();
73365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScrollBeforeReset.clear();
73465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        fireDataSetChanged();
73565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
73665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
73765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
73865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public View getSelectedView() {
73965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mSelectedIndex >= 0 ?
74065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                getChildAt(expandableIndexFromAdapterIndex(mSelectedIndex)) : null;
74165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
74265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
74365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public View getSelectedExpandedView() {
74465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ExpandedView ev = findExpandedView(mExpandedViews, getSelectedItemPosition());
74565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return ev == null ? null : ev.expandedView;
74665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
74765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
74865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public View getViewContainingScrollCenter() {
74965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return getChildAt(findViewIndexContainingScrollCenter());
75065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
75165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
75265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public int getIndexContainingScrollCenter() {
75365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return getAdapterIndex(findViewIndexContainingScrollCenter());
75465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
75565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
75665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
75765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public int getSelectedItemPosition() {
75865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mSelectedIndex;
75965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
76065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
76165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
76265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public Object getSelectedItem() {
76365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int index = getSelectedItemPosition();
76465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (index < 0) return null;
76565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return getAdapter().getItem(index);
76665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
76765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
76865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
76965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public long getSelectedItemId() {
77065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mAdapter != null) {
77165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int index = getSelectedItemPosition();
77265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (index < 0) return INVALID_ROW_ID;
77365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return mAdapter.getItemId(index);
77465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
77565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return INVALID_ROW_ID;
77665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
77765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
77865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public View getItemView(int position) {
77965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int index = expandableIndexFromAdapterIndex(position);
78065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (index >= firstExpandableIndex() && index < lastExpandableIndex()) {
78165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return getChildAt(index);
78265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
78365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return null;
78465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
78565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
78665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
78765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * set system scroll position from our scroll position,
78865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
78965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void adjustSystemScrollPos() {
79065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        scrollTo(mScroll.horizontal.getSystemScrollPos(), mScroll.vertical.getSystemScrollPos());
79165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
79265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
79365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
79465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
79565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScroll.horizontal.setSize(w);
79665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScroll.vertical.setSize(h);
79765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        scheduleScrollTask();
79865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
79965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
80065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
80165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * called from onLayout() to adjust all children's transformation based on how far they are from
80265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * {@link ScrollController.Axis#getScrollCenter()}
80365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
80465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void applyTransformations() {
80565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mItemTransform == null) {
80665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
80765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
80865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int lastExpandable = lastExpandableIndex();
80965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (int i = firstExpandableIndex(); i < lastExpandable; i++) {
81065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View child = getChildAt(i);
81165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mItemTransform.transform(child, getScrollCenter(child)
81265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    - mScroll.mainAxis().getScrollCenter(), mItemsOnOffAxis == 1 ? 0
81365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    : getCenterInOffAxis(child) - mScroll.secondAxis().getScrollCenter());
81465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
81565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
81665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
81765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
81865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
81965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        super.onLayout(changed, left, top, right, bottom);
82065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        updateViewsLocations(true);
82165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
82265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
82365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void scheduleScrollTask() {
82465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (!mScrollTaskRunning) {
82565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mScrollTaskRunning = true;
82665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            postOnAnimation(mScrollTask);
82765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
82865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
82965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
8306e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler    final Runnable mScrollTask = new Runnable() {
83165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
83265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void run() {
83365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            try {
83465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                scrollTaskRunInternal();
83565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } catch (RuntimeException ex) {
83665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                reset();
8376e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler                ex.printStackTrace();
83865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
83965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
84065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    };
84165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
84265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void scrollTaskRunInternal() {
84365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScrollTaskRunning = false;
84465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // 1. adjust mScrollController and system Scroll position
84565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mDataSetChangedFlag) {
84665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            reset();
84765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
84865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mAdapter == null || mAdapter.getCount() == 0) {
84965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            invalidate();
85065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mAdapter != null) {
85165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                fireItemChange();
85265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
85365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
85465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
85565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mMeasuredSpec == -1) {
85665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // not layout yet
85765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            requestLayout();
85865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            scheduleScrollTask();
85965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
86065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
86165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        restoreLoadingState();
86265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScroll.computeAndSetScrollPosition();
86365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
86465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        boolean noChildBeforeFill = getChildCount() == 0;
86565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
86665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (!noChildBeforeFill) {
86765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            updateViewsLocations(false);
86865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            adjustSystemScrollPos();
86965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
87065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
87165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // 2. prune views that scroll out of visible area
87265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        pruneInvisibleViewsInLayout();
87365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
87465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // 3. fill views in blank area
87565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        fillVisibleViewsInLayout();
87665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
87765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (noChildBeforeFill && getChildCount() > 0) {
87865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // if this is the first time add child(ren), we will get the initial value of
8796e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler            // mScrollCenter after fillVisibleViewsInLayout(), and we need initialize the system
88065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // scroll position
88165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            updateViewsLocations(false);
88265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            adjustSystemScrollPos();
88365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
88465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
88565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // 4. perform scroll position based animation
88665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        fireScrollChange();
88765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        applyTransformations();
88865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
88965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // 5. trigger another layout until the scroll stops
89065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (!mScroll.isFinished()) {
89165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            scheduleScrollTask();
89265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
89365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // force ScrollAdapterView to reorder child order and call getChildDrawingOrder()
89465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            invalidate();
89565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            fireItemChange();
89665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
89765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
89865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
89965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
90065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void requestChildFocus(View child, View focused) {
90165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        boolean receiveFocus = getFocusedChild() == null && child != null;
90265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        super.requestChildFocus(child, focused);
90365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (receiveFocus && mScroll.isFinished()) {
90465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // schedule {@link #updateViewsLocations()} for focus transition into expanded view
90565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            scheduleScrollTask();
90665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
90765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
90865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
90965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void recycleExpandableView(View child) {
91065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ChildViewHolder holder = ((ChildViewHolder)child.getTag(R.id.ScrollAdapterViewChild));
91165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (holder != null) {
91265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mRecycleViews.recycleView(child, holder.mItemViewType);
91365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
91465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
91565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
91665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void pruneInvisibleViewsInLayout() {
91765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View selectedView = getSelectedView();
91865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mScroll.isFinished() || mScroll.isMainAxisMovingForward()) {
91965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            while (true) {
92065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int firstIndex = firstExpandableIndex();
92165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                View child = getChildAt(firstIndex);
92265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (child == selectedView) {
92365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    break;
92465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
92565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                View nextChild = getChildAt(firstIndex + mItemsOnOffAxis);
92665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (nextChild == null) {
92765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    break;
92865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
92965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (mOrientation == HORIZONTAL) {
93065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (child.getRight() - getScrollX() > 0) {
93165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        // don't prune the first view if it's visible
93265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        break;
93365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
93465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                } else {
93565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    // VERTICAL is symmetric to HORIZONTAL, see comments above
93665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (child.getBottom() - getScrollY() > 0) {
93765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        break;
93865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
93965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
94065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                boolean foundFocus = false;
94165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                for (int i = 0; i < mItemsOnOffAxis; i++){
94265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    int childIndex = firstIndex + i;
94365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (childHasFocus(childIndex)) {
94465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        foundFocus = true;
94565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        break;
94665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
94765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
94865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (foundFocus) {
94965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    break;
95065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
95165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                for (int i = 0; i < mItemsOnOffAxis; i++){
95265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    child = getChildAt(firstExpandableIndex());
95365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mExpandableChildStates.saveInvisibleView(child, mLeftIndex + 1);
95465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    removeViewInLayout(child);
95565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    recycleExpandableView(child);
95665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mLeftIndex++;
95765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
95865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
95965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
96065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mScroll.isFinished() || !mScroll.isMainAxisMovingForward()) {
96165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            while (true) {
96265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int count = mRightIndex % mItemsOnOffAxis;
96365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (count == 0) {
96465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    count = mItemsOnOffAxis;
96565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
96665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (count > mRightIndex - mLeftIndex - 1) {
96765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    break;
96865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
96965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int lastIndex = lastExpandableIndex();
97065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                View child = getChildAt(lastIndex - 1);
97165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (child == selectedView) {
97265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    break;
97365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
97465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (mOrientation == HORIZONTAL) {
97565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (child.getLeft() - getScrollX() < getWidth()) {
97665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        // don't prune the last view if it's visible
97765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        break;
97865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
97965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                } else {
98065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    // VERTICAL is symmetric to HORIZONTAL, see comments above
98165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (child.getTop() - getScrollY() < getHeight()) {
98265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        break;
98365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
98465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
98565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                boolean foundFocus = false;
98665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                for (int i = 0; i < count; i++){
98765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    int childIndex = lastIndex - 1 - i;
98865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (childHasFocus(childIndex)) {
98965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        foundFocus = true;
99065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        break;
99165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
99265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
99365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (foundFocus) {
99465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    break;
99565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
99665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                for (int i = 0; i < count; i++){
99765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    child = getChildAt(lastExpandableIndex() - 1);
99865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mExpandableChildStates.saveInvisibleView(child, mRightIndex - 1);
99965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    removeViewInLayout(child);
100065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    recycleExpandableView(child);
100165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mRightIndex--;
100265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
100365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
100465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
100565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
100665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
100765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** check if expandable view or related expanded view has focus */
100865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private boolean childHasFocus(int expandableViewIndex) {
100965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View child = getChildAt(expandableViewIndex);
101065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (child.hasFocus()) {
101165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return true;
101265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
101365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ExpandedView v = findExpandedView(mExpandedViews, getAdapterIndex(expandableViewIndex));
101465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (v != null && v.expandedView.hasFocus()) {
101565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return true;
101665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
101765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return false;
101865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
101965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
102065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
102165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * @param gridSetting <br>
102265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * {@link #GRID_SETTING_SINGLE}: single item on second axis, i.e. not a grid view <br>
102365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * {@link #GRID_SETTING_AUTO}: auto calculate number of items on second axis <br>
102465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * >1: shown as a grid view, with given fixed number of items on second axis <br>
102565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
102665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setGridSetting(int gridSetting) {
102765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mGridSetting = gridSetting;
102865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        requestLayout();
102965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
103065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
103165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public int getGridSetting() {
103265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mGridSetting;
103365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
103465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
103565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void fillVisibleViewsInLayout() {
103665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        while (fillOneRightChildView(true)) {
103765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
103865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        while (fillOneLeftChildView(true)) {
103965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
104065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mRightIndex >= 0 && mLeftIndex == -1) {
104165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // first child available
104265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View child = getChildAt(firstExpandableIndex());
104365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int scrollCenter = getScrollCenter(child);
104465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mScroll.mainAxis().updateScrollMin(scrollCenter, getScrollLow(scrollCenter, child));
104565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
104665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mScroll.mainAxis().invalidateScrollMin();
104765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
104865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mRightIndex == mAdapter.getCount()) {
104965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // last child available
105065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View child = getChildAt(lastExpandableIndex() - 1);
105165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int scrollCenter = getScrollCenter(child);
105265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mScroll.mainAxis().updateScrollMax(scrollCenter, getScrollHigh(scrollCenter, child));
105365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
105465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mScroll.mainAxis().invalidateScrollMax();
105565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
105665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
105765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
105865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
105965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * try to add one left/top child view, returning false tells caller can stop loop
106065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
106165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private boolean fillOneLeftChildView(boolean stopOnInvisible) {
106265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // 1. check if we still need add view
106365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mLeftIndex < 0) {
106465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return false;
106565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
106665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int left = Integer.MAX_VALUE;
106765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int top = Integer.MAX_VALUE;
106865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (lastExpandableIndex() - firstExpandableIndex() > 0) {
106965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int childIndex = firstExpandableIndex();
107065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int last = Math.min(lastExpandableIndex(), childIndex + mItemsOnOffAxis);
107165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            for (int i = childIndex; i < last; i++) {
107265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                View v = getChildAt(i);
107365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (mOrientation == HORIZONTAL) {
107465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (v.getLeft() < left) {
107565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        left = v.getLeft();
107665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
107765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                } else {
107865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (v.getTop() < top) {
107965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        top = v.getTop();
108065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
108165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
108265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
108365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            boolean itemInvisible;
108465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mOrientation == HORIZONTAL) {
108565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                left -= mSpace;
108665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                itemInvisible = left - getScrollX() <= 0;
108765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                top = getPaddingTop();
108865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
108965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                top -= mSpace;
109065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                itemInvisible = top - getScrollY() <= 0;
109165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                left = getPaddingLeft();
109265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
109365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (itemInvisible && stopOnInvisible) {
109465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return false;
109565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
109665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
109765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return false;
109865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
109965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // 2. create view and layout
110065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return fillOneAxis(left, top, false, true);
110165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
110265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
110365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private View addAndMeasureExpandableView(int adapterIndex, int insertIndex) {
110465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int type = mAdapter.getItemViewType(adapterIndex);
110565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View recycleView = mRecycleViews.getView(type);
110665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View child = mAdapter.getView(adapterIndex, recycleView, this);
110765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (child == null) {
110865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return null;
110965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
111065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        child.setTag(R.id.ScrollAdapterViewChild, new ChildViewHolder(type));
111165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        addViewInLayout(child, insertIndex, child.getLayoutParams(), true);
111265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        measureChild(child);
111365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return child;
111465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
111565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
111665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void measureScrapChild(View child, int widthMeasureSpec, int heightMeasureSpec) {
111765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        LayoutParams p = child.getLayoutParams();
111865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (p == null) {
111965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            p = generateDefaultLayoutParams();
112065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            child.setLayoutParams(p);
112165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
112265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
112365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int childWidthSpec, childHeightSpec;
112465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mOrientation == VERTICAL) {
112565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec, 0, p.width);
112665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int lpHeight = p.height;
112765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (lpHeight > 0) {
112865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
112965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
113065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
113165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
113265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
113365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            childHeightSpec = ViewGroup.getChildMeasureSpec(heightMeasureSpec, 0, p.height);
113465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int lpWidth = p.width;
113565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (lpWidth > 0) {
113665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                childWidthSpec = MeasureSpec.makeMeasureSpec(lpWidth, MeasureSpec.EXACTLY);
113765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
113865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                childWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
113965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
114065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
114165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        child.measure(childWidthSpec, childHeightSpec);
114265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
114365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
114465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
114565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
114665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mAdapter == null) {
114765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            Log.e(TAG, "onMeasure: Adapter not available ");
114865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
114965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
115065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
115165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScroll.horizontal.setPadding(getPaddingLeft(), getPaddingRight());
115265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScroll.vertical.setPadding(getPaddingTop(), getPaddingBottom());
115365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
115465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
115565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
115665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
115765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
115865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int clientWidthSize = widthSize - getPaddingLeft() - getPaddingRight();
115965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int clientHeightSize = heightSize - getPaddingTop() - getPaddingBottom();
116065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
116165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mMeasuredSpec == -1) {
116265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View scrapView = mAdapter.getScrapView(this);
116365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            measureScrapChild(scrapView, MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
116465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mScrapWidth = scrapView.getMeasuredWidth();
116565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mScrapHeight = scrapView.getMeasuredHeight();
116665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
116765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
116865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mItemsOnOffAxis = mGridSetting > 0 ? mGridSetting
116965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            : mOrientation == HORIZONTAL ?
117065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                (heightMode == MeasureSpec.UNSPECIFIED ? 1 : clientHeightSize / mScrapHeight)
117165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                : (widthMode == MeasureSpec.UNSPECIFIED ? 1 : clientWidthSize / mScrapWidth);
117265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mItemsOnOffAxis == 0) {
117365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mItemsOnOffAxis = 1;
117465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
117565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
117665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mLoadingState != null && mItemsOnOffAxis != mLoadingState.itemsOnOffAxis) {
117765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mLoadingState = null;
117865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
117965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
118065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // see table below "height handling"
118165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (widthMode == MeasureSpec.UNSPECIFIED ||
118265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                (widthMode == MeasureSpec.AT_MOST && mOrientation == VERTICAL)) {
118365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int size = mOrientation == VERTICAL ? mScrapWidth * mItemsOnOffAxis
118465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    + mSpace * (mItemsOnOffAxis - 1) : mScrapWidth;
118565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            size += getPaddingLeft() + getPaddingRight();
118665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            widthSize = widthMode == MeasureSpec.AT_MOST ? Math.min(size, widthSize) : size;
118765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
118865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // table of height handling
118965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // heightMode:   UNSPECIFIED              AT_MOST                              EXACTLY
11906e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler        // HORIZONTAL    items*childHeight        min(items * childHeight, height)     height
119165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // VERTICAL      childHeight              height                               height
119265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (heightMode == MeasureSpec.UNSPECIFIED ||
119365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                (heightMode == MeasureSpec.AT_MOST && mOrientation == HORIZONTAL)) {
119465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int size = mOrientation == HORIZONTAL ?
119565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mScrapHeight * mItemsOnOffAxis + mSpace * (mItemsOnOffAxis - 1) : mScrapHeight;
119665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            size += getPaddingTop() + getPaddingBottom();
119765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            heightSize = heightMode == MeasureSpec.AT_MOST ? Math.min(size, heightSize) : size;
119865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
119965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mMeasuredSpec = mOrientation == HORIZONTAL ? heightMeasureSpec : widthMeasureSpec;
120065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
120165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setMeasuredDimension(widthSize, heightSize);
120265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
120365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // we allow scroll from padding low to padding high in the second axis
120465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int scrollMin = mScroll.secondAxis().getPaddingLow();
120565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int scrollMax = (mOrientation == HORIZONTAL ? heightSize : widthSize) -
120665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mScroll.secondAxis().getPaddingHigh();
120765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScroll.secondAxis().updateScrollMin(scrollMin, scrollMin);
120865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScroll.secondAxis().updateScrollMax(scrollMax, scrollMax);
120965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
121065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (int j = 0, size = mExpandedViews.size(); j < size; j++) {
121165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            ExpandedView v = mExpandedViews.get(j);
121265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            measureChild(v.expandedView);
121365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
121465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
121565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (int i = firstExpandableIndex(); i < lastExpandableIndex(); i++) {
121665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View v = getChildAt(i);
121765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (v.isLayoutRequested()) {
121865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                measureChild(v);
121965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
122065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
122165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
122265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
122365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
122465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * override to draw from two sides, center item is draw at last
122565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
122665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
122765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    protected int getChildDrawingOrder(int childCount, int i) {
122865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int focusIndex = mSelectedIndex < 0 ? -1 :
122965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                expandableIndexFromAdapterIndex(mSelectedIndex);
123065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (focusIndex < 0) {
123165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return i;
123265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
12336e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler        // supposedly 0 1 2 3 4 5 6 7 8 9, 4 is the center item
123465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // drawing order is 0 1 2 3 9 8 7 6 5 4
123565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (i < focusIndex) {
123665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return i;
123765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else if (i < childCount - 1) {
123865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return focusIndex + childCount - 1 - i;
123965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
124065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return focusIndex;
124165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
124265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
124365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
124465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
124565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * fill one off-axis views, the left/top of main axis will be interpreted as right/bottom if
124665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * leftToRight is false
124765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
124865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private boolean fillOneAxis(int left, int top, boolean leftToRight, boolean setInitialPos) {
124965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // 2. create view and layout
125065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int viewIndex = lastExpandableIndex();
125165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int itemsToAdd = leftToRight ? Math.min(mItemsOnOffAxis, mAdapter.getCount() - mRightIndex)
125265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                : mItemsOnOffAxis;
125365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int maxSize = 0;
125465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int maxSelectedSize = 0;
125565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (int i = 0; i < itemsToAdd; i++) {
125665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View child = leftToRight ? addAndMeasureExpandableView(mRightIndex + i, -1) :
125765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                addAndMeasureExpandableView(mLeftIndex - i, firstExpandableIndex());
125865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (child == null) {
125965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return false;
126065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
126165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            maxSize = Math.max(maxSize, mOrientation == HORIZONTAL ? child.getMeasuredWidth() :
126265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    child.getMeasuredHeight());
126365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            maxSelectedSize = Math.max(
126465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    maxSelectedSize, getSelectedItemSize(mLeftIndex - i, child));
126565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
126665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (!leftToRight) {
126765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            viewIndex = firstExpandableIndex();
126865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mOrientation == HORIZONTAL) {
126965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                left = left - maxSize;
127065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
127165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                top = top - maxSize;
127265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
127365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
127465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (int i = 0; i < itemsToAdd; i++) {
127565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View child = getChildAt(viewIndex + i);
127665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            ChildViewHolder h = (ChildViewHolder) child.getTag(R.id.ScrollAdapterViewChild);
127765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            h.mMaxSize = maxSize;
127865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mOrientation == HORIZONTAL) {
127965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                switch (mScroll.getScrollItemAlign()) {
128065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                case ScrollController.SCROLL_ITEM_ALIGN_CENTER:
128165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    child.layout(left + maxSize / 2 - child.getMeasuredWidth() / 2, top,
128265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            left + maxSize / 2 + child.getMeasuredWidth() / 2,
128365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            top + child.getMeasuredHeight());
128465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    break;
128565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                case ScrollController.SCROLL_ITEM_ALIGN_LOW:
128665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    child.layout(left, top, left + child.getMeasuredWidth(),
128765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            top + child.getMeasuredHeight());
128865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    break;
128965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                case ScrollController.SCROLL_ITEM_ALIGN_HIGH:
129065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    child.layout(left + maxSize - child.getMeasuredWidth(), top, left + maxSize,
129165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            top + child.getMeasuredHeight());
129265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    break;
129365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
129465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                top += child.getMeasuredHeight();
129565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                top += mSpace;
129665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
129765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                switch (mScroll.getScrollItemAlign()) {
129865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                case ScrollController.SCROLL_ITEM_ALIGN_CENTER:
129965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    child.layout(left, top + maxSize / 2 - child.getMeasuredHeight() / 2,
130065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            left + child.getMeasuredWidth(),
130165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            top + maxSize / 2 + child.getMeasuredHeight() / 2);
130265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    break;
130365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                case ScrollController.SCROLL_ITEM_ALIGN_LOW:
130465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    child.layout(left, top, left + child.getMeasuredWidth(),
130565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            top + child.getMeasuredHeight());
130665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    break;
130765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                case ScrollController.SCROLL_ITEM_ALIGN_HIGH:
130865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    child.layout(left, top + maxSize - child.getMeasuredHeight(),
130965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            left + getMeasuredWidth(), top + maxSize);
131065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    break;
131165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
131265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                left += child.getMeasuredWidth();
131365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                left += mSpace;
131465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
131565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (leftToRight) {
131665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mExpandableChildStates.loadView(child, mRightIndex);
131765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mRightIndex++;
131865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
131965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mExpandableChildStates.loadView(child, mLeftIndex);
132065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mLeftIndex--;
132165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
132265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            h.mScrollCenter = computeScrollCenter(viewIndex + i);
132365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (setInitialPos && leftToRight &&
132465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mAdapter.isEnabled(mRightIndex - 1) && !mMadeInitialSelection) {
132565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // this is the first child being added
132665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int centerMain = getScrollCenter(child);
132765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int centerSecond = getCenterInOffAxis(child);
132865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (mOrientation == HORIZONTAL) {
132965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mScroll.setScrollCenter(centerMain, centerSecond);
133065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                } else {
133165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mScroll.setScrollCenter(centerSecond, centerMain);
133265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
133365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mMadeInitialSelection = true;
133465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                transferFocusTo(child, 0);
133565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
133665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
133765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return true;
133865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
133965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
134065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * try to add one right/bottom child views, returning false tells caller can stop loop
134165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
134265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private boolean fillOneRightChildView(boolean stopOnInvisible) {
134365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // 1. check if we still need add view
134465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mRightIndex >= mAdapter.getCount()) {
134565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return false;
134665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
134765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int left = getPaddingLeft();
134865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int top = getPaddingTop();
134965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        boolean checkedChild = false;
135065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (lastExpandableIndex() - firstExpandableIndex() > 0) {
135165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // position of new view should starts from the last child or expanded view of last
135265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // child if it exists
135365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int childIndex = lastExpandableIndex() - 1;
135465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int gridPos = getAdapterIndex(childIndex) % mItemsOnOffAxis;
135565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            for (int i = childIndex - gridPos; i < lastExpandableIndex(); i++) {
135665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                View v = getChildAt(i);
135765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int adapterIndex = getAdapterIndex(i);
135865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                ExpandedView expandedView = findExpandedView(mExpandedViews, adapterIndex);
135965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (expandedView != null) {
136065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (mOrientation == HORIZONTAL) {
136165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        left = expandedView.expandedView.getRight();
136265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    } else {
136365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        top = expandedView.expandedView.getBottom();
136465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
136565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    checkedChild = true;
136665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    break;
136765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
136865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (mOrientation == HORIZONTAL) {
136965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (!checkedChild) {
137065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        checkedChild = true;
137165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        left = v.getRight();
137265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    } else if (v.getRight() > left) {
137365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        left = v.getRight();
137465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
137565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                } else {
137665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (!checkedChild) {
137765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        checkedChild = true;
137865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        top = v.getBottom();
137965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    } else if (v.getBottom() > top) {
138065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        top = v.getBottom();
138165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
138265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
138365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
138465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            boolean itemInvisible;
138565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mOrientation == HORIZONTAL) {
138665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                left += mSpace;
138765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                itemInvisible = left - getScrollX() >= getWidth();
138865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                top = getPaddingTop();
138965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
139065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                top += mSpace;
139165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                itemInvisible = top - getScrollY() >= getHeight();
139265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                left = getPaddingLeft();
139365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
139465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (itemInvisible && stopOnInvisible) {
139565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return false;
139665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
139765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
139865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // 2. create view and layout
139965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return fillOneAxis(left, top, true, true);
140065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
140165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
140265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int heuristicGetPersistentIndex() {
140365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int c = mAdapter.getCount();
140465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mScrollBeforeReset.id != INVALID_ROW_ID) {
140565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mScrollBeforeReset.index < c
140665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    && mAdapter.getItemId(mScrollBeforeReset.index) == mScrollBeforeReset.id) {
140765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return mScrollBeforeReset.index;
140865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
140965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            for (int i = 1; i <= SEARCH_ID_RANGE; i++) {
141065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int index = mScrollBeforeReset.index + i;
141165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (index < c && mAdapter.getItemId(index) == mScrollBeforeReset.id) {
141265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    return index;
141365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
141465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                index = mScrollBeforeReset.index - i;
141565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (index >=0 && index < c && mAdapter.getItemId(index) == mScrollBeforeReset.id) {
141665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    return index;
141765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
141865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
141965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
142065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mScrollBeforeReset.index >= c ? c - 1 : mScrollBeforeReset.index;
142165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
142265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
142365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void restoreLoadingState() {
142465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int selection;
142565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int viewLoc = Integer.MIN_VALUE;
142665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        float scrollPosition = 0f;
142765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mPendingSelection >= 0) {
142865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // got setSelection calls
142965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            selection = mPendingSelection;
143065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            scrollPosition = mPendingScrollPosition;
143165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else if (mScrollBeforeReset.isValid()) {
143265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // data was refreshed, try to recover where we were
143365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            selection = heuristicGetPersistentIndex();
143465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            viewLoc = mScrollBeforeReset.viewLocation;
143565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else if (mLoadingState != null) {
143665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // scrollAdapterView is restoring from loading state
143765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            selection = mLoadingState.index;
143865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
143965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
144065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
144165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mPendingSelection = -1;
144265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScrollBeforeReset.clear();
144365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mLoadingState = null;
144465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (selection < 0 || selection >= mAdapter.getCount()) {
144565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            Log.w(TAG, "invalid selection "+selection);
144665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
144765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
144865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
144965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // startIndex is the first child in the same offAxis of selection
145065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // We add this view first because we don't know "selection" position in offAxis
145165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int startIndex = selection - selection % mItemsOnOffAxis;
145265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int left, top;
145365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mOrientation == HORIZONTAL) {
145465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // estimation of left
145565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            left = viewLoc != Integer.MIN_VALUE ? viewLoc: mScroll.horizontal.getPaddingLow()
145665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    + mScrapWidth * (selection / mItemsOnOffAxis);
145765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            top = mScroll.vertical.getPaddingLow();
145865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
145965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            left = mScroll.horizontal.getPaddingLow();
146065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // estimation of top
146165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            top = viewLoc != Integer.MIN_VALUE ? viewLoc: mScroll.vertical.getPaddingLow()
146265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    + mScrapHeight * (selection / mItemsOnOffAxis);
146365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
146465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mRightIndex = startIndex;
146565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mLeftIndex = mRightIndex - 1;
146665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        fillOneAxis(left, top, true, false);
146765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mMadeInitialSelection = true;
146865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // fill all views, should include the "selection" view
146965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        fillVisibleViewsInLayout();
147065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View child = getExpandableView(selection);
147165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (child == null) {
147265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            Log.w(TAG, "unable to restore selection view");
147365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
147465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
147565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mExpandableChildStates.loadView(child, selection);
147665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (viewLoc != Integer.MIN_VALUE && mScrollerState == SCROLL_AND_CENTER_FOCUS) {
147765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // continue scroll animation but since the views and sizes might change, we need
147865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // update the scrolling final target
147965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int finalLocation = (mOrientation == HORIZONTAL) ? mScroll.getFinalX() :
148065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mScroll.getFinalY();
148165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mSelectedIndex = getAdapterIndex(indexOfChild(child));
148265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int scrollCenter = getScrollCenter(child);
148365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mScroll.mainAxis().getScrollCenter() <= finalLocation) {
148465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                while (scrollCenter < finalLocation) {
148565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    int nextAdapterIndex = mSelectedIndex + mItemsOnOffAxis;
148665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    View nextView = getExpandableView(nextAdapterIndex);
148765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (nextView == null) {
148865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        if (!fillOneRightChildView(false)) {
148965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            break;
149065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        }
149165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        nextView = getExpandableView(nextAdapterIndex);
149265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
149365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    int nextScrollCenter = getScrollCenter(nextView);
149465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (nextScrollCenter > finalLocation) {
149565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        break;
149665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
149765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mSelectedIndex = nextAdapterIndex;
149865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    scrollCenter = nextScrollCenter;
149965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
150065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
150165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                while (scrollCenter > finalLocation) {
150265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    int nextAdapterIndex = mSelectedIndex - mItemsOnOffAxis;
150365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    View nextView = getExpandableView(nextAdapterIndex);
150465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (nextView == null) {
150565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        if (!fillOneLeftChildView(false)) {
150665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            break;
150765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        }
150865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        nextView = getExpandableView(nextAdapterIndex);
150965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
151065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    int nextScrollCenter = getScrollCenter(nextView);
151165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (nextScrollCenter < finalLocation) {
151265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        break;
151365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
151465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mSelectedIndex = nextAdapterIndex;
151565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    scrollCenter = nextScrollCenter;
151665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
151765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
151865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mOrientation == HORIZONTAL) {
151965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mScroll.setFinalX(scrollCenter);
152065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
152165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mScroll.setFinalY(scrollCenter);
152265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
152365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
152465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // otherwise center focus to the view and stop animation
152565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            setSelectionInternal(selection, scrollPosition, false);
152665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
152765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
152865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
152965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void measureChild(View child) {
153065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        LayoutParams p = child.getLayoutParams();
153165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (p == null) {
153265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            p = generateDefaultLayoutParams();
153365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            child.setLayoutParams(p);
153465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
153565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mOrientation == VERTICAL) {
153665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int childWidthSpec = ViewGroup.getChildMeasureSpec(mMeasuredSpec, 0, p.width);
153765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int lpHeight = p.height;
153865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int childHeightSpec;
153965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (lpHeight > 0) {
154065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
154165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
154265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
154365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
154465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            child.measure(childWidthSpec, childHeightSpec);
154565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
154665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int childHeightSpec = ViewGroup.getChildMeasureSpec(mMeasuredSpec, 0, p.height);
154765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int lpWidth = p.width;
154865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int childWidthSpec;
154965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (lpWidth > 0) {
155065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                childWidthSpec = MeasureSpec.makeMeasureSpec(lpWidth, MeasureSpec.EXACTLY);
155165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
155265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                childWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
155365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
155465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            child.measure(childWidthSpec, childHeightSpec);
155565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
155665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
155765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
155865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
155965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public boolean dispatchKeyEvent(KeyEvent event) {
156065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // passing key event to focused child, which has chance to stop event processing by
156165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // returning true.
156265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // If child does not handle the event, we handle DPAD etc.
156365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return super.dispatchKeyEvent(event) || event.dispatch(this, null, null);
156465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
156565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
156665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    protected boolean internalKeyDown(int keyCode, KeyEvent event) {
156765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        switch (keyCode) {
156865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            case KeyEvent.KEYCODE_DPAD_LEFT:
156965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (handleArrowKey(View.FOCUS_LEFT, 0, false, false)) {
157065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    return true;
157165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
157265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                break;
157365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            case KeyEvent.KEYCODE_DPAD_RIGHT:
157465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (handleArrowKey(View.FOCUS_RIGHT, 0, false, false)) {
157565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    return true;
157665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
157765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                break;
157865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            case KeyEvent.KEYCODE_DPAD_UP:
157965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (handleArrowKey(View.FOCUS_UP, 0, false, false)) {
158065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    return true;
158165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
158265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                break;
158365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            case KeyEvent.KEYCODE_DPAD_DOWN:
158465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (handleArrowKey(View.FOCUS_DOWN, 0, false, false)) {
158565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    return true;
158665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
158765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                break;
158865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
158965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return super.onKeyDown(keyCode, event);
159065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
159165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
159265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
159365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public boolean onKeyDown(int keyCode, KeyEvent event) {
159465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return internalKeyDown(keyCode, event);
159565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
159665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
159765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
159865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public boolean onKeyUp(int keyCode, KeyEvent event) {
159965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        switch (keyCode) {
160065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            case KeyEvent.KEYCODE_DPAD_CENTER:
160165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            case KeyEvent.KEYCODE_ENTER:
160265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (getOnItemClickListener() != null) {
160365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    int index = findViewIndexContainingScrollCenter();
160465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    View child = getChildAt(index);
160565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (child != null) {
160665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        int adapterIndex = getAdapterIndex(index);
160765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        getOnItemClickListener().onItemClick(this, child,
160865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                adapterIndex, mAdapter.getItemId(adapterIndex));
160965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        return true;
161065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
161165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
161265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // otherwise fall back to default handling, typically handled by
161365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // the focused child view
161465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                break;
161565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
161665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return super.onKeyUp(keyCode, event);
161765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
161865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
161965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
162065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Scroll to next/last expandable view.
162165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * @param direction The direction corresponding to the arrow key that was pressed
162265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * @param repeats repeated count (0 means no repeat)
162365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * @return True if we consumed the event, false otherwise
162465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
162565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public boolean arrowScroll(int direction, int repeats) {
162665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (DBG) Log.d(TAG, "arrowScroll " + direction);
162765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return handleArrowKey(direction, repeats, true, false);
162865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
162965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
163065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** equivalent to arrowScroll(direction, 0) */
163165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public boolean arrowScroll(int direction) {
163265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return arrowScroll(direction, 0);
163365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
163465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
163565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public boolean isInScrolling() {
163665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return !mScroll.isFinished();
163765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
163865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
163965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public boolean isInScrollingOrDragging() {
164065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mScrollerState != NO_SCROLL;
164165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
164265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
164365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setPlaySoundEffects(boolean playSoundEffects) {
164465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mPlaySoundEffects = playSoundEffects;
164565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
164665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
164765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static boolean isDirectionGrowing(int direction) {
164865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return direction == View.FOCUS_RIGHT || direction == View.FOCUS_DOWN;
164965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
165065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
165165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static boolean isDescendant(View parent, View v) {
165265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        while (v != null) {
165365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            ViewParent p = v.getParent();
165465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (p == parent) {
165565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return true;
165665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
165765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (!(p instanceof View)) {
165865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return false;
165965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
166065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            v = (View) p;
166165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
166265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return false;
166365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
166465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
166565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private boolean requestNextFocus(int direction, View focused, View newFocus) {
166665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        focused.getFocusedRect(mTempRect);
166765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        offsetDescendantRectToMyCoords(focused, mTempRect);
166865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        offsetRectIntoDescendantCoords(newFocus, mTempRect);
166965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return newFocus.requestFocus(direction, mTempRect);
167065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
167165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
167265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    protected boolean handleArrowKey(int direction, int repeats, boolean forceFindNextExpandable,
167365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            boolean page) {
167465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View currentTop = getFocusedChild();
167565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View currentExpandable = getExpandableChild(currentTop);
167665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View focused = findFocus();
167765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (currentTop == currentExpandable && focused != null && !forceFindNextExpandable) {
167865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // find next focused inside expandable item
167965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View v = focused.focusSearch(direction);
168065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (v != null && v != focused && isDescendant(currentTop, v)) {
168165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                requestNextFocus(direction, focused, v);
168265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return true;
168365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
168465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
168565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        boolean isGrowing = isDirectionGrowing(direction);
168665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        boolean isOnOffAxis = false;
168765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (direction == View.FOCUS_RIGHT || direction == View.FOCUS_LEFT) {
168865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            isOnOffAxis = mOrientation == VERTICAL;
168965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else if (direction == View.FOCUS_DOWN || direction == View.FOCUS_UP) {
169065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            isOnOffAxis = mOrientation == HORIZONTAL;
169165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
169265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
169365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (currentTop != currentExpandable && !forceFindNextExpandable) {
169465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // find next focused inside expanded item
169565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View nextFocused = currentTop instanceof ViewGroup ? FocusFinder.getInstance()
169665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    .findNextFocus((ViewGroup) currentTop, findFocus(), direction)
169765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    : null;
169865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View nextTop = getTopItem(nextFocused);
169965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (nextTop == currentTop) {
170065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // within same expanded item
170165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // ignore at this level, the key handler of expanded item will take care
170265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return false;
170365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
170465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
170565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
170665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // focus to next expandable item
170765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int currentExpandableIndex = expandableIndexFromAdapterIndex(mSelectedIndex);
170865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (currentExpandableIndex < 0) {
170965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return false;
171065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
171165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View nextTop = null;
171265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (isOnOffAxis) {
171365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (isGrowing && currentExpandableIndex + 1 < lastExpandableIndex() &&
171465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            getAdapterIndex(currentExpandableIndex) % mItemsOnOffAxis
171565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            != mItemsOnOffAxis - 1) {
171665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                nextTop = getChildAt(currentExpandableIndex + 1);
171765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else if (!isGrowing && currentExpandableIndex - 1 >= firstExpandableIndex()
171865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    && getAdapterIndex(currentExpandableIndex) % mItemsOnOffAxis != 0) {
171965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                nextTop = getChildAt(currentExpandableIndex - 1);
172065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
172165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return !mNavigateOutOfOffAxisAllowed;
172265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
172365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
172465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int adapterIndex = getAdapterIndex(currentExpandableIndex);
172565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int focusAdapterIndex = adapterIndex;
172665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            for (int totalCount = repeats + 1; totalCount > 0;) {
172765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int nextFocusAdapterIndex = isGrowing ? focusAdapterIndex + mItemsOnOffAxis:
172865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    focusAdapterIndex - mItemsOnOffAxis;
172965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if ((isGrowing && nextFocusAdapterIndex >= mAdapter.getCount())
173065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        || (!isGrowing && nextFocusAdapterIndex < 0)) {
173165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (focusAdapterIndex == adapterIndex
173265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            || !mAdapter.isEnabled(focusAdapterIndex)) {
173365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        if (hasFocus() && mNavigateOutAllowed) {
173465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            View view = getChildAt(
173565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                    expandableIndexFromAdapterIndex(focusAdapterIndex));
173665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            if (view != null && !view.hasFocus()) {
173765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                view.requestFocus();
173865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            }
173965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        }
174065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        return !mNavigateOutAllowed;
174165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    } else {
174265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        break;
174365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
174465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
174565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                focusAdapterIndex = nextFocusAdapterIndex;
174665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (mAdapter.isEnabled(focusAdapterIndex)) {
174765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    totalCount--;
174865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
174965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
175065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (isGrowing) {
175165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                do {
175265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (focusAdapterIndex <= getAdapterIndex(lastExpandableIndex() - 1)) {
175365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        nextTop = getChildAt(expandableIndexFromAdapterIndex(focusAdapterIndex));
175465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        break;
175565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
175665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                } while (fillOneRightChildView(false));
175765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (nextTop == null) {
175865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    nextTop = getChildAt(lastExpandableIndex() - 1);
175965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
176065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
176165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                do {
176265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (focusAdapterIndex >= getAdapterIndex(firstExpandableIndex())) {
176365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        nextTop = getChildAt(expandableIndexFromAdapterIndex(focusAdapterIndex));
176465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        break;
176565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
176665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                } while (fillOneLeftChildView(false));
176765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (nextTop == null) {
176865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    nextTop = getChildAt(firstExpandableIndex());
176965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
177065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
177165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (nextTop == null) {
177265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return true;
177365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
177465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
177565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        scrollAndFocusTo(nextTop, direction, false, 0, page);
177665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mPlaySoundEffects) {
177765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
177865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
177965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return true;
178065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
178165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
178265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void fireItemChange() {
178365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int childIndex = findViewIndexContainingScrollCenter();
178465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View topItem = getChildAt(childIndex);
178565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (isFocused() && getDescendantFocusability() == FOCUS_AFTER_DESCENDANTS
178665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                && topItem != null) {
178765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // transfer focus to child for reset/restore
178865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            topItem.requestFocus();
178965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
179065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mOnItemChangeListeners != null && !mOnItemChangeListeners.isEmpty()) {
179165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (topItem == null) {
179265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (mItemSelected != -1) {
179365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    for (OnItemChangeListener listener : mOnItemChangeListeners) {
179465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        listener.onItemSelected(null, -1, 0);
179565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
179665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mItemSelected = -1;
179765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
179865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
179965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int adapterIndex = getAdapterIndex(childIndex);
180065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int scrollCenter = getScrollCenter(topItem);
180165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                for (OnItemChangeListener listener : mOnItemChangeListeners) {
180265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    listener.onItemSelected(topItem, adapterIndex, scrollCenter -
180365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            mScroll.mainAxis().getSystemScrollPos(scrollCenter));
180465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
180565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mItemSelected = adapterIndex;
180665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
180765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
180865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
180965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
181065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
181165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
181265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void updateScrollInfo(ScrollInfo info) {
181365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int scrollCenter = mScroll.mainAxis().getScrollCenter();
181465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int scrollCenterOff = mScroll.secondAxis().getScrollCenter();
181565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int index = findViewIndexContainingScrollCenter(
181665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                scrollCenter, scrollCenterOff, false);
181765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (index < 0) {
181865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            info.index = -1;
181965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
182065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
182165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View view = getChildAt(index);
182265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int center = getScrollCenter(view);
182365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (scrollCenter > center) {
182465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (index + mItemsOnOffAxis < lastExpandableIndex()) {
182565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int nextCenter = getScrollCenter(getChildAt(index + mItemsOnOffAxis));
182665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                info.mainPos = (float)(scrollCenter - center) / (nextCenter - center);
182765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
182865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // overscroll to right
182965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                info.mainPos = (float)(scrollCenter - center) / getSize(view);
183065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
183165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else if (scrollCenter == center){
183265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            info.mainPos = 0;
183365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
183465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (index - mItemsOnOffAxis >= firstExpandableIndex()) {
183565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                index = index - mItemsOnOffAxis;
183665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                view = getChildAt(index);
183765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int previousCenter = getScrollCenter(view);
183865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                info.mainPos = (float) (scrollCenter - previousCenter) /
183965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        (center - previousCenter);
184065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
184165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // overscroll to left, negative value
184265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                info.mainPos = (float) (scrollCenter - center) / getSize(view);
184365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
184465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
184565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int centerOffAxis = getCenterInOffAxis(view);
184665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (scrollCenterOff > centerOffAxis) {
184765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (index + 1 < lastExpandableIndex()) {
184865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int nextCenter = getCenterInOffAxis(getChildAt(index + 1));
184965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                info.secondPos = (float) (scrollCenterOff - centerOffAxis)
185065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        / (nextCenter - centerOffAxis);
185165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
185265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // overscroll to right
185365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                info.secondPos = (float) (scrollCenterOff - centerOffAxis) /
185465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        getSizeInOffAxis(view);
185565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
185665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else if (scrollCenterOff == centerOffAxis) {
185765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            info.secondPos = 0;
185865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
185965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (index - 1 >= firstExpandableIndex()) {
186065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                index = index - 1;
186165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                view = getChildAt(index);
186265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int previousCenter = getCenterInOffAxis(view);
186365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                info.secondPos = (float) (scrollCenterOff - previousCenter)
186465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        / (centerOffAxis - previousCenter);
186565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
186665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // overscroll to left, negative value
186765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                info.secondPos = (float) (scrollCenterOff - centerOffAxis) /
186865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        getSizeInOffAxis(view);
186965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
187065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
187165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        info.index = getAdapterIndex(index);
187265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        info.viewLocation = mOrientation == HORIZONTAL ? view.getLeft() : view.getTop();
187365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mAdapter.hasStableIds()) {
187465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            info.id = mAdapter.getItemId(info.index);
187565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
187665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
187765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
187865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void fireScrollChange() {
187965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int savedIndex = mCurScroll.index;
188065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        float savedMainPos = mCurScroll.mainPos;
188165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        float savedSecondPos = mCurScroll.secondPos;
188265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        updateScrollInfo(mCurScroll);
188365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mOnScrollListeners != null && !mOnScrollListeners.isEmpty()
188465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                &&(savedIndex != mCurScroll.index
188565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                || savedMainPos != mCurScroll.mainPos || savedSecondPos != mCurScroll.secondPos)) {
188665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mCurScroll.index >= 0) {
188765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                for (OnScrollListener l : mOnScrollListeners) {
188865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    l.onScrolled(getChildAt(expandableIndexFromAdapterIndex(
188965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            mCurScroll.index)), mCurScroll.index,
189065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            mCurScroll.mainPos, mCurScroll.secondPos);
189165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
189265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
189365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
189465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
189565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
189665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void fireItemSelected() {
189765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        OnItemSelectedListener listener = getOnItemSelectedListener();
189865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (listener != null) {
189965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            listener.onItemSelected(this, getSelectedView(), getSelectedItemPosition(),
190065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    getSelectedItemId());
190165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
190265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
190365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
190465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
190565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** manually set scroll position */
190665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void setSelectionInternal(int adapterIndex, float scrollPosition, boolean fireEvent) {
190765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (adapterIndex < 0 || mAdapter == null || adapterIndex >= mAdapter.getCount()
190865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                || !mAdapter.isEnabled(adapterIndex)) {
190965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            Log.w(TAG, "invalid selection index = " + adapterIndex);
191065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
191165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
191265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int viewIndex = expandableIndexFromAdapterIndex(adapterIndex);
191365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mDataSetChangedFlag || viewIndex < firstExpandableIndex() ||
191465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                viewIndex >= lastExpandableIndex()) {
191565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mPendingSelection = adapterIndex;
191665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mPendingScrollPosition = scrollPosition;
191765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            fireDataSetChanged();
191865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
191965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
192065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View view = getChildAt(viewIndex);
192165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int scrollCenter = getScrollCenter(view);
192265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int scrollCenterOffAxis = getCenterInOffAxis(view);
192365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int deltaMain;
192465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (scrollPosition > 0 && viewIndex + mItemsOnOffAxis < lastExpandableIndex()) {
192565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int nextCenter = getScrollCenter(getChildAt(viewIndex + mItemsOnOffAxis));
192665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            deltaMain = (int) ((nextCenter - scrollCenter) * scrollPosition);
192765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
192865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            deltaMain = (int) (getSize(view) * scrollPosition);
192965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
193065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mOrientation == HORIZONTAL) {
193165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mScroll.setScrollCenter(scrollCenter + deltaMain, scrollCenterOffAxis);
193265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
193365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mScroll.setScrollCenter(scrollCenterOffAxis, scrollCenter + deltaMain);
193465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
193565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        transferFocusTo(view, 0);
193665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        adjustSystemScrollPos();
193765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        applyTransformations();
193865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (fireEvent) {
193965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            updateViewsLocations(false);
194065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            fireScrollChange();
194165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (scrollPosition == 0) {
194265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                fireItemChange();
194365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
194465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
194565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
194665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
194765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void transferFocusTo(View topItem, int direction) {
194865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View oldSelection = getSelectedView();
194965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (topItem == oldSelection) {
195065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
195165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
195265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mSelectedIndex = getAdapterIndex(indexOfChild(topItem));
195365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View focused = findFocus();
195465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (focused != null) {
195565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (direction != 0) {
195665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                requestNextFocus(direction, focused, topItem);
195765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
195865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                topItem.requestFocus();
195965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
196065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
196165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        fireItemSelected();
196265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
196365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
196465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** scroll And Focus To expandable item in the main direction */
196565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void scrollAndFocusTo(View topItem, int direction, boolean easeFling, int duration,
196665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            boolean page) {
196765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (topItem == null) {
196865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mScrollerState = NO_SCROLL;
196965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
197065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
197165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int delta = getScrollCenter(topItem) - mScroll.mainAxis().getScrollCenter();
19726e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler        int deltaOffAxis = mItemsOnOffAxis == 1 ? 0 : // don't scroll 2nd axis for non-grid
197365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                getCenterInOffAxis(topItem) - mScroll.secondAxis().getScrollCenter();
197465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (delta != 0 || deltaOffAxis != 0) {
197565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mScrollerState = SCROLL_AND_CENTER_FOCUS;
197665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mScroll.startScrollByMain(delta, deltaOffAxis, easeFling, duration, page);
197765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // Instead of waiting scrolling animation finishes, we immediately change focus.
197865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // This will cause focused item to be off center and benefit is to dealing multiple
197965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // DPAD events without waiting animation finish.
198065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
198165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mScrollerState = NO_SCROLL;
198265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
198365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
198465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        transferFocusTo(topItem, direction);
198565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
198665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        scheduleScrollTask();
198765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
198865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
198965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public int getScrollCenterStrategy() {
199065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mScroll.mainAxis().getScrollCenterStrategy();
199165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
199265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
199365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setScrollCenterStrategy(int scrollCenterStrategy) {
199465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScroll.mainAxis().setScrollCenterStrategy(scrollCenterStrategy);
199565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
199665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
199765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public int getScrollCenterOffset() {
199865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mScroll.mainAxis().getScrollCenterOffset();
199965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
200065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
200165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setScrollCenterOffset(int scrollCenterOffset) {
200265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScroll.mainAxis().setScrollCenterOffset(scrollCenterOffset);
200365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
200465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
200565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setScrollCenterOffsetPercent(int scrollCenterOffsetPercent) {
200665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScroll.mainAxis().setScrollCenterOffsetPercent(scrollCenterOffsetPercent);
200765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
200865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
200965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setItemTransform(ScrollAdapterTransform transform) {
201065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mItemTransform = transform;
201165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
201265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
201365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public ScrollAdapterTransform getItemTransform() {
201465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mItemTransform;
201565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
201665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
201765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void ensureSimpleItemTransform() {
201865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (! (mItemTransform instanceof SimpleScrollAdapterTransform)) {
201965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mItemTransform = new SimpleScrollAdapterTransform(getContext());
202065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
202165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
202265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
202365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setLowItemTransform(Animator anim) {
202465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ensureSimpleItemTransform();
202565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ((SimpleScrollAdapterTransform)mItemTransform).setLowItemTransform(anim);
202665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
202765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
202865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setHighItemTransform(Animator anim) {
202965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ensureSimpleItemTransform();
203065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ((SimpleScrollAdapterTransform)mItemTransform).setHighItemTransform(anim);
203165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
203265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
203365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
203465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    protected float getRightFadingEdgeStrength() {
203565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mOrientation != HORIZONTAL || mAdapter == null || getChildCount() == 0) {
203665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return 0;
203765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
203865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mRightIndex == mAdapter.getCount()) {
203965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View lastChild = getChildAt(lastExpandableIndex() - 1);
204065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int maxEdge = lastChild.getRight();
204165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (getScrollX() + getWidth() >= maxEdge) {
204265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return 0;
204365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
204465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
204565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return 1;
204665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
204765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
204865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
204965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    protected float getBottomFadingEdgeStrength() {
205065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mOrientation != HORIZONTAL || mAdapter == null || getChildCount() == 0) {
205165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return 0;
205265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
205365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mRightIndex == mAdapter.getCount()) {
205465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View lastChild = getChildAt(lastExpandableIndex() - 1);
205565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int maxEdge = lastChild.getBottom();
205665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (getScrollY() + getHeight() >= maxEdge) {
205765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return 0;
205865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
205965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
206065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return 1;
206165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
206265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
206365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
206465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * get the view which is ancestor of "v" and immediate child of root view return "v" if
206565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * rootView is not ViewGroup or "v" is not in the subtree
206665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
20676e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler    private View getTopItem(View v) {
206865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ViewGroup root = this;
206965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View ret = v;
207065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        while (ret != null && ret.getParent() != root) {
207165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (!(ret.getParent() instanceof View)) {
207265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                break;
207365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
207465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            ret = (View) ret.getParent();
207565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
207665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (ret == null) {
207765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return v;
207865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
207965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return ret;
208065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
208165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
208265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
20836e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler    private int getCenter(View v) {
208465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mOrientation == HORIZONTAL ? (v.getLeft() + v.getRight()) / 2 : (v.getTop()
208565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                + v.getBottom()) / 2;
208665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
208765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
20886e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler    private int getCenterInOffAxis(View v) {
208965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mOrientation == VERTICAL ? (v.getLeft() + v.getRight()) / 2 : (v.getTop()
209065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                + v.getBottom()) / 2;
209165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
209265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
20936e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler    private int getSize(View v) {
209465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return ((ChildViewHolder) v.getTag(R.id.ScrollAdapterViewChild)).mMaxSize;
209565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
209665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
20976e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler    private int getSizeInOffAxis(View v) {
209865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mOrientation == HORIZONTAL ? v.getHeight() : v.getWidth();
209965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
210065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
210165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public View getExpandableView(int adapterIndex) {
210265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return getChildAt(expandableIndexFromAdapterIndex(adapterIndex));
210365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
210465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
210565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public int firstExpandableIndex() {
210665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mExpandedViews.size();
210765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
210865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
210965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public int lastExpandableIndex() {
211065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return getChildCount();
211165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
211265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
211365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int getAdapterIndex(int expandableViewIndex) {
211465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return expandableViewIndex - firstExpandableIndex() + mLeftIndex + 1;
211565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
211665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
211765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int expandableIndexFromAdapterIndex(int index) {
211865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return firstExpandableIndex() + index - mLeftIndex - 1;
211965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
212065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
212165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    View getExpandableChild(View view) {
212265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (view != null) {
212365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            for (int i = 0, size = mExpandedViews.size(); i < size; i++) {
212465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                ExpandedView v = mExpandedViews.get(i);
212565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (v.expandedView == view) {
212665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    return getChildAt(expandableIndexFromAdapterIndex(v.index));
212765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
212865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
212965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
213065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return view;
213165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
213265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
213365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static ExpandedView findExpandedView(ArrayList<ExpandedView> expandedView, int index) {
213465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int expandedCount = expandedView.size();
213565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (int i = 0; i < expandedCount; i++) {
213665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            ExpandedView v = expandedView.get(i);
213765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (v.index == index) {
213865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return v;
213965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
214065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
214165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return null;
214265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
214365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
214465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
214565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * This function is only called from {@link #updateViewsLocations()} Returns existing
214665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * ExpandedView or create a new one.
214765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
214865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private ExpandedView getOrCreateExpandedView(int index) {
214965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mExpandAdapter == null || index < 0) {
215065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return null;
215165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
215265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ExpandedView ret = findExpandedView(mExpandedViews, index);
215365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (ret != null) {
215465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return ret;
215565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
215665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int type = mExpandAdapter.getItemViewType(index);
215765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View recycleView = mRecycleExpandedViews.getView(type);
215865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View v = mExpandAdapter.getView(index, recycleView, ScrollAdapterView.this);
215965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (v == null) {
216065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return null;
216165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
216265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        addViewInLayout(v, 0, v.getLayoutParams(), true);
216365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mExpandedChildStates.loadView(v, index);
216465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        measureChild(v);
216565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (DBG) Log.d(TAG, "created new expanded View for " + index + " " + v);
216665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ExpandedView view = new ExpandedView(v, index, type);
216765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (int i = 0, size = mExpandedViews.size(); i < size; i++) {
216865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (view.index < mExpandedViews.get(i).index) {
216965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mExpandedViews.add(i, view);
217065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return view;
217165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
217265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
217365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mExpandedViews.add(view);
217465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return view;
217565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
217665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
217765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setAnimateLayoutChange(boolean animateLayoutChange) {
217865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mAnimateLayoutChange = animateLayoutChange;
217965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
218065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
218165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public boolean getAnimateLayoutChange() {
218265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mAnimateLayoutChange;
218365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
218465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
218565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
218665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Key function to update expandable views location and create/destroy expanded views
218765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
218865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void updateViewsLocations(boolean onLayout) {
218965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int lastExpandable = lastExpandableIndex();
219065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (((mExpandAdapter == null && !selectedItemCanScale() && mAdapterCustomAlign == null)
219165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                || lastExpandable == 0) &&
219265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                (!onLayout || getChildCount() == 0)) {
219365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
219465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
219565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
219665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int scrollCenter = mScroll.mainAxis().getScrollCenter();
219765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int scrollCenterOffAxis = mScroll.secondAxis().getScrollCenter();
219865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // 1 search center and nextCenter that contains mScrollCenter.
219965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int expandedCount = mExpandedViews.size();
220065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int center = -1;
220165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int nextCenter = -1;
220265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int expandIdx = -1;
220365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int firstExpandable = firstExpandableIndex();
220465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int alignExtraOffset = 0;
220565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (int idx = firstExpandable; idx < lastExpandable; idx++) {
220665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View view = getChildAt(idx);
220765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int centerMain = getScrollCenter(view);
220865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int centerOffAxis = getCenterInOffAxis(view);
220965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int viewSizeOffAxis = mOrientation == HORIZONTAL ? view.getHeight() : view.getWidth();
221065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (centerMain <= scrollCenter && (mItemsOnOffAxis == 1 || hasScrollPositionSecondAxis(
221165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    scrollCenterOffAxis, viewSizeOffAxis, centerOffAxis))) {
221265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // find last one match the criteria,  we can optimize it..
221365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                expandIdx = idx;
221465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                center = centerMain;
221565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (mAdapterCustomAlign != null) {
221665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    alignExtraOffset = mAdapterCustomAlign.getItemAlignmentExtraOffset(
221765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            getAdapterIndex(idx), view);
221865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
221965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
222065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
222165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (expandIdx == -1) {
222265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // mScrollCenter scrolls too fast, a fling action might cause this
222365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
222465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
222565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int nextExpandIdx = expandIdx + mItemsOnOffAxis;
222665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int nextAlignExtraOffset = 0;
222765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (nextExpandIdx < lastExpandable) {
222865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View nextView = getChildAt(nextExpandIdx);
222965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            nextCenter = getScrollCenter(nextView);
223065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mAdapterCustomAlign != null) {
223165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                nextAlignExtraOffset = mAdapterCustomAlign.getItemAlignmentExtraOffset(
223265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        getAdapterIndex(nextExpandIdx), nextView);
223365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
223465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
223565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            nextExpandIdx = -1;
223665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
223765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int previousExpandIdx = expandIdx - mItemsOnOffAxis;
223865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (previousExpandIdx < firstExpandable) {
223965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            previousExpandIdx = -1;
224065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
224165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
224265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // 2. prepare the expanded view, they could be new created or from existing.
224365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int xindex = getAdapterIndex(expandIdx);
224465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ExpandedView thisExpanded = getOrCreateExpandedView(xindex);
224565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ExpandedView nextExpanded = null;
224665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (nextExpandIdx != -1) {
224765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            nextExpanded = getOrCreateExpandedView(xindex + mItemsOnOffAxis);
224865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
224965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // cache one more expanded view before the visible one, it's always invisible
225065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ExpandedView previousExpanded = null;
225165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (previousExpandIdx != -1) {
225265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            previousExpanded = getOrCreateExpandedView(xindex - mItemsOnOffAxis);
225365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
225465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
225565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // these count and index needs to be updated after we inserted new views
225665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int newExpandedAdded = mExpandedViews.size() - expandedCount;
225765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        expandIdx += newExpandedAdded;
225865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (nextExpandIdx != -1) {
225965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            nextExpandIdx += newExpandedAdded;
226065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
226165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        expandedCount = mExpandedViews.size();
226265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        lastExpandable = lastExpandableIndex();
226365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
226465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // 3. calculate the expanded View size, and optional next expanded view size.
226565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int expandedSize = 0;
226665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int nextExpandedSize = 0;
226765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        float progress = 1;
226865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (expandIdx < lastExpandable - 1) {
226965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            progress = (float) (nextCenter - mScroll.mainAxis().getScrollCenter()) /
227065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                       (float) (nextCenter - center);
227165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (thisExpanded != null) {
227265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                expandedSize =
227365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        (mOrientation == HORIZONTAL ? thisExpanded.expandedView.getMeasuredWidth()
227465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                : thisExpanded.expandedView.getMeasuredHeight());
227565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                expandedSize = (int) (progress * expandedSize);
227665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                thisExpanded.setProgress(progress);
227765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
227865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (nextExpanded != null) {
227965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                nextExpandedSize =
228065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        (mOrientation == HORIZONTAL ? nextExpanded.expandedView.getMeasuredWidth()
228165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                : nextExpanded.expandedView.getMeasuredHeight());
228265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                nextExpandedSize = (int) ((1f - progress) * nextExpandedSize);
228365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                nextExpanded.setProgress(1f - progress);
228465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
228565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
228665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (thisExpanded != null) {
228765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                expandedSize =
228865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        (mOrientation == HORIZONTAL ? thisExpanded.expandedView.getMeasuredWidth()
228965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                : thisExpanded.expandedView.getMeasuredHeight());
229065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                thisExpanded.setProgress(1f);
229165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
229265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
229365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
229465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int totalExpandedSize = expandedSize + nextExpandedSize;
229565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int extraSpaceLow = 0, extraSpaceHigh = 0;
229665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // 4. update expandable views positions
229765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int low = Integer.MAX_VALUE;
229865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int expandedStart = 0;
229965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int nextExpandedStart = 0;
230065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int numOffAxis = (lastExpandable - firstExpandableIndex() + mItemsOnOffAxis - 1)
230165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                / mItemsOnOffAxis;
230265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        boolean canAnimateExpandedSize = mAnimateLayoutChange &&
230365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mScroll.isFinished() && mExpandAdapter != null;
230465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (int j = 0; j < numOffAxis; j++) {
230565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int viewIndex = firstExpandableIndex() + j * mItemsOnOffAxis;
230665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int endViewIndex = viewIndex + mItemsOnOffAxis - 1;
230765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (endViewIndex >= lastExpandable) {
230865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                endViewIndex = lastExpandable - 1;
230965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
231065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // get maxSize of the off-axis, get start position for first off-axis
231165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int maxSize = 0;
231265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            for (int k = viewIndex; k <= endViewIndex; k++) {
231365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                View view = getChildAt(k);
231465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                ChildViewHolder h = (ChildViewHolder) view.getTag(R.id.ScrollAdapterViewChild);
231565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (canAnimateExpandedSize) {
231665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    // remember last position in temporary variable
231765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (mOrientation == HORIZONTAL) {
231865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        h.mLocation = view.getLeft();
231965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        h.mLocationInParent = h.mLocation + view.getTranslationX();
232065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    } else {
232165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        h.mLocation = view.getTop();
232265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        h.mLocationInParent = h.mLocation + view.getTranslationY();
232365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
232465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
232565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                maxSize = Math.max(maxSize, mOrientation == HORIZONTAL ? view.getMeasuredWidth() :
232665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    view.getMeasuredHeight());
232765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (j == 0) {
232865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    int viewLow = mOrientation == HORIZONTAL ? view.getLeft() : view.getTop();
23296e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler                    // because we start over again,  we should remove the extra space
233065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (mScroll.mainAxis().getSelectedTakesMoreSpace()) {
233165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        viewLow -= h.mExtraSpaceLow;
233265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
233365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (viewLow < low) {
233465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        low = viewLow;
233565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
233665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
233765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
233865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // layout views within the off axis and get the max right/bottom
233965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int maxSelectedSize = Integer.MIN_VALUE;
234065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int maxHigh = low + maxSize;
234165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            for (int k = viewIndex; k <= endViewIndex; k++) {
234265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                View view = getChildAt(k);
234365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int viewStart = low;
234465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int viewMeasuredSize = mOrientation == HORIZONTAL ? view.getMeasuredWidth()
234565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        : view.getMeasuredHeight();
234665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                switch (mScroll.getScrollItemAlign()) {
234765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                case ScrollController.SCROLL_ITEM_ALIGN_CENTER:
234865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    viewStart += maxSize / 2 - viewMeasuredSize / 2;
234965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    break;
235065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                case ScrollController.SCROLL_ITEM_ALIGN_HIGH:
235165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    viewStart += maxSize - viewMeasuredSize;
235265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    break;
235365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                case ScrollController.SCROLL_ITEM_ALIGN_LOW:
235465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    break;
235565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
235665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (mOrientation == HORIZONTAL) {
235765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (view.isLayoutRequested()) {
235865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        measureChild(view);
235965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        view.layout(viewStart, view.getTop(), viewStart + view.getMeasuredWidth(),
236065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                view.getTop() + view.getMeasuredHeight());
236165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    } else {
236265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        view.offsetLeftAndRight(viewStart - view.getLeft());
236365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
236465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                } else {
236565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (view.isLayoutRequested()) {
236665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        measureChild(view);
236765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        view.layout(view.getLeft(), viewStart, view.getLeft() +
236865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                view.getMeasuredWidth(), viewStart + view.getMeasuredHeight());
236965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    } else {
237065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        view.offsetTopAndBottom(viewStart - view.getTop());
237165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
237265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
237365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (selectedItemCanScale()) {
237465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    maxSelectedSize = Math.max(maxSelectedSize,
237565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            getSelectedItemSize(getAdapterIndex(k), view));
237665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
237765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
237865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // we might need update mMaxSize/mMaxSelectedSize in case a relayout happens
237965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            for (int k = viewIndex; k <= endViewIndex; k++) {
238065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                View view = getChildAt(k);
238165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                ChildViewHolder h = (ChildViewHolder) view.getTag(R.id.ScrollAdapterViewChild);
238265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                h.mMaxSize = maxSize;
238365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                h.mExtraSpaceLow = 0;
238465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                h.mScrollCenter = computeScrollCenter(k);
238565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
238665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            boolean isTransitionFrom = viewIndex <= expandIdx && expandIdx <= endViewIndex;
238765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            boolean isTransitionTo = viewIndex <= nextExpandIdx && nextExpandIdx <= endViewIndex;
238865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // adding extra space
238965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (maxSelectedSize != Integer.MIN_VALUE) {
239065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int extraSpace = 0;
239165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (isTransitionFrom) {
239265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    extraSpace = (int) ((maxSelectedSize - maxSize) * progress);
239365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                } else if (isTransitionTo) {
239465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    extraSpace = (int) ((maxSelectedSize - maxSize) * (1 - progress));
239565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
239665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (extraSpace > 0) {
239765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    int lowExtraSpace;
239865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (mScroll.mainAxis().getSelectedTakesMoreSpace()) {
239965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        maxHigh = maxHigh + extraSpace;
240065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        totalExpandedSize = totalExpandedSize + extraSpace;
240165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        switch (mScroll.getScrollItemAlign()) {
240265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            case ScrollController.SCROLL_ITEM_ALIGN_CENTER:
240365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                lowExtraSpace = extraSpace / 2; // extraSpace added low and high
240465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                break;
240565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            case ScrollController.SCROLL_ITEM_ALIGN_HIGH:
240665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                lowExtraSpace = extraSpace; // extraSpace added on the low
240765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                break;
240865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            case ScrollController.SCROLL_ITEM_ALIGN_LOW:
240965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            default:
241065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                lowExtraSpace = 0; // extraSpace is added on the high
241165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                break;
241265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        }
241365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    } else {
241465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        // if we don't add extra space surrounding it,  the view should
241565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        // grow evenly on low and high
241665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        lowExtraSpace = extraSpace / 2;
241765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
241865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    extraSpaceLow += lowExtraSpace;
241965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    extraSpaceHigh += (extraSpace - lowExtraSpace);
242065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    for (int k = viewIndex; k <= endViewIndex; k++) {
242165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        View view = getChildAt(k);
242265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        if (mScroll.mainAxis().getSelectedTakesMoreSpace()) {
242365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            if (mOrientation == HORIZONTAL) {
242465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                view.offsetLeftAndRight(lowExtraSpace);
242565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            } else {
242665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                view.offsetTopAndBottom(lowExtraSpace);
242765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            }
242865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            ChildViewHolder h = (ChildViewHolder)
242965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                    view.getTag(R.id.ScrollAdapterViewChild);
243065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            h.mExtraSpaceLow = lowExtraSpace;
243165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        }
243265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
243365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
243465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
243565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // animate between different expanded view size
243665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (canAnimateExpandedSize) {
243765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                for (int k = viewIndex; k <= endViewIndex; k++) {
243865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    View view = getChildAt(k);
243965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    ChildViewHolder h = (ChildViewHolder) view.getTag(R.id.ScrollAdapterViewChild);
244065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    float target = (mOrientation == HORIZONTAL) ? view.getLeft() : view.getTop();
244165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (h.mLocation != target) {
244265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        if (mOrientation == HORIZONTAL) {
244365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            view.setTranslationX(h.mLocationInParent - target);
244465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            view.animate().translationX(0).start();
244565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        } else {
244665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            view.setTranslationY(h.mLocationInParent - target);
244765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            view.animate().translationY(0).start();
244865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        }
244965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
245065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
245165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
245265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // adding expanded size
245365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (isTransitionFrom) {
245465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                expandedStart = maxHigh;
245565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // "low" (next expandable start) is next to current one until fully expanded
245665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                maxHigh += progress == 1f ? expandedSize : 0;
245765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else if (isTransitionTo) {
245865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                nextExpandedStart = maxHigh;
245965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                maxHigh += progress == 1f ? nextExpandedSize : expandedSize + nextExpandedSize;
246065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
246165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // assign beginning position for next "off axis"
246265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            low = maxHigh + mSpace;
246365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
246465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScroll.mainAxis().setAlignExtraOffset(
246565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                (int) (alignExtraOffset * progress + nextAlignExtraOffset * (1 - progress)));
246665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScroll.mainAxis().setExpandedSize(totalExpandedSize);
246765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScroll.mainAxis().setExtraSpaceLow(extraSpaceLow);
246865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScroll.mainAxis().setExtraSpaceHigh(extraSpaceHigh);
246965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
247065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // 5. update expanded views
247165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (int j = 0; j < expandedCount;) {
247265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // remove views in mExpandedViews and are not newly created
247365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            ExpandedView v = mExpandedViews.get(j);
247465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (v!= thisExpanded && v!= nextExpanded && v != previousExpanded) {
247565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (v.expandedView.hasFocus()) {
247665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    View expandableView = getChildAt(expandableIndexFromAdapterIndex(v.index));
247765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                     expandableView.requestFocus();
247865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
247965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                v.close();
248065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mExpandedChildStates.saveInvisibleView(v.expandedView, v.index);
248165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                removeViewInLayout(v.expandedView);
248265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mRecycleExpandedViews.recycleView(v.expandedView, v.viewType);
248365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mExpandedViews.remove(j);
248465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                expandedCount--;
248565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
248665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                j++;
248765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
248865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
248965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (int j = 0, size = mExpandedViews.size(); j < size; j++) {
249065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            ExpandedView v = mExpandedViews.get(j);
249165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int start = v == thisExpanded ? expandedStart : nextExpandedStart;
249265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (!(v == previousExpanded || v == nextExpanded && progress == 1f)) {
249365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                v.expandedView.setVisibility(VISIBLE);
249465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
249565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mOrientation == HORIZONTAL) {
249665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (v.expandedView.isLayoutRequested()) {
249765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    measureChild(v.expandedView);
249865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
249965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                v.expandedView.layout(start, 0, start + v.expandedView.getMeasuredWidth(),
250065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        v.expandedView.getMeasuredHeight());
250165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
250265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (v.expandedView.isLayoutRequested()) {
250365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    measureChild(v.expandedView);
250465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
250565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                v.expandedView.layout(0, start, v.expandedView.getMeasuredWidth(),
250665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        start + v.expandedView.getMeasuredHeight());
250765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
250865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
250965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (int j = 0, size = mExpandedViews.size(); j < size; j++) {
251065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            ExpandedView v = mExpandedViews.get(j);
251165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (v == previousExpanded || v == nextExpanded && progress == 1f) {
251265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                v.expandedView.setVisibility(View.INVISIBLE);
251365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
251465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
251565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
251665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // 6. move focus from expandable view to expanded view, disable expandable view after it's
251765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // expanded
251865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mExpandAdapter != null && hasFocus()) {
251965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View focusedChild = getFocusedChild();
252065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int focusedIndex = indexOfChild(focusedChild);
252165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (focusedIndex >= firstExpandableIndex()) {
252265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                for (int j = 0, size = mExpandedViews.size(); j < size; j++) {
252365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    ExpandedView v = mExpandedViews.get(j);
252465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (expandableIndexFromAdapterIndex(v.index) == focusedIndex
252565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            && v.expandedView.getVisibility() == View.VISIBLE) {
252665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        v.expandedView.requestFocus();
252765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
252865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
252965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
253065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
253165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
253265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
253365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
253465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
253565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View view = getSelectedExpandedView();
253665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (view != null) {
253765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return view.requestFocus(direction, previouslyFocusedRect);
253865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
253965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        view = getSelectedView();
254065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (view != null) {
254165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return view.requestFocus(direction, previouslyFocusedRect);
254265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
254365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return false;
254465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
254565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
254665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int getScrollCenter(View view) {
254765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return ((ChildViewHolder) view.getTag(R.id.ScrollAdapterViewChild)).mScrollCenter;
254865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
254965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
255065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public int getScrollItemAlign() {
255165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mScroll.getScrollItemAlign();
255265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
255365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
255465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private boolean hasScrollPosition(int scrollCenter, int maxSize, int scrollPosInMain) {
255565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        switch (mScroll.getScrollItemAlign()) {
255665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        case ScrollController.SCROLL_ITEM_ALIGN_CENTER:
255765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return scrollCenter - maxSize / 2 - mSpaceLow < scrollPosInMain &&
255865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    scrollPosInMain < scrollCenter + maxSize / 2 + mSpaceHigh;
255965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        case ScrollController.SCROLL_ITEM_ALIGN_LOW:
256065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return scrollCenter - mSpaceLow <= scrollPosInMain &&
256165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    scrollPosInMain < scrollCenter + maxSize + mSpaceHigh;
256265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        case ScrollController.SCROLL_ITEM_ALIGN_HIGH:
256365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return scrollCenter - maxSize - mSpaceLow < scrollPosInMain &&
256465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    scrollPosInMain <= scrollCenter + mSpaceHigh;
256565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
256665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return false;
256765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
256865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
256965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private boolean hasScrollPositionSecondAxis(int scrollCenterOffAxis, int viewSizeOffAxis,
257065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int centerOffAxis) {
257165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return centerOffAxis - viewSizeOffAxis / 2 - mSpaceLow <= scrollCenterOffAxis
257265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                && scrollCenterOffAxis <= centerOffAxis + viewSizeOffAxis / 2 + mSpaceHigh;
257365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
257465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
257565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
257665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Get the center of expandable view in the state that all expandable views are collapsed, i.e.
257765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * expanded views are excluded from calculating.  The space is included in calculation
257865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
257965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int computeScrollCenter(int expandViewIndex) {
258065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int lastIndex = lastExpandableIndex();
258165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int firstIndex = firstExpandableIndex();
258265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View firstView = getChildAt(firstIndex);
258365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int center = 0;
258465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        switch (mScroll.getScrollItemAlign()) {
258565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        case ScrollController.SCROLL_ITEM_ALIGN_CENTER:
258665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            center = getCenter(firstView);
258765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            break;
258865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        case ScrollController.SCROLL_ITEM_ALIGN_LOW:
258965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            center = mOrientation == HORIZONTAL ? firstView.getLeft() : firstView.getTop();
259065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            break;
259165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        case ScrollController.SCROLL_ITEM_ALIGN_HIGH:
259265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            center = mOrientation == HORIZONTAL ? firstView.getRight() : firstView.getBottom();
259365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            break;
259465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
259565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mScroll.mainAxis().getSelectedTakesMoreSpace()) {
259665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            center -= ((ChildViewHolder) firstView.getTag(
259765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    R.id.ScrollAdapterViewChild)).mExtraSpaceLow;
259865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
259965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int nextCenter = -1;
260065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (int idx = firstIndex; idx < lastIndex; idx += mItemsOnOffAxis) {
260165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View view = getChildAt(idx);
260265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (idx <= expandViewIndex && expandViewIndex < idx + mItemsOnOffAxis) {
260365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return center;
260465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
260565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (idx < lastIndex - mItemsOnOffAxis) {
260665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // nextView is never null if scrollCenter is larger than center of current view
260765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                View nextView = getChildAt(idx + mItemsOnOffAxis);
260865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                switch (mScroll.getScrollItemAlign()) { // fixme
260965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    case ScrollController.SCROLL_ITEM_ALIGN_CENTER:
261065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        nextCenter = center + (getSize(view) + getSize(nextView)) / 2;
261165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        break;
261265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    case ScrollController.SCROLL_ITEM_ALIGN_LOW:
261365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        nextCenter = center + getSize(view);
261465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        break;
261565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    case ScrollController.SCROLL_ITEM_ALIGN_HIGH:
261665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        nextCenter = center + getSize(nextView);
261765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        break;
261865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
261965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                nextCenter += mSpace;
262065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
262165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                nextCenter = Integer.MAX_VALUE;
262265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
262365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            center = nextCenter;
262465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
262565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        assertFailure("Scroll out of range?");
262665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return 0;
262765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
262865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
262965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int getScrollLow(int scrollCenter, View view) {
263065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ChildViewHolder holder = (ChildViewHolder)view.getTag(R.id.ScrollAdapterViewChild);
263165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        switch (mScroll.getScrollItemAlign()) {
263265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        case ScrollController.SCROLL_ITEM_ALIGN_CENTER:
263365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return scrollCenter - holder.mMaxSize / 2;
263465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        case ScrollController.SCROLL_ITEM_ALIGN_LOW:
263565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return scrollCenter;
263665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        case ScrollController.SCROLL_ITEM_ALIGN_HIGH:
263765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return scrollCenter - holder.mMaxSize;
263865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
263965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return 0;
264065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
264165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
264265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int getScrollHigh(int scrollCenter, View view) {
264365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ChildViewHolder holder = (ChildViewHolder)view.getTag(R.id.ScrollAdapterViewChild);
264465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        switch (mScroll.getScrollItemAlign()) {
264565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        case ScrollController.SCROLL_ITEM_ALIGN_CENTER:
264665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return scrollCenter + holder.mMaxSize / 2;
264765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        case ScrollController.SCROLL_ITEM_ALIGN_LOW:
264865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return scrollCenter + holder.mMaxSize;
264965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        case ScrollController.SCROLL_ITEM_ALIGN_HIGH:
265065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return scrollCenter;
265165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
265265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return 0;
265365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
265465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
265565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
265665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * saves the current item index and scroll information for fully restore from
265765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
265865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    final static class AdapterViewState {
265965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int itemsOnOffAxis;
266065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int index; // index inside adapter of the current view
266165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        Bundle expandedChildStates = Bundle.EMPTY;
266265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        Bundle expandableChildStates = Bundle.EMPTY;
266365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
266465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
266565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    final static class SavedState extends BaseSavedState {
266665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
266765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        final AdapterViewState theState = new AdapterViewState();
266865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
266965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public SavedState(Parcelable superState) {
267065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            super(superState);
267165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
267265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
267365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
267465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void writeToParcel(Parcel out, int flags) {
267565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            super.writeToParcel(out, flags);
267665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            out.writeInt(theState.itemsOnOffAxis);
267765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            out.writeInt(theState.index);
267865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            out.writeBundle(theState.expandedChildStates);
267965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            out.writeBundle(theState.expandableChildStates);
268065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
268165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
268265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @SuppressWarnings("hiding")
268365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public static final Parcelable.Creator<SavedState> CREATOR =
268465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                new Parcelable.Creator<SavedState>() {
268565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    @Override
268665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    public SavedState createFromParcel(Parcel in) {
268765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        return new SavedState(in);
268865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
268965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
269065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    @Override
269165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    public SavedState[] newArray(int size) {
269265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        return new SavedState[size];
269365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
269465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                };
269565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
269665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        SavedState(Parcel in) {
269765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            super(in);
269865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            theState.itemsOnOffAxis = in.readInt();
269965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            theState.index = in.readInt();
270065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            ClassLoader loader = ScrollAdapterView.class.getClassLoader();
270165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            theState.expandedChildStates = in.readBundle(loader);
270265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            theState.expandableChildStates = in.readBundle(loader);
270365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
270465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
270565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
270665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
270765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    protected Parcelable onSaveInstanceState() {
270865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        Parcelable superState = super.onSaveInstanceState();
270965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        SavedState ss = new SavedState(superState);
271065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int index = findViewIndexContainingScrollCenter();
271165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (index < 0) {
271265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return superState;
271365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
271465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mExpandedChildStates.saveVisibleViews();
271565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mExpandableChildStates.saveVisibleViews();
271665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ss.theState.itemsOnOffAxis = mItemsOnOffAxis;
271765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ss.theState.index = getAdapterIndex(index);
271865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ss.theState.expandedChildStates = mExpandedChildStates.getChildStates();
271965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ss.theState.expandableChildStates = mExpandableChildStates.getChildStates();
272065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return ss;
272165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
272265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
272365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
272465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    protected void onRestoreInstanceState(Parcelable state) {
272565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (!(state instanceof SavedState)) {
272665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            super.onRestoreInstanceState(state);
272765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
272865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
272965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        SavedState ss = (SavedState)state;
273065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        super.onRestoreInstanceState(ss.getSuperState());
273165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mLoadingState = ss.theState;
273265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        fireDataSetChanged();
273365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
273465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
273565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
273665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Returns expandable children states policy, returns one of
273765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * {@link ViewsStateBundle#SAVE_NO_CHILD} {@link ViewsStateBundle#SAVE_VISIBLE_CHILD}
273865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * {@link ViewsStateBundle#SAVE_LIMITED_CHILD} {@link ViewsStateBundle#SAVE_ALL_CHILD}
273965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
274065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public int getSaveExpandableViewsPolicy() {
274165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mExpandableChildStates.getSavePolicy();
274265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
274365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
274465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** See explanation in {@link #getSaveExpandableViewsPolicy()} */
274565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setSaveExpandableViewsPolicy(int saveExpandablePolicy) {
274665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mExpandableChildStates.setSavePolicy(saveExpandablePolicy);
274765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
274865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
274965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
275065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Returns the limited number of expandable children that will be saved when
275165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * {@link #getSaveExpandableViewsPolicy()} is {@link ViewsStateBundle#SAVE_LIMITED_CHILD}
275265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
275365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public int getSaveExpandableViewsLimit() {
275465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mExpandableChildStates.getLimitNumber();
275565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
275665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
275765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** See explanation in {@link #getSaveExpandableViewsLimit()} */
275865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setSaveExpandableViewsLimit(int saveExpandableChildNumber) {
275965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mExpandableChildStates.setLimitNumber(saveExpandableChildNumber);
276065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
276165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
276265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
276365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Returns expanded children states policy, returns one of
276465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * {@link ViewsStateBundle#SAVE_NO_CHILD} {@link ViewsStateBundle#SAVE_VISIBLE_CHILD}
276565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * {@link ViewsStateBundle#SAVE_LIMITED_CHILD} {@link ViewsStateBundle#SAVE_ALL_CHILD}
276665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
276765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public int getSaveExpandedViewsPolicy() {
276865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mExpandedChildStates.getSavePolicy();
276965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
277065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
277165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** See explanation in {@link #getSaveExpandedViewsPolicy} */
277265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setSaveExpandedViewsPolicy(int saveExpandedChildPolicy) {
277365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mExpandedChildStates.setSavePolicy(saveExpandedChildPolicy);
277465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
277565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
277665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
277765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Returns the limited number of expanded children that will be saved when
277865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * {@link #getSaveExpandedViewsPolicy()} is {@link ViewsStateBundle#SAVE_LIMITED_CHILD}
277965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
278065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public int getSaveExpandedViewsLimit() {
278165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mExpandedChildStates.getLimitNumber();
278265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
278365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
278465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** See explanation in {@link #getSaveExpandedViewsLimit()} */
278565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setSaveExpandedViewsLimit(int mSaveExpandedNumber) {
278665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mExpandedChildStates.setLimitNumber(mSaveExpandedNumber);
278765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
278865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
278965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public ArrayList<OnItemChangeListener> getOnItemChangeListeners() {
279065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mOnItemChangeListeners;
279165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
279265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
279365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setOnItemChangeListener(OnItemChangeListener onItemChangeListener) {
279465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mOnItemChangeListeners.clear();
279565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        addOnItemChangeListener(onItemChangeListener);
279665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
279765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
279865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void addOnItemChangeListener(OnItemChangeListener onItemChangeListener) {
279965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (!mOnItemChangeListeners.contains(onItemChangeListener)) {
280065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mOnItemChangeListeners.add(onItemChangeListener);
280165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
280265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
280365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
280465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public ArrayList<OnScrollListener> getOnScrollListeners() {
280565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mOnScrollListeners;
280665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
280765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
280865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setOnScrollListener(OnScrollListener onScrollListener) {
280965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mOnScrollListeners.clear();
281065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        addOnScrollListener(onScrollListener);
281165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
281265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
281365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void addOnScrollListener(OnScrollListener onScrollListener) {
281465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (!mOnScrollListeners.contains(onScrollListener)) {
281565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mOnScrollListeners.add(onScrollListener);
281665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
281765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
281865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
281965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setExpandedItemInAnim(Animator animator) {
282065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mExpandedItemInAnim = animator;
282165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
282265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
282365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public Animator getExpandedItemInAnim() {
282465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mExpandedItemInAnim;
282565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
282665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
282765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setExpandedItemOutAnim(Animator animator) {
282865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mExpandedItemOutAnim = animator;
282965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
283065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
283165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public Animator getExpandedItemOutAnim() {
283265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mExpandedItemOutAnim;
283365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
283465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
283565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public boolean isNavigateOutOfOffAxisAllowed() {
283665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mNavigateOutOfOffAxisAllowed;
283765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
283865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
283965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public boolean isNavigateOutAllowed() {
284065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mNavigateOutAllowed;
284165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
284265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
284365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
284465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * if allow DPAD key in secondary axis to navigate out of ScrollAdapterView
284565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
284665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setNavigateOutOfOffAxisAllowed(boolean navigateOut) {
284765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mNavigateOutOfOffAxisAllowed = navigateOut;
284865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
284965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
285065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
285165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * if allow DPAD key in main axis to navigate out of ScrollAdapterView
285265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
285365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setNavigateOutAllowed(boolean navigateOut) {
285465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mNavigateOutAllowed = navigateOut;
285565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
285665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
285765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public boolean isNavigateInAnimationAllowed() {
285865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mNavigateInAnimationAllowed;
285965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
286065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
286165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
286265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * if {@code true} allow DPAD event from trackpadNavigation when ScrollAdapterView is in
286365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * animation, this does not affect physical keyboard or manually calling arrowScroll()
286465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
286565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setNavigateInAnimationAllowed(boolean navigateInAnimation) {
286665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mNavigateInAnimationAllowed = navigateInAnimation;
286765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
286865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
286965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** set space in pixels between two items */
287065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setSpace(int space) {
287165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mSpace = space;
287265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // mSpace may not be evenly divided by 2
287365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mSpaceLow = mSpace / 2;
287465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mSpaceHigh = mSpace - mSpaceLow;
287565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
287665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
287765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** get space in pixels between two items */
287865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public int getSpace() {
287965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mSpace;
288065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
288165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
288265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** set pixels of selected item, use {@link ScrollAdapterCustomSize} for more complicated case */
288365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setSelectedSize(int selectedScale) {
288465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mSelectedSize = selectedScale;
288565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
288665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
288765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /** get pixels of selected item */
288865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public int getSelectedSize() {
288965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mSelectedSize;
289065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
289165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
289265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setSelectedTakesMoreSpace(boolean selectedTakesMoreSpace) {
289365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mScroll.mainAxis().setSelectedTakesMoreSpace(selectedTakesMoreSpace);
289465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
289565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
289665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public boolean getSelectedTakesMoreSpace() {
289765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mScroll.mainAxis().getSelectedTakesMoreSpace();
289865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
289965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
290065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private boolean selectedItemCanScale() {
290165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mSelectedSize != 0 || mAdapterCustomSize != null;
290265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
290365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
290465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int getSelectedItemSize(int adapterIndex, View view) {
290565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mSelectedSize != 0) {
290665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return mSelectedSize;
290765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else if (mAdapterCustomSize != null) {
290865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return mAdapterCustomSize.getSelectItemSize(adapterIndex, view);
290965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
291065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return 0;
291165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
291265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
291365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static void assertFailure(String msg) {
291465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        throw new RuntimeException(msg);
291565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
291665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
291765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane}
2918