我们在实际开发一个款Android App时,经常会遇到Scrollview和ViewPager和ListView同时使用的场景,如下图所示的需求:
从上面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进行了初始化工作,但是运行上述代码之后, 实际显示效果并非我们所愿,如下图所示:
总结一下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对象当中,运行会得到如下图的效果:
可以看到,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));
运行以上代码之后, 效果如下, 完美^_^: