Removing placeholder view from RecyclerView on notifyDataSetChanged? - java

I'm working on a project where I have a RecyclerView displaying items from a SQLiteDatabase. So I have build a custom adapter that uses a cursor instead of a list of data so I can update it straight from database queries. Additionally, I have a "placeholder" view that gets put into the recyclerView if the cursor is empty (no rows returned) that basically says "nothing here, add something by clicking +".
My issue is that when I add an item in that state (when the RecyclerView is initially empty) and call notifyDataSetChanged, the RecyclerView attempts to recycle the placeholder view instead of inflating the correct view item and I get a NullPointerException because I am attempting to update UI elements that were never initialized.
Here's what my adapter looks like now:
public class TextItemListAdapter extends RecyclerView.Adapter<TextItemListAdapter.ViewHolder> {
private List<TextItem> dataList;
private Cursor cursor;
private Context context;
private boolean empty = false;
private TextItemDataLayer dataLayer;
/**
* Constructor
* #param c Context
* #param curs The cursor over the data (Must be a cursor over the TextItems table)
*/
public TextItemListAdapter(Context c, Cursor curs) {
context = c;
dataLayer = new TextItemDataLayer(c);
dataLayer.openDB();
if (curs != null) {
switchCursor(curs);
}
empty = isEmpty(cursor);
}
/**
* ViewHolders populate the RecyclerView. Each item in the list is contained in a VewHolder.
*/
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
View parent;
TextItem textItem;
TextView location;
TextView text;
TextView time;
/**
* Create the viewholder for the item
* #param itemView the view contained in the viewHolder
*/
public ViewHolder(View itemView) {
super(itemView);
this.parent = itemView;
if (!empty) {
itemView.setClickable(true);
itemView.setOnClickListener(this);
}
location = (TextView) itemView.findViewById(R.id.textItem_item_title);
text = (TextView) itemView.findViewById(R.id.textItem_item_text);
time = (TextView) itemView.findViewById(R.id.textItem_item_time);
}
/**
* Handle a click event on an TextItem
* #param v
*/
#Override
public void onClick(View v) {
Intent i = new Intent(context, EditTextItemActivity.class);
i.putExtra("textItem", textItem);
context.startActivity(i);
}
}
/**
* Creation of the viewholder
* #param parent
* #param i
* #return
*/
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int i) {
View view;
if (empty) {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.text_item_list_item_empty, parent, false);
} else {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.text_item_list_item, parent, false);
}
ViewHolder vh = new ViewHolder(view);
return vh;
}
/**
* When the viewholder is bound to the recyclerview, initialize things here
* #param holder
* #param position
*/
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
if (!empty) {
if (!cursor.moveToPosition(position)) {
throw new IllegalStateException("couldn't move cursor to position " + position);
}
holder.textItem = dataLayer.buildTextItem(cursor);
holder.location.setText(holder.textItem.getLocation().getName());
holder.text.setText(holder.textItem.getMessage());
holder.time.setText(holder.textItem.getTime());
}
}
/**
* Get the number of items in the dataset
*
* If the dataset is empty, return 1 so we can imflate the "empty list" viewHolder
* #return
*/
#Override
public int getItemCount() {
if (empty) {
return 1;
}
return cursor.getCount();
}
/**
* switch the existing cursor with a new one
* #param c
*/
public void switchCursor(Cursor c) {
// helper method in TextItemDataLayer that compares two cursors' contents
if (TextItemDataLayer.cursorsEqual(cursor, c)) {
c.close();
return;
}
if (cursor != null) {
cursor.close();
}
cursor = c;
cursor.moveToFirst();
empty = isEmpty(cursor);
notifyDataSetChanged();
}
/**
* Check to see if the cursor is empty
*
* #param c the cursor to check
* #return
*/
private boolean isEmpty(Cursor c) {
return (c == null || c.getCount() == 0);
}
}
Some things I've tried that I haven't gotten to work:
notifyItemRemoved(0); if the list changes from empty to not empty
Changing switchCursor to compare the two cursors and call either notifyDataSetChanged() or notifyItemRemoved(0) like this
// helper method in TextItemDataLayer that compares two cursors' contents
if (OterDataLayer.cursorsEqual(cursor, c)) {
c.close();
return;
}
if (!isEmpty(c) && isEmpty(cursor)) {
empty = false;
notifyItemRemoved(0);
// or notifyDataSetChanged();
}
if (cursor != null) {
cursor.close();
}
cursor = c;
cursor.moveToFirst();
empty = isEmpty(cursor);
notifyDataSetChanged();
I've also considered having the empty placeholder view not be in the RecyclerView but instead be in the parent view to avoid the issue altogether, although I think it's better in terms of the overall structure of the app for it to be handled in the RecyclerView.

Your problem is simply in understanding how RecyclerView.Adapter works. Please have a look at this for clarification pertaining to your problem.
My issue is that when I add an item in that state (when the RecyclerView is initially empty) and call notifyDataSetChanged, the RecyclerView attempts to recycle the placeholder view instead of inflating the correct view item
Your are right about that, and it's happening because you did not override getItemViewType (int position), and the RecyclerView has no way to know that there are actually two different ViewHolder types or states.
I suggest following the above linked answer, refactor your code to provide two different ViewHolders, and then implement getItemViewType (int position). Something along these lines:
#Override
public int getItemViewType(int position) {
if(empty)
return EMPTY_VIEW_TYPE_CODE;
return REGULAR_VIEW_TYPE_CODE;
}

Related

How can I stop my recyclerView view type from changing after scrolling?

Inside my RecyclerView Adapter class I have 2 view types to display the results of my query:
#Query("SELECT l.log_id, l.junction_id ,l.date, l.workout_id, l.total_weight_lifted,
l.reps, l.set_number FROM log_entries_table
AS l LEFT JOIN exercise_workout_junction_table AS ej
ON ej.exercise_workout_id = l.junction_id WHERE ej.exercise_id = :exerciseID
ORDER BY substr(l.date, -4) DESC, substr(l.date, -7) DESC, (l.date) DESC")
LiveData<List<Log_Entries>> getAllExerciseHistoryLogs(int exerciseID);
The first view type is used to display all logEntries in which the date is unique:
The second view type is to display the rest of the logEntries which share the same date as the above:
My current code works fine, however every time I scroll down and the recyclerView updates, all the log-Entries with 'unique' dates (which should use the first viewType) get changed to display the second view type.
How can I stop my recyclerView view type from changing?
Before scroll -> After Scroll
RecyclerView Adapter
public class ExerciseHistoryAdapter2 extends RecyclerView.Adapter {
private OnItemClickListener listener;
private List<Log_Entries> allLogEntries = new ArrayList<>();
private List<String> uniqueDates = new ArrayList<>();
String logEntryDate;
public void setExercises(List<Log_Entries> allLogEntries) {
this.allLogEntries = allLogEntries;
notifyDataSetChanged();
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view;
if (viewType == 0) {
view = layoutInflater.inflate(R.layout.exercise_history_item, parent, false);
return new ViewHolderOne(view);
}
view = layoutInflater.inflate(R.layout.exercise_history_item_two, parent, false);
return new ViewHolderTwo(view);
}
#Override
public long getItemId(int position) {
return allLogEntries.get(position).getLog_id();
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
logEntryDate = allLogEntries.get(position).getDate();
if (uniqueDates.contains(logEntryDate)) {
// bindViewHolder2
ViewHolderTwo viewHolderTwo = (ViewHolderTwo) holder;
viewHolderTwo.textViewWeight.setText(String.valueOf(allLogEntries.get(position).getTotal_weight_lifted()));
viewHolderTwo.textViewReps.setText(String.valueOf(allLogEntries.get(position).getReps()));
} else {
uniqueDates.add(logEntryDate);
//bind viewholder1
ViewHolderOne viewHolderOne = (ViewHolderOne) holder;
viewHolderOne.textViewDate.setText(allLogEntries.get(position).getDate());
viewHolderOne.textViewWeight.setText(String.valueOf(allLogEntries.get(position).getTotal_weight_lifted()));
viewHolderOne.textViewReps.setText(String.valueOf(allLogEntries.get(position).getReps()));
}
}
#Override
public int getItemCount() {
return allLogEntries.size();
}
#Override
public int getItemViewType(int position) {
logEntryDate = allLogEntries.get(position).getDate();
if (uniqueDates.contains(logEntryDate)) {
return 1;
}
return 0;
}
class ViewHolderOne extends RecyclerView.ViewHolder {
private TextView textViewDate;
private TextView textViewWeight;
private TextView textViewReps;
public ViewHolderOne(#NonNull View itemView) {
super(itemView);
textViewDate = itemView.findViewById(R.id.textView_dateH);
textViewWeight = itemView.findViewById(R.id.textView_weightH);
textViewReps = itemView.findViewById(R.id.textView_repss);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = getAdapterPosition();
if (listener != null && position != RecyclerView.NO_POSITION) {
listener.onItemClick(allLogEntries.get(position));
}
}
});
}
}
class ViewHolderTwo extends RecyclerView.ViewHolder {
private TextView textViewWeight;
private TextView textViewReps;
public ViewHolderTwo(#NonNull View itemView) {
super(itemView);
textViewWeight = itemView.findViewById(R.id.textView_weightH2);
textViewReps = itemView.findViewById(R.id.textView_repss2);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = getAdapterPosition();
if (listener != null && position != RecyclerView.NO_POSITION) {
listener.onItemClick(allLogEntries.get(position));
}
}
});
}
}
public interface OnItemClickListener {
void onItemClick(Log_Entries log_entries);
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
}
Your getItemViewType and onBindViewHolder has some issues.
#Override
public int getItemViewType(int position) {
// type 0 = with date header
// type 1 = without date header
// if list is sorted chronologically
if (position == 0) {
return 0
}
String currentDate = allLogEntries.get(position).getDate();
String previousDate = allLogEntries.get(position - 1).getDate();
if (currentDate.equals(previousDate)) {
return 1
} else {
return 0
}
}
The first item will always have the header since the list is sorted chronologically. For the rest of the items, you need to check whether the date for the current item is the same as the previous item. Based on that condition you return the type.
You do not need to manage a list of unique dates. You have shared mutable states between the functions that are being called multiple times and are not synced. Just delete these and the references of them from onBindViewHolder
private List<String> uniqueDates = new ArrayList<>();
String logEntryDate;
I think to set holder.setIsRecyclable(false); would solve the issue, because the recycler view will then no longer recycle the items... But this is not a good solution for long lists.
EDIT:
I reviewd your code in onBindViewHolder()...
I think the problem comes with uniqueDates.add(logEntryDate); and that the onBindViewHolder method is called multiple times.
This is how the recycler view proceeds:
the first item in list will be unique because uniqueDates is empty. Therefore it will be added to the list.
the other items will be added correctly, as you see in your first screenshot
when you scroll down, the onBindViewHolder method will be executed for every item again
because the uniqueDates list already contains the first date, as it was added in step one, this item will now recognized as not-unique one
the wrong list will be displayed after scrolling as you see in your second screenshot
SOLUTION:
You will have to add a logic which identifies unique dates in another way, which is independet of the onBindViewholder method
OR
you would have to add code, that removes dates on a specific point, so that the list identifies the first item every time as unique and not just the first time.

