关于scrollview嵌套listview的方法。但官方并不推荐这种做法。

许学真
2023-12-01

scrollview嵌套listview确实有方法实现,但是并不为官方推荐。

You should never use a ScrollView with a ListView, because ListView takes care of its own vertical scrolling. Most importantly, doing this defeats all of the important optimizations in ListView for dealing with large lists, since it effectively forces the ListView to display its entire list of items to fill up the infinite container supplied by ScrollView.


至于应该怎么做最合适我大概有个思路还没有实现,就是只用一个listview,填充不同的布局,这只是我的想法,我并不知道这种做法是否是最恰当的做法,但这种做法确实没有用到scrollview嵌套listview。

但是我还是总结了网上分享的方法以备不时之需:

原文:http://www.lai18.com/content/2146670.html

解决方案一 手动设置ListView高度

解决思路:  手动设定ListView的高度,在ListView设置了Adapter之后使用。

注意:  Adapter中getView方法返回的View的布局只能是LinearLayout组成, 因为只有LinearLayout才有measure()方法 ,如果使用其他的布局,在调用listItem.measure(0, 0);时就会抛异常, 除LinearLayout外的其他布局的这个方法就是直接抛异常的。

[code]    /**
     * 动态改变ListView的高度
     * @param listView
     */
    public static void setListViewHeightBasedOnChildren(ListView listView) {
        if(listView == null) return;

        ListAdapter adapter = listView.getAdapter();
        if (adapter == null) {
            return;
        }

        int totalHeight = 0;

        // 开始计算ListView里所有Item加起来的总高度
        for (int i = 0; i < adapter.getCount(); i++) {
            View listItem = adapter.getView(i, null, listView);
            listItem.measure(0, 0);
            totalHeight += listItem.getMeasuredHeight();
        }

        ViewGroup.LayoutParams params = listView.getLayoutParams();
        // 高度 = 所有分割线高度 + Item总高度
        params.height = totalHeight + (listView.getDividerHeight() * (adapter.getCount() - 1));
        listView.setLayoutParams(params);
    }


解决方案二 一个ListView渲染整个ScrollView内容

解决思路:  把ScrollView内的所用内容,全部都放到ListView中去渲染,ListView上、下方的View都作为ListView的Item去渲染,ListView中的普通Item是平级关系。

注意:  这里涉及到一个 打破ViewHolder复用问题 。我们可以看getView源代码,convertView返回的是 旧视图 (可以看看 Android 万能ViewHolder ,这里详解了convertView来源)。那么问题来了! 如果一个ListView渲染整个ScrollView内容,那么不同Item之间的复用问题如何解决 。其实AdapterView已经提供了支持,这里涉及到Adapter.getItemViewType(position),覆写getItemViewType,自己写不同Item的判断逻辑,再定制对应的ViewHolder去进行相同Type的Item的复用。

[code]    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        int viewType = this.getItemViewType(position);

        switch (viewType) {
            case TYPE_ONE:
                TypeOneHolder holderOne;
                View v = convertView;
                if (v == null) {
                    LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                    v = vi.inflate(R.layout.layout_mylistlist_item_type_1, parent, false);
                    holderOne = new TypeOneHolder();
                    holderOne.text = (TextView) v.findViewById(R.id.mylist_itemname);
                    v.setTag(holderOne);

                } else {
                    holderOne = (TypeOneHolder) v.getTag();
                }

                MyListItem myItem = adapterData.get(position);
                if (myItem != null) {
                    if (holderOne.text != null) {
                        holderOne.text.setText(myItem.getItemName());
                    }
                }
                return v;
            case TYPE_TWO:
                TypeTwoHolder holder2;
                View v = convertView;
                if (v == null) {
                    LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                    v = vi.inflate(R.layout.layout_mylistlist_item_type_2, parent, false);
                    holder2 = new TypeTwoHolder();
                    holder2.text = (TextView) v.findViewById(R.id.mylist_itemname);
                    holder2.icon = (ImageView) v.findViewById(R.id.mylist_itemicon);
                    v.setTag(holderOne);
                } else {
                    holder2 = (TypeTwoHolder) v.getTag();
                }

                MyListItem myItem = adapterData.get(position);
                if (myItem != null) {
                    if (holder2.text != null) {
                        holder2.text.setText(myItem.getItemName());
                    }
                    if (holder2.icon != null)
                        holder2.icon.setDrawable(R.drawable.icon1);
                }
                return v;
            default:
                //Throw exception, unknown data type
                break;
        }

    }


解决方案三 自定义LinearLayout取代ListView

解决思路:  手动调用Adapter.getView把一个一个子View拿到后,循环有序添加到LinearLayout中(LinearLayout.addView)。

注意:  这里需要依赖外部的Adapter和各种Listener,那些OnClick,OnLongClick等,都要自己写,在每个子View添加到LinearLayout前,给每个子View加这些事件。

[code]public class ListViewLayout extends LinearLayout {

    private BaseAdapter adapter;
    private OnClickListener onClickListener;

    public ListViewLayout(Context context) {
        super(context);
    }

    public ListViewLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public ListViewLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public ListViewLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    /**
     * 设置BaseAdapter和OnClickListener
     *
     * @param adapter
     * @param onClickListener
     */
    public void setAdapterAndListener(BaseAdapter adapter, OnClickListener onClickListener) {
        this.init(adapter, onClickListener);
        int count = this.adapter.getCount();
        this.removeAllViews();

        // 初始化LinearLayout内容
        for (int i = 0; i < count; i++) {
            View v = this.adapter.getView(i, null, null);
            v.setOnClickListener(this.onClickListener);
            this.addView(v, i);
        }
    }

    private void init(BaseAdapter adapter, OnClickListener onClickListener) {
        this.adapter = adapter;
        this.onClickListener = onClickListener;
    }

}


[code]ListViewLayout listViewLayout = (ListViewLayout) this.findViewById(R.id.listViewLayout);
listViewLayout.setAdapterAndListener(adapter,listener);


解决方案四 自定义拓展ListView

解决思路:  通过重写ListView.onMeasure方法,达到对ScrollView适配的效果。

注意:  默认显示的首项还是ListView,要手动把ScrollView滚动至顶端。

[code]public class SVListView extends ListView {

    public SVListView(Context context) {
        super(context);
    }

    public SVListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SVListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public SVListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    /**
     * 重新算高度,适应ScrollView的效果
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
                MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, expandSpec);
    }

}


[code]ScrollView sv = (ScrollView) findViewById(R.id.scrollview);
// 手动把ScrollView滚动至顶端
sv.smoothScrollTo(0, 0);


对比总结

方案一:不用对控件做任何修改,只需要调用一个方法。但是每次Adapter数据变的时候,又要重新调用。

方案二:原本在不同Type的Item这块,复用问题就很头疼,这里后续添加了不同Type的Item的复用方案,一个不错的方案。

方案三:这个方案缺点非常明显,各种监听都要自己去给Item逐个添加,也不能达到复用效果,更何况要是多种Type类型的Item呢?

方案四:仅仅只是重写了onMeasure,原生的ListView的好多特性都还保存着,其中也包括notifyDataSetChanged方法,相比其他方法是 最趋近于完美的方法 ,也只是需要在Activity中设定ScrollView滚动至顶端。




 类似资料: