Change text of TextView after inflating layout - java

I have a layout, default_label.xml, like so:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/label"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
Then, I have this class, which basically allows me to set the default button text for a spinner:
public class NothingSelectedSpinnerAdapter implements SpinnerAdapter, ListAdapter {
protected static final int EXTRA = 1;
protected SpinnerAdapter adapter;
protected Context context;
protected int nothingSelectedLayout;
protected int nothingSelectedDropdownLayout;
protected LayoutInflater layoutInflater;
protected TextView label;
/**
* Use this constructor to have NO 'Select One...' item, instead use
* the standard prompt or nothing at all.
*
* #param spinnerAdapter wrapped Adapter.
* #param nothingSelectedLayout layout for nothing selected, perhaps
* you want text grayed out like a prompt...
* #param context Context
*/
public NothingSelectedSpinnerAdapter(
SpinnerAdapter spinnerAdapter,
int nothingSelectedLayout, Context context) {
this(spinnerAdapter, nothingSelectedLayout, -1, context);
}
/**
* Use this constructor to Define your 'Select One...' layout as the first
* row in the returned choices.
* If you do this, you probably don't want a prompt on your spinner or it'll
* have two 'Select' rows.
*
* #param spinnerAdapter wrapped Adapter. Should probably return false for isEnabled(0)
* #param nothingSelectedLayout layout for nothing selected, perhaps you want
* text grayed out like a prompt...
* #param nothingSelectedDropdownLayout layout for your 'Select an Item...' in
* the dropdown.
* #param context Context
*/
public NothingSelectedSpinnerAdapter(SpinnerAdapter spinnerAdapter,
int nothingSelectedLayout, int nothingSelectedDropdownLayout, Context context) {
this.adapter = spinnerAdapter;
this.context = context;
this.nothingSelectedLayout = nothingSelectedLayout;
this.nothingSelectedDropdownLayout = nothingSelectedDropdownLayout;
layoutInflater = LayoutInflater.from(context);
}
#Override
public final View getView(int position, View convertView, ViewGroup parent) {
// This provides the View for the Selected Item in the Spinner, not
// the dropdown (unless dropdownView is not set).
if (position == 0) {
return getNothingSelectedView(parent);
}
return adapter.getView(position - EXTRA, null, parent); // Could re-use
// the convertView if possible.
}
public int getPosition(String value) {
int index = 0;
for (int i = 1; i < getCount() + EXTRA; i++) {
if (getItem(i).equals(value)) {
index = i;
break;
}
}
return index;
}
public TextView getNothingSelectedView(ViewGroup parent) {
label = (TextView) layoutInflater.inflate(nothingSelectedLayout, parent, false);
return label;
}
#Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
// Android BUG! http://code.google.com/p/android/issues/detail?id=17128 -
// Spinner does not support multiple view types
if (position == 0) {
return new View(context);
}
if (adapter.getItem(position-EXTRA).toString().equals("")){
View view = nothingSelectedDropdownLayout == -1 ?
new View(context) :
getNothingSelectedDropdownView(parent);
view.setEnabled(false);
view.setOnClickListener(null);
return view;
}
// Could re-use the convertView if possible, use setTag...
return adapter.getDropDownView(position - EXTRA, null, parent);
}
protected View getNothingSelectedDropdownView(ViewGroup parent) {
return layoutInflater.inflate(nothingSelectedDropdownLayout, parent, false);
}
#Override
public int getCount() {
int count = adapter.getCount();
return count == 0 ? 0 : count + EXTRA;
}
#Override
public Object getItem(int position) {
return position == 0 ? null : adapter.getItem(position - EXTRA);
}
#Override
public int getItemViewType(int position) {
return 0;
}
#Override
public int getViewTypeCount() {
return 1;
}
#Override
public long getItemId(int position) {
return position >= EXTRA ? adapter.getItemId(position - EXTRA) : position - EXTRA;
}
#Override
public boolean hasStableIds() {
return adapter.hasStableIds();
}
#Override
public boolean isEmpty() {
return adapter.isEmpty();
}
#Override
public void registerDataSetObserver(DataSetObserver observer) {
adapter.registerDataSetObserver(observer);
}
#Override
public void unregisterDataSetObserver(DataSetObserver observer) {
adapter.unregisterDataSetObserver(observer);
}
#Override
public boolean areAllItemsEnabled() {
return false;
}
#Override
public boolean isEnabled(int position) {
return position != 0; // Don't allow the 'nothing selected'
// item to be picked.
}
}
What I want to be able to do is change the text of the TextView, something like:
NothingSelectedSpinnerAdapter myAdapter = new NothingSelectedSpinnerAdapter(adapter, R.layout.default_label, getContext());
myAdapter.setText("Custom Label");
mySpinner.setAdapter(myAdapter);
However, when I try adding the following method to the above NothingSelectedSpinnerAdapter class:
public void setText(String text) {
label.setText(text);
}
I get the following error:
java.lang.NullPointerException: Attempt to invoke virtual method 'void
android.widget.TextView.setText(java.lang.CharSequence)' on a null
object reference
What should I change?

Take a look at the following three lines:
NothingSelectedSpinnerAdapter myAdapter = new NothingSelectedSpinnerAdapter(adapter, R.layout.default_label, getContext());
The Adapter is instantiated, label is null
myAdapter.setText("Custom Label");
The TextView label is still null, that's why label.setText(); causes a NullPointerException
mySpinner.setAdapter(myAdapter);
Now the runtime will be able to draw the Spinner items. This involves repeated calling of getView() (at least once for every item which should be drawn). After getView() has been executed for position = 0, you will have assigned a value to label. From now on, you can safely call setText() on it.
If you want to be able to call myAdapter.setText("Custom Label"); whenever you like without having to bother about the inner workings of the Adapter, then you can introduce a field private String mLabelText and implement the method as follows
public void setText(String labelText){
mLabelText = labelText;
if (label != null){
label.setText(labelText);
}
}
One last step: don't forget to set the text right after initializing label
public TextView getNothingSelectedView(ViewGroup parent) {
label = (TextView) layoutInflater.inflate(nothingSelectedLayout, parent, false);
label.setText(mLabelText);
return label;
}

Related

Multiple Spinner in a Recyclerview item

