0

I have an AlertDialog that I inflate from this layout:

<?xml version="1.0" encoding="utf-8"?>    
    <ListView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/List"
        android:layout_alignParentTop="true"
        android:layout_width="match_parent"
        android:layout_height="400dp"/>

I need every item of the list to be a view described by this xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="#20f0f0f0"
    android:orientation="horizontal" >

    <CheckBox 
        android:layout_gravity="center_vertical"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:id="@+id/cb_persistent"/>

    <TextView
        style="@style/Label"
        android:layout_gravity="center_vertical"
        android:gravity="center_horizontal|center_vertical"
        android:layout_width="fill_parent"
        android:layout_height="@dimen/button_height"
        android:layout_toLeftOf="@+id/btn_connect"
        android:layout_toRightOf="@+id/cb_persistent"
        android:id="@+id/lbl_name_address"/>

    <Button
        android:layout_gravity="center_vertical" 
        style="@style/Button.Plain"
        android:layout_width="wrap_content"
        android:layout_alignParentRight="true"
        android:id="@+id/btn_connect"
        android:text="@string/Connect"/>
</RelativeLayout>

And this is the adapter I'm trying to use for it. I've also tried implementing ListAdapter, result was the same: only 1 list row is showing, the dialog is exactly 1 row high. With this adapter it's the last row, with ListAdapter - the first. What am I doing wrong?

private class ListItem extends View {

        public ListItem(Context context) {
            super(context);
            View content = ((LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.list_element, null);
            setView(content);

            m_cbPersistent = (CheckBox) content.findViewById(R.id.cb_persistent);
            m_btnConnect   = (Button)   content.findViewById(R.id.btn_connect);
            m_lblName      = (TextView) content.findViewById(R.id.lbl_name_address);
        }

        public CheckBox m_cbPersistent = null;
        public Button   m_btnConnect   = null;
        public TextView m_lblName      = null;
    }

class NewAdapter extends ArrayAdapter<ListItem> 
    {

        public NewAdapter(Context context) {
            super(context, 0);
            m_context = context;
        }

        @Override
        public int getCount() {
            return m_items.size();
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if (position < getCount())
                return m_items.elementAt(position);
            else
                return null;
        }

        void addNewItem ()
        {
            m_items.add(new NetworkCameraEntry(m_context));
        }

        void removeItem (int index)
        {
            if (index < getCount())
            {
                m_items.remove(index);
            }
        }

        @Override
        public boolean isEmpty() {
            return m_items.size() <= 0;
        }

        @Override
        public boolean areAllItemsEnabled() {
            return true;
        }

        @Override
        public boolean isEnabled(int position) {
            return true;
        }

            private Context m_context = null;

        private Vector<ListItem> m_items = new Vector<ListItem>();
    }

This is how I initialize it in the AlertDialog's constructor:

public class MyDialog extends AlertDialog {

public MyDialog(Context context) {
        super(context, 0);

        View content = ((LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.network_cameras_list_window, null);
        setView(content);

        m_adapter = new NewAdapter(context);
        m_list = (ListView) content.findViewById(R.id.List);
        m_list.setAdapter(m_adapter);   

        m_adapter.addNewItem();

        m_adapter.addNewItem();
    }

    private ListView m_list = null;
    private NewAdapter m_adapter = null;
}
Violet Giraffe
  • 32,368
  • 48
  • 194
  • 335

1 Answers1

1

m_items.size is 1 when the adapter is constructed and gets populated over time. m_items.size is cached so you have to invalidate the adapter on each m_items.add

Yet this is not the way to go. A better option is to get your data populated before constructing the adapter and pass is to the adapter. Any altering of the data you have to notify / invalidate the adapter with

notifyDataSetInvalidated();
notifyDataSetChanged();
weakwire
  • 9,284
  • 8
  • 53
  • 78
  • 1
    if you call `notifyDataSetInvalidated();` you will have to reconstruct the adapter as you are effectively destroying it's contents. – ScouseChris Sep 18 '12 at 09:17
  • yes. in his case Option B is the way to go(create data first and adapter only job is to display them) – weakwire Sep 18 '12 at 09:18
  • Didn't help. Tried both the calls you've specified, then tried only `notifyDataSetChanged();` - still only one row showing. – Violet Giraffe Sep 18 '12 at 09:20
  • as @ScouseChris mentioned this will not work in you case. Did you try contructing the data first and pass them as argument to the adapter? This is the way to go. Adapter should only display data.. not generate them – weakwire Sep 18 '12 at 09:22
  • I very much don't like the idea of reconstructing the adapter every time data changes, is there no way to avoid it? And it doesn't generate data, it only generates views to display data. And no, constructing adapter and then setting it to the ListView changes nothing. – Violet Giraffe Sep 18 '12 at 09:22
  • can't you just calculate all the data first and pass is to the adapter? is there any obstacle? – weakwire Sep 18 '12 at 09:24
  • Data isn't the problem. Like I said, I tried constructing adapter first and only binding it with ListView afterwards. It still only shows one row, I don't understand why. Are you sure my layout is fine? – Violet Giraffe Sep 18 '12 at 10:03
  • I think the idea of creating the views while you add list item is not encouraged. I personally prefer passing the data to the adapter. You may want to make your getView() intelligent, and construct the views as per demand. Inside getView you may even reuse the convertView parameter. Please refer [link](http://stackoverflow.com/questions/3514548/creating-viewholders-for-listviews-with-different-item-layouts) – Arunkumar Sep 18 '12 at 10:06
  • @Ironhide: Thanks. I'm aware that getView is generally used for it, but how does it matter whether I create a View beforehand and fetch from cache in getView, or create immediately in getView? I don't think that's the reason behind my problem. – Violet Giraffe Sep 18 '12 at 10:10
  • @VioletGiraffe AFAIK what you want to do is a hack in the way adapter recycling works. You'd be better of creating a scrollview and addViews in it on demand. If not you'll need a very sophisticated custom adapter and i think that's another question entirely – weakwire Sep 18 '12 at 10:23
  • @weakwire: Are you sure ListView isn't supposed to support custom views? I thought it is, otherwise why even cumber its API with adapters and related stuff? – Violet Giraffe Sep 18 '12 at 10:27
  • 1
    @VioletGiraffe ofc custom views are supported.But adapters work best with data preloaded and then construct the adapter and pass the data. If your data are going to change in time call notifyDataSetInvalidated(). If you add data in your adapter (eg with a getter of a mutable collection) call notifyDataSetChanged() . And that's that. Now if you are going to mutate the data from within the adapter i'm afraid is not a safe task to work with. IMO Even if you make it work now it might break later. – weakwire Sep 18 '12 at 10:48
  • @VioletGiraffe could you try adding a statement m_adapter.notifyDataSetChanged() just before your constructor of MyDialog ends. – Arunkumar Sep 18 '12 at 10:55
  • @VioletGiraffe It does matter if you fetch the view from cache.. bcos as per your screen height, say for eg. your listview can display only 4 items, and your data size is more than that (say 20). In that case your cache will contain 20 views in memory, each holding an expensive reference to the context. Now imagine if your data size grows to thousands. However if you use ViewHolder pattern along with getview you would be always having 4 views in memory inspite of any number of items, which would make your listview more UI responsive and improves performance – Arunkumar Sep 18 '12 at 10:56