/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.widget; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.database.DataSetObserver; import android.os.Build; 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.ViewDebug; import android.view.ViewGroup; import android.view.ViewHierarchyEncoder; import android.view.ViewStructure; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.autofill.AutofillManager; /** * An AdapterView is a view whose children are determined by an {@link Adapter}. * *
* See {@link ListView}, {@link GridView}, {@link Spinner} and * {@link Gallery} for commonly used subclasses of AdapterView. * *
For more information about using AdapterView, read the * Binding to Data with AdapterView * developer guide.
* Implementers can call getItemAtPosition(position) if they need * to access the data associated with the selected item. * * @param parent The AdapterView where the click happened. * @param view The view within the AdapterView 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. */ void onItemClick(AdapterView> parent, View view, int position, long id); } /** * Register a callback to be invoked when an item in this AdapterView has * been clicked. * * @param listener The callback that will be invoked. */ public void setOnItemClickListener(@Nullable OnItemClickListener listener) { mOnItemClickListener = listener; } /** * @return The callback to be invoked with an item in this AdapterView has * been clicked, or null if no callback has been set. */ @Nullable public final OnItemClickListener getOnItemClickListener() { return mOnItemClickListener; } /** * Call the OnItemClickListener, if it is defined. Performs all normal * actions associated with clicking: reporting accessibility event, playing * a sound, etc. * * @param view The view within the AdapterView 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) { final boolean result; if (mOnItemClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); mOnItemClickListener.onItemClick(this, view, position, id); result = true; } else { result = false; } if (view != null) { view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); } return result; } /** * Interface definition for a callback to be invoked when an item in this * view has been clicked and held. */ public interface OnItemLongClickListener { /** * Callback method to be invoked when an item in this view has been * clicked and held. * * 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 */ boolean onItemLongClick(AdapterView> parent, View view, int position, long id); } /** * Register a callback to be invoked when an item in this AdapterView has * been clicked and held * * @param listener The callback that will run */ public void setOnItemLongClickListener(OnItemLongClickListener listener) { if (!isLongClickable()) { setLongClickable(true); } mOnItemLongClickListener = listener; } /** * @return The callback to be invoked with an item in this AdapterView has * been clicked and held, or null if no callback has been set. */ public final OnItemLongClickListener getOnItemLongClickListener() { return mOnItemLongClickListener; } /** * Interface definition for a callback to be invoked when * an item in this view has been selected. */ public interface OnItemSelectedListener { /** *
Callback method to be invoked when an item in this view has been * selected. This callback is invoked only when the newly selected * position is different from the previously selected position or if * there was no selected item.
* * Implementers can call getItemAtPosition(position) if they need to access the * data associated with the selected item. * * @param parent The AdapterView where the selection happened * @param view The view within the AdapterView that was clicked * @param position The position of the view in the adapter * @param id The row id of the item that is selected */ void onItemSelected(AdapterView> 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 AdapterView that now contains no selected item. */ void onNothingSelected(AdapterView> parent); } /** * Register a callback to be invoked when an item in this AdapterView has * been selected. * * @param listener The callback that will run */ public void setOnItemSelectedListener(@Nullable OnItemSelectedListener listener) { mOnItemSelectedListener = listener; } @Nullable public final OnItemSelectedListener getOnItemSelectedListener() { return mOnItemSelectedListener; } /** * 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 AdapterView. * */ public 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 AdapterView. */ 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; } /** * 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); /** * 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 AdapterView"); } /** * 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 AdapterView"); } /** * 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 AdapterView"); } /** * 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 AdapterView"); } /** * 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 AdapterView"); } /** * 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 AdapterView"); } /** * 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 AdapterView"); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { mLayoutHeight = getHeight(); } /** * 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. */ @ViewDebug.CapturedViewProperty public int getSelectedItemPosition() { return mNextSelectedPosition; } /** * @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID} * if nothing is selected. */ @ViewDebug.CapturedViewProperty public long getSelectedItemId() { return mNextSelectedRowId; } /** * @return The view corresponding to the currently selected item, or null * if nothing is selected */ public abstract View getSelectedView(); /** * @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 * AdapterView. (This is the number of data items, which may be * larger than the number of visible views.) */ @ViewDebug.CapturedViewProperty public int getCount() { return mItemCount; } /** * Returns 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. *
* Note: The result of this method only reflects the
* position of the data bound to view during the most recent
* layout pass. If the adapter's data set has changed without a subsequent
* layout pass, the position returned by this method may not match the
* current position of the data within the adapter.
*
* @param view an adapter item, or a descendant of an adapter item. This
* must be visible in this AdapterView 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()) != null && !v.equals(this)) {
listItem = v;
}
} catch (ClassCastException e) {
// We made it up to the window without find this list view
return INVALID_POSITION;
}
if (listItem != null) {
// Search the children for the list item
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
if (getChildAt(i).equals(listItem)) {
return mFirstPosition + 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 mFirstPosition;
}
/**
* 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 mFirstPosition + getChildCount() - 1;
}
/**
* Sets the currently selected item. To support accessibility subclasses that
* override this method must invoke the overridden super method first.
*
* @param position Index (starting at 0) of the data item to be selected.
*/
public abstract void setSelection(int position);
/**
* Sets the view to show if the adapter is empty
*/
@android.view.RemotableViewMethod
public void setEmptyView(View emptyView) {
mEmptyView = emptyView;
// If not explicitly specified this view is important for accessibility.
if (emptyView != null
&& emptyView.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
emptyView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
final T adapter = getAdapter();
final boolean empty = ((adapter == null) || adapter.isEmpty());
updateEmptyStatus(empty);
}
/**
* When the current adapter is empty, the AdapterView can display a special view
* called the empty view. The empty view is used to provide feedback to the user
* that no data is available in this AdapterView.
*
* @return The view to show if the adapter is empty.
*/
public View getEmptyView() {
return mEmptyView;
}
/**
* 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.
*/
boolean isInFilterMode() {
return false;
}
@Override
public void setFocusable(@Focusable int focusable) {
final T adapter = getAdapter();
final boolean empty = adapter == null || adapter.getCount() == 0;
mDesiredFocusableState = focusable;
if ((focusable & (FOCUSABLE_AUTO | FOCUSABLE)) == 0) {
mDesiredFocusableInTouchModeState = false;
}
super.setFocusable((!empty || isInFilterMode()) ? focusable : NOT_FOCUSABLE);
}
@Override
public void setFocusableInTouchMode(boolean focusable) {
final T adapter = getAdapter();
final boolean empty = adapter == null || adapter.getCount() == 0;
mDesiredFocusableInTouchModeState = focusable;
if (focusable) {
mDesiredFocusableState = FOCUSABLE;
}
super.setFocusableInTouchMode(focusable && (!empty || isInFilterMode()));
}
void checkFocus() {
final T adapter = getAdapter();
final boolean empty = adapter == null || adapter.getCount() == 0;
final boolean focusable = !empty || isInFilterMode();
// 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 && mDesiredFocusableInTouchModeState);
super.setFocusable(focusable ? mDesiredFocusableState : NOT_FOCUSABLE);
if (mEmptyView != null) {
updateEmptyStatus((adapter == null) || adapter.isEmpty());
}
}
/**
* 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).
*/
private void updateEmptyStatus(boolean empty) {
if (isInFilterMode()) {
empty = false;
}
if (empty) {
if (mEmptyView != null) {
mEmptyView.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 (mDataChanged) {
this.onLayout(false, mLeft, mTop, mRight, mBottom);
}
} else {
if (mEmptyView != null) mEmptyView.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) {
T adapter = getAdapter();
return (adapter == null || position < 0) ? null : adapter.getItem(position);
}
public long getItemIdAtPosition(int position) {
T adapter = getAdapter();
return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position);
}
@Override
public void setOnClickListener(OnClickListener l) {
throw new RuntimeException("Don't call setOnClickListener for an AdapterView. "
+ "You probably want setOnItemClickListener instead");
}
/**
* Override to prevent freezing of any views created by the adapter.
*/
@Override
protected void dispatchSaveInstanceState(SparseArray It also sets the autofill options in the structure; when overridden, it should set it as
* well, either explicitly by calling {@link ViewStructure#setAutofillOptions(CharSequence[])}
* or implicitly by calling {@code super.onProvideAutofillStructure(structure, flags)}.
*/
@Override
public void onProvideAutofillStructure(ViewStructure structure, int flags) {
super.onProvideAutofillStructure(structure, flags);
}
/** @hide */
@Override
protected void onProvideStructure(@NonNull ViewStructure structure,
@ViewStructureType int viewFor, int flags) {
super.onProvideStructure(structure, viewFor, flags);
if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
final Adapter adapter = getAdapter();
if (adapter == null) return;
final CharSequence[] options = adapter.getAutofillOptions();
if (options != null) {
structure.setAutofillOptions(options);
}
}
}
}