I want to create an interface between an adapter and a view holder (this view holder is an inner class of another adapter) so that I can update the text view (number). How can I do this?
In detail:
I have two recycle views (Main List Recycler View and Sub List Recycler View horizontally placed as shown in the fig) one having a number (as one of its item) and other having checkbox (as its item).
I have two adapters FilterMainListAdapter and FilterSubListAdapter with view holders FilterMainListViewHolder and FilterSubListViewHolder populating the fields.
When checkboxes are selected in the Sub List Recycler View, I want the corresponding number in the Main List Recycler View to update.
For this, I'm using and Interface.
public interface ChangeFilterMainNumber {
void OnChangeFilterMainNumberListener(int totalCheckedNumber);
}
I've checkbox's onClick method inside the FilterSubListViewHolder and I'm trying to send the total check boxes checked number as follows.
changeFilterMainNumber.OnChangeFilterMainNumberListener(totalCheckedNumber);
After that, I'm implementing ChangeFilterMainNumber interface inside the FilterMainListViewHolder
public class FilterMainListViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener,
ChangeFilterMainNumber {...}
But How can I define this interface inside the FilterSubListAdapter?
changeFilterMainNumber = ???;
[If it is an activity one can define the interface like this changeFilterMainNumber = (ChangeFilterMainNumber) context inside the default constructor of FilterSubListAdapter. But what about a view holder that is an inner class of another adapter?]
or is there a better approach in finding a solution to my problem other than this?
Update: You can take a look at the code here https://github.com/gSrikar/FilterScreen
If I implement the function as you want, I will implement like this:
(This is like an Observer pattern)
class Fragment/Activity implement OnChangeFilterMainNumberListener{
FilterMainListAdapter mainAdapter;
FilterSubListAdapter subAdapter;
void oncreate() {
mainAdapter = new FilterMainListAdapter(this);
}
#Override
void OnChangeFilterMainNumberListener(int totalCheckedNumber) {
.....
// Update data to sub list
}
}
class FilterMainListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
public interface ChangeFilterMainNumber {
void OnChangeFilterMainNumberListener(int totalCheckedNumber);
}
ChangeFilterMainNumber listener;
FilterMainListAdapter(ChangeFilterMainNumber listener) {
this.listener = listener;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
item.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(listener != null) {
listener.OnChangeFilterMainNumberListener(position)
}
}
});
}
}
class FilterSubListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
}
Related
I want to add an onClickListener to items in my RecyclerView. I added the listener in the Holder class as follows:
public class Holder extends RecyclerView.ViewHolder {
TextView firstName;
TextView lastName;
public Holder (final View itemView) {
super(itemView);
firstName = itemView.findViewById(R.id.firstName );
lastName= itemView.findViewById(R.id.lastName);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//Do work
}
}
}
But, I think this will cause the scrolling of the list to be a little jerky and not perfectly smooth specially on old devices.
Question 1:
Is there a better way to do that? Or how can I optimize my code?
Question 2:
I intend to add a dynamically changing variable for each item in the list such as a timer, and I don't want the scrolling to be too slow! How should I update the timers the best way?
Create a member variable for item OnClickListener and set it in Holder's constructor.It will be one listener in your adapter when app is running.
Jerky Scrolling
Since you are using RecyclerView I don't think that you will face any issue with scrolling because RecyclerView inherently comes with ViewHolder Pattern. (In case of Simple listView you have to make ViewHolder to avoid jerky scrolling)
Code improvement
Instead of adding a Listener in ViewHolder, make it a Class variable in your RecyclerView Adapter.
There is a standard way to add a Listener in RecyclerView
Create a listener
interface ClickListener{
void click();
}
implement this listener to Your Activity
YourActivity implements ClickListener{
}
Typecast this listener in Your Adapter
YourAdapter extends RecyclerView.Adapter<YourAdapter.Holder>{
ClickListener listener;
public YourAdapter(Context context)
{
this.context = context;
listener = (ClickListener)context;
}
public class Holder extends RecyclerView.ViewHolder {
TextView firstName;
TextView lastName;
public Holder (final View itemView) {
super(itemView);
firstName = itemView.findViewById(R.id.firstName );
lastName= itemView.findViewById(R.id.lastName);
}
// Item Click listener goes here.
#Override
public void onBindViewHolder(DownLoadViewHolder holder, final int position) {
// Do something
listener.click();
}
}
Just giving you the overview.
You can see THIS for reference.
Edited:
I want know about creating Anonymous Listener in bindViewHolder method cause any performance problem or not for large data set.
Suppose i have a RecyclerView Adapter. And in bindViewHolder method if i set all my listeners Anonymously does this cause any performance problem? Because when user scrolls the RecyclerView it will create lots of Anonymous listeners and set them to the views.
Example:
view.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
}
});
Or i can implements OnClickListener in my ViewHolder class and just add to views.Like
view.setOnClickListener(this);
Here lots of Anonymous Listeners are not created. Does this perform better from previous in performance calculation for large dataset?
Thanks in advance.
RecyclerView will only display few items, ViewHolder will only be created for items that are visible, so even if you have 1000s of items in your adapter, only small fraction of ViewHolders are created.
But you will have to be careful with addListener methods, for most setListener methods, you will be setting same listener again and again when item is recycled, which does not take less then few milliseconds as it only keeps reference of the listener implementation.
But with addListener, you will have to remove old listener before adding new one.
Example of setListener is setClickListener and example of addListener is addTextWatcher
//.. part of adapter
private TextWatcher textWatcher;
public void bindViewHolder(DataViewHolder holder, int index){
// no performance issue
holder.button.setClickListener( .... );
// wrong, this is added everytime
holder.editText.addTextWatcher( .... );
// this is safe...
if(textWatcher != null)
holder.editText.removeTextWatcher(textWatcher);
textWatcher = new TextWatcher(){
// ... implementation
};
holder.editText.addTextWatcher(textWatcher);
}
Basically, you set a OnClickListener in every item of your RecyclerView and "connect" it to your Activity or Fragment. This "connection" is important, so you can have your onItemClick method inside your Activity or Fragment and access the members there.
A minimal implementation would look like this (in a Fragment, but you can also use an Activity):
public class YourFragment extends Fragment implements RecyclerViewAdapter.ItemClickListener {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.fragment_your, container, false);
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recyclerview);
RecyclerViewAdapter recyclerViewAdapter = new RecyclerViewAdapter(activity);
recyclerViewAdapter.setClickListener(this);
recyclerView.setAdapter(recyclerViewAdapter);
return view;
}
#Override
public void onItemClick(View view, int position) {
// do something here
}
}
And the Adapter class
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
private ItemClickListener itemClickListener;
void setClickListener(ItemClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
}
interface ItemClickListener {
void onItemClick(View view, int position);
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
// TextView is an example
final TextView textView;
ViewHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.text);
textView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
if (itemClickListener != null) {
itemClickListener.onItemClick(view, getAdapterPosition());
}
}
}
}
I'm pretty sure the compiler just creates a no-name concrete version of your anonymous class under the hood. That is nearly identical to implementing the interface and providing this as a concrete listener. Realistically, you shouldn't have a performance problem with either.
Just keep in mind that of the fact that an anonymous class holds a reference to the outer class. That might create memory leaks (example: if the outer class is an activity) or just make it so that garbage collection happens all at once instead of small pieces over time. See Implicit and Synthetic Parameters in the oracle documentation for more details on that.
I have anEditTextin eachView, and inonViewRecycled(.)I update the item in theAdapterwith any user input. For the purpose of maintaining the information when scrollingViewsout of and back into view.
However, I realised that with this solution it's a bit of a hassle to retrieve the information that has been modified but not recycled. I solved this by adding eachViewHolderto a separate list in theAdapter(added inonCreateViewHolder(.)), since I couldn't find a simpler way to update theViewson the screen.
Is there any reason not to do this; let theAdapterhave direct access to theViewHolders? I.e. are there any better ways to call a function on allViewscurrently in existance?
Edit:
public class AdapterRv extends Adapter<AdapterRv.MyViewHolder> {
private List<Item> items;
private List<MyViewHoders> viewHolders;
public AdapterRv(List<Item> inItems) {
...
viewHolders = new ArrayList<>();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
private EditText text;
private Item item;
private MyViewHolder(View inView) {
...
}
public void bindItem(Item inItem) {
...
}
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup inParent, int inViewType) {
...
if (!viewHolders.contains(tmpHolder)) {
viewHolders.add(tmpHolder);
}
...
}
....
#Override
public void onViewRecycled(MyViewHolder inViewHolder) {
saveView(inViewHolder.item, inViewHolder.text.getText().toString());
}
private void saveViews() {
for (MyViewHolder tmpViewHolder : viewHolders) {
saveView(tmpViewHolder.item, tmpViewHolder.text.getText().toString());
}
}
private void saveView(Item inItem, String inNewText) {
if (inItem.getText().equals(inNewText)) {
return;
}
inItem.setText(inNewText);
}
public List<Item> fetchTexts() {
saveViews();
return items;
}
}
The purpose of the RecyclerView.Adapter is only to create ViewHolder instances and provide them with data, but it is the RecyclerView itself that requests holders and manages them.
The purpose of the ViewHolder is, as it name suggests, to hold the item's view hierarchy so the Views can be cached and reused by the RecyclerView. Hence, the adapter should only create and bind the correct data to holders, and it is recommended to not store any references to holders inside the adapter, since you only need the reference to holder in the onBindViewHolder(ViewHolder holder, int position method, where it is provided by the RecyclerView. It's also vice-versa, the view holders don't need a reference to the adapter, so your MyViewHolder should be marked static.
If you need to operate on recycler's views, the RecyclerView has plenty of methods in it's API, such as findViewHolderForLayoutPosition(int position), getChildViewHolder(View child) etc. You can also set listeners for observing scroll, item touch, view attach state etc. changes.
See the documentation for the RecyclerView
So, if you need to access and manipulate the views (ie. call the function on all of recycler's views), do it through the RecyclerView and not the adapter, because it's the recycler that manages them - adapter only provides data.
I'm trying to find the best solution to handle OnClick event, which generates by my card's button (see the picture bellow) within GridView.
So as you can see, I have just a normal GridView with cells made of my custom Card.
I just initialize GridView and it's adapter:
mGrid = (GridView) findViewById(R.id.grid);
mAdapter = new ImageTopicsAdapter(..blah blah blah..);
mGrid.setAdapter(mAdapter);
As you probably know I can easily handle OnClick events generated by GridView. But it will work only if I click on the card itself:
mGrid.setOnItemClickListener(..blah blah blah..);
I want to build something similar to this (see code bellow), so I can easily "implement" my Activity to handle my card's button OnClick event:
mGrid.setOnItemButtonClickListener(..blah blah blah..);
What is the best (clean\easy\elegant) way to do this?
Any help is truly appreciated. Alex. P.S. Sorry for my English:)
Since you want to dispatch to your activity, I would recommend exposing a method in the activity and call it directly from your click listener. The shortest (and cleanest from my perspective):
in your Adapter, say ArrayAdapter
define to listen for clicks (to avoid multitude of anonymous listener instances)
dispatch a call directly to your activity (since every view context is an activity)
context above can be treated as your ApplicationActivity only if you didn't manually provide some other context, say application context
private final MyAdapter extends ArrayAdapter implements View.OnClickListener {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// inflate your card then get a reference to your button
View card = ....;
card.findViewById(R.id.YOUR_BUTTON_ID).setOnClickListener(this);
return card;
}
#Override
public void onClick(View view) {
ApplicationActivity activity = (ApplicationActivity) view.getContext();
if (activity != null && !activity.isFinishing()) {
applicationActivity.onCardButtonClick();
}
}
}
// in your ApplicationActivity
public final class ApplicationActivity extends Activity {
...
public void onCardButtonClick() {
// deal with your click
}
}
There are other, textbook options (setting a listener, or activity in your view creation and so forth) but I avoid them since they don't solve absolutely anything.
They just add more dust in your code.
Any View context defined properly points to the activity (since it is a context too) which holds all view structure. This way you can access your activity quick and relatively easy.
BTW Event bus is not a good option since event buses are great for one-to-many relations (one dispatcher, many listeners) but add more complexity when used intensively for one-to-one calls (dispatcher-listener)
Addition for the comment
You can tweak a little the code and rather using the adapter, you can dispatch directly from your cell. In other words rather using the adapter as a delegate, create an anonymous listener and then reach and call the activity directly from your card button click:
public final MyAdapter extends ArrayAdapter {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// inflate your card then get a reference to your button
View card = ....;
card.findViewById(R.id.YOUR_BUTTON_ID).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
ApplicationActivity activity = (ApplicationActivity) view.getContext();
if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
applicationActivity.onCardButtonClick();
}
}
});
return card;
}
}
Addition for the comment - Compound View
To encapsulate all cell logic, you can create a custom view from scratch or use a compound view. The example below is using a compound view:
public class ApplicationActivity extends Activity {
....
public void onCardButtonClick(Cell cell) {
// do whatever you want with the model/view
}
}
// ViewModel instances are used in your adapter
public final class ViewModel {
public final String description;
public final String title;
public ViewModel(String title, String description) {
this.title = title != null ? title.trim() : "";
this.description = description != null ? description.trim() : "";
}
}
public final class Cell extends LinearLayout {
private View button;
private ViewModel model;
// ViewModel is data model and is the list of items in your adapter
public void update(ViewModel model) {
this.model = model;
// update your card with your model
}
public ViewModel getModel() {
return model;
}
#Override
protected void onAttachedToWindow() {
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener {
#Override
public void onClick(View view) {
ApplicationActivity activity = (ApplicationActivity) view.getContext();
if (model != null && activity != null && !activity.isFinishing() && !activity.isDestroyed() {
activity.onCardButtonClick(Cell.this);
}
}
});
}
}
// then your adapter `getView()` needs to inflate/create your compound view and return it
public final MyAdapter extends ArrayAdapter {
private final List<ViewModel> items;
public MyAdapter() {
// update your models from outside or create on the fly, etc.
this.items = ...;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
// inflate - say it is a layout file 'cell.xml'
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.cell);
}
((Cell) convertView).update(items.get(position));
return convertView;
}
}
Adapter should handle this. Generally your Adapter should have method like setOnOptionsClickListener(OnOptionsClickListener listener) assuming that we are talking about ellipsis button.
So in your Activity/Fragment you use following code
public interface OnOptionsClickListener {
void onOptionsClicked(View view, PictureItem item);
}
mAdapter= new MyGridAdapter();
mAdapter.setOnOptionsClickListener(new OnOptionsClickListener() {
public void onClick(View view, PictureItem item) {
//process click
}
});
And following inside Adapter
public void setOnOptionsClickListener(OnOptionsClickListener l) {
mOnOptionsClickListener = l;
}
findViewById(R.id.btn_options).setOnClickListener(new OnClickListener(){
public void OnClick(View view) {
mOnOptionsClickListener.onOptionsClicked(view, currentPictureItem);
}
});
Please notice. You need to declare interface only if you need to have extra parameters in OnClick() method (for example currentPictureItem to get image url or item id). Otherwise, you can use just OnClickListener.
Edit
So here is explanation. Adapter serves like a View-provider for your GridView. It creates views and it configure it basic state. That's why all click listeners should be set in Adapter during views initializing. Moreover, we don't want to have a messy Activity with nested Adapter, but we want to have Adapter as a separate class. This is the reason you will usually need to create additional interface in order to have an access to currentItem object to extract data from.
Looks like nobody knows how to do this. So I found solution myself with help of #Dimitar G. and #Konstantin Kiriushyn. Thank you, guys.
1) I will create my own custom CardView using Compound View system, which will be pretty simple: LinearLayout + ImageView + TextView + Button.
public class TopicCardView extends LinearLayout {
private ImageView mImage;
private Button mButtonMenu;
private TextView mTitle;
public TopicCardView (Context context) {
initializeViews(context);
}
private void initializeViews(Context context) {
LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.topic_card_view, this);
}
private void setTitle(...) {
...
}
private void setImage(...) {
...
}
private void setMenuClickListener(...) {
...
}
// and so on...
}
2) Then I will create method called createListOfGridCardsFromDB(...) in Activity\Fragment. It will generate list (LinkedList) of my custom CardViews (and it will also set titles\images and listeners to CardViews).
3) And then I will pass this generated LinkedList of my CardViews to GridViewAdapter.
This system makes able to use only one Adapter for all my card-grids in app. It also makes able to do nothing with clicks, interfaces, listeners and stuff in Adapter.
Hi I have a app that uses a Recycler view to display a bunch of items. Now I want to run an android test on the list but I don't know how to set a programmatic click on a given item. Can anyone tell me how to achieve this?
You have to implement it from scratch. The RecyclerView lacks for some of the awesome features that a ListView provides by default. Given this, in the adapter you have to declare an interface for the Observer between Fragment/Activity and the RecyclerView.
The correct method to attach this click event is onBindViewHolder. Then in the adapter you keep the reference to the CustomListener or the View.OnClickListener.
Here is a quick example on how to do this:
#Override
public void onBindViewHolder(SearchListAdapter.ViewHolder holder, final int position) {
//Item clicked
holder.mParent.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//Select or deselect
mListener.notify(holder, position);
}
});
}
And the ViewHolder should be like:
public class ViewHolder extends RecyclerView.ViewHolder {
private View mParent;
public ViewHolder(View itemView) {
super(itemView);
mParent = itemView;
}
}
Where mParent is the current row View.