Add RadioGroup in dynamic LinearLayout - java

I dynamically generate radiobuttons for an app (ex: 3 radiobuttons) in a LinearLayout I call parentLinearLayout. Problem is, they don't go automatically in a group, so if I check a button and then another, both check instead of un-checking the first one. So, I thought I should do a RadioGroup.
The app itself is a questionnaire app, and every question has a different number of possible answers. I create from a radiobutton layout the number of answers as radiobuttons.
Problem is, I get an error saying that I should remove the old view before doing so, but if I do this I lose my generated linearlayout: The specified child already has a parent. You must call removeView() on the child's parent first.
Here is my code:
public void drawRAnswers(int pst){
int drawables = qmclist.get(pst).getAnswers().size();
RadioGroup l1 = new RadioGroup(this);
for (int i=0;i<drawables;i++){
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View rowView = inflater.inflate(R.layout.radiobutton, null);
// Add the new row before the add field button.
parentLinearLayout.addView(rowView, parentLinearLayout.getChildCount());
rowView.setId(i);
RadioButton rd = (RadioButton) rowView.findViewById(R.id.radiobtn);
l1.addView(rd); <== HERE IS THE PROBLEM
rd.setText(current.getAnswers().get(i).getAns());
}
}
Any ideas how I could fix this? Thanks

You are creating a new layout during every iteration in loop.
By inflating a new view, you are losing the old views
Every time you are using the same id R.id.radiobtn for to find a view and add the same view again in the layout which is not possible hence the issue.
for (int i=0;i<drawables;i++){
// 1,2
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//
final View rowView = inflater.inflate(R.layout.radiobutton, null);
parentLinearLayout.addView(rowView, parentLinearLayout.getChildCount());
rowView.setId(i);
RadioButton rd = (RadioButton) rowView.findViewById(R.id.radiobtn);
l1.addView(rd); // same view with same id R.id.radiobtn
rd.setText(current.getAnswers().get(i).getAns());
}
Solution :
public void drawRAnswers(int pst){
int drawables = qmclist.get(pst).getAnswers().size();
// create a radio group as container with direction
RadioGroup l1 = new RadioGroup(this);
l1.setOrientation(LinearLayout.VERTICAL);
for (int i=0;i<drawables;i++){
// create radio button, with id and text
RadioButton rd = new RadioButton(this);
rd.setId(i);
rd.setText(current.getAnswers().get(i).getAns());
// add radio button into group
l1.addView(rd);
}
// finally, add radio group with buttons into parent layout
parentLinearLayout.addView(l1, parentLinearLayout.getChildCount());
}

You are quite close I guess just change some of the lines and you are good to go.
public void drawRAnswers(int pst){
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
int drawables = qmclist.get(pst).getAnswers().size();
RadioGroup l1 = new RadioGroup(this);
for (int i=0;i<drawables;i++){
final View rowView = inflater.inflate(R.layout.radiobutton, null);
// Add the new row before the add field button.
RadioButton rd = (RadioButton) rowView.findViewById(R.id.radiobtn);
rd.setText(current.getAnswers().get(i).getAns());
rd.setId(i);
l1.addView(rd);
}
parentLinearLayout.addView(l1, parentLinearLayout.getChildCount());
//or
parentLinearLayout.addView(l1);
}

RadioGroups are glorified LinearLayouts behind the scenes, that means they can support any children, but will only treat RadioButtons in the same “group” if they are direct children of the Group. E.g.:
Imagine This hierarchy:
<RadioGroup> // THIS YOU HAVE IN XML
<RadioButton 1> // ALL THESE YOU CREATE PROGRAMMATICALLY
<RadioButton 2>
<RadioButton 3>
</RadioGroup>
So your XML looks like:
<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/your_radio_button"
android:layout_width=“match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Radio Buttons will be programmatically added here -->
</RadioGroup>
So… you create everything programmatically, that’s fine. You have a “loop” like…
final RadioGroup group = //find your_radio_button in the layout.
// now iterate
for (Something something : listOfSomethings) {
final RadioButton rb = new RadioButton(context);
rb.setId(View.generateViewId());
rb.setText(something.giveMeTheText());
rb.setLayoutParams(layoutParams); // make these depending what your container for the RadioGroup is.
rb.setOnCheckedChangeListener(your OnCheckedChangeListener);
group.add(rb);
}
If you need to listen for changes, obviously add a common CompoundButton.OnCheckedChangeListener and override:
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// do what you need ;)
}
Bear in mind that the moment you do group.add(rb) if the Button has an onCheckedChangeListener, it will be fired… if you set it as checked (I think it also fires even if you don’t with a “false”), so you may want to delay the assignment of the listener until you’re done adding buttons to the RadioGroup and then iterate them and add them all at the same time (that doesn’t trigger the callback right there because the buttons are ALREADY in the hierarchy and haven’t changed).
If you need to insert something other than a RadioButton as the direct child of your RadioGroup that’s the topic for another question (it’s doable, not automatic and will require you to do some extra-work).