Change text of TextView, then use layout programmatically?

I have the following layout:
<?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"
android:text="Select an option" />
I use the above layout to set the default text of a spinner button, using this class:
/**
* Decorator Adapter to allow a Spinner to show a 'Nothing Selected...' initially
* displayed instead of the first choice in the Adapter.
*/
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;
/**
* 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
*/
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
*/
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.
}
/**
* View to show in Spinner with Nothing Selected
* Override this to do something dynamic... e.g. "37 Options Found"
* #param parent
* #return
*/
protected View getNothingSelectedView(ViewGroup parent) {
return layoutInflater.inflate(nothingSelectedLayout, parent, false);
}
#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 nothingSelectedDropdownLayout == -1 ?
new View(context) :
getNothingSelectedDropdownView(parent);
}
// Could re-use the convertView if possible, use setTag...
return adapter.getDropDownView(position - EXTRA, null, parent);
}
/**
* Override this to do something dynamic... For example, "Pick your favorite
* of these 37".
* #param parent
* #return
*/
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.
}
}
I initialize the above class like this:
NothingSelectedSpinnerAdapter myAdapter = new NothingSelectedSpinnerAdapter(spinnerAdapter, R.layout.layout_pasted_above, getContext());
myAdapter.setAdapter(spinnerAdapter);
However, I want to be able to change the text of the above layout programmatically.
How would I achieve this?
-
Apparently StackOverflow needs more words for me to submit this post, but I have no other important details to add, so I'm just adding this text in so I can actually submit this.
Get the id of your widget and set it to a textview variable in your java class
TextView variable= (TextView)findViewById(R.id.label);
then set the text to whatever string you want
variable.setText("insert your text");
You have to get the reference of TextView here after inflating the method.
protected View getNothingSelectedView(ViewGroup parent) {
View nothingSelectedView = layoutInflater.inflate(nothingSelectedLayout,
parent, false);
TextView labelText =(TextView)nothingSelectedView.findViewById(R.id.label);
labelText.setText("Set You Text Here");
return nothingSelectedView;
}
Just create a field, and expose a public method.
protected View getNothingSelectedView(ViewGroup parent) {
View nothingSelectedView = layoutInflater.inflate(nothingSelectedLayout,
parent,
false);
textViewField = (TextView) nothingSelectedView.findViewById(R.id.label);
return nothingSelectedView;
}
// Expose public method
public void changeText(String text) {
textViewField.setText(text);
}
Call it from wherever
adapter.changeText("new text");

