前言

最近在项目中遇到一个需求,要求为ScrollView+ViewPager+Fragment实现UI,其中ViewPager通过TabLayout实现Fragment的切换,fragment中使用RecyclerView(这里以简单ListView代替)展示数据。总体效果如下:

很大众化的界面设计,唯一不同的是顶部标题栏是半透明的,这种需求也是因为要和iOS的界面达成一致,因此UI搭建的稍显麻烦了一些,在开发中遇到一些界面布局以及事件消费的冲突问题:

1.ScrollView+ViewPager+Fragment数据不显示/不完全显示的冲突

2.FrameLayout/LinearLayout或其他布局点击穿透(click through)问题

3.ScrollView嵌套ListView不在最顶部显示的问题

1.界面搭建

界面搭建结构简图如下:

笔者采用的根布局为FrameLayout,上面第一层为ScrollView,里面包裹为一个RelativeLayout(注意ScrollView有且只能有一个子布局),其子布局为一个空白的TextView和ViewPager,空白的TextView高度等于顶层布局中标题栏和指示器的高度和;而ViewPager中添加数个包含有ListView的Fragment。

这个空白的TextView使得用户进入界面时,ListView正好显示在标题栏和指示器的正下方,而且用户滑动ScrollView时,ListView的Item会显示在半透明的标题栏底部。这也正是要选择FrameLayout作为跟布局的原因,仅仅使用线性布局和相对布局是很难达成这种效果的。

这种稍显麻烦的布局也出现了一些问题,导致

ScrollView+ViewPager+Fragment数据不显示/不完全显示的冲突

问题分析

出现问题有几种:

1.ListView数量仅仅显示一条:
出现原因:

在ScrollView中嵌套ListView空间,无法正确的计算ListView的大小,故可以通过代码,根据当前的ListView的列表项计算列表的尺寸。

解决方法:

1.取消ScrollView+ListView的布局,改为使用ListView.addHeaderView,为listView添加头布局。

[传送门]ListView.addHeaderView使用方法:

2.重写ListView的onMeasure方法,使得根据当前的ListView的列表项计算列表的尺寸并展示。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // TODO Auto-generated method stub
    int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
            MeasureSpec.AT_MOST);
    super.onMeasure(widthMeasureSpec, expandSpec);
}

笔者在此使用的是第二个方法,重写一个继承ListView的MyListView。这样做也有一个弊端,就是ListView的item复用就成了摆设,在使用listview承载大量item的时候,要考虑周全选择合适的方式解决问题,推荐使用第一种

2.ListView数据不显示:
出现原因:

笔者遇到这个问题时,一头雾水,因为根据log显示,数据源是有数据的,但就是listView没有展示,通过各种调试,发现当ScrollView的子布局为Linearlayout(包裹textview+viewpager)时,ListView数据无法展示,但改为使用RelativeLayout时没有任何问题。

解决方法:

将ScrollView的子布局改为RelativeLayout.

主界面代码如下(请注意里面有自定义的view,仅供参考,不能直接复制粘贴使用):

<FrameLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.mei_husky.testtab.MainActivity">


    <com.mei_husky.testtab.view.MyReboundScrollView
        android:id="@+id/scrollView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <!--必须用相对布局 线性布局不显示!-->
        <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="vertical">
            <TextView
                android:id="@+id/tv_hide"
                android:layout_width="wrap_content"
                android:layout_height="95dp" />
            <com.mei_husky.testtab.view.CustomViewPager
                android:layout_below="@id/tv_hide"
                android:id="@+id/viewpager"
                android:layout_width="match_parent"
                android:layout_height="match_parent"></com.mei_husky.testtab.view.CustomViewPager>
        </RelativeLayout>

    </com.mei_husky.testtab.view.MyReboundScrollView>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <!--放置点击穿透-->
        <TextView
            android:clickable="true"
            android:background="#44ffffff"
            android:id="@+id/tv_title"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:text="这是标题栏"
            android:textSize="18sp"
            android:gravity="center"
            />

        <android.support.design.widget.TabLayout
            android:id="@+id/tab"
            android:layout_width="match_parent"
            android:layout_height="55dp"
            android:background="#ffffffff"
            ></android.support.design.widget.TabLayout>
    </LinearLayout>
    </FrameLayout>

2.Fragment+RecyclerView(ListView或GridView)

RecyclerView的好处是能够比较方便的进行不同种类布局展示的切换,相对麻烦一些就是config的设置和监听的实现,不过这些通过度娘或者谷歌都能很方便的查到。

使用了listView或者GridView时,不可避免遇到一个问题,

ScrollView嵌套ListView不在最顶部显示的问题

问题分析

在布局中我们可以了解到,包含listview的ViewPager所在布局的上方还有一个空白的TextView,两者同在一个ScrollView的子布局下,因此在展示界面时,空白的TextView理应在(0,0)的位置上,但实际上确实空白的TextView被挤上去了,listView直接在(0,0)的位置上,只有下拉,我们才能看到我们所设置的textview,显然这是不合适的。

出现问题的效果如下:

出现原因:

个人猜测:Scroll包含ListView时,在展示界面时,ListView抢夺了ScrollView的焦点,因此直接以listView的左上角为(0,0)点展示在界面上。GridView,RecyclerView同理。

解决方法:

解决方案有两种:

1.设置myScrollView.smoothScrollTo(0,20);在数据展示完毕之后,使ScrollView直接跳转到顶部的位置。

2.设置ListView.setFocusable(false);相对应的ListView(GridView&RecyclerView),使其展示数据不获得焦点。

这两种解决方案经过笔者实践,都是可以解决问题的。笔者推荐两种方案同时使用,因为在这个项目中我们的SrcollView和ListView的逻辑是不在一个界面的(ScrollView的处理逻辑在MainActivity中,而listView则是在ViewPager中的fragment对象中做的处理)。

因此,以防万一,笔者在MainActivity中的onResume()方法中添加myScrollView.smoothScrollTo(0,20);在Fragment中添加了ListView.setFocusable(false);

这样做是考虑到,后续的下拉刷新或者分页等功能的实现做准备,因为下拉刷新或者分页功能,不需要处理ScrollView的逻辑,因此直接处理listView的adapter和数据源即可。两种方案的同时使用,为后面功能的拓展提供了方便,而不需要再次处理类似的问题。

*ps:ScrollView包裹listView同样也会触发另外一个弊端,就是listView无法直接获取滑动事件(因为滑动被ScrollView消费了),这样下拉刷新和上拉获得更多的功能实现更加困难一些,这个问题的解决方案将会下次更新。*

3.调试时的小问题

通过设置一个简单的数据源和adapter,我们可以将数据展示在界面上,同时,通过

android:background="#44ffffff" //为了显示效果此处透明度为44,推荐透明度为ee或dd

将标题栏设置为半透明的,在拖动listView(实际上是拖动ScrollView)的同时,listView的item也可以通过半透明的标题栏看到。我们给listView的item设置点击事件,弹出吐司提示条目的position值。

但是这样还出现了了一个问题,我们假设这样一种情况,标题栏上有一些button,比如搜索或者返回按钮,用户点击按钮实现相对应的功能。但是如果用户误操作,没有点到按钮,而是点到了标题栏其他的地方,这时,标题栏的正下方是listView的item,会发生什么情况呢?

2.FrameLayout/LinearLayout或其他布局点击穿透(click through)问题
出现原因:

显然这不是我们所想达到的效果,我们所希望的是,用户点击按钮,实现对应功能,点击标题栏其他地方,也不会触发listView中item的点击事件。

解决方法:

在xml布局文件中给标题栏的根部局中添加一行代码

android:clickable="true"

使得该布局在收到点击事件时,直接消费该事件,而不会将点击事件传给下一个weight。

总结

至此为止,功能基本实现

第一次写博文,难免会出现各种错误,望谅解,若有问题,欢迎各位前辈指出及勘正!