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