Anees Hikmat Abu Hmiad
Anees Hikmat Abu Hmiad

Reputation: 3560

Is it possible to build listview with text filed & checkbox in android?

enter image description here

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

Answers (2)

PPartisan
PPartisan

Reputation: 8231

I put together a program that looks very similar to your above table. The results appear as below on an emulator:

Image

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

Nauman Afzaal
Nauman Afzaal

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

Related Questions