Android SparseBooleanArray ArrayIndexOutOfBounds

My app has an SparseBooleanArray to store selected entries in a ListAdapter.
The array is initialized as empty in the constructor.
private SparseBooleanArray mSelectedItemsIds;
public TaskArrayAdapter(#NonNull Context context, #NonNull List<Task> objects) {
super(context, R.layout.item_task, objects);
mSelectedItemsIds = new SparseBooleanArray();
Log.d("Booleansize", String.valueOf(mSelectedItemsIds.size()));
}
Now in the getView method I check, whether the current position is in the array or not:
if (mSelectedItemsIds.valueAt(position)) {
convertView.setBackgroundColor(Color.BLUE);
convertView.getBackground().setAlpha(100);
} else {
The mSelectedItemsIds is not altered during my testing. I am inserting entries to the list. After exactly 12 entries it crashes on mSelectedItemsIds.valueAt(position) with ArrayIndexOutOfBound at 11.
On another device it crashes after 14 inserts.
The size of the array is always 0 before every check.
How is that possible?
Here are all relevant parts of the adapter class (I cut out On Click listener, that are never triggered)
public class TaskArrayAdapter extends ArrayAdapter<Task> {
// Array with all selectedIds
private SparseBooleanArray mSelectedItemsIds;
public TaskArrayAdapter(#NonNull Context context, #NonNull List<Task> objects) {
super(context, R.layout.item_task, objects);
mSelectedItemsIds = new SparseBooleanArray();
Log.d("Booleansize", String.valueOf(mSelectedItemsIds.size()));
}/**
* Called for the ListView to get the view at position
* #param position
* #param convertView
* #param parent
* #return
*/
#NonNull
#Override
public View getView(final int position, #Nullable View convertView, #NonNull ViewGroup parent) {
// Get the data item for this position
final Task task = getItem(position);
// Check if an existing view is being reused, otherwise inflate the view
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_task, parent, false);
}
// Lookup view for data population
TextView tvDescription = (TextView) convertView.findViewById(R.id.tvDescription);
TextView tvDueDate = (TextView) convertView.findViewById(R.id.tvDueDate);
CheckBox cbDone = (CheckBox) convertView.findViewById(R.id.cbDone);
// Populate the data into the template view using the data object
tvDescription.setText(task.description);
if (task.dueDate != null) {
tvDueDate.setText(task.dueDate.toString());
} else {
tvDueDate.setText(R.string.no_due_date);
}
cbDone.setChecked(task.done);
cbDone.setTag(position);
convertView.setTag(position);
// Set listeners
cbDone.setOnClickListener(checkBoxListener);
convertView.setOnClickListener(viewClickListener);
convertView.setOnLongClickListener(viewLongClickListener);
Log.d("Booleansize", String.valueOf(mSelectedItemsIds.size()));
// check if the current item is selected for background color
if (mSelectedItemsIds.valueAt(position)) {
convertView.setBackgroundColor(Color.BLUE);
convertView.getBackground().setAlpha(100);
} else {
convertView.setBackgroundColor(Color.TRANSPARENT);
convertView.getBackground().setAlpha(255);
}
// Return the completed view to render on screen
return convertView;
}/**
* Change the isSelected value
* #param position
*/
public void toggleSelection(int position) {
selectView(position, !mSelectedItemsIds.get(position));
}
/**
* Completely forget the current selection
*/
public void removeSelection() {
mSelectedItemsIds = new SparseBooleanArray();
notifyDataSetChanged();
}
/**
* Set the select view to the given value
* #param position
* #param value
*/
public void selectView(int position, boolean value) {
if (value) {
mSelectedItemsIds.put(position, value);
} else {
mSelectedItemsIds.delete(position);
}
notifyDataSetChanged();
}
/**
* get number of selected entries
* #return
*/
public int getSelectedCount() {
return mSelectedItemsIds.size();
}
public SparseBooleanArray getSelectedIds() {
return mSelectedItemsIds;
}
You probably meant to use SparseBooleanArray.get method:
boolean get (int key)
Gets the boolean mapped from the specified key, or false if no such mapping has been made.
Use like so:
if (mSelectedItemsIds.get(position)) {
convertView.setBackgroundColor(Color.BLUE);
convertView.getBackground().setAlpha(100);
} else {
....
}
valueAt(int position) on the other hand takes position within the array, that's from 0 to size() - 1. You can use that to iterate over all values within the array.

