All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.holoeverywhere.widget.AdapterView Maven / Gradle / Ivy

There is a newer version: 1.6.8
Show newest version

package org.holoeverywhere.widget;

import org.holoeverywhere.internal._ViewGroup;
import org.holoeverywhere.util.ReflectHelper;

import android.annotation.SuppressLint;
import android.content.Context;
import android.database.DataSetObserver;
import android.os.Build.VERSION;
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.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Adapter;

public abstract class AdapterView extends _ViewGroup {
    public static class AdapterContextMenuInfo implements
            ContextMenu.ContextMenuInfo {
        public long id;

        public int position;
        public View targetView;

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

    class AdapterDataSetObserver extends DataSetObserver {
        private Parcelable mInstanceState = null;

        public void clearSavedState() {
            mInstanceState = null;
        }

        @Override
        public void onChanged() {
            mDataChanged = true;
            mOldItemCount = mItemCount;
            mItemCount = getAdapter().getCount();
            if (AdapterView.this.getAdapter().hasStableIds()
                    && mInstanceState != null && mOldItemCount == 0
                    && mItemCount > 0) {
                onRestoreInstanceState(mInstanceState);
                mInstanceState = null;
            } else {
                rememberSyncState();
            }
            checkFocus();
            requestLayout();
        }

        @Override
        public void onInvalidated() {
            mDataChanged = true;
            if (AdapterView.this.getAdapter().hasStableIds()) {
                mInstanceState = onSaveInstanceState();
            }
            mOldItemCount = mItemCount;
            mItemCount = 0;
            mSelectedPosition = AdapterView.INVALID_POSITION;
            mSelectedRowId = AdapterView.INVALID_ROW_ID;
            mNextSelectedPosition = AdapterView.INVALID_POSITION;
            mNextSelectedRowId = AdapterView.INVALID_ROW_ID;
            mNeedSync = false;
            checkFocus();
            requestLayout();
        }
    }

    public interface OnItemClickListener {
        void onItemClick(AdapterView parent, View view, int position, long id);
    }

    public interface OnItemLongClickListener {
        boolean onItemLongClick(AdapterView parent, View view, int position,
                long id);
    }

    public interface OnItemSelectedListener {
        void onItemSelected(AdapterView parent, View view, int position,
                long id);

        void onNothingSelected(AdapterView parent);
    }

    private class SelectionNotifier implements Runnable {
        @Override
        public void run() {
            if (mDataChanged) {
                if (getAdapter() != null) {
                    post(this);
                }
            } else {
                fireOnSelected();
                performAccessibilityActionsOnSelected();
            }
        }
    }

    public static final int INVALID_POSITION = -1;
    public static final long INVALID_ROW_ID = Long.MIN_VALUE;
    public static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2;
    public static final int ITEM_VIEW_TYPE_IGNORE = -1;
    static final int SYNC_FIRST_POSITION = 1;
    static final int SYNC_MAX_DURATION_MILLIS = 100;
    static final int SYNC_SELECTED_POSITION = 0;
    boolean mBlockLayoutRequests = false;
    boolean mDataChanged;
    private boolean mDesiredFocusableInTouchModeState;
    private boolean mDesiredFocusableState;
    private View mEmptyView;
    @ViewDebug.ExportedProperty(category = "scrolling")
    int mFirstPosition = 0;
    boolean mInLayout = false;
    @ViewDebug.ExportedProperty(category = "list")
    int mItemCount;
    private int mLayoutHeight;
    boolean mNeedSync = false;
    @ViewDebug.ExportedProperty(category = "list")
    int mNextSelectedPosition = AdapterView.INVALID_POSITION;
    long mNextSelectedRowId = AdapterView.INVALID_ROW_ID;
    int mOldItemCount;
    int mOldSelectedPosition = AdapterView.INVALID_POSITION;
    long mOldSelectedRowId = AdapterView.INVALID_ROW_ID;
    OnItemClickListener mOnItemClickListener;
    OnItemLongClickListener mOnItemLongClickListener;
    OnItemSelectedListener mOnItemSelectedListener;
    @ViewDebug.ExportedProperty(category = "list")
    int mSelectedPosition = AdapterView.INVALID_POSITION;
    long mSelectedRowId = AdapterView.INVALID_ROW_ID;

    private SelectionNotifier mSelectionNotifier;

    int mSpecificTop;

    long mSyncHeight;

    int mSyncMode;

    int mSyncPosition;

    long mSyncRowId = AdapterView.INVALID_ROW_ID;

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

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

    @SuppressLint("NewApi")
    public AdapterView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        if (VERSION.SDK_INT >= 16
                && getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
            setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
        }
    }

