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滚动至顶端。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories