Reputation: 3560
All these data must come from a database, and I don't have a static number of fields, so what method can I use to build listView like in the Image ?
thanks a lot.
Upvotes: 2
Views: 443
Reputation: 8231
I put together a program that looks very similar to your above table. The results appear as below on an emulator:
To achieve it, you need to create a custom ListAdapter
. Inside the adapter, you override getView()
to create the layout for your row items. You will also need two XML 'layout' files if you want the results to appear as they do in my image: one as a template for the individual rows, and one to host the overall ListView
. If you don't want the permanent headers though, then you can just use the one row layout file. The Activity looks like this:
public class MainActivity extends ListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Helps create fake ISBN and quantity numbers
Random rand = new Random();
//Arrays to store fake Book Names, ISBN numbers and Quantity Numbers
final ArrayList<String> bookNames = new ArrayList<>();
final ArrayList<String> code = new ArrayList<>();
final ArrayList<String> quantity = new ArrayList<>();
// You can remove this if you don't want the permanent headers
setContentView(R.layout.activity_main);
//This populates our arrays with the fake names and numbers, so there is something to
//display in the list
for (int i = 1; i < 30; i++){
bookNames.add("Book No: " + i);
code.add(String.valueOf((rand.nextInt(100) * i) + 1000));
quantity.add(String.valueOf(rand.nextInt(5) + 1));
}
setListAdapter(new ListAdapter() {
For the ListAdapter
, leave everything as it is except for getCount()
, getView()
and getViewTypeCount()
(which you simply need to change to 1
). Set getCount()
to return bookNames.size()
. This will ensure that the ListView is always large or small enough to accommodate all the information in your array.
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) parent.getContext().
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.row, parent, false);
} else {
view = convertView;
}
TextView nameTV = (TextView) view.findViewById(R.id.nameTV);
TextView codeTV = (TextView) view.findViewById(R.id.codeTV);
TextView quantityTV = (TextView) view.findViewById(R.id.quantityTV);
nameTV.setText(bookNames.get(position));
quantityTV.setText(quantity.get(position));
codeTV.setText(code.get(position));
return view;
}
Differentiating between whether convertView is null or not is to determine whether the list is loading for the first time, or whether it is being populated as the user scrolls. The various TextView objects are populated by whatever item is in your array at the specified position
of the row. Here is the XML file for the row layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/checkBox"
android:checked="true" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/nameTV"
android:layout_weight="1" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/codeTV"
android:layout_weight="1" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/quantityTV"
android:layout_weight="1" />
I've set android:checked
to true
for the CheckBox so that they populated checked by default. finally, if you want to use permanent headers, here is the layout for the hosting activity.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/checkBoxHeader"
android:visibility="invisible"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Name"
android:id="@+id/nameTVHeader"
android:layout_weight="1"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="ISBN"
android:id="@+id/codeTVHeader"
android:layout_weight="1"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Quantity"
android:id="@+id/quantityTVHeader"
android:layout_weight="1"
android:textStyle="bold" />
</LinearLayout>
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@android:id/list"/>
I just created an invisible
CheckBox
in the header to ensure that the spacing was consistent, but you delete this and add an android:layout_marginLeft
attribute to the first TextView
item if you preferred.
EDIT:
To add the ability to change the quantity
figure is straightforward - it only requires changing the <TextView>
element in row.xml
to <EditText>
. However, the problem is that every time you scroll that row off the page, the ListView
will automatically refresh the original value and delete your edit. That is simply part of ListView's memory saving tactics.
To keep it, we need to save the new value to the array. The way I would do this is by using a pop-up Dialog
into which we can enter the new value. It would be possible to edit the text in the EditText
field directly as well, by implementing TextWatcher
or something similar, but I find this method easier to create. As such, in my example app, when you click on a quantity
field, it looks like this image.
To make this work, we need to first declare the ArrayList variables outside of onCreate
. This is because I will reference them later during a click event:
public class MainActivity extends ListActivity {
ArrayList<String> bookNames, code, quantity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//This isn't necessary, but I create it to make fake ISBN and quantity numbers
Random rand = new Random();
//Arrays to store fake Book Names, ISBN numbers and Quantity Numbers
bookNames = new ArrayList<>();
code = new ArrayList<>();
quantity = new ArrayList<>();
Next, inside getView()
, I'd add an onClickListener
to the quantityTV TextView
. This will launch a method called changeValue()
on a click event, that will create a pop-up Dialog
quantityTV.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
changeValue(position);
}
});
Finally, this method is added at the end of our MainActivity
, and contains the logic for launching a Dialog based on a template called update_value.xml
. When a user presses the "Accept" button, it updates the entry in the quantity
ArrayList
at the position our row is located. This is why I put a final int position
parameter in this method, because it means we can access our position
from getView()
.
void changeValue(final int position) {
LayoutInflater inflater = MainActivity.this.getLayoutInflater();
View popUp = inflater.inflate(R.layout.update_value, null);
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("Edit Value at Row: " + (position + 1)).setView(popUp)
.setPositiveButton("Accept", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
AlertDialog alert = (AlertDialog) dialog;
EditText value = (EditText) alert.findViewById(R.id.editText);
quantity.set(position, String.valueOf(value.getText()));
}
})
.setNegativeButton("Cancel", null)
.show();
}
The xml
file for the Dialog is very simple by the way, and just looks like this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New Value:"
android:textStyle="bold"
android:id="@+id/textView"
android:textSize="18sp" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
android:id="@+id/editText"
android:layout_gravity="center_horizontal"
android:textSize="16sp" />
</LinearLayout>
Edit 2:
I am not sure whether or not this is the recommended method for preventing CheckBoxes
, RadioButtons
etc. from repopulating every time a row is scrolled off screen, but it will work. I would recommend you also look into the setTag()
and getTag()
methods as they appear to be designed with this situation in mind.
What I do in my example is save the position
of the CheckBox
inside a Bundle
, and then check the position of each CheckBox against the position value stored inside the Bundle
. At the top of your MainActivity
, add the following:
Bundle bundle = new bundle();
Then, inside getView()
, add the following:
CheckBox checkBox = (CheckBox) view.findViewById(R.id.checkBox);
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (!isChecked) {
bundle.putInt("checked" + position, position);
} else {
bundle.remove("checked" + position);
}
}
});
if (bundle.get("checked" + position) != null){
checkBox.setChecked(false);
} else {
checkBox.setChecked(true);
}
This will work fine when scrolling through a list, but it isn't perfect - for instance, the list items will still refresh on a configuration change.
Upvotes: 1
Reputation: 1046
You can create a custom adapter and inflate your own item in getView as show below.
cell_view.xml
<CheckBox
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sample text"/>
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.cell_view, parent, false);
CheckBox checkBox = (CheckBox) rowView.findViewById(R.id.checkbox);
checkBox.setText("MY TEXT GOES HERE");// Update Your text
checkBox.setChecked(true);// Set checked/unchecked based on your functionality.
return rowView;
}
Upvotes: 1