Related

Registering multiple click events programatically (Android/Java)

So i have this program which create my list of cards which are relative layouts and they look like this.
Here is the code of it creation. Ps its in a for loop
LayoutInflater mInflater = (LayoutInflater) atv.getSystemService(atv.LAYOUT_INFLATER_SERVICE);
LinearLayout relativeLayoutz = (LinearLayout) mInflater.inflate(R.layout.rtl_enterprise, parent);
RelativeLayout rl = (RelativeLayout) relativeLayoutz.findViewById(R.id.rl_empresa);
rl.setId(i);
ImageView img = (ImageView) rl.getChildAt(0);
//
//Performance bottleneck needs fixing/ not loading all images and overloading thread
//
//loadImageByUrl(atv, enterprises.getEnterprises().get(k).getPhoto().toString(), img);
TextView txt = (TextView) rl.getChildAt(1);
txt.setText(enterprises.getEnterprises().get(i).getEnterpriseName());
TextView txt2 = (TextView) rl.getChildAt(2);
txt2.setText(enterprises.getEnterprises().get(i).getEnterpriseType().getEnterpriseTypeName());
TextView txt3 = (TextView) rl.getChildAt(3);
txt3.setText(enterprises.getEnterprises().get(i).getCountry());
LinearLayout relativeLayout = (LinearLayout) mInflater.inflate(R.layout.rtl_enterprise, parent);
relativeLayout.setId(i);
everything there works kinda ok, but i have a problem, i need to create a
onClick listener
Why?
I would call a method which needs some info about the card that has been clicked, and then redirect to a new activity which contains the info about the card that he clicked.
But i would need that onclick listener for each of these relative layouts, and i assume it would need to be initialized when the card is created since it create + 50 cards, but i have no idea of how to setup each listener.
Why do you create it like that?
a better solution to use RecyclerView.
and in the adapter, you can listen for the item click. write the code one time and when any item clicked. you will know the position of the clicked item.
see this tutorial
https://developer.android.com/guide/topics/ui/layout/recyclerview
https://www.androidhive.info/2016/01/android-working-with-recycler-view/

How to find the class of a checkbox that was clicked

I have a 'ChecklistItem' class that has the following properties:
private CheckBox checkBox;
private ImageButton noteButton;
private TextView vitalField;
I have an onClick Listener for my checkbox. Now the problem is, when I click on that checkbox and the OnClick() method gets called, how can I figure out what ChecklistItem that checkbox is a part of?
Whenever I click on a checkbox, I want to add the ChecklistItem that the checkbox is a part of to an array, but the OnClick() only knows about the checkbox that called it.
How can I get around this?
Ok so this answer is according to the "long discussion" we had
let's assume you want to make a - re usable - view of your list and you wrote a separate xml layout file called list_item as the following:
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/checkbox"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/text_view"/>
so now let's assume you are in the activity or fragment or wherever you want to host your view , NOW I have to point out this is just an example , usually a list view is what you would need in this case but again I have very little details about your app so I'm going to keep it simple
Assuming you have a vertical linear layout and you want to add these "rows" to it, each row represents one of your custom view
LinearLayout layout = findViewById(R.id.layout);
LayoutInflater inflater = LayoutInflater.from(this); // This inflater is responsible of creating instances of your view
View myView = inflater.inflate(R.layout.list_item, layout, false); // This view objects is the view you made in your xml file
CheckBox checkBox = (CheckBox) myView.findViewById(R.id.checkbox);
TextView textView = (TextView) myView.findViewById(R.id.text_view);
checkBox.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//if checkbox is checked enable textview for example
// here you have a reference to all the views you just created
// Weather you want to save them in a class together that's up to you and your app's logic
}
});
layout.addView((myView));
if the list is might exceed the screen height you may want to wrap your linear layout in a scroll view.
BTW: ListView is just a neat way to do this automatically by defining how you want each row to appear, and of course it manages your views for you and recycle them when they get of screen, but I just wanted to point out the concept.
Hope this helps you

