嵌套滚动多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可悬浮头效果实现的主要内容,如果未能解决你的问题,请参考以下文章

iOS: 悬浮的条件筛选框使用二

Android 嵌套滚动NestedScrollView+TabLayout+ViewPager+Fragment+RecyclerView 实现京东美团首页效果Tab页滚动到顶部时自动吸附

Android 嵌套滚动NestedScrollView+TabLayout+ViewPager+Fragment+RecyclerView 实现京东美团首页效果Tab页滚动到顶部时自动吸附

Android 嵌套滚动NestedScrollView+TabLayout+ViewPager+Fragment+RecyclerView 实现京东美团首页效果Tab页滚动到顶部时自动吸附

微信小程序 ,列表头滚动的过程中 view 悬浮在顶部

jQuery实现tab栏切换效果