Android学习之当ScrollView遭遇ListView(GridView)

来源:互联网 发布:单片机编程基础知识 编辑:程序博客网 时间:2024/05/01 09:38

概述

如今app的界面可谓是形形色色,有的简洁大方小清新,有的高端大气上档次,还有的狂拽炫酷吊炸天...总之就是各有特色;当然我们也不是来评头论足的啦,不管哪种界面都是由Android基本的View组成的,不同的布局LinearLayout、RelativeLayout,不同的控件ListView、GridView等等;接下来我们来看下图的界面我们来分析该如何实现:


其实看到上面这个布局大家应该已经想到了,尤其是做过商城之类的app的开发者,这种界面应该是很常见了(当然商城类的还要复杂些);界面中很明显列表项应该是ListView实现的,但是头布局和底部布局也是可以跟着一起滑动的,那应该怎么办呢?哈哈,我们可以用ScrollViev嵌套ListView,或者让ListView加载复杂布局,我想大概就是这两种方式了吧;好吧今天我们就用这两种方式来做出这个界面并比较优劣;

1.ScrollView嵌套ListView
2.ListView加载复杂布局

ScrollView嵌套ListView具体实现步骤

因为ScrollView和ListView都是可以滚动的,ScrollView嵌套ListView之后只能显示一项,因为无法计算ListView的高度。所以我们需要获取ListView的总的高度。其实这种方式实现起来略简单,只需重写ListView的onMeasure方法直接计算出ListView的高度就可以了。剩下了就是把不同的布局实例化并填充数据集就好了。
/** * 重新该方法达到ListView自适应ScrollView */@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,  MeasureSpec.AT_MOST);super.onMeasure(widthMeasureSpec, expandSpec);}

自定义ListView:
/** * 自定义ListView用以计算他的高度, */public class CustomListView extends ListView {public CustomListView(Context context, AttributeSet attrs,int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);// TODO Auto-generated constructor stub}public CustomListView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);// TODO Auto-generated constructor stub}public CustomListView(Context context, AttributeSet attrs) {super(context, attrs);// TODO Auto-generated constructor stub}public CustomListView(Context context) {super(context);// TODO Auto-generated constructor stub}/** * 重新该方法达到ListView自适应ScrollView */@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,  MeasureSpec.AT_MOST);super.onMeasure(widthMeasureSpec, expandSpec);}}

ListView加载复杂布局

其实这种方式说起来也不是很难,但是好多人觉得这样没有办法使用ViewHolder了导致findViewById很费时间,效率低下。今天我们就来继续使用ViewHolder让ListView继续复用View。

我们都应该知道ListView和GridView自带缓存机制,他会将在屏幕之外的View回收起来并加以缓存,当有新的Item要显示的时候就可以复用布局。但是如今我们的View是3种类型,ListView没法分辨要返回哪个View。为之奈何?嘿嘿,其实我们在实现ListView的BaseAdapter的时候一般都没有复写getItemViewType(int position)getViewTypeCount()这两个方法,殊不知这两个方法有效解决了适配器不知道返回哪个缓存View,通过复写这两个方法就可以返回正确类型的View,这样我们就不用担心视图错乱了。

布局实现

view_listview.xml
<?xml version="1.0" encoding="utf-8"?><ListView xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/multiListView"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="#ffe6ebef"    android:divider="@null"    android:dividerHeight="3dp"    android:listSelector="@android:color/transparent"    android:transcriptMode="alwaysScroll" />
一个ListView搞定

view_header.xml 头布局
<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="wrap_content" >    <android.support.v4.view.ViewPager        android:id="@+id/viewPager"        android:layout_width="match_parent"        android:layout_height="200dp" />    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_gravity="bottom"        android:orientation="vertical" >        <com.viewpagerindicator.CirclePageIndicator            android:id="@+id/indicator"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:padding="10dip" />                <TextView            android:layout_width="match_parent"            android:layout_height="36dp"            android:background="#99000000"            android:gravity="center"            android:text="清纯美女"            android:textColor="#ffffff" />    </LinearLayout></FrameLayout>

listview_item.xml 列表布局
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:layout_margin="16dp"    android:background="#ffffff"    android:orientation="horizontal"    android:paddingLeft="8dp"    android:paddingRight="8dp" >    <ImageView        android:id="@+id/imageView"        android:layout_width="108dp"        android:layout_height="72dp"        android:scaleType="centerCrop"        android:src="@drawable/ic_launcher" />    <LinearLayout        android:layout_width="match_parent"        android:layout_height="72dp"        android:gravity="center"        android:orientation="vertical" >        <TextView            android:id="@+id/titleTv"            android:layout_width="match_parent"            android:layout_height="0dp"            android:layout_weight="1"            android:padding="5dp"            android:singleLine="true"            android:text="标题"            android:textSize="20sp"            android:textStyle="bold" />        <TextView            android:id="@+id/contentTv"            android:layout_width="match_parent"            android:layout_height="0dp"            android:layout_weight="1"            android:gravity="bottom"            android:maxLines="3"            android:minLines="1"            android:padding="5dp"            android:text="内容"            android:textColor="#9cabab"            android:textSize="12sp" />    </LinearLayout></LinearLayout>

view_bottom.xml 底部布局
<?xml version="1.0" encoding="utf-8"?><TextView xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:text="底部布局"    android:gravity="center"    android:textSize="20sp"    android:padding="16dp"    android:background="#ffffff"    android:orientation="vertical" />    

ListView数据适配器的实现:
MyAdapter.java 这个其实就是实现布局的核心类了,希望可以帮到大家
public class MyAdapter extends BaseAdapter {private static final String TAG = "MyAdapter";/** * 3种视图类型 */private static final int TYPE_HEADER = 0;private static final int TYPE_LIST = 1;private static final int TYPE_BOTTOM = 2;private Context context;private LayoutInflater mInflater;private ViewPager mPager;private PagerAdapter mPagerAdapter;/** * 列表项数据集 */private List<ListItem> mList;/** * ViewPager数据集 */private List<ImageView> mViewpagerList;/** * ViewPager的指示器 */private PageIndicator mIndicator;/** * 记录当前ViewPager的页 */private int currentItem = 0;public MyAdapter(Context context, List<ListItem> mList,List<ImageView> mViewpagerList) {this.context = context;this.mList = mList;mInflater = LayoutInflater.from(this.context);this.mViewpagerList = mViewpagerList;initPageAdapter();}@Overridepublic int getCount() {// TODO Auto-generated method stubreturn mList.size() + 2;}@Overridepublic ListItem getItem(int position) {return mList.get(position - 1);}@Overridepublic long getItemId(int position) {// TODO Auto-generated method stubreturn position;}/** * 该方法必须实现,否则无法正确返回缓存的视图 */@Overridepublic int getItemViewType(int position) {if (position == 0) {return TYPE_HEADER;} else if (position == getCount() - 1) {return TYPE_BOTTOM;}return TYPE_LIST;}/** * 该方法必须实现,否则无法正确返回缓存的视图 */@Overridepublic int getViewTypeCount() {return 3;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {if (null == convertView) {convertView = initConvertView(position, convertView, parent);}setConvertViewData(position, convertView);return convertView;}/** * 为convertView设置数据 *  * @param position * @param convertView */private void setConvertViewData(int position, View convertView) {if (getItemViewType(position) == TYPE_LIST) {ViewHolder holder = (ViewHolder) convertView.getTag();ImageUniversalLoader.displayImage("drawable://" + getItem(position).getImgResId(),holder.mImageView);holder.mTitleTv.setText(getItem(position).getTitle());holder.mContentTv.setText(getItem(position).getContent());} else if (getItemViewType(position) == TYPE_HEADER) {mPager.setAdapter(mPagerAdapter);mIndicator.setCurrentItem(currentItem);mIndicator.setOnPageChangeListener(new OnPageChangeListener() {@Overridepublic void onPageSelected(int position) {currentItem = position;}@Overridepublic void onPageScrolled(int arg0, float arg1, int arg2) {// TODO Auto-generated method stub}@Overridepublic void onPageScrollStateChanged(int arg0) {// TODO Auto-generated method stub}});}}/** * 初始化要填充的convertView *  * @param position * @param convertView * @param parent */private View initConvertView(int position, View convertView,ViewGroup parent) {if (getItemViewType(position) == TYPE_HEADER) {convertView = mInflater.inflate(R.layout.view_header, parent, false);mPager = (ViewPager) convertView.findViewById(R.id.viewPager);mIndicator = (PageIndicator) convertView.findViewById(R.id.indicator);mPager.setAdapter(mPagerAdapter);mIndicator.setViewPager(mPager);} else if (getItemViewType(position) == TYPE_BOTTOM) {convertView = mInflater.inflate(R.layout.view_bottom, parent, false);} else {ViewHolder holder = new ViewHolder();convertView = mInflater.inflate(R.layout.listview_item, null);holder.mImageView = (ImageView) convertView.findViewById(R.id.imageView);holder.mTitleTv = (TextView) convertView.findViewById(R.id.titleTv);holder.mContentTv = (TextView) convertView.findViewById(R.id.contentTv);convertView.setTag(holder);}return convertView;}/** * 初始化ViewPager的Adapter */private void initPageAdapter() {mPagerAdapter = new PagerAdapter() {@Overridepublic boolean isViewFromObject(View view, Object o) {return view == (o);}@Overridepublic int getCount() {// TODO Auto-generated method stubreturn mViewpagerList.size();}@Overridepublic void destroyItem(ViewGroup container, int position,Object object) {if (mViewpagerList != null && mViewpagerList.size() > position&& mViewpagerList.get(position) != null)container.removeView(mViewpagerList.get(position));}@Overridepublic Object instantiateItem(ViewGroup container, int position) {container.addView(mViewpagerList.get(position), 0);return mViewpagerList.get(position);}};}/** * 可复用的ViewHolder */private class ViewHolder {ImageView mImageView;TextView mTitleTv;TextView mContentTv;}}

以上就是主要的实现类,有几个地方可能我要稍加解释一下

1.currentItem这个属性:这个属性主要是用来记录ViewPager滑动到那一项了,因为ListView的回收机制,当ViewPager离开屏幕又回到屏幕时实际上是重新加载了PagerAdapter,此时指示器又从0开始,所以记录下用以设置我们之前滑动的到的位置。

2.getCount()方法中我们加了2主要是因为头布局和底部布局,position = 0 实际上返回的是头布局而不是列表的第一项,所以在getItem()方法中我们获取列表项就变成了mList.get(position - 1);

好了剩下的我觉得没什么要说了,这样实现Adapter之后同样可以轻松利用ListView的回收缓存机制,大大提高了利用率和效率;

两种方式的比较

用ScrollView嵌套ListView只需重写onMeasure的方法即可,剩下的就和普通的布局没什么两样了。但是这种方式有个致命的问题,因为ListView嵌套在ScrollView中我们无法监听ListView的ScrollListener事件,ListView直接计算了总高度导致失去了缓存机制和回收机制,在第一次加载的时候都会把ListView的所有项加载进来。而你滑动的时候也不会再动态的执行getView方法了,原因就是ScrollView消费了ScrollListener事件,滚动也是ScrollView在滚动。那么我们想象如果列表项是大量的图片加载我想这样内存消耗和效率肯定会很低下弄不好会出现OutOfMemoryError;


想来说了上面的这些大家也知道我的观点了,我更倾向于后者ListView加载不同的视图,由于ListView和GridView他们拥有天生的缓存机制,就凭这一点我想把ScrollView比下去了;当然上面的方式也不是不好,所有的问题都是有参照点;其实如果列表项不是很多,而且大多是文字形式的我可能就会用上面的方式,简单方便。如果是图片项很多并且底部还有加载更多等选项我会选择后者。

总结

同一种UI不同的实现方式,其实也没法说哪种更优,主要还是看我们在合适的场合用合适的方式,既要讲究开发效率又要追求运行效率;编程本来就不是件难事,难的是如何把它做好,如何写出高质量的代码,当然在产品眼中他们是永远看不到;可是作为一个有志之士,我觉得我们还是要不断的优化优化再优化,重构重构再重构;





0 0
原创粉丝点击