Android adding n text fields

I just started learning android programming and can't seem to work out how to permit the user to create a selected number (n) of EditText fields by clicking a button n times such that each would be uniquely identified, namely 1,2,3...,n as well as accessible programatically, from a different method (invoked by a different button click). I hope the question is clear enough as I don't really have that much code to provide.
Do something like this
// A list to keep reference to your created edit texts
List<EditText> mEditTexts = new ArrayList<EditText>();
// Get root view of your activity
ViewGroup viewGroup = (ViewGroup) ((ViewGroup)
findViewById(android.R.id.content)).getChildAt(0);
// Get the button and set a click listener to it
Button mButton = (Button) findViewById(R.id.button_id);
mButton.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v){
// Build edit text
EditText mEditText = new EditText(v.getContext());
// Pass two args (arg1/arg2); must be LayoutParams.MATCH_PARENT,
// LayoutParams.WRAP_CONTENT, or an integer pixel value.
mEditText.setLayoutParams(new LayoutParams(arg1, arg2));
// Add the edit text to your list
mEditTexts.add(mEditText);
// Add edit text to your root view
viewGroup.addView(mEditText);
}
}
To check your edit text fields you can then access them from the list
for(EditText editText : mEditTexts){
Log.d(TAG, editText.getEditableText().toString());
}
or explicitly
int specificPosition = (SOME_INT);
EditText specificEditText = mEditTexts.get(specificPosition);
Haven't been able to test it so it might need some modifying but it should be something along those lines. You can also use your layout directly if you don't want to use the viewGroup. Modify it to something like
LinearLayout mLayout = (LinearLayout) findViewById(R.id.layout_id);
....
mLayout.addView(mEditText);

ListView with Spinner Inside: 1st Item of the listview affects its last item

