scrap = mScrapViews[i];
+ final int scrapCount = scrap.size();
+ for (int j = 0; j < scrapCount; j++) {
+ scrap.get(i).setDrawingCacheBackgroundColor(color);
+ }
+ }
+ }
+ // Just in case this is called during a layout pass
+ final View[] activeViews = mActiveViews;
+ final int count = activeViews.length;
+ for (int i = 0; i < count; ++i) {
+ final View victim = activeViews[i];
+ if (victim != null) {
+ victim.setDrawingCacheBackgroundColor(color);
+ }
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////
+ //Newly Added Methods.
+ /////////////////////////////////////////////////////
+
+ private void dispatchFinishTemporaryDetach(View v) {
+ if( v == null )
+ return;
+
+ v.onFinishTemporaryDetach();
+ if( v instanceof ViewGroup){
+ ViewGroup group = (ViewGroup) v;
+ final int count = group.getChildCount();
+ for (int i = 0; i < count; i++) {
+ dispatchFinishTemporaryDetach(group.getChildAt(i));
+ }
+ }
+ }
+}//end of class
diff --git a/src/com/huewu/pla/lib/internal/PLA_AdapterView.java b/src/com/huewu/pla/lib/internal/PLA_AdapterView.java
new file mode 100644
index 0000000..844a873
--- /dev/null
+++ b/src/com/huewu/pla/lib/internal/PLA_AdapterView.java
@@ -0,0 +1,1136 @@
+/*
+ * 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 com.huewu.pla.lib.internal;
+
+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.SoundEffectConstants;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.Adapter;
+import android.widget.ListView;
+
+
+/**
+ * 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.
+ */
+public abstract class PLA_AdapterView extends ViewGroup {
+
+ /**
+ * The item view type returned by {@link Adapter#getItemViewType(int)} when
+ * the adapter does not want the item's view recycled.
+ */
+ public static final int ITEM_VIEW_TYPE_IGNORE = -1;
+
+ /**
+ * The item view type returned by {@link Adapter#getItemViewType(int)} when
+ * the item is a header or footer.
+ */
+ public static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2;
+
+ /**
+ * The position of the first child displayed
+ */
+ @ViewDebug.ExportedProperty
+ int mFirstPosition = 0;
+
+ /**
+ * The offset in pixels from the top of the AdapterView to the top
+ * of the view to select during the next layout.
+ */
+ int mSpecificTop;
+
+ /**
+ * Position from which to start looking for mSyncRowId
+ */
+ int mSyncPosition;
+
+ /**
+ * Row id to look for when data has changed
+ */
+ long mSyncRowId = INVALID_ROW_ID;
+
+ /**
+ * Height of the view when mSyncPosition and mSyncRowId where set
+ */
+ long mSyncHeight;
+
+ /**
+ * True if we need to sync to mSyncRowId
+ */
+ boolean mNeedSync = false;
+
+ /**
+ * Indicates whether to sync based on the selection or position. Possible
+ * values are {@link #SYNC_SELECTED_POSITION} or
+ * {@link #SYNC_FIRST_POSITION}.
+ */
+ int mSyncMode;
+
+ /**
+ * Our height after the last layout
+ */
+ private int mLayoutHeight;
+
+ /**
+ * Sync based on the selected child
+ */
+ static final int SYNC_SELECTED_POSITION = 0;
+
+ /**
+ * Sync based on the first child displayed
+ */
+ static final int SYNC_FIRST_POSITION = 1;
+
+ /**
+ * Maximum amount of time to spend in {@link #findSyncPosition()}
+ */
+ static final int SYNC_MAX_DURATION_MILLIS = 100;
+
+ /**
+ * Indicates that this view is currently being laid out.
+ */
+ boolean mInLayout = false;
+
+ /**
+ * The listener that receives notifications when an item is selected.
+ */
+ OnItemSelectedListener mOnItemSelectedListener;
+
+ /**
+ * The listener that receives notifications when an item is clicked.
+ */
+ OnItemClickListener mOnItemClickListener;
+
+ /**
+ * The listener that receives notifications when an item is long clicked.
+ */
+ OnItemLongClickListener mOnItemLongClickListener;
+
+ /**
+ * True if the data has changed since the last layout
+ */
+ boolean mDataChanged;
+
+ /**
+ * The position within the adapter's data set of the item to select
+ * during the next layout.
+ */
+ @ViewDebug.ExportedProperty
+ int mNextSelectedPosition = INVALID_POSITION;
+
+ /**
+ * The item id of the item to select during the next layout.
+ */
+ long mNextSelectedRowId = INVALID_ROW_ID;
+
+ /**
+ * The position within the adapter's data set of the currently selected item.
+ */
+ @ViewDebug.ExportedProperty
+ int mSelectedPosition = INVALID_POSITION;
+
+ /**
+ * The item id of the currently selected item.
+ */
+ long mSelectedRowId = INVALID_ROW_ID;
+
+ /**
+ * View to show if there are no items to show.
+ */
+ private View mEmptyView;
+
+ /**
+ * The number of items in the current adapter.
+ */
+ @ViewDebug.ExportedProperty
+ int mItemCount;
+
+ /**
+ * The number of items in the adapter before a data changed event occured.
+ */
+ int mOldItemCount;
+
+ /**
+ * 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;
+
+ /**
+ * The last selected position we used when notifying
+ */
+ int mOldSelectedPosition = INVALID_POSITION;
+
+ /**
+ * The id of the last selected position we used when notifying
+ */
+ long mOldSelectedRowId = 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 mDesiredFocusableState;
+ private boolean mDesiredFocusableInTouchModeState;
+
+ private SelectionNotifier mSelectionNotifier;
+ /**
+ * 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.
+ */
+ boolean mBlockLayoutRequests = false;
+
+ public PLA_AdapterView(Context context) {
+ super(context);
+ }
+
+ public PLA_AdapterView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public PLA_AdapterView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+
+ /**
+ * Interface definition for a callback to be invoked when an item in this
+ * AdapterView has been clicked.
+ */
+ public interface OnItemClickListener {
+
+ /**
+ * Callback method to be invoked when an item in this AdapterView has
+ * been clicked.
+ *
+ * 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(PLA_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(OnItemClickListener listener) {
+ mOnItemClickListener = listener;
+ }
+
+ /**
+ * @return The callback to be invoked with an item in this AdapterView has
+ * been clicked, or null id no callback has been set.
+ */
+ public final OnItemClickListener getOnItemClickListener() {
+ return mOnItemClickListener;
+ }
+
+ /**
+ * Call the OnItemClickListener, if it is defined.
+ *
+ * @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) {
+ if (mOnItemClickListener != null) {
+ playSoundEffect(SoundEffectConstants.CLICK);
+ mOnItemClickListener.onItemClick(this, view, position, id);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 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(PLA_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 id no callback as 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.
+ *
+ * Impelmenters 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(PLA_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(PLA_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(OnItemSelectedListener listener) {
+ mOnItemSelectedListener = listener;
+ }
+
+ 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 view.)
+ */
+ @ViewDebug.CapturedViewProperty
+ public int getCount() {
+ return mItemCount;
+ }
+
+ /**
+ * 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
+ * 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()).equals(this)) {
+ listItem = v;
+ }
+ } catch (ClassCastException e) {
+ // 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)) {
+ 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 overriden 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
+ */
+ public void setEmptyView(View emptyView) {
+ mEmptyView = emptyView;
+
+ 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
+ * call 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(boolean focusable) {
+ final T adapter = getAdapter();
+ final boolean empty = adapter == null || adapter.getCount() == 0;
+
+ mDesiredFocusableState = focusable;
+ if (!focusable) {
+ mDesiredFocusableInTouchModeState = false;
+ }
+
+ super.setFocusable(focusable && (!empty || isInFilterMode()));
+ }
+
+ @Override
+ public void setFocusableInTouchMode(boolean focusable) {
+ final T adapter = getAdapter();
+ final boolean empty = adapter == null || adapter.getCount() == 0;
+
+ mDesiredFocusableInTouchModeState = focusable;
+ if (focusable) {
+ mDesiredFocusableState = true;
+ }
+
+ 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);
+ 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, getLeft(), getTop(), getRight(), getBottom());
+ }
+ } 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 container) {
+ dispatchFreezeSelfOnly(container);
+ }
+
+ /**
+ * Override to prevent thawing of any views created by the adapter.
+ */
+ @Override
+ protected void dispatchRestoreInstanceState(SparseArray container) {
+ dispatchThawSelfOnly(container);
+ }
+
+ class AdapterDataSetObserver extends DataSetObserver {
+
+ private Parcelable mInstanceState = null;
+
+ @Override
+ public void onChanged() {
+ mDataChanged = true;
+ mOldItemCount = mItemCount;
+ mItemCount = getAdapter().getCount();
+
+ // Detect the case where a cursor that was previously invalidated has
+ // been repopulated with new data.
+ if (PLA_AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
+ && mOldItemCount == 0 && mItemCount > 0) {
+ PLA_AdapterView.this.onRestoreInstanceState(mInstanceState);
+ mInstanceState = null;
+ } else {
+ rememberSyncState();
+ }
+ checkFocus();
+ requestLayout();
+ }
+
+ @Override
+ public void onInvalidated() {
+ mDataChanged = true;
+
+ if (PLA_AdapterView.this.getAdapter().hasStableIds()) {
+ // Remember the current state for the case where our hosting activity is being
+ // stopped and later restarted
+ mInstanceState = PLA_AdapterView.this.onSaveInstanceState();
+ }
+
+ // Data is invalid so we should reset our state
+ mOldItemCount = mItemCount;
+ mItemCount = 0;
+ mSelectedPosition = INVALID_POSITION;
+ mSelectedRowId = INVALID_ROW_ID;
+ mNextSelectedPosition = INVALID_POSITION;
+ mNextSelectedRowId = INVALID_ROW_ID;
+ mNeedSync = false;
+ checkSelectionChanged();
+
+ checkFocus();
+ requestLayout();
+ }
+
+ public void clearSavedState() {
+ mInstanceState = null;
+ }
+ }
+
+ private class SelectionNotifier extends Handler implements Runnable {
+ public void run() {
+ if (mDataChanged) {
+ // Data has changed between when this SelectionNotifier
+ // was posted and now. We need to wait until the AdapterView
+ // has been synched to the new data.
+ post(this);
+ } else {
+ fireOnSelected();
+ }
+ }
+ }
+
+ void selectionChanged() {
+ if (mOnItemSelectedListener != null) {
+ if (mInLayout || mBlockLayoutRequests) {
+ // 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 (mSelectionNotifier == null) {
+ mSelectionNotifier = new SelectionNotifier();
+ }
+ mSelectionNotifier.post(mSelectionNotifier);
+ } else {
+ fireOnSelected();
+ }
+ }
+
+ // we fire selection events here not in View
+ if (mSelectedPosition != ListView.INVALID_POSITION && isShown() && !isInTouchMode()) {
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ }
+ }
+
+ private void fireOnSelected() {
+ if (mOnItemSelectedListener == null)
+ return;
+
+ int selection = this.getSelectedItemPosition();
+ if (selection >= 0) {
+ View v = getSelectedView();
+ mOnItemSelectedListener.onItemSelected(this, v, selection,
+ getAdapter().getItemId(selection));
+ } else {
+ mOnItemSelectedListener.onNothingSelected(this);
+ }
+ }
+
+ @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. AdapterView 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 AdapterView to avoid
+ // generation of such event for each child
+ View selectedView = getSelectedView();
+ if (selectedView != null) {
+ populated = selectedView.dispatchPopulateAccessibilityEvent(event);
+ }
+
+ if (!populated) {
+ if (selectedView != null) {
+ event.setEnabled(selectedView.isEnabled());
+ }
+ event.setItemCount(getCount());
+ event.setCurrentItemIndex(getSelectedItemPosition());
+ }
+
+ return populated;
+ }
+
+ @Override
+ protected boolean canAnimate() {
+ return super.canAnimate() && mItemCount > 0;
+ }
+
+ void handleDataChanged() {
+ final int count = mItemCount;
+ boolean found = false;
+
+ if (count > 0) {
+
+ int newPos;
+
+ // Find the row we are supposed to sync to
+ if (mNeedSync) {
+ // Update this first, since setNextSelectedPositionInt inspects
+ // it
+ mNeedSync = 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
+ int selectablePos = lookForSelectablePosition(newPos, true);
+ if (selectablePos == newPos) {
+ // Same row id is selected
+ setNextSelectedPositionInt(newPos);
+ found = true;
+ }
+ }
+ }
+ if (!found) {
+ // 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, true);
+ if (selectablePos < 0) {
+ // Looking down didn't work -- try looking up
+ selectablePos = lookForSelectablePosition(newPos, false);
+ }
+ if (selectablePos >= 0) {
+ setNextSelectedPositionInt(selectablePos);
+ checkSelectionChanged();
+ found = true;
+ }
+ }
+ }
+ if (!found) {
+ // Nothing is selected
+ mSelectedPosition = INVALID_POSITION;
+ mSelectedRowId = INVALID_ROW_ID;
+ mNextSelectedPosition = INVALID_POSITION;
+ mNextSelectedRowId = INVALID_ROW_ID;
+ mNeedSync = false;
+ checkSelectionChanged();
+ }
+ }
+
+ void checkSelectionChanged() {
+ if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
+ selectionChanged();
+ mOldSelectedPosition = mSelectedPosition;
+ mOldSelectedRowId = mSelectedRowId;
+ }
+ }
+
+ /**
+ * Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition
+ * 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 mSyncRowId, or {@link #INVALID_POSITION} if it can't
+ * be found
+ */
+ int findSyncPosition() {
+ int count = mItemCount;
+
+ if (count == 0) {
+ return INVALID_POSITION;
+ }
+
+ long idToMatch = mSyncRowId;
+ int seed = mSyncPosition;
+
+ // If there isn't a selection don't hunt for it
+ if (idToMatch == INVALID_ROW_ID) {
+ return INVALID_POSITION;
+ }
+
+ // Pin seed to reasonable values
+ seed = Math.max(0, seed);
+ seed = Math.min(count - 1, seed);
+
+ long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS;
+
+ long rowId;
+
+ // 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
+ 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 && hitFirst) {
+ // Looked at everything
+ break;
+ }
+
+ if (hitFirst || (next && !hitLast)) {
+ // 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 || (!next && !hitFirst)) {
+ // 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.
+ * @param lookDown Whether to look down for other positions.
+ * @return The next selectable position starting at position and then searching either up or
+ * down. Returns {@link #INVALID_POSITION} if nothing can be found.
+ */
+ int lookForSelectablePosition(int position, boolean lookDown) {
+ return position;
+ }
+
+ /**
+ * Utility to keep mSelectedPosition and mSelectedRowId in sync
+ * @param position Our current position
+ */
+ void setSelectedPositionInt(int position) {
+ mSelectedPosition = position;
+ mSelectedRowId = getItemIdAtPosition(position);
+ }
+
+ /**
+ * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync
+ * @param position Intended value for mSelectedPosition the next time we go
+ * through layout
+ */
+ void setNextSelectedPositionInt(int position) {
+ mNextSelectedPosition = position;
+ mNextSelectedRowId = getItemIdAtPosition(position);
+ // If we are trying to sync to the selection, update that too
+ if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) {
+ mSyncPosition = position;
+ mSyncRowId = mNextSelectedRowId;
+ }
+ }
+
+ /**
+ * Remember enough information to restore the screen state when the data has
+ * changed.
+ *
+ */
+ void rememberSyncState() {
+ if (getChildCount() > 0) {
+ mNeedSync = true;
+ mSyncHeight = mLayoutHeight;
+ if (mSelectedPosition >= 0) {
+ // Sync the selection state
+ View v = getChildAt(mSelectedPosition - mFirstPosition);
+ mSyncRowId = mNextSelectedRowId;
+ mSyncPosition = mNextSelectedPosition;
+ if (v != null) {
+ mSpecificTop = v.getTop();
+ }
+ mSyncMode = SYNC_SELECTED_POSITION;
+ } else {
+ // Sync the based on the offset of the first view
+ View v = getChildAt(0);
+ T adapter = getAdapter();
+ if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
+ mSyncRowId = adapter.getItemId(mFirstPosition);
+ } else {
+ mSyncRowId = NO_ID;
+ }
+ mSyncPosition = mFirstPosition;
+ if (v != null) {
+ mSpecificTop = v.getTop();
+ }
+ mSyncMode = SYNC_FIRST_POSITION;
+ }
+ }
+ }
+}
diff --git a/src/com/huewu/pla/lib/internal/PLA_HeaderViewListAdapter.java b/src/com/huewu/pla/lib/internal/PLA_HeaderViewListAdapter.java
new file mode 100644
index 0000000..392da84
--- /dev/null
+++ b/src/com/huewu/pla/lib/internal/PLA_HeaderViewListAdapter.java
@@ -0,0 +1,274 @@
+/*
+ * 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 com.huewu.pla.lib.internal;
+
+import android.database.DataSetObserver;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.ListAdapter;
+import android.widget.WrapperListAdapter;
+
+import java.util.ArrayList;
+
+/**
+ * ListAdapter used when a ListView has header views. This ListAdapter
+ * wraps another one and also keeps track of the header views and their
+ * associated data objects.
+ *This is intended as a base class; you will probably not need to
+ * use this class directly in your own code.
+ */
+public class PLA_HeaderViewListAdapter implements WrapperListAdapter, Filterable {
+
+ private final ListAdapter mAdapter;
+
+ // These two ArrayList are assumed to NOT be null.
+ // They are indeed created when declared in ListView and then shared.
+ ArrayList mHeaderViewInfos;
+ ArrayList mFooterViewInfos;
+
+ // Used as a placeholder in case the provided info views are indeed null.
+ // Currently only used by some CTS tests, which may be removed.
+ static final ArrayList EMPTY_INFO_LIST =
+ new ArrayList();
+
+ boolean mAreAllFixedViewsSelectable;
+
+ private final boolean mIsFilterable;
+
+ public PLA_HeaderViewListAdapter(ArrayList headerViewInfos,
+ ArrayList footerViewInfos,
+ ListAdapter adapter) {
+ mAdapter = adapter;
+ mIsFilterable = adapter instanceof Filterable;
+
+ if (headerViewInfos == null) {
+ mHeaderViewInfos = EMPTY_INFO_LIST;
+ } else {
+ mHeaderViewInfos = headerViewInfos;
+ }
+
+ if (footerViewInfos == null) {
+ mFooterViewInfos = EMPTY_INFO_LIST;
+ } else {
+ mFooterViewInfos = footerViewInfos;
+ }
+
+ mAreAllFixedViewsSelectable =
+ areAllListInfosSelectable(mHeaderViewInfos)
+ && areAllListInfosSelectable(mFooterViewInfos);
+ }
+
+ public int getHeadersCount() {
+ return mHeaderViewInfos.size();
+ }
+
+ public int getFootersCount() {
+ return mFooterViewInfos.size();
+ }
+
+ public boolean isEmpty() {
+ return mAdapter == null || mAdapter.isEmpty();
+ }
+
+ private boolean areAllListInfosSelectable(ArrayList infos) {
+ if (infos != null) {
+ for (PLA_ListView.FixedViewInfo info : infos) {
+ if (!info.isSelectable) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ public boolean removeHeader(View v) {
+ for (int i = 0; i < mHeaderViewInfos.size(); i++) {
+ PLA_ListView.FixedViewInfo info = mHeaderViewInfos.get(i);
+ if (info.view == v) {
+ mHeaderViewInfos.remove(i);
+
+ mAreAllFixedViewsSelectable =
+ areAllListInfosSelectable(mHeaderViewInfos)
+ && areAllListInfosSelectable(mFooterViewInfos);
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public boolean removeFooter(View v) {
+ for (int i = 0; i < mFooterViewInfos.size(); i++) {
+ PLA_ListView.FixedViewInfo info = mFooterViewInfos.get(i);
+ if (info.view == v) {
+ mFooterViewInfos.remove(i);
+
+ mAreAllFixedViewsSelectable =
+ areAllListInfosSelectable(mHeaderViewInfos)
+ && areAllListInfosSelectable(mFooterViewInfos);
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public int getCount() {
+ if (mAdapter != null) {
+ return getFootersCount() + getHeadersCount() + mAdapter.getCount();
+ } else {
+ return getFootersCount() + getHeadersCount();
+ }
+ }
+
+ public boolean areAllItemsEnabled() {
+ if (mAdapter != null) {
+ return mAreAllFixedViewsSelectable && mAdapter.areAllItemsEnabled();
+ } else {
+ return true;
+ }
+ }
+
+ public boolean isEnabled(int position) {
+ // Header (negative positions will throw an ArrayIndexOutOfBoundsException)
+ int numHeaders = getHeadersCount();
+ if (position < numHeaders) {
+ return mHeaderViewInfos.get(position).isSelectable;
+ }
+
+ // Adapter
+ final int adjPosition = position - numHeaders;
+ int adapterCount = 0;
+ if (mAdapter != null) {
+ adapterCount = mAdapter.getCount();
+ if (adjPosition < adapterCount) {
+ return mAdapter.isEnabled(adjPosition);
+ }
+ }
+
+ // Footer (off-limits positions will throw an ArrayIndexOutOfBoundsException)
+ return mFooterViewInfos.get(adjPosition - adapterCount).isSelectable;
+ }
+
+ public Object getItem(int position) {
+ // Header (negative positions will throw an ArrayIndexOutOfBoundsException)
+ int numHeaders = getHeadersCount();
+ if (position < numHeaders) {
+ return mHeaderViewInfos.get(position).data;
+ }
+
+ // Adapter
+ final int adjPosition = position - numHeaders;
+ int adapterCount = 0;
+ if (mAdapter != null) {
+ adapterCount = mAdapter.getCount();
+ if (adjPosition < adapterCount) {
+ return mAdapter.getItem(adjPosition);
+ }
+ }
+
+ // Footer (off-limits positions will throw an ArrayIndexOutOfBoundsException)
+ return mFooterViewInfos.get(adjPosition - adapterCount).data;
+ }
+
+ public long getItemId(int position) {
+ int numHeaders = getHeadersCount();
+ if (mAdapter != null && position >= numHeaders) {
+ int adjPosition = position - numHeaders;
+ int adapterCount = mAdapter.getCount();
+ if (adjPosition < adapterCount) {
+ return mAdapter.getItemId(adjPosition);
+ }
+ }
+ return -1;
+ }
+
+ public boolean hasStableIds() {
+ if (mAdapter != null) {
+ return mAdapter.hasStableIds();
+ }
+ return false;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ // Header (negative positions will throw an ArrayIndexOutOfBoundsException)
+ int numHeaders = getHeadersCount();
+ if (position < numHeaders) {
+ return mHeaderViewInfos.get(position).view;
+ }
+
+ // Adapter
+ final int adjPosition = position - numHeaders;
+ int adapterCount = 0;
+ if (mAdapter != null) {
+ adapterCount = mAdapter.getCount();
+ if (adjPosition < adapterCount) {
+ return mAdapter.getView(adjPosition, convertView, parent);
+ }
+ }
+
+ // Footer (off-limits positions will throw an ArrayIndexOutOfBoundsException)
+ return mFooterViewInfos.get(adjPosition - adapterCount).view;
+ }
+
+ public int getItemViewType(int position) {
+ int numHeaders = getHeadersCount();
+ if (mAdapter != null && position >= numHeaders) {
+ int adjPosition = position - numHeaders;
+ int adapterCount = mAdapter.getCount();
+ if (adjPosition < adapterCount) {
+ return mAdapter.getItemViewType(adjPosition);
+ }
+ }
+
+ return PLA_AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
+ }
+
+ public int getViewTypeCount() {
+ if (mAdapter != null) {
+ return mAdapter.getViewTypeCount();
+ }
+ return 1;
+ }
+
+ public void registerDataSetObserver(DataSetObserver observer) {
+ if (mAdapter != null) {
+ mAdapter.registerDataSetObserver(observer);
+ }
+ }
+
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ if (mAdapter != null) {
+ mAdapter.unregisterDataSetObserver(observer);
+ }
+ }
+
+ public Filter getFilter() {
+ if (mIsFilterable) {
+ return ((Filterable) mAdapter).getFilter();
+ }
+ return null;
+ }
+
+ public ListAdapter getWrappedAdapter() {
+ return mAdapter;
+ }
+}
diff --git a/src/com/huewu/pla/lib/internal/PLA_ListView.java b/src/com/huewu/pla/lib/internal/PLA_ListView.java
new file mode 100644
index 0000000..a36be0f
--- /dev/null
+++ b/src/com/huewu/pla/lib/internal/PLA_ListView.java
@@ -0,0 +1,2619 @@
+/*
+ * 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 com.huewu.pla.lib.internal;
+
+import java.util.ArrayList;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.ListAdapter;
+import android.widget.WrapperListAdapter;
+
+import com.huewu.lib.pla.R;
+
+/*
+ * Implementation Notes:
+ *
+ * Some terminology:
+ *
+ * index - index of the items that are currently visible
+ * position - index of the items in the cursor
+ */
+
+
+/**
+ * A view that shows items in a vertically scrolling list. The items
+ * come from the {@link ListAdapter} associated with this view.
+ *
+ * See the List View
+ * tutorial.
+ *
+ * @attr ref android.R.styleable#ListView_entries
+ * @attr ref android.R.styleable#ListView_divider
+ * @attr ref android.R.styleable#ListView_dividerHeight
+ * @attr ref android.R.styleable#ListView_choiceMode
+ * @attr ref android.R.styleable#ListView_headerDividersEnabled
+ * @attr ref android.R.styleable#ListView_footerDividersEnabled
+ */
+public class PLA_ListView extends PLA_AbsListView {
+
+ //TODO Not Supproted Features
+ //Entry from XML.
+ //Choice Mode & Item Selection.
+ //Filter
+ //Handle Key Event & Arrow Scrolling..
+ //Can't find Footer & Header findBy methods...
+
+ /**
+ * Used to indicate a no preference for a position type.
+ */
+ static final int NO_POSITION = -1;
+
+ /**
+ * When arrow scrolling, ListView will never scroll more than this factor
+ * times the height of the list.
+ */
+ private static final float MAX_SCROLL_FACTOR = 0.33f;
+
+ /**
+ * A class that represents a fixed view in a list, for example a header at the top
+ * or a footer at the bottom.
+ */
+ public class FixedViewInfo {
+ /** The view to add to the list */
+ public View view;
+ /** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */
+ public Object data;
+ /** true
if the fixed view should be selectable in the list */
+ public boolean isSelectable;
+ }
+
+ private ArrayList mHeaderViewInfos = new ArrayList();
+ private ArrayList mFooterViewInfos = new ArrayList();
+
+ Drawable mDivider;
+ int mDividerHeight;
+
+ Drawable mOverScrollHeader;
+ Drawable mOverScrollFooter;
+
+ private boolean mIsCacheColorOpaque;
+ private boolean mDividerIsOpaque;
+ private boolean mClipDivider;
+
+ private boolean mHeaderDividersEnabled;
+ private boolean mFooterDividersEnabled;
+
+ private boolean mAreAllItemsSelectable = true;
+
+ private boolean mItemsCanFocus = false;
+
+ // used for temporary calculations.
+ private final Rect mTempRect = new Rect();
+ private Paint mDividerPaint;
+
+ // Keeps focused children visible through resizes
+ private FocusSelector mFocusSelector;
+
+ public PLA_ListView(Context context) {
+ this(context, null);
+ }
+
+ public PLA_ListView(Context context, AttributeSet attrs) {
+ this(context, attrs, R.attr.listViewStyle);
+ }
+
+ public PLA_ListView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.ListView, defStyle, 0);
+
+ // final Drawable d = a.getDrawable(android.R.drawable.divider_horizontal_bright);
+ // if (d != null) {
+ // // If a divider is specified use its intrinsic height for divider height
+ // setDivider(d);
+ // }
+
+ final Drawable osHeader = a.getDrawable(
+ R.styleable.ListView_overScrollHeader);
+ if (osHeader != null) {
+ setOverscrollHeader(osHeader);
+ }
+
+ final Drawable osFooter = a.getDrawable(
+ R.styleable.ListView_overScrollFooter);
+ if (osFooter != null) {
+ setOverscrollFooter(osFooter);
+ }
+
+ // Use the height specified, zero being the default
+ final int dividerHeight = a.getDimensionPixelSize(
+ R.styleable.ListView_dividerHeight, 0);
+ if (dividerHeight != 0) {
+ setDividerHeight(dividerHeight);
+ }
+
+ mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true);
+ mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true);
+
+ a.recycle();
+ }
+
+ /**
+ * @return The maximum amount a list view will scroll in response to
+ * an arrow event.
+ */
+ public int getMaxScrollAmount() {
+ // return (int) (MAX_SCROLL_FACTOR * (mBottom - mTop));
+ return (int) (MAX_SCROLL_FACTOR * (getBottom() - getTop()));
+ }
+
+ /**
+ * Make sure views are touching the top or bottom edge, as appropriate for
+ * our gravity
+ */
+ private void adjustViewsUpOrDown() {
+ final int childCount = getChildCount();
+ int delta;
+
+ if (childCount > 0) {
+ View child;
+
+ if (!mStackFromBottom) {
+ // Uh-oh -- we came up short. Slide all views up to make them
+ // align with the top
+ child = getChildAt(0);
+ delta = child.getTop() - mListPadding.top;
+ if (mFirstPosition != 0) {
+ // It's OK to have some space above the first item if it is
+ // part of the vertical spacing
+ delta -= mDividerHeight;
+ }
+ if (delta < 0) {
+ // We only are looking to see if we are too low, not too high
+ delta = 0;
+ }
+ } else {
+ // we are too high, slide all views down to align with bottom
+ child = getChildAt(childCount - 1);
+ delta = child.getBottom() - (getHeight() - mListPadding.bottom);
+
+ if (mFirstPosition + childCount < mItemCount) {
+ // It's OK to have some space below the last item if it is
+ // part of the vertical spacing
+ delta += mDividerHeight;
+ }
+
+ if (delta > 0) {
+ delta = 0;
+ }
+ }
+
+ if (delta != 0) {
+ // offsetChildrenTopAndBottom(-delta);
+ tryOffsetChildrenTopAndBottom(-delta);
+ }
+ }
+ }
+
+ /**
+ * Add a fixed view to appear at the top of the list. If addHeaderView is
+ * called more than once, the views will appear in the order they were
+ * added. Views added using this call can take focus if they want.
+ *
+ * NOTE: Call this before calling setAdapter. This is so ListView can wrap
+ * the supplied cursor with one that will also account for header and footer
+ * views.
+ *
+ * @param v The view to add.
+ * @param data Data to associate with this view
+ * @param isSelectable whether the item is selectable
+ */
+ public void addHeaderView(View v, Object data, boolean isSelectable) {
+
+ if (mAdapter != null) {
+ throw new IllegalStateException(
+ "Cannot add header view to list -- setAdapter has already been called.");
+ }
+
+ FixedViewInfo info = new FixedViewInfo();
+ info.view = v;
+ info.data = data;
+ info.isSelectable = isSelectable;
+ mHeaderViewInfos.add(info);
+ }
+
+ /**
+ * Add a fixed view to appear at the top of the list. If addHeaderView is
+ * called more than once, the views will appear in the order they were
+ * added. Views added using this call can take focus if they want.
+ *
+ * NOTE: Call this before calling setAdapter. This is so ListView can wrap
+ * the supplied cursor with one that will also account for header and footer
+ * views.
+ *
+ * @param v The view to add.
+ */
+ public void addHeaderView(View v) {
+ addHeaderView(v, null, true);
+ }
+
+ @Override
+ public int getHeaderViewsCount() {
+ return mHeaderViewInfos.size();
+ }
+
+ /**
+ * Removes a previously-added header view.
+ *
+ * @param v The view to remove
+ * @return true if the view was removed, false if the view was not a header
+ * view
+ */
+ public boolean removeHeaderView(View v) {
+ if (mHeaderViewInfos.size() > 0) {
+ boolean result = false;
+ if (((PLA_HeaderViewListAdapter) mAdapter).removeHeader(v)) {
+ mDataSetObserver.onChanged();
+ result = true;
+ }
+ removeFixedViewInfo(v, mHeaderViewInfos);
+ return result;
+ }
+ return false;
+ }
+
+ private void removeFixedViewInfo(View v, ArrayList where) {
+ int len = where.size();
+ for (int i = 0; i < len; ++i) {
+ FixedViewInfo info = where.get(i);
+ if (info.view == v) {
+ where.remove(i);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Add a fixed view to appear at the bottom of the list. If addFooterView is
+ * called more than once, the views will appear in the order they were
+ * added. Views added using this call can take focus if they want.
+ *
+ * NOTE: Call this before calling setAdapter. This is so ListView can wrap
+ * the supplied cursor with one that will also account for header and footer
+ * views.
+ *
+ * @param v The view to add.
+ * @param data Data to associate with this view
+ * @param isSelectable true if the footer view can be selected
+ */
+ public void addFooterView(View v, Object data, boolean isSelectable) {
+ FixedViewInfo info = new FixedViewInfo();
+ info.view = v;
+ info.data = data;
+ info.isSelectable = isSelectable;
+ mFooterViewInfos.add(info);
+
+ // in the case of re-adding a footer view, or adding one later on,
+ // we need to notify the observer
+ if (mDataSetObserver != null) {
+ mDataSetObserver.onChanged();
+ }
+ }
+
+ /**
+ * Add a fixed view to appear at the bottom of the list. If addFooterView is called more
+ * than once, the views will appear in the order they were added. Views added using
+ * this call can take focus if they want.
+ *
NOTE: Call this before calling setAdapter. This is so ListView can wrap the supplied
+ * cursor with one that will also account for header and footer views.
+ *
+ *
+ * @param v The view to add.
+ */
+ public void addFooterView(View v) {
+ addFooterView(v, null, true);
+ }
+
+ @Override
+ public int getFooterViewsCount() {
+ return mFooterViewInfos.size();
+ }
+
+ /**
+ * Removes a previously-added footer view.
+ *
+ * @param v The view to remove
+ * @return
+ * true if the view was removed, false if the view was not a footer view
+ */
+ public boolean removeFooterView(View v) {
+ if (mFooterViewInfos.size() > 0) {
+ boolean result = false;
+ if (((PLA_HeaderViewListAdapter) mAdapter).removeFooter(v)) {
+ mDataSetObserver.onChanged();
+ result = true;
+ }
+ removeFixedViewInfo(v, mFooterViewInfos);
+ return result;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the adapter currently in use in this ListView. The returned adapter
+ * might not be the same adapter passed to {@link #setAdapter(ListAdapter)} but
+ * might be a {@link WrapperListAdapter}.
+ *
+ * @return The adapter currently used to display data in this ListView.
+ *
+ * @see #setAdapter(ListAdapter)
+ */
+ @Override
+ public ListAdapter getAdapter() {
+ return mAdapter;
+ }
+
+ /**
+ * Sets the data behind this ListView.
+ *
+ * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
+ * depending on the ListView features currently in use. For instance, adding
+ * headers and/or footers will cause the adapter to be wrapped.
+ *
+ * @param adapter The ListAdapter which is responsible for maintaining the
+ * data backing this list and for producing a view to represent an
+ * item in that data set.
+ *
+ * @see #getAdapter()
+ */
+ @Override
+ public void setAdapter(ListAdapter adapter) {
+ if (null != mAdapter) {
+ mAdapter.unregisterDataSetObserver(mDataSetObserver);
+ }
+
+ resetList();
+ mRecycler.clear();
+
+ if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
+ mAdapter = new PLA_HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
+ } else {
+ mAdapter = adapter;
+ }
+
+ mOldSelectedPosition = INVALID_POSITION;
+ mOldSelectedRowId = INVALID_ROW_ID;
+ if (mAdapter != null) {
+ mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
+ mOldItemCount = mItemCount;
+ mItemCount = mAdapter.getCount();
+ checkFocus();
+
+ mDataSetObserver = new AdapterDataSetObserver();
+ mAdapter.registerDataSetObserver(mDataSetObserver);
+
+ mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
+
+ int position;
+ if (mStackFromBottom) {
+ position = lookForSelectablePosition(mItemCount - 1, false);
+ } else {
+ position = lookForSelectablePosition(0, true);
+ }
+ setSelectedPositionInt(position);
+ setNextSelectedPositionInt(position);
+
+ if (mItemCount == 0) {
+ // Nothing selected
+ checkSelectionChanged();
+ }
+
+ } else {
+ mAreAllItemsSelectable = true;
+ checkFocus();
+ // Nothing selected
+ checkSelectionChanged();
+ }
+
+ requestLayout();
+ }
+
+
+ /**
+ * The list is empty. Clear everything out.
+ */
+ @Override
+ void resetList() {
+ // The parent's resetList() will remove all views from the layout so we need to
+ // cleanup the state of our footers and headers
+ clearRecycledState(mHeaderViewInfos);
+ clearRecycledState(mFooterViewInfos);
+
+ super.resetList();
+
+ mLayoutMode = LAYOUT_NORMAL;
+ }
+
+ private void clearRecycledState(ArrayList infos) {
+ if (infos != null) {
+ final int count = infos.size();
+
+ for (int i = 0; i < count; i++) {
+ final View child = infos.get(i).view;
+ final LayoutParams p = (LayoutParams) child.getLayoutParams();
+ if (p != null) {
+ p.recycledHeaderFooter = false;
+ }
+ }
+ }
+ }
+
+ /**
+ * @return Whether the list needs to show the top fading edge
+ */
+ private boolean showingTopFadingEdge() {
+ // final int listTop = mScrollY + mListPadding.top;
+ final int listTop = getScrollY() + mListPadding.top;
+ return (mFirstPosition > 0) || (getChildAt(0).getTop() > listTop);
+ }
+
+ /**
+ * @return Whether the list needs to show the bottom fading edge
+ */
+ private boolean showingBottomFadingEdge() {
+ final int childCount = getChildCount();
+ final int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
+ final int lastVisiblePosition = mFirstPosition + childCount - 1;
+
+ // final int listBottom = mScrollY + getHeight() - mListPadding.bottom;
+ final int listBottom = getScrollY() + getHeight() - mListPadding.bottom;
+
+ return (lastVisiblePosition < mItemCount - 1)
+ || (bottomOfBottomChild < listBottom);
+ }
+
+
+ @Override
+ public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
+
+ int rectTopWithinChild = rect.top;
+
+ // offset so rect is in coordinates of the this view
+ rect.offset(child.getLeft(), child.getTop());
+ rect.offset(-child.getScrollX(), -child.getScrollY());
+
+ final int height = getHeight();
+ int listUnfadedTop = getScrollY();
+ int listUnfadedBottom = listUnfadedTop + height;
+ final int fadingEdge = getVerticalFadingEdgeLength();
+
+ if (showingTopFadingEdge()) {
+ // leave room for top fading edge as long as rect isn't at very top
+ if ((mSelectedPosition > 0) || (rectTopWithinChild > fadingEdge)) {
+ listUnfadedTop += fadingEdge;
+ }
+ }
+
+ int childCount = getChildCount();
+ int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
+
+ if (showingBottomFadingEdge()) {
+ // leave room for bottom fading edge as long as rect isn't at very bottom
+ if ((mSelectedPosition < mItemCount - 1)
+ || (rect.bottom < (bottomOfBottomChild - fadingEdge))) {
+ listUnfadedBottom -= fadingEdge;
+ }
+ }
+
+ int scrollYDelta = 0;
+
+ if (rect.bottom > listUnfadedBottom && rect.top > listUnfadedTop) {
+ // need to MOVE DOWN to get it in view: move down just enough so
+ // that the entire rectangle is in view (or at least the first
+ // screen size chunk).
+
+ if (rect.height() > height) {
+ // just enough to get screen size chunk on
+ scrollYDelta += (rect.top - listUnfadedTop);
+ } else {
+ // get entire rect at bottom of screen
+ scrollYDelta += (rect.bottom - listUnfadedBottom);
+ }
+
+ // make sure we aren't scrolling beyond the end of our children
+ int distanceToBottom = bottomOfBottomChild - listUnfadedBottom;
+ scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
+ } else if (rect.top < listUnfadedTop && rect.bottom < listUnfadedBottom) {
+ // need to MOVE UP to get it in view: move up just enough so that
+ // entire rectangle is in view (or at least the first screen
+ // size chunk of it).
+
+ if (rect.height() > height) {
+ // screen size chunk
+ scrollYDelta -= (listUnfadedBottom - rect.bottom);
+ } else {
+ // entire rect at top
+ scrollYDelta -= (listUnfadedTop - rect.top);
+ }
+
+ // make sure we aren't scrolling any further than the top our children
+ int top = getChildAt(0).getTop();
+ int deltaToTop = top - listUnfadedTop;
+ scrollYDelta = Math.max(scrollYDelta, deltaToTop);
+ }
+
+ final boolean scroll = scrollYDelta != 0;
+ if (scroll) {
+ scrollListItemsBy(-scrollYDelta);
+ positionSelector(child);
+ mSelectedTop = child.getTop();
+ invalidate();
+ }
+ return scroll;
+ }
+
+ protected int getChildLeft(int pos) {
+ return mListPadding.left;
+ }
+
+
+ protected int getPreservedChildTop(int pos) {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ protected int getPreservedChildBottom(int pos) {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void fillGap(boolean down) {
+ final int count = getChildCount();
+ if (down) {
+ final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight :
+ getListPaddingTop();
+ fillDown(mFirstPosition + count, startOffset);
+ correctTooHigh(getChildCount());
+ } else {
+ final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight :
+ getHeight() - getListPaddingBottom();
+ fillUp(mFirstPosition - 1, startOffset);
+ correctTooLow(getChildCount());
+ }
+ }
+
+ /**
+ * Fills the list from pos down to the end of the list view.
+ *
+ * @param pos The first position to put in the list
+ *
+ * @param nextTop The location where the top of the item associated with pos
+ * should be drawn
+ *
+ * @return The view that is currently selected, if it happens to be in the
+ * range that we draw.
+ */
+ private View fillDown(int pos, int top) {
+ View selectedView = null;
+
+ //int end = (mBottom - mTop) - mListPadding.bottom;
+ int end = (getBottom() - getTop()) - mListPadding.bottom;
+ int nextTop = getCurrentChildBottom();
+
+ while (nextTop < end && pos < mItemCount) {
+ // is this the selected item?
+ boolean selected = pos == mSelectedPosition;
+ View child = makeAndAddView(pos, nextTop, true, selected);
+ // nextTop = child.getBottom() + mDividerHeight;
+ nextTop = getCurrentChildBottom() + mDividerHeight;
+ if (selected) {
+ selectedView = child;
+ }
+ pos++;
+ }
+
+ return selectedView;
+ }
+
+ /**
+ * Fills the list from pos up to the top of the list view.
+ *
+ * @param pos The first position to put in the list
+ *
+ * @param nextBottom The location where the bottom of the item associated
+ * with pos should be drawn
+ *
+ * @return The view that is currently selected
+ */
+ private View fillUp(int pos, int bottom) {
+ View selectedView = null;
+ int end = mListPadding.top;
+ int nextBottom = getCurrentChildTop();
+
+ while (nextBottom > end && pos >= 0) {
+ // is this the selected item?
+ boolean selected = pos == mSelectedPosition;
+ View child = makeAndAddView(pos, nextBottom, false, selected);
+ // nextBottom = child.getTop() - mDividerHeight;
+ nextBottom = getCurrentChildTop();
+ if (selected) {
+ selectedView = child;
+ }
+ pos--;
+ }
+
+ mFirstPosition = pos + 1;
+
+ return selectedView;
+ }
+
+ /**
+ * Fills the list from top to bottom, starting with mFirstPosition
+ *
+ * @param nextTop The location where the top of the first item should be
+ * drawn
+ *
+ * @return The view that is currently selected
+ */
+ private View fillFromTop(int nextTop) {
+ mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
+ mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
+ if (mFirstPosition < 0) {
+ mFirstPosition = 0;
+ }
+ return fillDown(mFirstPosition, nextTop);
+ }
+
+
+ /**
+ * Put mSelectedPosition in the middle of the screen and then build up and
+ * down from there. This method forces mSelectedPosition to the center.
+ *
+ * @param childrenTop Top of the area in which children can be drawn, as
+ * measured in pixels
+ * @param childrenBottom Bottom of the area in which children can be drawn,
+ * as measured in pixels
+ * @return Currently selected view
+ */
+ private View fillFromMiddle(int childrenTop, int childrenBottom) {
+ int height = childrenBottom - childrenTop;
+
+ int position = reconcileSelectedPosition();
+
+ View sel = makeAndAddView(position, childrenTop, true, true);
+ mFirstPosition = position;
+
+ int selHeight = sel.getMeasuredHeight();
+ if (selHeight <= height) {
+ sel.offsetTopAndBottom((height - selHeight) / 2);
+ }
+
+ fillAboveAndBelow(sel, position);
+
+ if (!mStackFromBottom) {
+ correctTooHigh(getChildCount());
+ } else {
+ correctTooLow(getChildCount());
+ }
+
+ return sel;
+ }
+
+ /**
+ * Once the selected view as been placed, fill up the visible area above and
+ * below it.
+ *
+ * @param sel The selected view
+ * @param position The position corresponding to sel
+ */
+ private void fillAboveAndBelow(View sel, int position) {
+ final int dividerHeight = mDividerHeight;
+ if (!mStackFromBottom) {
+ fillUp(position - 1, sel.getTop() - dividerHeight);
+ adjustViewsUpOrDown();
+ fillDown(position + 1, sel.getBottom() + dividerHeight);
+ } else {
+ fillDown(position + 1, sel.getBottom() + dividerHeight);
+ adjustViewsUpOrDown();
+ fillUp(position - 1, sel.getTop() - dividerHeight);
+ }
+ }
+
+
+ /**
+ * Fills the grid based on positioning the new selection at a specific
+ * location. The selection may be moved so that it does not intersect the
+ * faded edges. The grid is then filled upwards and downwards from there.
+ *
+ * @param selectedTop Where the selected item should be
+ * @param childrenTop Where to start drawing children
+ * @param childrenBottom Last pixel where children can be drawn
+ * @return The view that currently has selection
+ */
+ private View fillFromSelection(int selectedTop, int childrenTop, int childrenBottom) {
+ int fadingEdgeLength = getVerticalFadingEdgeLength();
+ final int selectedPosition = mSelectedPosition;
+
+ View sel;
+
+ final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength,
+ selectedPosition);
+ final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
+ selectedPosition);
+
+ sel = makeAndAddView(selectedPosition, selectedTop, true, true);
+
+
+ // Some of the newly selected item extends below the bottom of the list
+ if (sel.getBottom() > bottomSelectionPixel) {
+ // Find space available above the selection into which we can scroll
+ // upwards
+ final int spaceAbove = sel.getTop() - topSelectionPixel;
+
+ // Find space required to bring the bottom of the selected item
+ // fully into view
+ final int spaceBelow = sel.getBottom() - bottomSelectionPixel;
+ final int offset = Math.min(spaceAbove, spaceBelow);
+
+ // Now offset the selected item to get it into view
+ sel.offsetTopAndBottom(-offset);
+ } else if (sel.getTop() < topSelectionPixel) {
+ // Find space required to bring the top of the selected item fully
+ // into view
+ final int spaceAbove = topSelectionPixel - sel.getTop();
+
+ // Find space available below the selection into which we can scroll
+ // downwards
+ final int spaceBelow = bottomSelectionPixel - sel.getBottom();
+ final int offset = Math.min(spaceAbove, spaceBelow);
+
+ // Offset the selected item to get it into view
+ sel.offsetTopAndBottom(offset);
+ }
+
+ // Fill in views above and below
+ fillAboveAndBelow(sel, selectedPosition);
+
+ if (!mStackFromBottom) {
+ correctTooHigh(getChildCount());
+ } else {
+ correctTooLow(getChildCount());
+ }
+
+ return sel;
+ }
+
+ /**
+ * Calculate the bottom-most pixel we can draw the selection into
+ *
+ * @param childrenBottom Bottom pixel were children can be drawn
+ * @param fadingEdgeLength Length of the fading edge in pixels, if present
+ * @param selectedPosition The position that will be selected
+ * @return The bottom-most pixel we can draw the selection into
+ */
+ private int getBottomSelectionPixel(int childrenBottom, int fadingEdgeLength,
+ int selectedPosition) {
+ int bottomSelectionPixel = childrenBottom;
+ if (selectedPosition != mItemCount - 1) {
+ bottomSelectionPixel -= fadingEdgeLength;
+ }
+ return bottomSelectionPixel;
+ }
+
+ /**
+ * Calculate the top-most pixel we can draw the selection into
+ *
+ * @param childrenTop Top pixel were children can be drawn
+ * @param fadingEdgeLength Length of the fading edge in pixels, if present
+ * @param selectedPosition The position that will be selected
+ * @return The top-most pixel we can draw the selection into
+ */
+ private int getTopSelectionPixel(int childrenTop, int fadingEdgeLength, int selectedPosition) {
+ // first pixel we can draw the selection into
+ int topSelectionPixel = childrenTop;
+ if (selectedPosition > 0) {
+ topSelectionPixel += fadingEdgeLength;
+ }
+ return topSelectionPixel;
+ }
+
+
+ private class FocusSelector implements Runnable {
+ private int mPosition;
+ private int mPositionTop;
+
+ public FocusSelector setup(int position, int top) {
+ mPosition = position;
+ mPositionTop = top;
+ return this;
+ }
+
+ public void run() {
+ setSelectionFromTop(mPosition, mPositionTop);
+ }
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ if (getChildCount() > 0) {
+ View focusedChild = getFocusedChild();
+ if (focusedChild != null) {
+ final int childPosition = mFirstPosition + indexOfChild(focusedChild);
+ final int childBottom = focusedChild.getBottom();
+ // final int offset = Math.max(0, childBottom - (h - mPaddingTop));
+ final int offset = Math.max(0, childBottom - (h - getPaddingTop()));
+ final int top = focusedChild.getTop() - offset;
+ if (mFocusSelector == null) {
+ mFocusSelector = new FocusSelector();
+ }
+ post(mFocusSelector.setup(childPosition, top));
+ }
+ }
+ super.onSizeChanged(w, h, oldw, oldh);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Sets up mListPadding
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ int childWidth = 0;
+ int childHeight = 0;
+
+ mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
+ if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED ||
+ heightMode == MeasureSpec.UNSPECIFIED)) {
+ final View child = obtainView(0, mIsScrap);
+
+ measureScrapChild(child, 0, widthMeasureSpec);
+
+ childWidth = child.getMeasuredWidth();
+ childHeight = child.getMeasuredHeight();
+
+ if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
+ ((LayoutParams) child.getLayoutParams()).viewType)) {
+ mRecycler.addScrapView(child);
+ }
+ }
+
+ if (widthMode == MeasureSpec.UNSPECIFIED) {
+ widthSize = mListPadding.left + mListPadding.right + childWidth +
+ getVerticalScrollbarWidth();
+ }
+
+ if (heightMode == MeasureSpec.UNSPECIFIED) {
+ heightSize = mListPadding.top + mListPadding.bottom + childHeight +
+ getVerticalFadingEdgeLength() * 2;
+ }
+
+ if (heightMode == MeasureSpec.AT_MOST) {
+ // TODO: after first layout we should maybe start at the first visible position, not 0
+ heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
+ }
+
+ setMeasuredDimension(widthSize, heightSize);
+ mWidthMeasureSpec = widthMeasureSpec;
+ }
+
+ private void measureScrapChild(View child, int position, int widthMeasureSpec) {
+ LayoutParams p = (LayoutParams) child.getLayoutParams();
+ if (p == null) {
+ p = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT, 0);
+ child.setLayoutParams(p);
+ }
+ p.viewType = mAdapter.getItemViewType(position);
+ p.forceAdd = true;
+
+ int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
+ mListPadding.left + mListPadding.right, p.width);
+ int lpHeight = p.height;
+ int childHeightSpec;
+ if (lpHeight > 0) {
+ childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
+ } else {
+ childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ }
+ child.measure(childWidthSpec, childHeightSpec);
+ }
+
+ /**
+ * @return True to recycle the views used to measure this ListView in
+ * UNSPECIFIED/AT_MOST modes, false otherwise.
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "list")
+ protected boolean recycleOnMeasure() {
+ return true;
+ }
+
+ /**
+ * Measures the height of the given range of children (inclusive) and
+ * returns the height with this ListView's padding and divider heights
+ * included. If maxHeight is provided, the measuring will stop when the
+ * current height reaches maxHeight.
+ *
+ * @param widthMeasureSpec The width measure spec to be given to a child's
+ * {@link View#measure(int, int)}.
+ * @param startPosition The position of the first child to be shown.
+ * @param endPosition The (inclusive) position of the last child to be
+ * shown. Specify {@link #NO_POSITION} if the last child should be
+ * the last available child from the adapter.
+ * @param maxHeight The maximum height that will be returned (if all the
+ * children don't fit in this value, this value will be
+ * returned).
+ * @param disallowPartialChildPosition In general, whether the returned
+ * height should only contain entire children. This is more
+ * powerful--it is the first inclusive position at which partial
+ * children will not be allowed. Example: it looks nice to have
+ * at least 3 completely visible children, and in portrait this
+ * will most likely fit; but in landscape there could be times
+ * when even 2 children can not be completely shown, so a value
+ * of 2 (remember, inclusive) would be good (assuming
+ * startPosition is 0).
+ * @return The height of this ListView with the given children.
+ */
+ final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
+ final int maxHeight, int disallowPartialChildPosition) {
+
+ final ListAdapter adapter = mAdapter;
+ if (adapter == null) {
+ return mListPadding.top + mListPadding.bottom;
+ }
+
+ // Include the padding of the list
+ int returnedHeight = mListPadding.top + mListPadding.bottom;
+ final int dividerHeight = ((mDividerHeight > 0) && mDivider != null) ? mDividerHeight : 0;
+ // The previous height value that was less than maxHeight and contained
+ // no partial children
+ int prevHeightWithoutPartialChild = 0;
+ int i;
+ View child;
+
+ // mItemCount - 1 since endPosition parameter is inclusive
+ endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
+ final PLA_AbsListView.RecycleBin recycleBin = mRecycler;
+ final boolean recyle = recycleOnMeasure();
+ final boolean[] isScrap = mIsScrap;
+
+ for (i = startPosition; i <= endPosition; ++i) {
+ child = obtainView(i, isScrap);
+
+ measureScrapChild(child, i, widthMeasureSpec);
+
+ if (i > 0) {
+ // Count the divider for all but one child
+ returnedHeight += dividerHeight;
+ }
+
+ // Recycle the view before we possibly return from the method
+ if (recyle && recycleBin.shouldRecycleViewType(
+ ((LayoutParams) child.getLayoutParams()).viewType)) {
+ recycleBin.addScrapView(child);
+ }
+
+ returnedHeight += child.getMeasuredHeight();
+
+ if (returnedHeight >= maxHeight) {
+ // We went over, figure out which height to return. If returnedHeight > maxHeight,
+ // then the i'th position did not fit completely.
+ return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
+ && (i > disallowPartialChildPosition) // We've past the min pos
+ && (prevHeightWithoutPartialChild > 0) // We have a prev height
+ && (returnedHeight != maxHeight) // i'th child did not fit completely
+ ? prevHeightWithoutPartialChild
+ : maxHeight;
+ }
+
+ if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
+ prevHeightWithoutPartialChild = returnedHeight;
+ }
+ }
+
+ // At this point, we went through the range of children, and they each
+ // completely fit, so return the returnedHeight
+ return returnedHeight;
+ }
+
+ @Override
+ int findMotionRow(int y) {
+ int childCount = getChildCount();
+ if (childCount > 0) {
+ if (!mStackFromBottom) {
+ for (int i = 0; i < childCount; i++) {
+ View v = getChildAt(i);
+ if (y <= v.getBottom()) {
+ return mFirstPosition + i;
+ }
+ }
+ } else {
+ for (int i = childCount - 1; i >= 0; i--) {
+ View v = getChildAt(i);
+ if (y >= v.getTop()) {
+ return mFirstPosition + i;
+ }
+ }
+ }
+ }
+ return INVALID_POSITION;
+ }
+
+ /**
+ * Put a specific item at a specific location on the screen and then build
+ * up and down from there.
+ *
+ * @param position The reference view to use as the starting point
+ * @param top Pixel offset from the top of this view to the top of the
+ * reference view.
+ *
+ * @return The selected view, or null if the selected view is outside the
+ * visible area.
+ */
+ private View fillSpecific(int position, int top) {
+ boolean tempIsSelected = position == mSelectedPosition;
+ View temp = makeAndAddView(position, top, true, tempIsSelected);
+ // Possibly changed again in fillUp if we add rows above this one.
+ mFirstPosition = position;
+
+ View above;
+ View below;
+
+ final int dividerHeight = mDividerHeight;
+ if (!mStackFromBottom) {
+ above = fillUp(position - 1, temp.getTop() - dividerHeight);
+ // This will correct for the top of the first view not touching the top of the list
+ adjustViewsUpOrDown();
+ below = fillDown(position + 1, temp.getBottom() + dividerHeight);
+ int childCount = getChildCount();
+ if (childCount > 0) {
+ correctTooHigh(childCount);
+ }
+ } else {
+ below = fillDown(position + 1, temp.getBottom() + dividerHeight);
+ // This will correct for the bottom of the last view not touching the bottom of the list
+ adjustViewsUpOrDown();
+ above = fillUp(position - 1, temp.getTop() - dividerHeight);
+ int childCount = getChildCount();
+ if (childCount > 0) {
+ correctTooLow(childCount);
+ }
+ }
+
+ if (tempIsSelected) {
+ return temp;
+ } else if (above != null) {
+ return above;
+ } else {
+ return below;
+ }
+ }
+
+ /**
+ * Check if we have dragged the bottom of the list too high (we have pushed the
+ * top element off the top of the screen when we did not need to). Correct by sliding
+ * everything back down.
+ *
+ * @param childCount Number of children
+ */
+ private void correctTooHigh(int childCount) {
+ // First see if the last item is visible. If it is not, it is OK for the
+ // top of the list to be pushed up.
+ int lastPosition = mFirstPosition + childCount - 1;
+ if (lastPosition == mItemCount - 1 && childCount > 0) {
+
+ // Get the last child ...
+ final View lastChild = getChildAt(childCount - 1);
+
+ // ... and its bottom edge
+ final int lastBottom = lastChild.getBottom();
+
+ // This is bottom of our drawable area
+ // final int end = (mBottom - mTop) - mListPadding.bottom;
+ final int end = (getBottom() - getTop()) - mListPadding.bottom;
+
+ // This is how far the bottom edge of the last view is from the bottom of the
+ // drawable area
+ int bottomOffset = end - lastBottom;
+ View firstChild = getChildAt(0);
+ final int firstTop = firstChild.getTop();
+
+ // Make sure we are 1) Too high, and 2) Either there are more rows above the
+ // first row or the first row is scrolled off the top of the drawable area
+ if (bottomOffset > 0 && (mFirstPosition > 0 || firstTop < mListPadding.top)) {
+ if (mFirstPosition == 0) {
+ // Don't pull the top too far down
+ bottomOffset = Math.min(bottomOffset, mListPadding.top - firstTop);
+ }
+ // Move everything down
+ // offsetChildrenTopAndBottom(bottomOffset);
+ tryOffsetChildrenTopAndBottom(bottomOffset);
+ if (mFirstPosition > 0) {
+ // Fill the gap that was opened above mFirstPosition with more rows, if
+ // possible
+ fillUp(mFirstPosition - 1, firstChild.getTop() - mDividerHeight);
+ // Close up the remaining gap
+ adjustViewsUpOrDown();
+ }
+
+ }
+ }
+ }
+
+ /**
+ * Check if we have dragged the bottom of the list too low (we have pushed the
+ * bottom element off the bottom of the screen when we did not need to). Correct by sliding
+ * everything back up.
+ *
+ * @param childCount Number of children
+ */
+ private void correctTooLow(int childCount) {
+ // First see if the first item is visible. If it is not, it is OK for the
+ // bottom of the list to be pushed down.
+ if (mFirstPosition == 0 && childCount > 0) {
+
+ // Get the first child ...
+ final View firstChild = getChildAt(0);
+
+ // ... and its top edge
+ final int firstTop = firstChild.getTop();
+
+ // This is top of our drawable area
+ final int start = mListPadding.top;
+
+ // This is bottom of our drawable area
+ // final int end = (mBottom - mTop) - mListPadding.bottom;
+ final int end = (getBottom() -getTop()) - mListPadding.bottom;
+
+ // This is how far the top edge of the first view is from the top of the
+ // drawable area
+ int topOffset = firstTop - start;
+ View lastChild = getChildAt(childCount - 1);
+ final int lastBottom = lastChild.getBottom();
+ int lastPosition = mFirstPosition + childCount - 1;
+
+ // Make sure we are 1) Too low, and 2) Either there are more rows below the
+ // last row or the last row is scrolled off the bottom of the drawable area
+ if (topOffset > 0) {
+ if (lastPosition < mItemCount - 1 || lastBottom > end) {
+ if (lastPosition == mItemCount - 1) {
+ // Don't pull the bottom too far up
+ topOffset = Math.min(topOffset, lastBottom - end);
+ }
+ // Move everything up
+ // offsetChildrenTopAndBottom(-topOffset);
+ tryOffsetChildrenTopAndBottom(-topOffset);
+ if (lastPosition < mItemCount - 1) {
+ // Fill the gap that was opened below the last position with more rows, if
+ // possible
+ fillDown(lastPosition + 1, lastChild.getBottom() + mDividerHeight);
+ // Close up the remaining gap
+ adjustViewsUpOrDown();
+ }
+ } else if (lastPosition == mItemCount - 1) {
+ adjustViewsUpOrDown();
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ protected void layoutChildren() {
+ final boolean blockLayoutRequests = mBlockLayoutRequests;
+ if (!blockLayoutRequests) {
+ mBlockLayoutRequests = true;
+ } else {
+ return;
+ }
+
+ try {
+ super.layoutChildren();
+
+ invalidate();
+
+ if (mAdapter == null) {
+ resetList();
+ invokeOnItemScrollListener();
+ return;
+ }
+
+ int childrenTop = mListPadding.top;
+ //int childrenBottom = mBottom - mTop - mListPadding.bottom;
+ int childrenBottom = getBottom() - getTop() - mListPadding.bottom;
+
+ int childCount = getChildCount();
+ int index = 0;
+ int delta = 0;
+
+ View sel;
+ View oldSel = null;
+ View oldFirst = null;
+ View newSel = null;
+
+ View focusLayoutRestoreView = null;
+
+ // Remember stuff we will need down below
+ switch (mLayoutMode) {
+ case LAYOUT_SET_SELECTION:
+ index = mNextSelectedPosition - mFirstPosition;
+ if (index >= 0 && index < childCount) {
+ newSel = getChildAt(index);
+ }
+ break;
+ case LAYOUT_FORCE_TOP:
+ case LAYOUT_FORCE_BOTTOM:
+ case LAYOUT_SPECIFIC:
+ case LAYOUT_SYNC:
+ break;
+ case LAYOUT_MOVE_SELECTION:
+ default:
+ // Remember the previously selected view
+ index = mSelectedPosition - mFirstPosition;
+ if (index >= 0 && index < childCount) {
+ oldSel = getChildAt(index);
+ }
+
+ // Remember the previous first child
+ oldFirst = getChildAt(0);
+
+ if (mNextSelectedPosition >= 0) {
+ delta = mNextSelectedPosition - mSelectedPosition;
+ }
+
+ // Caution: newSel might be null
+ newSel = getChildAt(index + delta);
+ }
+
+
+ boolean dataChanged = mDataChanged;
+ if (dataChanged) {
+ handleDataChanged();
+ }
+
+ // Handle the empty set by removing all views that are visible
+ // and calling it a day
+ if (mItemCount == 0) {
+ resetList();
+ invokeOnItemScrollListener();
+ return;
+ } else if (mItemCount != mAdapter.getCount()) {
+ throw new IllegalStateException("The content of the adapter has changed but "
+ + "ListView did not receive a notification. Make sure the content of "
+ + "your adapter is not modified from a background thread, but only "
+ + "from the UI thread. [in ListView(" + getId() + ", " + getClass()
+ + ") with Adapter(" + mAdapter.getClass() + ")]");
+ }
+
+ setSelectedPositionInt(mNextSelectedPosition);
+
+ // Pull all children into the RecycleBin.
+ // These views will be reused if possible
+ final int firstPosition = mFirstPosition;
+ final RecycleBin recycleBin = mRecycler;
+
+ // reset the focus restoration
+ View focusLayoutRestoreDirectChild = null;
+
+
+ // Don't put header or footer views into the Recycler. Those are
+ // already cached in mHeaderViews;
+ if (dataChanged) {
+ for (int i = 0; i < childCount; i++) {
+ recycleBin.addScrapView(getChildAt(i));
+ if (ViewDebug.TRACE_RECYCLER) {
+ ViewDebug.trace(getChildAt(i),
+ ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, index, i);
+ }
+ }
+ } else {
+ recycleBin.fillActiveViews(childCount, firstPosition);
+ }
+
+ // take focus back to us temporarily to avoid the eventual
+ // call to clear focus when removing the focused child below
+ // from messing things up when ViewRoot assigns focus back
+ // to someone else
+ final View focusedChild = getFocusedChild();
+ if (focusedChild != null) {
+ // TODO: in some cases focusedChild.getParent() == null
+
+ // we can remember the focused view to restore after relayout if the
+ // data hasn't changed, or if the focused position is a header or footer
+ if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) {
+ focusLayoutRestoreDirectChild = focusedChild;
+ // remember the specific view that had focus
+ focusLayoutRestoreView = findFocus();
+ if (focusLayoutRestoreView != null) {
+ // tell it we are going to mess with it
+ focusLayoutRestoreView.onStartTemporaryDetach();
+ }
+ }
+ requestFocus();
+ }
+
+ // Clear out old views
+ detachAllViewsFromParent();
+
+ switch (mLayoutMode) {
+ case LAYOUT_SET_SELECTION:
+ if (newSel != null) {
+ sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
+ } else {
+ sel = fillFromMiddle(childrenTop, childrenBottom);
+ }
+ break;
+ case LAYOUT_SYNC:
+ sel = fillSpecific(mSyncPosition, mSpecificTop);
+ break;
+ case LAYOUT_FORCE_BOTTOM:
+ sel = fillUp(mItemCount - 1, childrenBottom);
+ adjustViewsUpOrDown();
+ break;
+ case LAYOUT_FORCE_TOP:
+ mFirstPosition = 0;
+ sel = fillFromTop(childrenTop);
+ adjustViewsUpOrDown();
+ break;
+ case LAYOUT_SPECIFIC:
+ sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
+ break;
+ default:
+ if (childCount == 0) {
+ if (!mStackFromBottom) {
+ final int position = lookForSelectablePosition(0, true);
+ setSelectedPositionInt(position);
+ sel = fillFromTop(childrenTop);
+ } else {
+ final int position = lookForSelectablePosition(mItemCount - 1, false);
+ setSelectedPositionInt(position);
+ sel = fillUp(mItemCount - 1, childrenBottom);
+ }
+ } else {
+ if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
+ sel = fillSpecific(mSelectedPosition,
+ oldSel == null ? childrenTop : oldSel.getTop());
+ } else if (mFirstPosition < mItemCount) {
+ sel = fillSpecific(mFirstPosition,
+ oldFirst == null ? childrenTop : oldFirst.getTop());
+ } else {
+ sel = fillSpecific(0, childrenTop);
+ }
+ }
+ break;
+ }
+
+ // Flush any cached views that did not get reused above
+ recycleBin.scrapActiveViews();
+
+ if (sel != null) {
+ // the current selected item should get focus if items
+ // are focusable
+ if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {
+ final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&
+ focusLayoutRestoreView.requestFocus()) || sel.requestFocus();
+ if (!focusWasTaken) {
+ // selected item didn't take focus, fine, but still want
+ // to make sure something else outside of the selected view
+ // has focus
+ final View focused = getFocusedChild();
+ if (focused != null) {
+ focused.clearFocus();
+ }
+ positionSelector(sel);
+ } else {
+ sel.setSelected(false);
+ mSelectorRect.setEmpty();
+ }
+ } else {
+ positionSelector(sel);
+ }
+ mSelectedTop = sel.getTop();
+ } else {
+ if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {
+ View child = getChildAt(mMotionPosition - mFirstPosition);
+ if (child != null) positionSelector(child);
+ } else {
+ mSelectedTop = 0;
+ mSelectorRect.setEmpty();
+ }
+
+ // even if there is not selected position, we may need to restore
+ // focus (i.e. something focusable in touch mode)
+ if (hasFocus() && focusLayoutRestoreView != null) {
+ focusLayoutRestoreView.requestFocus();
+ }
+ }
+
+ // tell focus view we are done mucking with it, if it is still in
+ // our view hierarchy.
+ if (focusLayoutRestoreView != null
+ && focusLayoutRestoreView.getWindowToken() != null) {
+ focusLayoutRestoreView.onFinishTemporaryDetach();
+ }
+
+ mLayoutMode = LAYOUT_NORMAL;
+ mDataChanged = false;
+ mNeedSync = false;
+ setNextSelectedPositionInt(mSelectedPosition);
+
+ updateScrollIndicators();
+
+ if (mItemCount > 0) {
+ checkSelectionChanged();
+ }
+
+ invokeOnItemScrollListener();
+ } finally {
+ if (!blockLayoutRequests) {
+ mBlockLayoutRequests = false;
+ }
+ }
+ }
+
+ /**
+ * @param child a direct child of this list.
+ * @return Whether child is a header or footer view.
+ */
+ private boolean isDirectChildHeaderOrFooter(View child) {
+
+ final ArrayList headers = mHeaderViewInfos;
+ final int numHeaders = headers.size();
+ for (int i = 0; i < numHeaders; i++) {
+ if (child == headers.get(i).view) {
+ return true;
+ }
+ }
+ final ArrayList footers = mFooterViewInfos;
+ final int numFooters = footers.size();
+ for (int i = 0; i < numFooters; i++) {
+ if (child == footers.get(i).view) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Obtain the view and add it to our list of children. The view can be made
+ * fresh, converted from an unused view, or used as is if it was in the
+ * recycle bin.
+ *
+ * @param position Logical position in the list
+ * @param childrenBottomOrTop Top or bottom edge of the view to add
+ * @param flow If flow is true, align top edge to y. If false, align bottom
+ * edge to y.
+ * @param childrenLeft Left edge where children should be positioned
+ * @param selected Is this position selected?
+ * @return View that was added
+ */
+ @SuppressWarnings("deprecation")
+ private View makeAndAddView(int position, int childrenBottomOrTop, boolean flow,
+ boolean selected) {
+ View child;
+
+ int childrenLeft;
+ if (!mDataChanged) {
+ // Try to use an exsiting view for this position
+ child = mRecycler.getActiveView(position);
+ if (child != null) {
+
+ if (ViewDebug.TRACE_RECYCLER) {
+ ViewDebug.trace(child, ViewDebug.RecyclerTraceType.RECYCLE_FROM_ACTIVE_HEAP,
+ position, getChildCount());
+ }
+
+ // Found it -- we're using an existing child
+ // This just needs to be positioned
+ childrenLeft = getChildLeft(position);
+ setupChild(child, position, childrenBottomOrTop, flow, childrenLeft, selected, true);
+ return child;
+ }
+ }
+
+ //Notify new item is added to view.
+ onItemAddedToList( position, flow );
+ childrenLeft = getChildLeft( position );
+
+ // Make a new view for this position, or convert an unused view if possible
+ child = obtainView(position, mIsScrap);
+
+ // This needs to be positioned and measured
+ setupChild(child, position, childrenBottomOrTop, flow, childrenLeft, selected, mIsScrap[0]);
+
+ return child;
+ }
+
+ /**
+ * @param position position of newly adde ditem.
+ * @param flow If flow is true, align top edge to y. If false, align bottom edge to y.
+ */
+ protected void onItemAddedToList(int position, boolean flow) {
+ }
+
+ /**
+ * Add a view as a child and make sure it is measured (if necessary) and
+ * positioned properly.
+ *
+ * @param child The view to add
+ * @param position The position of this child
+ * @param y The y position relative to which this view will be positioned
+ * @param flowDown If true, align top edge to y. If false, align bottom
+ * edge to y.
+ * @param childrenLeft Left edge where children should be positioned
+ * @param selected Is this position selected?
+ * @param recycled Has this view been pulled from the recycle bin? If so it
+ * does not need to be remeasured.
+ */
+ private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
+ boolean selected, boolean recycled) {
+
+ Log.v("PLA_ListView", "setupChild: " + position);
+
+ final boolean isSelected = selected && shouldShowSelector();
+ final boolean updateChildSelected = isSelected != child.isSelected();
+ final int mode = mTouchMode;
+ final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
+ mMotionPosition == position;
+ final boolean updateChildPressed = isPressed != child.isPressed();
+ final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
+
+ // Respect layout params that are already in the view. Otherwise make some up...
+ // noinspection unchecked
+ PLA_AbsListView.LayoutParams p = (PLA_AbsListView.LayoutParams) child.getLayoutParams();
+ if (p == null) {
+ p = new PLA_AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT, 0);
+ }
+ p.viewType = mAdapter.getItemViewType(position);
+
+ if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter &&
+ p.viewType == PLA_AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
+ attachViewToParent(child, flowDown ? -1 : 0, p);
+ } else {
+ p.forceAdd = false;
+ if (p.viewType == PLA_AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
+ p.recycledHeaderFooter = true;
+ }
+ addViewInLayout(child, flowDown ? -1 : 0, p, true);
+ }
+
+ if (updateChildSelected) {
+ child.setSelected(isSelected);
+ }
+
+ if (updateChildPressed) {
+ child.setPressed(isPressed);
+ }
+
+ if (needToMeasure) {
+ int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
+ mListPadding.left + mListPadding.right, p.width);
+ int lpHeight = p.height;
+ int childHeightSpec;
+ if (lpHeight > 0) {
+ childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
+ } else {
+ childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ }
+
+ onMeasureChild( child, position, childWidthSpec, childHeightSpec );
+ //child.measure(childWidthSpec, childHeightSpec);
+ } else {
+ cleanupLayoutState(child);
+ }
+
+ final int w = child.getMeasuredWidth();
+ final int h = child.getMeasuredHeight();
+ final int childTop = flowDown ? y : y - h;
+
+ if (needToMeasure) {
+ final int childRight = childrenLeft + w;
+ final int childBottom = childTop + h;
+ //child.layout(childrenLeft, childTop, childRight, childBottom);
+ onLayoutChild(child, position, childrenLeft, childTop, childRight, childBottom);
+ } else {
+ final int offsetLeft = childrenLeft - child.getLeft();
+ final int offsetTop = childTop - child.getTop();
+ onOffsetChild(child, position, offsetLeft, offsetTop);
+ }
+
+ if (mCachingStarted && !child.isDrawingCacheEnabled()) {
+ child.setDrawingCacheEnabled(true);
+ }
+ }
+
+ protected void onOffsetChild(View child, int position, int offsetLeft, int offsetTop) {
+ child.offsetLeftAndRight(offsetLeft);
+ child.offsetTopAndBottom(offsetTop);
+ }
+
+ protected void onLayoutChild(View child, int position, int l, int t, int r, int b) {
+ child.layout(l, t, r, b);
+ }
+
+ /**
+ * this method is called every time a new child is mesaure.
+ * @param child
+ * @param widthMeasureSpec
+ * @param heightMeasureSpec
+ */
+ protected void onMeasureChild(View child, int position, int widthMeasureSpec, int heightMeasureSpec) {
+ child.measure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ protected boolean canAnimate() {
+ return super.canAnimate() && mItemCount > 0;
+ }
+
+ /**
+ * Sets the currently selected item. If in touch mode, the item will not be selected
+ * but it will still be positioned appropriately. If the specified selection position
+ * is less than 0, then the item at position 0 will be selected.
+ *
+ * @param position Index (starting at 0) of the data item to be selected.
+ */
+ @Override
+ public void setSelection(int position) {
+ setSelectionFromTop(position, 0);
+ }
+
+ /**
+ * Sets the selected item and positions the selection y pixels from the top edge
+ * of the ListView. (If in touch mode, the item will not be selected but it will
+ * still be positioned appropriately.)
+ *
+ * @param position Index (starting at 0) of the data item to be selected.
+ * @param y The distance from the top edge of the ListView (plus padding) that the
+ * item will be positioned.
+ */
+ public void setSelectionFromTop(int position, int y) {
+ if (mAdapter == null) {
+ return;
+ }
+
+ if (!isInTouchMode()) {
+ position = lookForSelectablePosition(position, true);
+ if (position >= 0) {
+ setNextSelectedPositionInt(position);
+ }
+ } else {
+ mResurrectToPosition = position;
+ }
+
+ if (position >= 0) {
+ mLayoutMode = LAYOUT_SPECIFIC;
+ mSpecificTop = mListPadding.top + y;
+
+ if (mNeedSync) {
+ mSyncPosition = position;
+ mSyncRowId = mAdapter.getItemId(position);
+ }
+
+ requestLayout();
+ }
+ }
+
+ /**
+ * Makes the item at the supplied position selected.
+ *
+ * @param position the position of the item to select
+ */
+ @Override
+ void setSelectionInt(int position) {
+ setNextSelectedPositionInt(position);
+ boolean awakeScrollbars = false;
+
+ final int selectedPosition = mSelectedPosition;
+
+ if (selectedPosition >= 0) {
+ if (position == selectedPosition - 1) {
+ awakeScrollbars = true;
+ } else if (position == selectedPosition + 1) {
+ awakeScrollbars = true;
+ }
+ }
+
+ layoutChildren();
+
+ if (awakeScrollbars) {
+ awakenScrollBars();
+ }
+ }
+
+ /**
+ * Find a position that can be selected (i.e., is not a separator).
+ *
+ * @param position The starting position to look at.
+ * @param lookDown Whether to look down for other positions.
+ * @return The next selectable position starting at position and then searching either up or
+ * down. Returns {@link #INVALID_POSITION} if nothing can be found.
+ */
+ @Override
+ int lookForSelectablePosition(int position, boolean lookDown) {
+ final ListAdapter adapter = mAdapter;
+ if (adapter == null || isInTouchMode()) {
+ return INVALID_POSITION;
+ }
+
+ final int count = adapter.getCount();
+ if (!mAreAllItemsSelectable) {
+ if (lookDown) {
+ position = Math.max(0, position);
+ while (position < count && !adapter.isEnabled(position)) {
+ position++;
+ }
+ } else {
+ position = Math.min(position, count - 1);
+ while (position >= 0 && !adapter.isEnabled(position)) {
+ position--;
+ }
+ }
+
+ if (position < 0 || position >= count) {
+ return INVALID_POSITION;
+ }
+ return position;
+ } else {
+ if (position < 0 || position >= count) {
+ return INVALID_POSITION;
+ }
+ return position;
+ }
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ boolean populated = super.dispatchPopulateAccessibilityEvent(event);
+
+ // If the item count is less than 15 then subtract disabled items from the count and
+ // position. Otherwise ignore disabled items.
+ if (!populated) {
+ int itemCount = 0;
+ int currentItemIndex = getSelectedItemPosition();
+
+ ListAdapter adapter = getAdapter();
+ if (adapter != null) {
+ final int count = adapter.getCount();
+ if (count < 15) {
+ for (int i = 0; i < count; i++) {
+ if (adapter.isEnabled(i)) {
+ itemCount++;
+ } else if (i <= currentItemIndex) {
+ currentItemIndex--;
+ }
+ }
+ } else {
+ itemCount = count;
+ }
+ }
+
+ event.setItemCount(itemCount);
+ event.setCurrentItemIndex(currentItemIndex);
+ }
+
+ return populated;
+ }
+
+ /**
+ * setSelectionAfterHeaderView set the selection to be the first list item
+ * after the header views.
+ */
+ public void setSelectionAfterHeaderView() {
+ final int count = mHeaderViewInfos.size();
+ if (count > 0) {
+ mNextSelectedPosition = 0;
+ return;
+ }
+
+ if (mAdapter != null) {
+ setSelection(count);
+ } else {
+ mNextSelectedPosition = count;
+ mLayoutMode = LAYOUT_SET_SELECTION;
+ }
+
+ }
+
+ /**
+ * Scrolls up or down by the number of items currently present on screen.
+ *
+ * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
+ * @return whether selection was moved
+ */
+ boolean pageScroll(int direction) {
+ int nextPage = -1;
+ boolean down = false;
+
+ if (direction == FOCUS_UP) {
+ nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1);
+ } else if (direction == FOCUS_DOWN) {
+ nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1);
+ down = true;
+ }
+
+ if (nextPage >= 0) {
+ int position = lookForSelectablePosition(nextPage, down);
+ if (position >= 0) {
+ mLayoutMode = LAYOUT_SPECIFIC;
+ // mSpecificTop = mPaddingTop + getVerticalFadingEdgeLength();
+ mSpecificTop = getPaddingTop() + getVerticalFadingEdgeLength();
+
+ if (down && position > mItemCount - getChildCount()) {
+ mLayoutMode = LAYOUT_FORCE_BOTTOM;
+ }
+
+ if (!down && position < getChildCount()) {
+ mLayoutMode = LAYOUT_FORCE_TOP;
+ }
+
+ setSelectionInt(position);
+ invokeOnItemScrollListener();
+ if (!awakenScrollBars()) {
+ invalidate();
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Go to the last or first item if possible (not worrying about panning across or navigating
+ * within the internal focus of the currently selected item.)
+ *
+ * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
+ *
+ * @return whether selection was moved
+ */
+ public boolean fullScroll(int direction) {
+ boolean moved = false;
+ if (direction == FOCUS_UP) {
+ if (mSelectedPosition != 0) {
+ int position = lookForSelectablePosition(0, true);
+ if (position >= 0) {
+ mLayoutMode = LAYOUT_FORCE_TOP;
+ setSelectionInt(position);
+ invokeOnItemScrollListener();
+ }
+ moved = true;
+ }
+ } else if (direction == FOCUS_DOWN) {
+ if (mSelectedPosition < mItemCount - 1) {
+ int position = lookForSelectablePosition(mItemCount - 1, true);
+ if (position >= 0) {
+ mLayoutMode = LAYOUT_FORCE_BOTTOM;
+ setSelectionInt(position);
+ invokeOnItemScrollListener();
+ }
+ moved = true;
+ }
+ }
+
+ if (moved && !awakenScrollBars()) {
+ awakenScrollBars();
+ invalidate();
+ }
+
+ return moved;
+ }
+
+ /**
+ * Scroll the children by amount, adding a view at the end and removing
+ * views that fall off as necessary.
+ *
+ * @param amount The amount (positive or negative) to scroll.
+ */
+ private void scrollListItemsBy(int amount) {
+ // offsetChildrenTopAndBottom(amount);
+ tryOffsetChildrenTopAndBottom(amount);
+
+ final int listBottom = getHeight() - mListPadding.bottom;
+ final int listTop = mListPadding.top;
+ final PLA_AbsListView.RecycleBin recycleBin = mRecycler;
+
+ if (amount < 0) {
+ // shifted items up
+
+ // may need to pan views into the bottom space
+ View last = getLastChild();
+ int numChildren = getChildCount();
+ // View last = getChildAt(numChildren - 1);
+
+ while (last.getBottom() < listBottom) {
+ final int lastVisiblePosition = mFirstPosition + numChildren - 1;
+ if (lastVisiblePosition < mItemCount - 1) {
+ addViewBelow(last, lastVisiblePosition);
+ last = getLastChild();
+ numChildren++;
+ } else {
+ break;
+ }
+ }
+
+ // may have brought in the last child of the list that is skinnier
+ // than the fading edge, thereby leaving space at the end. need
+ // to shift back
+ if (last.getBottom() < listBottom) {
+ // offsetChildrenTopAndBottom(listBottom - last.getBottom());
+ tryOffsetChildrenTopAndBottom(listBottom - last.getBottom());
+ }
+
+ // top views may be panned off screen
+ View first = getChildAt(0);
+ while (first.getBottom() < listTop) {
+ PLA_AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams();
+ if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
+ detachViewFromParent(first);
+ recycleBin.addScrapView(first);
+ } else {
+ removeViewInLayout(first);
+ }
+ first = getChildAt(0);
+ mFirstPosition++;
+ }
+ } else {
+ // shifted items down
+ View first = getChildAt(0);
+
+ // may need to pan views into top
+ while ((first.getTop() > listTop) && (mFirstPosition > 0)) {
+ first = addViewAbove(first, mFirstPosition);
+ mFirstPosition--;
+ }
+
+ // may have brought the very first child of the list in too far and
+ // need to shift it back
+ if (first.getTop() > listTop) {
+ // offsetChildrenTopAndBottom(listTop - first.getTop());
+ tryOffsetChildrenTopAndBottom(listTop - first.getTop());
+ }
+
+ int lastIndex = getChildCount() - 1;
+ View last = getChildAt(lastIndex);
+
+ // bottom view may be panned off screen
+ while (last.getTop() > listBottom) {
+ PLA_AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams();
+ if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
+ detachViewFromParent(last);
+ recycleBin.addScrapView(last);
+ } else {
+ removeViewInLayout(last);
+ }
+ last = getChildAt(--lastIndex);
+ }
+ }
+ }
+
+ protected View getLastChild() {
+ int numChildren = getChildCount();
+ return getChildAt(numChildren - 1);
+ }
+
+ private View addViewAbove(View theView, int position) {
+ int abovePosition = position - 1;
+ View view = obtainView(abovePosition, mIsScrap);
+ int edgeOfNewChild = theView.getTop() - mDividerHeight;
+ setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left,
+ false, mIsScrap[0]);
+ return view;
+ }
+
+ private View addViewBelow(View theView, int position) {
+ int belowPosition = position + 1;
+ View view = obtainView(belowPosition, mIsScrap);
+ int edgeOfNewChild = theView.getBottom() + mDividerHeight;
+ setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left,
+ false, mIsScrap[0]);
+ return view;
+ }
+
+ /**
+ * Indicates that the views created by the ListAdapter can contain focusable
+ * items.
+ *
+ * @param itemsCanFocus true if items can get focus, false otherwise
+ */
+ public void setItemsCanFocus(boolean itemsCanFocus) {
+ mItemsCanFocus = itemsCanFocus;
+ if (!itemsCanFocus) {
+ setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+ }
+ }
+
+ /**
+ * @return Whether the views created by the ListAdapter can contain focusable
+ * items.
+ */
+ public boolean getItemsCanFocus() {
+ return mItemsCanFocus;
+ }
+
+ /**
+ * @hide Pending API council approval.
+ */
+ @Override
+ public boolean isOpaque() {
+ // return (mCachingStarted && mIsCacheColorOpaque && mDividerIsOpaque &&
+ // hasOpaqueScrollbars()) || super.isOpaque();
+ //we can ignore scrollbar...
+ return (mCachingStarted && mIsCacheColorOpaque && mDividerIsOpaque) || super.isOpaque();
+ }
+
+ @Override
+ public void setCacheColorHint(int color) {
+ final boolean opaque = (color >>> 24) == 0xFF;
+ mIsCacheColorOpaque = opaque;
+ if (opaque) {
+ if (mDividerPaint == null) {
+ mDividerPaint = new Paint();
+ }
+ mDividerPaint.setColor(color);
+ }
+ super.setCacheColorHint(color);
+ }
+
+ void drawOverscrollHeader(Canvas canvas, Drawable drawable, Rect bounds) {
+ final int height = drawable.getMinimumHeight();
+
+ canvas.save();
+ canvas.clipRect(bounds);
+
+ final int span = bounds.bottom - bounds.top;
+ if (span < height) {
+ bounds.top = bounds.bottom - height;
+ }
+
+ drawable.setBounds(bounds);
+ drawable.draw(canvas);
+
+ canvas.restore();
+ }
+
+ void drawOverscrollFooter(Canvas canvas, Drawable drawable, Rect bounds) {
+ final int height = drawable.getMinimumHeight();
+
+ canvas.save();
+ canvas.clipRect(bounds);
+
+ final int span = bounds.bottom - bounds.top;
+ if (span < height) {
+ bounds.bottom = bounds.top + height;
+ }
+
+ drawable.setBounds(bounds);
+ drawable.draw(canvas);
+
+ canvas.restore();
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ // Draw the dividers
+ final int dividerHeight = mDividerHeight;
+ final Drawable overscrollHeader = mOverScrollHeader;
+ final Drawable overscrollFooter = mOverScrollFooter;
+ final boolean drawOverscrollHeader = overscrollHeader != null;
+ final boolean drawOverscrollFooter = overscrollFooter != null;
+ final boolean drawDividers = dividerHeight > 0 && mDivider != null;
+
+ if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) {
+ // Only modify the top and bottom in the loop, we set the left and right here
+ final Rect bounds = mTempRect;
+ // bounds.left = mPaddingLeft;
+ // bounds.right = mRight - mLeft - mPaddingRight;
+ bounds.left = getPaddingLeft();
+ bounds.right = getRight() - getLeft() - getPaddingRight();
+
+ final int count = getChildCount();
+ final int headerCount = mHeaderViewInfos.size();
+ final int itemCount = mItemCount;
+ final int footerLimit = itemCount - mFooterViewInfos.size() - 1;
+ final boolean headerDividers = mHeaderDividersEnabled;
+ final boolean footerDividers = mFooterDividersEnabled;
+ final int first = mFirstPosition;
+ final boolean areAllItemsSelectable = mAreAllItemsSelectable;
+ final ListAdapter adapter = mAdapter;
+ // If the list is opaque *and* the background is not, we want to
+ // fill a rect where the dividers would be for non-selectable items
+ // If the list is opaque and the background is also opaque, we don't
+ // need to draw anything since the background will do it for us
+ final boolean fillForMissingDividers = drawDividers && isOpaque() && !super.isOpaque();
+
+ if (fillForMissingDividers && mDividerPaint == null && mIsCacheColorOpaque) {
+ mDividerPaint = new Paint();
+ mDividerPaint.setColor(getCacheColorHint());
+ }
+ final Paint paint = mDividerPaint;
+
+ // final int listBottom = mBottom - mTop - mListPadding.bottom + mScrollY;
+ final int listBottom = getBottom() - getTop() - mListPadding.bottom + getScrollY();
+ if (!mStackFromBottom) {
+ int bottom = 0;
+
+ // Draw top divider or header for overscroll
+ // final int scrollY = mScrollY;
+ final int scrollY = getScrollY();
+ if (count > 0 && scrollY < 0) {
+ if (drawOverscrollHeader) {
+ bounds.bottom = 0;
+ bounds.top = scrollY;
+ drawOverscrollHeader(canvas, overscrollHeader, bounds);
+ } else if (drawDividers) {
+ bounds.bottom = 0;
+ bounds.top = -dividerHeight;
+ drawDivider(canvas, bounds, -1);
+ }
+ }
+
+ for (int i = 0; i < count; i++) {
+ if ((headerDividers || first + i >= headerCount) &&
+ (footerDividers || first + i < footerLimit)) {
+ View child = getChildAt(i);
+ bottom = child.getBottom();
+ // Don't draw dividers next to items that are not enabled
+ if (drawDividers &&
+ (bottom < listBottom && !(drawOverscrollFooter && i == count - 1))) {
+ if ((areAllItemsSelectable ||
+ (adapter.isEnabled(first + i) && (i == count - 1 ||
+ adapter.isEnabled(first + i + 1))))) {
+ bounds.top = bottom;
+ bounds.bottom = bottom + dividerHeight;
+ drawDivider(canvas, bounds, i);
+ } else if (fillForMissingDividers) {
+ bounds.top = bottom;
+ bounds.bottom = bottom + dividerHeight;
+ canvas.drawRect(bounds, paint);
+ }
+ }
+ }
+ }
+
+ // final int overFooterBottom = mBottom + mScrollY;
+ final int overFooterBottom = getBottom() + getScrollY();
+ if (drawOverscrollFooter && first + count == itemCount &&
+ overFooterBottom > bottom) {
+ bounds.top = bottom;
+ bounds.bottom = overFooterBottom;
+ drawOverscrollFooter(canvas, overscrollFooter, bounds);
+ }
+ } else {
+ int top;
+ int listTop = mListPadding.top;
+
+ // final int scrollY = mScrollY;
+ final int scrollY = getScrollY();
+
+ if (count > 0 && drawOverscrollHeader) {
+ bounds.top = scrollY;
+ bounds.bottom = getChildAt(0).getTop();
+ drawOverscrollHeader(canvas, overscrollHeader, bounds);
+ }
+
+ final int start = drawOverscrollHeader ? 1 : 0;
+ for (int i = start; i < count; i++) {
+ if ((headerDividers || first + i >= headerCount) &&
+ (footerDividers || first + i < footerLimit)) {
+ View child = getChildAt(i);
+ top = child.getTop();
+ // Don't draw dividers next to items that are not enabled
+ if (drawDividers && top > listTop) {
+ if ((areAllItemsSelectable ||
+ (adapter.isEnabled(first + i) && (i == count - 1 ||
+ adapter.isEnabled(first + i + 1))))) {
+ bounds.top = top - dividerHeight;
+ bounds.bottom = top;
+ // Give the method the child ABOVE the divider, so we
+ // subtract one from our child
+ // position. Give -1 when there is no child above the
+ // divider.
+ drawDivider(canvas, bounds, i - 1);
+ } else if (fillForMissingDividers) {
+ bounds.top = top - dividerHeight;
+ bounds.bottom = top;
+ canvas.drawRect(bounds, paint);
+ }
+ }
+ }
+ }
+
+ if (count > 0 && scrollY > 0) {
+ if (drawOverscrollFooter) {
+ // final int absListBottom = mBottom;
+ final int absListBottom = getBottom();
+ bounds.top = absListBottom;
+ bounds.bottom = absListBottom + scrollY;
+ drawOverscrollFooter(canvas, overscrollFooter, bounds);
+ } else if (drawDividers) {
+ bounds.top = listBottom;
+ bounds.bottom = listBottom + dividerHeight;
+ drawDivider(canvas, bounds, -1);
+ }
+ }
+ }
+ }
+
+ // Draw the indicators (these should be drawn above the dividers) and children
+ super.dispatchDraw(canvas);
+ }
+
+ /**
+ * Draws a divider for the given child in the given bounds.
+ *
+ * @param canvas The canvas to draw to.
+ * @param bounds The bounds of the divider.
+ * @param childIndex The index of child (of the View) above the divider.
+ * This will be -1 if there is no child above the divider to be
+ * drawn.
+ */
+ void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
+ // This widget draws the same divider for all children
+ final Drawable divider = mDivider;
+ final boolean clipDivider = mClipDivider;
+
+ if (!clipDivider) {
+ divider.setBounds(bounds);
+ } else {
+ canvas.save();
+ canvas.clipRect(bounds);
+ }
+
+ divider.draw(canvas);
+
+ if (clipDivider) {
+ canvas.restore();
+ }
+ }
+
+ /**
+ * Returns the drawable that will be drawn between each item in the list.
+ *
+ * @return the current drawable drawn between list elements
+ */
+ public Drawable getDivider() {
+ return mDivider;
+ }
+
+ /**
+ * Sets the drawable that will be drawn between each item in the list. If the drawable does
+ * not have an intrinsic height, you should also call {@link #setDividerHeight(int)}
+ *
+ * @param divider The drawable to use.
+ */
+ public void setDivider(Drawable divider) {
+ if (divider != null) {
+ mDividerHeight = divider.getIntrinsicHeight();
+ mClipDivider = divider instanceof ColorDrawable;
+ } else {
+ mDividerHeight = 0;
+ mClipDivider = false;
+ }
+ mDivider = divider;
+ mDividerIsOpaque = divider == null || divider.getOpacity() == PixelFormat.OPAQUE;
+ requestLayoutIfNecessary();
+ }
+
+ /**
+ * @return Returns the height of the divider that will be drawn between each item in the list.
+ */
+ public int getDividerHeight() {
+ return mDividerHeight;
+ }
+
+ /**
+ * Sets the height of the divider that will be drawn between each item in the list. Calling
+ * this will override the intrinsic height as set by {@link #setDivider(Drawable)}
+ *
+ * @param height The new height of the divider in pixels.
+ */
+ public void setDividerHeight(int height) {
+ mDividerHeight = height;
+ requestLayoutIfNecessary();
+ }
+
+ /**
+ * Enables or disables the drawing of the divider for header views.
+ *
+ * @param headerDividersEnabled True to draw the headers, false otherwise.
+ *
+ * @see #setFooterDividersEnabled(boolean)
+ * @see #addHeaderView(android.view.View)
+ */
+ public void setHeaderDividersEnabled(boolean headerDividersEnabled) {
+ mHeaderDividersEnabled = headerDividersEnabled;
+ invalidate();
+ }
+
+ /**
+ * Enables or disables the drawing of the divider for footer views.
+ *
+ * @param footerDividersEnabled True to draw the footers, false otherwise.
+ *
+ * @see #setHeaderDividersEnabled(boolean)
+ * @see #addFooterView(android.view.View)
+ */
+ public void setFooterDividersEnabled(boolean footerDividersEnabled) {
+ mFooterDividersEnabled = footerDividersEnabled;
+ invalidate();
+ }
+
+ /**
+ * Sets the drawable that will be drawn above all other list content.
+ * This area can become visible when the user overscrolls the list.
+ *
+ * @param header The drawable to use
+ */
+ public void setOverscrollHeader(Drawable header) {
+ mOverScrollHeader = header;
+ // if (mScrollY < 0) {
+ // invalidate();
+ // }
+
+ if (getScrollY() < 0) {
+ invalidate();
+ }
+ }
+
+ /**
+ * @return The drawable that will be drawn above all other list content
+ */
+ public Drawable getOverscrollHeader() {
+ return mOverScrollHeader;
+ }
+
+ /**
+ * Sets the drawable that will be drawn below all other list content.
+ * This area can become visible when the user overscrolls the list,
+ * or when the list's content does not fully fill the container area.
+ *
+ * @param footer The drawable to use
+ */
+ public void setOverscrollFooter(Drawable footer) {
+ mOverScrollFooter = footer;
+ invalidate();
+ }
+
+ /**
+ * @return The drawable that will be drawn below all other list content
+ */
+ public Drawable getOverscrollFooter() {
+ return mOverScrollFooter;
+ }
+
+ @Override
+ protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+ super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+
+ int closetChildIndex = -1;
+ if (gainFocus && previouslyFocusedRect != null) {
+ // previouslyFocusedRect.offset(mScrollX, mScrollY);
+ previouslyFocusedRect.offset(getScrollX(), getScrollY());
+
+ final ListAdapter adapter = mAdapter;
+ // Don't cache the result of getChildCount or mFirstPosition here,
+ // it could change in layoutChildren.
+ if (adapter.getCount() < getChildCount() + mFirstPosition) {
+ mLayoutMode = LAYOUT_NORMAL;
+ layoutChildren();
+ }
+
+ // figure out which item should be selected based on previously
+ // focused rect
+ Rect otherRect = mTempRect;
+ int minDistance = Integer.MAX_VALUE;
+ final int childCount = getChildCount();
+ final int firstPosition = mFirstPosition;
+
+ for (int i = 0; i < childCount; i++) {
+ // only consider selectable views
+ if (!adapter.isEnabled(firstPosition + i)) {
+ continue;
+ }
+
+ View other = getChildAt(i);
+ other.getDrawingRect(otherRect);
+ offsetDescendantRectToMyCoords(other, otherRect);
+ int distance = getDistance(previouslyFocusedRect, otherRect, direction);
+
+ if (distance < minDistance) {
+ minDistance = distance;
+ closetChildIndex = i;
+ }
+ }
+ }
+
+ if (closetChildIndex >= 0) {
+ setSelection(closetChildIndex + mFirstPosition);
+ } else {
+ requestLayout();
+ }
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * Children specified in XML are assumed to be header views. After we have
+ * parsed them move them out of the children list and into mHeaderViews.
+ */
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ int count = getChildCount();
+ if (count > 0) {
+ for (int i = 0; i < count; ++i) {
+ addHeaderView(getChildAt(i));
+ }
+ removeAllViews();
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (mItemsCanFocus && ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
+ // Don't handle edge touches immediately -- they may actually belong to one of our
+ // descendants.
+ return false;
+ }
+ return super.onTouchEvent(ev);
+ }
+
+
+ @Override
+ public boolean performItemClick(View view, int position, long id) {
+ boolean handled = false;
+
+ handled |= super.performItemClick(view, position, id);
+
+ return handled;
+ }
+
+ /**
+ * Sets the checked state of the specified position. The is only valid if
+ * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
+ * {@link #CHOICE_MODE_MULTIPLE}.
+ *
+ * @param position The item whose checked state is to be checked
+ * @param value The new checked state for the item
+ */
+ public void setItemChecked(int position, boolean value) {
+ }
+
+ /**
+ * Returns the checked state of the specified position. The result is only
+ * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE}
+ * or {@link #CHOICE_MODE_MULTIPLE}.
+ *
+ * @param position The item whose checked state to return
+ * @return The item's checked state or false
if choice mode
+ * is invalid
+ *
+ * @see #setChoiceMode(int)
+ */
+ public boolean isItemChecked(int position) {
+ return false;
+ }
+
+ /**
+ * Returns the currently checked item. The result is only valid if the choice
+ * mode has been set to {@link #CHOICE_MODE_SINGLE}.
+ *
+ * @return The position of the currently checked item or
+ * {@link #INVALID_POSITION} if nothing is selected
+ *
+ * @see #setChoiceMode(int)
+ */
+ public int getCheckedItemPosition() {
+ return INVALID_POSITION;
+ }
+
+ /**
+ * Returns the set of checked items in the list. The result is only valid if
+ * the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
+ *
+ * @return A SparseBooleanArray which will return true for each call to
+ * get(int position) where position is a position in the list,
+ * or null
if the choice mode is set to
+ * {@link #CHOICE_MODE_NONE}.
+ */
+ public SparseBooleanArray getCheckedItemPositions() {
+ return null;
+ }
+
+ /**
+ * Returns the set of checked items ids. The result is only valid if the
+ * choice mode has not been set to {@link #CHOICE_MODE_NONE}.
+ *
+ * @return A new array which contains the id of each checked item in the
+ * list.
+ *
+ * @deprecated Use {@link #getCheckedItemIds()} instead.
+ */
+ @Deprecated
+ public long[] getCheckItemIds() {
+ // Use new behavior that correctly handles stable ID mapping.
+ if (mAdapter != null && mAdapter.hasStableIds()) {
+ return getCheckedItemIds();
+ }
+
+ return new long[0];
+ }
+
+ /**
+ * Returns the set of checked items ids. The result is only valid if the
+ * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
+ * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
+ *
+ * @return A new array which contains the id of each checked item in the
+ * list.
+ */
+ public long[] getCheckedItemIds() {
+ return new long[0];
+ }
+
+ /**
+ * Clear any choices previously set
+ */
+ public void clearChoices() {
+ }
+
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+ return new SavedState(superState);
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ SavedState ss = (SavedState) state;
+
+ super.onRestoreInstanceState(ss.getSuperState());
+ }
+
+}//end of class
diff --git a/src/com/huewu/pla/sample/SampleActivity.java b/src/com/huewu/pla/sample/SampleActivity.java
new file mode 100644
index 0000000..c1e2057
--- /dev/null
+++ b/src/com/huewu/pla/sample/SampleActivity.java
@@ -0,0 +1,62 @@
+package com.huewu.pla.sample;
+
+import java.util.Arrays;
+import java.util.Random;
+
+import com.huewu.lib.pla.R;
+import com.huewu.pla.lib.internal.PLA_AdapterView;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.widget.Adapter;
+import android.widget.ArrayAdapter;
+
+public class SampleActivity extends Activity {
+
+ private class MySimpleAdapter extends ArrayAdapter {
+
+ public MySimpleAdapter(Context context, int layoutRes) {
+ super(context, layoutRes, android.R.id.text1);
+ }
+ }
+
+ private PLA_AdapterView mAdapterView = null;
+ private MySimpleAdapter mAdapter = null;
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.act_sample);
+ mAdapterView = (PLA_AdapterView) findViewById(R.id.list);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ initAdapter();
+ mAdapterView.setAdapter(mAdapter);
+ }
+
+ private Random mRand = new Random();
+ private void initAdapter() {
+ mAdapter = new MySimpleAdapter(this, R.layout.item_sample);
+
+ for( int i = 0; i < 100; ++i){
+ //generate 100 random items.
+
+ StringBuilder builder = new StringBuilder();
+ builder.append("Hello!![");
+ builder.append(i);
+ builder.append("] ");
+
+ char[] chars = new char[mRand.nextInt(100)];
+ Arrays.fill(chars, '1');
+ builder.append(chars);
+ mAdapter.add(builder.toString());
+ }
+
+ }
+
+}//end of class