Reputation: 4256
I am creating an ArrayAdapter for my gridView considering header and footer views.
activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<GridView
android:id="@+id/activity_main_gridview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:numColumns="4" />
</RelativeLayout>
The problem is that when I scroll to the top or the bottom of the grid header/bottom views get over grid items and, when clicking on any item it goes back to the original position (I will put screenshots if necessary)
This is my GridView adapter (params in order: context, arraylist of grid items(without headers/footers), header view for all first row, footer view for all last row, gridview columns number):
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mGridView = (GridView) findViewById(R.id.activity_main_gridview);
List<String> list = new ArrayList<String>();
for (int i = 0; i < 500; i++) {
list.add("item " + i);
}
View headerView = new View(this);
headerView.setMinimumHeight(70);
headerView.setBackgroundColor(Color.argb(255, 63, 81, 181));
GridHeaderFooterAdapter adapter =
new GridHeaderFooterAdapter(this, list, headerView, headerView, 4);
mGridView.setAdapter(adapter);
}
private final class GridHeaderFooterAdapter extends ArrayAdapter<String> {
private int mNumColumns;
private int mListSize;
private List<String> mList;
private View mHeaderView;
private View mFooterView;
private LayoutInflater mInflater;
public GridHeaderFooterAdapter(ActivityMain context, List<String> list, View headerView, View footerView, int numColumns) {
super(context, 0);
this.mNumColumns = numColumns;
this.mListSize = list.size();
this.mList = list;
this.mHeaderView = headerView;
this.mFooterView = footerView;
mInflater = context.getLayoutInflater();
}
@Override
public int getCount() {
int count = 0;
//headers
count += mNumColumns;
//list items
count += mListSize;
//footers
count += mNumColumns + (mNumColumns - mList.size() % mNumColumns);
Log.w("ActivityMain", "getCount() = " + count);
return count;
}
@Override
public String getItem(int position) {
//discard header items
return mList.get(position - mNumColumns);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//headers
if (position < mNumColumns){
Log.w("ActivityMain", "headers: position = " + position);
return mHeaderView;
}
//listitems
if (position >= mNumColumns && position < mNumColumns + mListSize) {
Log.w("ActivityMain", "listitems: position = " + position);
LinearLayout ll = (LinearLayout) mInflater.inflate(R.layout.listitem_main, null);
TextView tv = (TextView) ll.findViewById(R.id.textView);
tv.setText(getItem(position));
return ll;
}
//footers
if (position >= mNumColumns + mListSize){
Log.w("ActivityMain", "footers: position = " + position);
/*TODO: some footers might be in the same row as listitem, which is wrong because Gridview uses last item of the row to determine row height... */
return mFooterView;
}
return null;
}
}
BEFORE SCROLLING: AFTER SCROLLING:
As you can see, the target is to add a header to avoid first items being behind the status bar
Upvotes: 2
Views: 1019
Reputation: 6821
Manually playing around with the position numbers is a dangerous thing, though quite possible if done correctly. Problem here is that you are trying to bypass how the getView()
method operates. getView()
will maintain it's own reference to the returned View and recycle or destroy it accordingly. It's already been hugely optimized to provide great performance, and to achieve your end goal, you want to work with it...not against.
Ultimately, you want a spacing for the 1st (header) and last (footer) row in your GridView to avoid being overlapped with the translucent status/navigaion bars. I'll give you a higher overview of what needs to happen.
getCount()
needs to return:
mListSize + (mNumColumns * 2)
Why? Because you'll need a header view for every column and a footer view for every column as well.
getItem()
needs to return something for all possible positions. That means whatever value returned bygetCount()
needs to be able to return something...even if it's an empty string. So something like:
// if a header position or a footer position
if (position < mNumColumns || position > mListSize + mNumColumns) {
return ""
} else {
return mList.get(position - mNumColumns);
}
Note, this may be off by a little considering count is not zero based but position is...but it shows the basic gist of what you need.
getView()
needs to render a view as normal. Difference being that if the position number is indicative of a header/footer position, you return a blank view. So for example:
ViewHolder vh = null;
if (convertView == null) {
//inflate your view
convertView.setTag(vh);
} else {
vh = (ViewHolder) convertView;
}
//If position is indicative of a header/footer. Alternatively you could see if getItem() returns an empty string...assuming your data can never contain an empty string.
if (position < mNumColumns || position > mListSize + mNumColumns) {
//Don't fill the view with any data. Make any visible elements INVISIBLE
} else {
//populate convertView with data
}
Get rid of the whole notion of passing in a view for the adapter to use. That will def break everything. If performance is very much a concern then a better alternative (and the proper one...more so then what I posted) is to use getItemViewType()
and getViewTypeCount()
. However that will require you to create a class object that can represent your actual data (eg Strings) and a flag that indicates it's a header, footer, and the data.
Upvotes: 2