Well as the title says, I need a listener that gives me the position of the item which was recycled.
So far I only found how to get the holder of the recycled item:
override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder)
}
and I don't get why it doesn't also return the damn position of it
You might want to use either of the methods according to your use case to get the position of the view to be recycled to create the new view.
override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder)
holder.layoutPosition
holder.absoluteAdapterPosition
holder.bindingAdapterPosition
}
holder.layoutPosition: Returns the position of the ViewHolder in terms of the latest layout pass.
holder.absoluteAdapterPosition: Returns the Adapter position of the item represented by this ViewHolder with respect to the RecyclerView's Adapter.
holder.bindingAdapterPosition Returns the Adapter position of the item represented by this ViewHolder with respect to the Adapter that bound it.
You can use the tag of the view to complete
override fun onBindViewHolder(holder: HostingAndAdditionalHolder, position: Int)
{
...
holder.binding.cb.tag = position
...
}
Set up the listener when creating the view. In the code below, I set up the listener for cb.
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): HostingAndAdditionalHolder {
return HostingAndAdditionalSettingsItemViewBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
).run {
cb.setOnClickListener(checkOnClickListener)
HostingAndAdditionalHolder(this)
}
}
This is my listener code
private val checkOnClickListener = View.OnClickListener { view ->
view.tag.let {
if (it is Int) it else null
}?.let {
...
}
}
Related
I'm trying to create a recyclerView, but when I try to reference my textView I get an error that says Unresolved Reference: textViewTitle, I have tried many ways and I could not, I appreciate your wise help.
class RecyclerAdapter(private val userList: ArrayList<Sitios> = ArrayList()) :
RecyclerView.Adapter<RecyclerAdapter.ViewHolder>() {
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.card_view, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
//Unresolved reference: textViewTitle
holder.itemView.textViewTitle.text = userList[position].description
}
override fun getItemCount(): Int = userList.size
holder.itemView is the View you pass into your ViewHolder class when onCreateViewHolder runs. It's just a normal View, and there's no field called textViewTitle in that class, which is what you're trying to reference with holder.itemView.textViewTitle - that's why it's saying the reference is unresolved
What you probably want to do is find a TextView with that ID within itemView's hierarchy, so you need to do
holder.itemView.findViewById<TextView>(R.id.textViewTitle)
or whatever the ID is. But typically, people will put fields in the ViewHolder class instead, set them by doing the lookup once, and then you can just access them like you're trying to do:
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
lateinit var textViewTitle : TextView
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.card_view, parent, false)
// or you could make the TextView a val in the ViewHolder's constructor and just pass it in instead of setting it after creation
val holder = ViewHolder(v)
holder.textViewTitle = v.findViewById<TextView>(R.id.textViewTitle)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// now the reference is a field on the ViewHolder
holder.textViewTitle.text = userList[position].description
}
this is better because onBindViewHolder runs every time a new item scrolls into view, so you don't want to be doing lots of findViewByIds when you can just do it once
If you're doing it the field way, like up there, instead of making the view references constructor parameters (which can get messy when you have a few) I'd write it more like this:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.card_view, parent, false)
return ViewHolder(v).apply {
textViewTitle = v.findViewById(R.id.textViewTitle)
// and any other views you need to set up
}
}
it looks pretty neat, especially with several views!
I have a RecyclerView that shows a list of items.
If there is no item to show, The recyclerview shows one item with a specific view (to tell the user there is no item instead of a white screen).
Within HistoryFragment:
private void initRecyclerView(Boolean isNoResult){
HistoryRecyclerViewAdapter adapter = new HistoryRecyclerViewAdapter(mContext, mRecords, **isNoResult**);
mRecyclerView.setLayoutManager(new LinearLayoutManager(mContext));
mRecyclerView.setAdapter(adapter);
}
Within HistoryRecyclerViewAdapter:
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view;
if(**isEmpty**) {
**view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_listitem_prhistory_empty, parent, false);**
} else {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_listitem_prhistory, parent, false);
}
ViewHolder holder = new ViewHolder(view);
return holder;
}
So, it's possible to remove items one by one, if we click on them.
I would like to set isEmpty to true and refresh the RecyclerView when the dataSet is null.
I already know where to call that method but I really don't know how I can do that? (i.e. refresh the RecyclerView with isEmpty = true so I can display the cell that explain to the user that there is no record anymore).
Don't inflate different view-holders, because when the adapter has no items, not a single one of them will ever be inflated. Instead one can wrap the RecyclerView together with a "no data" Fragment into a ViewFlipper, which then can be switched to the Fragment, when the RecyclerView adapter has no items.
Best practice is to use an empty view outside of RecyclerView but in case you like to do what you want:
1.in onCreateViewHolder only inflate one layout which has empty and item views
on item delete check if your array is empty and then add a null item
then in onBindViewHolder check the model if model is Null visible empty view otherwise show item view
summary:
onBind:
model is null : empty View visible
model is not null: item View visible
use interface to refresh the RecyclerView after remove something like this
public interface RefreshRecyclerView {
public void Refresh();
}
then in activity or fragment implement the interface
Fragment implements RefreshRecyclerView
you will have override method like this
#Override
public void Refresh() {
// set adapter again here
}
then pass the interface to adapter like this
RefreshRecyclerView refresh = (RefreshRecyclerView) this;
yourRecycler.setadapter(refresh);
fially when user clicked on adapter use this
refresh. Refresh();
I have a recycler adapter which has 3 itemviewtypes. No.1 type of view has an initial animation to show the view which I've written under the holder instance check in the OnBindView . it works fine. but when there is any change in the other view types, notifydatasetchanged() is called and the adapter is set again which makes the animation start again.
i tried putting a flag for the animation where it would only animate the first time. is there a smarter way to do this?
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int
viewType) {
mSharedPreferencesManager.setVersionNewFeatureCheck(BuildConfig.VERSION_NAME);
Logger.i(TAG, "onCreateViewHolder, " + viewType);
if (viewType == TYPE_HEADER) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.converation_header_row, parent, false);
return new HeaderViewHolder(v);
} else if(viewType == TYPE_ANNOUNCEMENT) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_new_feature_notification, parent, false);
return new FeatureNotificationViewHolder(v);
}
else{
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.conversation_row, parent, false);
return new ConversationRowViewHolder(v);
}
}
f (holder instanceof FeatureNotificationViewHolder) {
final FeatureNotificationViewHolder featureNotificationViewHolder = (FeatureNotificationViewHolder) holder;
featureNotificationViewHolder.chatNewFeatureNotif.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
final int height = featureNotificationViewHolder.chatNewFeatureNotif.getMeasuredHeight();
featureNotificationViewHolder.chatNewFeatureNotif.setVisibility(View.GONE);
featureNotificationViewHolder.chatNewFeatureNotif.getViewTreeObserver().removeOnPreDrawListener(this);
mOverflowAnimations.showView(featureNotificationViewHolder.chatNewFeatureNotif, height, 6);
return false;
}
});
.notifyDataSetChanged() will notify all of your items inside the RecyclerView, if you don't want everything to reset consider using .notifyItemChanged(int position) method on your adapter instance. You'll need to know exactly the index/position of the item to be notified.
Instead of using notifyDataSetChanged, you should use notifyItemChanged to only change the item that needs to be changed, and not the whole adapter or data set.
This won't lead to the unnecessary animation because you're only changing a specific item. This is also much faster and takes up less processing and rendering power.
You can read more about it here.
I customized RecyclerView by adding separate layouts for header and footer. I created constants to determine the property of header, footer and list item in the adapter class. I also created a ViewHolder pattern and assign the layouts to be shown based on the view type. I fixed the header at zeroth position and footer at last position of the array by override getItemViewType method.
I want to make the footer element clickable, so I assign a setOnClickListener(new View.OnClickListener()) and overwrote onClick(view: View)
My goal is to click on the footer and scrollToPosition 0 or 1 (0 = Header, 1 = first item element).
That's MyAdapter definition:
class MyAdapter(context: Context, data: Array[String]) extends RecyclerView.Adapter[RecyclerView.ViewHolder]
...
override def onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int): Unit = holder match {
...
case holder:FooterViewHolder =>
holder.backToTop.setOnClickListener(new View.OnClickListener() {
override def onClick (view: View) {
backToTop(???)
Toast.makeText (context, "Clicked Footer", Toast.LENGTH_SHORT).show()
}
})
...
}
...
I read I just need to do this: recyclerView.getLayoutManager().scrollToPosition(position)
Unfortunately I can't access the LayoutManager from my Adapter class. Any ideas what I can do?
Another approach
#Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
this.recyclerView = recyclerView;
}
Make constuctor of MyAdapter as follow.
MyAdapter myAdapter=new MyAdapter(context,list,mLayoutManager);
In the case of a ListView if we want to make a particular item selected we use the setSelection method. How do we do this in case of RecyclerView?
Use RecyclerView LayoutManager to scroll item at position
recyclerView.getLayoutManager().scrollToPosition(position)
Check
scrollToPositionWithOffset(int position, int offset)
scrollToPositionWithOffset(5,0);
from LinearLayoutManager
Scroll to the specified adapter position with the given offset from resolved layout start.
pass offset as 0 if you want selection at top
This worked for me
Check
recyclerView.scrollToPosition(cursor.getcount() - 1);
ListView.setSelected() does (at least) two things:
It sets the item in the list to be selected (while removing the selection from another item - if such exists)
It scrolls the list so that the item will be visible on the screen.
To achieve 2. either call scrollToPosition() method of RecyclerView (as indicated by Loser), or call one of the scrolling methods of the LayoutManager object depending on your desired scrolling behavior.
For example,
recyclerView.getLayoutManager().smoothScrollToPosition()
You may want to scroll the minimum so that the selected item shows on the screen.
If so and you are using LinearLayoutManager or GridLayoutManager, you can build such scroll logic based on
findFirstCompletelyVisibleItemPosition() and findLastCompletelyVisibleItemPosition() defined in these classes.
Achieving 1. is more tricky. You may want to use the following recipe:
First define a background color in colors.xml, item_state_selected_color, to be used when an item is selected.
In your onCreateViewHolder() implementation create a StateListDrawalbe and set it as the background of the view.
Say something like this:
public ItemViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
// inflate the item view
View itemView = LayoutInflater.from(viewGroup.getContext()).
inflate(itemResourceId,viewGroup, false);
// create color drawable by a resorce id
ColorDrawable colorDrawableSelected =
new ColorDrawable(resources.getColor(R.color.item_state_selected_color));
// create StateListDrawable object and define its states
StateListDrawable stateListDrawable = new StateListDrawable();
stateListDrawable.addState(new int[]{android.R.attr.state_selected}, colorDrawableSelected);
stateListDrawable.addState(StateSet.WILD_CARD, null);
// set the StateListDrawable as background of the item view
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) {
itemView.setBackgroundDrawable(stateListDrawable);
}
else {
itemView.itemView.setBackground(stateListDrawable);
}
// create view holder object providing it with the item view
return new YourViewHolder(itemView);
}
In YourAdapter object (or elsewhere) save a variable, mCurrentSelectedPosition (probably initialized to -1) that holds the current selected position.
Still in the adapter, define handler for clicks on recycler view items, depending on your click logic. For example:
void onItemClick(int position) {
YourViewHolder yourViewHolder;
int oldSelectedPosition = mCurrentSelectedPosition;
if (position != mCurrentSelectedPosition) {
mCurrentSelectedPosition = position;
if (oldSelectedPosition != -1) {
yourViewHolder = findViewHolderForPosition(oldSelectedPosition);
yourViewHolder.itemView.setSelected(false);
}
yourViewHolder = findViewHolderForPosition(mCurrentSelectedPosition);
yourViewHolder.itemView.setSelected(true);
}
}
Next, in the constructor of YourViewHolder set listener to clicks on the item:
public YourViewHolder(View itemView,YourAdapter adapter) {
mAdapter = adapter;
// ... other code here ...
itemView.setOnClickListener(this);
}
Still in YourViewHolder override the onClick() method to delegate handling to the adapter. like this
#Override
public void onClick(View v) {
mAdapter.onItemClick(getPosition());
}
Now there is just last problem to solve - we need to keep track of the selected item with respect to recycling.
#Override
public void onBindViewHolder(YourViewHolder yourViewHolder, int position) {
if (position == mCurrentSelectedPosition) {
yourViewHolder.itemView.setSelected(true);
}
else {
yourViewHolder.itemView.setSelected(false);
}
// ... other code here ...
}
Good luck!