ListView: implement header and footer Views

Underlying HeaderViewListAdapter class is copied from AOSP
This commit is contained in:
Julian Winkler
2024-08-29 13:56:58 +02:00
parent 80013bc2d8
commit 6c454085dd
3 changed files with 289 additions and 10 deletions

View File

@@ -8,7 +8,7 @@ import android.view.ViewGroup;
public abstract class AbsListView extends AdapterView { public abstract class AbsListView extends AdapterView {
public boolean mIsChildViewEnabled = false; // this field gets directly accessed by androidx DropDownListView public boolean mIsChildViewEnabled = false; // this field gets directly accessed by androidx DropDownListView
private Observer observer = new Observer(); protected Observer observer = new Observer();
public AbsListView(Context context) { public AbsListView(Context context) {
super(context); super(context);
@@ -79,7 +79,7 @@ public abstract class AbsListView extends AdapterView {
public interface SelectionBoundsAdjuster {} public interface SelectionBoundsAdjuster {}
private class Observer extends DataSetObserver { class Observer extends DataSetObserver {
@Override @Override
public void onChanged() { public void onChanged() {

View File

@@ -1,5 +1,231 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.widget; package android.widget;
public class HeaderViewListAdapter { import java.util.ArrayList;
import android.database.DataSetObserver;
import android.view.View;
import android.view.ViewGroup;
/**
* 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.
*<p>This is intended as a base class; you will probably not need to
* use this class directly in your own code.
*/
public class HeaderViewListAdapter implements ListAdapter, 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<ListView.FixedViewInfo> mHeaderViewInfos;
ArrayList<ListView.FixedViewInfo> 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<ListView.FixedViewInfo> EMPTY_INFO_LIST =
new ArrayList<ListView.FixedViewInfo>();
boolean mAreAllFixedViewsSelectable;
public HeaderViewListAdapter(ArrayList<ListView.FixedViewInfo> headerViewInfos,
ArrayList<ListView.FixedViewInfo> footerViewInfos,
ListAdapter adapter) {
mAdapter = adapter;
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<ListView.FixedViewInfo> infos) {
if (infos != null) {
for (ListView.FixedViewInfo info : infos) {
if (!info.isSelectable) {
return false;
}
}
}
return true;
}
public boolean removeHeader(View v) {
for (int i = 0; i < mHeaderViewInfos.size(); i++) {
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++) {
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 Object getItem(int position) {
// Header (negative positions will throw an IndexOutOfBoundsException)
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 IndexOutOfBoundsException)
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 IndexOutOfBoundsException)
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 IndexOutOfBoundsException)
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 /*ITEM_VIEW_TYPE_HEADER_OR_FOOTER*/ -2;
}
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 ListAdapter getWrappedAdapter() {
return mAdapter;
}
} }

View File

@@ -1,5 +1,6 @@
package android.widget; package android.widget;
import java.util.ArrayList;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.util.AttributeSet; import android.util.AttributeSet;
@@ -7,6 +8,21 @@ import android.view.View;
public class ListView extends AbsListView { public class ListView extends AbsListView {
static class FixedViewInfo {
public View view;
public Object data;
public boolean isSelectable;
public FixedViewInfo(View view, Object data, boolean isSelectable) {
this.view = view;
this.data = data;
this.isSelectable = isSelectable;
}
}
private ArrayList<FixedViewInfo> headerViews = new ArrayList<FixedViewInfo>();
private ArrayList<FixedViewInfo> footerViews = new ArrayList<FixedViewInfo>();
public ListView(Context context) { public ListView(Context context) {
super(context); super(context);
} }
@@ -15,35 +31,72 @@ public class ListView extends AbsListView {
super(context, attributeSet); super(context, attributeSet);
} }
public ListView(Context context, AttributeSet attributeSet, int defStyleAttr) {
super(context, attributeSet, defStyleAttr);
}
@Override
public void setAdapter(ListAdapter adapter) {
if (getHeaderViewsCount() > 0 || getFooterViewsCount() > 0) {
adapter = new HeaderViewListAdapter(headerViews, footerViews, adapter);
}
super.setAdapter(adapter);
}
public int getDividerHeight() {return 0;} public int getDividerHeight() {return 0;}
public Drawable getDivider() {return null;} public Drawable getDivider() {return null;}
public void setTextFilterEnabled(boolean enabled) {} public void setTextFilterEnabled(boolean enabled) {}
public void addHeaderView(View v, Object data, boolean isSelectable) {} public void addHeaderView(View v, Object data, boolean isSelectable) {
headerViews.add(new FixedViewInfo(v, data, isSelectable));
if (getAdapter() instanceof HeaderViewListAdapter) {
observer.onChanged();
} else if (getAdapter() != null) {
setAdapter(getAdapter());
}
}
public void setDrawSelectorOnTop(boolean dummy) {} public void setDrawSelectorOnTop(boolean dummy) {}
public void addHeaderView(View view) {} public void addHeaderView(View view) {
addHeaderView(view, null, true);
}
public boolean removeHeaderView(View view) { return true; } public boolean removeHeaderView(View view) {
boolean result = false;
if (getAdapter() instanceof HeaderViewListAdapter)
result = ((HeaderViewListAdapter)getAdapter()).removeHeader(view);
if (result)
observer.onChanged();
return result;
}
public int getHeaderViewsCount() { public int getHeaderViewsCount() {
return 0; return headerViews.size();
} }
public int getFooterViewsCount() { public int getFooterViewsCount() {
return 0; return footerViews.size();
} }
public void setDivider(Drawable drawable) {} public void setDivider(Drawable drawable) {}
public void setSelectionFromTop(int position, int y) {} public void setSelectionFromTop(int position, int y) {}
public void addFooterView(View v, Object data, boolean isSelectable) {} public void addFooterView(View v, Object data, boolean isSelectable) {
footerViews.add(new FixedViewInfo(v, data, isSelectable));
if (getAdapter() instanceof HeaderViewListAdapter) {
observer.onChanged();
} else if (getAdapter() != null) {
setAdapter(getAdapter());
}
}
public void addFooterView(View v) {} public void addFooterView(View v) {
addFooterView(v, null, true);
}
public void setDividerHeight(int height) {} public void setDividerHeight(int height) {}
} }