我们在实际开发一个款Android App时,经常会遇到Scrollview和ViewPager和ListView同时使用的场景,如下图所示的需求:

NestScrollView包含ViewPager scrollview嵌套viewpager_android


从上面gif图片可以看出,在一个页面的最上方有一个首页轮播的效果,在首页轮播的下方又会有一个ListView或者是GridView来显示我们的网络请求数据。

对于上述需求,我们很容易想到先使用ViewPager的来实现首页轮播的效果,然后在下方添加一个ListView或者GridView并将数据通过自定义适配器的方式填充到视图当中,比如如下代码所示:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    
    <ScrollView
        android:id="@+id/scrollView_MainActivity"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_red_light" >
        
        <LinearLayout
            android:id="@+id/content_Layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/holo_orange_light"
            android:orientation="vertical" >
            
            <android.support.v4.view.ViewPager
                android:id="@+id/viewPager_MainActivity"
                android:layout_width="match_parent"
                android:layout_height="match_parent" >
            </android.support.v4.view.ViewPager>

            <LinearLayout
                android:id="@+id/layout_strips"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/holo_blue_light"
                android:gravity="center"
                android:orientation="horizontal" >
            </LinearLayout>

            <ListView
                android:id="@+id/listView_MainActivity"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@android:color/holo_green_light" >
            </ListView>
            
        </LinearLayout>
        
    </ScrollView>
    
</FrameLayout>



上面的xml布局文件中,最外层是一个FramLayout,然后内部有一个Scrollview(

背景颜色是红色),我们在使用Scrollview的时候,并需在其内部使用 一个LinearLayout,然后将所需要展示的内容都放到此LinearLayout中,从上面的布局代码中,可以看到我在此LinearLayout( 背景颜色为橙色)中,最上方放置一个ViewPager用来展示首页轮播效果,然后接着放置一个LinearLayout( 背景颜色为蓝色)用来展示首页轮播图当前所滚动的页面指示器,最后再放置一个ListView( 背景颜色为绿色)用来显示业务数据内容。


最后只需要在MainActivity中,对各个UI控件找到并初始化即可,如下代码所示,这里只贴出核心代码, 图片、自定义适配器等就不再贴出来了:

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		//初始化首页轮播所需要的ImageView,以及页面指示器的ImageView
		initImageList();
		//初始化ViewPager,并设置相应的适配器
		initViewPagers();
		//调用Handler的postDelay方法,实现ViewPager自动滚动效果
		loopViewPager();
		//初始化ListView,并填充数据
		initListView();
	}

	//创建Handler对象,用来实现每隔1秒钟,ViewPager自动滚动播放一帧的效果
	private Handler handler_loop = new Handler();
	
	private void loopViewPager() {
		handler_loop.postDelayed(new Runnable() {

			@Override
			public void run() {
				viewPager.setCurrentItem(viewPager.getCurrentItem() + 1, true);
				handler_loop.postDelayed(this, 2000);
			}
		}, 2000);
	}

	//创建List集合,用来模拟ListView所需要展示的数据
	private List<String> list = new ArrayList<>();
	//声明ListView控件
	private ListView listView;
	//声明ListView控件的适配器
	private ArrayAdapter<String> adapter;
	
	private void initListView() {
		listView = (ListView) findViewById(R.id.listView_MainActivity);
		for(int i = 0; i < 50; i++){
			list.add("this is " + i);
		}
		adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, list);
		listView.setAdapter(adapter);
	}
	
	//声明ViewPager对象,用来展示首页轮播效果
	private ViewPager viewPager;

	private void initViewPagers() {
		viewPager = (ViewPager) findViewById(R.id.viewPager_MainActivity);
		viewPager.setAdapter(new MyAdapter());
	}

	//创建List集合,用来保存需要在首页轮播中播放的ImageView对象
	private List<ImageView> stripList = new ArrayList<>();
	//创建List集合,用来保存需要在首页轮播中播放的页面指示器的ImageView对象
	private List<ImageView> imageList = new ArrayList<>();
	//声明首页轮播页面指示器的布局引用,通过此引用可以动态的添加指示器ImageView对象到视图当中
	private LinearLayout stripLayout = null;
	
	private void initImageList() {
		TypedArray array = getResources().obtainTypedArray(R.array.imageList);
		for(int i = 0; i < array.length(); i++){
			ImageView imageView = new ImageView(this);
			imageView.setImageDrawable(array.getDrawable(i));
			imageView.setLayoutParams(new LayoutParams(
					LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
			imageView.setScaleType(ScaleType.FIT_XY);
			imageList.add(imageView);
		}
		
		stripLayout = (LinearLayout) findViewById(R.id.layout_strips);
		for(int i = 0; i < array.length(); i++){
			ImageView imageView = new ImageView(this);
			imageView.setLayoutParams(new LayoutParams(25, 25));
			imageView.setScaleType(ScaleType.CENTER_CROP);
			imageView.setImageResource(R.drawable.strip_selector);
			
			if(i == 0){
				imageView.setEnabled(false);
			}else{
				imageView.setEnabled(true);
			}
			
			stripList.add(imageView);
			stripLayout.addView(imageView);
		}
	}

}



在MainActivity.java中,我们依次对ViewPager和ListView进行了初始化工作,但是运行上述代码之后, 实际显示效果并非我们所愿,如下图所示:

NestScrollView包含ViewPager scrollview嵌套viewpager_ListView_02


总结一下Scrollview中嵌套ViewPager和ListView出现的问题:

1、ViewPager如果在Scrollview中使用,并且宽高指定为match_parent或者wrap_content则不会显示内容, 但是如果将高度指定位200dp,则ViewPager则会将内容显示到屏幕上

2、ListView同ViewPager类似,必须指定固定高度之后,才可以显示相应的高度,否则只显示一个Item的内容高度,然后在ListView区域上下滑动时,Scrollview并没有效果, 而是实现的是ListView的上下滚动事件


上述总结的同时,其实也将解决方案也一并说出来了,也就是不管是ViewPager还是ListView都必须指定固定的高度,因此我们就要想办法在java代码中算出ListView和ViewPager的实际所占用的高度,然后通过setLayoutParams方法动态设置控件的高度。



~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~我是华丽丽的分割线~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

接下来我们就分别来看下如何依次解决这两个问题。


首先我们先来解决ListView显示不全的问题:

正如刚才所说,将ListView的高度指定为固定值可以解决此问题,但是我们在xml文件中无法知道ListView数据,也就不可能知道它所占得高应该是多少,因此我们要在Java代码中动态的算出, 如下代码:

/**
	* 动态的算出ListView实际的LayoutParams
	* 最关键的是算出LayoutParams.height
	*/
	public ViewGroup.LayoutParams getListViewParams() {
		//通过ListView获取其中的适配器adapter
	    ListAdapter listAdapter = listView.getAdapter();

	    //声明默认高度为0
	    int totalHeight = 0; 
	    //便利ListView所有的item,累加所有item的高度就是ListView的实际高度
	    for (int i = 0; i < listAdapter.getCount(); i++) { 
	        View listItem = listAdapter.getView(i, null, listView); 
	        listItem.measure(0, 0); 
	        totalHeight += listItem.getMeasuredHeight(); 
	    } 
	    //将累加获取的totalHeight赋值给LayoutParams的height属性
	    ViewGroup.LayoutParams params = listView.getLayoutParams(); 
	    params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1)); 
	    return params;
	}

将上述代码添加在iniListView方法最后,返回LayoutParams对象,并设置到ListView对象当中,运行会得到如下图的效果:

NestScrollView包含ViewPager scrollview嵌套viewpager_ViewPager_03

可以看到,ListView已经可以显示完全,并且现在在ListView上下滑动的同时,实现的是整个Scrollview的滑动事件  

但是也存在着两个问题:

1、第一次启动此App时,页面的顶端默认是ListView的第一个Item,必须手动滑动才能将ViewPager的布局显示出来,因此我们需要在代码中调用一下Scrollview的smoothScrollTo(0, 0)方法,将Scrollview滑动到最顶端

2、ListView的自定义BaseAdapter中的getView方法,在返回item的视图时,item的布局根元素必须是LinearLayout,因为只有LinearLayout才有measure方法


OK ! 接下来就剩下ViewPager的高度需要解决了 

ViewPager不能单纯的获取子View的高度来计算(因为如果使用ViewPager的话,适配器有时候用到的是FragmentPagerAdapter,不太方便计算其中的高度), 因为我们根据当前手机的宽高以及屏幕密度指定一个ViewPager高度的默认值,如下代码所示:

/*
		 * ViewPager如果放到ScrollView当中,需要在Java代码中通过LayoutParams动态的
		 * 设置一个固定值的高,否则ViewPager中的内容无法显示
		 */
		// 1、获取屏幕密度
		float desity = getResources().getDisplayMetrics().density;
		// 2、获取ViewPager在不同屏幕密度上的手机的高度
		int viewPagerHeight = (int) (VIEWPAGER_DEFAULT_HEIGHT * desity);
		// 3、通过setLayoutParams方式,给ViewPager动态设置高度
		viewPager.setLayoutParams(new LinearLayout.LayoutParams(
				LinearLayout.LayoutParams.MATCH_PARENT, viewPagerHeight));



运行以上代码之后, 效果如下, 完美^_^:

NestScrollView包含ViewPager scrollview嵌套viewpager_Scrollview_04