I am trying to add rows to a TableLayout that I define in an XML file. The XML file contains a header row for the table.
I can add new rows quite well using info from various tutorials but the code required for setting up the layout for the new rows is a horrendous mess and it seems like a pain in the ass to maintain whenever the layout for the header row changes.
Is it possible to create new rows to a TableLayout while still defining the row layout in XML? For example define a template row in XML, obtain a handle to it in code and then clone the template whenever I need it.
Or is the right way to do this somehow completely different?
Your proposed approach will work fine and it more or less matches the common pattern used when populating ListView items.
Define a layout that contains a single row. Obtain a LayoutInflater by using LayoutInflater.from(myActivity). Use this inflater to create new rows using your layout like a template. Generally you will want to use the 3-argument form of LayoutInflater#inflate passing false for the third attachToRoot parameter.
Let's say you wanted to use a template layout with a label and a button in each item. It might look something like this: (Though yours would define your table rows instead.)
res/layout/item.xml:
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView android:id="#+id/my_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button android:id="#+id/my_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
Then at the point where you inflate:
// Inflate the layout and find the component views to configure
final View item = inflater.inflate(R.layout.item, parentView, false);
final TextView label = (TextView) item.findViewById(R.id.my_label);
final Button button = (Button) item.findViewById(R.id.my_button);
// Configure component views
label.setText(labelText);
button.setText(buttonText);
button.setOnClickListener(buttonClickListener);
// Add to parent
parentView.addView(item);
Related
I found two SO threads that tell how to center title and message in an AlertDialog object and faked my way through writing a method that I hope to be able to call to center any AlertDialog. It worked fine on a phone and a tablet to display even multi-line messages, both with and without '\n's.
public void showCenteredInfoDialog(TextView _title, TextView _message) {
_title.setGravity(Gravity.CENTER);
LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setPositiveButton("OK", null);
builder.setCustomTitle(_title);
builder.setMessage(_message.getText());
AlertDialog dialog = builder.show();
TextView messageView = (TextView)
dialog.findViewById(android.R.id.message);
messageView.setGravity(Gravity.CENTER);
}
I did a considerable amount of customizing--i.e., I have SOME clue about what I found and did--but one line has left me wondering:
TextView messageView = (TextView) dialog.findViewById(android.R.id.message);
What is android.R.id.message?
Here is all the documentation I could find about it:
android.R.id
public static final int message = 16908299
Where can I find more documentation for the Android.R.id objects (and more)? This seems to be a possible gold mine.
In Android, views contained in layouts generally (though not always) have an id. The purpose of this id is to be able to identify particular views, for example:
Button button = (Button)layout.findViewById(R.id.button1);
button.setOnClickListener(...);
When you create a layout XML file, you're generally creating new ids for your views, the syntax is:
<Button
android:id="#+id/button1"
...
This will create an integer value in your project's R file (R.id.button1).
android.R.id, on the other hand, contains the ids of views that are either defined in the Android framework, or must be somehow referenced by it.
In your example, the AlertDialog.Builder creates a TextView with a fixed id, android.R.id.message. That way, you can take the view hierarchy returned by show(), and find the TextView inside it.
You can take a look at the full list of predefined ids in the documentation, however this list is not very informative in itself. The ids are generally mentioned in the documentation for each particular feature that uses them.
As an example of the other use case (marking your own view with a predefined android id), when using a ListFragment, if you provide a custom layout then you must include a ListView with id R.id.list. This is because the ListFragment class inspects the inflated layout to look for this widget. See the documentation:
ListFragment has a default layout that consists of a single list
view. However, if you desire, you can customize the fragment layout by
returning your own view hierarchy from onCreateView(LayoutInflater,
ViewGroup, Bundle). To do this, your view hierarchy must contain a
ListView object with the id "#android:id/list" (or list if it's in
code)
Optionally, your view hierarchy can contain another view object of any
type to display when the list view is empty. This "empty list"
notifier must have an id "android:empty".
android.R.id.message refers to the Resource with an id named message.
For example, the TextView here:
<AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:id="#+id/backbutton"
android:text="Back"
android:layout_x="10px"
android:layout_y="5px"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/message"
android:layout_x="10px"
android:layout_y="110px"
android:text="First Name"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</AbsoluteLayout>
Excuse me when I tell you that if you didn't know that, you really should read the Android Training Guide: http://developer.android.com/training/index.html
#matiash 's answer gives some good insight.
In the simplest terms, suppose you were providing a custom layout for a dialog, you would just access a view using R.id.viewId - an id which you defined in the custom xml layout.
android.R.id.message will let you access views that are predefined by android. Such as in your case, you're using an AlertDialog with a predefined layout. So, using the particular id you'll be able to access the TextView where you can set a message in the AlertDialog.
I am running into a very frustrating series of problems. I would like a definitive solution; thus, I will award bounty for this question.
Requirements
Generate a list of pairs of EditText views that automatically retain their values on orientation change (screen rotation).
The number of EditText pairs is determined at runtime.
Failing method: Use a ListView
Use a ListView that has an ArrayAdapter. The array adapter uses a layout to generate each pair of TextView views. The XML for a ListView item might appear as:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<EditText
android:id="#+id/edit_first"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<EditText
android:id="#+id/edit_second"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
The ArrayAdapter would simply inflate the layout for each view.
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View row = inflater.inflate(R.layout.listview_pair, parent, false);
The values entered into the EditText are not retained on orientation change. Since the EditText views are instantiated through the ArrayAdapter, the Android runtime has no way of automatically pre-populating the EditText views with their last state before rotation. This means we must save/restore user-input manually -- failing requirement #1.
Failing method: Use a TableLayout and TableRows
Instead of using a ListView, we can inflate one TableRow layout for each EditText pair and attach it to the TableLayout. Since we only know the number of pairs at runtime (per requirement #2) we must instantiate the layout programmatically. Something like:
for (int i = 0; i < numPairs; ++i)
{
TableRow row = (TableRow) View.inflate(this, R.layout.tablerow_pair, null);
table.addView(row);
}
This also fails to retain state on orientation change. Each pair of EditText views has the same Ids as all other EditText pairs in the ListView. This happens because we instantiate the same layout for each item in the list; thus, they all have the same Ids. On rotation, Android will give every pair of EditText values the same values since they share the same Id.
Recapitulation
So, is there a way to create a list of EditText pairs that retain their state automatically upon orientation change? It seems like there must be a way, yet researching this question is difficult because it's a fairly specialized use case. I would be glad to reward some bounty for somebody who can give me a nice explanation of this situation.
You can use setId() with your EditText widgets, whether you create those widgets via their constructors or via layout inflation (per your second strategy). Use generateViewId() to get distinct view IDs.
I need to put a series of checkboxes in the cell of a ListView. The problem is that I do not know ahead of time how many. I am guessing, but please correct me if I am wrong, that I need to create it programmatically. There will only be a few cells so I am not worried about reusability. How do I approach doing that?
No xml just build the cell in the getView method --> is this as simple as creating a View and add to it?
xml but append checkboxes to the xml --> I have no idea how to do this.
Is there some sort of dynamic xml CheckBox list that I can use?
I always prefer to use XML to separate the presentation of the controller (or code that controls the app)..
You just need to create a list view and define it's adapter. The adapter will have an XML (a row) where you can design the checkbox and the other elements that you want...
Check out the documentation where you can see an example of a listview with an adapter.
-- edit:
In your case you need to add the checkboxes programmatically in your adapter. Just define a view in your xml where you can add your checkboxes.
-- edit2:
Here is an example XML:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="50dp" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/checkboxContainer" >
</RelativeLayout>
</RelativeLayout>
In your adapter you
//get the container
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.NAME_OF_YOUR_XML, parent, false);
RelativeLayout your_container = (RelativeLayout) rowView.findViewById(R.id.checkboxContainer);
//declare your checkbox
CheckBox cb = new CheckBox(context);
cb.setText("your text");
// add the checkbox to your container
your_container.addView(cb);
This is just an example. I didn't test the code. Maybe you should use a linear layout instead of a relative layout to place your checkboxes easily..
easiest and most flexible is to use the ArrayAdapter and make an XML layout that looks like the single cell you'd want. Put your data in the array, create the Adapter and assign it to the ListView, and boom, your list has the right number of rows (cells). You can also customize the binding easily so that each cell has some information based on the corresponding Array entry.
Say I have a layout file that I would like to use as part of another layout, how would I do that?
For example, I have a table layout at /res/layout/table.xml. I want to use that table as a component inside a relative layout at /res/layout/relative_stuff.xml. Say my relative layout is to contain the table and two buttons.
The simple case is to do the combination completely inside the relative_stuff.xml file. But a better case would be the ability to set the table xml programmatically: the reality is I want to choose from many different tables, for now say two tables at: /res/layout/table_1.xml and /res/layout/table_2.xml.
So basically my main layout file is /res/layout/relative_stuff.xml. And I want to set either of two tables inside it programmatically.
You an re-use layouts using an include tag.
For example, using your example layout/table.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width=”match_parent”
android:layout_height=”match_parent”>
<include layout="#layout/table"/>
</LinearLayout>
If you don't want to do it in XML, you can use a LayoutInflater to inflate your XML and add it to whatever container you are using.
LayoutInflater mLayoutInflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
View tableLayout = mLayoutInflater.inflate(R.layout.table, (ViewGroup) findViewById(R.layout.root_id));
rootLayout.addView(tableLayout);
You can use Layout Inflator Service to add multiple add the activity.
I've been grappling with this problem throughout the life of my project. I have many lists in my project and most of them have headers. I have been making a separate layout file and adding it to the list using addHeaderView(). The problem is that when the data (ArrayList, in my case) is empty, the header doesn't appear. After hours of searching for a way to reuse the header layout as an empty view, I gave up and decided to replicate the layout in code and assign it as an empty view using setEmptyView(). The problem with this is that a lot of the headers contain clickable views and so I have to double all of my clickable logic for every view that is duplicated. I tried to include the header before but failed, mostly because I still don't quite understand how layouts are inflated, etc.
Finally, I have come up with a solution that I would like to share with the community, in case others are having a similar problem. I don't know if this is the best way to solve this problem and any feedback or suggestions would definitely be welcomed.
here is the code for the layout that contains the list list view:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
>
<ListView
android:id="#+id/lv_my_list"
style="#style/ListStyle"
android:headerDividersEnabled="false"
/>
<include
layout="#layout/my_list_header"
android:id="#+id/my_list_empty"
/>
</LinearLayout>
the style defines the layout width and height among other things.
now I have the layout file that contains the header view
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="#+id/my_list_header"
>
<Button
android:id="#+id/my_list_button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Click Me"
/>
</LinearLayout>
I know LinearLayout is not very efficient and I am experimenting with using merge or other efficiency measures, but this is the first version that works so I'm going with it for now. Finally the code:
// set up the header
myListView = (ListView)findViewById(R.id.lv_my_list);
View header = View.inflate(this, R.layout.my_list_header, null);
Button b = (Button)header.findViewById(R.id.my_list_button);
b.setOnClickListener(this);
myListView.addHeaderView(header);
// set up the empty view
LinearLayout ll = (LinearLayout)findViewById(R.id.my_list_empty);
b = (Button)ll.findViewById(R.id.my_list_button);
b.setOnClickListener(this);
meLocalListView.setEmptyView(ll);
The reason I wrapped the button in a layout is because I can set each instance of the button to use this as an OnClickListener and then refer to both of them with a single id: R.id.my_list_button in my onClick method. I need to test this a lot more but it seems to work for now. I haven't implemented it on all my lists yet, just the one so it might not work in all situations. If you have any suggestions please let me know because this has been a problem for me for a long time now. One problem might be that if you want to instantiate the button from the ID you would probably have to go through this entire process again to access the correct IDs?
If you want to show the header of a ListView when the list is empty, what you really want to do is to show the list itself rather than a separate empty view. So, the simplest way is to check whether your list is empty and set the list visible if it is not already:
getListView().setVisibility(View.VISIBLE);
You can test this very easily with the following code:
public class TestListActivity extends ListActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test_listview);
// create the empty grid item mapping
String[] from = new String[] {};
int[] to = new int[] {};
// prepare the empty list
List<HashMap<String, String>> fillMaps = new ArrayList<HashMap<String, String>>();
// fill in the grid_item layout
SimpleAdapter adapter = new SimpleAdapter(this, fillMaps, R.layout.test_listitem, from, to);
View listHeader =
((LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE))
.inflate(R.layout.test_listheader, null, false);
getListView().addHeaderView(listHeader);
getListView().setAdapter(adapter);
}
}
The list is empty, but the header is visible.
My solution is to create a view with the layout for the header, but set the background to be the same as the list items.
android:background="#android:drawable/list_selector_background"
But my solution does not work if I use 'include' to embed the header layout from another layout file. Do not know why.