This week only. Pastebin PRO Accounts Christmas Special! Don't miss out!Want more features on Pastebin? Sign Up, it's FREE!
Guest

EcoGallery: Android Gallery Widget with recycling

By: a guest on May 4th, 2011  |  syntax: Java  |  size: 107.41 KB  |  views: 11,181  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. attrs.xml
  2.  
  3.     <?xml version="1.0" encoding="utf-8"?>
  4.     <resources>
  5.        
  6.         <declare-styleable name="EcoGallery">
  7.                 <attr name="gravity" format="integer" />
  8.                 <attr name="animationDuration" format="integer" />
  9.                 <attr name="unselectedAlpha" format="float" />
  10.                 <attr name="spacing" format="dimension" />
  11.         </declare-styleable>
  12.         <attr name="ecoGalleryStyle" format="reference" />
  13.        
  14.         <declare-styleable name="CustomAbsSpinner">
  15.                 <attr name="entries" format="reference" />
  16.         </declare-styleable>
  17.         <attr name="customAbsSpinnerStyle" format="reference" />
  18.     </resources>
  19.  
  20. CustomAdapterView
  21.  
  22.     public abstract class CustomAdapterView<T extends Adapter> extends ViewGroup {
  23.    
  24.         /**
  25.          * The item view type returned by {@link Adapter#getItemViewType(int)} when
  26.          * the adapter does not want the item's view recycled.
  27.          */
  28.         public static final int ITEM_VIEW_TYPE_IGNORE = -1;
  29.    
  30.         /**
  31.          * The item view type returned by {@link Adapter#getItemViewType(int)} when
  32.          * the item is a header or footer.
  33.          */
  34.         public static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2;
  35.    
  36.         /**
  37.          * The position of the first child displayed
  38.          */
  39.         int mFirstPosition = 0;
  40.    
  41.         /**
  42.          * The offset in pixels from the top of the AdapterView to the top
  43.          * of the view to select during the next layout.
  44.          */
  45.         int mSpecificTop;
  46.    
  47.         /**
  48.          * Position from which to start looking for mSyncRowId
  49.          */
  50.         int mSyncPosition;
  51.    
  52.         /**
  53.          * Row id to look for when data has changed
  54.          */
  55.         long mSyncRowId = INVALID_ROW_ID;
  56.    
  57.         /**
  58.          * Height of the view when mSyncPosition and mSyncRowId where set
  59.          */
  60.         long mSyncHeight;
  61.    
  62.         /**
  63.          * True if we need to sync to mSyncRowId
  64.          */
  65.         boolean mNeedSync = false;
  66.    
  67.         /**
  68.          * Indicates whether to sync based on the selection or position. Possible
  69.          * values are {@link #SYNC_SELECTED_POSITION} or
  70.          * {@link #SYNC_FIRST_POSITION}.
  71.          */
  72.         int mSyncMode;
  73.    
  74.         /**
  75.          * Our height after the last layout
  76.          */
  77.         private int mLayoutHeight;
  78.    
  79.         /**
  80.          * Sync based on the selected child
  81.          */
  82.         static final int SYNC_SELECTED_POSITION = 0;
  83.    
  84.         /**
  85.          * Sync based on the first child displayed
  86.          */
  87.         static final int SYNC_FIRST_POSITION = 1;
  88.    
  89.         /**
  90.          * Maximum amount of time to spend in {@link #findSyncPosition()}
  91.          */
  92.         static final int SYNC_MAX_DURATION_MILLIS = 100;
  93.    
  94.         /**
  95.          * Indicates that this view is currently being laid out.
  96.          */
  97.         boolean mInLayout = false;
  98.    
  99.         /**
  100.          * The listener that receives notifications when an item is selected.
  101.          */
  102.         OnItemSelectedListener mOnItemSelectedListener;
  103.    
  104.         /**
  105.          * The listener that receives notifications when an item is clicked.
  106.          */
  107.         OnItemClickListener mOnItemClickListener;
  108.    
  109.         /**
  110.          * The listener that receives notifications when an item is long clicked.
  111.          */
  112.         OnItemLongClickListener mOnItemLongClickListener;
  113.    
  114.         /**
  115.          * True if the data has changed since the last layout
  116.          */
  117.         boolean mDataChanged;
  118.    
  119.         /**
  120.          * The position within the adapter's data set of the item to select
  121.          * during the next layout.
  122.          */
  123.         int mNextSelectedPosition = INVALID_POSITION;
  124.    
  125.         /**
  126.          * The item id of the item to select during the next layout.
  127.          */
  128.         long mNextSelectedRowId = INVALID_ROW_ID;
  129.    
  130.         /**
  131.          * The position within the adapter's data set of the currently selected item.
  132.          */
  133.         int mSelectedPosition = INVALID_POSITION;
  134.    
  135.         /**
  136.          * The item id of the currently selected item.
  137.          */
  138.         long mSelectedRowId = INVALID_ROW_ID;
  139.    
  140.         /**
  141.          * View to show if there are no items to show.
  142.          */
  143.         private View mEmptyView;
  144.    
  145.         /**
  146.          * The number of items in the current adapter.
  147.          */
  148.         int mItemCount;
  149.    
  150.         /**
  151.          * The number of items in the adapter before a data changed event occured.
  152.          */
  153.         int mOldItemCount;
  154.    
  155.         /**
  156.          * Represents an invalid position. All valid positions are in the range 0 to 1 less than the
  157.          * number of items in the current adapter.
  158.          */
  159.         public static final int INVALID_POSITION = -1;
  160.    
  161.         /**
  162.          * Represents an empty or invalid row id
  163.          */
  164.         public static final long INVALID_ROW_ID = Long.MIN_VALUE;
  165.    
  166.         /**
  167.          * The last selected position we used when notifying
  168.          */
  169.         int mOldSelectedPosition = INVALID_POSITION;
  170.        
  171.         /**
  172.          * The id of the last selected position we used when notifying
  173.          */
  174.         long mOldSelectedRowId = INVALID_ROW_ID;
  175.    
  176.         /**
  177.          * Indicates what focusable state is requested when calling setFocusable().
  178.          * In addition to this, this view has other criteria for actually
  179.          * determining the focusable state (such as whether its empty or the text
  180.          * filter is shown).
  181.          *
  182.          * @see #setFocusable(boolean)
  183.          * @see #checkFocus()
  184.          */
  185.         private boolean mDesiredFocusableState;
  186.         private boolean mDesiredFocusableInTouchModeState;
  187.    
  188.         private SelectionNotifier mSelectionNotifier;
  189.         /**
  190.          * When set to true, calls to requestLayout() will not propagate up the parent hierarchy.
  191.          * This is used to layout the children during a layout pass.
  192.          */
  193.         boolean mBlockLayoutRequests = false;
  194.    
  195.         public CustomAdapterView(Context context) {
  196.             super(context);
  197.         }
  198.    
  199.         public CustomAdapterView(Context context, AttributeSet attrs) {
  200.             super(context, attrs);
  201.         }
  202.    
  203.         public CustomAdapterView(Context context, AttributeSet attrs, int defStyle) {
  204.             super(context, attrs, defStyle);
  205.         }
  206.    
  207.    
  208.         /**
  209.          * Interface definition for a callback to be invoked when an item in this
  210.          * AdapterView has been clicked.
  211.          */
  212.         public interface OnItemClickListener {
  213.    
  214.             /**
  215.              * Callback method to be invoked when an item in this AdapterView has
  216.              * been clicked.
  217.              * <p>
  218.              * Implementers can call getItemAtPosition(position) if they need
  219.              * to access the data associated with the selected item.
  220.              *
  221.              * @param parent The AdapterView where the click happened.
  222.              * @param view The view within the AdapterView that was clicked (this
  223.              *            will be a view provided by the adapter)
  224.              * @param position The position of the view in the adapter.
  225.              * @param id The row id of the item that was clicked.
  226.              */
  227.             void onItemClick(CustomAdapterView<?> parent, View view, int position, long id);
  228.         }
  229.    
  230.         /**
  231.          * Register a callback to be invoked when an item in this AdapterView has
  232.          * been clicked.
  233.          *
  234.          * @param listener The callback that will be invoked.
  235.          */
  236.         public void setOnItemClickListener(OnItemClickListener listener) {
  237.             mOnItemClickListener = listener;
  238.         }
  239.    
  240.         /**
  241.          * @return The callback to be invoked with an item in this AdapterView has
  242.          *         been clicked, or null id no callback has been set.
  243.          */
  244.         public final OnItemClickListener getOnItemClickListener() {
  245.             return mOnItemClickListener;
  246.         }
  247.    
  248.         /**
  249.          * Call the OnItemClickListener, if it is defined.
  250.          *
  251.          * @param view The view within the AdapterView that was clicked.
  252.          * @param position The position of the view in the adapter.
  253.          * @param id The row id of the item that was clicked.
  254.          * @return True if there was an assigned OnItemClickListener that was
  255.          *         called, false otherwise is returned.
  256.          */
  257.         public boolean performItemClick(View view, int position, long id) {
  258.             if (mOnItemClickListener != null) {
  259.                 playSoundEffect(SoundEffectConstants.CLICK);
  260.                 mOnItemClickListener.onItemClick(this, view, position, id);
  261.                 return true;
  262.             }
  263.    
  264.             return false;
  265.         }
  266.    
  267.         /**
  268.          * Interface definition for a callback to be invoked when an item in this
  269.          * view has been clicked and held.
  270.          */
  271.         public interface OnItemLongClickListener {
  272.             /**
  273.              * Callback method to be invoked when an item in this view has been
  274.              * clicked and held.
  275.              *
  276.              * Implementers can call getItemAtPosition(position) if they need to access
  277.              * the data associated with the selected item.
  278.              *
  279.              * @param parent The AbsListView where the click happened
  280.              * @param view The view within the AbsListView that was clicked
  281.              * @param position The position of the view in the list
  282.              * @param id The row id of the item that was clicked
  283.              *
  284.              * @return true if the callback consumed the long click, false otherwise
  285.              */
  286.             boolean onItemLongClick(CustomAdapterView<?> parent, View view, int position, long id);
  287.         }
  288.    
  289.    
  290.         /**
  291.          * Register a callback to be invoked when an item in this AdapterView has
  292.          * been clicked and held
  293.          *
  294.          * @param listener The callback that will run
  295.          */
  296.         public void setOnItemLongClickListener(OnItemLongClickListener listener) {
  297.             if (!isLongClickable()) {
  298.                 setLongClickable(true);
  299.             }
  300.             mOnItemLongClickListener = listener;
  301.         }
  302.    
  303.         /**
  304.          * @return The callback to be invoked with an item in this AdapterView has
  305.          *         been clicked and held, or null id no callback as been set.
  306.          */
  307.         public final OnItemLongClickListener getOnItemLongClickListener() {
  308.             return mOnItemLongClickListener;
  309.         }
  310.    
  311.         /**
  312.          * Interface definition for a callback to be invoked when
  313.          * an item in this view has been selected.
  314.          */
  315.         public interface OnItemSelectedListener {
  316.             /**
  317.              * Callback method to be invoked when an item in this view has been
  318.              * selected.
  319.              *
  320.              * Impelmenters can call getItemAtPosition(position) if they need to access the
  321.              * data associated with the selected item.
  322.              *
  323.              * @param parent The AdapterView where the selection happened
  324.              * @param view The view within the AdapterView that was clicked
  325.              * @param position The position of the view in the adapter
  326.              * @param id The row id of the item that is selected
  327.              */
  328.             void onItemSelected(CustomAdapterView<?> parent, View view, int position, long id);
  329.    
  330.             /**
  331.              * Callback method to be invoked when the selection disappears from this
  332.              * view. The selection can disappear for instance when touch is activated
  333.              * or when the adapter becomes empty.
  334.              *
  335.              * @param parent The AdapterView that now contains no selected item.
  336.              */
  337.             void onNothingSelected(CustomAdapterView<?> parent);
  338.         }
  339.    
  340.    
  341.         /**
  342.          * Register a callback to be invoked when an item in this AdapterView has
  343.          * been selected.
  344.          *
  345.          * @param listener The callback that will run
  346.          */
  347.         public void setOnItemSelectedListener(OnItemSelectedListener listener) {
  348.             mOnItemSelectedListener = listener;
  349.         }
  350.    
  351.         public final OnItemSelectedListener getOnItemSelectedListener() {
  352.             return mOnItemSelectedListener;
  353.         }
  354.    
  355.         /**
  356.          * Extra menu information provided to the
  357.          * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) }
  358.          * callback when a context menu is brought up for this AdapterView.
  359.          *
  360.          */
  361.         public static class AdapterContextMenuInfo implements ContextMenu.ContextMenuInfo {
  362.    
  363.             public AdapterContextMenuInfo(View targetView, int position, long id) {
  364.                 this.targetView = targetView;
  365.                 this.position = position;
  366.                 this.id = id;
  367.             }
  368.    
  369.             /**
  370.              * The child view for which the context menu is being displayed. This
  371.              * will be one of the children of this AdapterView.
  372.              */
  373.             public View targetView;
  374.    
  375.             /**
  376.              * The position in the adapter for which the context menu is being
  377.              * displayed.
  378.              */
  379.             public int position;
  380.    
  381.             /**
  382.              * The row id of the item for which the context menu is being displayed.
  383.              */
  384.             public long id;
  385.         }
  386.    
  387.         /**
  388.          * Returns the adapter currently associated with this widget.
  389.          *
  390.          * @return The adapter used to provide this view's content.
  391.          */
  392.         public abstract T getAdapter();
  393.    
  394.         /**
  395.          * Sets the adapter that provides the data and the views to represent the data
  396.          * in this widget.
  397.          *
  398.          * @param adapter The adapter to use to create this view's content.
  399.          */
  400.         public abstract void setAdapter(T adapter);
  401.    
  402.         /**
  403.          * This method is not supported and throws an UnsupportedOperationException when called.
  404.          *
  405.          * @param child Ignored.
  406.          *
  407.          * @throws UnsupportedOperationException Every time this method is invoked.
  408.          */
  409.         @Override
  410.         public void addView(View child) {
  411.             throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
  412.         }
  413.    
  414.         /**
  415.          * This method is not supported and throws an UnsupportedOperationException when called.
  416.          *
  417.          * @param child Ignored.
  418.          * @param index Ignored.
  419.          *
  420.          * @throws UnsupportedOperationException Every time this method is invoked.
  421.          */
  422.         @Override
  423.         public void addView(View child, int index) {
  424.             throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView");
  425.         }
  426.    
  427.         /**
  428.          * This method is not supported and throws an UnsupportedOperationException when called.
  429.          *
  430.          * @param child Ignored.
  431.          * @param params Ignored.
  432.          *
  433.          * @throws UnsupportedOperationException Every time this method is invoked.
  434.          */
  435.         @Override
  436.         public void addView(View child, LayoutParams params) {
  437.             throw new UnsupportedOperationException("addView(View, LayoutParams) "
  438.                     + "is not supported in AdapterView");
  439.         }
  440.    
  441.         /**
  442.          * This method is not supported and throws an UnsupportedOperationException when called.
  443.          *
  444.          * @param child Ignored.
  445.          * @param index Ignored.
  446.          * @param params Ignored.
  447.          *
  448.          * @throws UnsupportedOperationException Every time this method is invoked.
  449.          */
  450.         @Override
  451.         public void addView(View child, int index, LayoutParams params) {
  452.             throw new UnsupportedOperationException("addView(View, int, LayoutParams) "
  453.                     + "is not supported in AdapterView");
  454.         }
  455.    
  456.         /**
  457.          * This method is not supported and throws an UnsupportedOperationException when called.
  458.          *
  459.          * @param child Ignored.
  460.          *
  461.          * @throws UnsupportedOperationException Every time this method is invoked.
  462.          */
  463.         @Override
  464.         public void removeView(View child) {
  465.             throw new UnsupportedOperationException("removeView(View) is not supported in AdapterView");
  466.         }
  467.    
  468.         /**
  469.          * This method is not supported and throws an UnsupportedOperationException when called.
  470.          *
  471.          * @param index Ignored.
  472.          *
  473.          * @throws UnsupportedOperationException Every time this method is invoked.
  474.          */
  475.         @Override
  476.         public void removeViewAt(int index) {
  477.             throw new UnsupportedOperationException("removeViewAt(int) is not supported in AdapterView");
  478.         }
  479.    
  480.         /**
  481.          * This method is not supported and throws an UnsupportedOperationException when called.
  482.          *
  483.          * @throws UnsupportedOperationException Every time this method is invoked.
  484.          */
  485.         @Override
  486.         public void removeAllViews() {
  487.             throw new UnsupportedOperationException("removeAllViews() is not supported in AdapterView");
  488.         }
  489.    
  490.         @Override
  491.         protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  492.             mLayoutHeight = getHeight();
  493.         }
  494.    
  495.         /**
  496.          * Return the position of the currently selected item within the adapter's data set
  497.          *
  498.          * @return int Position (starting at 0), or {@link #INVALID_POSITION} if there is nothing selected.
  499.          */
  500.         @ViewDebug.CapturedViewProperty
  501.         public int getSelectedItemPosition() {
  502.             return mNextSelectedPosition;
  503.         }
  504.    
  505.         /**
  506.          * @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID}
  507.          * if nothing is selected.
  508.          */
  509.         @ViewDebug.CapturedViewProperty
  510.         public long getSelectedItemId() {
  511.             return mNextSelectedRowId;
  512.         }
  513.    
  514.         /**
  515.          * @return The view corresponding to the currently selected item, or null
  516.          * if nothing is selected
  517.          */
  518.         public abstract View getSelectedView();
  519.    
  520.         /**
  521.          * @return The data corresponding to the currently selected item, or
  522.          * null if there is nothing selected.
  523.          */
  524.         public Object getSelectedItem() {
  525.             T adapter = getAdapter();
  526.             int selection = getSelectedItemPosition();
  527.             if (adapter != null && adapter.getCount() > 0 && selection >= 0) {
  528.                 return adapter.getItem(selection);
  529.             } else {
  530.                 return null;
  531.             }
  532.         }
  533.    
  534.         /**
  535.          * @return The number of items owned by the Adapter associated with this
  536.          *         AdapterView. (This is the number of data items, which may be
  537.          *         larger than the number of visible view.)
  538.          */
  539.         @ViewDebug.CapturedViewProperty
  540.         public int getCount() {
  541.             return mItemCount;
  542.         }
  543.    
  544.         /**
  545.          * Get the position within the adapter's data set for the view, where view is a an adapter item
  546.          * or a descendant of an adapter item.
  547.          *
  548.          * @param view an adapter item, or a descendant of an adapter item. This must be visible in this
  549.          *        AdapterView at the time of the call.
  550.          * @return the position within the adapter's data set of the view, or {@link #INVALID_POSITION}
  551.          *         if the view does not correspond to a list item (or it is not currently visible).
  552.          */
  553.         public int getPositionForView(View view) {
  554.             View listItem = view;
  555.             try {
  556.                 View v;
  557.                 while (!(v = (View) listItem.getParent()).equals(this)) {
  558.                     listItem = v;
  559.                 }
  560.             } catch (ClassCastException e) {
  561.                 // We made it up to the window without find this list view
  562.                 return INVALID_POSITION;
  563.             }
  564.    
  565.             // Search the children for the list item
  566.             final int childCount = getChildCount();
  567.             for (int i = 0; i < childCount; i++) {
  568.                 if (getChildAt(i).equals(listItem)) {
  569.                     return mFirstPosition + i;
  570.                 }
  571.             }
  572.    
  573.             // Child not found!
  574.             return INVALID_POSITION;
  575.         }
  576.    
  577.         /**
  578.          * Returns the position within the adapter's data set for the first item
  579.          * displayed on screen.
  580.          *
  581.          * @return The position within the adapter's data set
  582.          */
  583.         public int getFirstVisiblePosition() {
  584.             return mFirstPosition;
  585.         }
  586.    
  587.         /**
  588.          * Returns the position within the adapter's data set for the last item
  589.          * displayed on screen.
  590.          *
  591.          * @return The position within the adapter's data set
  592.          */
  593.         public int getLastVisiblePosition() {
  594.             return mFirstPosition + getChildCount() - 1;
  595.         }
  596.    
  597.         /**
  598.          * Sets the currently selected item. To support accessibility subclasses that
  599.          * override this method must invoke the overriden super method first.
  600.          *
  601.          * @param position Index (starting at 0) of the data item to be selected.
  602.          */
  603.         public abstract void setSelection(int position);
  604.    
  605.         /**
  606.          * Sets the view to show if the adapter is empty
  607.          */
  608.         public void setEmptyView(View emptyView) {
  609.             mEmptyView = emptyView;
  610.    
  611.             final T adapter = getAdapter();
  612.             final boolean empty = ((adapter == null) || adapter.isEmpty());
  613.             updateEmptyStatus(empty);
  614.         }
  615.    
  616.         /**
  617.          * When the current adapter is empty, the AdapterView can display a special view
  618.          * call the empty view. The empty view is used to provide feedback to the user
  619.          * that no data is available in this AdapterView.
  620.          *
  621.          * @return The view to show if the adapter is empty.
  622.          */
  623.         public View getEmptyView() {
  624.             return mEmptyView;
  625.         }
  626.    
  627.         /**
  628.          * Indicates whether this view is in filter mode. Filter mode can for instance
  629.          * be enabled by a user when typing on the keyboard.
  630.          *
  631.          * @return True if the view is in filter mode, false otherwise.
  632.          */
  633.         boolean isInFilterMode() {
  634.             return false;
  635.         }
  636.    
  637.         @Override
  638.         public void setFocusable(boolean focusable) {
  639.             final T adapter = getAdapter();
  640.             final boolean empty = adapter == null || adapter.getCount() == 0;
  641.    
  642.             mDesiredFocusableState = focusable;
  643.             if (!focusable) {
  644.                 mDesiredFocusableInTouchModeState = false;
  645.             }
  646.    
  647.             super.setFocusable(focusable && (!empty || isInFilterMode()));
  648.         }
  649.    
  650.         @Override
  651.         public void setFocusableInTouchMode(boolean focusable) {
  652.             final T adapter = getAdapter();
  653.             final boolean empty = adapter == null || adapter.getCount() == 0;
  654.    
  655.             mDesiredFocusableInTouchModeState = focusable;
  656.             if (focusable) {
  657.                 mDesiredFocusableState = true;
  658.             }
  659.    
  660.             super.setFocusableInTouchMode(focusable && (!empty || isInFilterMode()));
  661.         }
  662.    
  663.         void checkFocus() {
  664.             final T adapter = getAdapter();
  665.             final boolean empty = adapter == null || adapter.getCount() == 0;
  666.             final boolean focusable = !empty || isInFilterMode();
  667.             // The order in which we set focusable in touch mode/focusable may matter
  668.             // for the client, see View.setFocusableInTouchMode() comments for more
  669.             // details
  670.             super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
  671.             super.setFocusable(focusable && mDesiredFocusableState);
  672.             if (mEmptyView != null) {
  673.                 updateEmptyStatus((adapter == null) || adapter.isEmpty());
  674.             }
  675.         }
  676.    
  677.         /**
  678.          * Update the status of the list based on the empty parameter.  If empty is true and
  679.          * we have an empty view, display it.  In all the other cases, make sure that the listview
  680.          * is VISIBLE and that the empty view is GONE (if it's not null).
  681.          */
  682.         private void updateEmptyStatus(boolean empty) {
  683.             if (isInFilterMode()) {
  684.                 empty = false;
  685.             }
  686.    
  687.             if (empty) {
  688.                 if (mEmptyView != null) {
  689.                     mEmptyView.setVisibility(View.VISIBLE);
  690.                     setVisibility(View.GONE);
  691.                 } else {
  692.                     // If the caller just removed our empty view, make sure the list view is visible
  693.                     setVisibility(View.VISIBLE);
  694.                 }
  695.    
  696.                 // We are now GONE, so pending layouts will not be dispatched.
  697.                 // Force one here to make sure that the state of the list matches
  698.                 // the state of the adapter.
  699.                 if (mDataChanged) {          
  700.                     this.onLayout(false, getLeft(), getTop(), getRight(), getBottom());
  701.                 }
  702.             } else {
  703.                 if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
  704.                 setVisibility(View.VISIBLE);
  705.             }
  706.         }
  707.    
  708.         /**
  709.          * Gets the data associated with the specified position in the list.
  710.          *
  711.          * @param position Which data to get
  712.          * @return The data associated with the specified position in the list
  713.          */
  714.         public Object getItemAtPosition(int position) {
  715.             T adapter = getAdapter();
  716.             return (adapter == null || position < 0) ? null : adapter.getItem(position);
  717.         }
  718.    
  719.         public long getItemIdAtPosition(int position) {
  720.             T adapter = getAdapter();
  721.             return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position);
  722.         }
  723.    
  724.         @Override
  725.         public void setOnClickListener(OnClickListener l) {
  726.             throw new RuntimeException("Don't call setOnClickListener for an AdapterView. "
  727.                     + "You probably want setOnItemClickListener instead");
  728.         }
  729.    
  730.         /**
  731.          * Override to prevent freezing of any views created by the adapter.
  732.          */
  733.         @Override
  734.         protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
  735.             dispatchFreezeSelfOnly(container);
  736.         }
  737.    
  738.         /**
  739.          * Override to prevent thawing of any views created by the adapter.
  740.          */
  741.         @Override
  742.         protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
  743.             dispatchThawSelfOnly(container);
  744.         }
  745.    
  746.         class AdapterDataSetObserver extends DataSetObserver {
  747.    
  748.             private Parcelable mInstanceState = null;
  749.    
  750.             @Override
  751.             public void onChanged() {
  752.                 mDataChanged = true;
  753.                 mOldItemCount = mItemCount;
  754.                 mItemCount = getAdapter().getCount();
  755.    
  756.                 // Detect the case where a cursor that was previously invalidated has
  757.                 // been repopulated with new data.
  758.                 if (CustomAdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
  759.                         && mOldItemCount == 0 && mItemCount > 0) {
  760.                     CustomAdapterView.this.onRestoreInstanceState(mInstanceState);
  761.                     mInstanceState = null;
  762.                 } else {
  763.                     rememberSyncState();
  764.                 }
  765.                 checkFocus();
  766.                 requestLayout();
  767.             }
  768.    
  769.             @Override
  770.             public void onInvalidated() {
  771.                 mDataChanged = true;
  772.    
  773.                 if (CustomAdapterView.this.getAdapter().hasStableIds()) {
  774.                     // Remember the current state for the case where our hosting activity is being
  775.                     // stopped and later restarted
  776.                     mInstanceState = CustomAdapterView.this.onSaveInstanceState();
  777.                 }
  778.    
  779.                 // Data is invalid so we should reset our state
  780.                 mOldItemCount = mItemCount;
  781.                 mItemCount = 0;
  782.                 mSelectedPosition = INVALID_POSITION;
  783.                 mSelectedRowId = INVALID_ROW_ID;
  784.                 mNextSelectedPosition = INVALID_POSITION;
  785.                 mNextSelectedRowId = INVALID_ROW_ID;
  786.                 mNeedSync = false;
  787.    
  788.                 checkFocus();
  789.                 requestLayout();
  790.             }
  791.    
  792.             public void clearSavedState() {
  793.                 mInstanceState = null;
  794.             }
  795.         }
  796.    
  797.         @Override
  798.         protected void onDetachedFromWindow() {
  799.             super.onDetachedFromWindow();
  800.             removeCallbacks(mSelectionNotifier);
  801.         }
  802.    
  803.         private class SelectionNotifier implements Runnable {
  804.             public void run() {
  805.                 if (mDataChanged) {
  806.                     // Data has changed between when this SelectionNotifier
  807.                     // was posted and now. We need to wait until the AdapterView
  808.                     // has been synched to the new data.
  809.                     if (getAdapter() != null) {
  810.                         post(this);
  811.                     }
  812.                 } else {
  813.                     fireOnSelected();
  814.                 }
  815.             }
  816.         }
  817.    
  818.         void selectionChanged() {
  819.             if (mOnItemSelectedListener != null) {
  820.                 if (mInLayout || mBlockLayoutRequests) {
  821.                     // If we are in a layout traversal, defer notification
  822.                     // by posting. This ensures that the view tree is
  823.                     // in a consistent state and is able to accomodate
  824.                     // new layout or invalidate requests.
  825.                     if (mSelectionNotifier == null) {
  826.                         mSelectionNotifier = new SelectionNotifier();
  827.                     }
  828.                     post(mSelectionNotifier);
  829.                 } else {
  830.                     fireOnSelected();
  831.                 }
  832.             }
  833.    
  834.             // we fire selection events here not in View
  835.             if (mSelectedPosition != ListView.INVALID_POSITION && isShown() && !isInTouchMode()) {
  836.                 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
  837.             }
  838.         }
  839.    
  840.         private void fireOnSelected() {
  841.             if (mOnItemSelectedListener == null)
  842.                 return;
  843.    
  844.             int selection = this.getSelectedItemPosition();
  845.             if (selection >= 0) {
  846.                 View v = getSelectedView();
  847.                 mOnItemSelectedListener.onItemSelected(this, v, selection,
  848.                         getAdapter().getItemId(selection));
  849.             } else {
  850.                 mOnItemSelectedListener.onNothingSelected(this);
  851.             }
  852.         }
  853.    
  854.         @Override
  855.         public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
  856.             boolean populated = false;
  857.             // This is an exceptional case which occurs when a window gets the
  858.             // focus and sends a focus event via its focused child to announce
  859.             // current focus/selection. AdapterView fires selection but not focus
  860.             // events so we change the event type here.
  861.             if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
  862.                 event.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED);
  863.             }
  864.    
  865.             // we send selection events only from AdapterView to avoid
  866.             // generation of such event for each child
  867.             View selectedView = getSelectedView();
  868.             if (selectedView != null) {
  869.                 populated = selectedView.dispatchPopulateAccessibilityEvent(event);
  870.             }
  871.    
  872.             if (!populated) {
  873.                 if (selectedView != null) {
  874.                     event.setEnabled(selectedView.isEnabled());
  875.                 }
  876.                 event.setItemCount(getCount());
  877.                 event.setCurrentItemIndex(getSelectedItemPosition());
  878.             }
  879.    
  880.             return populated;
  881.         }
  882.    
  883.         @Override
  884.         protected boolean canAnimate() {
  885.             return super.canAnimate() && mItemCount > 0;
  886.         }
  887.    
  888.         void handleDataChanged() {
  889.             final int count = mItemCount;
  890.             boolean found = false;
  891.    
  892.             if (count > 0) {
  893.    
  894.                 int newPos;
  895.    
  896.                 // Find the row we are supposed to sync to
  897.                 if (mNeedSync) {
  898.                     // Update this first, since setNextSelectedPositionInt inspects
  899.                     // it
  900.                     mNeedSync = false;
  901.    
  902.                     // See if we can find a position in the new data with the same
  903.                     // id as the old selection
  904.                     newPos = findSyncPosition();
  905.                     if (newPos >= 0) {
  906.                         // Verify that new selection is selectable
  907.                         int selectablePos = lookForSelectablePosition(newPos, true);
  908.                         if (selectablePos == newPos) {
  909.                             // Same row id is selected
  910.                             setNextSelectedPositionInt(newPos);
  911.                             found = true;
  912.                         }
  913.                     }
  914.                 }
  915.                 if (!found) {
  916.                     // Try to use the same position if we can't find matching data
  917.                     newPos = getSelectedItemPosition();
  918.    
  919.                     // Pin position to the available range
  920.                     if (newPos >= count) {
  921.                         newPos = count - 1;
  922.                     }
  923.                     if (newPos < 0) {
  924.                         newPos = 0;
  925.                     }
  926.    
  927.                     // Make sure we select something selectable -- first look down
  928.                     int selectablePos = lookForSelectablePosition(newPos, true);
  929.                     if (selectablePos < 0) {
  930.                         // Looking down didn't work -- try looking up
  931.                         selectablePos = lookForSelectablePosition(newPos, false);
  932.                     }
  933.                     if (selectablePos >= 0) {
  934.                         setNextSelectedPositionInt(selectablePos);
  935.                         checkSelectionChanged();
  936.                         found = true;
  937.                     }
  938.                 }
  939.             }
  940.             if (!found) {
  941.                 // Nothing is selected
  942.                 mSelectedPosition = INVALID_POSITION;
  943.                 mSelectedRowId = INVALID_ROW_ID;
  944.                 mNextSelectedPosition = INVALID_POSITION;
  945.                 mNextSelectedRowId = INVALID_ROW_ID;
  946.                 mNeedSync = false;
  947.                 checkSelectionChanged();
  948.             }
  949.         }
  950.    
  951.         void checkSelectionChanged() {
  952.             if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
  953.                 selectionChanged();
  954.                 mOldSelectedPosition = mSelectedPosition;
  955.                 mOldSelectedRowId = mSelectedRowId;
  956.             }
  957.         }
  958.    
  959.         /**
  960.          * Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition
  961.          * and then alternates between moving up and moving down until 1) we find the right position, or
  962.          * 2) we run out of time, or 3) we have looked at every position
  963.          *
  964.          * @return Position of the row that matches mSyncRowId, or {@link #INVALID_POSITION} if it can't
  965.          *         be found
  966.          */
  967.         int findSyncPosition() {
  968.             int count = mItemCount;
  969.    
  970.             if (count == 0) {
  971.                 return INVALID_POSITION;
  972.             }
  973.    
  974.             long idToMatch = mSyncRowId;
  975.             int seed = mSyncPosition;
  976.    
  977.             // If there isn't a selection don't hunt for it
  978.             if (idToMatch == INVALID_ROW_ID) {
  979.                 return INVALID_POSITION;
  980.             }
  981.    
  982.             // Pin seed to reasonable values
  983.             seed = Math.max(0, seed);
  984.             seed = Math.min(count - 1, seed);
  985.    
  986.             long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS;
  987.    
  988.             long rowId;
  989.    
  990.             // first position scanned so far
  991.             int first = seed;
  992.    
  993.             // last position scanned so far
  994.             int last = seed;
  995.    
  996.             // True if we should move down on the next iteration
  997.             boolean next = false;
  998.    
  999.             // True when we have looked at the first item in the data
  1000.             boolean hitFirst;
  1001.    
  1002.             // True when we have looked at the last item in the data
  1003.             boolean hitLast;
  1004.    
  1005.             // Get the item ID locally (instead of getItemIdAtPosition), so
  1006.             // we need the adapter
  1007.             T adapter = getAdapter();
  1008.             if (adapter == null) {
  1009.                 return INVALID_POSITION;
  1010.             }
  1011.    
  1012.             while (SystemClock.uptimeMillis() <= endTime) {
  1013.                 rowId = adapter.getItemId(seed);
  1014.                 if (rowId == idToMatch) {
  1015.                     // Found it!
  1016.                     return seed;
  1017.                 }
  1018.    
  1019.                 hitLast = last == count - 1;
  1020.                 hitFirst = first == 0;
  1021.    
  1022.                 if (hitLast && hitFirst) {
  1023.                     // Looked at everything
  1024.                     break;
  1025.                 }
  1026.    
  1027.                 if (hitFirst || (next && !hitLast)) {
  1028.                     // Either we hit the top, or we are trying to move down
  1029.                     last++;
  1030.                     seed = last;
  1031.                     // Try going up next time
  1032.                     next = false;
  1033.                 } else if (hitLast || (!next && !hitFirst)) {
  1034.                     // Either we hit the bottom, or we are trying to move up
  1035.                     first--;
  1036.                     seed = first;
  1037.                     // Try going down next time
  1038.                     next = true;
  1039.                 }
  1040.    
  1041.             }
  1042.    
  1043.             return INVALID_POSITION;
  1044.         }
  1045.    
  1046.         /**
  1047.          * Find a position that can be selected (i.e., is not a separator).
  1048.          *
  1049.          * @param position The starting position to look at.
  1050.          * @param lookDown Whether to look down for other positions.
  1051.          * @return The next selectable position starting at position and then searching either up or
  1052.          *         down. Returns {@link #INVALID_POSITION} if nothing can be found.
  1053.          */
  1054.         int lookForSelectablePosition(int position, boolean lookDown) {
  1055.             return position;
  1056.         }
  1057.    
  1058.         /**
  1059.          * Utility to keep mSelectedPosition and mSelectedRowId in sync
  1060.          * @param position Our current position
  1061.          */
  1062.         void setSelectedPositionInt(int position) {
  1063.             mSelectedPosition = position;
  1064.             mSelectedRowId = getItemIdAtPosition(position);
  1065.         }
  1066.    
  1067.         /**
  1068.          * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync
  1069.          * @param position Intended value for mSelectedPosition the next time we go
  1070.          * through layout
  1071.          */
  1072.         void setNextSelectedPositionInt(int position) {
  1073.             mNextSelectedPosition = position;
  1074.             mNextSelectedRowId = getItemIdAtPosition(position);
  1075.             // If we are trying to sync to the selection, update that too
  1076.             if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) {
  1077.                 mSyncPosition = position;
  1078.                 mSyncRowId = mNextSelectedRowId;
  1079.             }
  1080.         }
  1081.    
  1082.         /**
  1083.          * Remember enough information to restore the screen state when the data has
  1084.          * changed.
  1085.          *
  1086.          */
  1087.         void rememberSyncState() {
  1088.             if (getChildCount() > 0) {
  1089.                 mNeedSync = true;
  1090.                 mSyncHeight = mLayoutHeight;
  1091.                 if (mSelectedPosition >= 0) {
  1092.                     // Sync the selection state
  1093.                     View v = getChildAt(mSelectedPosition - mFirstPosition);
  1094.                     mSyncRowId = mNextSelectedRowId;
  1095.                     mSyncPosition = mNextSelectedPosition;
  1096.                     if (v != null) {
  1097.                         mSpecificTop = v.getTop();
  1098.                     }
  1099.                     mSyncMode = SYNC_SELECTED_POSITION;
  1100.                 } else {
  1101.                     // Sync the based on the offset of the first view
  1102.                     View v = getChildAt(0);
  1103.                     T adapter = getAdapter();
  1104.                     if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
  1105.                         mSyncRowId = adapter.getItemId(mFirstPosition);
  1106.                     } else {
  1107.                         mSyncRowId = NO_ID;
  1108.                     }
  1109.                     mSyncPosition = mFirstPosition;
  1110.                     if (v != null) {
  1111.                         mSpecificTop = v.getTop();
  1112.                     }
  1113.                     mSyncMode = SYNC_FIRST_POSITION;
  1114.                 }
  1115.             }
  1116.         }
  1117.     }
  1118.  
  1119. <br>
  1120. CustomAbsSpinner: added the `add` and `get` methods to recycle bin that ignore position, modified recycler use in the class to use that.  
  1121.  
  1122.     public abstract class CustomAbsSpinner extends CustomAdapterView<SpinnerAdapter> {
  1123.    
  1124.         SpinnerAdapter mAdapter;
  1125.    
  1126.         int mHeightMeasureSpec;
  1127.         int mWidthMeasureSpec;
  1128.         boolean mBlockLayoutRequests;
  1129.         int mSelectionLeftPadding = 0;
  1130.         int mSelectionTopPadding = 0;
  1131.         int mSelectionRightPadding = 0;
  1132.         int mSelectionBottomPadding = 0;
  1133.         Rect mSpinnerPadding = new Rect();
  1134.         View mSelectedView = null;
  1135.         Interpolator mInterpolator;
  1136.    
  1137.         RecycleBin mRecycler = new RecycleBin();
  1138.         private DataSetObserver mDataSetObserver;
  1139.    
  1140.    
  1141.         /** Temporary frame to hold a child View's frame rectangle */
  1142.         private Rect mTouchFrame;
  1143.    
  1144.         public CustomAbsSpinner(Context context) {
  1145.             super(context);
  1146.             initAbsSpinner();
  1147.         }
  1148.    
  1149.         public CustomAbsSpinner(Context context, AttributeSet attrs) {
  1150.             this(context, attrs, 0);
  1151.         }
  1152.    
  1153.         public CustomAbsSpinner(Context context, AttributeSet attrs, int defStyle) {
  1154.             super(context, attrs, defStyle);
  1155.             initAbsSpinner();
  1156.    
  1157.             TypedArray a = context.obtainStyledAttributes(attrs,
  1158.                     R.styleable.CustomAbsSpinner, defStyle, 0);
  1159.    
  1160.             CharSequence[] entries = a.getTextArray(R.styleable.CustomAbsSpinner_entries);
  1161.             if (entries != null) {
  1162.                 ArrayAdapter<CharSequence> adapter =
  1163.                         new ArrayAdapter<CharSequence>(context,
  1164.                                 R.layout.simple_spinner_item, entries);
  1165.                 adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item);
  1166.                 setAdapter(adapter);
  1167.             }
  1168.    
  1169.             a.recycle();
  1170.         }
  1171.    
  1172.         /**
  1173.          * Common code for different constructor flavors
  1174.          */
  1175.         private void initAbsSpinner() {
  1176.             setFocusable(true);
  1177.             setWillNotDraw(false);
  1178.         }
  1179.    
  1180.    
  1181.         /**
  1182.          * The Adapter is used to provide the data which backs this Spinner.
  1183.          * It also provides methods to transform spinner items based on their position
  1184.          * relative to the selected item.
  1185.          * @param adapter The SpinnerAdapter to use for this Spinner
  1186.          */
  1187.         @Override
  1188.         public void setAdapter(SpinnerAdapter adapter) {
  1189.             if (null != mAdapter) {
  1190.                 mAdapter.unregisterDataSetObserver(mDataSetObserver);
  1191.                 resetList();
  1192.             }
  1193.            
  1194.             mAdapter = adapter;
  1195.            
  1196.             mOldSelectedPosition = INVALID_POSITION;
  1197.             mOldSelectedRowId = INVALID_ROW_ID;
  1198.            
  1199.             if (mAdapter != null) {
  1200.                 mOldItemCount = mItemCount;
  1201.                 mItemCount = mAdapter.getCount();
  1202.                 checkFocus();
  1203.    
  1204.                 mDataSetObserver = new AdapterDataSetObserver();
  1205.                 mAdapter.registerDataSetObserver(mDataSetObserver);
  1206.    
  1207.                 int position = mItemCount > 0 ? 0 : INVALID_POSITION;
  1208.    
  1209.                 setSelectedPositionInt(position);
  1210.                 setNextSelectedPositionInt(position);
  1211.                
  1212.                 if (mItemCount == 0) {
  1213.                     // Nothing selected
  1214.                     checkSelectionChanged();
  1215.                 }
  1216.                
  1217.             } else {
  1218.                 checkFocus();            
  1219.                 resetList();
  1220.                 // Nothing selected
  1221.                 checkSelectionChanged();
  1222.             }
  1223.    
  1224.             requestLayout();
  1225.         }
  1226.    
  1227.         /**
  1228.          * Clear out all children from the list
  1229.          */
  1230.         void resetList() {
  1231.             mDataChanged = false;
  1232.             mNeedSync = false;
  1233.            
  1234.             removeAllViewsInLayout();
  1235.             mOldSelectedPosition = INVALID_POSITION;
  1236.             mOldSelectedRowId = INVALID_ROW_ID;
  1237.            
  1238.             setSelectedPositionInt(INVALID_POSITION);
  1239.             setNextSelectedPositionInt(INVALID_POSITION);
  1240.             invalidate();
  1241.         }
  1242.    
  1243.         /**
  1244.          * @see android.view.View#measure(int, int)
  1245.          *
  1246.          * Figure out the dimensions of this Spinner. The width comes from
  1247.          * the widthMeasureSpec as Spinnners can't have their width set to
  1248.          * UNSPECIFIED. The height is based on the height of the selected item
  1249.          * plus padding.
  1250.          */
  1251.         @Override
  1252.         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  1253.             int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  1254.             int widthSize;
  1255.             int heightSize;
  1256.    
  1257.             int paddingLeft = getPaddingLeft();
  1258.             int paddingRight = getPaddingRight();
  1259.             int paddingTop = getPaddingTop();
  1260.             int paddingBottom = getPaddingBottom();
  1261.            
  1262.             mSpinnerPadding.left = paddingLeft > mSelectionLeftPadding ? paddingLeft
  1263.                     : mSelectionLeftPadding;
  1264.             mSpinnerPadding.top = paddingTop > mSelectionTopPadding ? paddingTop
  1265.                     : mSelectionTopPadding;
  1266.             mSpinnerPadding.right = paddingRight > mSelectionRightPadding ? paddingRight
  1267.                     : mSelectionRightPadding;
  1268.             mSpinnerPadding.bottom = paddingBottom > mSelectionBottomPadding ? paddingBottom
  1269.                     : mSelectionBottomPadding;
  1270.    
  1271.             if (mDataChanged) {
  1272.                 handleDataChanged();
  1273.             }
  1274.            
  1275.             int preferredHeight = 0;
  1276.             int preferredWidth = 0;
  1277.             boolean needsMeasuring = true;
  1278.            
  1279.             int selectedPosition = getSelectedItemPosition();
  1280.             if (selectedPosition >= 0 && mAdapter != null) {
  1281.                 // Try looking in the recycler. (Maybe we were measured once already)
  1282.                 View view = mRecycler.get();
  1283.                 if (view == null) {
  1284.                     // Make a new one
  1285.                     view = mAdapter.getView(selectedPosition, null, this);
  1286.                 }
  1287.    
  1288.                 if (view != null) {
  1289.                     // Put in recycler for re-measuring and/or layout
  1290.                     mRecycler.add(selectedPosition, view);
  1291.                 }
  1292.    
  1293.                 if (view != null) {
  1294.                     if (view.getLayoutParams() == null) {
  1295.                         mBlockLayoutRequests = true;
  1296.                         view.setLayoutParams(generateDefaultLayoutParams());
  1297.                         mBlockLayoutRequests = false;
  1298.                     }
  1299.                     measureChild(view, widthMeasureSpec, heightMeasureSpec);
  1300.                    
  1301.                     preferredHeight = getChildHeight(view) + mSpinnerPadding.top + mSpinnerPadding.bottom;
  1302.                     preferredWidth = getChildWidth(view) + mSpinnerPadding.left + mSpinnerPadding.right;
  1303.                    
  1304.                     needsMeasuring = false;
  1305.                 }
  1306.             }
  1307.            
  1308.             if (needsMeasuring) {
  1309.                 // No views -- just use padding
  1310.                 preferredHeight = mSpinnerPadding.top + mSpinnerPadding.bottom;
  1311.                 if (widthMode == MeasureSpec.UNSPECIFIED) {
  1312.                     preferredWidth = mSpinnerPadding.left + mSpinnerPadding.right;
  1313.                 }
  1314.             }
  1315.    
  1316.             preferredHeight = Math.max(preferredHeight, getSuggestedMinimumHeight());
  1317.             preferredWidth = Math.max(preferredWidth, getSuggestedMinimumWidth());
  1318.    
  1319.             heightSize = resolveSize(preferredHeight, heightMeasureSpec);
  1320.             widthSize = resolveSize(preferredWidth, widthMeasureSpec);
  1321.    
  1322.             setMeasuredDimension(widthSize, heightSize);
  1323.             mHeightMeasureSpec = heightMeasureSpec;
  1324.             mWidthMeasureSpec = widthMeasureSpec;
  1325.         }
  1326.    
  1327.        
  1328.         int getChildHeight(View child) {
  1329.             return child.getMeasuredHeight();
  1330.         }
  1331.        
  1332.         int getChildWidth(View child) {
  1333.             return child.getMeasuredWidth();
  1334.         }
  1335.        
  1336.         @Override
  1337.         protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
  1338.             return new ViewGroup.LayoutParams(
  1339.                     ViewGroup.LayoutParams.FILL_PARENT,
  1340.                     ViewGroup.LayoutParams.WRAP_CONTENT);
  1341.         }
  1342.        
  1343.         void recycleAllViews() {
  1344.             int childCount = getChildCount();
  1345.             final CustomAbsSpinner.RecycleBin recycleBin = mRecycler;
  1346.    
  1347.             // All views go in recycler
  1348.             for (int i=0; i<childCount; i++) {
  1349.                 View v = getChildAt(i);
  1350.                 int index = mFirstPosition + i;
  1351.                 recycleBin.put(index, v);
  1352.             }  
  1353.         }
  1354.        
  1355.         @Override
  1356.         void handleDataChanged() {
  1357.             // FIXME -- this is called from both measure and layout.
  1358.             // This is harmless right now, but we don't want to do redundant work if
  1359.             // this gets more complicated
  1360.            super.handleDataChanged();
  1361.         }
  1362.        
  1363.      
  1364.    
  1365.         /**
  1366.          * Jump directly to a specific item in the adapter data.
  1367.          */
  1368.         public void setSelection(int position, boolean animate) {
  1369.             // Animate only if requested position is already on screen somewhere
  1370.             boolean shouldAnimate = animate && mFirstPosition <= position &&
  1371.                     position <= mFirstPosition + getChildCount() - 1;
  1372.             setSelectionInt(position, shouldAnimate);
  1373.         }
  1374.        
  1375.    
  1376.         @Override
  1377.         public void setSelection(int position) {
  1378.             setNextSelectedPositionInt(position);
  1379.             requestLayout();
  1380.             invalidate();
  1381.         }
  1382.        
  1383.    
  1384.         /**
  1385.          * Makes the item at the supplied position selected.
  1386.          *
  1387.          * @param position Position to select
  1388.          * @param animate Should the transition be animated
  1389.          *
  1390.          */
  1391.         void setSelectionInt(int position, boolean animate) {
  1392.             if (position != mOldSelectedPosition) {
  1393.                 mBlockLayoutRequests = true;
  1394.                 int delta  = position - mSelectedPosition;
  1395.                 setNextSelectedPositionInt(position);
  1396.                 layout(delta, animate);
  1397.                 mBlockLayoutRequests = false;
  1398.             }
  1399.         }
  1400.    
  1401.         abstract void layout(int delta, boolean animate);
  1402.    
  1403.         @Override
  1404.         public View getSelectedView() {
  1405.             if (mItemCount > 0 && mSelectedPosition >= 0) {
  1406.                 return getChildAt(mSelectedPosition - mFirstPosition);
  1407.             } else {
  1408.                 return null;
  1409.             }
  1410.         }
  1411.        
  1412.         /**
  1413.          * Override to prevent spamming ourselves with layout requests
  1414.          * as we place views
  1415.          *
  1416.          * @see android.view.View#requestLayout()
  1417.          */
  1418.         @Override
  1419.         public void requestLayout() {
  1420.             if (!mBlockLayoutRequests) {
  1421.                 super.requestLayout();
  1422.             }
  1423.         }
  1424.    
  1425.      
  1426.    
  1427.         @Override
  1428.         public SpinnerAdapter getAdapter() {
  1429.             return mAdapter;
  1430.         }
  1431.    
  1432.         @Override
  1433.         public int getCount() {
  1434.             return mItemCount;
  1435.         }
  1436.    
  1437.         /**
  1438.          * Maps a point to a position in the list.
  1439.          *
  1440.          * @param x X in local coordinate
  1441.          * @param y Y in local coordinate
  1442.          * @return The position of the item which contains the specified point, or
  1443.          *         {@link #INVALID_POSITION} if the point does not intersect an item.
  1444.          */
  1445.         public int pointToPosition(int x, int y) {
  1446.             Rect frame = mTouchFrame;
  1447.             if (frame == null) {
  1448.                 mTouchFrame = new Rect();
  1449.                 frame = mTouchFrame;
  1450.             }
  1451.    
  1452.             final int count = getChildCount();
  1453.             for (int i = count - 1; i >= 0; i--) {
  1454.                 View child = getChildAt(i);
  1455.                 if (child.getVisibility() == View.VISIBLE) {
  1456.                     child.getHitRect(frame);
  1457.                     if (frame.contains(x, y)) {
  1458.                         return mFirstPosition + i;
  1459.                     }
  1460.                 }
  1461.             }
  1462.             return INVALID_POSITION;
  1463.         }
  1464.        
  1465.         static class SavedState extends BaseSavedState {
  1466.             long selectedId;
  1467.             int position;
  1468.    
  1469.             /**
  1470.              * Constructor called from {@link CustomAbsSpinner#onSaveInstanceState()}
  1471.              */
  1472.             SavedState(Parcelable superState) {
  1473.                 super(superState);
  1474.             }
  1475.            
  1476.             /**
  1477.              * Constructor called from {@link #CREATOR}
  1478.              */
  1479.             private SavedState(Parcel in) {
  1480.                 super(in);
  1481.                 selectedId = in.readLong();
  1482.                 position = in.readInt();
  1483.             }
  1484.    
  1485.             @Override
  1486.             public void writeToParcel(Parcel out, int flags) {
  1487.                 super.writeToParcel(out, flags);
  1488.                 out.writeLong(selectedId);
  1489.                 out.writeInt(position);
  1490.             }
  1491.    
  1492.             @Override
  1493.             public String toString() {
  1494.                 return "AbsSpinner.SavedState{"
  1495.                         + Integer.toHexString(System.identityHashCode(this))
  1496.                         + " selectedId=" + selectedId
  1497.                         + " position=" + position + "}";
  1498.             }
  1499.    
  1500.             public static final Parcelable.Creator<SavedState> CREATOR
  1501.                     = new Parcelable.Creator<SavedState>() {
  1502.                 public SavedState createFromParcel(Parcel in) {
  1503.                     return new SavedState(in);
  1504.                 }
  1505.    
  1506.                 public SavedState[] newArray(int size) {
  1507.                     return new SavedState[size];
  1508.                 }
  1509.             };
  1510.         }
  1511.    
  1512.         @Override
  1513.         public Parcelable onSaveInstanceState() {
  1514.             Parcelable superState = super.onSaveInstanceState();
  1515.             SavedState ss = new SavedState(superState);
  1516.             ss.selectedId = getSelectedItemId();
  1517.             if (ss.selectedId >= 0) {
  1518.                 ss.position = getSelectedItemPosition();
  1519.             } else {
  1520.                 ss.position = INVALID_POSITION;
  1521.             }
  1522.             return ss;
  1523.         }
  1524.    
  1525.         @Override
  1526.         public void onRestoreInstanceState(Parcelable state) {
  1527.             SavedState ss = (SavedState) state;
  1528.      
  1529.             super.onRestoreInstanceState(ss.getSuperState());
  1530.    
  1531.             if (ss.selectedId >= 0) {
  1532.                 mDataChanged = true;
  1533.                 mNeedSync = true;
  1534.                 mSyncRowId = ss.selectedId;
  1535.                 mSyncPosition = ss.position;
  1536.                 mSyncMode = SYNC_SELECTED_POSITION;
  1537.                 requestLayout();
  1538.             }
  1539.         }
  1540.    
  1541.         class RecycleBin {
  1542.             private SparseArray<View> mScrapHeap = new SparseArray<View>();
  1543.    
  1544.             public void put(int position, View v) {
  1545.                 mScrapHeap.put(position, v);
  1546.             }
  1547.      
  1548.             public void add(int position, View v) {
  1549.                 mScrapHeap.put(mScrapHeap.size(), v);
  1550.             }
  1551.             public View get() {
  1552.                 if (mScrapHeap.size() < 1) return null;
  1553.                
  1554.                 View result = mScrapHeap.valueAt(0);
  1555.                 int key = mScrapHeap.keyAt(0);
  1556.                
  1557.                 if (result != null) {
  1558.                         mScrapHeap.delete(key);
  1559.                 }
  1560.                 return result;
  1561.             }
  1562.            
  1563.             View get(int position) {
  1564.                 // System.out.print("Looking for " + position);
  1565.                 View result = mScrapHeap.get(position);
  1566.                 if (result != null) {
  1567.                     // System.out.println(" HIT");
  1568.                     mScrapHeap.delete(position);
  1569.                 } else {
  1570.                     // System.out.println(" MISS");
  1571.                 }
  1572.                 return result;
  1573.             }
  1574.            
  1575.             View peek(int position) {
  1576.                 // System.out.print("Looking for " + position);
  1577.                 return mScrapHeap.get(position);
  1578.             }
  1579.            
  1580.             void clear() {
  1581.                 final SparseArray<View> scrapHeap = mScrapHeap;
  1582.                
  1583.                 final int count = scrapHeap.size();
  1584.                 for (int i = 0; i < count; i++) {
  1585.                     final View view = scrapHeap.valueAt(i);
  1586.                     if (view != null) {
  1587.                         removeDetachedView(view, true);
  1588.                     }
  1589.                 }
  1590.                            
  1591.                 scrapHeap.clear();
  1592.             }
  1593.         }
  1594.     }
  1595.  
  1596. <br>
  1597. EcoGallery: (it recycles)
  1598.  
  1599.     public class EcoGallery extends CustomAbsSpinner implements GestureDetector.OnGestureListener {
  1600.    
  1601.         private static final String TAG = "Gallery";
  1602.    
  1603.         private static final boolean localLOGV = false;
  1604.    
  1605.         /**
  1606.          * Duration in milliseconds from the start of a scroll during which we're
  1607.          * unsure whether the user is scrolling or flinging.
  1608.          */
  1609.         private static final int SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT = 250;
  1610.    
  1611.         private static final String LOG_TAG = null;
  1612.    
  1613.         /**
  1614.          * Horizontal spacing between items.
  1615.          */
  1616.         private int mSpacing = 0;
  1617.    
  1618.         /**
  1619.          * How long the transition animation should run when a child view changes
  1620.          * position, measured in milliseconds.
  1621.          */
  1622.         private int mAnimationDuration = 200;
  1623.    
  1624.         /**
  1625.          * The alpha of items that are not selected.
  1626.          */
  1627.         private float mUnselectedAlpha;
  1628.        
  1629.         /**
  1630.          * Left most edge of a child seen so far during layout.
  1631.          */
  1632.         private int mLeftMost;
  1633.    
  1634.         /**
  1635.          * Right most edge of a child seen so far during layout.
  1636.          */
  1637.         private int mRightMost;
  1638.    
  1639.         private int mGravity;
  1640.    
  1641.         /**
  1642.          * Helper for detecting touch gestures.
  1643.          */
  1644.         private GestureDetector mGestureDetector;
  1645.    
  1646.         /**
  1647.          * The position of the item that received the user's down touch.
  1648.          */
  1649.         private int mDownTouchPosition;
  1650.    
  1651.         /**
  1652.          * The view of the item that received the user's down touch.
  1653.          */
  1654.         private View mDownTouchView;
  1655.        
  1656.         /**
  1657.          * Executes the delta scrolls from a fling or scroll movement.
  1658.          */
  1659.         private FlingRunnable mFlingRunnable = new FlingRunnable();
  1660.    
  1661.         /**
  1662.          * Sets mSuppressSelectionChanged = false. This is used to set it to false
  1663.          * in the future. It will also trigger a selection changed.
  1664.          */
  1665.         private Runnable mDisableSuppressSelectionChangedRunnable = new Runnable() {
  1666.             public void run() {
  1667.                 mSuppressSelectionChanged = false;
  1668.                 selectionChanged();
  1669.             }
  1670.         };
  1671.        
  1672.         /**
  1673.          * When fling runnable runs, it resets this to false. Any method along the
  1674.          * path until the end of its run() can set this to true to abort any
  1675.          * remaining fling. For example, if we've reached either the leftmost or
  1676.          * rightmost item, we will set this to true.
  1677.          */
  1678.         private boolean mShouldStopFling;
  1679.        
  1680.         /**
  1681.          * The currently selected item's child.
  1682.          */
  1683.         private View mSelectedChild;
  1684.        
  1685.         /**
  1686.          * Whether to continuously callback on the item selected listener during a
  1687.          * fling.
  1688.          */
  1689.         private boolean mShouldCallbackDuringFling = true;
  1690.    
  1691.         /**
  1692.          * Whether to callback when an item that is not selected is clicked.
  1693.          */
  1694.         private boolean mShouldCallbackOnUnselectedItemClick = true;
  1695.    
  1696.         /**
  1697.          * If true, do not callback to item selected listener.
  1698.          */
  1699.         private boolean mSuppressSelectionChanged;
  1700.    
  1701.         /**
  1702.          * If true, we have received the "invoke" (center or enter buttons) key
  1703.          * down. This is checked before we action on the "invoke" key up, and is
  1704.          * subsequently cleared.
  1705.          */
  1706.         private boolean mReceivedInvokeKeyDown;
  1707.        
  1708.         private AdapterContextMenuInfo mContextMenuInfo;
  1709.    
  1710.    
  1711.         /**
  1712.          * If true, this onScroll is the first for this user's drag (remember, a
  1713.          * drag sends many onScrolls).
  1714.          */
  1715.         private boolean mIsFirstScroll;
  1716.        
  1717.  
  1718.         /**
  1719.          * If true the reflection calls failed and this widget will behave
  1720.          * unpredictably if used further
  1721.          */
  1722.         private boolean mBroken;
  1723.        
  1724.         public EcoGallery(Context context) {
  1725.             this(context, null);
  1726.         }
  1727.    
  1728.         public EcoGallery(Context context, AttributeSet attrs) {
  1729.             this(context, attrs, R.attr.ecoGalleryStyle);
  1730.         }
  1731.    
  1732.         public EcoGallery(Context context, AttributeSet attrs, int defStyle) {
  1733.             super(context, attrs, defStyle);
  1734.            
  1735.             mBroken = true;
  1736.                    
  1737.             mGestureDetector = new GestureDetector(context, this);
  1738.             mGestureDetector.setIsLongpressEnabled(true);
  1739.            
  1740.             TypedArray a = context.obtainStyledAttributes(
  1741.                     attrs, R.styleable.EcoGallery, defStyle, 0);
  1742.    
  1743.             int index = a.getInt(R.styleable.EcoGallery_gravity, -1);
  1744.             if (index >= 0) {
  1745.                 setGravity(index);
  1746.             }
  1747.    
  1748.             int animationDuration =
  1749.                     a.getInt(R.styleable.EcoGallery_animationDuration, -1);
  1750.             if (animationDuration > 0) {
  1751.                 setAnimationDuration(animationDuration);
  1752.             }
  1753.    
  1754.             int spacing =
  1755.                     a.getDimensionPixelOffset(R.styleable.EcoGallery_spacing, 0);
  1756.             setSpacing(spacing);
  1757.    
  1758.             float unselectedAlpha = a.getFloat(
  1759.                     R.styleable.EcoGallery_unselectedAlpha, 0.5f);
  1760.             setUnselectedAlpha(unselectedAlpha);
  1761.            
  1762.             a.recycle();
  1763.    
  1764.             // We draw the selected item last (because otherwise the item to the
  1765.             // right overlaps it)
  1766.             int FLAG_USE_CHILD_DRAWING_ORDER = 0x400;
  1767.             int FLAG_SUPPORT_STATIC_TRANSFORMATIONS = 0x800;
  1768.             Class vgClass = ViewGroup.class;
  1769.            
  1770.             try {
  1771.                         Field childDrawingOrder = vgClass
  1772.                                         .getDeclaredField("FLAG_USE_CHILD_DRAWING_ORDER");
  1773.                         Field supportStaticTrans = vgClass
  1774.                                         .getDeclaredField("FLAG_SUPPORT_STATIC_TRANSFORMATIONS");
  1775.                        
  1776.                         childDrawingOrder.setAccessible(true);
  1777.                         supportStaticTrans.setAccessible(true);
  1778.                        
  1779.                         FLAG_USE_CHILD_DRAWING_ORDER = childDrawingOrder.getInt(this);
  1780.                         FLAG_SUPPORT_STATIC_TRANSFORMATIONS = supportStaticTrans.getInt(this);
  1781.             } catch (NoSuchFieldException e) {
  1782.                         Log.e(LOG_TAG, e.getMessage(), e);
  1783.                 } catch (IllegalAccessException e) {
  1784.                         Log.e(LOG_TAG, e.getMessage(), e);
  1785.                 }
  1786.             try {      
  1787.                         // set new group flags
  1788.                         Field groupFlags = vgClass.getDeclaredField("mGroupFlags");
  1789.                         groupFlags.setAccessible(true);
  1790.                         int groupFlagsValue = groupFlags.getInt(this);
  1791.    
  1792.                         groupFlagsValue |= FLAG_USE_CHILD_DRAWING_ORDER;
  1793.                         groupFlagsValue |= FLAG_SUPPORT_STATIC_TRANSFORMATIONS;
  1794.                        
  1795.                         groupFlags.set(this, groupFlagsValue);
  1796.                        
  1797.                         // working!
  1798.                         mBroken = false;
  1799.                 } catch (NoSuchFieldException e) {
  1800.                         Log.e(LOG_TAG, e.getMessage(), e);
  1801.                 } catch (IllegalAccessException e) {
  1802.                         Log.e(LOG_TAG, e.getMessage(), e);
  1803.                 }
  1804.         }
  1805.        
  1806.         /**
  1807.          * @return Whether the widget is broken or working (functional)
  1808.          */
  1809.         public boolean isBroken() {
  1810.                 return mBroken;
  1811.         }
  1812.    
  1813.         /**
  1814.          * Whether or not to callback on any {@link #getOnItemSelectedListener()}
  1815.          * while the items are being flinged. If false, only the final selected item
  1816.          * will cause the callback. If true, all items between the first and the
  1817.          * final will cause callbacks.
  1818.          *
  1819.          * @param shouldCallback Whether or not to callback on the listener while
  1820.          *            the items are being flinged.
  1821.          */
  1822.         public void setCallbackDuringFling(boolean shouldCallback) {
  1823.             mShouldCallbackDuringFling = shouldCallback;
  1824.         }
  1825.    
  1826.         /**
  1827.          * Whether or not to callback when an item that is not selected is clicked.
  1828.          * If false, the item will become selected (and re-centered). If true, the
  1829.          * {@link #getOnItemClickListener()} will get the callback.
  1830.          *
  1831.          * @param shouldCallback Whether or not to callback on the listener when a
  1832.          *            item that is not selected is clicked.
  1833.          * @hide
  1834.          */
  1835.         public void setCallbackOnUnselectedItemClick(boolean shouldCallback) {
  1836.             mShouldCallbackOnUnselectedItemClick = shouldCallback;
  1837.         }
  1838.        
  1839.         /**
  1840.          * Sets how long the transition animation should run when a child view
  1841.          * changes position. Only relevant if animation is turned on.
  1842.          *
  1843.          * @param animationDurationMillis The duration of the transition, in
  1844.          *        milliseconds.
  1845.          *
  1846.          * @attr ref android.R.styleable#Gallery_animationDuration
  1847.          */
  1848.         public void setAnimationDuration(int animationDurationMillis) {
  1849.             mAnimationDuration = animationDurationMillis;
  1850.         }
  1851.    
  1852.         /**
  1853.          * Sets the spacing between items in a Gallery
  1854.          *
  1855.          * @param spacing The spacing in pixels between items in the Gallery
  1856.          *
  1857.          * @attr ref android.R.styleable#Gallery_spacing
  1858.          */
  1859.         public void setSpacing(int spacing) {
  1860.             mSpacing = spacing;
  1861.         }
  1862.    
  1863.         /**
  1864.          * Sets the alpha of items that are not selected in the Gallery.
  1865.          *
  1866.          * @param unselectedAlpha the alpha for the items that are not selected.
  1867.          *
  1868.          * @attr ref android.R.styleable#Gallery_unselectedAlpha
  1869.          */
  1870.         public void setUnselectedAlpha(float unselectedAlpha) {
  1871.             mUnselectedAlpha = unselectedAlpha;
  1872.         }
  1873.    
  1874.         @Override
  1875.         protected boolean getChildStaticTransformation(View child, Transformation t) {
  1876.            
  1877.             t.clear();
  1878.             t.setAlpha(child == mSelectedChild ? 1.0f : mUnselectedAlpha);
  1879.            
  1880.             return true;
  1881.         }
  1882.    
  1883.         @Override
  1884.         protected int computeHorizontalScrollExtent() {
  1885.             // Only 1 item is considered to be selected
  1886.             return 1;
  1887.         }
  1888.    
  1889.         @Override
  1890.         protected int computeHorizontalScrollOffset() {
  1891.             // Current scroll position is the same as the selected position
  1892.             return mSelectedPosition;
  1893.         }
  1894.    
  1895.         @Override
  1896.         protected int computeHorizontalScrollRange() {
  1897.             // Scroll range is the same as the item count
  1898.             return mItemCount;
  1899.         }
  1900.    
  1901.         @Override
  1902.         protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
  1903.             return p instanceof LayoutParams;
  1904.         }
  1905.    
  1906.         @Override
  1907.         protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
  1908.             return new LayoutParams(p);
  1909.         }
  1910.    
  1911.         @Override
  1912.         public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
  1913.             return new LayoutParams(getContext(), attrs);
  1914.         }
  1915.    
  1916.         @Override
  1917.         protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
  1918.             /*
  1919.              * Gallery expects EcoGallery.LayoutParams.
  1920.              */
  1921.             return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
  1922.                     ViewGroup.LayoutParams.WRAP_CONTENT);
  1923.         }
  1924.    
  1925.         @Override
  1926.         protected void onLayout(boolean changed, int l, int t, int r, int b) {
  1927.             super.onLayout(changed, l, t, r, b);
  1928.            
  1929.             /*
  1930.              * Remember that we are in layout to prevent more layout request from
  1931.              * being generated.
  1932.              */
  1933.             mInLayout = true;
  1934.             layout(0, false);
  1935.            
  1936.             mInLayout = false;
  1937.         }
  1938.    
  1939.         @Override
  1940.         int getChildHeight(View child) {
  1941.             return child.getMeasuredHeight();
  1942.         }
  1943.        
  1944.         /**
  1945.          * Tracks a motion scroll. In reality, this is used to do just about any
  1946.          * movement to items (touch scroll, arrow-key scroll, set an item as selected).
  1947.          *
  1948.          * @param deltaX Change in X from the previous event.
  1949.          */
  1950.         void trackMotionScroll(int deltaX) {
  1951.    
  1952.             if (getChildCount() == 0) {
  1953.                 return;
  1954.             }
  1955.            
  1956.             boolean toLeft = deltaX < 0;
  1957.            
  1958.             int limitedDeltaX = getLimitedMotionScrollAmount(toLeft, deltaX);
  1959.             if (limitedDeltaX != deltaX) {
  1960.                 // The above call returned a limited amount, so stop any scrolls/flings
  1961.                 mFlingRunnable.endFling(false);
  1962.                 onFinishedMovement();
  1963.             }
  1964.            
  1965.             offsetChildrenLeftAndRight(limitedDeltaX);
  1966.            
  1967.             detachOffScreenChildren(toLeft);
  1968.            
  1969.             if (toLeft) {
  1970.                 // If moved left, there will be empty space on the right
  1971.                 fillToGalleryRight();
  1972.             } else {
  1973.                 // Similarly, empty space on the left
  1974.                 fillToGalleryLeft();
  1975.             }
  1976.            
  1977.             setSelectionToCenterChild();
  1978.            
  1979.             invalidate();
  1980.         }
  1981.    
  1982.         int getLimitedMotionScrollAmount(boolean motionToLeft, int deltaX) {
  1983.             int extremeItemPosition = motionToLeft ? mItemCount - 1 : 0;
  1984.             View extremeChild = getChildAt(extremeItemPosition - mFirstPosition);
  1985.            
  1986.             if (extremeChild == null) {
  1987.                 return deltaX;
  1988.             }
  1989.            
  1990.             int extremeChildCenter = getCenterOfView(extremeChild);
  1991.             int galleryCenter = getCenterOfGallery();
  1992.            
  1993.             if (motionToLeft) {
  1994.                 if (extremeChildCenter <= galleryCenter) {
  1995.                    
  1996.                     // The extreme child is past his boundary point!
  1997.                     return 0;
  1998.                 }
  1999.             } else {
  2000.                 if (extremeChildCenter >= galleryCenter) {
  2001.    
  2002.                     // The extreme child is past his boundary point!
  2003.                     return 0;
  2004.                 }
  2005.             }
  2006.            
  2007.             int centerDifference = galleryCenter - extremeChildCenter;
  2008.    
  2009.             return motionToLeft
  2010.                     ? Math.max(centerDifference, deltaX)
  2011.                     : Math.min(centerDifference, deltaX);
  2012.         }
  2013.    
  2014.         /**
  2015.          * Offset the horizontal location of all children of this view by the
  2016.          * specified number of pixels.
  2017.          *
  2018.          * @param offset the number of pixels to offset
  2019.          */
  2020.         private void offsetChildrenLeftAndRight(int offset) {
  2021.             for (int i = getChildCount() - 1; i >= 0; i--) {
  2022.                 getChildAt(i).offsetLeftAndRight(offset);
  2023.             }
  2024.         }
  2025.        
  2026.         /**
  2027.          * @return The center of this Gallery.
  2028.          */
  2029.         private int getCenterOfGallery() {
  2030.                 int paddingLeft = getPaddingLeft();
  2031.             return (getWidth() - paddingLeft - getPaddingRight()) / 2 + paddingLeft;
  2032.         }
  2033.        
  2034.         /**
  2035.          * @return The center of the given view.
  2036.          */
  2037.         private static int getCenterOfView(View view) {
  2038.             return view.getLeft() + view.getWidth() / 2;
  2039.         }
  2040.        
  2041.         /**
  2042.          * Detaches children that are off the screen (i.e.: Gallery bounds).
  2043.          *
  2044.          * @param toLeft Whether to detach children to the left of the Gallery, or
  2045.          *            to the right.
  2046.          */
  2047.         private void detachOffScreenChildren(boolean toLeft) {
  2048.             int numChildren = getChildCount();
  2049.             int firstPosition = mFirstPosition;
  2050.             int start = 0;
  2051.             int count = 0;
  2052.    
  2053.             if (toLeft) {
  2054.                 final int galleryLeft = getPaddingLeft();
  2055.                 for (int i = 0; i < numChildren; i++) {
  2056.                     final View child = getChildAt(i);
  2057.                     if (child.getRight() >= galleryLeft) {
  2058.                         break;
  2059.                     } else {
  2060.                         count++;
  2061.                         mRecycler.add(firstPosition + i, child);
  2062.                     }
  2063.                 }
  2064.             } else {
  2065.                 final int galleryRight = getWidth() - getPaddingRight();
  2066.                 for (int i = numChildren - 1; i >= 0; i--) {
  2067.                     final View child = getChildAt(i);
  2068.                     if (child.getLeft() <= galleryRight) {
  2069.                         break;
  2070.                     } else {
  2071.                         start = i;
  2072.                         count++;
  2073.                         mRecycler.add(firstPosition + i, child);
  2074.                     }
  2075.                 }
  2076.             }
  2077.    
  2078.             detachViewsFromParent(start, count);
  2079.            
  2080.             if (toLeft) {
  2081.                 mFirstPosition += count;
  2082.             }
  2083.         }
  2084.        
  2085.         /**
  2086.          * Scrolls the items so that the selected item is in its 'slot' (its center
  2087.          * is the gallery's center).
  2088.          */
  2089.         private void scrollIntoSlots() {
  2090.            
  2091.             if (getChildCount() == 0 || mSelectedChild == null) return;
  2092.            
  2093.             int selectedCenter = getCenterOfView(mSelectedChild);
  2094.             int targetCenter = getCenterOfGallery();
  2095.            
  2096.             int scrollAmount = targetCenter - selectedCenter;
  2097.             if (scrollAmount != 0) {
  2098.                 mFlingRunnable.startUsingDistance(scrollAmount);
  2099.             } else {
  2100.                 onFinishedMovement();
  2101.             }
  2102.         }
  2103.    
  2104.         private void onFinishedMovement() {
  2105.             if (mSuppressSelectionChanged) {
  2106.                 mSuppressSelectionChanged = false;
  2107.                
  2108.                 // We haven't been callbacking during the fling, so do it now
  2109.                 super.selectionChanged();
  2110.             }
  2111.             invalidate();
  2112.         }
  2113.        
  2114.         @Override
  2115.         void selectionChanged() {
  2116.             if (!mSuppressSelectionChanged) {
  2117.                 super.selectionChanged();
  2118.             }
  2119.         }
  2120.    
  2121.         /**
  2122.          * Looks for the child that is closest to the center and sets it as the
  2123.          * selected child.
  2124.          */
  2125.         private void setSelectionToCenterChild() {
  2126.            
  2127.             View selView = mSelectedChild;
  2128.             if (mSelectedChild == null) return;
  2129.            
  2130.             int galleryCenter = getCenterOfGallery();
  2131.            
  2132.             // Common case where the current selected position is correct
  2133.             if (selView.getLeft() <= galleryCenter && selView.getRight() >= galleryCenter) {
  2134.                 return;
  2135.             }
  2136.            
  2137.             // TODO better search
  2138.             int closestEdgeDistance = Integer.MAX_VALUE;
  2139.             int newSelectedChildIndex = 0;
  2140.             for (int i = getChildCount() - 1; i >= 0; i--) {
  2141.                
  2142.                 View child = getChildAt(i);
  2143.                
  2144.                 if (child.getLeft() <= galleryCenter && child.getRight() >=  galleryCenter) {
  2145.                     // This child is in the center
  2146.                     newSelectedChildIndex = i;
  2147.                     break;
  2148.                 }
  2149.                
  2150.                 int childClosestEdgeDistance = Math.min(Math.abs(child.getLeft() - galleryCenter),
  2151.                         Math.abs(child.getRight() - galleryCenter));
  2152.                 if (childClosestEdgeDistance < closestEdgeDistance) {
  2153.                     closestEdgeDistance = childClosestEdgeDistance;
  2154.                     newSelectedChildIndex = i;
  2155.                 }
  2156.             }
  2157.            
  2158.             int newPos = mFirstPosition + newSelectedChildIndex;
  2159.            
  2160.             if (newPos != mSelectedPosition) {
  2161.                 setSelectedPositionInt(newPos);
  2162.                 setNextSelectedPositionInt(newPos);
  2163.                 checkSelectionChanged();
  2164.             }
  2165.         }
  2166.    
  2167.         /**
  2168.          * Creates and positions all views for this Gallery.
  2169.          * <p>
  2170.          * We layout rarely, most of the time {@link #trackMotionScroll(int)} takes
  2171.          * care of repositioning, adding, and removing children.
  2172.          *
  2173.          * @param delta Change in the selected position. +1 means the selection is
  2174.          *            moving to the right, so views are scrolling to the left. -1
  2175.          *            means the selection is moving to the left.
  2176.          */
  2177.         @Override
  2178.         void layout(int delta, boolean animate) {
  2179.    
  2180.             int childrenLeft = mSpinnerPadding.left;
  2181.             int childrenWidth = getRight() - getLeft() - mSpinnerPadding.left - mSpinnerPadding.right;
  2182.    
  2183.             if (mDataChanged) {
  2184.                 handleDataChanged();
  2185.             }
  2186.    
  2187.             // Handle an empty gallery by removing all views.
  2188.             if (mItemCount == 0) {
  2189.                 resetList();
  2190.                 return;
  2191.             }
  2192.    
  2193.             // Update to the new selected position.
  2194.             if (mNextSelectedPosition >= 0) {
  2195.                 setSelectedPositionInt(mNextSelectedPosition);
  2196.             }
  2197.    
  2198.             // All views go in recycler while we are in layout
  2199.             recycleAllViews();
  2200.    
  2201.             // Clear out old views
  2202.             detachAllViewsFromParent();
  2203.    
  2204.             /*
  2205.              * These will be used to give initial positions to views entering the
  2206.              * gallery as we scroll
  2207.              */
  2208.             mRightMost = 0;
  2209.             mLeftMost = 0;
  2210.    
  2211.             // Make selected view and center it
  2212.            
  2213.             /*
  2214.              * mFirstPosition will be decreased as we add views to the left later
  2215.              * on. The 0 for x will be offset in a couple lines down.
  2216.              */  
  2217.             mFirstPosition = mSelectedPosition;
  2218.             View sel = makeAndAddView(mSelectedPosition, 0, 0, true);
  2219.            
  2220.             // Put the selected child in the center
  2221.             int selectedOffset = childrenLeft + (childrenWidth / 2) - (sel.getWidth() / 2);
  2222.             sel.offsetLeftAndRight(selectedOffset);
  2223.    
  2224.             fillToGalleryRight();
  2225.             fillToGalleryLeft();
  2226.    
  2227.             invalidate();
  2228.             checkSelectionChanged();
  2229.    
  2230.             mDataChanged = false;
  2231.             mNeedSync = false;
  2232.             setNextSelectedPositionInt(mSelectedPosition);
  2233.            
  2234.             updateSelectedItemMetadata();
  2235.         }
  2236.        
  2237.         private void fillToGalleryLeft() {
  2238.             int itemSpacing = mSpacing;
  2239.             int galleryLeft = getPaddingLeft();
  2240.            
  2241.             // Set state for initial iteration
  2242.             View prevIterationView = getChildAt(0);
  2243.             int curPosition;
  2244.             int curRightEdge;
  2245.            
  2246.             if (prevIterationView != null) {
  2247.                 curPosition = mFirstPosition - 1;
  2248.                 curRightEdge = prevIterationView.getLeft() - itemSpacing;
  2249.             } else {
  2250.                 // No children available!
  2251.                 curPosition = 0;
  2252.                 curRightEdge = getRight() - getLeft() - getPaddingRight();
  2253.                 mShouldStopFling = true;
  2254.             }
  2255.                    
  2256.             while (curRightEdge > galleryLeft && curPosition >= 0) {
  2257.                 prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
  2258.                         curRightEdge, false);
  2259.    
  2260.                 // Remember some state
  2261.                 mFirstPosition = curPosition;
  2262.                
  2263.                 // Set state for next iteration
  2264.                 curRightEdge = prevIterationView.getLeft() - itemSpacing;
  2265.                 curPosition--;
  2266.             }
  2267.         }
  2268.        
  2269.         private void fillToGalleryRight() {
  2270.             int itemSpacing = mSpacing;
  2271.             int galleryRight = getRight() - getLeft() - getPaddingRight();
  2272.             int numChildren = getChildCount();
  2273.             int numItems = mItemCount;
  2274.            
  2275.             // Set state for initial iteration
  2276.             View prevIterationView = getChildAt(numChildren - 1);
  2277.             int curPosition;
  2278.             int curLeftEdge;
  2279.            
  2280.             if (prevIterationView != null) {
  2281.                 curPosition = mFirstPosition + numChildren;
  2282.                 curLeftEdge = prevIterationView.getRight() + itemSpacing;
  2283.             } else {
  2284.                 mFirstPosition = curPosition = mItemCount - 1;
  2285.                 curLeftEdge = getPaddingLeft();
  2286.                 mShouldStopFling = true;
  2287.             }
  2288.                    
  2289.             while (curLeftEdge < galleryRight && curPosition < numItems) {
  2290.                 prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
  2291.                         curLeftEdge, true);
  2292.    
  2293.                 // Set state for next iteration
  2294.                 curLeftEdge = prevIterationView.getRight() + itemSpacing;
  2295.                 curPosition++;
  2296.             }
  2297.         }
  2298.    
  2299.         /**
  2300.          * Obtain a view, either by pulling an existing view from the recycler or by
  2301.          * getting a new one from the adapter. If we are animating, make sure there
  2302.          * is enough information in the view's layout parameters to animate from the
  2303.          * old to new positions.
  2304.          *
  2305.          * @param position Position in the gallery for the view to obtain
  2306.          * @param offset Offset from the selected position
  2307.          * @param x X-coordinate indicating where this view should be placed. This
  2308.          *        will either be the left or right edge of the view, depending on
  2309.          *        the fromLeft parameter
  2310.          * @param fromLeft Are we positioning views based on the left edge? (i.e.,
  2311.          *        building from left to right)?
  2312.          * @return A view that has been added to the gallery
  2313.          */
  2314.         private View makeAndAddView(int position, int offset, int x,
  2315.                 boolean fromLeft) {
  2316.    
  2317.             View child;
  2318.    
  2319.             child = mRecycler.get();
  2320.             // pass child as convertview
  2321.             child = mAdapter.getView(position, child, this);
  2322.    
  2323.             // Position the view
  2324.             setUpChild(child, offset, x, fromLeft);
  2325.    
  2326.             return child;
  2327.         }
  2328.    
  2329.         /**
  2330.          * Helper for makeAndAddView to set the position of a view and fill out its
  2331.          * layout paramters.
  2332.          *
  2333.          * @param child The view to position
  2334.          * @param offset Offset from the selected position
  2335.          * @param x X-coordintate indicating where this view should be placed. This
  2336.          *        will either be the left or right edge of the view, depending on
  2337.          *        the fromLeft paramter
  2338.          * @param fromLeft Are we posiitoning views based on the left edge? (i.e.,
  2339.          *        building from left to right)?
  2340.          */
  2341.         private void setUpChild(View child, int offset, int x, boolean fromLeft) {
  2342.    
  2343.             // Respect layout params that are already in the view. Otherwise
  2344.             // make some up...
  2345.             LayoutParams lp = (LayoutParams)
  2346.                 child.getLayoutParams();
  2347.             if (lp == null) {
  2348.                 lp = (LayoutParams) generateDefaultLayoutParams();
  2349.             }
  2350.    
  2351.             addViewInLayout(child, fromLeft ? -1 : 0, lp);
  2352.    
  2353.             child.setSelected(offset == 0);
  2354.    
  2355.             // Get measure specs
  2356.             int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
  2357.                     mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height);
  2358.             int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
  2359.                     mSpinnerPadding.left + mSpinnerPadding.right, lp.width);
  2360.    
  2361.             // Measure child
  2362.             child.measure(childWidthSpec, childHeightSpec);
  2363.    
  2364.             int childLeft;
  2365.             int childRight;
  2366.    
  2367.             // Position vertically based on gravity setting
  2368.             int childTop = calculateTop(child, true);
  2369.             int childBottom = childTop + child.getMeasuredHeight();
  2370.    
  2371.             int width = child.getMeasuredWidth();
  2372.             if (fromLeft) {
  2373.                 childLeft = x;
  2374.                 childRight = childLeft + width;
  2375.             } else {
  2376.                 childLeft = x - width;
  2377.                 childRight = x;
  2378.             }
  2379.    
  2380.             child.layout(childLeft, childTop, childRight, childBottom);
  2381.         }
  2382.    
  2383.         /**
  2384.          * Figure out vertical placement based on mGravity
  2385.          *
  2386.          * @param child Child to place
  2387.          * @return Where the top of the child should be
  2388.          */
  2389.         private int calculateTop(View child, boolean duringLayout) {
  2390.             int myHeight = duringLayout ? getMeasuredHeight() : getHeight();
  2391.             int childHeight = duringLayout ? child.getMeasuredHeight() : child.getHeight();
  2392.            
  2393.             int childTop = 0;
  2394.    
  2395.             switch (mGravity) {
  2396.             case Gravity.TOP:
  2397.                 childTop = mSpinnerPadding.top;
  2398.                 break;
  2399.             case Gravity.CENTER_VERTICAL:
  2400.                 int availableSpace = myHeight - mSpinnerPadding.bottom
  2401.                         - mSpinnerPadding.top - childHeight;
  2402.                 childTop = mSpinnerPadding.top + (availableSpace / 2);
  2403.                 break;
  2404.             case Gravity.BOTTOM:
  2405.                 childTop = myHeight - mSpinnerPadding.bottom - childHeight;
  2406.                 break;
  2407.             }
  2408.             return childTop;
  2409.         }
  2410.    
  2411.         @Override
  2412.         public boolean onTouchEvent(MotionEvent event) {
  2413.    
  2414.             // Give everything to the gesture detector
  2415.             boolean retValue = mGestureDetector.onTouchEvent(event);
  2416.    
  2417.             int action = event.getAction();
  2418.             if (action == MotionEvent.ACTION_UP) {
  2419.                 // Helper method for lifted finger
  2420.                 onUp();
  2421.             } else if (action == MotionEvent.ACTION_CANCEL) {
  2422.                 onCancel();
  2423.             }
  2424.            
  2425.             return retValue;
  2426.         }
  2427.        
  2428.         /**
  2429.          * {@inheritDoc}
  2430.          */
  2431.         public boolean onSingleTapUp(MotionEvent e) {
  2432.    
  2433.             if (mDownTouchPosition >= 0) {
  2434.                
  2435.                 // An item tap should make it selected, so scroll to this child.
  2436.                 scrollToChild(mDownTouchPosition - mFirstPosition);
  2437.    
  2438.                 // Also pass the click so the client knows, if it wants to.
  2439.                 if (mShouldCallbackOnUnselectedItemClick || mDownTouchPosition == mSelectedPosition) {
  2440.                     performItemClick(mDownTouchView, mDownTouchPosition, mAdapter
  2441.                             .getItemId(mDownTouchPosition));
  2442.                 }
  2443.                
  2444.                 return true;
  2445.             }
  2446.            
  2447.             return false;
  2448.         }
  2449.    
  2450.         /**
  2451.          * {@inheritDoc}
  2452.          */
  2453.         public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
  2454.            
  2455.             if (!mShouldCallbackDuringFling) {
  2456.                 // We want to suppress selection changes
  2457.                
  2458.                 // Remove any future code to set mSuppressSelectionChanged = false
  2459.                 removeCallbacks(mDisableSuppressSelectionChangedRunnable);
  2460.    
  2461.                 // This will get reset once we scroll into slots
  2462.                 if (!mSuppressSelectionChanged) mSuppressSelectionChanged = true;
  2463.             }
  2464.            
  2465.             // Fling the gallery!
  2466.             mFlingRunnable.startUsingVelocity((int) -velocityX);
  2467.            
  2468.             return true;
  2469.         }
  2470.    
  2471.         /**
  2472.          * {@inheritDoc}
  2473.          */
  2474.         public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
  2475.    
  2476.             if (localLOGV) Log.v(TAG, String.valueOf(e2.getX() - e1.getX()));
  2477.            
  2478.             /*
  2479.              * Now's a good time to tell our parent to stop intercepting our events!
  2480.              * The user has moved more than the slop amount, since GestureDetector
  2481.              * ensures this before calling this method. Also, if a parent is more
  2482.              * interested in this touch's events than we are, it would have
  2483.              * intercepted them by now (for example, we can assume when a Gallery is
  2484.              * in the ListView, a vertical scroll would not end up in this method
  2485.              * since a ListView would have intercepted it by now).
  2486.              */
  2487.             getParent().requestDisallowInterceptTouchEvent(true);
  2488.            
  2489.             // As the user scrolls, we want to callback selection changes so related-
  2490.             // info on the screen is up-to-date with the gallery's selection
  2491.             if (!mShouldCallbackDuringFling) {
  2492.                 if (mIsFirstScroll) {
  2493.                     /*
  2494.                      * We're not notifying the client of selection changes during
  2495.                      * the fling, and this scroll could possibly be a fling. Don't
  2496.                      * do selection changes until we're sure it is not a fling.
  2497.                      */
  2498.                     if (!mSuppressSelectionChanged) mSuppressSelectionChanged = true;
  2499.                     postDelayed(mDisableSuppressSelectionChangedRunnable, SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT);
  2500.                 }
  2501.             } else {
  2502.                 if (mSuppressSelectionChanged) mSuppressSelectionChanged = false;
  2503.             }
  2504.            
  2505.             // Track the motion
  2506.             trackMotionScroll(-1 * (int) distanceX);
  2507.            
  2508.             mIsFirstScroll = false;
  2509.             return true;
  2510.         }
  2511.        
  2512.         /**
  2513.          * {@inheritDoc}
  2514.          */
  2515.         public boolean onDown(MotionEvent e) {
  2516.    
  2517.             // Kill any existing fling/scroll
  2518.             mFlingRunnable.stop(false);
  2519.    
  2520.             // Get the item's view that was touched
  2521.             mDownTouchPosition = pointToPosition((int) e.getX(), (int) e.getY());
  2522.            
  2523.             if (mDownTouchPosition >= 0) {
  2524.                 mDownTouchView = getChildAt(mDownTouchPosition - mFirstPosition);
  2525.                 mDownTouchView.setPressed(true);
  2526.             }
  2527.            
  2528.             // Reset the multiple-scroll tracking state
  2529.             mIsFirstScroll = true;
  2530.            
  2531.             // Must return true to get matching events for this down event.
  2532.             return true;
  2533.         }
  2534.    
  2535.         /**
  2536.          * Called when a touch event's action is MotionEvent.ACTION_UP.
  2537.          */
  2538.         void onUp() {
  2539.            
  2540.             if (mFlingRunnable.mScroller.isFinished()) {
  2541.                 scrollIntoSlots();
  2542.             }
  2543.            
  2544.             dispatchUnpress();
  2545.         }
  2546.        
  2547.         /**
  2548.          * Called when a touch event's action is MotionEvent.ACTION_CANCEL.
  2549.          */
  2550.         void onCancel() {
  2551.             onUp();
  2552.         }
  2553.        
  2554.         /**
  2555.          * {@inheritDoc}
  2556.          */
  2557.         public void onLongPress(MotionEvent e) {
  2558.            
  2559.             if (mDownTouchPosition < 0) {
  2560.                 return;
  2561.             }
  2562.            
  2563.             performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
  2564.             long id = getItemIdAtPosition(mDownTouchPosition);
  2565.             dispatchLongPress(mDownTouchView, mDownTouchPosition, id);
  2566.         }
  2567.    
  2568.         // Unused methods from GestureDetector.OnGestureListener below
  2569.        
  2570.         /**
  2571.          * {@inheritDoc}
  2572.          */
  2573.         public void onShowPress(MotionEvent e) {
  2574.         }
  2575.    
  2576.         // Unused methods from GestureDetector.OnGestureListener above
  2577.        
  2578.         private void dispatchPress(View child) {
  2579.            
  2580.             if (child != null) {
  2581.                 child.setPressed(true);
  2582.             }
  2583.            
  2584.             setPressed(true);
  2585.         }
  2586.        
  2587.         private void dispatchUnpress() {
  2588.            
  2589.             for (int i = getChildCount() - 1; i >= 0; i--) {
  2590.                 getChildAt(i).setPressed(false);
  2591.             }
  2592.            
  2593.             setPressed(false);
  2594.         }
  2595.        
  2596.         @Override
  2597.         public void dispatchSetSelected(boolean selected) {
  2598.             /*
  2599.              * We don't want to pass the selected state given from its parent to its
  2600.              * children since this widget itself has a selected state to give to its
  2601.              * children.
  2602.              */
  2603.         }
  2604.    
  2605.         @Override
  2606.         protected void dispatchSetPressed(boolean pressed) {
  2607.            
  2608.             // Show the pressed state on the selected child
  2609.             if (mSelectedChild != null) {
  2610.                 mSelectedChild.setPressed(pressed);
  2611.             }
  2612.         }
  2613.    
  2614.         @Override
  2615.         protected ContextMenuInfo getContextMenuInfo() {
  2616.             return mContextMenuInfo;
  2617.         }
  2618.    
  2619.         @Override
  2620.         public boolean showContextMenuForChild(View originalView) {
  2621.    
  2622.             final int longPressPosition = getPositionForView(originalView);
  2623.             if (longPressPosition < 0) {
  2624.                 return false;
  2625.             }
  2626.            
  2627.             final long longPressId = mAdapter.getItemId(longPressPosition);
  2628.             return dispatchLongPress(originalView, longPressPosition, longPressId);
  2629.         }
  2630.    
  2631.         @Override
  2632.         public boolean showContextMenu() {
  2633.            
  2634.             if (isPressed() && mSelectedPosition >= 0) {
  2635.                 int index = mSelectedPosition - mFirstPosition;
  2636.                 View v = getChildAt(index);
  2637.                 return dispatchLongPress(v, mSelectedPosition, mSelectedRowId);
  2638.             }        
  2639.            
  2640.             return false;
  2641.         }
  2642.    
  2643.         private boolean dispatchLongPress(View view, int position, long id) {
  2644.             boolean handled = false;
  2645.            
  2646.             if (mOnItemLongClickListener != null) {
  2647.                 handled = mOnItemLongClickListener.onItemLongClick(this, mDownTouchView,
  2648.                         mDownTouchPosition, id);
  2649.             }
  2650.    
  2651.             if (!handled) {
  2652.                 mContextMenuInfo = new AdapterContextMenuInfo(view, position, id);
  2653.                 handled = super.showContextMenuForChild(this);
  2654.             }
  2655.    
  2656.             if (handled) {
  2657.                 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
  2658.             }
  2659.            
  2660.             return handled;
  2661.         }
  2662.        
  2663.         @Override
  2664.         public boolean dispatchKeyEvent(KeyEvent event) {
  2665.             // Gallery steals all key events
  2666.             return event.dispatch(this, null, null);
  2667.         }
  2668.    
  2669.         /**
  2670.          * Handles left, right, and clicking
  2671.          * @see android.view.View#onKeyDown
  2672.          */
  2673.         @Override
  2674.         public boolean onKeyDown(int keyCode, KeyEvent event) {
  2675.             switch (keyCode) {
  2676.                
  2677.             case KeyEvent.KEYCODE_DPAD_LEFT:
  2678.                 if (movePrevious()) {
  2679.                     playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
  2680.                 }
  2681.                 return true;
  2682.    
  2683.             case KeyEvent.KEYCODE_DPAD_RIGHT:
  2684.                 if (moveNext()) {
  2685.                     playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
  2686.                 }
  2687.                 return true;
  2688.    
  2689.             case KeyEvent.KEYCODE_DPAD_CENTER:
  2690.             case KeyEvent.KEYCODE_ENTER:
  2691.                 mReceivedInvokeKeyDown = true;
  2692.                 // fallthrough to default handling
  2693.             }
  2694.            
  2695.             return super.onKeyDown(keyCode, event);
  2696.         }
  2697.    
  2698.         @Override
  2699.         public boolean onKeyUp(int keyCode, KeyEvent event) {
  2700.             switch (keyCode) {
  2701.             case KeyEvent.KEYCODE_DPAD_CENTER:
  2702.             case KeyEvent.KEYCODE_ENTER: {
  2703.                
  2704.                 if (mReceivedInvokeKeyDown) {
  2705.                     if (mItemCount > 0) {
  2706.        
  2707.                         dispatchPress(mSelectedChild);
  2708.                         postDelayed(new Runnable() {
  2709.                             public void run() {
  2710.                                 dispatchUnpress();
  2711.                             }
  2712.                         }, ViewConfiguration.getPressedStateDuration());
  2713.        
  2714.                         int selectedIndex = mSelectedPosition - mFirstPosition;
  2715.                         performItemClick(getChildAt(selectedIndex), mSelectedPosition, mAdapter
  2716.                                 .getItemId(mSelectedPosition));
  2717.                     }
  2718.                 }
  2719.                
  2720.                 // Clear the flag
  2721.                 mReceivedInvokeKeyDown = false;
  2722.                
  2723.                 return true;
  2724.             }
  2725.             }
  2726.    
  2727.             return super.onKeyUp(keyCode, event);
  2728.         }
  2729.        
  2730.         boolean movePrevious() {
  2731.             if (mItemCount > 0 && mSelectedPosition > 0) {
  2732.                 scrollToChild(mSelectedPosition - mFirstPosition - 1);
  2733.                 return true;
  2734.             } else {
  2735.                 return false;
  2736.             }
  2737.         }
  2738.    
  2739.         boolean moveNext() {
  2740.             if (mItemCount > 0 && mSelectedPosition < mItemCount - 1) {
  2741.                 scrollToChild(mSelectedPosition - mFirstPosition + 1);
  2742.                 return true;
  2743.             } else {
  2744.                 return false;
  2745.             }
  2746.         }
  2747.    
  2748.         private boolean scrollToChild(int childPosition) {
  2749.             View child = getChildAt(childPosition);
  2750.            
  2751.             if (child != null) {
  2752.                 int distance = getCenterOfGallery() - getCenterOfView(child);
  2753.                 mFlingRunnable.startUsingDistance(distance);
  2754.                 return true;
  2755.             }
  2756.            
  2757.             return false;
  2758.         }
  2759.        
  2760.         @Override
  2761.         void setSelectedPositionInt(int position) {
  2762.             super.setSelectedPositionInt(position);
  2763.    
  2764.             // Updates any metadata we keep about the selected item.
  2765.             updateSelectedItemMetadata();
  2766.         }
  2767.    
  2768.         private void updateSelectedItemMetadata() {
  2769.            
  2770.             View oldSelectedChild = mSelectedChild;
  2771.    
  2772.             View child = mSelectedChild = getChildAt(mSelectedPosition - mFirstPosition);
  2773.             if (child == null) {
  2774.                 return;
  2775.             }
  2776.    
  2777.             child.setSelected(true);
  2778.             child.setFocusable(true);
  2779.    
  2780.             if (hasFocus()) {
  2781.                 child.requestFocus();
  2782.             }
  2783.    
  2784.             // We unfocus the old child down here so the above hasFocus check
  2785.             // returns true
  2786.             if (oldSelectedChild != null) {
  2787.    
  2788.                 // Make sure its drawable state doesn't contain 'selected'
  2789.                 oldSelectedChild.setSelected(false);
  2790.                
  2791.                 // Make sure it is not focusable anymore, since otherwise arrow keys
  2792.                 // can make this one be focused
  2793.                 oldSelectedChild.setFocusable(false);
  2794.             }
  2795.            
  2796.         }
  2797.        
  2798.         /**
  2799.          * Describes how the child views are aligned.
  2800.          * @param gravity
  2801.          *
  2802.          * @attr ref android.R.styleable#Gallery_gravity
  2803.          */
  2804.         public void setGravity(int gravity)
  2805.         {
  2806.             if (mGravity != gravity) {
  2807.                 mGravity = gravity;
  2808.                 requestLayout();
  2809.             }
  2810.         }
  2811.    
  2812.         @Override
  2813.         protected int getChildDrawingOrder(int childCount, int i) {
  2814.             int selectedIndex = mSelectedPosition - mFirstPosition;
  2815.            
  2816.             // Just to be safe
  2817.             if (selectedIndex < 0) return i;
  2818.            
  2819.             if (i == childCount - 1) {
  2820.                 // Draw the selected child last
  2821.                 return selectedIndex;
  2822.             } else if (i >= selectedIndex) {
  2823.                 // Move the children to the right of the selected child earlier one
  2824.                 return i + 1;
  2825.             } else {
  2826.                 // Keep the children to the left of the selected child the same
  2827.                 return i;
  2828.             }
  2829.         }
  2830.    
  2831.         @Override
  2832.         protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
  2833.             super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
  2834.            
  2835.             /*
  2836.              * The gallery shows focus by focusing the selected item. So, give
  2837.              * focus to our selected item instead. We steal keys from our
  2838.              * selected item elsewhere.
  2839.              */
  2840.             if (gainFocus && mSelectedChild != null) {
  2841.                 mSelectedChild.requestFocus(direction);
  2842.             }
  2843.    
  2844.         }
  2845.    
  2846.         /**
  2847.          * Responsible for fling behavior. Use {@link #startUsingVelocity(int)} to
  2848.          * initiate a fling. Each frame of the fling is handled in {@link #run()}.
  2849.          * A FlingRunnable will keep re-posting itself until the fling is done.
  2850.          *
  2851.          */
  2852.         private class FlingRunnable implements Runnable {
  2853.             /**
  2854.              * Tracks the decay of a fling scroll
  2855.              */
  2856.             private Scroller mScroller;
  2857.    
  2858.             /**
  2859.              * X value reported by mScroller on the previous fling
  2860.              */
  2861.             private int mLastFlingX;
  2862.    
  2863.             public FlingRunnable() {
  2864.                 mScroller = new Scroller(getContext());
  2865.             }
  2866.    
  2867.             private void startCommon() {
  2868.                 // Remove any pending flings
  2869.                 removeCallbacks(this);
  2870.             }
  2871.            
  2872.             public void startUsingVelocity(int initialVelocity) {
  2873.                 if (initialVelocity == 0) return;
  2874.                
  2875.                 startCommon();
  2876.                
  2877.                 int initialX = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
  2878.                 mLastFlingX = initialX;
  2879.                 mScroller.fling(initialX, 0, initialVelocity, 0,
  2880.                         0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
  2881.                 post(this);
  2882.             }
  2883.    
  2884.             public void startUsingDistance(int distance) {
  2885.                 if (distance == 0) return;
  2886.                
  2887.                 startCommon();
  2888.                
  2889.                 mLastFlingX = 0;
  2890.                 mScroller.startScroll(0, 0, -distance, 0, mAnimationDuration);
  2891.                 post(this);
  2892.             }
  2893.            
  2894.             public void stop(boolean scrollIntoSlots) {
  2895.                 removeCallbacks(this);
  2896.                 endFling(scrollIntoSlots);
  2897.             }
  2898.            
  2899.             private void endFling(boolean scrollIntoSlots) {
  2900.                 /*
  2901.                  * Force the scroller's status to finished (without setting its
  2902.                  * position to the end)
  2903.                  */
  2904.                 mScroller.forceFinished(true);
  2905.                
  2906.                 if (scrollIntoSlots) scrollIntoSlots();
  2907.             }
  2908.    
  2909.             public void run() {
  2910.    
  2911.                 if (mItemCount == 0) {
  2912.                     endFling(true);
  2913.                     return;
  2914.                 }
  2915.    
  2916.                 mShouldStopFling = false;
  2917.                
  2918.                 final Scroller scroller = mScroller;
  2919.                 boolean more = scroller.computeScrollOffset();
  2920.                 final int x = scroller.getCurrX();
  2921.    
  2922.                 // Flip sign to convert finger direction to list items direction
  2923.                 // (e.g. finger moving down means list is moving towards the top)
  2924.                 int delta = mLastFlingX - x;
  2925.    
  2926.                 // Pretend that each frame of a fling scroll is a touch scroll
  2927.                 if (delta > 0) {
  2928.                     // Moving towards the left. Use first view as mDownTouchPosition
  2929.                     mDownTouchPosition = mFirstPosition;
  2930.    
  2931.                     // Don't fling more than 1 screen
  2932.                     delta = Math.min(getWidth() - getPaddingLeft() - getPaddingRight() - 1, delta);
  2933.                 } else {
  2934.                     // Moving towards the right. Use last view as mDownTouchPosition
  2935.                     int offsetToLast = getChildCount() - 1;
  2936.                     mDownTouchPosition = mFirstPosition + offsetToLast;
  2937.    
  2938.                     // Don't fling more than 1 screen
  2939.                     delta = Math.max(-(getWidth() - getPaddingRight() - getPaddingLeft() - 1), delta);
  2940.                 }
  2941.    
  2942.                 trackMotionScroll(delta);
  2943.    
  2944.                 if (more && !mShouldStopFling) {
  2945.                     mLastFlingX = x;
  2946.                     post(this);
  2947.                 } else {
  2948.                    endFling(true);
  2949.                 }
  2950.             }
  2951.            
  2952.         }
  2953.        
  2954.         /**
  2955.          * Gallery extends LayoutParams to provide a place to hold current
  2956.          * Transformation information along with previous position/transformation
  2957.          * info.
  2958.          *
  2959.          */
  2960.         public static class LayoutParams extends ViewGroup.LayoutParams {
  2961.             public LayoutParams(Context c, AttributeSet attrs) {
  2962.                 super(c, attrs);
  2963.             }
  2964.    
  2965.             public LayoutParams(int w, int h) {
  2966.                 super(w, h);
  2967.             }
  2968.    
  2969.             public LayoutParams(ViewGroup.LayoutParams source) {
  2970.                 super(source);
  2971.             }
  2972.         }
  2973.     }
  2974.  
  2975. You'll also need to copy `simple_spinner_item` and `simple_spinner_dropdown_item` from the SDK layout folder into yours.
  2976.  
  2977. Then to reference it layout XML:
  2978.  
  2979.    <com.blah.EcoGallery
  2980.        xmlns:android="http://schemas.android.com/apk/res/android"
  2981.        xmlns:blah="http://schemas.android.com/apk/res/com.blah"
  2982.        android:layout_width="fill_parent"
  2983.        android:layout_height="fill_parent"
  2984.        blah:spacing="0px"
  2985.        blah:unselectedAlpha="0.5"
  2986.        blah:animationDuration="200"
  2987.        blah:gravity="center_horizontal"
  2988.        />
clone this paste RAW Paste Data