RecyclerView individual viewTypes - android

I want to display many [CardViews] within a [RecyclerView].
Therefore I am using an adapter that extends
RecyclerView.Adapter < RecyclerView.ViewHolder >
Everything works fine so far.
But now, I want to populate the CardView with different widgets(checkboxes, textviews, buttons.. ) in different order, dependent on the data of the current dataset position. Defining a ViewHolder class for every possible combination of the widgets order is not an option due too many possibilities.
I managed it already with two RecyclerView-Adapters. One for the RecyclerView "list" and one for populating the CardView inside each item.
But that leads to bad performance.
So I came back to use only one RecyclerView and one Adapter.
But, how to populate each CardView in a good way then? Thank you for your help!
Update1:
This is my current and result updated_screenshot. Looks "fine" so far but is achieved not in a proper way.
I did it as following:
Setting an Adapter (OuterAdapter) to the recyclerView, that holds each CardView:
public class OuterAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
.
.
.
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
rviewHolder = (RviewHolder) holder;
llm = new LinearLayoutManager(context);
rviewHolder.rv.setLayoutManager(llm);
innerAdapter = new VHAdapter(context, questionList);
rviewHolder.rv.setAdapter(innerAdapter);
}
.
.
.
}
Now, every Item has its own "VHAdapter" which fills the CardViews like:
#Override
public void onBindViewHolder(RecyclerView.ViewHolder h, final int position) {
switch (h.getItemViewType()) {
case ONLY_HEADER_TEXT:
HeaderTextHolder holder = (HeaderTextHolder) h;
holder.tv.setText(q.getText());
break;
case ANSWER_TYPE_NUMBER:
NumberHolder nh = (NumberHolder) h;
nh.tv.setText(q.getText());
nh.et.setOnEditorActionListener(new TextView.OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
tempAnswer.setNumber(Integer.parseInt(v.getText().toString()));
tempAnswer.setQuestionType(Question.ANSWER_TYPE_NUMBER);
return false;
}
});
break;
.
.
.
What I am trying to do now is getting rid the VHAdapter. But that means, I'd have to manually add widgets to ViewHolders rootLayout at OuterAdapter's onBindViewHolder.. Is that correct?

Related

Android: Query model before adding to RecyclerView Adapter

I have Tab/ViewPager Layout all consists of RecyclerView with datas depending on 'ID', What I tried so far is to get all the data in my model and add to adapter, then set the visibility to hide others data but it just leaves a blank space without the layout in my recyclerview. So i would like to query it first and just pick all the ID's with 245 before adding the result to my adapter.
Guys I'm trying to remove some items in recycler view by
#Override
public void onBindViewHolder(#NonNull RecyclerViewAdapter.ViewHolder holder, final int position) {
final int i = holder.getAdapterPosition();
int id = mDataset.getCargoItem().get(i).getCargoStatusId();
if (id != 245){
holder.itemView.setVisibility(View.GONE);
}
}
Please comment if you need more of my codes
change this ..
int id = mDataset.getCargoItem().get(position).getCargoStatusId();

add recyclerView inside another recyclerView

