嵌套滚动多TAB可悬浮头效果实现
Posted freeCodeSunny
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了嵌套滚动多TAB可悬浮头效果实现相关的知识,希望对你有一定的参考价值。
前言
在前面的文章中我们已经实现过嵌套滚动可以悬浮头效果,当时有两种实现:
1. Listview多tab上滑悬浮 一种是一个ListView里面切换数据源,同时监控页面滚动,布局页面中设置两层,一层放置悬浮头,滚动到一定位置时,显示出悬浮头
2. 多TAB可悬浮头控件还有一种是上面悬浮头部内容,底层多tab采用viewpager来实现,在viewpager里面的listview中插入相同大小的头部,监控listview的滚动来同步滚动头部内容。
上面两种方式都能实现上面的效果,但都有比较大的缺点,第一种当有个tab时,数据切换复杂,需要处理的状态太多,第二种也需要处理很多种回调情况,计算各种偏移量。
这里我们采用系统原生控件,来采用一种新的实现方式。
效果
这里我们来看看实现后的效果。由于gif每次生成的都很大,传不上来,只好截几张图了(谁有好办法可以分享一下,gif尺寸小了太糊,大了传不上来)
上图第一张是初始进入效果,第二张是部分滑动后效果
第三图是上划到一定程度Tab悬浮效果,第四图是下拉刷新效果
实现
上面我们已经看了效果图,因此我们来实现一下,这里主要采用了系统的SwipeRefreshLayout来实现刷新效果,CoordinatorLayout+AppBarLayout+CollapsingToolbarLayout+NestedScrollView来实现头部悬浮,与底部内容嵌套滚动效果,这里我们不需要引入外部控件,也不需要监控太多状态就能实现Tab悬浮头效果
布局
首先我们来看看布局内容
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.demo.example.activity.NestScrollingActivity">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@color/global_fg_color"
app:titleTextColor="@color/white"/>
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/refresh_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:contentScrim="@color/global_fg_color"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<include layout="@layout/include_scroll_head"></include>
</android.support.design.widget.CollapsingToolbarLayout>
<android.support.design.widget.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/global_fg_color"
app:tabIndicatorColor="#FFFFFFFF"
app:tabSelectedTextColor="#FF888888"
app:tabTextColor="#FFFFFFFF"></android.support.design.widget.TabLayout>
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_nest_scrolling"/>
</android.support.design.widget.CoordinatorLayout>
</android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>
这里我们没有把Toolbar放到CollapsingToolbarLayout,放到CollapsingToolbarLayout里面是实现Toolbar效果的常用实现,这里我们主要将Toolbar固定位置,其他内容全部滚动折叠(这里采用不同的实现,可以实现不同的效果,可以实现沉浸式状态栏,标题随着页面滚动显示不同的效果。我已经实现了沉浸式效果,如有需要留言)
我们将头部内容都放置到CollapsingToolbarLayout,我们采用include方式引入,内容如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
android:id="@+id/feed_detail_content"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/global_bg_color"
android:paddingBottom="@dimen/dp_size_18"
android:paddingLeft="@dimen/dp_size_10"
android:paddingRight="@dimen/dp_size_10"
android:paddingTop="@dimen/dp_size_18">
<ImageView
android:id="@+id/imageView_head"
android:layout_width="@dimen/head_image"
android:layout_height="@dimen/head_image"
android:src="@drawable/avatar_default_head"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/textView_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/dp_size_12"
android:lines="1"
android:text="用户昵称"
android:textColor="@color/white"
android:textSize="@dimen/sp_size_16"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView_head"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/textView_role"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:lines="1"
android:text="描述信息"
android:textColor="@color/white_opacity_50"
android:textSize="@dimen/sp_size_10"
app:layout_constraintBottom_toBottomOf="@+id/imageView_head"
app:layout_constraintEnd_toEndOf="@+id/textView_name"
app:layout_constraintStart_toStartOf="@+id/textView_name"/>
<TextView
android:id="@+id/textView_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="@dimen/dp_size_12"
android:text="我是内容,很长很长的内容"
android:textColor="@color/white_opacity_80"
android:textSize="@dimen/sp_size_14"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView_head"/>
<ImageView
android:id="@+id/image_content"
android:layout_width="match_parent"
android:layout_height="150dp"
android:layout_marginTop="@dimen/dp_size_10"
android:scaleType="centerCrop"
android:src="@drawable/beautify"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView_content"></ImageView>
<TextView
android:id="@+id/textView_source"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lines="1"
android:paddingTop="@dimen/dp_size_10"
android:text="13.30 我是底部文案"
android:textColor="@color/white_opacity_30"
android:textSize="@dimen/sp_size_12"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image_content"/>
</android.support.constraint.ConstraintLayout>
头部内容我们采用了ConstraintLayout来布局,你可以采用任何布局来实现你想要的效果,这里只是一个样例。
最上面的布局中还有一部分content_nest_scrolling,这个是除标题为其他的内容,内容如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView
android:id="@+id/nested_scroll_view"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorAccent"
android:fillViewport="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.demo.example.activity.NestScrollingActivity"
tools:showIn="@layout/activity_nest_scrolling">
<android.support.v4.view.ViewPager
android:id="@+id/data_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/global_bg_color"
android:nestedScrollingEnabled="false"></android.support.v4.view.ViewPager>
</android.support.v4.widget.NestedScrollView>
这里要注意的是,内容必须放到NestedScrollView才能实现嵌套滚动,上面的内容滚动后,页面布局发生了变化,因此需要NestedScrollView来处理变化的效果,由于我们是多个Tab,因此用Viewpage来实现,Tab的效果用TabLayout来实现,他可以与ViewPager实现联动,你也可以采取其他的实现。
界面实现
布局有了,我们再来实现Activity界面:
package com.demo.example.activity;
import android.os.Bundle;
import android.os.Handler;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.NestedScrollView;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import com.demo.example.R;
import com.demo.example.fragment.TabFragment;
import com.demo.example.util.LogUtil;
public class NestScrollingActivity extends AppCompatActivity
private ViewPager viewPager;
private TabLayout tabLayout;
private NestedScrollView scrollView;
private SwipeRefreshLayout refreshLayout;
private AppBarLayout appBarLayout;
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nest_scrolling);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
findViews();
init();
private void findViews()
viewPager = findViewById(R.id.data_pager);
tabLayout = findViewById(R.id.tab_layout);
scrollView = findViewById(R.id.nested_scroll_view);
refreshLayout = findViewById(R.id.refresh_layout);
appBarLayout = findViewById(R.id.app_bar);
private void init()
handler = new Handler(getMainLooper());
appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener()
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset)
if (!refreshLayout.isRefreshing())
refreshLayout.setEnabled(verticalOffset == 0);
LogUtil.e("verticalOffset=" + verticalOffset);
);
viewPager.setAdapter(new TabAdapter(getSupportFragmentManager()));
viewPager.setOffscreenPageLimit(3);
tabLayout.setupWithViewPager(viewPager);
refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener()
@Override
public void onRefresh()
handler.postDelayed(new Runnable()
@Override
public void run()
refreshLayout.setRefreshing(false);
, 500);
);
private class TabAdapter extends FragmentPagerAdapter
public TabAdapter(FragmentManager fm)
super(fm);
@Override
public Fragment getItem(int position)
return new TabFragment();
@Override
public int getCount()
return 3;
@Override
public CharSequence getPageTitle(int position)
return "TAB" + position;
界面中没有太多需要实现的东西,主要是给设置ViewPager里面的内容,没有这里放置三个Fragment。这里我们主要看看以下内容:
appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener()
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset)
if (!refreshLayout.isRefreshing())
refreshLayout.setEnabled(verticalOffset == 0);
LogUtil.e("verticalOffset=" + verticalOffset);
);
这里我们给appBarLayout设置一个OnOffsetChangedListener来监听appBarLayout滚动的距离,为什么要设置这个? 这是由于我们最外层嵌套了一层SwipeRefreshLayout, SwipeRefreshLayout实现了下拉刷新的效果,因此你往上滚的时候是没什么问题的,但是向下滚动时,由于内容嵌套了滚动控件,会导致下拉刷新的触发时机不对,会在页面中途就出现刷新效果,因此我们只有到appBarLayout的内容完全展示的时候,才设置SwipeRefreshLayout可刷新。
还有一部分内容我们来看看:
refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener()
@Override
public void onRefresh()
handler.postDelayed(new Runnable()
@Override
public void run()
refreshLayout.setRefreshing(false);
, 500);
);
上述的代码又是干什么的?由于SwipeRefreshLayout实现了刷新效果,但是是需要手动来停止该效果的,因此上面只是模拟一个刷新的效果。实际运用中,由于刷新是在最外层的,而刷新的内容一般在Fragment中,因此你需要调用Fragment进行内容刷新,当时刷新完成时你需要回调页面停止,这里没有采用callback方式实现,也可以采用google新的架构组件LiveData来实现,用LiveData可以实现解耦。
Fragment
最后我们来看看TabFrament的实现:
public class TabFragment extends Fragment
RecyclerView recyclerView;
private List<String> datas;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle
savedInstanceState)
return inflater.inflate(R.layout.fragment_tab_layout, container, false);
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState)
super.onActivityCreated(savedInstanceState);
findViews();
init();
private void findViews()
recyclerView = getView().findViewById(R.id.list);
private void init()
getDatas();
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.setAdapter(new BaseAdapter<String>(datas, new BaseDelegate()
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
return new StringViewHolder(parent, getItemView(parent, viewType));
@Override
public int getItemViewType(Object data)
return 0;
@Override
public int getLayoutId(int viewType)
return R.layout.tab_item_view_holder;
));
private class StringViewHolder extends BaseViewHolder<String>
private TextView textView;
/**
* @param parent current no use, may be future use
* @param view
*/
public StringViewHolder(ViewGroup parent, View view)
super(parent, view);
@Override
public void findViews()
textView = itemView.findViewById(R.id.content);
@Override
public void onBindViewHolder(String data)
textView.setText(data);
private void getDatas()
datas = new ArrayList<>();
for (int i = 0; i < 20; ++i)
datas.add("content" + i);
这里就是一般的实现,一个recyclerView实现列表效果。没有太多复杂的东西。根据你的需要进行实现。
结语
按照套路最后放出代码实现NestScrollingView.
以上是关于嵌套滚动多TAB可悬浮头效果实现的主要内容,如果未能解决你的问题,请参考以下文章
Android 嵌套滚动NestedScrollView+TabLayout+ViewPager+Fragment+RecyclerView 实现京东美团首页效果Tab页滚动到顶部时自动吸附
Android 嵌套滚动NestedScrollView+TabLayout+ViewPager+Fragment+RecyclerView 实现京东美团首页效果Tab页滚动到顶部时自动吸附
Android 嵌套滚动NestedScrollView+TabLayout+ViewPager+Fragment+RecyclerView 实现京东美团首页效果Tab页滚动到顶部时自动吸附