I create gridview with sectionviews and have some problems with it
#Override
public View getView(int position, View convertView, ViewGroup parent) {
int size = mArray.get(0).size();
int z = size + (COLUMNS_NUM * 2);
//sectionView
if (position < (COLUMNS_NUM * 2) || (position > (z -1) && position < (z + COLUMNS_NUM))) {
if(position == COLUMNS_NUM ){
return header(R.string.grid_last_added, convertView);
}
if(position == z){
return header(R.string.grid_last_added, convertView);
}
if (convertView == null) {
convertView = new View(mContext);
}
// Set empty view with height of ActionBar
convertView.setLayoutParams(new AbsListView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, mActionBarHeight));
if(position == COLUMNS_NUM || position == z){
convertView.setLayoutParams(new AbsListView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, mActionBarHeight - 15));
}
return convertView;
}
View v = convertView;
if(v == null){
v = LayoutInflater.from(mContext).inflate(R.layout.player_movie, null);
v.setLayoutParams(mImageViewLayoutParams);
}
TextView title = (TextView) v.findViewById(R.id.serial_title);
TextView seria = (TextView) v.findViewById(R.id.when);
ImageView poster = (ImageView) v.findViewById(R.id.poster);
//Offten.debug(getCount(), position);
int x = (position - (COLUMNS_NUM * 2) )< mArray.get(0).size() ? position - (COLUMNS_NUM * 2) : position - (z + COLUMNS_NUM);
int y = (position - (COLUMNS_NUM * 2) )< mArray.get(0).size() ? 0 : 1;
// Offten.debug(x + " " + (position < mArray.get(0).size() ? "true" : "false") + " " + mArray.get(0).size());
//Offten.debug( Boolean.toString((position - (COLUMNS_NUM * 2) )< mArray.get(0).size()) + " " + x + " " + z);
Serial serial = mArray.get(y).get(x);
title.setText(serial.getTitle());
seria.setText(serial.getSeria());
mFetcher.loadImage( serial.getPoster(), poster);
if(v.getLayoutParams().height != mItemHeight){
v.setLayoutParams(mImageViewLayoutParams);
}
return v;
}
public TextView header(int message, View convertView){
TextView view = (TextView) convertView; // issue with cast exception
if(view == null){
view = new TextView(mContext);
}
view.setText(underline(mContext.getResources().getString(message)));
view.setTextSize(18);
view.setTypeface(typeface, Typeface.BOLD_ITALIC);
view.setTextColor(Color.WHITE);
view.setGravity(Gravity.CENTER_VERTICAL);
view.setLayoutParams(new AbsListView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, mActionBarHeight - 15));
return view;
}
With this code i always have issue with cast exception in my header TextView. (View cannot be cast to TextView)
#Override
public int getViewTypeCount() {
// Two types of views, the normal ImageView and the top row of empty views
return 2;
}
#Override
public int getCount() {
int size = 0;
for(List<Serial> s : mArray){
size += s.size() + COLUMNS_NUM;
}
return size > 0 ? size + COLUMNS_NUM : size;
}
#Override
public int getItemViewType(int position) {
int size = mArray.get(0).size();
int z = size + (COLUMNS_NUM * 2);
return position < (COLUMNS_NUM * 2) || (position > (z -1) && position < (z + COLUMNS_NUM)) ? 1 : 0;
}
can i solve my problem??
Do like this
public TextView header(int message, View convertView){
TextView view = (TextView) convertView.findViewById(R.id.<textviewId>); // issue with cast exception
if(view == null){
view = new TextView(mContext);
}
view.setText(underline(mContext.getResources().getString(message)));
view.setTextSize(18);
view.setTypeface(typeface, Typeface.BOLD_ITALIC);
view.setTextColor(Color.WHITE);
view.setGravity(Gravity.CENTER_VERTICAL);
view.setLayoutParams(new AbsListView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, mActionBarHeight - 15));
return view;
}
So i fount answer for my question! i have 3 difference view, but i declareted only 2, and i had problems! Thanks for all to helping!
#Override
public int getItemViewType(int position) {
int size = mArray.get(0).size();
int z = size + (COLUMNS_NUM * 2);
if (position < (COLUMNS_NUM * 2) || (position > (z -1) && position < (z + COLUMNS_NUM))) {
if(position == COLUMNS_NUM || position == z){
return 1;
}
return 2;
}
return 0;
}
#Override
public int getViewTypeCount() {
// Two types of views, the normal ImageView and the top row of empty views
return 3;
}
Related
I attempted to reverse engineer a gridview from an example found here:
https://github.com/liaohuqiu/android-GridViewWithHeaderAndFooter
using his source code found here:
https://github.com/liaohuqiu/android-cube-app/blob/master/src/in/srain/cube/demo/ui/fragment/GridViewWithHeaderAndFooterFragment.java#L89
but keep getting a casting error when I try to display images within it.
I would like to learn how to do it myself because his code crashes when I try loading images using glide.
Here is the gridview code I have:
package customgridapp.customgridview;
import android.annotation.SuppressLint;
import android.content.Context;
import android.database.DataSetObservable;
import android.database.DataSetObserver;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.FrameLayout;
import android.widget.GridView;
import android.widget.ListAdapter;
import android.widget.WrapperListAdapter;
import java.util.ArrayList;
public class TestFooterGridView extends GridView{
private static final String TAG = "TestFooterGridView";
/**
* A class that represents a fixed view in a list, for example a header at the top
* or a footer at the bottom.
*/
private static class FixedViewInfo {
/**
* The view to add to the grid
*/
public View view;
public ViewGroup viewContainer;
/**
* The data backing the view. This is returned from {#link ListAdapter#getItem(int)}.
*/
public Object data;
/**
* <code>true</code> if the fixed view should be selectable in the grid
*/
public boolean isSelectable;
}
private ArrayList<FixedViewInfo> mHeaderViewInfos = new ArrayList<FixedViewInfo>();
private ArrayList<FixedViewInfo> mFooterViewInfos = new ArrayList<FixedViewInfo>();
private int mRequestedNumColumns;
private int mNumColmuns = 1;
private void initHeaderGridView() {
super.setClipChildren(false);
}
public TestFooterGridView(Context context) {
super(context);
initHeaderGridView();
}
public TestFooterGridView(Context context, AttributeSet attrs) {
super(context, attrs);
initHeaderGridView();
}
public TestFooterGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initHeaderGridView();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mRequestedNumColumns != AUTO_FIT) {
mNumColmuns = mRequestedNumColumns;
}
if (mNumColmuns <= 0) {
mNumColmuns = 1;
}
ListAdapter adapter = getAdapter();
if (adapter != null && adapter instanceof FooterViewGridAdapter) {
((FooterViewGridAdapter) adapter).setNumColumns(getNumColumns());
}
}
#Override
public void setClipChildren(boolean clipChildren) {
// Ignore, since the header rows depend on not being clipped
}
/**
* Add a fixed view to appear at the top of the grid. If addHeaderView is
* called more than once, the views will appear in the order they were
* added. Views added using this call can take focus if they want.
* <p>
* NOTE: Call this before calling setAdapter. This is so HeaderFooterGridView can wrap
* the supplied cursor with one that will also account for header views.
*
* #param v The view to add.
* #param data Data to associate with this view
* #param isSelectable whether the item is selectable
*/
public void addHeaderView(View v, Object data, boolean isSelectable) {
ListAdapter adapter = getAdapter();
if (adapter != null && !(adapter instanceof FooterViewGridAdapter)) {
throw new IllegalStateException(
"Cannot add header view to grid -- setAdapter has already been called.");
}
FixedViewInfo info = new FixedViewInfo();
// FrameLayout fl = new FullWidthFixedViewLayout(getContext());
// fl.addView(v);
info.view = v;
// info.viewContainer = fl;
info.data = data;
info.isSelectable = isSelectable;
mHeaderViewInfos.add(info);
// in the case of re-adding a header view, or adding one later on,
// we need to notify the observer
if (adapter != null) {
((FooterViewGridAdapter) adapter).notifyDataSetChanged();
}
}
/**
* Add a fixed view to appear at the bottom of the grid. If addFooterView is
* called more than once, the views will appear in the order they were
* added. Views added using this call can take focus if they want.
* <p>
* NOTE: Call this before calling setAdapter. This is so HeaderFooterGridView can wrap
* the supplied cursor with one that will also account for header views.
*
* #param v The view to add.
* #param data Data to associate with this view
* #param isSelectable whether the item is selectable
*/
public void addFooterView(View v, Object data, boolean isSelectable) {
ListAdapter adapter = getAdapter();
if (adapter != null && !(adapter instanceof FooterViewGridAdapter)) {
throw new IllegalStateException(
"Cannot add footer view to grid -- setAdapter has already been called.");
}
FixedViewInfo info = new FixedViewInfo();
FrameLayout fl = new FullWidthFixedViewLayout(getContext());
fl.addView(v);
info.view = v;
info.viewContainer = fl;
info.data = data;
info.isSelectable = isSelectable;
mFooterViewInfos.add(info);
// in the case of re-adding a header view, or adding one later on,
// we need to notify the observer
if (adapter != null) {
((FooterViewGridAdapter) adapter).notifyDataSetChanged();
}
}
/**
* Add a fixed view to appear at the bottom of the grid. If addFooterView is
* called more than once, the views will appear in the order they were
* added. Views added using this call can take focus if they want.
* <p>
* NOTE: Call this before calling setAdapter. This is so HeaderFooterGridView can wrap
* the supplied cursor with one that will also account for header views.
*
* #param v The view to add.
*/
public void addFooterView(View v) {
addFooterView(v, null, false);
}
private void removeFixedViewInfo(View v, ArrayList<FixedViewInfo> where) {
int len = where.size();
for (int i = 0; i < len; ++i) {
FixedViewInfo info = where.get(i);
if (info.view == v) {
where.remove(i);
break;
}
}
}
#Override
public void setAdapter(ListAdapter adapter) {
if (mHeaderViewInfos.size() > 0 || mFooterViewInfos.size() > 0) {
FooterViewGridAdapter hadapter = new FooterViewGridAdapter(mFooterViewInfos, adapter);
int numColumns = getNumColumns();
if (numColumns > 1) {
hadapter.setNumColumns(numColumns);
}
super.setAdapter(hadapter);
} else {
super.setAdapter(adapter);
}
}
private class FullWidthFixedViewLayout extends FrameLayout {
public FullWidthFixedViewLayout(Context context) {
super(context);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int targetWidth = TestFooterGridView.this.getMeasuredWidth()
- TestFooterGridView.this.getPaddingLeft()
- TestFooterGridView.this.getPaddingRight();
widthMeasureSpec = MeasureSpec.makeMeasureSpec(targetWidth,
MeasureSpec.getMode(widthMeasureSpec));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
#Override
public void setNumColumns(int numColumns) {
super.setNumColumns(numColumns);
// Store specified value for less than Honeycomb.
mRequestedNumColumns = numColumns;
}
#Override
#SuppressLint("NewApi")
public int getNumColumns() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
return super.getNumColumns();
}
// Return value for less than Honeycomb.
return mNumColmuns;
}
/**
* ListAdapter used when a HeaderFooterGridView 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.
*/
private static class FooterViewGridAdapter implements WrapperListAdapter, Filterable {
// This is used to notify the container of updates relating to number of columns
// or headers changing, which changes the number of placeholders needed
private final DataSetObservable mDataSetObservable = new DataSetObservable();
private final ListAdapter mAdapter;
private int mNumColumns = 1;
// This ArrayList is assumed to NOT be null.
ArrayList<FixedViewInfo> mFooterViewInfos;
boolean mAreAllFixedViewsSelectable;
private final boolean mIsFilterable;
public FooterViewGridAdapter(ArrayList<FixedViewInfo> footerViewInfos, ListAdapter adapter) {
mAdapter = adapter;
mIsFilterable = adapter instanceof Filterable;
if (footerViewInfos == null) {
throw new IllegalArgumentException("footerViewInfos cannot be null");
}
mFooterViewInfos = footerViewInfos;
mAreAllFixedViewsSelectable = areAllListInfosSelectable(mFooterViewInfos);
}
public int getHeadersCount() {
return 0;
}
public int getFootersCount() {
return mFooterViewInfos.size();
}
#Override
public boolean isEmpty() {
return (mAdapter == null || mAdapter.isEmpty()) && getHeadersCount() == 0 && getFootersCount() == 0;
}
public void setNumColumns(int numColumns) {
if (numColumns < 1) {
throw new IllegalArgumentException("Number of columns must be 1 or more");
}
if (mNumColumns != numColumns) {
mNumColumns = numColumns;
notifyDataSetChanged();
}
}
private boolean areAllListInfosSelectable(ArrayList<FixedViewInfo> infos) {
if (infos != null) {
for (FixedViewInfo info : infos) {
if (!info.isSelectable) {
return false;
}
}
}
return true;
}
public boolean removeFooter(View v) {
for (int i = 0; i < mFooterViewInfos.size(); i++) {
FixedViewInfo info = mFooterViewInfos.get(i);
if (info.view == v) {
mFooterViewInfos.remove(i);
mAreAllFixedViewsSelectable = areAllListInfosSelectable(mFooterViewInfos);
mDataSetObservable.notifyChanged();
return true;
}
}
return false;
}
#Override
public int getCount() {
if (mAdapter != null) {
final int lastRowItemCount = (mAdapter.getCount() % mNumColumns);
final int emptyItemCount = ((lastRowItemCount == 0) ? 0 : mNumColumns - lastRowItemCount);
return (getHeadersCount() * mNumColumns) + mAdapter.getCount() + emptyItemCount + (getFootersCount() * mNumColumns);
} else {
return (getHeadersCount() * mNumColumns) + (getFootersCount() * mNumColumns);
}
}
#Override
public boolean areAllItemsEnabled() {
if (mAdapter != null) {
return mAreAllFixedViewsSelectable && mAdapter.areAllItemsEnabled();
} else {
return true;
}
}
#Override
public boolean isEnabled(int position) {
// Header (negative positions will throw an ArrayIndexOutOfBoundsException)
int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
if (position < numHeadersAndPlaceholders) {
return (position % mNumColumns == 0);
// && mHeaderViewInfos.get(position / mNumColumns).isSelectable;
}
// Adapter
if (position < numHeadersAndPlaceholders + mAdapter.getCount()) {
final int adjPosition = position - numHeadersAndPlaceholders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
return mAdapter.isEnabled(adjPosition);
}
}
}
// Empty item
final int lastRowItemCount = (mAdapter.getCount() % mNumColumns);
final int emptyItemCount = ((lastRowItemCount == 0) ? 0 : mNumColumns - lastRowItemCount);
if (position < numHeadersAndPlaceholders + mAdapter.getCount() + emptyItemCount) {
// return false;
return (position % mNumColumns == 0)
&& mFooterViewInfos.get((position - numHeadersAndPlaceholders - mAdapter.getCount() - emptyItemCount) / mNumColumns).isSelectable;
}
// Footer
int numFootersAndPlaceholders = getFootersCount() * mNumColumns;
if (position < numHeadersAndPlaceholders + mAdapter.getCount() + emptyItemCount + numFootersAndPlaceholders) {
return (position % mNumColumns == 0)
&& mFooterViewInfos.get((position - numHeadersAndPlaceholders - mAdapter.getCount() - emptyItemCount) / mNumColumns).isSelectable;
}
throw new ArrayIndexOutOfBoundsException(position);
}
#Override
public Object getItem(int position) {
// Header (negative positions will throw an ArrayIndexOutOfBoundsException)
int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
if (position < numHeadersAndPlaceholders) {
if (position % mNumColumns == 0) {
return 0; //mHeaderViewInfos.get(position / mNumColumns).data;
}
return null;
}
// Adapter
if (position < numHeadersAndPlaceholders + mAdapter.getCount()) {
final int adjPosition = position - numHeadersAndPlaceholders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
return mAdapter.getItem(adjPosition);
}
}
}
// Empty item
final int lastRowItemCount = (mAdapter.getCount() % mNumColumns);
Log.e(TAG, "Lastrowitemcount:" + lastRowItemCount);
final int emptyItemCount = ((lastRowItemCount == 0) ? 0 : mNumColumns - lastRowItemCount);
Log.e(TAG, "emptyitemcount:" + emptyItemCount);
if (position < numHeadersAndPlaceholders + mAdapter.getCount() + emptyItemCount) {
/*return null;*/
return mFooterViewInfos.get((position - numHeadersAndPlaceholders - mAdapter.getCount() - emptyItemCount) / mNumColumns).data;
}
// Footer
int numFootersAndPlaceholders = getFootersCount() * mNumColumns;
if (position < numHeadersAndPlaceholders + mAdapter.getCount() + emptyItemCount + numFootersAndPlaceholders) {
if (position % mNumColumns == 0) {
return mFooterViewInfos.get((position - numHeadersAndPlaceholders - mAdapter.getCount() - emptyItemCount) / mNumColumns).data;
}
}
throw new ArrayIndexOutOfBoundsException(position);
}
#Override
public long getItemId(int position) {
int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
if (mAdapter != null) {
if (position >= numHeadersAndPlaceholders && position < numHeadersAndPlaceholders + mAdapter.getCount()) {
int adjPosition = position - numHeadersAndPlaceholders;
int adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
return mAdapter.getItemId(adjPosition);
}
}
}
return -1;
}
#Override
public boolean hasStableIds() {
if (mAdapter != null) {
return mAdapter.hasStableIds();
}
return false;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// Header (negative positions will throw an ArrayIndexOutOfBoundsException)
int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
if (position < numHeadersAndPlaceholders) {
/*View headerViewContainer = mHeaderViewInfos
.get(position / mNumColumns).viewContainer;*/
if (position % mNumColumns == 0) {
return null; //headerViewContainer;
} else {
convertView = new View(parent.getContext());
// We need to do this because GridView uses the height of the last item
// in a row to determine the height for the entire row.
convertView.setVisibility(View.INVISIBLE);
// convertView.setMinimumHeight(headerViewContainer.getHeight());
return convertView;
}
}
// Adapter
// TODO Current implementation may not be enough in the case of 3 or more column. May need to be careful on the INVISIBLE View height.
if (position < numHeadersAndPlaceholders + mAdapter.getCount()) {
final int adjPosition = position - numHeadersAndPlaceholders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
convertView = mAdapter.getView(adjPosition, convertView, parent);
convertView.setVisibility(View.VISIBLE);
return convertView;
}
}
}
// Empty item
final int lastRowItemCount = (mAdapter.getCount() % mNumColumns);
Log.e(TAG, "Lastrowitemcount:" + lastRowItemCount);
final int emptyItemCount = ((lastRowItemCount == 0) ? 0 : mNumColumns - lastRowItemCount);
Log.e(TAG, "Emptyitemcount:" + emptyItemCount);
if (position < numHeadersAndPlaceholders + mAdapter.getCount() + emptyItemCount) {
// We need to do this because GridView uses the height of the last item
// in a row to determine the height for the entire row.
// TODO Current implementation may not be enough in the case of 3 or more column. May need to be careful on the INVISIBLE View height.
convertView = mAdapter.getView(mAdapter.getCount() - 1, convertView, parent);
convertView.setVisibility(View.INVISIBLE);
return convertView;
}
// Footer
int numFootersAndPlaceholders = getFootersCount() * mNumColumns;
if (position < numHeadersAndPlaceholders + mAdapter.getCount() + emptyItemCount + numFootersAndPlaceholders) {
View footerViewContainer = mFooterViewInfos
.get((position - numHeadersAndPlaceholders - mAdapter.getCount() - emptyItemCount) / mNumColumns).viewContainer;
if (position % mNumColumns == 0) {
return footerViewContainer;
} else {
convertView = new View(parent.getContext());
// We need to do this because GridView uses the height of the last item
// in a row to determine the height for the entire row.
convertView.setVisibility(View.INVISIBLE);
convertView.setMinimumHeight(footerViewContainer.getHeight());
return convertView;
}
}
throw new ArrayIndexOutOfBoundsException(position);
}
#Override
public int getItemViewType(int position) {
int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
if (position < numHeadersAndPlaceholders && (position % mNumColumns != 0)) {
// Placeholders get the last view type number
return mAdapter != null ? mAdapter.getViewTypeCount() : 1;
}
if (mAdapter != null && position >= numHeadersAndPlaceholders && position < numHeadersAndPlaceholders + mAdapter.getCount() + (mNumColumns - (mAdapter.getCount() % mNumColumns))) {
int adjPosition = position - numHeadersAndPlaceholders;
int adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
return mAdapter.getItemViewType(adjPosition);
} else if (adapterCount != 0 && mNumColumns != 1) {
return mAdapter.getItemViewType(adapterCount - 1);
}
}
int numFootersAndPlaceholders = getFootersCount() * mNumColumns;
if (mAdapter != null && position < numHeadersAndPlaceholders + mAdapter.getCount() + numFootersAndPlaceholders) {
return mAdapter != null ? mAdapter.getViewTypeCount() : 1;
}
return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
}
#Override
public int getViewTypeCount() {
if (mAdapter != null) {
return mAdapter.getViewTypeCount() + 1;
}
return 2;
}
#Override
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
if (mAdapter != null) {
mAdapter.registerDataSetObserver(observer);
}
}
#Override
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
if (mAdapter != null) {
mAdapter.unregisterDataSetObserver(observer);
}
}
#Override
public Filter getFilter() {
if (mIsFilterable) {
return ((Filterable) mAdapter).getFilter();
}
return null;
}
#Override
public ListAdapter getWrappedAdapter() {
return mAdapter;
}
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
}
}
I tried understanding his code by removing any code relating to headers and only looking at what he does with footers.
In short: How do I add a footer to a three column gridview?
What do I need?
You don't need any libraries for that. Just try this ....
Create Layout like this.....
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v4.widget.NestedScrollView
android:id="#+id/nestedScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="Header"
android:textColor="#color/white"
android:textSize="16sp" />
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="Footer"
android:textColor="#color/white"
android:textSize="16sp" />
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
</LinearLayout>
And Use this code in your Activity or Fragment...
RecyclerView recyclerView = findViewById(R.id.recyclerView);
ViewCompat.setNestedScrollingEnabled(recyclerView, false);
GridLayoutManager mLayoutManager = new GridLayoutManager(activity, 3);
recyclerView.setLayoutManager(mLayoutManager);
adapter = new YourCustomAdapter(this);
recyclerView.setAdapter(adapter);
Note:- In My point of view don't depend on libraries. If they will remove it in future then you have to change your work again. If you want to use library try to import their GitHub whole module in your project. So you don't depend on their changes. There is an incident with me, where i used image compression library in my app. After one year they removed their library and i had to work again on that module. And if your are using some famous company libraries then it is fine like Facebook, Google , Square or more. Many big company also depend on their Library.
I have a collection of photos, and I'm using a RecyclerView to display them. I want to have the first element in my RecyclerView span 2 columns AND 2 rows:
I know I can span 2 columns with setSpanSizeLookup:
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 3);
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
if (position == 0) {
return 2;
} else {
return 1;
}
}
});
but how can I also make the first item span 2 rows as well?
I have tried setting the first item's height to be different by inflating a different layout with double the height of the others, but that resulted in every item on the same row as the first item also being stretched to that height:
#Override
public ProfilePicViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView;
if (viewType == TYPE_MAIN_PHOTO) {
itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_view_main_profile_photo, parent, false);
} else {
itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_view_profile_photo, parent, false);
}
return new ProfilePicViewHolder(itemView);
}
You cannot achieve this behavior with GridLayoutManager, because it only supports spanning multiple columns.
Nick Butcher is currently implementing a custom SpannedGridLayoutManager that does exactly what you want. It allows you to span multiple rows and columns at the same time. The implementation is still WIP, but already works quite well.
SpannedGridLayoutManager.java
package io.plaidapp.ui.recyclerview;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.PointF;
import android.graphics.Rect;
import android.support.annotation.Keep;
import android.support.annotation.NonNull;
import android.support.v7.widget.LinearSmoothScroller;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
import io.plaidapp.R;
/**
* A {#link RecyclerView.LayoutManager} which displays a regular grid (i.e. all cells are the same
* size) and allows simultaneous row & column spanning.
*/
public class SpannedGridLayoutManager extends RecyclerView.LayoutManager {
private GridSpanLookup spanLookup;
private int columns = 1;
private float cellAspectRatio = 1f;
private int cellHeight;
private int[] cellBorders;
private int firstVisiblePosition;
private int lastVisiblePosition;
private int firstVisibleRow;
private int lastVisibleRow;
private boolean forceClearOffsets;
private SparseArray<GridCell> cells;
private List<Integer> firstChildPositionForRow; // key == row, val == first child position
private int totalRows;
private final Rect itemDecorationInsets = new Rect();
public SpannedGridLayoutManager(GridSpanLookup spanLookup, int columns, float cellAspectRatio) {
this.spanLookup = spanLookup;
this.columns = columns;
this.cellAspectRatio = cellAspectRatio;
setAutoMeasureEnabled(true);
}
#Keep /* XML constructor, see RecyclerView#createLayoutManager */
public SpannedGridLayoutManager(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.SpannedGridLayoutManager, defStyleAttr, defStyleRes);
columns = a.getInt(R.styleable.SpannedGridLayoutManager_spanCount, 1);
parseAspectRatio(a.getString(R.styleable.SpannedGridLayoutManager_aspectRatio));
// TODO use this!
int orientation = a.getInt(
R.styleable.SpannedGridLayoutManager_android_orientation, RecyclerView.VERTICAL);
a.recycle();
setAutoMeasureEnabled(true);
}
public interface GridSpanLookup {
SpanInfo getSpanInfo(int position);
}
public void setSpanLookup(#NonNull GridSpanLookup spanLookup) {
this.spanLookup = spanLookup;
}
public static class SpanInfo {
public int columnSpan;
public int rowSpan;
public SpanInfo(int columnSpan, int rowSpan) {
this.columnSpan = columnSpan;
this.rowSpan = rowSpan;
}
public static final SpanInfo SINGLE_CELL = new SpanInfo(1, 1);
}
public static class LayoutParams extends RecyclerView.LayoutParams {
int columnSpan;
int rowSpan;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(ViewGroup.MarginLayoutParams source) {
super(source);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(RecyclerView.LayoutParams source) {
super(source);
}
}
#Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
calculateWindowSize();
calculateCellPositions(recycler, state);
if (state.getItemCount() == 0) {
detachAndScrapAttachedViews(recycler);
firstVisibleRow = 0;
resetVisibleItemTracking();
return;
}
// TODO use orientationHelper
int startTop = getPaddingTop();
int scrollOffset = 0;
if (forceClearOffsets) { // see #scrollToPosition
startTop = -(firstVisibleRow * cellHeight);
forceClearOffsets = false;
} else if (getChildCount() != 0) {
scrollOffset = getDecoratedTop(getChildAt(0));
startTop = scrollOffset - (firstVisibleRow * cellHeight);
resetVisibleItemTracking();
}
detachAndScrapAttachedViews(recycler);
int row = firstVisibleRow;
int availableSpace = getHeight() - scrollOffset;
int lastItemPosition = state.getItemCount() - 1;
while (availableSpace > 0 && lastVisiblePosition < lastItemPosition) {
availableSpace -= layoutRow(row, startTop, recycler, state);
row = getNextSpannedRow(row);
}
layoutDisappearingViews(recycler, state, startTop);
}
#Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
#Override
public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
return new LayoutParams(c, attrs);
}
#Override
public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
if (lp instanceof ViewGroup.MarginLayoutParams) {
return new LayoutParams((ViewGroup.MarginLayoutParams) lp);
} else {
return new LayoutParams(lp);
}
}
#Override
public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
return lp instanceof LayoutParams;
}
#Override
public void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) {
removeAllViews();
reset();
}
#Override
public boolean supportsPredictiveItemAnimations() {
return true;
}
#Override
public boolean canScrollVertically() {
return true;
}
#Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state){
if (getChildCount() == 0 || dy == 0) return 0;
int scrolled;
int top = getDecoratedTop(getChildAt(0));
if (dy < 0) { // scrolling content down
if (firstVisibleRow == 0) { // at top of content
int scrollRange = -(getPaddingTop() - top);
scrolled = Math.max(dy, scrollRange);
} else {
scrolled = dy;
}
if (top - scrolled >= 0) { // new top row came on screen
int newRow = firstVisibleRow - 1;
if (newRow >= 0) {
int startOffset = top - (firstVisibleRow * cellHeight);
layoutRow(newRow, startOffset, recycler, state);
}
}
int firstPositionOfLastRow = getFirstPositionInSpannedRow(lastVisibleRow);
int lastRowTop = getDecoratedTop(
getChildAt(firstPositionOfLastRow - firstVisiblePosition));
if (lastRowTop - scrolled > getHeight()) { // last spanned row scrolled out
recycleRow(lastVisibleRow, recycler, state);
}
} else { // scrolling content up
int bottom = getDecoratedBottom(getChildAt(getChildCount() - 1));
if (lastVisiblePosition == getItemCount() - 1) { // is at end of content
int scrollRange = Math.max(bottom - getHeight() + getPaddingBottom(), 0);
scrolled = Math.min(dy, scrollRange);
} else {
scrolled = dy;
}
if ((bottom - scrolled) < getHeight()) { // new row scrolled in
int nextRow = lastVisibleRow + 1;
if (nextRow < getSpannedRowCount()) {
int startOffset = top - (firstVisibleRow * cellHeight);
layoutRow(nextRow, startOffset, recycler, state);
}
}
int lastPositionInRow = getLastPositionInSpannedRow(firstVisibleRow, state);
int bottomOfFirstRow =
getDecoratedBottom(getChildAt(lastPositionInRow - firstVisiblePosition));
if (bottomOfFirstRow - scrolled < 0) { // first spanned row scrolled out
recycleRow(firstVisibleRow, recycler, state);
}
}
offsetChildrenVertical(-scrolled);
return scrolled;
}
#Override
public void scrollToPosition(int position) {
if (position >= getItemCount()) position = getItemCount() - 1;
firstVisibleRow = getRowIndex(position);
resetVisibleItemTracking();
forceClearOffsets = true;
removeAllViews();
requestLayout();
}
#Override
public void smoothScrollToPosition(
RecyclerView recyclerView, RecyclerView.State state, int position) {
if (position >= getItemCount()) position = getItemCount() - 1;
LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext()) {
#Override
public PointF computeScrollVectorForPosition(int targetPosition) {
final int rowOffset = getRowIndex(targetPosition) - firstVisibleRow;
return new PointF(0, rowOffset * cellHeight);
}
};
scroller.setTargetPosition(position);
startSmoothScroll(scroller);
}
#Override
public int computeVerticalScrollRange(RecyclerView.State state) {
// TODO update this to incrementally calculate
if (firstChildPositionForRow == null) return 0;
return getSpannedRowCount() * cellHeight + getPaddingTop() + getPaddingBottom();
}
#Override
public int computeVerticalScrollExtent(RecyclerView.State state) {
return getHeight();
}
#Override
public int computeVerticalScrollOffset(RecyclerView.State state) {
if (getChildCount() == 0) return 0;
return getPaddingTop() + (firstVisibleRow * cellHeight) - getDecoratedTop(getChildAt(0));
}
#Override
public View findViewByPosition(int position) {
if (position < firstVisiblePosition || position > lastVisiblePosition) return null;
return getChildAt(position - firstVisiblePosition);
}
public int getFirstVisibleItemPosition() {
return firstVisiblePosition;
}
private static class GridCell {
final int row;
final int rowSpan;
final int column;
final int columnSpan;
GridCell(int row, int rowSpan, int column, int columnSpan) {
this.row = row;
this.rowSpan = rowSpan;
this.column = column;
this.columnSpan = columnSpan;
}
}
/**
* This is the main layout algorithm, iterates over all items and places them into [column, row]
* cell positions. Stores this layout info for use later on. Also records the adapter position
* that each row starts at.
* <p>
* Note that if a row is spanned, then the row start position is recorded as the first cell of
* the row that the spanned cell starts in. This is to ensure that we have sufficient contiguous
* views to layout/draw a spanned row.
*/
private void calculateCellPositions(RecyclerView.Recycler recycler, RecyclerView.State state) {
final int itemCount = state.getItemCount();
cells = new SparseArray<>(itemCount);
firstChildPositionForRow = new ArrayList<>();
int row = 0;
int column = 0;
recordSpannedRowStartPosition(row, column);
int[] rowHWM = new int[columns]; // row high water mark (per column)
for (int position = 0; position < itemCount; position++) {
SpanInfo spanInfo;
int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(position);
if (adapterPosition != RecyclerView.NO_POSITION) {
spanInfo = spanLookup.getSpanInfo(adapterPosition);
} else {
// item removed from adapter, retrieve its previous span info
// as we can't get from the lookup (adapter)
spanInfo = getSpanInfoFromAttachedView(position);
}
if (spanInfo.columnSpan > columns) {
spanInfo.columnSpan = columns; // or should we throw?
}
// check horizontal space at current position else start a new row
// note that this may leave gaps in the grid; we don't backtrack to try and fit
// subsequent cells into gaps. We place the responsibility on the adapter to provide
// continuous data i.e. that would not span column boundaries to avoid gaps.
if (column + spanInfo.columnSpan > columns) {
row++;
recordSpannedRowStartPosition(row, position);
column = 0;
}
// check if this cell is already filled (by previous spanning cell)
while (rowHWM[column] > row) {
column++;
if (column + spanInfo.columnSpan > columns) {
row++;
recordSpannedRowStartPosition(row, position);
column = 0;
}
}
// by this point, cell should fit at [column, row]
cells.put(position, new GridCell(row, spanInfo.rowSpan, column, spanInfo.columnSpan));
// update the high water mark book-keeping
for (int columnsSpanned = 0; columnsSpanned < spanInfo.columnSpan; columnsSpanned++) {
rowHWM[column + columnsSpanned] = row + spanInfo.rowSpan;
}
// if we're spanning rows then record the 'first child position' as the first item
// *in the row the spanned item starts*. i.e. the position might not actually sit
// within the row but it is the earliest position we need to render in order to fill
// the requested row.
if (spanInfo.rowSpan > 1) {
int rowStartPosition = getFirstPositionInSpannedRow(row);
for (int rowsSpanned = 1; rowsSpanned < spanInfo.rowSpan; rowsSpanned++) {
int spannedRow = row + rowsSpanned;
recordSpannedRowStartPosition(spannedRow, rowStartPosition);
}
}
// increment the current position
column += spanInfo.columnSpan;
}
totalRows = rowHWM[0];
for (int i = 1; i < rowHWM.length; i++) {
if (rowHWM[i] > totalRows) {
totalRows = rowHWM[i];
}
}
}
private SpanInfo getSpanInfoFromAttachedView(int position) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (position == getPosition(child)) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
return new SpanInfo(lp.columnSpan, lp.rowSpan);
}
}
// errrrr?
return SpanInfo.SINGLE_CELL;
}
private void recordSpannedRowStartPosition(final int rowIndex, final int position) {
if (getSpannedRowCount() < (rowIndex + 1)) {
firstChildPositionForRow.add(position);
}
}
private int getRowIndex(final int position) {
return position < cells.size() ? cells.get(position).row : -1;
}
private int getSpannedRowCount() {
return firstChildPositionForRow.size();
}
private int getNextSpannedRow(int rowIndex) {
int firstPositionInRow = getFirstPositionInSpannedRow(rowIndex);
int nextRow = rowIndex + 1;
while (nextRow < getSpannedRowCount()
&& getFirstPositionInSpannedRow(nextRow) == firstPositionInRow) {
nextRow++;
}
return nextRow;
}
private int getFirstPositionInSpannedRow(int rowIndex) {
return firstChildPositionForRow.get(rowIndex);
}
private int getLastPositionInSpannedRow(final int rowIndex, RecyclerView.State state) {
int nextRow = getNextSpannedRow(rowIndex);
return (nextRow != getSpannedRowCount()) ? // check if reached boundary
getFirstPositionInSpannedRow(nextRow) - 1
: state.getItemCount() - 1;
}
/**
* Lay out a given 'row'. We might actually add more that one row if the requested row contains
* a row-spanning cell. Returns the pixel height of the rows laid out.
* <p>
* To simplify logic & book-keeping, views are attached in adapter order, that is child 0 will
* always be the earliest position displayed etc.
*/
private int layoutRow(
int rowIndex, int startTop, RecyclerView.Recycler recycler, RecyclerView.State state) {
int firstPositionInRow = getFirstPositionInSpannedRow(rowIndex);
int lastPositionInRow = getLastPositionInSpannedRow(rowIndex, state);
boolean containsRemovedItems = false;
int insertPosition = (rowIndex < firstVisibleRow) ? 0 : getChildCount();
for (int position = firstPositionInRow;
position <= lastPositionInRow;
position++, insertPosition++) {
View view = recycler.getViewForPosition(position);
LayoutParams lp = (LayoutParams) view.getLayoutParams();
containsRemovedItems |= lp.isItemRemoved();
GridCell cell = cells.get(position);
addView(view, insertPosition);
// TODO use orientation helper
int wSpec = getChildMeasureSpec(
cellBorders[cell.column + cell.columnSpan] - cellBorders[cell.column],
View.MeasureSpec.EXACTLY, 0, lp.width, false);
int hSpec = getChildMeasureSpec(cell.rowSpan * cellHeight,
View.MeasureSpec.EXACTLY, 0, lp.height, true);
measureChildWithDecorationsAndMargin(view, wSpec, hSpec);
int left = cellBorders[cell.column] + lp.leftMargin;
int top = startTop + (cell.row * cellHeight) + lp.topMargin;
int right = left + getDecoratedMeasuredWidth(view);
int bottom = top + getDecoratedMeasuredHeight(view);
layoutDecorated(view, left, top, right, bottom);
lp.columnSpan = cell.columnSpan;
lp.rowSpan = cell.rowSpan;
}
if (firstPositionInRow < firstVisiblePosition) {
firstVisiblePosition = firstPositionInRow;
firstVisibleRow = getRowIndex(firstVisiblePosition);
}
if (lastPositionInRow > lastVisiblePosition) {
lastVisiblePosition = lastPositionInRow;
lastVisibleRow = getRowIndex(lastVisiblePosition);
}
if (containsRemovedItems) return 0; // don't consume space for rows with disappearing items
GridCell first = cells.get(firstPositionInRow);
GridCell last = cells.get(lastPositionInRow);
return (last.row + last.rowSpan - first.row) * cellHeight;
}
/**
* Remove and recycle all items in this 'row'. If the row includes a row-spanning cell then all
* cells in the spanned rows will be removed.
*/
private void recycleRow(
int rowIndex, RecyclerView.Recycler recycler, RecyclerView.State state) {
int firstPositionInRow = getFirstPositionInSpannedRow(rowIndex);
int lastPositionInRow = getLastPositionInSpannedRow(rowIndex, state);
int toRemove = lastPositionInRow;
while (toRemove >= firstPositionInRow) {
int index = toRemove - firstVisiblePosition;
removeAndRecycleViewAt(index, recycler);
toRemove--;
}
if (rowIndex == firstVisibleRow) {
firstVisiblePosition = lastPositionInRow + 1;
firstVisibleRow = getRowIndex(firstVisiblePosition);
}
if (rowIndex == lastVisibleRow) {
lastVisiblePosition = firstPositionInRow - 1;
lastVisibleRow = getRowIndex(lastVisiblePosition);
}
}
private void layoutDisappearingViews(
RecyclerView.Recycler recycler, RecyclerView.State state, int startTop) {
// TODO
}
private void calculateWindowSize() {
// TODO use OrientationHelper#getTotalSpace
int cellWidth =
(int) Math.floor((getWidth() - getPaddingLeft() - getPaddingRight()) / columns);
cellHeight = (int) Math.floor(cellWidth * (1f / cellAspectRatio));
calculateCellBorders();
}
private void reset() {
cells = null;
firstChildPositionForRow = null;
firstVisiblePosition = 0;
firstVisibleRow = 0;
lastVisiblePosition = 0;
lastVisibleRow = 0;
cellHeight = 0;
forceClearOffsets = false;
}
private void resetVisibleItemTracking() {
// maintain the firstVisibleRow but reset other state vars
// TODO make orientation agnostic
int minimumVisibleRow = getMinimumFirstVisibleRow();
if (firstVisibleRow > minimumVisibleRow) firstVisibleRow = minimumVisibleRow;
firstVisiblePosition = getFirstPositionInSpannedRow(firstVisibleRow);
lastVisibleRow = firstVisibleRow;
lastVisiblePosition = firstVisiblePosition;
}
private int getMinimumFirstVisibleRow() {
int maxDisplayedRows = (int) Math.ceil((float) getHeight() / cellHeight) + 1;
if (totalRows < maxDisplayedRows) return 0;
int minFirstRow = totalRows - maxDisplayedRows;
// adjust to spanned rows
return getRowIndex(getFirstPositionInSpannedRow(minFirstRow));
}
/* Adapted from GridLayoutManager */
private void calculateCellBorders() {
cellBorders = new int[columns + 1];
int totalSpace = getWidth() - getPaddingLeft() - getPaddingRight();
int consumedPixels = getPaddingLeft();
cellBorders[0] = consumedPixels;
int sizePerSpan = totalSpace / columns;
int sizePerSpanRemainder = totalSpace % columns;
int additionalSize = 0;
for (int i = 1; i <= columns; i++) {
int itemSize = sizePerSpan;
additionalSize += sizePerSpanRemainder;
if (additionalSize > 0 && (columns - additionalSize) < sizePerSpanRemainder) {
itemSize += 1;
additionalSize -= columns;
}
consumedPixels += itemSize;
cellBorders[i] = consumedPixels;
}
}
private void measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec) {
calculateItemDecorationsForChild(child, itemDecorationInsets);
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + itemDecorationInsets.left,
lp.rightMargin + itemDecorationInsets.right);
heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + itemDecorationInsets.top,
lp.bottomMargin + itemDecorationInsets.bottom);
child.measure(widthSpec, heightSpec);
}
private int updateSpecWithExtra(int spec, int startInset, int endInset) {
if (startInset == 0 && endInset == 0) {
return spec;
}
int mode = View.MeasureSpec.getMode(spec);
if (mode == View.MeasureSpec.AT_MOST || mode == View.MeasureSpec.EXACTLY) {
return View.MeasureSpec.makeMeasureSpec(
View.MeasureSpec.getSize(spec) - startInset - endInset, mode);
}
return spec;
}
/* Adapted from ConstraintLayout */
private void parseAspectRatio(String aspect) {
if (aspect != null) {
int colonIndex = aspect.indexOf(':');
if (colonIndex >= 0 && colonIndex < aspect.length() - 1) {
String nominator = aspect.substring(0, colonIndex);
String denominator = aspect.substring(colonIndex + 1);
if (nominator.length() > 0 && denominator.length() > 0) {
try {
float nominatorValue = Float.parseFloat(nominator);
float denominatorValue = Float.parseFloat(denominator);
if (nominatorValue > 0 && denominatorValue > 0) {
cellAspectRatio = Math.abs(nominatorValue / denominatorValue);
return;
}
} catch (NumberFormatException e) {
// Ignore
}
}
}
}
throw new IllegalArgumentException("Could not parse aspect ratio: '" + aspect + "'");
}
}
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SpannedGridLayoutManager">
<attr name="android:orientation" />
<attr name="spanCount" />
<attr name="aspectRatio" format="string" />
</declare-styleable>
</resources>
The code is also available here.
Example usage
The code requires RecyclerView 23.2.0 or higher.
So add the following line to your build.gradle, if you didn't already do so.
dependencies {
compile 'com.android.support:recyclerview-v7:24.2.1'
}
To achieve the layout shown in the initial post, we define the LayoutManager as follows
recyclerView.setLayoutManager(new SpannedGridLayoutManager(
new SpannedGridLayoutManager.GridSpanLookup() {
#Override
public SpannedGridLayoutManager.SpanInfo getSpanInfo(int position) {
if (position == 0) {
return new SpannedGridLayoutManager.SpanInfo(2, 2);
} else {
return new SpannedGridLayoutManager.SpanInfo(1, 1);
}
}
},
3 /* Three columns */,
1f /* We want our items to be 1:1 ratio */));
You can use SpannedGridLayoutManager library wrote by Arasthel in here
This is the result
You can achieve this behavior by using RecycleView for rows only, with ViewHolder for each row. So you will have RowViewHolder for simple rows and something like DoubleRowViewHolder for custom layout that will have 3 items, just the way you want.
I have a list view and a custom adapter which holds 10 items and shows 6 items at a time, until the user scrolls down to reveal the last few items. As the items are being selected by my code I am changing the background color of each specific item one after the other, and it works fine until I hit the last item and use:
mailList.smoothScrollToPosition(0);
to scroll back up to the top of the list. When this happens the background color for item 0 does not change, and then once I select the next item that background color changes and it continues to work perfectly.
To change the background color of each item I am using this method:
public void selectRow(int position){
// Get the row
View row = getViewByPosition(position, mailList);
// Highlight background colour
row.setBackgroundColor(ContextCompat.getColor(activity, R.color.Pallette_Peach_CC));
// For every other row apart from 'position'
// Check if read/unread and set background colour
View v;
for(int i = 0; i < mailList.getCount(); i++){
if(i != position){
v = getViewByPosition(i, mailList);
if(Consts.mailBox.get(i).isRead()){
v.setBackgroundColor(ContextCompat.getColor(activity, R.color.Ivory_Transparent_55));
}else{
v.setBackgroundColor(ContextCompat.getColor(activity, R.color.Ivory_Transparent_77));
}
}
}
}
which uses the method getViewByPosition() to return the view so I can set it's color:
// returns a specific row
private View getViewByPosition(int pos, ListView listView) {
final int firstListItemPosition = listView.getFirstVisiblePosition();
final int lastListItemPosition = firstListItemPosition + listView.getChildCount() - 1;
if (pos < firstListItemPosition || pos > lastListItemPosition ) {
return listView.getAdapter().getView(pos, null, listView);
} else {
final int childIndex = pos - firstListItemPosition;
return listView.getChildAt(childIndex);
}
}
On the first iteration item 0 changes color, but once I reach the end of the list (where list positions 9(the end of the list) up to 3 are visible) and smoothScroll to the first (0) position, the color of item 0 does not change. To call the selectRow method I use:
if(position == 0) {
mailList.smoothScrollToPosition(0);
selectRow(0);
}
So it should scroll back up to the top of the list, call selectRow(0) and work like it did at the first iteration, but it doesn't seem to be doing that. Can anyone help me out? Thanks.
EDIT:
Here is my getView method from my adapter class:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if(view == null){
view = inflater.inflate(R.layout.fragment_mail_row, null);
}
LinearLayout rowContainer = (LinearLayout) view.findViewById(R.id.mailRowContainer);
ImageView mailIcon = (ImageView) view.findViewById(R.id.mailRowImage);
TextView mailTitle = (TextView) view.findViewById(R.id.mailRowTitleText);
TextView mailDate = (TextView) view.findViewById(R.id.mailRowTimeText);
MailItem mMailItem = mail.get(position);
String title = mMailItem.getTitle();
String timestamp;
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
Date today = new Date();
Date messageDate = mMailItem.getDate();
if(sdf.format(today).equals(sdf.format(messageDate))){
// If date of message is today then display the time
SimpleDateFormat tf = new SimpleDateFormat("kk:mm");
timestamp = tf.format(messageDate);
}else{
SimpleDateFormat df = new SimpleDateFormat("dd/MM/yy");
timestamp = df.format(messageDate);
}
// If mail is already opened, re-set the background colour
if(mMailItem.isRead()){
rowContainer.setBackgroundColor(ContextCompat.getColor(activity, R.color.Ivory_Transparent_55));
}else{
rowContainer.setBackgroundColor(ContextCompat.getColor(activity, R.color.Ivory_Transparent_77));
}
// Set the title and time
mailTitle.setText(title);
mailDate.setText(timestamp);
if(mMailItem.getType().equals(Consts.MAIL_TYPE_AUDIO)){
if(mMailItem.isRead()){
mailIcon.setImageResource(R.mipmap.ic_mail_audio_open);
}else{
mailIcon.setImageResource(R.mipmap.ic_mail_audio_closed);
}
}else{
if(mMailItem.isRead()){
mailIcon.setImageResource(R.mipmap.ic_mail_opened);
}else{
mailIcon.setImageResource(R.mipmap.ic_mail_closed);
}
}
return view;
}
EDIT 2:
For further clarity, the selectRow method is being called from a seperate class called MailSelector which is in its entirety here:
public class MailSelector {
double noOfItems;
double noOfItemsPerPage;
int noOfPages;
int noOfItemsOnLastPage;
private ListView mailList;
private Activity activity;
public MailSelector(Activity a, ListView list){
this.activity = a;
this.mailList = list;
noOfItemsPerPage = mailList.getLastVisiblePosition() - mailList.getFirstVisiblePosition();
}
public int nextRow(int position){
// Calculate item/page info
noOfItems = mailList.getCount();
if(noOfItems <= noOfItemsPerPage ){
// Only one page so iterate and return to start position
// If only one item or if after last item, select position 0
if(mailList.getChildCount() == 1 || position >= mailList.getCount()){
selectRow(0);
return 0;
}
}else{
// More than one page so handle scrolling
noOfPages = (int) Math.ceil(noOfItems / noOfItemsPerPage);
// Calculate current page. Round down and + 1, as page 1 is actually 0
int currentPage = getCurrentPage(position);
int curPageStartPos = (int) ((int) (noOfItemsPerPage * currentPage) - noOfItemsPerPage);
int curPageEndPos = curPageStartPos + ((int) noOfItemsPerPage - 1);
noOfItemsOnLastPage = (int)(noOfItems % noOfItemsPerPage);
// Scrolling if on last item of page
if(position == curPageEndPos){
// Check if last page
if(currentPage == noOfPages){
// Scroll back to position 0
mailList.smoothScrollToPosition(0);
selectRow(0);
return 0;
}else if(currentPage == noOfPages - 1){
// If page before last
// Get last position of last page
int lastPosOfNextPage = mailList.getCount() - 1;
mailList.smoothScrollToPosition(lastPosOfNextPage);
position = (int) ((position + 1) % noOfItems);
selectRow(position);
}else{
int lastPosOfNextPage = (int) (position + noOfItemsPerPage);
mailList.smoothScrollToPosition(lastPosOfNextPage);
position = (int) ((position + 1) % noOfItems);
selectRow(position);
}
}else{
position = (int) ((position + 1) % noOfItems);
selectRow(position);
}
}
if(position == 0) {
mailList.smoothScrollToPosition(0);
selectRow(0);
}
return position;
}
public void selectRow(int position){
// Get the row
View row = getViewByPosition(position, mailList);
// Highlight background colour
row.setBackgroundColor(ContextCompat.getColor(activity, R.color.Pallette_Peach_CC));
// For every other row apart from 'position'
// Check if read/unread and set background colour
View v;
for(int i = 0; i < mailList.getCount(); i++){
if(i != position){
v = getViewByPosition(i, mailList);
if(Consts.mailBox.get(i).isRead()){
v.setBackgroundColor(ContextCompat.getColor(activity, R.color.Ivory_Transparent_55));
}else{
v.setBackgroundColor(ContextCompat.getColor(activity, R.color.Ivory_Transparent_77));
}
}
}
}
// returns a specific row
private View getViewByPosition(int pos, ListView listView) {
final int firstListItemPosition = listView.getFirstVisiblePosition();
final int lastListItemPosition = firstListItemPosition + listView.getChildCount() - 1;
if (pos < firstListItemPosition || pos > lastListItemPosition ) {
return listView.getAdapter().getView(pos, null, listView);
} else {
final int childIndex = pos - firstListItemPosition;
return listView.getChildAt(childIndex);
}
}
private int getCurrentPage(int position){
return ((int) Math.floor(position / (double) noOfItemsPerPage)) + 1;
}
}
I have an application in which I am showing ListView with two different layouts. I have about 25 items in a list and initial items shows in well order with everything as I want but when I scroll down some items in particular row overrides each other and every time I scroll they show up in different manner than the previous one. Images also gets shuffled and overrides each other.
This is my Custom Adapter class :-
public class CustomAdapterSettings extends BaseAdapter {
private static final int TYPE_ITEM = 0;
private static final int TYPE_SEPARATOR = 1;
private static final int TYPE_MAX_COUNT = TYPE_SEPARATOR + 1;
private ArrayList<String> arrListData = new ArrayList<String>();
private ArrayList<String> arrListDescription = new ArrayList<String>();
private LayoutInflater layInflater;
private Context context;
private TreeSet<Integer> treeSeparatorsSet = new TreeSet<Integer>();
public CustomAdapterSettings(Context context) {
this.context = context;
layInflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public void addItem(final String data, final String description) {
arrListData.add(data);
arrListDescription.add(description);
notifyDataSetChanged();
}
public void addSeparatorItem(final String seperator) {
arrListData.add(seperator);
arrListDescription.add(seperator);
// save separator position
treeSeparatorsSet.add(arrListData.size() - 1);
notifyDataSetChanged();
}
#Override
public int getItemViewType(int position) {
return treeSeparatorsSet.contains(position) ? TYPE_SEPARATOR
: TYPE_ITEM;
}
#Override
public int getViewTypeCount() {
return TYPE_MAX_COUNT;
}
#Override
public int getCount() {
return arrListData.size();
}
#Override
public String getItem(int position) {
return arrListData.get(position);
}
public String getItemDescription(int position) {
return arrListDescription.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
private static class ViewHolder {
public TextView tvSettingsTagSeper;
public TextView tvSettingsDescription;
public CheckBox cbSettings;
public ImageView ivSettings;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
int type;
type = getItemViewType(position);
System.out.println("getView " + position + " " + convertView
+ " type = " + type);
if (convertView == null) {
viewHolder = new ViewHolder();
switch (type) {
case TYPE_ITEM:
convertView = layInflater.inflate(
R.layout.layout_settings_list, null);
viewHolder.tvSettingsTagSeper = (TextView) convertView
.findViewById(R.id.tvSettingsTag);
viewHolder.tvSettingsDescription = (TextView) convertView
.findViewById(R.id.tvSettingsDescription);
viewHolder.cbSettings = (CheckBox) convertView
.findViewById(R.id.cbSettings);
viewHolder.ivSettings = (ImageView) convertView
.findViewById(R.id.ivSettings);
break;
case TYPE_SEPARATOR:
convertView = layInflater.inflate(R.layout.layout_seperator,
null);
viewHolder.tvSettingsTagSeper = (TextView) convertView
.findViewById(R.id.tvSeperator);
break;
}
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
if (type == 0) {
viewHolder.tvSettingsTagSeper.setTextSize(
TypedValue.COMPLEX_UNIT_PX,
(int) (ScreenCalculationActivity.heightScreen * 0.035));
viewHolder.tvSettingsTagSeper.setPadding(0,
(int) (ScreenCalculationActivity.heightScreen * 0.01),
(int) (ScreenCalculationActivity.widthScreen * 0.15), 0);
viewHolder.tvSettingsDescription.setPadding(0,
(int) (ScreenCalculationActivity.heightScreen * 0.001),
(int) (ScreenCalculationActivity.widthScreen * 0.15), 0);
RelativeLayout.LayoutParams layParams = (RelativeLayout.LayoutParams) viewHolder.ivSettings
.getLayoutParams();
layParams.setMargins(0, 0,
(int) (ScreenCalculationActivity.widthScreen * 0.045), 0);
viewHolder.ivSettings.getLayoutParams().height = (int) (ScreenCalculationActivity.heightScreen * 0.06);
viewHolder.ivSettings.getLayoutParams().width = (int) (ScreenCalculationActivity.heightScreen * 0.06);
viewHolder.ivSettings.setLayoutParams(layParams);
layParams = (RelativeLayout.LayoutParams) viewHolder.cbSettings
.getLayoutParams();
layParams.setMargins(0, 0,
(int) (ScreenCalculationActivity.widthScreen * 0.03), 0);
viewHolder.cbSettings.setLayoutParams(layParams);
layParams = (RelativeLayout.LayoutParams) viewHolder.tvSettingsTagSeper
.getLayoutParams();
layParams.setMargins(
(int) (ScreenCalculationActivity.widthScreen * 0.05), 0, 0,
0);
viewHolder.tvSettingsTagSeper.setLayoutParams(layParams);
layParams = (RelativeLayout.LayoutParams) viewHolder.tvSettingsDescription
.getLayoutParams();
layParams.setMargins(
(int) (ScreenCalculationActivity.widthScreen * 0.05), 0, 0,
0);
viewHolder.tvSettingsDescription.setLayoutParams(layParams);
} else {
RelativeLayout.LayoutParams layParams = (RelativeLayout.LayoutParams) viewHolder.tvSettingsTagSeper
.getLayoutParams();
layParams.setMargins(
(int) (ScreenCalculationActivity.widthScreen * 0.02), 0, 0,
0);
viewHolder.tvSettingsTagSeper.setLayoutParams(layParams);
}
if (type == 0) {
if (position == 1 || position == 2 || position == 3
|| position == 6 || position == 9 || position == 10
|| position == 12 || position == 14 || position == 16
|| position == 18 || position == 22) {
viewHolder.ivSettings.setVisibility(View.VISIBLE);
} else {
viewHolder.cbSettings.setVisibility(View.VISIBLE);
}
viewHolder.cbSettings.setFocusable(false);
viewHolder.cbSettings.setFocusableInTouchMode(false);
viewHolder.ivSettings.setFocusable(false);
viewHolder.ivSettings.setFocusableInTouchMode(false);
if (arrListDescription.get(position) == null) {
RelativeLayout.LayoutParams layParams = (RelativeLayout.LayoutParams) viewHolder.tvSettingsTagSeper
.getLayoutParams();
layParams.addRule(RelativeLayout.CENTER_VERTICAL);
viewHolder.tvSettingsTagSeper.setLayoutParams(layParams);
}
viewHolder.tvSettingsTagSeper.setText(arrListData.get(position));
viewHolder.tvSettingsDescription.setText(arrListDescription
.get(position));
} else {
viewHolder.tvSettingsTagSeper.setText(arrListData.get(position));
}
return convertView;
}
}
This is my layout layout_settings_list :-
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/relLaySettings"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="#+id/tvSettingsTag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scrollHorizontally="true"
android:singleLine="true"
android:textColor="#color/White"
android:textStyle="bold"
android:typeface="monospace" />
<TextView
android:id="#+id/tvSettingsDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/tvSettingsTag"
android:textColor="#color/LightGrey" />
<CheckBox
android:id="#+id/cbSettings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:visibility="gone" />
<ImageView
android:id="#+id/ivSettings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:src="#drawable/dropdown2"
android:visibility="gone" />
This is my layout layout_seperator :-
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#color/LightGrey"
android:clickable="false">
<TextView
android:id="#+id/tvSeperator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textStyle="bold"
android:textColor="#color/Black" />
I don't see the exact source of your problem, but I see several little issues that might cause what you see.
Several backing lists
The purpose of your adapter is to convert a list of objects into a list a views in the screen. Having multiple lists containing the data does not seem right to me. It shouldn't cause actual problems, but it could make your mental representation harder.
You should probably have objects encapsulating the properties you will reflect in your views:
public class Item {
}
public class DataItem extends Item {
String data;
String description;
}
public class SeparatorItem extends Item {
String description;
}
And instead of 2 lists and 1 set, you would only have one list:
ArrayList<Item> list;
Then you could base your item type on the actual class of the item (instead of using a Set of indices):
#Override
public int getItemViewType(int position) {
Item item = list.get(position);
if (item instanceof DataItem)
return TYPE_ITEM;
else
return TYPE_SEPARATOR;
}
Suspicious if statement
if (position == 1 || position == 2 || position == 3
|| position == 6 || position == 9 || position == 10
|| position == 12 || position == 14 || position == 16
|| position == 18 || position == 22) {
viewHolder.ivSettings.setVisibility(View.VISIBLE);
} else {
viewHolder.cbSettings.setVisibility(View.VISIBLE);
}
Two problems with this if:
it hardcodes some positions. There should be a field in your backing objects (the Items of your list) that represents the fact that ivSettings or cbSettings (one view or the other) should be displayed.
you use setVisibility(View.VISIBLE), but where do you set the other view to View.GONE? They are not initialized, so the visibility of ivSettings or cbSettings (the one you don't make visible) will be the one of your convertView.
Suggested corrected if:
Item item = list.get(position);
if (item.shouldDisplayIvSettings()) {
viewHolder.ivSettings.setVisibility(View.VISIBLE);
viewHolder.cbSettings.setVisibility(View.GONE);
} else {
viewHolder.ivSettings.setVisibility(View.GONE);
viewHolder.cbSettings.setVisibility(View.VISIBLE);
}
Constants declarations VS use of integer literals
You had it right when you declared constants for your types of elements:
private static final int TYPE_ITEM = 0;
private static final int TYPE_SEPARATOR = 1;
But then you don't use the constants, but integer literals in your ifs:
if (type == 0) {
This is not much, but could cause errors when your code evolves. It should be replaced by the appropriate constant:
if (type == TYPE_ITEM) {
The code is as follows:
It should not be clickable at position 0. I used view.setClickable(false) but it was of no use.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// Get the textview and imageview
int c = ctx.getResources().getColor(R.color.expandlist);
int selectedColor = ctx.getResources().getColor(R.color.colorexpand);
View view = super.getView(position, convertView, parent);
TextView textView = (TextView) view.findViewById(R.id.cust_view);
Typeface tf = Typeface.createFromAsset(ctx.getAssets(),
"fonts/segoeuil.ttf");
ImageView imageView = (ImageView) view.findViewById(R.id.productImage);
textView.setTypeface(tf);
//Check the position so that proper values are assigned
if (position == 0) {
File sdCardRoot = Environment.getExternalStorageDirectory();
File yourDir = new File(sdCardRoot, "ProductImages" + "/Main" + ID
+ ".jpg");
if (yourDir.getPath() != null) {
Bitmap picture = BitmapFactory.decodeFile(yourDir.getPath());
int width = picture.getWidth();
int height = picture.getWidth();
float aspectRatio = (float) width / (float) height;
int newWidth = 149;
int newHeight = (int) (150 / aspectRatio);
picture = Bitmap.createScaledBitmap(picture, newWidth,
newHeight, true);
imageView.setImageBitmap(picture);
textView.setPadding(13, 200, 0, 0);
}
textView.setClickable(false);
imageView.setClickable(false);
view.setClickable(false);
} else {
if (position == 1) {
if (ProductItemListFragment.videos) {
textView.setBackgroundColor(selectedColor);
} else {
textView.setBackgroundColor(Color.TRANSPARENT);
}
} else if (position == 2) {
if (ProductItemListFragment.images) {
textView.setBackgroundColor(selectedColor);
} else {
textView.setBackgroundColor(Color.TRANSPARENT);
}
} else if (position == 3) {
if (ProductItemListFragment.story) {
textView.setBackgroundColor(selectedColor);
} else {
textView.setBackgroundColor(Color.TRANSPARENT);
}
} else {
textView.setBackgroundColor(Color.TRANSPARENT);
}
// hide/remove image
imageView.setVisibility(View.GONE); // or GONE, as you wish
textView.setPadding(13, 10, 0, 15);
}
return view;
}