I have a list of data to be displayed in a recycler view and that is working fine. Now I have another dynamic list of data to be displayed inside the parent recycler-view. So I tried using another recycler-view inside the parent recycler-view, that is not working fine. It will be good if I get some idea of using recycler-view inside another one. Thanks in advance..!
I have illustrated my problem with an example:
For eg: I have a parent recyclerView with five linearLayout and I have created a child recyclerView inside the Linearlayout with visibility GONE. Now when I click the first Linearlayout I am changing the visibility of child recyclerView for the first Linearlayout to VISIBLE and attaching a separate view to it and same concept for all the other Linearlayouts. What happens is when I click first, second, third and fourth linearLayout the child recyclerView is not displaying date which I pass to it, all those first, sec, third and fourth data are accumulated and displayed in the last (i.e) inside fifth linearLayout.
Here is my parent recyclerview code:
class CardAdapter extends RecyclerView.Adapter<CardAdapter.MyViewHolder>
{
RecyclerView insideCardRecyclerView,recyclerView;
List<String> monthsWeek = new ArrayList<>();
List<String> dealers = new ArrayList<>();
List<String> dealersList = new ArrayList<>();
List<String> date = new ArrayList<>();
HashSet<String> dealersListHash = new HashSet<>();
public CardAdapter(List<String> monthsWeek,List<String> dealers,List<String> date)
{
this.monthsWeek = monthsWeek;
this.dealers = dealers;
this.date = date;
}
public class MyViewHolder extends RecyclerView.ViewHolder
{
ProgressBar progressBar;
RecyclerView recyclerView;
TextView period;
LinearLayout linearLayoutParent,linearLayoutCardDetails;
public MyViewHolder(View view)
{
super(view);
linearLayoutParent = (LinearLayout) view.findViewById(R.id.card_view_linear_parent_layout);
linearLayoutCardDetails = (LinearLayout) view.findViewById(R.id.linear_card_layout_details);
period = (TextView) view.findViewById(R.id.period_summary_graph_card);
insideCardRecyclerView = (RecyclerView) view.findViewById(R.id.summary_graph_card_view_recycler_view);
}
}
#Override
public CardAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
View itemView = LayoutInflater.from(getActivity())
.inflate(R.layout.summary_card_view,parent,false);
recyclerView = (RecyclerView) parent;
return new CardAdapter.MyViewHolder(itemView);
}
#Override
public void onBindViewHolder(final MyViewHolder holder, final int position)
{
holder.period.setText(monthsWeek.get(position));
holder.linearLayoutParent.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view)
{
if(searchClick)
{
for (String date1 : date)
{
if(Objects.equals(date1,monthsWeek.get(position)))
{
Log.e("Summary123 date..///", date1);
dealersList.add(dealers.get(date.indexOf(date1)));
}
}
searchClick = false;
holder.linearLayoutCardDetails.setVisibility(View.VISIBLE);
dealersListHash.addAll(dealersList);
dealersList.clear();
dealersList.addAll(dealersListHash);
//if the condition is true i am attaching another recyclerview inside this.
cardAdapterList = new CardAdapterList(dealersList);
LinearLayoutManager mLayoutManager1 = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL,true);
mLayoutManager1.setReverseLayout(false);
insideCardRecyclerView.setLayoutManager(mLayoutManager1);
insideCardRecyclerView.setItemAnimator(new DefaultItemAnimator());
insideCardRecyclerView.setAdapter(cardAdapterList);
}
else
{
searchClick = true;
holder.linearLayoutCardDetails.setVisibility(View.GONE);
}
}
});
}
#Override
public int getItemCount() {
return monthsWeek.size();
}
}
I've been in trouble sometimes with RecyclerView when multiple lists are needed to be shown in a single page of the application. Its not a very good idea actually to have multiple lists in a single layout but however, the idea of having a ScrollView and the lists inside that ScrollView is even worse.
I had to implement a ListView inside a ScrollView once and yes it was not a very good experience. Firstly, my list was not scrolling at all. Then I had to add some code to disable the scrolling when the touch is detected inside the list. It was not a very good idea of solving the actual problem. I had another problem of having a fixed height of the ListView. In case of list items with dynamic heights, the solution failed.
Having two lists in the layout, one after one is not a good idea either. As the first list need to have a fixed height.
So, after searching for suggestions about how can I implement two lists in a single layout file, I found most of the developers suggests of having a single list with a header and footer if necessary. Later, I could manage to show two lists in a single RecyclerView using my custom Adapter. I thought I should save some of my code for future use and hence, you see this note.
You can refer this sample code.

How to change colors of parent Fragment and views of RecyclerView depending on OnClickListener position from recyclerview

This is a difficult one, so I attached a picture to help visualize a bit.
So I have a recyclerview which contains a palette of colors. It loads the colors by creating a new ImageView for each color, and then changing the color and drawable of each imageview, having a different one for the "selected color". The selected color is also used to change the color of the toolbar from the parent DialogFragment.
The problem comes that the OnClicklistener hasn't worked at all after hours of research. Right now, I can't use an OnClickListener without the app crashing.
Now, lets get to the actual code and problems.
I'll declare (to you) some variables and classes I'm using:
ArrayList<String> colorsList = //arraylist which contains all the colors which can be displayed
NewNotebookFragment = //DialogFragment, contains a toolbar, miscellaneous views and a rvNewNotebook
RecyclerView rvNewNotebook = //the recyclerview containing imageviews representing each individual color retrieved from colorsList
NewNotebookAdapter = adapter which is bound to rvNewNotebook
Right now, what I've done is make a method in NewNotebookFragment called changeColor which can receive an int, representing the position of the color which was selected. changeColor() changes the color of the toolbar. changeColor() is called from an onClick method from an OnclickListener, defined in the Viewholder in NewNotebookAdapter.
Now comes the actual code.
newNotebookFragment:
public class NewNotebookFragment extends DialogFragment {
RecyclerView rvNewNotebook;
int activeColor;
ArrayList<String> colors=Helpers.getPossibleColors();
#Override
public View onCreateView ...}
#Override
public void onViewCreated(View v, Bundle savedInstanceState){
activeColor=new Random().nextInt(colors.size());
toolbar = (Toolbar) v.findViewById(R.id.newnotebooktoolbar);
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
#Override
public boolean onMenuItemClick(MenuItem item) {
return false;
}
});
toolbar.inflateMenu(R.menu.newnotebook);
toolbar.setTitle("Create new Notebook");
if(Helpers.isColorDark(Color.parseColor(colors.get(activeColor)))){
toolbar.setTitleTextColor(getResources().getColor(R.color.md_dark_primary_text));
}else{
toolbar.setTitleTextColor(getResources().getColor(R.color.md_light_primary_text));
}
rvNewNotebook = (RecyclerView) v.findViewById(R.id.rvNewNotebook);
final GridLayoutManager rvNotebookManager = new GridLayoutManager(getContext(),6);
rvNewNotebook.setLayoutManager(rvNotebookManager);
NewNotebookAdapter adapter= new NewNotebookAdapter(getContext(), Helpers.getPossibleColors(), activeColor);
toolbar.setBackgroundColor(Color.parseColor(Helpers.getPossibleColors().get(activeColor)));
rvNewNotebook.setAdapter(adapter);
Log.d("onViewCreated", ""+rvNewNotebook.getWidth());
}
#Override public void onResume(){...}
public static NewNotebookFragment newInstance() {...}
public void changeColor(int position){
if(Helpers.isColorDark(Color.parseColor(colors.get(position)))){
toolbar.setTitleTextColor(getResources().getColor(R.color.md_dark_primary_text));
}else{
toolbar.setTitleTextColor(getResources().getColor(R.color.md_light_primary_text));
}
toolbar.setBackgroundColor(Color.parseColor(Helpers.getPossibleColors().get(position)));
}
NewNotebookAdapter:
public class NewNotebookAdapter extends RecyclerView.Adapter<NewNotebookAdapter.ViewHolder>{
Context context;
ArrayList<String> colors = new ArrayList<>();
int activeColor;
public NewNotebookAdapter(){
}
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
public ImageView ivSwatch;
public Toolbar toolbar;
public ViewHolder(View itemView) {
super(itemView);
ivSwatch = (ImageView) itemView.findViewById(R.id.ivSwatch);
toolbar = (Toolbar) itemView.findViewById(R.id.newnotebooktoolbar);
ivSwatch.setOnClickListener(this);
}
#Override
public void onClick(View view) {
NewNotebookFragment newNotebookFragment = new NewNotebookFragment();
final int adapterPosition = ViewHolder.this.getAdapterPosition();
Log.d("OnClick. Adapter", "getAdapterPosition: "+adapterPosition);
newNotebookFragment.changeColor(adapterPosition);
}
}
public NewNotebookAdapter(Context context, ArrayList<String> colors, int activeColor){
this.context=context;
this.colors=colors;
this.activeColor=activeColor;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.colorcircle,parent,false);
return new ViewHolder(v);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Drawable drawablefull = ContextCompat.getDrawable(context,R.drawable.colorcircle);
Drawable drawableHollow = ContextCompat.getDrawable(context,R.drawable.coloroutlinecircle);
if (position==activeColor){
holder.ivSwatch.setImageDrawable(drawablefull.mutate());
}else if (position!=activeColor){
holder.ivSwatch.setImageDrawable(drawableHollow.mutate());
}
holder.ivSwatch.setColorFilter(Color.parseColor(colors.get(position)));
The current problem I have is the following stack trace:
FATAL EXCEPTION: main
Process: com.twotowerstudios.virtualnotebookdesign, PID: 32272
java.lang.IllegalStateException: Fragment NewNotebookFragment{b10003e} not attached to Activity
at android.support.v4.app.Fragment.getResources(Fragment.java:648)
at com.twotowerstudios.virtualnotebookdesign.NewNotebookDialog.NewNotebookFragment.changeColor(NewNotebookFragment.java:85)
at com.twotowerstudios.virtualnotebookdesign.NewNotebookDialog.NewNotebookAdapter$ViewHolder.onClick(NewNotebookAdapter.java:49)
at android.view.View.performClick(View.java)
at android.view.View$PerformClick.run(View.java)
at android.os.Handler.handleCallback(Handler.java)
at android.os.Handler.dispatchMessage(Handler.java)
at android.os.Looper.loop(Looper.java)
at android.app.ActivityThread.main(ActivityThread.java)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java)
at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:132)
Now I'm almost positive this is related to my onClick method, which creates a new instance of NewNotebookFragment, causing the fragment I'm using in the onClick method to not actually be the same as the one which is already being used and displayed. HOWEVER, I'm making this thread because I have no real idea on how to solve the issue as a whole. I don't know how I could for example get the actual NewNotebookFragment from the onClick method so I could modify it or call methods from it. I don't know if once that
s fixed if it would even work or anything. How could I get this working?
Edit: I got it working perfectly. Basically what I did was define the setonclickListener inside the onBindViewHolder, on the Imageviews. The clickListener will call an interface called AdapterInterface, and use a method in it called clickListener, which allows me to pass the int of the color to the parent fragment. The fragment will implement that interface defined in the adapter, and it will override clickListener, using the color passed to it and from it invoking the changeColor method. If anyone in the future needs examples, feel free to contact me. The relevant changes are in these two files, uploaded to Github: https://gist.github.com/coldblade2000/0c2cac8b1af4df5985fe6cebebc9cff2 The links contain a certain level of explanation and documentation about the two classes
Looking at your code, you're creating a new Fragment every time it's clicked. I believe what you want is to change color of the dialog fragment toolbar. The first thing you can do is to create an interface listener for as a Callback to Fragment, which is passed into Adapter. Something like this
public interface ViewHolderOnClickListener {
//This method can be any parameters, I'm pasing color here since you need the color code
public void onViewHolderClick(View itemView, int position, int color);
}
And in your adapter, and viewholder Constructor methods, add an interface instance as your parameter. You can either directly pass it on creating Adapter or make your Fragment implements the interface and pass it with "this"
public TempAdapter(ViewHolderOnClickListener viewHolderOnClickListener) {
this.viewHolderOnClickListener = viewHolderOnClickListener;
}
And In your ViewHolder, do a callback with
#Override
public void onClick(View view) {
viewHolderOnClickListener.onViewHolderClick(*PARAMS HERE*);
}
That way, your fragment can listen to OnClick That is happening inside your ViewHolder, and respond accordingly.
Another way is to use EventBuses

