前言
最近在做一些项目和毕业设计,所以自从上次梳理完数据结构之后,一直想写些什么,但是又没有比较好的内容,所以博客从过完年之后就停更了很长很长很长一段时间,不过这次在做项目的时候,正好遇到一个我本以为很简单,结果折腾了好久的一个问题,其实这个问题对于做Android开发的同学来说,并不陌生,那就是滑动布局的互相嵌套。
当然并不仅限于标题中写的这种嵌套,只要是可滑动的布局,例如GridView,各种自定义的可滑动控件等等,都会遇到这个问题,如果嵌套相对简单的话,例如ScrollView
嵌套RecyclerView
,解决滑动冲突的办法就简单点,可以使用NestedScrollView
替代ScrollView
或者重写RecyclerView
的onMeasure
方法,都可以达到解决冲突的效果,但是一但嵌套复杂起来,像ScrollView+TabLayout+ViewPager+RecyclerView
这种,情况就不一样了,那么下面分享下我这次遇到的问题,以及整个探索过程,包括我最后的解决办法。
目录
1、需要实现的效果
2、探索过程
3、解决方案
正文
1、需要实现的效果
在项目中,我需要实现的效果其实在很多APP里都可以找到,由于项目需求明确写明是仿京东发现页面,所以是需要实现一个类似京东发现页面的效果,我们来到京东的发现页面,效果如下
这里把图中的效果再总结一下:
1、整体布局分为顶部标题栏,顶部导航栏,下方可左右滑动的内容区,导航栏和内容区是联动的
2、整体页面任意位置往上滑动时,若标题栏在屏幕可见区域内,则标题栏会滑出屏幕,导航栏会悬浮在顶部
3、整体页面任意位置往下滑动时,若标题栏之前被滑出了屏幕,则标题栏随下滑而出现在导航栏上方(图中没有演示出来,实际是有这个效果的)
4、若内容区滑到最顶部,再往下拉时,会产生下拉刷新
2、探索过程
2.1 我最初的实现思路
首先看到这个布局,我的思路很清晰,当然也想当然的认为这样实现没有问题,因为之前也遇到过类似的布局,虽然没这个复杂,但是最后都解决了。
首先整个页面肯定是一个Activity
的布局,下方的底部导航栏就暂且忽略,不是本文的重点,我们需要关注的就是中间这个页面(就是一个Fragment
)的布局,由于整个页面是可滑动的,所以布局最外层肯定是一个ScrollView
,布局最上面是一个标题栏,这个没啥好说的,标题栏下面是一个导航栏,导航栏的实现也不陌生了,就是一个TabLayout
,导航栏下面是内容区,内容区可左右滑动,很明显,内容区可使用ViewPager
实现,在内容区里,是一条条的文章数据,这个我的实现思路是使用RecyclerView
,虽然你会发现有些布局项和其它不同,但是没关系,我们可以通过适配器中的viewType
来控制,然后导航栏和内容区的联动效果可使用TabLayout
的setupWithViewPager
方法直接实现联动效果。
上面的思路实现了整体的布局效果,但是一些小细节还没有实现:
细节一:TabLayout
滑到顶部时,悬浮在顶部,而标题栏直接滑出屏幕,这个效果要实现,主要是实现一个滑动监听, 我们可以监听最外层ScrollView
的滑动,根据参数来判断TabLayout
是否滑到了屏幕顶部,如果滑到了顶部,则在布局中移除原TabLayout
,然后可以在屏幕顶部位置写一个空的顶部布局,再将TabLayout
添加到这个空的布局中。反之,如果ScrollView
滑动到了顶部,此时应该将TabLayout
从顶部布局中移除,将TabLayout
回归原位,涉及到的方法主要有addView
和removeView
。
细节二:整体布局任意位置下滑时,如果标题栏之前被滑出了屏幕,那么会随下滑而逐渐出现在屏幕顶部,这个效果,我最初没有去实现,因为需求中没有这个,但是我后来发现了这个细节效果,现在的思路也是和上面的一样,监听ScrollView
的滑动,根据滑动参数判断上滑还是下滑,如果是下滑的话,那么直接将标题栏布局添加到顶部布局,并根据下滑的距离慢慢将标题栏布局滑出来
2.2 尝试写代码实现
首先根据上面的整体布局思路来一步步实现,至于下面的两个细节问题,先放一放,待会再来实现,ok,有了整体思路,我们不难写出如下代码:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:id="@+id/container_normal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/top_info"
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="我是标题栏"
android:textSize="18sp" />
</RelativeLayout>
<android.support.design.widget.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@android:color/white"
app:tabSelectedTextColor="@android:color/black"
app:tabMode="fixed"/>
<android.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</ScrollView>
现在我们尝试往这个布局中添加简单的内容,看有没有什么问题出现,为了简化,我就给ViewPager
弄三个简易的Fragment
,每个Fragment
布局如下
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="第一个页面"
android:textSize="20sp" />
</RelativeLayout>
对应的java代码如下
public class OneFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_one,container,false);
}
}
我们创建三个这样的Fragment
用于内容填充,接下来就是给tabLayout
和viewPager
设置内容,由于不是重点,我就不废话,直接放上Activity
中的相关代码,如下
public class MainActivity extends AppCompatActivity {
private TabLayout tabLayout;
private ViewPager viewPager;
private FragmentPagerAdapter mPageAdapter;
private ArrayList<String> titleList = new ArrayList<>();
private ArrayList<Fragment> fragmentList=new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tabLayout=findViewById(R.id.tabLayout);
viewPager=findViewById(R.id.viewPager);
titleList.clear();
titleList.add("标签一");
titleList.add("标签二");
titleList.add("标签三");
fragmentList.clear();
fragmentList.add(new OneFragment());
fragmentList.add(new TwoFragment());
fragmentList.add(new ThreeFragment());
mPageAdapter=new FragmentPagerAdapter(getSupportFragmentManager()) {
@Override
public Fragment getItem(int i) {
return fragmentList.get(i);
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return titleList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public int getCount() {
return titleList.size();
}
};
viewPager.setAdapter(mPageAdapter);
tabLayout.setupWithViewPager(viewPager);
}
}
最终运行出来的效果如下
ok,可以明显的看到,我们明明设置了ViewPager而且设置了内容,但是却发现ViewPager
没有显示出来,第一个问题出现了,究其原因就是我们在ScrollView
中嵌套了ViewPager
,导致ViewPager
的高度计算不正确,所以我们可以通过重写ViewPager
的onMeasure
方法来重新计算高度,除此之外,还有一个简单的解决办法,就是给ScrollView
设置fillViewport
属性为true
,这个属性的作用就是让子布局中的内容铺满全屏,ok,设置了该属性之后,我们运行看效果
接下来我们继续丰富其中的内容, 将Fragment
中的内容更改为RecyclerView
的列表,看看有没有什么问题,Fragment
布局很简单,就一个RecyclerView
,我们看Fragment
对应的java代码,如下
public class OneFragment extends Fragment {
private RecyclerView mRecyclerView;
private RecyclerView.LayoutManager mLayoutManager;
private RecyclerViewAdapter mAdapter;
private View mainView;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mainView=inflater.inflate(R.layout.fragment_one,container,false);
initView();
return mainView;
}
private void initView(){
mRecyclerView=mainView.findViewById(R.id.recyclerview);
mLayoutManager=new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
ArrayList<String> data=new ArrayList<>();
for(int i=0;i<20;i++){
data.add("列表项"+i);
}
mAdapter=new RecyclerViewAdapter(getActivity(),data);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setAdapter(mAdapter);
}
}
适配器代码如下
public class