Android 具有ListView的自定义适配器

Android 具有ListView的自定义适配器,android,android-layout,android-listview,android-adapter,Android,Android Layout,Android Listview,Android Adapter,我试图利用我在Github上找到的自定义ListView库来固定节的标题。该库工作正常,但是我现在尝试实现自己的自定义适配器,以便固定的标题具有与其他列表项不同的布局。我得到一个错误: java.lang.UnsupportedOperationException:AdapterView中不支持addView(视图,布局参数) 在自定义ListView中有对适配器的调用:ListAdapter adapter=getAdapter()但我使用的是BaseAdapter。我不知道如何在ListVi

我试图利用我在Github上找到的自定义
ListView
库来固定节的标题。该库工作正常,但是我现在尝试实现自己的自定义
适配器
,以便固定的标题具有与其他列表项不同的布局。我得到一个错误:

java.lang.UnsupportedOperationException:AdapterView中不支持addView(视图,布局参数)

在自定义ListView中有对适配器的调用:
ListAdapter adapter=getAdapter()但我使用的是
BaseAdapter
。我不知道如何在
ListView
中使用我的
BaseAdapter
,或者如何自定义
ListAdapter
,以便使用不同的
布局

列表视图:

public class PinnedHeaderListView extends ListView {

/** Wrapper class for pinned section view and its position in the list. */
static class PinnedSection {
    public View view;
    public int position;
    public long id;
}

// fields used for drawing shadow under a pinned section
private GradientDrawable mShadowDrawable;
private int mSectionsDistanceY;
private int mShadowHeight;

/** Delegating listener, can be null. */
OnScrollListener mDelegateOnScrollListener;

/** Shadow for being recycled, can be null. */
PinnedSection mRecycleSection;

/** shadow instance with a pinned view, can be null. */
PinnedSection mPinnedSection;

/** Pinned view Y-translation. We use it to stick pinned view to the next section. */
int mTranslateY;

/** Scroll listener which does the magic */
private final OnScrollListener mOnScrollListener = new OnScrollListener() {

    @Override public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (mDelegateOnScrollListener != null) {
            mDelegateOnScrollListener.onScrollStateChanged(view, scrollState);
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

        if (mDelegateOnScrollListener != null) {
            mDelegateOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
        }

        // get expected adapter or fail fast
        ListAdapter adapter = getAdapter();
        if (adapter == null || visibleItemCount == 0) return;

        final boolean isFirstVisibleItemSection =
                isItemViewTypePinned(adapter, adapter.getItemViewType(firstVisibleItem));

        if (isFirstVisibleItemSection) {
            View sectionView = getChildAt(0);
            if (sectionView.getTop() == getPaddingTop()) { // view sticks to the top, no need for pinned shadow
                destroyPinnedShadow();
            } else { // section doesn't stick to the top, make sure we have a pinned shadow
                ensureShadowForFirstItem(firstVisibleItem, firstVisibleItem, visibleItemCount);
            }

        } else { // section is not at the first visible position
            int sectionPosition = findCurrentSectionPosition(firstVisibleItem);
            if (sectionPosition > -1) { // we have section position
                ensureShadowForPosition(sectionPosition, firstVisibleItem, visibleItemCount);
            } else { // there is no section for the first visible item, destroy shadow
                destroyPinnedShadow();
            }
        }
    }
};

private Runnable recreatePinnedShadow = new Runnable() {
    @Override
    public void run() {
        recreatePinnedShadow();
    }
};

/** Default change observer. */
private final DataSetObserver mDataSetObserver = new DataSetObserver() {
    @Override public void onChanged() {
        post(recreatePinnedShadow);
    };
    @Override public void onInvalidated() {
        post(recreatePinnedShadow);
    }
};

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

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

private void initView() {
    setOnScrollListener(mOnScrollListener);
    initShadow(true);
}

public void setShadowVisible(boolean visible) {
    initShadow(visible);
    if (mPinnedSection != null) {
        View v = mPinnedSection.view;
        invalidate(v.getLeft(), v.getTop(), v.getRight(), v.getBottom() + mShadowHeight);
    }
}

public void initShadow(boolean visible) {
    if (visible) {
        if (mShadowDrawable == null) {
            mShadowDrawable = new GradientDrawable(Orientation.TOP_BOTTOM,
                    new int[] { Color.parseColor("#ffa0a0a0"), Color.parseColor("#50a0a0a0"), Color.parseColor("#00a0a0a0")});
            mShadowHeight = (int) (8 * getResources().getDisplayMetrics().density);
        }
    } else {
        if (mShadowDrawable != null) {
            mShadowDrawable = null;
            mShadowHeight = 0;
        }
    }
}

/** Create shadow wrapper with a pinned view for a view at given position */
void createPinnedShadow(int position) {

    // try to recycle shadow
    PinnedSection pinnedShadow = mRecycleSection;
    mRecycleSection = null;

    // create new shadow, if needed
    if (pinnedShadow == null) pinnedShadow = new PinnedSection();
    // request new view using recycled view, if such
    View pinnedView = getAdapter().getView(position, pinnedShadow.view, PinnedHeaderListView.this);

    // read layout parameters
    LayoutParams layoutParams = (LayoutParams) pinnedView.getLayoutParams();
    if (layoutParams == null) { // create default layout params
        layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        pinnedView.setLayoutParams(layoutParams);
    }

    int heightMode = MeasureSpec.getMode(layoutParams.height);
    int heightSize = MeasureSpec.getSize(layoutParams.height);

    if (heightMode == MeasureSpec.UNSPECIFIED) heightMode = MeasureSpec.EXACTLY;

    int maxHeight = getHeight() - getListPaddingTop() - getListPaddingBottom();
    if (heightSize > maxHeight) heightSize = maxHeight;

    // measure & layout
    int ws = MeasureSpec.makeMeasureSpec(getWidth() - getListPaddingLeft() - getListPaddingRight(), MeasureSpec.EXACTLY);
    int hs = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
    pinnedView.measure(ws, hs);
    pinnedView.layout(0, 0, pinnedView.getMeasuredWidth(), pinnedView.getMeasuredHeight());
    mTranslateY = 0;

    // initialize pinned shadow
    pinnedShadow.view = pinnedView;
    pinnedShadow.position = position;
    pinnedShadow.id = getAdapter().getItemId(position);

    // store pinned shadow
    mPinnedSection = pinnedShadow;
}

/** Destroy shadow wrapper for currently pinned view */
void destroyPinnedShadow() {
    if (mPinnedSection != null) {
        // keep shadow for being recycled later
        mRecycleSection = mPinnedSection;
        mPinnedSection = null;
    }
}

/**
 * Makes sure we have a pinned header for the first position.
 */
void ensureShadowForFirstItem(int sectionPosition, int firstVisibleItem, int visibleItemCount) {
    // if the first item is a section, only recreate if getTop() < 0

    View sectionView = getChildAt(0);

    // when scrolling downwards, invalidate header iff sectionView's top exceeds view boundaries
    if (mPinnedSection != null && mPinnedSection.position != sectionPosition
            && sectionView.getTop() <= getPaddingTop()) {
        destroyPinnedShadow();
    }
    // when scrolling upwards, invalidate header as soon as sectionView leaves the building
    else if (mPinnedSection != null && mPinnedSection.position == sectionPosition
            && sectionView.getTop() > getPaddingTop()) {
        destroyPinnedShadow();
    }

    // create header based on the view of the current section position
    if (mPinnedSection == null && sectionView.getTop() <= getPaddingTop()) {
        createPinnedShadow(sectionPosition);
    }
    // create header based on the view of the previous section position
    else if (mPinnedSection == null && sectionView.getTop() > getPaddingTop()) {
        int prevSection = findPreviousVisibleSectionPosition(sectionPosition);
        if (prevSection > -1) {
            createPinnedShadow(prevSection);
        }
    }

    if (mPinnedSection != null && sectionView.getTop() > getPaddingTop()) {
        final int bottom = mPinnedSection.view.getBottom() + getPaddingTop();
        mSectionsDistanceY = sectionView.getTop() - bottom;
        if (mSectionsDistanceY < 0) {
            // next section overlaps pinned shadow, move it up
            mTranslateY = mSectionsDistanceY;
        } else {
            // next section does not overlap with pinned, stick to top
            mTranslateY = 0;
        }
    } else {
        mTranslateY = 0;
        mSectionsDistanceY = Integer.MAX_VALUE;
    }

}

/** Makes sure we have an actual pinned shadow for given position. */
void ensureShadowForPosition(int sectionPosition, int firstVisibleItem, int visibleItemCount) {

    if (mPinnedSection != null && mPinnedSection.position != sectionPosition) {
        // invalidate shadow, if required
        destroyPinnedShadow();
    }

    if (mPinnedSection == null) { // create shadow, if empty
        createPinnedShadow(sectionPosition);
    }

    // align shadow according to next section position, if needed
    int nextPosition = sectionPosition + 1;
    if (nextPosition < getCount()) {
        int nextSectionPosition = findFirstVisibleSectionPosition(nextPosition,
                visibleItemCount - (nextPosition - firstVisibleItem));
        if (nextSectionPosition > -1) {
            View nextSectionView = getChildAt(nextSectionPosition - firstVisibleItem);
            final int bottom = mPinnedSection.view.getBottom() + getPaddingTop();
            mSectionsDistanceY = nextSectionView.getTop() - bottom;
            if (mSectionsDistanceY < 0) {
                // next section overlaps pinned shadow, move it up
                mTranslateY = mSectionsDistanceY;
            } else {
                // next section does not overlap with pinned, stick to top
                mTranslateY = 0;
            }
        } else {
            // no other sections are visible, stick to top
            mTranslateY = 0;
            mSectionsDistanceY = Integer.MAX_VALUE;
        }
    }
}

int findPreviousVisibleSectionPosition(int fromPosition) {
    ListAdapter adapter = getAdapter();
    for (int childIndex = fromPosition - 1; childIndex >= 0; childIndex--) {
        int viewType = adapter.getItemViewType(childIndex);
        if (isItemViewTypePinned(adapter, viewType))
            return childIndex;
    }
    return -1;
}

int findFirstVisibleSectionPosition(int firstVisibleItem, int visibleItemCount) {
    ListAdapter adapter = getAdapter();
    for (int childIndex = 0; childIndex < visibleItemCount; childIndex++) {
        int position = firstVisibleItem + childIndex;
        int viewType = adapter.getItemViewType(position);
        if (isItemViewTypePinned(adapter, viewType)) return position;
    }
    return -1;
}

int findCurrentSectionPosition(int fromPosition) {
    ListAdapter adapter = getAdapter();

    if (adapter instanceof SectionIndexer) {
        // try fast way by asking section indexer
        SectionIndexer indexer = (SectionIndexer) adapter;
        int sectionPosition = indexer.getSectionForPosition(fromPosition);
        int itemPosition = indexer.getPositionForSection(sectionPosition);
        int typeView = adapter.getItemViewType(itemPosition);
        if (isItemViewTypePinned(adapter, typeView)) {
            return itemPosition;
        } // else, no luck
    }

    // try slow way by looking through to the next section item above
    for (int position=fromPosition; position>=0; position--) {
        int viewType = adapter.getItemViewType(position);
        if (isItemViewTypePinned(adapter, viewType)) return position;
    }
    return -1; // no candidate found
}

void recreatePinnedShadow() {
    destroyPinnedShadow();
    ListAdapter adapter = getAdapter();
    if (adapter != null && adapter.getCount() > 0) {
        int firstVisiblePosition = getFirstVisiblePosition();
        int sectionPosition = findCurrentSectionPosition(firstVisiblePosition);
        if (sectionPosition == -1) return; // no views to pin, exit
        ensureShadowForPosition(sectionPosition,
                firstVisiblePosition, getLastVisiblePosition() - firstVisiblePosition);
    }
}

@Override
public void setOnScrollListener(OnScrollListener listener) {
    if (listener == mOnScrollListener) {
        super.setOnScrollListener(listener);
    } else {
        mDelegateOnScrollListener = listener;
    }
}

@Override
public void onRestoreInstanceState(Parcelable state) {
    super.onRestoreInstanceState(state);
    // restore pinned view after configuration change
    post(recreatePinnedShadow);
}

@Override
public void setAdapter(ListAdapter adapter) {
    // unregister observer at old adapter and register on new one
    ListAdapter oldAdapter = getAdapter();
    if (oldAdapter != null) oldAdapter.unregisterDataSetObserver(mDataSetObserver);
    if (adapter != null) adapter.registerDataSetObserver(mDataSetObserver);

    // destroy pinned shadow, if new adapter is not same as old one
    if (oldAdapter != adapter) destroyPinnedShadow();

    super.setAdapter(adapter);
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);
    if (mPinnedSection != null) {
        int parentWidth = r - l - getPaddingLeft() - getPaddingRight();
        int shadowWidth = mPinnedSection.view.getWidth();
        if (parentWidth != shadowWidth) {
            recreatePinnedShadow();
        }
    }
}