I have a recycler view with 11-row data items. I need to fix two spinners in each row. That the second spinner data should be dependent on the first Spinner data changes. While I'm making new entry data loaded correctly. When I came to edit the first spinner data loading correctly done and already existing data was fixed correctly. But in the second spinner, it shows the data of the last loaded item. i.e. Last row (11th row) spinner data has been set in all the second spinners. How to solve this?
Shown below code is under onBindViewHolder() method in recyclerViewAdapter
fortnightLookupList = gCache.getFortNightLookup();
for(int i = 0; i<fortnightLookupList.size(); i++){
funtioningSpinnerList.add(fortnightLookupList.get(i).getFunctioning());
}
reasoningAdapter =
new ArrayAdapter<>(thisActivity, android.R.layout.simple_spinner_item, funtioningSpinnerList);
reasoningAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
holder.functioningSpinner.setAdapter(reasoningAdapter);
if (entryStatus.equals(Constants.NEW)) {
holder.functioningSpinner.setOnItemSelectedListener(new AdapterView . OnItemSelectedListener () {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
if (pos > 0) {
remoteLocationEntryModelData.get(position).checkList =
funtioningSpinnerList.get(pos);
//loadScoreSpinner(funtioningSpinnerList.get(pos), holder, position);
scoreList.clear();
for (int i = 0; i < fortnightLookupList.size(); i++) {
if (fortnightLookupList.get(i).getFunctioning().equals(funtioningSpinnerList.get(pos)) ) {
scoreList.add(fortnightLookupList.get(i).getScore());
}
}
scoreAdapter =
new ArrayAdapter<>(thisActivity, android.R.layout.simple_spinner_item, scoreList);
scoreAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
holder.scoreSpinner.setAdapter(scoreAdapter);
} else {
remoteLocationEntryModelData.get(position).checkList = "$";
scoreList.clear();
scoreList.add(0, "---- select ----");
scoreAdapter =
new ArrayAdapter<>(thisActivity, android.R.layout.simple_spinner_item, scoreList);
scoreAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
holder.scoreSpinner.setAdapter(scoreAdapter);
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
} else if (entryStatus.equals(Constants.EDIT)) {
if (!TextUtils.isEmpty( String.valueOf( editList.getDescriptions().get(position).getLogId() ) ) ) {
remoteLocationEntryModelData.get(position).logId =
editList.getDescriptions().get(position).getLogId();
}
if (!TextUtils.isEmpty(editList.getDescriptions().get(position).getRemarks())) {
holder.remarksEdt.setText(editList.getDescriptions().get(position).getRemarks());
}
holder.functioningSpinner.setSelection(
funtioningSpinnerList.indexOf(
editList.getDescriptions().get(position).getCheckList()
)
);
holder.functioningSpinner.setOnItemSelectedListener(new AdapterView . OnItemSelectedListener () {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
if (pos > 0) {
remoteLocationEntryModelData.get(position).checkList =
funtioningSpinnerList.get(pos);
//loadScoreSpinner(funtioningSpinnerList.get(pos), holder, position);
scoreList.clear();
for (int i = 0; i < fortnightLookupList.size(); i++) {
if (fortnightLookupList.get(i).getFunctioning()
.equals(funtioningSpinnerList.get(pos))
) {
scoreList.add(fortnightLookupList.get(i).getScore());
}
}
scoreAdapter =
new ArrayAdapter<>(thisActivity, android.R.layout.simple_spinner_item, scoreList);
scoreAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
holder.scoreSpinner.setAdapter(scoreAdapter);
//holder.scoreSpinner.setSelection(scoreList.indexOf(editList.getDescriptions().get(position).getScore()));
} else {
remoteLocationEntryModelData.get(position).checkList = "$";
scoreList.clear();
scoreList.add(0, "---- select ----");
scoreAdapter =
new ArrayAdapter<>(thisActivity, android.R.layout.simple_spinner_item, scoreList);
scoreAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
holder.scoreSpinner.setAdapter(scoreAdapter);
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
Note While I debug it step by step it is working correctly, but not when during run
Current Output
Expected Output
You save scoreList in a global variable and never redefines this value
In holder.functioningSpinner.setOnItemSelectedListener you makes scoreList.clear()
with always same line you set same List reference to all adapters in new ArrayAdapter<>(thisActivity, android.R.layout.simple_spinner_item, scoreList)
with scoreList.clear() you removes data in all adapters and adds new values.
=> is the reason, why you see the data of last item in all list items
Note While I debug it step by step it is working correctly, but not
when during run
Reason for it is:
in debug mode you are slow. there everything comes step by step.
in real execution everything happens in some milliseconds
HOW TO SOLVE
Possibility 1. recreate the list instead of clear data. You make already always new ArrayAdapter, why you store scoreList instance globally?
Possibility 2. Make a holder as own class and everything you do in onBindViewHolder make in a holder class.
Example:
generate base holder class
public abstract class BaseRecyclerBindingHolder<Item> extends RecyclerView.ViewHolder {
public BaseRecyclerBindingHolder(View itemView) {
super(itemView);
}
public abstract void configureView(Item item);
}
and base adapter class
public abstract class BaseRecyclerBindingAdapter<Item, Holder extends BaseRecyclerBindingHolder<? super Item>, ItemBinding extends ViewBinding>
extends RecyclerView.Adapter<Holder> {
// Store a member variable for the items
protected final List<Item> mItems;
/**
* #param items adapter items to show in UI
*/
public BaseRecyclerBindingAdapter(List<Item> items) {
mItems = items;
}
/**
* Usually involves inflating a layout from XML and returning the holder
*
* #param parent container of adapter items
* #param viewType type of item
* #return ViewHolder for adapter item
*/
#NonNull
#Override
public Holder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
// Inflate the custom layout
ItemBinding itemView = getLayoutResource(LayoutInflater.from(parent.getContext()), parent);
return getHolder(itemView, viewType);
}
/**
* Involves populating data into the item through holder
*
* #param holder ViewHolder class to construct UI for item
* #param position item position in adapter
*/
#Override
public void onBindViewHolder(#NonNull Holder holder, int position) {
Item item = mItems.get(position);
holder.configureView(item);
}
/**
* #return count of items in adapter
*/
#Override
public int getItemCount() {
return mItems.size();
}
/**
* gets the type of item of specified position in adapter
*
* #param position item position
* #return default 0
*/
#Override
public abstract int getItemViewType(int position);
/**
* Usually use:
* ItemBinding.inflate(layoutInflater);
* To use parents properties of width and height, make this way:
* ItemBinding.inflate(layoutInflater, parent, false);
*
* #param layoutInflater the inflater to set XMl to view
* #param parent container of adapter items
* #return ViewBinding element with access to all views with IDs in UI
*/
protected abstract ItemBinding getLayoutResource(LayoutInflater layoutInflater, ViewGroup parent);
/**
* based on viewType gets the ViewHolder class for the item
*
* #param viewBinding element with access to all views with IDs in UI
* #param viewType type of item
* #return ViewHolder for adapter item
*/
protected abstract Holder getHolder(ItemBinding viewBinding, int viewType);
/**
* #param position item position
* #return item on specified position in adapter
*/
public Item getItem(int position) {
return mItems.get(position);
}
/**
* #return list of items in adapter
*/
public List<Item> getItems() {
return mItems;
}
}
now generate specific holder for your purpose
public class YourHolderName extends BaseRecyclerBindingHolder<YourHolderItem> {
private final YourHolderBinding binding;
// add here more variable that you needs from adapter
public YourHolderName(YourHolderBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
#Override
public void configureView(YourHolderItem yourHolderItem) {
// here comes what you make i your holder
}
}
and at least define adapter that is always super easy and small
public class YourAdapterName extends BaseRecyclerBindingAdapter<YourHolderItem, YourHolderName, YourHolderBinding> {
/**
* #param yourHolderItem adapter items to show in UI
*/
// add more parameters if needed
public YourAdapterName(List<YourHolderItem> yourHolderItems) {
super(yourHolderItems);
}
#Override
public int getItemViewType(int position) {
return 0;
}
#Override
protected YourHolderBinding getLayoutResource(LayoutInflater layoutInflater, ViewGroup parent) {
return YourHolderBinding.inflate(layoutInflater, parent, false);
}
#Override
protected YourHolderName getHolder(YourHolderBinding viewBinding, int viewType) {
// add more parameters if needed
return new YourHolderName(viewBinding);
}
}

How to add separator at particular position inside recyclerview?

I have a list of taskLists. To show the list I have used recyclerview. I have 1st 3 items as today , tomorrow and later in my list. I want to add one separator after 1st 3 items in recycler view. How can I do this?
Adapter :
public class ListAdapter extends RecyclerView.Adapter<ListAdapter.ItemViewHolder>{
ArrayList<ListData> item;
public static final int TYPE1=1;
Context conext;
public ListAdapter(Context context, ArrayList<ListData> item) {
this.conext=context;
this.item=item;
}
public interface OnItemClickListener {
void onItemClick(ListData listData);
}
#Override
public int getItemCount() {
return item.size();
}
public void remove(int position) {
item.remove(position);
notifyItemRemoved(position);
}
// #Override
// public int getItemViewType(int position) {
// return item.get(position).getExpenseType();// Assume that this return 1 0r 2
// }
#Override
public void onBindViewHolder(ItemViewHolder itemViewHolder,final int i) {
itemViewHolder.listName.setText(item.get(i).getTitle());
}
#Override
public ItemViewHolder onCreateViewHolder(ViewGroup viewGroup,int viewType) {
View itemView = LayoutInflater.
from(viewGroup.getContext()).
inflate(R.layout.list_layout, viewGroup, false);
return new ItemViewHolder(itemView,viewType);
}
public static class ItemViewHolder extends RecyclerView.ViewHolder {
TextView listName;
ItemViewHolder(View itemView, int viewType) {
super(itemView);
listName = (TextView)itemView.findViewById(R.id.listData);
}
}
#Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
}
}
Can anyone help with this how can I put separator after 3 items in list?
Thank you..
You should define 2 types of RecyclerView rows:
...YourRecyclerAdapter extends RecyclerView.Adapter<BaseViewHolder>
public static final int COMMON = 1;
public static final int SEPARATOR = 2;
Override getItemViewType method of your Adapter:
#Override
public int getItemViewType(int position) {
if (position%10 == 0) //each 10 row is separator (change it!)
return SEPARATOR;
else return COMMON;
}
Change onCreateViewHolder method of your Adapter:
#Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == COMMON)
return new ItemViewHolder(LayoutInflater.from(activity).inflate(R.layout.list_layout, parent, false));
else
return new SeparatorHolder(LayoutInflater.from(activity).inflate(R.layout.separator_item, parent, false));
}
#Override
public void onBindViewHolder(BaseViewHolder holder, int position) {
if (getItemViewType(position) == COMMON) {
//do stuff
} else {
}
}
ItemViewHolder extends BaseViewHolder
SeparatorHolder extends BaseViewHolder
BaseViewHolder extends RecyclerView.ViewHolder
One solution is define 2 types of RecyclerView rows (one for normal row and one for separator)
Another solution is you should a Separator View in the bottom of your custom RecycleView row xml
<View
android:id="#+id/separatorView"
android:layout_width="match_parent"
android:layout_height="3dp"
android:visible="gone"
android:background="#android:color/darker_gray"/>
Then in bindViewHolder of your RecyclerView.Adapter, hide the separator in normal row and visible it in separator row
#Override
public void bindViewHolder(ViewHolder holder, int position) {
if(position == separatorPosition){
holder.separatorView.visible = View.VISIBLE;
}else{
holder.separatorView.visible = View.GONE;
}
}
Hope this help
You can create two ViewHolder class and Switch them in onCreateViewHolder. One containing your custom line, and others as your custom list items.
class ViewHolderLine extends RecyclerView.ViewHolder { //contains line
}
class ViewHolderItems extends RecyclerView.ViewHolder { //contains data
}
#Override
public int getItemViewType(int position) {
return item.get(position).getExpenseType();// Assume that this return 1 0r 2
}
#Override
public ItemViewHolder onCreateViewHolder(ViewGroup viewGroup,int viewType) {
switch (viewType) {
case 1: return new ViewHolderLine();
case 2:
View itemView = LayoutInflater.
from(viewGroup.getContext()).
inflate(R.layout.list_layout, viewGroup, false);
return new ItemViewHolder(itemView,viewType);
}
}
You can see the details description here for more info.
if you know that you are only going to add separator in the 1st three items, then you can put a condition based on the position of the item, inside onBindViewHolder.
ps: Please do not forget to add an else block after an if block
I have a recyclerview with section header and variable number of items in each section. The section should have a line separator across entire screen. and items must have a padded line between them.
So, my solution(in kotlin) was having two viewholders, one for header and one for item. in getItemViewType, return the type based on the item.
override fun getItemViewType(position: Int): Int {
if(dataList[position] is HeaderItem)
return Companion.TYPE_HEADER
return Companion.TYPE_ITEM
}
and create the corresponding viewholder in createviewholder, bind accordingly.
Used
parent.getChildViewHolder(parent.getChildAt(i))
to decide the padding(or color or width) for the item separator.
class ItemDivider(context: Context) : RecyclerView.ItemDecoration() {
private var mDivider: Drawable
private val mContext = context
companion object {
private val ATTRS = intArrayOf(android.R.attr.listDivider)
}
init {
val styledAttributes = context.obtainStyledAttributes(ATTRS)
mDivider = styledAttributes.getDrawable(0)
mDivider.setTint(ContextCompat.getColor(mContext, <color>))
styledAttributes.recycle()
}
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State?) {
val right = parent.width - parent.paddingRight
for (i in 0 until parent.childCount - 1) {
val child = parent.getChildAt(i)
val params = child.layoutParams as RecyclerView.LayoutParams
val left = if (parent.getChildViewHolder(child) is HeaderViewHolder) {
0
} else {
child.left + params.leftMargin + mContext.resources.getDimension(<padding_in_dp>).toInt()
}
val top = child.bottom + params.bottomMargin
val bottom = top + mDivider.intrinsicHeight + <divider_height>
mDivider.setBounds(left, top, right, bottom)
mDivider.draw(c)
}
}
}
remember to replace the color, padding, divider height with valid values.
Hope it helps!

Android 5.0 - Add header/footer to a RecyclerView

I spent a moment trying to figure out a way to add a header to a RecyclerView, unsuccessfully.
This is what I got so far:
#Override
protected void onCreate(Bundle savedInstanceState) {
...
layouManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layouManager);
LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
headerPlaceHolder = inflater.inflate(R.layout.view_header_holder_medium, null, false);
layouManager.addView(headerPlaceHolder, 0);
...
}
The LayoutManager seems to be the object handling the disposition of the RecyclerView items. As I couldn't find any addHeaderView(View view) method, I decided to go with the LayoutManager's addView(View view, int position) method and to add my header view in the first position to act as a header.
Aaand this is where things get uglier:
java.lang.NullPointerException: Attempt to read from field 'android.support.v7.widget.RecyclerView$ViewHolder android.support.v7.widget.RecyclerView$LayoutParams.mViewHolder' on a null object reference
at android.support.v7.widget.RecyclerView.getChildViewHolderInt(RecyclerView.java:2497)
at android.support.v7.widget.RecyclerView$LayoutManager.addViewInt(RecyclerView.java:4807)
at android.support.v7.widget.RecyclerView$LayoutManager.addView(RecyclerView.java:4803)
at com.mathieumaree.showz.fragments.CategoryFragment.setRecyclerView(CategoryFragment.java:231)
at com.mathieumaree.showz.fragments.CategoryFragment.access$200(CategoryFragment.java:47)
at com.mathieumaree.showz.fragments.CategoryFragment$2.success(CategoryFragment.java:201)
at com.mathieumaree.showz.fragments.CategoryFragment$2.success(CategoryFragment.java:196)
at retrofit.CallbackRunnable$1.run(CallbackRunnable.java:41)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5221)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
After getting several NullPointerExceptions trying to call the addView(View view) at different moments of the Activity creation (also tried adding the view once everything is set up, even the Adapter's data), I realized I have no idea if this is the right way to do it (and it doesn't look to be).
PS: Also, a solution that could handle the GridLayoutManager in addition to the LinearLayoutManager would be really appreciated!
I had to add a footer to my RecyclerView and here I'm sharing my code snippet as I thought it might be useful. Please check the comments inside the code for better understanding of the overall flow.
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
public class RecyclerViewWithFooterAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int FOOTER_VIEW = 1;
private ArrayList<String> data; // Take any list that matches your requirement.
private Context context;
// Define a constructor
public RecyclerViewWithFooterAdapter(Context context, ArrayList<String> data) {
this.context = context;
this.data = data;
}
// Define a ViewHolder for Footer view
public class FooterViewHolder extends ViewHolder {
public FooterViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Do whatever you want on clicking the item
}
});
}
}
// Now define the ViewHolder for Normal list item
public class NormalViewHolder extends ViewHolder {
public NormalViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Do whatever you want on clicking the normal items
}
});
}
}
// And now in onCreateViewHolder you have to pass the correct view
// while populating the list item.
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v;
if (viewType == FOOTER_VIEW) {
v = LayoutInflater.from(context).inflate(R.layout.list_item_footer, parent, false);
FooterViewHolder vh = new FooterViewHolder(v);
return vh;
}
v = LayoutInflater.from(context).inflate(R.layout.list_item_normal, parent, false);
NormalViewHolder vh = new NormalViewHolder(v);
return vh;
}
// Now bind the ViewHolder in onBindViewHolder
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
try {
if (holder instanceof NormalViewHolder) {
NormalViewHolder vh = (NormalViewHolder) holder;
vh.bindView(position);
} else if (holder instanceof FooterViewHolder) {
FooterViewHolder vh = (FooterViewHolder) holder;
}
} catch (Exception e) {
e.printStackTrace();
}
}
// Now the critical part. You have return the exact item count of your list
// I've only one footer. So I returned data.size() + 1
// If you've multiple headers and footers, you've to return total count
// like, headers.size() + data.size() + footers.size()
#Override
public int getItemCount() {
if (data == null) {
return 0;
}
if (data.size() == 0) {
//Return 1 here to show nothing
return 1;
}
// Add extra view to show the footer view
return data.size() + 1;
}
// Now define getItemViewType of your own.
#Override
public int getItemViewType(int position) {
if (position == data.size()) {
// This is where we'll add footer.
return FOOTER_VIEW;
}
return super.getItemViewType(position);
}
// So you're done with adding a footer and its action on onClick.
// Now set the default ViewHolder for NormalViewHolder
public class ViewHolder extends RecyclerView.ViewHolder {
// Define elements of a row here
public ViewHolder(View itemView) {
super(itemView);
// Find view by ID and initialize here
}
public void bindView(int position) {
// bindView() method to implement actions
}
}
}
The above code snippet adds a footer to the RecyclerView. You can check this GitHub repository for checking the implementation of adding both header and a footer.
Very simple to solve!!
I don't like an idea of having logic inside adapter as a different view type because every time it checks for the view type before returning the view. Below solution avoids extra checks.
Just add LinearLayout (vertical) header view + recyclerview + footer view inside android.support.v4.widget.NestedScrollView.
Check this out:
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:id="#+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<android.support.v7.widget.RecyclerView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutManager="LinearLayoutManager"/>
<View
android:id="#+id/footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
Add this line of code for smooth scrolling
RecyclerView v = (RecyclerView) findViewById(...);
v.setNestedScrollingEnabled(false);
This will lose all RV performance and RV will try to lay out all view holders regardless of the layout_height of RV
Recommended using for the small size list like Nav drawer or settings etc.
I had the same problem on Lollipop and created two approaches to wrap the Recyclerview adapter. One is pretty easy to use, but I'm not sure how it will behave with a changing dataset. Because it wraps your adapter and you need to make yourself sure to call methods like notifyDataSetChanged on the right adapter-object.
The other shouldn't have such problems. Just let your regular adapter extend the class, implement the abstract methods and you should be ready. And here they are:
gists
HeaderRecyclerViewAdapterV1.java usage new HeaderRecyclerViewAdapterV1(new RegularAdapter());
HeaderRecyclerViewAdapterV2.java usage RegularAdapter extends HeaderRecyclerViewAdapterV2
HeaderRecyclerViewAdapterV1
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
/**
* Created by sebnapi on 08.11.14.
* <p/>
* This is a Plug-and-Play Approach for adding a Header or Footer to
* a RecyclerView backed list
* <p/>
* Just wrap your regular adapter like this
* <p/>
* new HeaderRecyclerViewAdapterV1(new RegularAdapter())
* <p/>
* Let RegularAdapter implement HeaderRecyclerView, FooterRecyclerView or both
* and you are ready to go.
* <p/>
* I'm absolutely not sure how this will behave with changes in the dataset.
* You can always wrap a fresh adapter and make sure to not change the old one or
* use my other approach.
* <p/>
* With the other approach you need to let your Adapter extend HeaderRecyclerViewAdapterV2
* (and therefore change potentially more code) but possible omit these shortcomings.
* <p/>
* TOTALLY UNTESTED - USE WITH CARE - HAVE FUN :)
*/
public class HeaderRecyclerViewAdapterV1 extends RecyclerView.Adapter {
private static final int TYPE_HEADER = Integer.MIN_VALUE;
private static final int TYPE_FOOTER = Integer.MIN_VALUE + 1;
private static final int TYPE_ADAPTEE_OFFSET = 2;
private final RecyclerView.Adapter mAdaptee;
public HeaderRecyclerViewAdapterV1(RecyclerView.Adapter adaptee) {
mAdaptee = adaptee;
}
public RecyclerView.Adapter getAdaptee() {
return mAdaptee;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_HEADER && mAdaptee instanceof HeaderRecyclerView) {
return ((HeaderRecyclerView) mAdaptee).onCreateHeaderViewHolder(parent, viewType);
} else if (viewType == TYPE_FOOTER && mAdaptee instanceof FooterRecyclerView) {
return ((FooterRecyclerView) mAdaptee).onCreateFooterViewHolder(parent, viewType);
}
return mAdaptee.onCreateViewHolder(parent, viewType - TYPE_ADAPTEE_OFFSET);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (position == 0 && holder.getItemViewType() == TYPE_HEADER && useHeader()) {
((HeaderRecyclerView) mAdaptee).onBindHeaderView(holder, position);
} else if (position == mAdaptee.getItemCount() && holder.getItemViewType() == TYPE_FOOTER && useFooter()) {
((FooterRecyclerView) mAdaptee).onBindFooterView(holder, position);
} else {
mAdaptee.onBindViewHolder(holder, position - (useHeader() ? 1 : 0));
}
}
#Override
public int getItemCount() {
int itemCount = mAdaptee.getItemCount();
if (useHeader()) {
itemCount += 1;
}
if (useFooter()) {
itemCount += 1;
}
return itemCount;
}
private boolean useHeader() {
if (mAdaptee instanceof HeaderRecyclerView) {
return true;
}
return false;
}
private boolean useFooter() {
if (mAdaptee instanceof FooterRecyclerView) {
return true;
}
return false;
}
#Override
public int getItemViewType(int position) {
if (position == 0 && useHeader()) {
return TYPE_HEADER;
}
if (position == mAdaptee.getItemCount() && useFooter()) {
return TYPE_FOOTER;
}
if (mAdaptee.getItemCount() >= Integer.MAX_VALUE - TYPE_ADAPTEE_OFFSET) {
new IllegalStateException("HeaderRecyclerViewAdapter offsets your BasicItemType by " + TYPE_ADAPTEE_OFFSET + ".");
}
return mAdaptee.getItemViewType(position) + TYPE_ADAPTEE_OFFSET;
}
public static interface HeaderRecyclerView {
public RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent, int viewType);
public void onBindHeaderView(RecyclerView.ViewHolder holder, int position);
}
public static interface FooterRecyclerView {
public RecyclerView.ViewHolder onCreateFooterViewHolder(ViewGroup parent, int viewType);
public void onBindFooterView(RecyclerView.ViewHolder holder, int position);
}
}
HeaderRecyclerViewAdapterV2
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
/**
* Created by sebnapi on 08.11.14.
* <p/>
* If you extend this Adapter you are able to add a Header, a Footer or both
* by a similar ViewHolder pattern as in RecyclerView.
* <p/>
* If you want to omit changes to your class hierarchy you can try the Plug-and-Play
* approach HeaderRecyclerViewAdapterV1.
* <p/>
* Don't override (Be careful while overriding)
* - onCreateViewHolder
* - onBindViewHolder
* - getItemCount
* - getItemViewType
* <p/>
* You need to override the abstract methods introduced by this class. This class
* is not using generics as RecyclerView.Adapter make yourself sure to cast right.
* <p/>
* TOTALLY UNTESTED - USE WITH CARE - HAVE FUN :)
*/
public abstract class HeaderRecyclerViewAdapterV2 extends RecyclerView.Adapter {
private static final int TYPE_HEADER = Integer.MIN_VALUE;
private static final int TYPE_FOOTER = Integer.MIN_VALUE + 1;
private static final int TYPE_ADAPTEE_OFFSET = 2;
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_HEADER) {
return onCreateHeaderViewHolder(parent, viewType);
} else if (viewType == TYPE_FOOTER) {
return onCreateFooterViewHolder(parent, viewType);
}
return onCreateBasicItemViewHolder(parent, viewType - TYPE_ADAPTEE_OFFSET);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (position == 0 && holder.getItemViewType() == TYPE_HEADER) {
onBindHeaderView(holder, position);
} else if (position == getBasicItemCount() && holder.getItemViewType() == TYPE_FOOTER) {
onBindFooterView(holder, position);
} else {
onBindBasicItemView(holder, position - (useHeader() ? 1 : 0));
}
}
#Override
public int getItemCount() {
int itemCount = getBasicItemCount();
if (useHeader()) {
itemCount += 1;
}
if (useFooter()) {
itemCount += 1;
}
return itemCount;
}
#Override
public int getItemViewType(int position) {
if (position == 0 && useHeader()) {
return TYPE_HEADER;
}
if (position == getBasicItemCount() && useFooter()) {
return TYPE_FOOTER;
}
if (getBasicItemType(position) >= Integer.MAX_VALUE - TYPE_ADAPTEE_OFFSET) {
new IllegalStateException("HeaderRecyclerViewAdapter offsets your BasicItemType by " + TYPE_ADAPTEE_OFFSET + ".");
}
return getBasicItemType(position) + TYPE_ADAPTEE_OFFSET;
}
public abstract boolean useHeader();
public abstract RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent, int viewType);
public abstract void onBindHeaderView(RecyclerView.ViewHolder holder, int position);
public abstract boolean useFooter();
public abstract RecyclerView.ViewHolder onCreateFooterViewHolder(ViewGroup parent, int viewType);
public abstract void onBindFooterView(RecyclerView.ViewHolder holder, int position);
public abstract RecyclerView.ViewHolder onCreateBasicItemViewHolder(ViewGroup parent, int viewType);
public abstract void onBindBasicItemView(RecyclerView.ViewHolder holder, int position);
public abstract int getBasicItemCount();
/**
* make sure you don't use [Integer.MAX_VALUE-1, Integer.MAX_VALUE] as BasicItemViewType
*
* #param position
* #return
*/
public abstract int getBasicItemType(int position);
}
Feedback and forks appreciated. I will use HeaderRecyclerViewAdapterV2 by my self and evolve, test and post the changes in the future.
EDIT: #OvidiuLatcu Yes I had some problems. Actually I stopped offsetting the Header implicitly by position - (useHeader() ? 1 : 0) and instead created a public method int offsetPosition(int position) for it. Because if you set an OnItemTouchListener on Recyclerview, you can intercept the touch, get the x,y coordinates of the touch, find the according child view and then call recyclerView.getChildPosition(...)and you will always get the non-offsetted position in the adapter! This is a shortcomming in the RecyclerView Code, I don't see an easy method to overcome this. This is why I now offset the positions explicit when I need to by my own code.
I haven't tried this, but I would simply add 1 (or 2, if you want both a header and footer) to the integer returned by getItemCount in your adapter. You can then override getItemViewType in your adapter to return a different integer when i==0: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)
createViewHolder is then passed the integer you returned from getItemViewType, allowing you to create or configure the view holder differently for the header view: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#createViewHolder(android.view.ViewGroup, int)
Don't forget to subtract one from the position integer passed to bindViewHolder.
You can used this GitHub library allowing to add Header and/or Footer in your RecyclerView in the simplest way possible.
You need to add HFRecyclerView library in your project or you can also grab it from Gradle:
compile 'com.mikhaellopez:hfrecyclerview:1.0.0'
This is a result in image:
EDIT:
If you just want to add a margin at the top and/or bottom with this library: SimpleItemDecoration:
int offsetPx = 10;
recyclerView.addItemDecoration(new StartOffsetItemDecoration(offsetPx));
recyclerView.addItemDecoration(new EndOffsetItemDecoration(offsetPx));
I ended up implementing my own adapter to wrap any other adapter and provide methods to add header and footer views.
Created a gist here: HeaderViewRecyclerAdapter.java
The main feature I wanted was a similar interface to a ListView, so I wanted to be able to inflate the views in my Fragment and add them to the RecyclerView in onCreateView. This is done by creating a HeaderViewRecyclerAdapter passing the adapter to be wrapped, and calling addHeaderView and addFooterView passing your inflated views. Then set the HeaderViewRecyclerAdapter instance as the adapter on the RecyclerView.
An extra requirement was that I needed to be able to easily swap out adapters while keeping the headers and footers, I didn't want to have multiple adapters with multiple instances of these headers and footers. So you can call setAdapter to change the wrapped adapter leaving the headers and footers intact, with the RecyclerView being notified of the change.
recyclerview:1.2.0 introduces ConcatAdapter class which concatenates multiple adapters into a single one. So it allows to create separate header/footer adapters and reuse them across multiple lists.
myRecyclerView.adapter = ConcatAdapter(headerAdapter, listAdapter, footerAdapter)
Take a look at the announcement article. It contains a sample how to display a loading progress in header and footer using ConcatAdapter.
For the moment when I post this answer the version 1.2.0 of the library is in alpha stage, so the api might change. You can check the status here.
my "keep it simple stupid" way ...it waste some resources , i know , but i dont care as my code keep simple
so...
First, add a footer with visibility GONE to your item_layout
<LinearLayout
android:id="#+id/footer"
android:layout_width="match_parent"
android:layout_height="80dp"
android:orientation="vertical"
android:visibility="gone">
</LinearLayout>
Then, set it visible on the last item
public void onBindViewHolder(ChannelAdapter.MyViewHolder holder, int position) {
boolean last = position==data.size()-1;
//....
holder.footer.setVisibility(View.GONE);
if (last && showFooter){
holder.footer.setVisibility(View.VISIBLE);
}
}
do the opposite for header
Based on #seb's solution, I created a subclass of RecyclerView.Adapter that supports an arbitrary number of headers and footers.
https://gist.github.com/mheras/0908873267def75dc746
Although it seems to be a solution, I also think this thing should be managed by the LayoutManager. Unfortunately, I need it now and I don't have time to implement a StaggeredGridLayoutManager from scratch (nor even extend from it).
I'm still testing it, but you can try it out if you want. Please let me know if you find any issues with it.
You can use viewtype to solve this problem, here is my demo:
https://github.com/yefengfreedom/RecyclerViewWithHeaderFooterLoadingEmptyViewErrorView
you can define some recycler view display mode:
public static final int MODE_DATA = 0, MODE_LOADING = 1, MODE_ERROR = 2, MODE_EMPTY = 3, MODE_HEADER_VIEW = 4, MODE_FOOTER_VIEW = 5;
2.override the getItemViewType mothod
#Override
public int getItemViewType(int position) {
if (mMode == RecyclerViewMode.MODE_LOADING) {
return RecyclerViewMode.MODE_LOADING;
}
if (mMode == RecyclerViewMode.MODE_ERROR) {
return RecyclerViewMode.MODE_ERROR;
}
if (mMode == RecyclerViewMode.MODE_EMPTY) {
return RecyclerViewMode.MODE_EMPTY;
}
//check what type our position is, based on the assumption that the order is headers > items > footers
if (position < mHeaders.size()) {
return RecyclerViewMode.MODE_HEADER_VIEW;
} else if (position >= mHeaders.size() + mData.size()) {
return RecyclerViewMode.MODE_FOOTER_VIEW;
}
return RecyclerViewMode.MODE_DATA;
}
3.override the getItemCount method
#Override
public int getItemCount() {
if (mMode == RecyclerViewMode.MODE_DATA) {
return mData.size() + mHeaders.size() + mFooters.size();
} else {
return 1;
}
}
4.override the onCreateViewHolder method. create view holder by viewType
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == RecyclerViewMode.MODE_LOADING) {
RecyclerView.ViewHolder loadingViewHolder = onCreateLoadingViewHolder(parent);
loadingViewHolder.itemView.setLayoutParams(
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, parent.getHeight() - mToolBarHeight)
);
return loadingViewHolder;
}
if (viewType == RecyclerViewMode.MODE_ERROR) {
RecyclerView.ViewHolder errorViewHolder = onCreateErrorViewHolder(parent);
errorViewHolder.itemView.setLayoutParams(
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, parent.getHeight() - mToolBarHeight)
);
errorViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View v) {
if (null != mOnErrorViewClickListener) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mOnErrorViewClickListener.onErrorViewClick(v);
}
}, 200);
}
}
});
return errorViewHolder;
}
if (viewType == RecyclerViewMode.MODE_EMPTY) {
RecyclerView.ViewHolder emptyViewHolder = onCreateEmptyViewHolder(parent);
emptyViewHolder.itemView.setLayoutParams(
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, parent.getHeight() - mToolBarHeight)
);
emptyViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View v) {
if (null != mOnEmptyViewClickListener) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mOnEmptyViewClickListener.onEmptyViewClick(v);
}
}, 200);
}
}
});
return emptyViewHolder;
}
if (viewType == RecyclerViewMode.MODE_HEADER_VIEW) {
RecyclerView.ViewHolder headerViewHolder = onCreateHeaderViewHolder(parent);
headerViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View v) {
if (null != mOnHeaderViewClickListener) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mOnHeaderViewClickListener.onHeaderViewClick(v, v.getTag());
}
}, 200);
}
}
});
return headerViewHolder;
}
if (viewType == RecyclerViewMode.MODE_FOOTER_VIEW) {
RecyclerView.ViewHolder footerViewHolder = onCreateFooterViewHolder(parent);
footerViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View v) {
if (null != mOnFooterViewClickListener) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mOnFooterViewClickListener.onFooterViewClick(v, v.getTag());
}
}, 200);
}
}
});
return footerViewHolder;
}
RecyclerView.ViewHolder dataViewHolder = onCreateDataViewHolder(parent);
dataViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View v) {
if (null != mOnItemClickListener) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mOnItemClickListener.onItemClick(v, v.getTag());
}
}, 200);
}
}
});
dataViewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(final View v) {
if (null != mOnItemLongClickListener) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mOnItemLongClickListener.onItemLongClick(v, v.getTag());
}
}, 200);
return true;
}
return false;
}
});
return dataViewHolder;
}
5.Override the onBindViewHolder method. bind data by viewType
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (mMode == RecyclerViewMode.MODE_LOADING) {
onBindLoadingViewHolder(holder, position);
} else if (mMode == RecyclerViewMode.MODE_ERROR) {
onBindErrorViewHolder(holder, position);
} else if (mMode == RecyclerViewMode.MODE_EMPTY) {
onBindEmptyViewHolder(holder, position);
} else {
if (position < mHeaders.size()) {
if (mHeaders.size() > 0) {
onBindHeaderViewHolder(holder, position);
}
} else if (position >= mHeaders.size() + mData.size()) {
if (mFooters.size() > 0) {
onBindFooterViewHolder(holder, position - mHeaders.size() - mData.size());
}
} else {
onBindDataViewHolder(holder, position - mHeaders.size());
}
}
}
You can use the library SectionedRecyclerViewAdapter to group your items in sections and add a header to each section, like on the image below:
First you create your section class:
class MySection extends StatelessSection {
String title;
List<String> list;
public MySection(String title, List<String> list) {
// call constructor with layout resources for this Section header, footer and items
super(R.layout.section_header, R.layout.section_item);
this.title = title;
this.list = list;
}
#Override
public int getContentItemsTotal() {
return list.size(); // number of items of this section
}
#Override
public RecyclerView.ViewHolder getItemViewHolder(View view) {
// return a custom instance of ViewHolder for the items of this section
return new MyItemViewHolder(view);
}
#Override
public void onBindItemViewHolder(RecyclerView.ViewHolder holder, int position) {
MyItemViewHolder itemHolder = (MyItemViewHolder) holder;
// bind your view here
itemHolder.tvItem.setText(list.get(position));
}
#Override
public RecyclerView.ViewHolder getHeaderViewHolder(View view) {
return new SimpleHeaderViewHolder(view);
}
#Override
public void onBindHeaderViewHolder(RecyclerView.ViewHolder holder) {
MyHeaderViewHolder headerHolder = (MyHeaderViewHolder) holder;
// bind your header view here
headerHolder.tvItem.setText(title);
}
}
Then you set up the RecyclerView with your sections and change the SpanSize of the headers with a GridLayoutManager:
// Create an instance of SectionedRecyclerViewAdapter
SectionedRecyclerViewAdapter sectionAdapter = new SectionedRecyclerViewAdapter();
// Create your sections with the list of data
MySection section1 = new MySection("My Section 1 title", dataList1);
MySection section2 = new MySection("My Section 2 title", dataList2);
// Add your Sections to the adapter
sectionAdapter.addSection(section1);
sectionAdapter.addSection(section2);
// Set up a GridLayoutManager to change the SpanSize of the header
GridLayoutManager glm = new GridLayoutManager(getContext(), 2);
glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
switch(sectionAdapter.getSectionItemViewType(position)) {
case SectionedRecyclerViewAdapter.VIEW_TYPE_HEADER:
return 2;
default:
return 1;
}
}
});
// Set up your RecyclerView with the SectionedRecyclerViewAdapter
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(glm);
recyclerView.setAdapter(sectionAdapter);
I would just add an alternative to all those HeaderRecyclerViewAdapter implementation. CompoundAdapter:
https://github.com/negusoft/CompoundAdapter-android
It is a more flexible approach, since you can create a AdapterGroup out of Adapters. For the header example, use your adapter as it is, along with an adapter containing one item for the header:
AdapterGroup adapterGroup = new AdapterGroup();
adapterGroup.addAdapter(SingleAdapter.create(R.layout.header));
adapterGroup.addAdapter(new MyAdapter(...));
recyclerView.setAdapter(adapterGroup);
It is fairly simple and readable. You can implement more complex adapter easily using the same principle.
Excellent answer by #reaz-murshed shared here . But I dont like the part where datasize is added with +1 and returning Footer View if the end is reached.
It tells that every last element is a Footer View and I had really hard time removing the footer view.
Instead did something like this for my case -
private List<RealResponse> addEmptyLoaderResponse(List<RealResponse> originalList){
if(originalList == null){
originalList= new ArrayList<>();
}
originalList.add(new EmptyRealResponse());
return originalList;
}
private class EmptyRealResponse extends RealResponse{
/**Just an Empty class as placeholder for loader at Footer View
*
*/
}
public void setItems(List<InconcurPostResponse> items) {
this.items = addEmptyLoaderResponse(items);
}
#Override
public int getItemCount() {
return items.size();
}
#Override
public int getItemViewType(int position){
if(this.items.get(position) instanceof EmptyRealResponse){
return ViewTypes.FOOTER_VIEW_TYPE.getViewType();
}
return super.getItemViewType(position);
}
This is way cleaner for me and It loads actual Object into Recycler View. Plus I did get the benefit of removing the Footer View when I dont need it Or If I want to add more Placeholder Footer View.
I know I come late, but only recently I was able to implement such "addHeader" to the Adapter. In my FlexibleAdapter project you can call setHeader on a Sectionable item, then you call showAllHeaders. If you need only 1 header then the first item should have the header. If you delete this item, then the header is automatically linked to the next one.
Unfortunately footers are not covered (yet).
The FlexibleAdapter allows you to do much more than create headers/sections.
You really should have a look: https://github.com/davideas/FlexibleAdapter.

Onclick listner to expandable listview

I have created an expandable listview, but the onclick listener to the child list items could not be attached.
The activity code:
public class MyActivity extends Activity {
private ExpandableListView mExpandableList;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mExpandableList = (ExpandableListView)findViewById(R.id.expandable_list);
ArrayList<Parent> arrayParents = new ArrayList<Parent>();
ArrayList<String> arrayChildren = new ArrayList<String>();
//here we set the parents and the children
for (int i = 0; i < 2; i++){
//for each "i" create a new Parent object to set the title and the children
Parent parent = new Parent();
parent.setTitle("Parent " + i);
arrayChildren = new ArrayList<String>();
for (int j = 0; j < 3; j++) {
arrayChildren.add("Child " + j);
}
parent.setArrayChildren(arrayChildren);
//in this array we add the Parent object. We will use the arrayParents at the setAdapter
arrayParents.add(parent);
}
//sets the adapter that provides data to the list.
mExpandableList.setAdapter(new MyCustomAdapter(MyActivity.this,arrayParents));
}
}
The custom adapter for the lists:
public class MyCustomAdapter extends BaseExpandableListAdapter {
private LayoutInflater inflater;
private ArrayList<Parent> mParent;
public MyCustomAdapter(Context context, ArrayList<Parent> parent){
mParent = parent;
inflater = LayoutInflater.from(context);
}
#Override
//counts the number of group/parent items so the list knows how many times calls getGroupView() method
public int getGroupCount() {
return mParent.size();
}
#Override
//counts the number of children items so the list knows how many times calls getChildView() method
public int getChildrenCount(int i) {
return mParent.get(i).getArrayChildren().size();
}
#Override
//gets the title of each parent/group
public Object getGroup(int i) {
return mParent.get(i).getTitle();
}
#Override
//gets the name of each item
public Object getChild(int i, int i1) {
return mParent.get(i).getArrayChildren().get(i1);
}
#Override
public long getGroupId(int i) {
return i;
}
#Override
public long getChildId(int i, int i1) {
return i1;
}
#Override
public boolean hasStableIds() {
return true;
}
#Override
//in this method you must set the text to see the parent/group on the list
public View getGroupView(int i, boolean b, View view, ViewGroup viewGroup) {
if (view == null) {
view = inflater.inflate(R.layout.list_item_parent, viewGroup,false);
}
TextView textView = (TextView) view.findViewById(R.id.list_item_text_view);
//"i" is the position of the parent/group in the list
textView.setText(getGroup(i).toString());
//return the entire view
return view;
}
#Override
//in this method you must set the text to see the children on the list
public View getChildView(int i, int i1, boolean b, View view, ViewGroup viewGroup) {
if (view == null) {
view = inflater.inflate(R.layout.list_item_child, viewGroup,false);
}
TextView textView = (TextView) view.findViewById(R.id.list_item_text_child);
//"i" is the position of the parent/group in the list and
//"i1" is the position of the child
textView.setText(mParent.get(i).getArrayChildren().get(i1));
//return the entire view
return view;
}
#Override
public boolean isChildSelectable(int i, int i1) {
return true;
}
#Override
public void registerDataSetObserver(DataSetObserver observer) {
/* used to make the notifyDataSetChanged() method work */
super.registerDataSetObserver(observer);
}
}
The parent class:
public class Parent {
private String mTitle;
private ArrayList<String> mArrayChildren;
public String getTitle() {
return mTitle;
}
public void setTitle(String mTitle) {
this.mTitle = mTitle;
}
public ArrayList<String> getArrayChildren() {
return mArrayChildren;
}
public void setArrayChildren(ArrayList<String> mArrayChildren) {
this.mArrayChildren = mArrayChildren;
}
}
What should I do to add onclick listener to the child list items?
Add this after you setAdapter of the Expandable list
mExpandableList.setOnChildClickListener(new OnChildClickListener() {
#Override
public boolean onChildClick(ExpandableListView parent, View v,int groupPosition, int childPosition, long id) {
/* You must make use of the View v, find the view by id and extract the text as below*/
TextView tv= (TextView) v.findViewById(R.id.childTextView);
String data= tv.getText().toString();
return true; // i missed this
}
});
You need to add
ChildClickListener
like this : mExpandableList.setOnChildClickListener
add this line to onCreate method
read here
also this is a good example
Declare your listView with an overridden onChildClick
ExpandableListView listView = getExpandableListView();
listView.setChoiceMode(ExpandableListView.CHOICE_MODE_SINGLE);
listView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
#Override
public boolean onChildClick(ExpandableListView parent, View v,int groupPosition, int childPosition,long id) {
Log.d(TAG,"I got clicked childPosition:["+childPosition+"] groupPosition:["+groupPosition+"] id:["+id+"]");
return true;
}
});

WheelView with custom TextView doesn't highlight currentItem

As a follow up to this question, I have successfully implemented the WheelView with custom TextView (which has custom font for translation purposes).
adapter.setItemResource(R.layout.layout_item);
adapter.setItemTextResource(R.id.text);
The problem now is that the wheelview doesn't highlight current Item as it should.
Adapter code:
/**
* Adapter for string based wheel. Highlights the current value.
*/
private class DateArrayAdapter extends ArrayWheelAdapter<String> {
// Index of current item
int currentItem;
// Index of item to be highlighted
int currentValue;
/**
* Constructor
*/
public DateArrayAdapter(Context context, String[] items, int current) {
super(context, items);
this.currentValue = current;
setTextSize(16);
}
#Override
protected void configureTextView(TextView view) {
super.configureTextView(view);
if (currentItem == currentValue) {
view.setTextColor(getResources().getColor(R.color.holo_blue));
}
view.setTypeface(Typeface.SANS_SERIF);
view.setTextSize(18);
}
#Override
public View getItem(int index, View cachedView, ViewGroup parent) {
currentItem = index;
return super.getItem(index, cachedView, parent);
}
}
In the above code, it does not go to configureTextView at all to highlight the item.
Original source of WheelView.
Okay, I figured it out. In case it helps anybody else, here's the code:
/**
* Adapter for string based wheel. Highlights the current value.
*/
private class DateArrayAdapter extends ArrayWheelAdapter<String> {
// Index of current item
int currentItem;
// Index of item to be highlighted
int currentValue;
/**
* Constructor
*/
public DateArrayAdapter(Context context, String[] items, int current) {
super(context, items);
this.currentValue = current;
setItemResource(R.layout.wheelview_textview);
setItemTextResource(R.id.wheelview_date);
setTextSize(16);
}
protected void configureTextView(CustomTextView view) {
super.configureTextView(view);
if (currentItem == currentValue) {
view.setTextColor(getResources().getColor(R.color.holo_blue));
}
}
#Override
public View getItem(int index, View cachedView, ViewGroup parent) {
currentItem = index;
View view = super.getItem(index, cachedView, parent);
CustomTextView date = (CustomTextView) view.findViewById(R.id.wheelview_date);
configureTextView(date);
return view;
//return super.getItem(index, cachedView, parent);
}
}

Categories

Resources