ImageView onClick method

I have an activity MyActivityListView and her xml file is this http://pastebin.com/B2thprhT.
The activity does nothing to special , when we click on a row calls another activity passing elements. The rows of this xml are build using this other xml file http://pastebin.com/H321gRP8.
In the activity associated ad the first xml file on the onCreate() method its present this row: listView.setAdapter(new MyListViewAdapter(getApplicationContext())); and the MyListViewAdapter is made in this way
public class MyListViewAdapter extends BaseAdapter {
List<TestTable> testTableArrayList;
private LayoutInflater inflater;
private Context context;
private DatabaseHelper databaseHelper;
public MyListViewAdapter(/*ArrayList<TestTable> testTableArrayList,*/ Context context) {
// this.testTableArrayList = testTableArrayList;
this.context = context;
this.databaseHelper = new DatabaseHelper(context);
this.testTableArrayList = databaseHelper.getValues();
inflater = ( LayoutInflater )context.
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/**
* Ritorna il numero degli elementi presenti nell'ArrayList
* #return numero di elementi nell'ArrayList
*/
#Override
public int getCount() {
// return 0;
return testTableArrayList.size();
}
/**
* Restituisce l'oggetto in posizione position
* #param position = posizione dell'oggetto che verra' restituito
* #return oggetto in posizione position
*/
#Override
public Object getItem(int position) {
// return null;
return testTableArrayList.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View row = inflater.inflate(R.layout.custom_single_row_list_view, null);
TextView zero = (TextView) row.findViewById(R.id.txtIdOnCustomView);
TextView uno = (TextView) row.findViewById(R.id.txtPrimoValoreOnCustomView);
TextView due = (TextView) row.findViewById(R.id.txtSecondoValoreOnCustomView);
TextView tre = (TextView) row.findViewById(R.id.txtTerzoValoreOnCustomView);
TestTable temp = testTableArrayList.get(position);
zero.setText(String.valueOf(temp.getId()));
uno.setText(temp.getVal1());
due.setText(temp.getVal2());
tre.setText(temp.getVal3());
// TextView uno
return row;
}
}`
As you see, in the second xml file is present an ImageView that is clickable, but I don't understand where insert the method associated at the click because if I insert that method on MyListViewAdapter class they return an error, if I insert on ActivityListView class they return error.
Thank at all and forgive me for my english, it's the first time I ask something in english :)
Just use findViewById() to get a reference to your ImageView like you did with the TextViews and set an OnClickListener in code. Do this in the same place where you also get your TextViews.

Getting position of View in onCreateViewHolder

I am using a RecyclerView with a single row layout with an ImageView and a TextView.
I want to implement a OnClickListener for the View and not for seperate ViewHolder objects. How can i get the position of the view in the Adapter?
Right now i'm deleting comments on click, but i cannot select the clicked View. I added a TODO in the appropriate line.
public class CommentAdapter extends RecyclerView.Adapter<CommentAdapter.ViewHolder> {
/** List of Comment objects */
private List<Comment> mCommentList;
/** Database with Comment objects */
private CommentsDataSource mDataSource;
/**
* Construcutor for CommentAdapter
*
* #param commentList List of Comment objects
* #param dataSource Database with Comment objects
*/
public CommentAdapter(List<Comment> commentList, CommentsDataSource dataSource) {
this.mCommentList = commentList;
this.mDataSource = dataSource;
}
/**
* Add Comment objects to RecyclerView
*
* #param position The position where the Comment object is added
* #param comment Comment Object
*/
public void add(int position, Comment comment) {
mCommentList.add(position, comment);
notifyItemInserted(position);
}
/**
* Remove Comment objects from RecyclerView
*
* #param comment Comment Object
*/
public void remove(Comment comment) {
int position = mCommentList.indexOf(comment);
// Avoid double tap remove
if (position != -1) {
mCommentList.remove(position);
mDataSource.deleteComment(comment);
notifyItemRemoved(position);
}
}
#Override
public ViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.single_line_row, parent, false);
view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO get position
remove(mCommentList.get(getItemCount() - 1));
}
});
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
final Comment comment = mCommentList.get(position);
holder.comment.setText(comment.getComment());
}
#Override
public int getItemCount() {
return mCommentList.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
/** ImageView icon */
public ImageView icon;
/** TextView comment */
public TextView comment;
/**
* Constructor for ViewHolder
*
* #param itemView Layout for each row of RecyclerView
*/
public ViewHolder(final View itemView) {
super(itemView);
icon = (ImageView) itemView.findViewById(R.id.icon);
comment = (TextView) itemView.findViewById(R.id.comment);
}
}
}
You cannot use the position parameter of onBindViewHolder in a callback.
If a new item is added above, RecyclerView will not rebind your item so the position is obsolete.
Instead, RecyclerView provides a getAdapterPosition method on the ViewHolder.
#Override
public ViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.single_line_row, parent, false);
final ViewHolder holder = new ViewHolder(view);
view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
final int position = holder.getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
remove(mCommentList.get(position));
}
}
});
return holder;
}
I've added position != RecyclerView.NO_POSITION check because when item is removed, RecyclerView will fade out the View so it may still be clicked by the user but its adapter position will return NO_POSITION.
you can create a method to update position in your class.
in my case I need to attach watcher and get the position to update arraylist. here is the example:
class DodolWatcher bla bla {
private var position: Int = 0
fun updatePosition(pos:Int)
{
position = pos
}
override fun onTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {
Log.d("test", position.toString())
}
}
and in your onCreateViewHolder you can attach watcher to edittext
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RVPaymentMethodAdapter.ViewHolder {
....
bla bla
....
theWatcher = DodolWatcher() <-- this is the trick
amount.addTextChangedListener(theWatcher)
}
and you will able to update position in your bindViewHolder like this:
override fun onBindViewHolder(viewHolder: RVPaymentMethodAdapter.ViewHolder, position: Int) {
theWatcher.updatePosition(viewHolder.adapterPosition) <-- this is the trick
}
override getItemViewType method in your Adapter:
#Override
public int getItemViewType(int position) {
//...
return position;
}
Now viewType is position
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
int position=viewType; //position
//your code
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recursive, parent, false);
return new ViewHolder(view);
}
for example
public class BrandBtnAdapter extends RecyclerView.Adapter<BrandBtnAdapter.MyViewHolder>
{
//............
#Override
public int getItemViewType(int position)
{
//...
return position;
}
#Override
public BrandBtnAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
int position = viewType; //position
final View itemView = mInflater.inflate(mResource, parent, false);
return new BrandBtnAdapter.MyViewHolder(itemView);
}
}

Categories