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.
I have recycler-view with items in it and can be scrolled vertically. Currently what i achieved is items are added one after another like a list. By i need to place them side by side.
Like the image below
And my output is
My recycler-view setup code:
topicAdapter = new TopicAdapter(topicList, getActivity());
topicListView.setLayoutManager(new LinearLayoutManager(getActivity()));
topicListView.setAdapter(topicAdapter);
and adapter code is:
public class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.CategoryViewHolder> {
private List<Topic> topicList;
Context context;
public TopicAdapter(List<Topic> topicList, Context context) {
this.topicList = topicList;
this.context = context;
}
#Override
public CategoryViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//inflate the layout file
View groceryProductView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_suggested_topics, parent, false);
CategoryViewHolder holder = new CategoryViewHolder(groceryProductView);
return holder;
}
#Override
public void onBindViewHolder(CategoryViewHolder holder, final int position) {
holder.txtview.setText(topicList.get(position).getName());
}
#Override
public int getItemCount() {
return topicList.size();
}
public class CategoryViewHolder extends RecyclerView.ViewHolder {
TextView txtview;
public CategoryViewHolder(View view) {
super(view);
txtview = view.findViewById(R.id.titleView);
}
}
}
I can suggest you with a simple solution but, you cant achieve complete requirement with this code. You'll get side by side.
Replace
topicListView.setLayoutManager(new LinearLayoutManager(getActivity()));
with
topicListView.setLayoutManager(new GridLayoutManager(getActivity(), 3));
// 3 denotes the number of rows per column
You can do this using Google's latest design component ChipGroup
Else you can use Flexbox-Layout by showing your tags in Grid Layout.
If you wish to go for Flexbox-Layout, check answer of avik
Add This
topicListView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL,false));
Use StaggeredGridLayoutManager for recyclerview
I think a good way to do this is by using Material Choice Chips, you can learn how to use them here. You can then use a ChipGroup to group them and allow them to flow across multiple lines.
However, to solve your question at hand, you can use a GridLayoutManager and then supply a SpanSizeLookup.
I have a class which handles character selection from a RecyclerView and everything works, but I want to update text of the elements in the NavigationView header with the right information. So far I've been trying to use ButterKnife to solve this, but with no success. However, I've been able to make it work in this way:
private ImageView mImageView;
private TextViewTitle mTextViewName;
private TextViewRegular mTextViewTitle;
private static View mHeaderView;
public void setHeaderView(View headerView) {
mHeaderView = headerView;
selectedCharacterInstance.setHeaderViewElements();
}
private void setHeaderViewElements() {
mImageView = mHeaderView.findViewById(R.id.selected_character_info1);
mTextViewName = mHeaderView.findViewById(R.id.selected_character_info2);
mTextViewTitle = mHeaderView.findViewById(R.id.selected_character_info3);
}
I pass the headerView from the MainActivity. I don't like this approach, but I might be wrong since I am fairly new to Android programming. Is this the right approach? Is there a way to solve this using ButterKnife? (I tried ButterKnife but the ImageView and the TextViews were always null)
I use also Butter Knife for my navigation header. For the header, I create a view holder:
protected static class HeaderViewHolder {
#BindView(R.id.user_name)
protected TextView mUserNameTxt;
#BindView(R.id.user_email)
protected TextView mUserEmailTxt;
HeaderViewHolder(View view) {
ButterKnife.bind(this, view);
}
}
Then in my activity's onCreate method:
View header = mNavigationView.getHeaderView(0);
mHeaderViewHolder = new HeaderViewHolder(header);
mHeaderViewHolder.mUserEmailTxt.setText(userEmail);
This allows me to use mHeaderViewHolder like any other RecyclerView holder.
EDIT: I've solved this issue, if interested, please take a look at my answer to see how I did it!
I am currently working in Android Studio. I have a ListView that I populate with several items. Within each of these items is an ImageButton that has a "+" as the image. What I want to do is, whenever that image is clicked (not the entire ListView item, just the image), I want that image of "+" to become another image. Any help would be appreciated, as this has been an ongoing issue for a while!
Here is the current code that I attempt to use to achieve this:
final ImageButton movieSeen = (ImageButton convertView.findViewById(R.id.movieWatched);
movieSeen.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
movieSeen.setImageResource(R.drawable.ic_check_circle_black_24dp);
}
});
Currently this does update the image that I click correctly, BUT it also updates images that are not yet rendered on the screen, so when I scroll the list view down, other objects are also changed to ic_check_circle_black_24dp.
What I want is pretty straightforward, I just don't know how to achieve it. I just want to click an ImageButton, that's inside an item on a ListView, and have that ImageButton change its image resource.
Here is my custom array adapter as requested!
private class MovieScrollAdapter extends ArrayAdapter<Movie> {//custom array adapter
private Context context;
private List<Movie> movies;
public MovieScrollAdapter(Context context, List<Movie> movies){
super(context, -1, movies);
this.context = context;
this.movies = movies;
if(this.movies.isEmpty()){//if no results were returned after all processing, display a toast letting the user know
Toast.makeText(getApplicationContext(), R.string.no_matches, Toast.LENGTH_SHORT).show();
}
}
#Override
public View getView(final int position, View convertView, ViewGroup parent){
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context.
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.movie_layout, parent, false);
}
TextView title = (TextView) convertView.findViewById(R.id.title);
title.setText(movies.get(position).getTitle());
TextView plot = (TextView) convertView.findViewById(R.id.plot);
plot.setText(movies.get(position).getPlot());
TextView genre = (TextView) convertView.findViewById(R.id.genre);
genre.setText(movies.get(position).getGenre());
TextView metaScore = (TextView) convertView.findViewById(R.id.metascore);
if(movies.get(position).getMetaScore() == -1){//if the metaScore is set to -1, that means movie has not been rated, which by inference means it is not yet released
metaScore.setText(R.string.movie_not_released);
metaScore.setTextSize(TypedValue.COMPLEX_UNIT_SP, 9.5f);//smaller text so it fits without breaking anything
metaScore.setTextColor(getColor(R.color.colorAccent));
} else {
metaScore.setText(" " + Integer.valueOf(movies.get(position).getMetaScore()).toString() + " ");//using white space for minor formatting, instead of altering margins each time this is rendered
metaScore.setTextSize(TypedValue.COMPLEX_UNIT_SP, 25);
//setting up a "highlighted" background to achieve metacritic square effect
Spannable spanText = Spannable.Factory.getInstance().newSpannable(metaScore.getText());
spanText.setSpan(new BackgroundColorSpan(getColor(R.color.metaScore)), 3, 7, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
metaScore.setText(spanText);
metaScore.setTextColor(getColor(android.R.color.primary_text_dark));
}
ImageView image = (ImageView) convertView.findViewById(R.id.imageView);
new ImageDownloadTask((ImageView)image).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, movies.get(position).getPosterURL());//because there are several images to load here, we let these threads run parallel
title.setOnClickListener(new View.OnClickListener() {//setting up a simple onClickListener that will open a link leading to more info about the movie in question!
#Override
public void onClick(View v) {
Uri uri = Uri.parse(movies.get(position).getMovieURL());
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
});
final ImageButton movieSeen = (ImageButton) convertView.findViewById(R.id.movieWatched);
movieSeen.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
movieSeen.setImageResource(R.drawable.ic_check_circle_black_24dp);
}
});
return convertView;
}
}
The problem is on a ListView, the views are being reused to save memory and avoid creating a lot of views, so when you change a view it keeps the state while it's being reused to show another item.
For example, you have 100 elements, you touch the first element ImageButton and that button is changed. Maybe on the screen there are 5 elements of the list showing, and you changed the first one. But if you scroll to the element number 15 the system is not creating 15 views, is taking the first one you clicked before and is changing the content.
So, you are expecting to have a view with a "+" ImageButton icon but you see another icon, that's because you must keep the view state inside a model object and set the state every time 'getView' is called.
Post your list adapter to see how is implemented.
UPDATE:
Now I see your adapter implementation I suggest you to add an int field inside Movie class to save the resource id you want to show on the ImageButton. Then inside the onClickListener you must set to this field the resource you want to show on the view when its clicked, and call notifyDataSetChanged(). After that you must do inside getView():
movieSeen.setImageResource(movies.get(position).getButtonImageResource());
Use RecyclerView and set the OnItemClickListener on your ImageButton within your view holder.
This already answered question should help.
The adapted code below is coming from this nice tutorial. Using ReciclerView with an adapter like this will solve your concern.
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private ArrayList<String> mDataset;
public class ViewHolder extends RecyclerView.ViewHolder {
public ImageView imageView;
public TextView txtHeader;
public ViewHolder(View v) {
super(v);
txtHeader = (TextView) v.findViewById(R.id.xxx);
imageView = (ImageView) v.findViewById(R.id.yyy);
}
}
public MyAdapter(ArrayList<String> myDataset) {
mDataset = myDataset;
}
#Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.rowlayout, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
// Replace the contents of a view (invoked by the layout manager)
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
final String name = mDataset.get(position);
holder.txtHeader.setText(mDataset.get(position));
holder.imageView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// Do here what you need to change the image content
}
});
holder.itemView.setBackground(....); // Initialize your image content here...
}
//...
}
Here is my suggestion to achieve what you want :
Create An Interface in your adapter :
public interface YourInterface{
void selectedImage(int position,ImageView iamgeView);
}
Create variable interface in your adapter that you just created :
private YourInterface yourInterface;
and make your adapter constructor like this :
public YourAdapterConstructor(YourInterface yourInterface){
this.yourInterface = yourInterface;
}
in your ImageView onClickListener :
final ImageButton movieSeen = (ImageButton) convertView.findViewById(R.id.movieWatched);
movieSeen.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
yourInterface.selectedImage(position, imageView);
}
});
and then finally in your class activity, Implements YourInterface and change you ImageView there :
#Override
public void selectedImage(final int position,final ImageView imageView) {
//change your image view here
}
I'd like to thank everyone for their support. Unfortunately, with the way my code is written (rather messily and without much regard for what my professors taught me), I was unable to get most of these solutions to work. I did however, find a solution that falls in line with my own framework that I've had going into this. Unfortunately I could not redo my entire adapter method, or implement various interfaces that would cause me to have to rewrite a huge chunk of code for something seemingly trivial.
So, if anyone finds themselves in this situation in the future, here is my solution:
In the Movie class, I add a boolean value that represents my values, along with some getters and setters:
private boolean watchedStatus;
public boolean hasSeen() {return watchedStatus;}
public void toggleWatchedStatus(){
watchedStatus = !watchedStatus;
}
In the getView method, I simply get a reference to the ImageButton, and then based on the boolean value returned by "hasSeen," I set the ImageResource to one of two states:
#Override
public View getView(final int position, View convertView, ViewGroup parent){
ImageButton movieSeen = (ImageButton) convertView.findViewById(R.id.movieSeen);
if(movies.get(position).hasSeen()){
movieSeen.setImageResource(R.drawable.ic_check_circle_black_24dp);
} else {
movieSeen.setImageResource(R.drawable.ic_add_circle_black_24dp);
}
}
Next, I override the OnClickListener, and make it so that whenever the button is clicked, the boolean value in the Movie.java class is toggled. The key here was using the ArrayAdapter's method "notifyDataSetChanged()" This completes the process, and lets the ListView know that it should update itself:
final ImageButton movieSeenForClick = (ImageButton) convertView.findViewById(R.id.movieSeen);
movieSeen.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//movieSeenForClick.setImageResource(R.drawable.ic_check_circle_black_24dp);
movies.get(position).toggleWatchedStatus();
System.out.println(movies.get(position).hasSeen() + " ------- position: " + position);
notifyDataSetChanged();
}
});
Thanks again for the time taken to provide information, a lot of it really did steer me int he right direction, I just had to use the information correctly with the way my code was structured.
I am working on xamarin android apps. I used recycler view for displaying the images in each row. Now for each item image there is some link which I need to redirect. How can I achieve this onclick action for each image in recycler view.
Please help me in this regard.
Thanks
When creating a recycler view, you need to create a RecyclerView adapter which (among other things) implements methods for creating and binding a viewholder to the item in the recycler view. Somewhere in your code (oftentimes within this recycler view adapter class), you need to define the viewholder that you will use for your recyclerview items. This is where you should assign the onClickListener to your imageView.
Here is an example of a viewholder definition that I think may help you:
public class YourViewHolder extends RecyclerView.ViewHolder {
protected ImageView yourImage;
public YourViewHolder(View v) {
super(v);
final View theView = v;
yourImage = (ImageView) v.findViewById(R.id.yourImage);
yourImage.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// image clicked... do something
}
});
}
}
Let me know if you need more information on how to set up the recycler view adapter class (I have assumed that you already have done this, but I could be mistaken).