您的位置:首页 > 移动开发 > Android开发

17.Android ScrollView嵌套ListView 技巧

2015-09-28 17:49 429 查看

17.Android ScrollView嵌套ListView 技巧

Android ScrollView嵌套ListView 技巧
需求所致

巧妙避免

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

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

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

解决方案四 自定义拓展ListView

对比总结

需求所致

现在的需求中,一个简单的列表已经不能满足产品的设计。往往都是在一个View中会有许多控件,ListView又包含在其中。我们都知道,这样的页面肯定是一个屏幕的长度都不能符合的,然而ListView又是滚动的,这次就出现了ScrollView嵌套ListView的尴尬场面。但是!ScrollView又只能有一个子控件,又只能用外面包装一个无用的Layout去对接。

巧妙避免

其实很多时候的需求往往是,一个列表,然后上面有一堆View。这次可以不用ScrollView嵌套ListView的处理。直接把上面拿一堆View包裹起来,作为ListView的HeadView添加进去。 同理,FootView也是一样,但是过于复杂的页面,可以考虑ScrollView嵌套ListView的处理。

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

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

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

/**
* 动态改变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的复用。

@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加这些事件。

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;
}

}


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


解决方案四 自定义拓展ListView

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

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

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);
}

}


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滚动至顶端。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息