Android Open Source - carousel-android Carousel Base Adapter






From Project

Back to project page carousel-android.

License

The source code is released under:

The Code Project Open License (CPOL) 1.02 Preamble This License governs Your use of the Work. This License is intended to allow developers to use the Source Code and Executable Files provided as par...

If you think the Android project carousel-android listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package fr.rolandl.carousel;
/* w  ww .  j  a v  a 2  s . c o  m*/
import android.annotation.SuppressLint;
import android.content.Context;
import android.database.DataSetObserver;
import android.os.Handler;
import android.os.Parcelable;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.Adapter;
import android.widget.ListView;

/**
 * @author Igor Kushnarev, Ludovic Roland
 * @since 2014.12.19
 */
//Inspired by http://www.codeproject.com/Articles/146145/Android-D-Carousel
public abstract class CarouselBaseAdapter<T extends Adapter>
    extends ViewGroup
{

  /**
   * Extra menu information provided to the
   * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) } callback when a context menu is
   * brought up for this CarouselAdapter.
   */
  protected static class AdapterContextMenuInfo
      implements ContextMenu.ContextMenuInfo
  {

    public AdapterContextMenuInfo(View targetView, int position, long id)
    {
      this.targetView = targetView;
      this.position = position;
      this.id = id;
    }

    /**
     * The child view for which the context menu is being displayed. This will be one of the children of this CarouselAdapter.
     */
    public View targetView;

    /**
     * The position in the adapter for which the context menu is being displayed.
     */
    public int position;

    /**
     * The row id of the item for which the context menu is being displayed.
     */
    public long id;

  }

  protected class AdapterDataSetObserver
      extends DataSetObserver
  {

    private Parcelable mInstanceState = null;

    @Override
    public void onChanged()
    {
      dataChanged = true;
      oldItemCount = itemCount;
      itemCount = getAdapter().getCount();

      // Detect the case where a cursor that was previously invalidated has
      // been repopulated with new data.
      if (CarouselBaseAdapter.this.getAdapter().hasStableIds() == true && mInstanceState != null && oldItemCount == 0 && itemCount > 0)
      {
        CarouselBaseAdapter.this.onRestoreInstanceState(mInstanceState);
        mInstanceState = null;
      }
      else
      {
        rememberSyncState();
      }

      checkFocus();
      requestLayout();
    }

    @Override
    public void onInvalidated()
    {
      dataChanged = true;

      if (CarouselBaseAdapter.this.getAdapter().hasStableIds() == true)
      {
        // Remember the current state for the case where our hosting activity is being
        // stopped and later restarted
        mInstanceState = CarouselBaseAdapter.this.onSaveInstanceState();
      }

      // Data is invalid so we should reset our state
      oldItemCount = itemCount;
      itemCount = 0;
      selectedPosition = CarouselBaseAdapter.INVALID_POSITION;
      selectedRowId = CarouselBaseAdapter.INVALID_ROW_ID;
      nextSelectedPosition = CarouselBaseAdapter.INVALID_POSITION;
      nextSelectedRowId = CarouselBaseAdapter.INVALID_ROW_ID;
      needSync = false;

      checkSelectionChanged();
      checkFocus();
      requestLayout();
    }

    public void clearSavedState()
    {
      mInstanceState = null;
    }

  }

  private class SelectionNotifier
      extends Handler
      implements Runnable
  {

    @Override
    public void run()
    {
      if (dataChanged == true)
      {
        // Data has changed between when this SelectionNotifier
        // was posted and now. We need to wait until the CarouselAdapter
        // has been synched to the new data.
        post(this);
      }
      else
      {
        fireOnSelected();
      }
    }

  }

  /**
   * Interface definition for a callback to be invoked when an item in this CarouselAdapter has been clicked.
   */
  public static interface OnItemClickListener
  {

    /**
     * Callback method to be invoked when an item in this CarouselAdapter has been clicked.
     * <p/>
     * Implementers can call getItemAtPosition(position) if they need to access the data associated with the selected item.
     *
     * @param parent   The CarouselAdapter where the click happened.
     * @param view     The view within the CarouselAdapter that was clicked (this will be a view provided by the adapter)
     * @param position The position of the view in the adapter.
     * @param id       The row id of the item that was clicked.
     */
    public void onItemClick(CarouselBaseAdapter<?> parent, View view, int position, long id);

  }

  /**
   * Interface definition for a callback to be invoked when an item in this view has been clicked and held.
   */
  public static interface OnItemLongClickListener
  {

    /**
     * Callback method to be invoked when an item in this view has been clicked and held.
     * <p/>
     * Implementers can call getItemAtPosition(position) if they need to access the data associated with the selected item.
     *
     * @param parent   The AbsListView where the click happened
     * @param view     The view within the AbsListView that was clicked
     * @param position The position of the view in the list
     * @param id       The row id of the item that was clicked
     * @return true if the callback consumed the long click, false otherwise
     */
    public boolean onItemLongClick(CarouselBaseAdapter<?> parent, View view, int position, long id);

  }

  /**
   * Interface definition for a callback to be invoked when an item in this view has been selected.
   */
  public static interface OnItemSelectedListener
  {

    /**
     * Callback method to be invoked when an item in this view has been selected.
     * <p/>
     * Implementers can call getItemAtPosition(position) if they need to access the data associated with the selected item.
     *
     * @param parent   The CarouselAdapter where the selection happened
     * @param view     The view within the CarouselAdapter that was clicked
     * @param position The position of the view in the adapter
     * @param id       The row id of the item that is selected
     */
    public void onItemSelected(CarouselBaseAdapter<?> parent, View view, int position, long id);

    /**
     * Callback method to be invoked when the selection disappears from this view. The selection can disappear for instance when touch is activated or
     * when the adapter becomes empty.
     *
     * @param parent The CarouselAdapter that now contains no selected item.
     */
    public void onNothingSelected(CarouselBaseAdapter<?> parent);

  }

  /**
   * Represents an invalid position. All valid positions are in the range 0 to 1 less than the number of items in the current adapter.
   */
  public static final int INVALID_POSITION = -1;

  /**
   * Represents an empty or invalid row id
   */
  public static final long INVALID_ROW_ID = Long.MIN_VALUE;

  /**
   * Sync based on the selected child
   */
  protected static final int SYNC_SELECTED_POSITION = 0;

  /**
   * Sync based on the first child displayed
   */
  private static final int SYNC_FIRST_POSITION = 1;

  /**
   * Maximum amount of time to spend in {@link #findSyncPosition()}
   */
  private static final int SYNC_MAX_DURATION_MILLIS = 100;

  /**
   * The position of the first child displayed
   */
  protected int firstPosition = 0;

  /**
   * Position from which to start looking for syncRowId
   */
  protected int syncPosition;

  /**
   * Row id to look for when data has changed
   */
  protected long syncRowId = CarouselBaseAdapter.INVALID_ROW_ID;

  /**
   * True if we need to sync to syncRowId
   */
  protected boolean needSync = false;

  /**
   * Indicates whether to sync based on the selection or position. Possible values are {@link #SYNC_SELECTED_POSITION} or {@link #SYNC_FIRST_POSITION}
   * .
   */
  protected int syncMode;

  /**
   * Indicates that this view is currently being laid out.
   */
  protected boolean isInLayout = false;

  /**
   * The listener that receives notifications when an item is selected.
   */
  private OnItemSelectedListener onItemSelectedListener;

  /**
   * The listener that receives notifications when an item is clicked.
   */
  private OnItemClickListener onItemClickListener;

  /**
   * The listener that receives notifications when an item is long clicked.
   */
  protected OnItemLongClickListener onItemLongClickListener;

  /**
   * True if the data has changed since the last layout
   */
  protected boolean dataChanged;

  /**
   * The position within the adapter's data set of the item to select during the next layout.
   */
  protected int nextSelectedPosition = CarouselBaseAdapter.INVALID_POSITION;

  /**
   * The item id of the item to select during the next layout.
   */
  private long nextSelectedRowId = CarouselBaseAdapter.INVALID_ROW_ID;

  /**
   * The position within the adapter's data set of the currently selected item.
   */
  protected int selectedPosition = CarouselBaseAdapter.INVALID_POSITION;

  /**
   * The item id of the currently selected item.
   */
  protected long selectedRowId = CarouselBaseAdapter.INVALID_ROW_ID;

  /**
   * View to show if there are no items to show.
   */
  private View emptyView;

  /**
   * The number of items in the current adapter.
   */
  protected int itemCount;

  /**
   * The number of items in the adapter before a data changed event occured.
   */
  protected int oldItemCount;

  /**
   * The last selected position we used when notifying
   */
  protected int oldSelectedPosition = CarouselBaseAdapter.INVALID_POSITION;

  /**
   * The id of the last selected position we used when notifying
   */
  protected long oldSelectedRowId = CarouselBaseAdapter.INVALID_ROW_ID;

  /**
   * Indicates what focusable state is requested when calling setFocusable(). In addition to this, this view has other criteria for actually
   * determining the focusable state (such as whether its empty or the text filter is shown).
   *
   * @see #setFocusable(boolean)
   * @see #checkFocus()
   */
  private boolean desiredFocusableState;

  private boolean desiredFocusableInTouchModeState;

  private SelectionNotifier selectionNotifier;

  /**
   * When set to true, calls to requestLayout() will not propagate up the parent hierarchy. This is used to layout the children during a layout pass.
   */
  private boolean blockLayoutRequests = false;

  public CarouselBaseAdapter(Context context)
  {
    super(context);
  }

  public CarouselBaseAdapter(Context context, AttributeSet attrs)
  {
    super(context, attrs);
  }

  public CarouselBaseAdapter(Context context, AttributeSet attrs, int defStyle)
  {
    super(context, attrs, defStyle);
  }

  /**
   * Register a callback to be invoked when an item in this CarouselAdapter has been clicked.
   *
   * @param listener The callback that will be invoked.
   */
  public void setOnItemClickListener(OnItemClickListener listener)
  {
    onItemClickListener = listener;
  }

  /**
   * @return The callback to be invoked with an item in this CarouselAdapter has been clicked, or null id no callback has been set.
   */
  public final OnItemClickListener getOnItemClickListener()
  {
    return onItemClickListener;
  }

  /**
   * Call the OnItemClickListener, if it is defined.
   *
   * @param view     The view within the CarouselAdapter that was clicked.
   * @param position The position of the view in the adapter.
   * @param id       The row id of the item that was clicked.
   * @return True if there was an assigned OnItemClickListener that was called, false otherwise is returned.
   */
  public boolean performItemClick(View view, int position, long id)
  {
    if (onItemClickListener != null)
    {
      playSoundEffect(SoundEffectConstants.CLICK);
      onItemClickListener.onItemClick(this, view, position, id);
      return true;
    }

    return false;
  }

  /**
   * Register a callback to be invoked when an item in this CarouselAdapter has been clicked and held
   *
   * @param listener The callback that will run
   */
  public void setOnItemLongClickListener(OnItemLongClickListener listener)
  {
    if (!isLongClickable())
    {
      setLongClickable(true);
    }

    onItemLongClickListener = listener;
  }

  /**
   * @return The callback to be invoked with an item in this CarouselAdapter has been clicked and held, or null id no callback as been set.
   */
  public final OnItemLongClickListener getOnItemLongClickListener()
  {
    return onItemLongClickListener;
  }

  /**
   * Register a callback to be invoked when an item in this CarouselAdapter has been selected.
   *
   * @param listener The callback that will run
   */
  public void setOnItemSelectedListener(OnItemSelectedListener listener)
  {
    onItemSelectedListener = listener;
  }

  public final OnItemSelectedListener getOnItemSelectedListener()
  {
    return onItemSelectedListener;
  }

  /**
   * Return the position of the currently selected item within the adapter's data set
   *
   * @return int Position (starting at 0), or {@link #INVALID_POSITION} if there is nothing selected.
   */
  public int getSelectedItemPosition()
  {
    return nextSelectedPosition;
  }

  /**
   * @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID} if nothing is selected.
   */
  public long getSelectedItemId()
  {
    return nextSelectedRowId;
  }

  /**
   * @return The data corresponding to the currently selected item, or null if there is nothing selected.
   */
  public Object getSelectedItem()
  {
    T adapter = getAdapter();
    int selection = getSelectedItemPosition();
    if (adapter != null && adapter.getCount() > 0 && selection >= 0)
    {
      return adapter.getItem(selection);
    }
    else
    {
      return null;
    }
  }

  /**
   * @return The number of items owned by the Adapter associated with this CarouselAdapter. (This is the number of data items, which may be larger
   * than the number of visible view.)
   */
  public int getCount()
  {
    return itemCount;
  }

  /**
   * Get the position within the adapter's data set for the view, where view is a an adapter item or a descendant of an adapter item.
   *
   * @param view an adapter item, or a descendant of an adapter item. This must be visible in this CarouselAdapter at the time of the call.
   * @return the position within the adapter's data set of the view, or {@link #INVALID_POSITION} if the view does not correspond to a list item (or
   * it is not currently visible).
   */
  public int getPositionForView(View view)
  {
    View listItem = view;

    try
    {
      View v;

      while (!(v = (View) listItem.getParent()).equals(this))
      {
        listItem = v;
      }
    }
    catch (ClassCastException exception)
    {
      // We made it up to the window without find this list view
      return INVALID_POSITION;
    }

    // Search the children for the list item
    final int childCount = getChildCount();

    for (int i = 0; i < childCount; i++)
    {
      if (getChildAt(i).equals(listItem) == true)
      {
        return firstPosition + i;
      }
    }

    // Child not found!
    return INVALID_POSITION;
  }

  /**
   * Returns the position within the adapter's data set for the first item displayed on screen.
   *
   * @return The position within the adapter's data set
   */
  public int getFirstVisiblePosition()
  {
    return firstPosition;
  }

  /**
   * Returns the position within the adapter's data set for the last item displayed on screen.
   *
   * @return The position within the adapter's data set
   */
  public int getLastVisiblePosition()
  {
    return firstPosition + getChildCount() - 1;
  }

  /**
   * Sets the view to show if the adapter is empty
   */
  public void setEmptyView(View emptyView)
  {
    this.emptyView = emptyView;
    final T adapter = getAdapter();
    final boolean empty = ((adapter == null) || adapter.isEmpty());
    updateEmptyStatus(empty);
  }

  /**
   * When the current adapter is empty, the CarouselAdapter can display a special view call the empty view. The empty view is used to provide feedback
   * to the user that no data is available in this CarouselAdapter.
   *
   * @return The view to show if the adapter is empty.
   */
  public View getEmptyView()
  {
    return emptyView;
  }

  /**
   * Indicates whether this view is in filter mode. Filter mode can for instance be enabled by a user when typing on the keyboard.
   *
   * @return True if the view is in filter mode, false otherwise.
   */
  private boolean isInFilterMode()
  {
    return false;
  }

  protected void checkFocus()
  {
    final T adapter = getAdapter();
    final boolean empty = adapter == null || adapter.getCount() == 0;
    final boolean focusable = empty == false || isInFilterMode() == true;
    // The order in which we set focusable in touch mode/focusable may matter
    // for the client, see View.setFocusableInTouchMode() comments for more
    // details
    super.setFocusableInTouchMode(focusable == true && desiredFocusableInTouchModeState == true);
    super.setFocusable(focusable == true && desiredFocusableState == true);

    if (emptyView != null)
    {
      updateEmptyStatus((adapter == null) || adapter.isEmpty() == true);
    }
  }

  /**
   * Update the status of the list based on the empty parameter. If empty is true and we have an empty view, display it. In all the other cases, make
   * sure that the listview is VISIBLE and that the empty view is GONE (if it's not null).
   */
  @SuppressLint("WrongCall")
  private void updateEmptyStatus(boolean empty)
  {
    if (isInFilterMode() == true)
    {
      empty = false;
    }

    if (empty == true)
    {
      if (emptyView != null)
      {
        emptyView.setVisibility(View.VISIBLE);
        setVisibility(View.GONE);
      }
      else
      {
        // If the caller just removed our empty view, make sure the list view is visible
        setVisibility(View.VISIBLE);
      }

      // We are now GONE, so pending layouts will not be dispatched.
      // Force one here to make sure that the state of the list matches
      // the state of the adapter.
      if (dataChanged == true)
      {
        this.onLayout(false, getLeft(), getTop(), getRight(), getBottom());
      }
    }
    else
    {
      if (emptyView != null)
      {
        emptyView.setVisibility(View.GONE);
      }

      setVisibility(View.VISIBLE);
    }
  }

  /**
   * Gets the data associated with the specified position in the list.
   *
   * @param position Which data to get
   * @return The data associated with the specified position in the list
   */
  public Object getItemAtPosition(int position)
  {
    final T adapter = getAdapter();
    return (adapter == null || position < 0) ? null : adapter.getItem(position);
  }

  protected void selectionChanged()
  {
    if (onItemSelectedListener != null)
    {
      if (isInLayout == true || blockLayoutRequests == true)
      {
        // If we are in a layout traversal, defer notification
        // by posting. This ensures that the view tree is
        // in a consistent state and is able to accomodate
        // new layout or invalidate requests.
        if (selectionNotifier == null)
        {
          selectionNotifier = new SelectionNotifier();
        }

        selectionNotifier.post(selectionNotifier);
      }
      else
      {
        fireOnSelected();
      }
    }

    // we fire selection events here not in View
    if (selectedPosition != ListView.INVALID_POSITION && isShown() == true && isInTouchMode() == false)
    {
      sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
    }
  }

  private void fireOnSelected()
  {
    if (onItemSelectedListener == null)
    {
      return;
    }

    final int selection = this.getSelectedItemPosition();

    if (selection >= 0)
    {
      final View v = getSelectedView();
      onItemSelectedListener.onItemSelected(this, v, selection, getAdapter().getItemId(selection));
    }
    else
    {
      onItemSelectedListener.onNothingSelected(this);
    }
  }

  public long getItemIdAtPosition(int position)
  {
    final T adapter = getAdapter();
    return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position);
  }

  protected void handleDataChanged()
  {
    final int count = itemCount;
    boolean found = false;

    if (count > 0)
    {
      int newPos;

      // Find the row we are supposed to sync to
      if (needSync == true)
      {
        // Update this first, since setNextSelectedPositionInt inspects it
        needSync = false;

        // See if we can find a position in the new data with the same id as the old selection
        newPos = findSyncPosition();

        if (newPos >= 0)
        {
          // Verify that new selection is selectable
          final int selectablePos = lookForSelectablePosition(newPos);

          if (selectablePos == newPos)
          {
            // Same row id is selected
            setNextSelectedPositionInt(newPos);
            found = true;
          }
        }
      }
      if (found == false)
      {
        // Try to use the same position if we can't find matching data
        newPos = getSelectedItemPosition();

        // Pin position to the available range
        if (newPos >= count)
        {
          newPos = count - 1;
        }

        if (newPos < 0)
        {
          newPos = 0;
        }

        // Make sure we select something selectable -- first look down
        int selectablePos = lookForSelectablePosition(newPos);

        if (selectablePos < 0)
        {
          // Looking down didn't work -- try looking up
          selectablePos = lookForSelectablePosition(newPos);
        }
        if (selectablePos >= 0)
        {
          setNextSelectedPositionInt(selectablePos);
          checkSelectionChanged();
          found = true;
        }
      }
    }
    if (found == false)
    {
      // Nothing is selected
      selectedPosition = INVALID_POSITION;
      selectedRowId = INVALID_ROW_ID;
      nextSelectedPosition = INVALID_POSITION;
      nextSelectedRowId = INVALID_ROW_ID;
      needSync = false;
      checkSelectionChanged();
    }
  }

  protected void checkSelectionChanged()
  {
    if ((selectedPosition != oldSelectedPosition) || (selectedRowId != oldSelectedRowId))
    {
      selectionChanged();
      oldSelectedPosition = selectedPosition;
      oldSelectedRowId = selectedRowId;
    }
  }

  /**
   * Searches the adapter for a position matching syncRowId. The search starts at syncPosition and then alternates between moving up and moving down
   * until 1) we find the right position, or 2) we run out of time, or 3) we have looked at every position
   *
   * @return Position of the row that matches syncRowId, or {@link #INVALID_POSITION} if it can't be found
   */
  private int findSyncPosition()
  {
    final int count = itemCount;

    if (count == 0)
    {
      return INVALID_POSITION;
    }

    final long idToMatch = syncRowId;

    // If there isn't a selection don't hunt for it
    if (idToMatch == INVALID_ROW_ID)
    {
      return INVALID_POSITION;
    }

    // Pin seed to reasonable values
    int seed = syncPosition;
    seed = Math.max(0, seed);
    seed = Math.min(count - 1, seed);

    long rowId;
    final long endTime = SystemClock.uptimeMillis() + CarouselBaseAdapter.SYNC_MAX_DURATION_MILLIS;

    // first position scanned so far
    int first = seed;

    // last position scanned so far
    int last = seed;

    // True if we should move down on the next iteration
    boolean next = false;

    // True when we have looked at the first item in the data
    boolean hitFirst;

    // True when we have looked at the last item in the data
    boolean hitLast;

    // Get the item ID locally (instead of getItemIdAtPosition), so
    // we need the adapter
    final T adapter = getAdapter();

    if (adapter == null)
    {
      return INVALID_POSITION;
    }

    while (SystemClock.uptimeMillis() <= endTime)
    {
      rowId = adapter.getItemId(seed);

      if (rowId == idToMatch)
      {
        // Found it!
        return seed;
      }

      hitLast = last == count - 1;
      hitFirst = first == 0;

      if (hitLast == true && hitFirst == true)
      {
        // Looked at everything
        break;
      }

      if (hitFirst == true || (next == true && hitLast == false))
      {
        // Either we hit the top, or we are trying to move down
        last++;
        seed = last;
        // Try going up next time
        next = false;
      }
      else if (hitLast == true || (next == false && hitFirst == false))
      {
        // Either we hit the bottom, or we are trying to move up
        first--;
        seed = first;
        // Try going down next time
        next = true;
      }

    }

    return INVALID_POSITION;
  }

  /**
   * Find a position that can be selected (i.e., is not a separator).
   *
   * @param position The starting position to look at.
   * @return The next selectable position starting at position and then searching either up or down. Returns {@link #INVALID_POSITION} if nothing can
   * be found.
   */
  private int lookForSelectablePosition(int position)
  {
    return position;
  }

  /**
   * Utility to keep selectedPosition and selectedRowId in sync
   *
   * @param position Our current position
   */
  protected void setSelectedPositionInt(int position)
  {
    selectedPosition = position;
    selectedRowId = getItemIdAtPosition(position);
  }

  /**
   * Utility to keep nextSelectedPosition and nextSelectedRowId in sync
   *
   * @param position Intended value for selectedPosition the next time we go through layout
   */
  protected void setNextSelectedPositionInt(int position)
  {
    nextSelectedPosition = position;
    nextSelectedRowId = getItemIdAtPosition(position);

    // If we are trying to sync to the selection, update that too
    if (needSync == true && syncMode == CarouselBaseAdapter.SYNC_SELECTED_POSITION && position >= 0)
    {
      syncPosition = position;
      syncRowId = nextSelectedRowId;
    }
  }

  /**
   * Remember enough information to restore the screen state when the data has changed.
   */
  private void rememberSyncState()
  {
    if (getChildCount() > 0)
    {
      needSync = true;

      if (selectedPosition >= 0)
      {
        // Sync the selection state
        syncRowId = nextSelectedRowId;
        syncPosition = nextSelectedPosition;

        syncMode = CarouselBaseAdapter.SYNC_SELECTED_POSITION;
      }
      else
      {
        // Sync the based on the offset of the first view
        final T adapter = getAdapter();

        if (firstPosition >= 0 && firstPosition < adapter.getCount())
        {
          syncRowId = adapter.getItemId(firstPosition);
        }
        else
        {
          syncRowId = NO_ID;
        }

        syncPosition = firstPosition;
        syncMode = CarouselBaseAdapter.SYNC_FIRST_POSITION;
      }
    }
  }

  @Override
  public void setFocusable(boolean focusable)
  {
    final T adapter = getAdapter();
    final boolean empty = adapter == null || adapter.getCount() == 0;
    desiredFocusableState = focusable;

    if (focusable == false)
    {
      desiredFocusableInTouchModeState = false;
    }

    super.setFocusable(focusable == true && (empty == false || isInFilterMode() == true));
  }

  @Override
  public void setFocusableInTouchMode(boolean focusable)
  {
    final T adapter = getAdapter();
    final boolean empty = adapter == null || adapter.getCount() == 0;
    desiredFocusableInTouchModeState = focusable;

    if (focusable == true)
    {
      desiredFocusableState = true;
    }

    super.setFocusableInTouchMode(focusable == true && (empty == false || isInFilterMode() == true));
  }

  @Override
  public void setOnClickListener(OnClickListener l)
  {
    throw new RuntimeException("Don't call setOnClickListener for an CarouselAdapter. " + "You probably want setOnItemClickListener instead");
  }

  /**
   * Override to prevent freezing of any views created by the adapter.
   */
  @Override
  protected void dispatchSaveInstanceState(SparseArray<Parcelable> container)
  {
    dispatchFreezeSelfOnly(container);
  }

  /**
   * Override to prevent thawing of any views created by the adapter.
   */
  @Override
  protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container)
  {
    dispatchThawSelfOnly(container);
  }

  @Override
  public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event)
  {
    boolean populated = false;

    // This is an exceptional case which occurs when a window gets the
    // focus and sends a focus event via its focused child to announce
    // current focus/selection. CarouselAdapter fires selection but not focus
    // events so we change the event type here.
    if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED)
    {
      event.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED);
    }

    // we send selection events only from CarouselAdapter to avoid
    // generation of such event for each child
    final View selectedView = getSelectedView();

    if (selectedView != null)
    {
      populated = selectedView.dispatchPopulateAccessibilityEvent(event);
    }

    if (populated == false)
    {
      if (selectedView != null)
      {
        event.setEnabled(selectedView.isEnabled());
      }

      event.setItemCount(getCount());
      event.setCurrentItemIndex(getSelectedItemPosition());
    }

    return populated;
  }

  @Override
  protected boolean canAnimate()
  {
    return super.canAnimate() == true && itemCount > 0;
  }

  /**
   * This method is not supported and throws an UnsupportedOperationException when called.
   *
   * @param child Ignored.
   * @throws UnsupportedOperationException Every time this method is invoked.
   */
  @Override
  public void addView(View child)
  {
    throw new UnsupportedOperationException("addView(View) is not supported in CarouselAdapter");
  }

  /**
   * This method is not supported and throws an UnsupportedOperationException when called.
   *
   * @param child Ignored.
   * @param index Ignored.
   * @throws UnsupportedOperationException Every time this method is invoked.
   */
  @Override
  public void addView(View child, int index)
  {
    throw new UnsupportedOperationException("addView(View, int) is not supported in CarouselAdapter");
  }

  /**
   * This method is not supported and throws an UnsupportedOperationException when called.
   *
   * @param child  Ignored.
   * @param params Ignored.
   * @throws UnsupportedOperationException Every time this method is invoked.
   */
  @Override
  public void addView(View child, LayoutParams params)
  {
    throw new UnsupportedOperationException("addView(View, LayoutParams) " + "is not supported in CarouselAdapter");
  }

  /**
   * This method is not supported and throws an UnsupportedOperationException when called.
   *
   * @param child  Ignored.
   * @param index  Ignored.
   * @param params Ignored.
   * @throws UnsupportedOperationException Every time this method is invoked.
   */
  @Override
  public void addView(View child, int index, LayoutParams params)
  {
    throw new UnsupportedOperationException("addView(View, int, LayoutParams) " + "is not supported in CarouselAdapter");
  }

  /**
   * This method is not supported and throws an UnsupportedOperationException when called.
   *
   * @param child Ignored.
   * @throws UnsupportedOperationException Every time this method is invoked.
   */
  @Override
  public void removeView(View child)
  {
    throw new UnsupportedOperationException("removeView(View) is not supported in CarouselAdapter");
  }

  /**
   * This method is not supported and throws an UnsupportedOperationException when called.
   *
   * @param index Ignored.
   * @throws UnsupportedOperationException Every time this method is invoked.
   */
  @Override
  public void removeViewAt(int index)
  {
    throw new UnsupportedOperationException("removeViewAt(int) is not supported in CarouselAdapter");
  }

  /**
   * This method is not supported and throws an UnsupportedOperationException when called.
   *
   * @throws UnsupportedOperationException Every time this method is invoked.
   */
  @Override
  public void removeAllViews()
  {
    throw new UnsupportedOperationException("removeAllViews() is not supported in CarouselAdapter");
  }

  @Override
  protected void onLayout(boolean changed, int left, int top, int right, int bottom)
  {
  }

  /**
   * Returns the adapter currently associated with this widget.
   *
   * @return The adapter used to provide this view's content.
   */
  public abstract T getAdapter();

  /**
   * Sets the adapter that provides the data and the views to represent the data in this widget.
   *
   * @param adapter The adapter to use to create this view's content.
   */
  public abstract void setAdapter(T adapter);

  /**
   * @return The view corresponding to the currently selected item, or null if nothing is selected
   */
  public abstract View getSelectedView();

  /**
   * Sets the currently selected item. To support accessibility subclasses that override this method must invoke the overriden super method first.
   *
   * @param position Index (starting at 0) of the data item to be selected.
   */
  public abstract void setSelection(int position);
}




Java Source Code List

fr.rolandl.carousel.CarouselAdapter.java
fr.rolandl.carousel.CarouselBaseAdapter.java
fr.rolandl.carousel.CarouselItem.java
fr.rolandl.carousel.CarouselSpinner.java
fr.rolandl.carousel.Carousel.java
fr.rolandl.carousel.Rotator.java
fr.rolandl.sample.carousel.MainActivity.java
fr.rolandl.sample.carousel.adapter.MyAdapter.java
fr.rolandl.sample.carousel.bo.Photo.java