ListView in ScrollView potential workaround

2019-01-09 02:18发布

问题:

I've done all of the research on the matter. I know that Google thinks it's pointless and that the developers, know that it's not. I also know that there is no known workaround, but I know that I am close to making one. The user DougW posted this code:

public class Utility {
    public static void setListViewHeightBasedOnChildren(ListView listView) {
        ListAdapter listAdapter = listView.getAdapter(); 
        if (listAdapter == null) {
            // pre-condition
            return;
        }

        int totalHeight = 0;
        for (int i = 0; i < listAdapter.getCount(); i++) {
            View listItem = listAdapter.getView(i, null, listView);
            listItem.measure(0, 0);
            totalHeight += listItem.getMeasuredHeight();
        }

        ViewGroup.LayoutParams params = listView.getLayoutParams();
        params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
        listView.setLayoutParams(params);
    }
}

Which almost gets the job done for me. But when I try it, I get a NullPointer exception at the listItem.measure(0, 0) line. The listItem itself is initialized, but the method throws the exception anyway. Please tell me how I can fix this.

Here is my code:

public class ExpenseReportsActivity extends Activity {

    private ListView lvReports;
    private ExpenseReportListAdapter adapter;
    private Button btnSend;
    private Button btnCancel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.expensereports);

        lvReports = (ListView)findViewById(R.id.lv_reports);
        lvReports.setBackgroundResource(R.drawable.shape_expense_report_list);

        ColorDrawable cd = new ColorDrawable(0xFFffffff);
        lvReports.setDivider(cd);
        lvReports.setDividerHeight(1);

        adapter = new ExpenseReportListAdapter(this);
        lvReports.setAdapter(adapter);

        int totalHeight = 0;
        for (int i = 0; i < adapter.getCount(); i++) {
            View listItem = adapter.getView(i, null, lvReports);
            listItem.measure(0, 0);
            totalHeight += listItem.getMeasuredHeight();
        }

        ViewGroup.LayoutParams params = lvReports.getLayoutParams();
        params.height = totalHeight + (lvReports.getDividerHeight() * (adapter.getCount() - 1));
        lvReports.setLayoutParams(params);
    }
}

Another workaround I am working on is using my custom view's onWindowFocusChanged method. It tells the exacts height of the view. The problem is that the event isn't fired while I am still in my Activiy's onCreate method, nor in my Activity's onWindowFocusChanged method. I tried a custom event, but it never fired (it was placed inside my custom view's onWindowFocusChanged method and the listener was in my Activity's onWindowFocusChanged method).

回答1:

Ok, as far as I got your needs I think you may just use the ListView.addFooterView(View v) method:

http://developer.android.com/reference/android/widget/ListView.html#addFooterView(android.view.View)

It will allow you to have all your list items + "a few buttons" footer to be scrolled as a single block.

So the code should be smth like that:

import android.os.Bundle;
import android.view.LayoutInflater;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;

public class YourActivity extends ListActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        LayoutInflater factory = getLayoutInflater();
        LinearLayout footer = 
            (LinearLayout) factory.inflate(R.layout.your_a_few_buttons_footer, null);

        getListView().addFooterView(footer);

        String[] array = new String[50];
        for (int i = 0; i < 50;) { array[i] = "LoremIpsum " + (++i); }

        setListAdapter(
            new ArrayAdapter<String>(this, R.layout.list_item, array)
        );
    }   
}

Note, the doc says addFooterView() should be called BEFORE the setListAdapter().

UPDATE: to add a View at the top of the list use ListView.addHeaderView(View v). Note that, for instance, LinearLayout is also a View. So you can put anything you want as a header or a footer and it'll be scrolled with the list as an indivisible block.



回答2:

Out of curiosity, is your layout using RelativeLayout? If so, calling measure(0,0) will always throw an NPE, but a LinearLayout will not. http://groups.google.com/group/android-developers/browse_thread/thread/5a947482d7dcb605

Change it to Linear and you can make that call. I hope that helps!


I have a situation in my app where I have paragraphs of text, imageviews, all sorts of information on a given subject...and then, depending on the item, there is possibly a ListView of comparison data in the middle of all of that info. About one in every 10 items has it, nestled between all the text. The comparison data is never more than 4 items at max, so I don't want the ListView to scroll, ever. I just want the ListView to appear in its entirety at the exact point I specify.

Adding them all as nested Linear Layouts is insane, so is using MergeAdapter to put all of that together when I may not even have a ListView on screen. And using complex ListView headers & footers is out of the question as well.

I'm not the first person to want that kind of functionality, and I won't be the last. The above solution is nearly perfect, it sizes my ListView so that it's full on screen, and all the scrolling comes from the ScrollView parent. (It's easy as sin to do on the iOS SDK, btw., and a lot of apps over there do similar things; we'll need a good solution for this.)