I'm using RecyclerView to create a list, and i was suffering a lot, because i saw different ways to implement an OnClickListener, and comments says like very coupled, very tricky, etc.
And about several days thinking how to implement it, i did this.
On my Fragment, implement the View.OnClickListener and the onClickEvent and when i'm implementing the RecyclerView.Adapter pass the View.OnClickListener reference in the constructor, like this:
public RecyclerViewRepoListAdapter(View.OnClickListener fragmentListener, List<Repos> repoList){
if (repoList != null){
this.reposList = repoList;
this.fragmentListener = fragmentListener;
}
}
After that, when i'm creating the Holder to the different list components, i register the Listener to every (in this case) LinearLayout like this:
#Override
public ListItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//Inflate the corresponding layout
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_repositories, parent, false);
//OnClickListener
itemView.setOnClickListener(fragmentListener);
//Here modify the view like margins, paddings, view size and layout parameters
//return the ViewHolder
return new ListItemViewHolder(itemView);
}
My cuestion is, what is your opinion about this implementation?
And if its ok, i hope this helps another one.
Related
I want to make an android chat app. I made a simple layout for the chat part. I made two layouts, my message and their message. Each of them only have one text view. Imagine that now I want to add a layout for a picture with a caption. In this case, I have to build two layout. One for my message and second for incoming message or their message. But if I do this for every possible message, the number of layout increases and also there are a lot of repetitive things in my layouts.
Like in this layout, you can see that in my and their message there is an image and a caption and these two layouts have similarities. I want to make a layout with image and caption and then use it in my or their message. In this case, I can easily update my layouts and save myself creating lots of layouts for each possible message. What is the best way to do this, or it's impossible or not good?
I know that I can reuse layout in two different ways. first by using include keyword in layout and the second one is to inflate layout. For example like this:
ConstraintLayout item = findViewById(R.id.main);
View child = getLayoutInflater().inflate(R.layout.item, null);
item.addView(child);
What is the best method to reuse layouts and prevent creating layout which only are different slightly and how existing chat applications do the same? I'm using recycler view for chat layout.
Use recycle view instead and use different views for the different views. Check the message type (myMessage,their message,any other) and change your views using an adpater.
In your adapter,
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == 0) {
View v = layoutInflater.inflate(R.layout.chat_row_sender, parent, false);
return new ChatViewHolder2(v);
} else if(viewType ==1){
View v = layoutInflater.inflate(R.layout.chat_row_receiver, parent, false);
return new ChatViewHolder1(v);
}else {
View v = layoutInflater.inflate(R.layout.row_chat_header, parent, false);
return new ChatHeaderViewHolder(v);
}
}
and use 3 viewHolders in your adapter,
public class ChatViewHolder1 extends RecyclerView.ViewHolder {
public ChatViewHolder1(View itemView) {
super(itemView);
...
}
}
public class ChatHeaderViewHolder extends RecyclerView.ViewHolder {
public ChatHeaderViewHolder(View itemView) {
super(itemView);
......
}
}
public class ChatViewHolder2 extends RecyclerView.ViewHolder {
public ChatViewHolder2(View itemView) {
super(itemView);
...
}
}
Use attribute in the xml to reuse layout.
like <Include>
I don't see why it is effective to apply the listener to the holder.
I spent much of the day looking at articles. The topics here on stackoverflow covered the range well.
The choices I found:
Attach to TextView - creates multiple listeners.
Attach to ViewHolder - again creates multiple listeners, unless you create a listener and then use that to attach which you can also do with the TextView.
Attach a onItemTouchListener - a surprisingly complicated way.
What I did not see was just a straight forward implements View.OnClickListener attached to the adapter. If you do this, then generate the one public void onClick(View view), and give the adapter a copy of the RecyclerView and then assign the Adapter (this) as the listener to the item view you have inflated in onCreateViewHolder. Then access the position through the RecyclerView getChildAdapterPosition function. See code snippets.
//create adapter class implementing the on click listener
public class WordListAdapter extends RecyclerView.Adapter<WordListAdapter.WordViewHolder> implements View.OnClickListener {
private final LinkedList<String> mWordList;
private LayoutInflater mInFlater;
private final RecyclerView recyclerView;
//in constructor, pass in RecyclerView created in MainActivity
public WordListAdapter(Context context, LinkedList<String> wordList, RecyclerView rView) {
recyclerView = rView; <----
mInFlater = LayoutInflater.from(context);
this.mWordList = wordList;
}
//implementation of View.OnClickListener makes
#Override
public void onClick(View view) {
//get the position of the item that was clicked
int mPosition = recyclerView.getChildAdapterPosition(view); <----
//other code to do what you want with the list(eg. String element = mWordList.get(mPosition);)
}
//And you set your pointer in the adapter class
#Override
public WordListAdapter.WordViewHolder onCreateViewHolder( #NonNull ViewGroup parent, int viewType) {
View mItemView = mInFlater.inflate(R.layout.wordlist_item, parent, false);
mItemView.setOnClickListener(this); <----
return new WordViewHolder(mItemView, this);
}
It seams to me that this answer is simple, and has the advantage of a single thread on click listener that is all handled in the list adapter.
I guess, I mostly wanted to share my solution on a closed topic but I certainly welcome any suggestions as to why this might not be better. This is actually, my solution to challenge2 in Android fundamentals 04.5 RecyclerView.
The primary reason is is suggested to attach the onClickListener to the view holder as opposed to the adapter itself is because the view is what is being clicked on when a user clicks on an item within the Recyclerview.
The Holder you refer to is a class that contains the view and applying the click listener to this holder allows you to differentiate which view (position / item) is actually being clicked.
It's similar to explaining why you would attach a click listener to the button in a layout versus the entire layout itself. Sure, you could attach the click listener to the entire layout, but a user might intend to click on something else in your view as opposed to the button and you would have no way of distinguishing which one was clicked since your clickListener does not help to identify which.
I have taken an example code for implementing a RecyclerView, but trying to transpose it to work in a Child Fragment in my app.
The code is under 'Create Lists - examples'
Creating Lists and Cards
I run into trouble with my adapter..
public static class ViewHolder extends RecyclerView.ViewHolder {
// each data item is just a string in this case
public TextView mTextView;
public ViewHolder(TextView v) {
super(v);
mTextView = v;
}
}
> #Override
> public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
> int viewType) {
> View v = LayoutInflater.from(parent.getContext())
> .inflate(R.layout.my_text_view, parent, false);
> ViewHolder vh = new ViewHolder(v);
> return vh;
> }
First, it doesn't build, complaining that I am calling constructor ViewHolder with a View, when the constructor is expecting a TextView.
Looking at the code, I agree!
But this is an official example so it must be right?
So what is different to my version compared with the example?
Two things that I can think of...
1)
the layout my_text_view is not given in the example, so I made my own. Did I make it right?
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/t_title"
android:title="title"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
2)
I am calling the adapter from my child fragment, and the example was not written with fragments in mind.
I've probably bitten off more than I can understand there.
Still, working this through, as far as i understand.
The 'parent' coming into my OnCreateViewHolder, is my RecyclerView ?
'v' should be the LinearLayout of my 'my_text_view' ?
'ViewHolder' should end up being a class with property mTextView equaling the TextView from my xml.
I just don't see how I go from v=LinearLayout , to TextView??
Anyone like to help explain to this noob ??
That example is not very good. It looks like two different projects spliced together. It should be something like this:
public class MyAdapter extends RecyclerView.Adapter {
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView mTextView;
public ViewHolder(View v) {
super(v);
mTextView = (TextView) v.findViewById(R.id.t_title);
}
}
#Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.my_text_view, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
}
To explain this segment:
onCreateViewHolder is the method that will be called any time the RecyclerView needs to create a new type of ViewHolder. This may get called only a dozen or so times to get enough Views to cover the screen or it may be called quite a few times if you have many view types.
The parameter parent in this case will be the RecyclerView that accompanies it. Why not just make it RecycerView? Because Google designers decided it should be a ViewGroup. Also it's an Adapter pattern so the only guarantee you're supposed to have is that it's a ViewGroup (i.e., may not be a RecyclerView so you shouldn't build the Adapter with that assumption). Realistically, it will pretty much always be a RecyclerView though.
The parameter int viewType is to tell you what kind of View you're building. This is determined if you override getItemViewType() method of the Adapter. You don't need to worry about this if you only have one type of View though.
For the ViewHolder, this is to basically cache the different types of Views in your layout. These can be ImageView, TextView, etc. These will be "recycled" constantly as the user scrolls so you're not always creating them. That way, you only have to update the Views with the current information. In this case, there's just a title. The ViewHolder passed in bindViewHolder will be where the actual updating happens. This is called all the time, so there's no need to initialize the Views in onCreateViewHolder. Just need to create them.
I've got RecyclerView list where items views are created partially from layout in .xml file (let's say header) and partially programmatically based on list items. I'm using few different itemsViewTypes.
Which approach is better?
1) to generate Views in adapter:
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_item, parent, false);
MyViewHolder holder = new MyViewHolder(view);
createAndAddNewViews(barsHolder);
createAndAddNewViewsBasedOnSomeParams(param1, param2);
// both createAndAddNewViews() and
// createAndAddNewViewsBasedOnSomeParams() are Adapter methods
return holder;
}
2) to generate Views in ViewHolder:
public MyViewHolder(View itemView) {
super(itemView);
... // findViewsById and so on
createAndAddNewViews();
createAndAddNewViewsBasedOnSomeParams(param1, param2);
// both createAndAddNewViews() and
// createAndAddNewViewsBasedOnSomeParams() are ViewHolder methods
}
Of course my examples are simplified just to show the case.
From code reading perspective it's better to have views generation logic inside holders. Every different item has it's own creation code inside one class. I don't have to deal with every single item in Adapter. But I'm not sure if it's ok simply from ViewHolder pattern design and from memory and speed optimization perspective.
I wonder which approach you consider as better and why.
Edit: Does the same applies for modifying view (onBindViewHolder) and methods in Adapter/ViewHolder?
Thanks!
Performance wise it's the same in both the approaches. If you have different ViewHolders for different itemTypes you can do in in ViewHolder to segregate the code belonging to a certain ViewHolder at one place
I'm trying to implement the below image. My first thought was to have everything above the grid layout be the first row of the grid and use SpanSizeLookup to set the span size to the number of columns in the RecyclerView, but this feels like something that will give me a lot of problems.
I've been reading about putting a RecyclerView inside a NestedScrollView, people say it works, but I can't seem to get it to work properly. The scrolling doesn't seem to work right, I can't get the grid to even show up without setting a minHeight, but then it just looks bad.
Is there another option I'm not considering or is one of these the direction I should be going?
What kind of problems are you anticipating from SpanSizeLookup? You can implement it with a few lines as follows (I'd recommend using values from integers.xml for flexibility).
GridLayoutManager glm = new GridLayoutManager(getContext(), 3);
glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override public int getSpanSize(int position) {
return (position == 0) ? 3 : 1;
}
});
If your header layout needs views and fields that your regular layout doesn't have, you'll want to create separate views and tell your adapter about them. Something like this.
#Override
public int getItemViewType(int position) {
if (position == 0)
return TYPE_HEADER;
else
return TYPE_REGULAR;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_HEADER) {
MyHeaderView view = (MyHeaderView) LayoutInflater
.from(parent.getContext())
.inflate(R.layout.my_header_view, parent, false);
return new MyHeaderViewHolder(view);
} else {
MyRegularView view = (MyRegularView) LayoutInflater
.from(parent.getContext())
.inflate(R.layout.my_regular_view, parent, false);
return new MyRegularViewHolder(view);
}
}
An example header view could be like this (you'd call bindTo() from MyHeaderViewHolder).
public final class MyHeaderView extends LinearLayout {
#Bind(R.id.image) ImageView imageView;
#Bind(R.id.title) TextView titleView;
public MyHeaderView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
ButterKnife.bind(this);
}
public void bindTo(String imageUrl, String title) {
Glide.with(getContext())
.load(imageUrl).into(imageView);
titleView.setText(title);
}
}
use StaggeredGridLayoutManager
use a different layout for your first item (the whole complex view)
get its layout params and setFullSpan
This makes the item as wide as the RecyclerView itself (similar to match_parent).
Set various click listeners in the specific ViewHolder that's responsible for this item. Using this approach would set the whole complex view to behave (scroll) as part of the RecyclerView while still making it available for (input) events.
You could have a look at Bookends
Or another(the way i usually do it) way would be to use a GridLayouManager with a SpanSizeLookUp. And use multiple ViewTypes i.e. one for Header,Footer and Items.
Go for 1 if you have only a single header and what a ListView-ish interface in your code.
Go for 2 if you are not sure about how many Custom ViewTypes you would be adding.It assures you have maximum scalability in the future.
If you are considering massive scalability,I suggest you read this article by Hannes Dorfman .