    @Override
    public void addView(View child) {
        throw new UnsupportedOperationException(
                "addView(View) is not supported in AdapterView");
    }

    @Override
    public void addView(View child, int index) {
        throw new UnsupportedOperationException(
                "addView(View, int) is not supported in AdapterView");
    }

    @Override
    public void addView(View child, int index, LayoutParams params) {
        throw new UnsupportedOperationException(
                "addView(View, int, LayoutParams) "
                        + "is not supported in AdapterView");
    }

    @Override
    public void addView(View child, LayoutParams params) {
        throw new UnsupportedOperationException("addView(View, LayoutParams) "
                + "is not supported in AdapterView");
    }

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

    void checkFocus() {
        final T adapter = getAdapter();
        final boolean empty = adapter == null || adapter.getCount() == 0;
        final boolean focusable = !empty || isInFilterMode();
        super.setFocusableInTouchMode(focusable
                && mDesiredFocusableInTouchModeState);
        super.setFocusable(focusable && mDesiredFocusableState);
        if (mEmptyView != null) {
            updateEmptyStatus(adapter == null || adapter.isEmpty());
        }
    }

    void checkSelectionChanged() {
        if (mSelectedPosition != mOldSelectedPosition
                || mSelectedRowId != mOldSelectedRowId) {
            selectionChanged();
            mOldSelectedPosition = mSelectedPosition;
            mOldSelectedRowId = mSelectedRowId;
        }
    }

