I've made an RecyclerView which is expandable , my expanded items in RecyclerView have different counts and it's not possible to set a single layout for them.
My program compares the price of some services ( 6 different services for now ) and every service has a different count of sub services which that count will be passed to RV Adapter.
I want somethings like this :
different expanded item counts
I've tried to solve it with these solutions :
FIRST SOLUTION :
my RV data model has a int variable named to serviceCount and gets data from MainActivity for each type of service, my layout should repeat as serviceCount size , I've written this code in onBindViewHolder :
if (holder.detailLayout.getVisibility() == View.VISIBLE) {
for (int i = 0; i < priceList.get(position).getServiceCount(); i++) {
// Code
}
I'm trying to create a layout programmatically and repeat it as that size which is something like this :
for (int i = 0; i < priceList.get(position).getServiceCount(); i++) {
ConstraintLayout newDetailLayout = new ConstraintLayout(context);
ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(
ConstraintLayout.LayoutParams.MATCH_PARENT,
ConstraintLayout.LayoutParams.WRAP_CONTENT);
layoutParams.topToBottom = R.id.tv_pricedetail_service;
layoutParams.rightToRight = 0;
layoutParams.leftToLeft = 0;
layoutParams.setMargins(0,margin8dp*i*6,0,0);
Button requestButton = new Button(context);
requestButton.setId(View.generateViewId());
requestButton.setText("درخواست" + " " + String.valueOf(i));
ConstraintLayout.LayoutParams requestButtonParams = new ConstraintLayout.LayoutParams(
ConstraintLayout.LayoutParams.WRAP_CONTENT,
ConstraintLayout.LayoutParams.WRAP_CONTENT);
requestButtonParams.leftToLeft = 0;
requestButtonParams.topToTop = 0;
requestButtonParams.setMargins(margin8dp *4,margin8dp *2,0,0);
newDetailLayout.addView(requestButton, requestButtonParams);
TextView serviceName = new TextView(context);
serviceName.setId(View.generateViewId());
serviceName.setText("تست" + " " + String.valueOf(i));
ConstraintLayout.LayoutParams serviceNameParams = new ConstraintLayout.LayoutParams(
ConstraintLayout.LayoutParams.WRAP_CONTENT,
ConstraintLayout.LayoutParams.WRAP_CONTENT);
serviceNameParams.topToTop = 0;
serviceNameParams.rightToRight = 0;
serviceNameParams.baselineToBaseline = requestButton.getId();
serviceNameParams.setMargins(0,margin8dp *2,margin8dp *4,0);
newDetailLayout.addView(serviceName, serviceNameParams);
TextView serviceCost = new TextView(context);
serviceCost.setText("هزینه" + " " + String.valueOf(i));
ConstraintLayout.LayoutParams serviceCostParams = new ConstraintLayout.LayoutParams(
ConstraintLayout.LayoutParams.WRAP_CONTENT,
ConstraintLayout.LayoutParams.WRAP_CONTENT);
serviceCostParams.leftToRight = requestButton.getId();
serviceCostParams.rightToLeft = serviceName.getId();
serviceCostParams.baselineToBaseline = requestButton.getId();
newDetailLayout.addView(serviceCost, serviceCostParams);
holder.detailLayout.addView(newDetailLayout, layoutParams);
}
//Toast.makeText(context, String.valueOf(priceList.get(position).getServiceCount()), Toast.LENGTH_SHORT).show();
}
output of my code is this : output view BUT when user expand the first item the other items in expanded view copy the first item expanded detail ! and I should create different layout for every expanded layout.
SECOND SOLUTION:
I've made 6 different layout for each service ( they will be more in future ) and inflate them in onCreateViewHolder with instantiated variables
is this right for doing something like this ? or I can do something better ?
EDIT :
onBindViewHolder Codes :
public void onBindViewHolder(#NonNull itemsViewHolder holder, int position) {
// Init Layout
final priceItemDM items = priceList.get(position);
holder.iv_logo.setImageDrawable(items.getLogo());
holder.txt_name.setText(items.getName());
holder.txt_price.setText(items.getPrice());
// Expand & Collapse Mode
final boolean isExpanded = position == mExpandedPosition;
final int positionNo = position;
holder.detailLayout.setVisibility(isExpanded ? View.VISIBLE : View.GONE);
holder.itemView.setActivated(isExpanded);
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (!items.getPrice().equals(receivingData) && !items.getPrice().equals(receiverError)) {
mExpandedPosition = isExpanded ? -1 : positionNo;
notifyItemChanged(positionNo);
} else {
Toast.makeText(context, "اطلاعات ناقص است ، لطفا مجددا تلاش فرمایید", Toast.LENGTH_SHORT).show();
}
}
});
if (!items.getPrice().equals(receivingData)) {
holder.pb_loading.setVisibility(View.INVISIBLE);
if (holder.detailLayout.getVisibility() == View.VISIBLE &&
!items.getPrice().equals(receiverError)) {
//priceList.get(position).getServiceNames().size()
holder.detailLayout.removeAllViews();
holder.detailLayout.addView(childView);
}
}
}
my expanded items in RecyclerView have different counts and it's not possible to set a single layout for them.
In your case it actually is possible to use a single layout for all items because they all look the same. They all can be expanded in the same way and their contents always look the same - only the amount of child items is different, but it doesn't mean you can't use one layout for them.
You should have two XML layout files - one for the expandable item, and one for the inner child row (the one that has a button).
The first solution is correct, you can't go with the second one. Creating a new layout every time makes no sense because your project will quickly turn into a mess due to the amount of files and the code that inflates them. Although the first solution doesn't have to be that complicated. I see that you are configuring all views in runtime - it would look much simpler if you do it in XML and just inflate the view when needed.
when user expand the first item the other items in expanded view copy the first item expanded detail ! and I should create different layout for every expanded layout.
I'm not sure I get your point but the approach is correct. The only thing is that you have to keep in mind this is a RecyclerView which reuses its child views when you scroll.
Example:
You expand item#1 and inflate 5 child rows in it, then you scroll. If item#4 is also expanded the recycler view will reuse item#1 when showing item#4, i.e. item#4 will automatically get 5 child rows even if it shouldn't have that many.
That means you have to clean up the child rows every time in onBindViewHolder to make sure you don't display information from the previous item. You will get rid of the problem if your onBindViewHolder always returns correct representation of a view for the given position. If you forget to clean up some reused views, you might see duplicated information while you scroll. If this is not really clear, please read how the ViewHolder pattern works, it's pretty simple once you get used to it.
Good luck!
Related
Probably the question is not as easy to understand as I wish.
I created a RecylerView which holds objects of the type c_TakeTimeObjects from an ArrayList. These objects have special information about themselves, like a Date.
So there could be, lets say 20, of these Objects in an ArrayList and what I want to do is to just show the ones which "Date" value is matching with the Date value shown in a TextView above the RecylerView.
If the TextView shows "12.07.2016" only the elements from the ArrayList which have their "Date" Value set to "12.07.2016" should be shown.
If there aren't any Objects with these "Date" value the List should be empty.
To try this out, I made the following:
#Override
public void onBindViewHolder(#NonNull c_ViewHolder c_viewHolder, int i) {
c_TakeTimeObjects currentItem = c_takeTimeObjects.get(i);
if(currentItem.getiActivityDate().equals("12.06.2016")) {
c_viewHolder.mImageView.setImageResource(currentItem.getiImageResource());
c_viewHolder.mCardview.setLayoutParams(new CardView.LayoutParams((int) (currentItem.getiActivityTime() * fDISPLAYFACTOR), CardView.LayoutParams.WRAP_CONTENT));
c_viewHolder.mCardview.setCardBackgroundColor(currentItem.getiImageColor());
}
}
This is the content of the ArrayList:
private void createExampleList() {
StatusImageList = new ArrayList<>();
StatusImageList.add(new c_TakeTimeObjects(c_GlobalValues.iBreakMode,((int) (idefaultRestMinutesEarly)), "12.07.2016",this));
StatusImageList.add(new c_TakeTimeObjects(c_GlobalValues.iWorkingMode, ((int) (idefaultWorkingMinutes)), "12.07.2016",this));
StatusImageList.add(new c_TakeTimeObjects(c_GlobalValues.iBreakMode, ((int) (idefaultBreakMinutes)), "12.07.2016",this));
StatusImageList.add(new c_TakeTimeObjects(c_GlobalValues.iWorkingMode, ((int) (idefaultWorkingMinutes)),"12.07.2016",this));
StatusImageList.add(new c_TakeTimeObjects(c_GlobalValues.iBreakMode, ((int) (idefaultRestMinutesLate)),"12.07.2016",this));
}
See difference "12.06.2019" to "12.07.2019".
I thought the RecylerView is empty now, but in fact it adds 5 white elements without any content. Did I ask for the Date at the wrong method or where did I go wrong?
RecyclerView adapters don't have a notion of filtering data. That's something you need to handle on your own. One thing you could do is pass your RecyclerView adapter your list AFTER filtering it.
For example:
List<c_TakeTimeObjects> statusImageList = createExampleList();
Iterator<c_TakeTimeObjects> iter = statusImageLists.iterator();
while (iter.hasNext()) {
if (iter.next().getActivityDate() != /* ... */) {
iter.remove()
}
}
// Set data, constructor, or whatever method you use to set list data
adapter.setData(statusImageList);
Adapters are very good at exactly one thing: rendering views for items in a list. No filtering or transforming included otherwise.
Yeah, this behaviour is expected.
A quick solution would be to hide an element if it does not pass your if-check but there's a style to this approach. Simply doing itemView.setVisibility(Visibility.GONE) would not solve this problem. All you need to do is to set the width and the height of the element to zero (thus mimicking non-visibility). You can change your onBindViewHolder implementation to this:
#Override
public void onBindViewHolder(#NonNull c_ViewHolder c_viewHolder, int i) {
c_TakeTimeObjects currentItem = c_takeTimeObjects.get(i);
if(currentItem.getiActivityDate().equals("12.06.2016")) {
c_viewHolder.mImageView.setImageResource(currentItem.getiImageResource());
c_viewHolder.mCardview.setLayoutParams(new CardView.LayoutParams((int) (currentItem.getiActivityTime() * fDISPLAYFACTOR), CardView.LayoutParams.WRAP_CONTENT));
c_viewHolder.mCardview.setCardBackgroundColor(currentItem.getiImageColor());
}
else {
RecyclerView.LayoutParams param = (RecyclerView.LayoutParams) c_viewHolder.getLayoutParams();
param.height = 0;
param.width = 0;
c_viewHolder.setLayoutParams(param);
}
}
This way, the view does not show up if your if-check isn't passed.
I hope this helps. Merry coding!
I followed the below link to dynamically add a layout multiple times using inflater and AddView()
Is there a way to programmatically create copies of a layout in android?
I used a loop to create multiple entries. But only one entry is comming up which is the result of last loop index
Below is my C# code
I can see only one child inside the parent which is the result of last loop.
What I missed?
var parent = FindViewById<RelativeLayout>(Resource.Id.ParentLayoutWrapper);
for (int i = 0; i < 4; i++)
{
var view = LayoutInflater.Inflate(Resource.Layout.RepeatingLayout, parent, false);
var txtView = view.FindViewById<TextView>(Resource.Id.textViewSample);
txtView.Text = i.ToString()+ " Android application is debugging";
txtView.Id = i;
parent.AddView(view, i);
}
The original post you worked from had a LinearLayout as the parent layout, not a RelativeLayout like you have. When you add a view (or another layout) to a LinearLayout, it gets positioned below (when LinearLayout has vertical orientation) any existing elements in the layout. However, the elements in a RelativeLayout need to use positioning properties to determine where they will be in the RelativeLayout, so every time you add the new layout, RepeatingLayout, since you are not changing the layout options, the view/layout is added over the existing view/layout. So change the parent layout to a LinearLayout in your layout file and then this should work:
LinearLayout parent = FindViewById<LinearLayout>(Resource.Id.parentLayout);
for (int i = 0; i < 4; i++)
{
var view = LayoutInflater.Inflate(Resource.Layout.RepeatingLayout, null);
var tv = view.FindViewById<TextView>(Resource.Id.textViewSample);
tv.Text = i.ToString() + " Android application is debugging";
parent.AddView(view);
}
Trying to do the same with a RelativeLayout as the parent layout highly complicates things unnecessarily.
I am trying to set parameters (margins in this case) per item in listview.
But when i do, it just sets the margin of last iteration.
How can i set properties per item (row) in the listview?
Ultimately i want to set diffrent "gaps" beween the items (so i can use it for my custom calendarview)
Eventually setDividerHeight() per item is good too, but i have the same problem on that function; namely one value for height and not a variabhle that can be changed per row.
//for loop
for (int i = 0; i < planning.size(); i++)
{
planning = getPlanning(medewerkerId, beginDate, eindDate);
int space = i * 15;
final ListView lijstje = (ListView) getActivity().findViewById(R.id.sundayList);
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) lijstje.getLayoutParams();
params.setMargins(0,space,0,0); // this wont work, and sets the height only on last iteration
lijstje.setAdapter(new PlanbordAdapter(getActivity(), R.layout.planning_item, planning));
lijstje.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
//onclick stuff here
}
}
Well to awnser my own question: Turns out i needed to use ".invalidate()" on the view in which i projected the list on, in order to redraw the items with correct margings set. hope this helps someone in the future with the same problem.
I am creating lots of view dynamically and adding them to my RelativeLayout. I need to keep track of all of these views so I can remove them later, so I add them is an ArrayList. But when I try to remove them all from the layout, they are not all removed.
ArrayList<LineView> lineChain = new ArrayList<LineView>();
LineView linkLine;
RelativeLayout wrapper; // Removed params etc.
// Later on in code
// This occurs many times
linkLine = new LineView(getApplicationContext());
wrapper.addView(linkLine, rlp);
lineChain.add(linkLine);
This is what I do when I try to remove all of the views. This only happens once:
for (int i = 0; i <= lineChain.size() -1; i++) {
LineView lv = lineChain.get(i);
wrapper.removeView(lv);
lineChain.remove(i);
}
As I said, the problem is that not all the lines are removed - I havn't managed to work out the pattern for which are deleted and which aren't.
You have a bug in your remove code.
for (int i = 0; i <= lineChain.size() -1; i++) {
LineView lv = lineChain.get(i);
wrapper.removeView(lv);
lineChain.remove(i);
}
The documentation for ArrayList.remove(int) function says:
Removes the element at the specified position in this list. Shifts any
subsequent elements to the left (subtracts one from their indices).
When you remove an item at an index i, all the remaining elements are shifted to the left. So for example if you remove an element at position 0. The element at position 1 is shifted to position 0 and is never removed (because you are incrementing i).
The your code to this:
while (!lineChain.isEmpty()) {
LineView lv = lineChain.get(0);
wrapper.removeView(lv);
lineChain.remove(0);
}
I think that the problem is while removing the element from the List after the cycle.
You should do something like this:
for (LineView lv : lineChain) {
((RelativeLayout) namebar.getParent()).removeView(namebar);
}
If you want to get rid of all the elements, just reset the list
lineChain = new ArrayList<LineView>()
I did get the drag and drop working and the TouchListView class works great. However in my case I have rows of various height due to my adapter which contains an EditText that can have multiple lines. Therefore after I drop, all my rows convert to the tlv:normal_height which in my case is 74dip. This causes many rows to cut off all my text in the EditTexts. I tried re initializing my adapter (mylistview.setAdapter= myadapter), setting the ListView to GONE then VISIBLE and invalidateViews() but nothing seems to reset the ListView back to before I dragged, short of leaving the activity and coming back. What can be done here? -Thx
tlv:normal_height="74dip"
tlv:expanded_height="128dip"
There's little question that the original AOSP code was designed for uniform row heights, and the whole expanded_height construct was there to provide space for the user to visualize where the drop would occur.
One starting point would probably be to create a TouchListAdapter mixin interface (akin to SpinnerAdapter) where the normal_height and expanded_height would be retrieved dynamically from the adapter based on position as opposed to being fixed values declared in the layout. Whether that alone would be sufficient or more work would need to be done, I can't say.
If you come up with a solution, patches are welcome. Otherwise, I'll probably take a look at this sometime, but not very soon.
My apologies for not having a near-term silver bullet.
I edited the unExpandViews() method - called getAdapter() and for every item in my adapter set the height to 0 and then all the rows were set back to original. I also bypassed the delete part of the method since it did not apply to me.
private void unExpandViews(boolean deletion) {
int height_saved = 0;
CheckBoxifiedTextListAdapter cbla = (CheckBoxifiedTextListAdapter)getAdapter();
for (int i = 0;i < cbla.getCount(); i++)
{
//View v = getChildAt(i);
View v = cbla.getView(i, null, null);
//if (v == null)
//{
/*
if (deletion)
{
// HACK force update of mItemCount
int position = getFirstVisiblePosition();
int y = getChildAt(0).getTop();
setAdapter(getAdapter());
setSelectionFromTop(position, y);
// end hack
}
layoutChildren(); // force children to be recreated where needed
v = getChildAt(i);
if (v == null)
{
break;
}
height_saved = v.getHeight();
*/
//}
//else
//height_saved = v.getHeight();
if (isDraggableRow(v))
{
ViewGroup.LayoutParams params = v.getLayoutParams();
params.height = 0;
v.setLayoutParams(params);
v.setVisibility(View.VISIBLE);
}
}
}