I had this weird scenario where every time I select a value from the spinner in the ListView's 1st Item, the last ListView'sitem its spinner value is the same as the first item. This will only happen when the total number of ListView items is 5 and above. I commented out the codes and retain just the declarations and it's still happening. Is this a bug in Android?
Clarifications:
My ListView's Scroll Listener is empty
My spinner's setOnItemSelectedListener is commented out.
Android SDK Tool version is 22.6.2
Android SDK Platform-Tools is 19.0.1
Here's the adapter code:
#Override
public View getView(final int position, View convertView, final ViewGroup parent) {
Viewholder v = new Viewholder();
v.rowView = convertView;
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (v.rowView == null) {
v.rowView = inflater.inflate(R.layout.inner_base_header_cutom, parent, false);
v.spinner = (Spinner) v.rowView.findViewById(R.id.spinner1);
v.rowView.setTag(v);
} else {
v = (Viewholder) v.rowView.getTag();
}
return v.rowView;
}
ViewHolder:
class Viewholder{
View rowView;
TextView itemPID;
TextView itemPrice;
TextView itemItemId;
Spinner spinner;
TextView subtotal;
}
XML:
<Spinner
android:id="#+id/spinner1"
android:layout_width="100dip"
android:layout_height="wrap_content"
android:layout_margin="20dip"
android:entries="#array/quanitiy"
android:layout_alignTop="#+id/itemPrice"
android:layout_toRightOf="#+id/imageDisplay" />
Array:
<string-array name="quanitiy">
<item>Quantity</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
<item>10</item>
</string-array>
UPDATE
I commented out the code for OnItemClickListner. I updated the code above, still the problem exists. The only thing left is the declarations.
Scenario:
If I select 1 at the first item of the spinner [index 0] of the ListView, the last item of ListView's spinner gets also 1 without interaction. When down the listview's items until the last part, that's where I found out that they are both the same. I commented out the codes and just retained the declarations.
Ok now I got it. Thanks for the clarifications. Your problem comes from views being recycled. You should take a look at this post to understand recycling better.
The short answer
Basically, when you use the convertView in getView(), you are reusing the view of an item that is no longer visible. That's why its fields (including the spinner, the textview for the price...) are already set to something, and you should set them yourself in getView(). The values to give these textfields and the spinner should depend on the item at the specified position.
The detailed answer
Here's what recycling does (picture taken from the post previously mentioned):
Your problem
Here when you scroll to display a new item (let's say item 8, like the picture), the view used to display it is the same as the one for the first item (item 1). Therefore, when you select something on the spinner of the first item, and then scroll down, you will see the change in the last item too because your getView doesn't change the values of the views that were used for item 1 (but it is supposed to update them!).
Note: The correspondance of the first and the last item in your case is totally by chance. Usually, the view is reused for 2 items that are approximately separated by the number of items in one screen height, as you can see in the picture.
Inline explanation in YOUR code for getView
Viewholder v = new Viewholder(); // why create a new one, while you might reuse the former one?
// here rowView becomes the same as the former item's view, with all the former values
v.rowView = convertView;
// you don't need this here but only in the case rowView is null, should be in your 'if'
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (v.rowView == null) {
v.rowView = inflater.inflate(R.layout.inner_base_header_cutom, parent,false);
v.spinner = (Spinner) v.rowView.findViewById(R.id.spinner1);
v.rowView.setTag(v);
// here you inflated a new view (nothing reused)
// but you didn't initialize anything
} else {
v = (Viewholder) v.rowView.getTag();
// here you're reusing the former View and ViewHolder
// and you don't change the values
}
The solution
Your not supposed to let the items behave as they please, but you're supposed to tell them what to display when you initialize the item view's content in getView(). For the picture above, this means you need to change what used to be item 1 into item 8.
You should keep the state of each item in your adapter. You probably have a backing list or something to display different elements in your ListView depending on the position, right?
Then you should use a similar list (or the same) to store the value selected in the spinner, the value displayed by the text field for the price etc. These should probably correspond to fields of the items in your current list anyway, so you probably already have some place to store them.
On the other hand, when you reuse the convertView in getView(), ensure you initialize the spinner (and text field, and everything in this item view) with the correct values for the item at position.
Solution code (template) for getView
Viewholder v;
if (convertView == null) {
// create a new holder for the new item view
v = new Viewholder();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v.rowView = inflater.inflate(R.layout.inner_base_header_cutom, parent,false);
v.rowView.setTag(v);
// populate the new holder's fields
v.spinner = (Spinner) v.rowView.findViewById(R.id.spinner1);
v.itemPID = (TextView) v.rowView.findViewById(...); // put the right ID here
v.itemPrice = (TextView) v.rowView.findViewById(...); // put the right ID here
v.itemItemId = (TextView) v.rowView.findViewById(...); // put the right ID here
v.subtotal = (TextView) v.rowView.findViewById(...); // put the right ID here
} else {
v = (Viewholder) convertView.getTag();
// the views of v are already populated here (reused)
}
/*
* Reused or not, the item view needs to be initialized here.
* Initialize all views contained in v with values that are
* meaningful for the item at 'position'.
*/
// for instance:
MyItemClass theItemAtPosition = myBackingList.get(position);
v.subtotal.setText(String.valueOf(theItemAtPosition.getSubtotal()));
v.spinner.setSelection(theItemAtPosition.getQuantity());
// to be continued, you get the idea ;)

Radio Buttons inside expandable list remain when expanding other child elements

I want to make an expandable list of radio groups in my android app. each element of the list should contain (when opened) several individual radio groups. So far I am testing with only one radiogroup per child and already I am encountering a problem I can't seem to solve by myself.
When I expand a parent of my expandable list, it seems to work properly. The radiobuttons of a radiogroup (which is the only child element of a parent right now) are displayed the way they should. However, when I expand another parent of my expandable list, the radio buttons are added to the already existing buttons of the other child group. Which means: Once I've expanded several child groups, each one of the expanded children contains tons of radio buttons, flooding the whole screen.
Simply put, when I expand parent 1 I get the choices
yes
no
maybe
When I expand the second parent, I get the radio choices
yes
no
maybe
yes
no
perhaps
probably
... which are also added to the radio button group thats inside parent 1. So now I have 2 large expanded list elements. When I expand another group, the choices get added to all the child views as well, and so on, and so on...
So, following simple logic it seems that all the radio buttons are not put into a new group (thats newly created every time I expand a child list), but instead they are added to the same, already existing group.
I have been able to pretty much isolate the problem to the getChildView function inside my ExpandableListAdapter class. This is the code which causes the output described above:
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View view,
ViewGroup parent) {
ExpandListChild child = (ExpandListChild) getChild(groupPosition, childPosition);
if (view == null) {
LayoutInflater infalInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = infalInflater.inflate(R.layout.child_layout, null);
}
ArrayList<HashMap<String,String>> inputDetailsList = child.getInputfields();
//RadioGroup rg = new RadioGroup(view.getContext());
RadioGroup rg = (RadioGroup) view.findViewById(R.id.radiogroup);
int has_radiogroup = 0;
for (int i = 0; i <inputDetailsList.size(); i++){
if (inputDetailsList.get(i).get("type").equals("radio")){
has_radiogroup = 1;
RadioButton rb = new RadioButton(rg.getContext());
rb.setText(inputDetailsList.get(i).get("textcontent"));
rg.addView(rb);
}
}
return view;
}
Here is the xml file that is used as child layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="55dip"
android:background="#color/ExpandChildBackground"
android:orientation="vertical" >
<TextView
android:id="#+id/tvChild"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="#color/Black"
android:textSize="17dip" />
<RadioGroup
android:id="#+id/radiogroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
>
</RadioGroup>
</LinearLayout>
The line where I suspect my error could be this one:
RadioGroup rg = (RadioGroup) view.findViewById(R.id.radiogroup);
It looks for the radio group I created inside the inflated child layout (R.layout.child_layout). But it does not use a new one for each expanded child view and instead uses an existing one to apply the buttons to. In other words, it seems like the radio group never gets the sign that its "finished" and/or the child view never gets the sign that a new radioGroup has to be used instead of the old one.
What I have tried is creating a new RadioGroup for each child view using the following line (you can see it is the commented line in the code above), instead of using the radiogroup I defined in my layout.
Making a fresh radiogroup would look like this:
RadioGroup rg = new RadioGroup(view.getContext());
However, this does not work, it does not display any radio groups, let alone buttons at all. I have also tried removing the radiogroup element from the layout xml-file (because its not used anyway). Still no luck. Using simple unorganized textviews for each element seems to work properly, so this points to a problem with my radio group implementation as well.
Honestly, I am out of ideas right now. I might be getting the wrong context, but I have no clue what context to use. If anyone is able to point me the right direction, I would be extremely grateful. Thanks in advance.
If anyone is interested, I have been able to fix the problem myself. I have simply removed the if-condition [ if (view == null) ], so the new code looks like this:
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View view,
ViewGroup parent) {
ExpandListChild child = (ExpandListChild) getChild(groupPosition, childPosition);
LayoutInflater infalInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = infalInflater.inflate(R.layout.child_layout, null);
ArrayList<HashMap<String,String>> inputDetailsList = child.getInputfields();
RadioGroup rg = (RadioGroup) view.findViewById(R.id.radiogroup);
int has_radiogroup = 0;
for (int i = 0; i <inputDetailsList.size(); i++){
if (inputDetailsList.get(i).get("type").equals("radio")){
System.out.println("type: "+inputDetailsList.get(i).get("type"));
has_radiogroup = 1;
RadioButton rb = new RadioButton(rg.getContext());
rb.setText(inputDetailsList.get(i).get("textcontent"));
System.out.println("textcontent: "+inputDetailsList.get(i).get("textcontent"));
rb.setId(Integer.parseInt(inputDetailsList.get(i).get("value")));
if (has_radiogroup == 1){
rg.addView(rb);
}
}
}
return view;
}
Now it makes sense. The LayoutInflater is called everytime a new child view is generated, i.e. a group is expanded. Before this, it just used the existing view (view was not null) that was filled with radiobuttons, and added the new radiobuttons to that existing view, resulting in my list flooding the screen.
Thanks however to anyone trying to get their heads into the problem.
This is worked for me.
For RadioButtons and CheckBoxs
android:focusable="false"

Categories