Android 具有ListView的自定义适配器
我试图利用我在Github上找到的自定义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
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);