@Override
protected void dispatchDraw(Canvas canvas) {
    super.dispatchDraw(canvas);

    if (mPinnedSection != null) {

        // prepare variables
        int pLeft = getListPaddingLeft();
        int pTop = getListPaddingTop();
        View view = mPinnedSection.view;

        // draw child
        canvas.save();

        int clipHeight = view.getHeight() +
                (mShadowDrawable == null ? 0 : Math.min(mShadowHeight, mSectionsDistanceY));
        canvas.clipRect(pLeft, pTop, pLeft + view.getWidth(), pTop + clipHeight);

        canvas.translate(pLeft, pTop + mTranslateY);
        drawChild(canvas, mPinnedSection.view, getDrawingTime());

        if (mShadowDrawable != null && mSectionsDistanceY > 0) {
            mShadowDrawable.setBounds(mPinnedSection.view.getLeft(),
                    mPinnedSection.view.getBottom(),
                    mPinnedSection.view.getRight(),
                    mPinnedSection.view.getBottom() + mShadowHeight);
            mShadowDrawable.draw(canvas);
        }

        canvas.restore();
    }
}

public static boolean isItemViewTypePinned(ListAdapter adapter, int viewType) {
    if (adapter instanceof HeaderViewListAdapter) {
        adapter = ((HeaderViewListAdapter)adapter).getWrappedAdapter();
    }
    return ((MainActivity.SimpleAdapter) adapter).isItemViewTypePinned(viewType);
}