    @Override
    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
        View selectedView = getSelectedView();
        if (selectedView != null
                && selectedView.getVisibility() == View.VISIBLE
                && selectedView.dispatchPopulateAccessibilityEvent(event)) {
            return true;
        }
        return false;
    }

    @Override
    protected void dispatchRestoreInstanceState(
            SparseArray container) {
        dispatchThawSelfOnly(container);
    }

    @Override
    protected void dispatchSaveInstanceState(SparseArray container) {
        dispatchFreezeSelfOnly(container);
    }

    int findSyncPosition() {
        int count = mItemCount;
        if (count == 0) {
            return AdapterView.INVALID_POSITION;
        }
        long idToMatch = mSyncRowId;
        int seed = mSyncPosition;
        if (idToMatch == AdapterView.INVALID_ROW_ID) {
            return AdapterView.INVALID_POSITION;
        }
        seed = Math.max(0, seed);
        seed = Math.min(count - 1, seed);
        long endTime = SystemClock.uptimeMillis()
                + AdapterView.SYNC_MAX_DURATION_MILLIS;
        long rowId;
        int first = seed;
        int last = seed;
        boolean next = false;
        boolean hitFirst;
        boolean hitLast;
        T adapter = getAdapter();
        if (adapter == null) {
            return AdapterView.INVALID_POSITION;
        }
        while (SystemClock.uptimeMillis() <= endTime) {
            rowId = adapter.getItemId(seed);
            if (rowId == idToMatch) {
                return seed;
            }
            hitLast = last == count - 1;
            hitFirst = first == 0;
            if (hitLast && hitFirst) {
                break;
            }
            if (hitFirst || next && !hitLast) {
                last++;
                seed = last;
                next = false;
            } else if (hitLast || !next && !hitFirst) {
                first--;
                seed = first;
                next = true;
            }
        }
        return AdapterView.INVALID_POSITION;
    }

    private void fireOnSelected() {
        if (mOnItemSelectedListener == null) {
            return;
        }
        final int selection = getSelectedItemPosition();
        if (selection >= 0) {
            View v = getSelectedView();
            mOnItemSelectedListener.onItemSelected(this, v, selection,
                    getAdapter().getItemId(selection));
        } else {
            mOnItemSelectedListener.onNothingSelected(this);
        }
    }

    public abstract T getAdapter();

    @ViewDebug.CapturedViewProperty
    public int getCount() {
        return mItemCount;
    }

    public View getEmptyView() {
        return mEmptyView;
    }

    public int getFirstVisiblePosition() {
        return mFirstPosition;
    }

    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 ? AdapterView.INVALID_ROW_ID
                : adapter.getItemId(position);
    }

    public int getLastVisiblePosition() {
        return mFirstPosition + getChildCount() - 1;
    }

    public final OnItemClickListener getOnItemClickListener() {
        return mOnItemClickListener;
    }

    public final OnItemLongClickListener getOnItemLongClickListener() {
        return mOnItemLongClickListener;
    }

    public final OnItemSelectedListener getOnItemSelectedListener() {
        return mOnItemSelectedListener;
    }

    public int getPositionForView(View view) {
        View listItem = view;
        try {
            View v;
            while (!(v = (View) listItem.getParent()).equals(this)) {
                listItem = v;
            }
        } catch (ClassCastException e) {
            return AdapterView.INVALID_POSITION;
        }
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            if (getChildAt(i).equals(listItem)) {
                return mFirstPosition + i;
            }
        }
        return AdapterView.INVALID_POSITION;
    }

    public Object getSelectedItem() {
        T adapter = getAdapter();
        int selection = getSelectedItemPosition();
        if (adapter != null && adapter.getCount() > 0 && selection >= 0) {
            return adapter.getItem(selection);
        } else {
            return null;
        }
    }

    @ViewDebug.CapturedViewProperty
    public long getSelectedItemId() {
        return mNextSelectedRowId;
    }

    @ViewDebug.CapturedViewProperty
    public int getSelectedItemPosition() {
        return mNextSelectedPosition;
    }

    public abstract View getSelectedView();

    void handleDataChanged() {
        final int count = mItemCount;
        boolean found = false;
        if (count > 0) {
            int newPos;
            if (mNeedSync) {
                mNeedSync = false;
                newPos = findSyncPosition();
                if (newPos >= 0) {
                    int selectablePos = lookForSelectablePosition(newPos, true);
                    if (selectablePos == newPos) {
                        setNextSelectedPositionInt(newPos);
                        found = true;
                    }
                }
            }
            if (!found) {
                newPos = getSelectedItemPosition();
                if (newPos >= count) {
                    newPos = count - 1;
                }
                if (newPos < 0) {
                    newPos = 0;
                }
                int selectablePos = lookForSelectablePosition(newPos, true);
                if (selectablePos < 0) {
                    selectablePos = lookForSelectablePosition(newPos, false);
                }
                if (selectablePos >= 0) {
                    setNextSelectedPositionInt(selectablePos);
                    checkSelectionChanged();
                    found = true;
                }
            }
        }
        if (!found) {
            mSelectedPosition = AdapterView.INVALID_POSITION;
            mSelectedRowId = AdapterView.INVALID_ROW_ID;
            mNextSelectedPosition = AdapterView.INVALID_POSITION;
            mNextSelectedRowId = AdapterView.INVALID_ROW_ID;
            mNeedSync = false;
            checkSelectionChanged();
        }
        ReflectHelper.invoke(this, "notifyAccessibilityStateChanged", null);
    }

    boolean isInFilterMode() {
        return false;
    }

    private boolean isScrollableForAccessibility() {
        T adapter = getAdapter();
        if (adapter != null) {
            final int itemCount = adapter.getCount();
            return itemCount > 0
                    && (getFirstVisiblePosition() > 0 || getLastVisiblePosition() < itemCount - 1);
        }
        return false;
    }

    int lookForSelectablePosition(int position, boolean lookDown) {
        return position;
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        removeCallbacks(mSelectionNotifier);
    }

    @SuppressLint("NewApi")
    @Override
    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
        super.onInitializeAccessibilityEvent(event);
        event.setClassName(AdapterView.class.getName());
        event.setScrollable(isScrollableForAccessibility());
        View selectedView = getSelectedView();
        if (selectedView != null) {
            event.setEnabled(selectedView.isEnabled());
        }
        event.setCurrentItemIndex(getSelectedItemPosition());
        event.setFromIndex(getFirstVisiblePosition());
        event.setToIndex(getLastVisiblePosition());
        event.setItemCount(getCount());
    }

    @SuppressLint("NewApi")
    @Override
    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
        super.onInitializeAccessibilityNodeInfo(info);
        info.setClassName(AdapterView.class.getName());
        info.setScrollable(isScrollableForAccessibility());
        View selectedView = getSelectedView();
        if (selectedView != null) {
            info.setEnabled(selectedView.isEnabled());
        }
    }

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

    @SuppressLint("NewApi")
    @Override
    public boolean onRequestSendAccessibilityEvent(View child,
            AccessibilityEvent event) {
        if (super.onRequestSendAccessibilityEvent(child, event)) {
            AccessibilityEvent record = AccessibilityEvent.obtain();
            onInitializeAccessibilityEvent(record);
            child.dispatchPopulateAccessibilityEvent(record);
            event.appendRecord(record);
            return true;
        }
        return false;
    }

    private void performAccessibilityActionsOnSelected() {
        if (!isAccessibilityManagerEnabled()) {
            return;
        }
        final int position = getSelectedItemPosition();
        if (position >= 0) {
            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
        }
    }

    public boolean performItemClick(View view, int position, long id) {
        if (mOnItemClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            if (view != null) {
                view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
            }
            mOnItemClickListener.onItemClick(this, view, position, id);
            return true;
        }

        return false;
    }

    void rememberSyncState() {
        if (getChildCount() > 0) {
            mNeedSync = true;
            mSyncHeight = mLayoutHeight;
            if (mSelectedPosition >= 0) {
                View v = getChildAt(mSelectedPosition - mFirstPosition);
                mSyncRowId = mNextSelectedRowId;
                mSyncPosition = mNextSelectedPosition;
                if (v != null) {
                    mSpecificTop = v.getTop();
                }
                mSyncMode = AdapterView.SYNC_SELECTED_POSITION;
            } else {
                View v = getChildAt(0);
                T adapter = getAdapter();
                if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
                    mSyncRowId = adapter.getItemId(mFirstPosition);
                } else {
                    mSyncRowId = View.NO_ID;
                }
                mSyncPosition = mFirstPosition;
                if (v != null) {
                    mSpecificTop = v.getTop();
                }
                mSyncMode = AdapterView.SYNC_FIRST_POSITION;
            }
        }
    }

    @Override
    public void removeAllViews() {
        throw new UnsupportedOperationException(
                "removeAllViews() is not supported in AdapterView");
    }

    @Override
    public void removeView(View child) {
        throw new UnsupportedOperationException(
                "removeView(View) is not supported in AdapterView");
    }

    @Override
    public void removeViewAt(int index) {
        throw new UnsupportedOperationException(
                "removeViewAt(int) is not supported in AdapterView");
    }

    void selectionChanged() {
        if (mOnItemSelectedListener != null || isAccessibilityManagerEnabled()) {
            if (mInLayout || mBlockLayoutRequests) {
                if (mSelectionNotifier == null) {
                    mSelectionNotifier = new SelectionNotifier();
                }
                post(mSelectionNotifier);
            } else {
                fireOnSelected();
                performAccessibilityActionsOnSelected();
            }
        }
    }

    public abstract void setAdapter(T adapter);

    @SuppressLint("NewApi")
    public void setEmptyView(View emptyView) {
        mEmptyView = emptyView;
        if (VERSION.SDK_INT >= 16
                && emptyView != null
                && emptyView.getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
            emptyView
                    .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
        }
        final T adapter = getAdapter();
        final boolean empty = adapter == null || adapter.isEmpty();
        updateEmptyStatus(empty);
    }

    @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 setNextSelectedPositionInt(int position) {
        mNextSelectedPosition = position;
        mNextSelectedRowId = getItemIdAtPosition(position);
        if (mNeedSync && mSyncMode == AdapterView.SYNC_SELECTED_POSITION
                && position >= 0) {
            mSyncPosition = position;
            mSyncRowId = mNextSelectedRowId;
        }
    }

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

    public void setOnItemClickListener(OnItemClickListener listener) {
        mOnItemClickListener = listener;
    }

    public void setOnItemLongClickListener(OnItemLongClickListener listener) {
        if (!isLongClickable()) {
            setLongClickable(true);
        }
        mOnItemLongClickListener = listener;
    }

    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
        mOnItemSelectedListener = listener;
    }

    void setSelectedPositionInt(int position) {
        mSelectedPosition = position;
        mSelectedRowId = getItemIdAtPosition(position);
    }

    public abstract void setSelection(int position);

    private void updateEmptyStatus(boolean empty) {
        if (isInFilterMode()) {
            empty = false;
        }

        if (empty) {
            if (mEmptyView != null) {
                mEmptyView.setVisibility(View.VISIBLE);
                setVisibility(View.GONE);
            } else {
                setVisibility(View.VISIBLE);
            }
            if (mDataChanged) {
                this.onLayout(false, getLeft(), getTop(), getRight(),
                        getBottom());
            }
        } else {
            if (mEmptyView != null) {
                mEmptyView.setVisibility(View.GONE);
            }
            setVisibility(View.VISIBLE);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy