Creating and removing views in java - android

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>()

Related

Xamarin dynamically added views using inflater inside loop adding only one entry

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.

Expandable RecyclerView With Different Details

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!

Decreasing row/column count of gridlayout in android

I need a dynamic gridlayout that can be toggled between 3 by 3 and 4 by 4. I can setRowCount and setColumnCount from 3 to 4 but not from 4 to 3. It will display following issue:
Caused by: java.lang.IllegalArgumentException: rowCount must be
greater than or equal to the maximum of all grid indices (and spans)
defined in the LayoutParams of each child.
Is there any work around to achieve this using gridlayout?
I realize this question is quite old, but for people who are still encountering this exception today, I'll offer an explanation that may shed some light upon how downsizing a GridLayout works and why I believe it is/was throwing an exception for the OP.
In Short:
Child views of the GridLayout can, after downsizing, occupy cells that are not within the GridLayout's grid, which is causing the IllegalArgumentException mentioned by the OP. To avoid this, remove child views that will occupy cells outside of the GridLayout's grid before actually calling setRowCount() or setColumnCount(). This can be done via GridLayout.removeView(aboutToBeIllegalChild); or by wiping the entire layout using GridLayout.removeAllViews();.
In Long:
All that calling GridLayout.setRowCount() does, is specify a new number of rows that the layout should contain. It does not, however, mess with the child views that the GridLayout currently contains, nor it's specified Spec (what column(s) and row(s) the child view occupies).
What the exception is basically telling us, and the docs confirm, is that a GridLayout does not allow any of its child views to occupy cells that are outside of the GridLayouts grid. As an example, the layout will not allow a child view to occupy cell (5, 1) when the grid is only 4 x 1.
This leads us to why the original poster was successful at dynamically increasing the GridLayout's dimensions, while being unsuccessful at decreasing it. When enlarging the dimensions, any child views that were already attached to the GridLayout with specified cells, would still be placed in legal cells if the grid received extra rows or columns dynamically. When reducing the dimensions of the grid, child views that were placed in cells that would disappear as a consequence of removing rows or columns, would now be considered illegal.
To work around this, you must either remove those (about to be) illegal child views from its parent GridLayout beforehand by calling GridLayout.removeView(aboutToBeIllegalChild); or simply wipe the entire GridLayout by calling GridLayout.removeAllViews();.
Hope this helps!
Based on Teun Kooijman answer you can just change Spec in GridLayout.LayoutParams and keep all Views inside the GridLayout:
private void changeColumnCount(int columnCount) {
if (gridLayout.getColumnCount() != columnCount) {
final int viewsCount = gridLayout.getChildCount();
for (int i = 0; i < viewsCount; i++) {
View view = gridLayout.getChildAt(i);
//new GridLayout.LayoutParams created with Spec.UNSPECIFIED
//which are package visible
view.setLayoutParams(new GridLayout.LayoutParams());
}
gridLayout.setColumnCount(columnCount);
}
}
You can also change Spec in other way by accessing GridLayout.LayoutParams.rowSpec and GridLayout.LayoutParams.columnSpec
For me, the issue was to change the number of columns of the GridView when the app changes the orientation. I achieved it by putting the bellow code in public void onConfigurationChanged(Configuration newConfig).
if (mGridLayout.getColumnCount() != getResources().getInteger(R.integer.nav_columns)) {
final int viewsCount = mGridLayout.getChildCount();
for (int i = 0; i < viewsCount; i++) {
View view = mGridLayout.getChildAt(i);
GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams();
int colIndex = i%getResources().getInteger(R.integer.nav_columns);
int rowIndex = i/getResources().getInteger(R.integer.nav_columns);
layoutParams.height = LinearLayout.LayoutParams.WRAP_CONTENT;
layoutParams.width = 0;
layoutParams.columnSpec = GridLayout.spec(colIndex,1,GridLayout.FILL,1f);
layoutParams.rowSpec = GridLayout.spec(rowIndex);
view.setLayoutParams(layoutParams);
}
mGridLayout.setColumnCount(getResources().getInteger(R.integer.nav_columns));
}
The layout parameter values may need change depending on your need.
According to #Hensin 's answer, I have modify his codes for show how to copy the previous grid items layout parameters as following:
if (gridLayout.getColumnCount() != columnCount) {
final int viewsCount = gridLayout.getChildCount();
for (int i = 0; i < viewsCount; i++) {
View view = gridLayout.getChildAt(i);
GridLayout.LayoutParams oldParams = (GridLayout.LayoutParams) view.getLayoutParams();
GridLayout.LayoutParams newParams = new GridLayout.LayoutParams();
newParams.width = oldParams.width;
newParams.height = oldParams.height;
newParams.setMargins(oldParams.leftMargin, oldParams.topMargin, oldParams.rightMargin, oldParams.bottomMargin);
view.setLayoutParams(newParams);
}
gridLayout.setColumnCount(columnCount);
}
You can now re order your items with almost the same layout parameters

Want to set different parameters per ListView item in Android

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.

Commonsware Drag Drop shrinks row height permanently

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);
}
}
}

Categories

Resources