Multiple header in recyclerview android

I have created a recyclerview for displaying data which is fetching from the server. I had used only single layout for displaying the data.
Now my requirement is like when I upload images or videos, then the uploading status should be displayed on top of the data which is displaying from the server. i-e on 0th position of recyclerview. I can add any number of images or videos.
after image or video successfully upload i also want to remove that row from recyclerview. I thought of doing using getItemViewType(). In this using two layout. I don't know this method is correct or not. I am not getting any solution to this,
Please.....
any help...
You can easily achieve that using the library SectionedRecyclerViewAdapter.
You should first create a section class:
class MySection extends StatelessSection {
String title;
List<String> list;
public MySection(String title, List<String> list) {
// call constructor with layout resources for this Section header, footer and items
super(R.layout.section_header, R.layout.section_footer, R.layout.section_item);
this.title = title;
this.list = list;
}
#Override
public int getContentItemsTotal() {
return list.size(); // number of items of this section
}
#Override
public RecyclerView.ViewHolder getItemViewHolder(View view) {
// return a custom instance of ViewHolder for the items of this section
return new MyItemViewHolder(view);
}
#Override
public void onBindItemViewHolder(RecyclerView.ViewHolder holder, int position) {
MyItemViewHolder itemHolder = (MyItemViewHolder) holder;
// bind your view here
itemHolder.tvItem.setText(list.get(position));
}
#Override
public RecyclerView.ViewHolder getHeaderViewHolder(View view) {
return new SimpleHeaderViewHolder(view);
}
#Override
public void onBindHeaderViewHolder(RecyclerView.ViewHolder holder) {
MyHeaderViewHolder headerHolder = (MyHeaderViewHolder) holder;
// bind your header view here
headerHolder.tvItem.setText(title);
}
}
Then you set up the RecyclerView with your Sections:
// Create an instance of SectionedRecyclerViewAdapter
SectionedRecyclerViewAdapter sectionAdapter = new SectionedRecyclerViewAdapter();
MySection uploadsSection = new MySection("Uploads", uploadList);
MySection downloadsSection = new MySection("Downloads", downloadList);
// Add your Sections
sectionAdapter.addSection(uploadsSection);
sectionAdapter.addSection(downloadsSection);
// Set up your RecyclerView with the SectionedRecyclerViewAdapter
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setAdapter(sectionAdapter);
This way you manage the items from your upload and download lists separately by removing them from uploadList/downloadList then notifying the changes to the adapter. The upload items will always be displayed at the top of the RecyclerView because they are in the first section added to the adapter.
If you have different layouts for uploads and downloads you can create a different Section class for each.
Make two viewTypes like:
private static final int REGULAR_HOLDER = 1;
private static final int LOADING_HOLDER = 2;
Override getItemViewType and return LOADING_HOLDER for position 0, and REGULAR for all others. Also have state if you are loading or not. If you are not loading anything you will return REGULAR_HOLDER for all rows (positions).
Then in onCreate check if you have REGULAR or LOADING viewType, and create proper Holder. Important: make your Adapter implement RecyclerView.Adapter<VH> not your custom implementation of ViewHolder.
onBind executes next. There you will have to check if your viewHolder object you get is instance of RegularViewHolder or instance of LoadingViewHolder, like:
if (holder instance of RegularViewHolder) {
holder.doStuff();
} else if (holder instance of LoadingViewHolder) {
holder.showLoading();
}
Now, before this you should made two layouts. One is for your regular rows, and other is for row that will show loading. Make two classes that implement ViewHolder, in example above i called them RegularViewHolder and LoadingViewHolder.
EDIT: few things to keep in mind. I told you to keep a loading state (loading or not loading), so if you want to remove LOADING row, you could make that change and call notifyDataSetChanged();. Now, getItemViewType should return all REGULAR rows if you did it right.
Also you should keep in mind that if you want to show 10 rows of your data. Your getItemCount() should return 11 (10 + loading row) if there is loading happening. Also, in that case your data rows start from second row (position 1).

ListView: grouping items by section

Taking for example Gmail App, on my Navigation Drawer, I want a ListView that is grouped by section, similar to inbox, all labels.
Is this behavior achieved by using multiple ListView separated by a "header" TextView (which I have to build manually obviously), or is this section-grouped behavior supported by the Adapter or ListView?
Don't use multiple ListViews, it will mess things up for the scroll.
What you describe can be achieve by using only one ListView + adapter with multiple item view types like this:
public class MyAdapter extends ArrayAdapter<Object> {
// It's very important that the first item have a value of 0.
// If not, the adapter won't work properly (I didn't figure out why yet)
private int TYPE_SEPARATOR = 0;
private int TYPE_DATA = 1;
class Separator {
String title;
}
public MyAdapter(Context context, int resource) {
super(context, resource);
}
#Override
public boolean areAllItemsEnabled() {
return false;
}
#Override
public int getItemViewType(int position) {
if (getItem(position).getClass().isAssignableFrom(Separator.class)) {
return TYPE_SEPARATOR;
}
return TYPE_DATA;
}
#Override
public int getViewTypeCount() {
// Assuming you have only 2 view types
return 2;
}
#Override
public boolean isEnabled(int position) {
// Mark separators as not enabled. That way, the onclick and onlongclik listener
// won't be triggered for those items.
return getItemViewType(position) != TYPE_SEPARATOR;
}
}
You just have to implement your own getView method for a correct rendering.
I am not sure exactly how the Gmail app achieves this behavior, but it seems as though you should work on a custom adapter. Using multiple list views would not be a productive way to approach this problem, as one wants to keep the rows of data (messages) together in single list items.

Categories

Resources