/**
 * Sets the selected item and positions the selection y pixels from the top edge of the
 * ListView, or bottom edge of the pinned view iff it exists. (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.
 * @param adjustForHeader If true, will additionally scroll down so first item will be below header
 */
public void setSelectionFromTop(final int position, final int y, boolean adjustForHeader) {
    setSelectionFromTop(position, y);

    if (adjustForHeader) {
        post(new Runnable() {
            @Override
            public void run() {
                // do additional scrolling if a pinned view is displayed
                int pinnedOffset = (mPinnedSection == null ? 0 : mPinnedSection.view.getBottom() + getDividerHeight());
                if (pinnedOffset > 0) {
                    PinnedHeaderListView.super.setSelectionFromTop(position, y + pinnedOffset);
                }
            }
        });
    }
}

/**
 * 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);
}
}
它引用的行是:

headerView=mInflater.充气(R.layout.header\u list\u项目,父项)

我尝试将此行更改为:

headerView=mInflater.flate(R.layout.header\u list\u项,父项,false)

但是,我在以下情况下得到一个
空指针异常

header.setBackgroundResource(R.drawable.myHeaderImage)


因为标题(我的标题xml中的
TextView
)为空。

充气方法重载,您在此行中使用的两个参数版本:

headerView = mInflater.inflate(R.layout.header_list_item, parent);
实际上,尝试将膨胀视图添加到名为
父视图
适配器视图中(这是导致原始
不支持操作异常的原因)。如果您查看(第364-366行):

您可以看到为什么这是真的-因为您传递的是非空根,所以您的原始代码相当于调用

headerView = mInflater.inflate(R.layout.header_list_item, parent, true);
您在行中看到的
NullPointerException

header.setBackgroundResource(R.drawable.myHeaderImage);
这并不奇怪。当你写作时

View headerView = convertView;
TextView header = (TextView)findViewById(R.id.header);

if (headerView == null) {
    headerView = mInflater.inflate(R.layout.header_list_item, parent);
}
你可能真的想写

View headerView = convertView;

if (headerView == null) {
    headerView = mInflater.inflate(R.layout.header_list_item, parent, false);
}

TextView header = (TextView)headerView.findViewById(R.id.header);
后一个代码在(保证非空)
headerView
中搜索,以找到相应的
TextView
AdapterView
本身不包含id为
标题的
TextView

headerView = mInflater.inflate(R.layout.header_list_item, parent);
public View inflate(int resource, ViewGroup root) {
    return inflate(resource, root, root != null);
}
headerView = mInflater.inflate(R.layout.header_list_item, parent, true);
header.setBackgroundResource(R.drawable.myHeaderImage);
View headerView = convertView;
TextView header = (TextView)findViewById(R.id.header);

if (headerView == null) {
    headerView = mInflater.inflate(R.layout.header_list_item, parent);
}
View headerView = convertView;

if (headerView == null) {
    headerView = mInflater.inflate(R.layout.header_list_item, parent, false);
}

TextView header = (TextView)headerView.findViewById(R.id.header);