• 在RecyclerView列表滚动的时候显示或者隐藏Toolbar


    先看一下效果:

    本文将讲解如何实现类似于Google+应用中,当列表滚动的时候,ToolBar(以及悬浮操作按钮)的显示与隐藏(向下滚动隐藏,向上滚动显示),这种效果在Material Design 清单中有提到:

    “在合适的地方,当列表向下滚动,app bar可以退出屏幕,以便为内容区域留下更多的空间;而当列表向上滚动回来的时候,app bar又重新显示出来”。

    注:这里的向下滚动是指滚动到下面查看更多内容,相对应的手势操作其实是往上。同理向上滚动是指查看前面的内容,而手势其实是向下。

    虽然此文我们将使用RecyclerView作为列表,但是这种实现方式适用于任何可以滚动的容器(某些情况下也许要稍微多做点工作,比如listview)。我想到了两种实现的方式:

    1. 在列表的上面加个padding。

    2. 为列表加个header。

    我打算只写出第二种实现方式,因为有很多人询问关于如何给RecyclerView加上header的问题,因此借着这个机会就一起讲了。但是我也会非常简单的描述一下第一种实现方法。

    开始

    首先添加必要的库

     1 dependencies {
     2     compile fileTree(include: ['*.jar'], dir: 'libs')
     3     androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
     4         exclude group: 'com.android.support', module: 'support-annotations'
     5     })
     6     compile 'com.android.support:appcompat-v7:25.3.1'
     7     compile 'com.android.support.constraint:constraint-layout:1.0.2'
     8     testCompile 'junit:junit:4.12'
     9     compile 'com.android.support:design:26.0.0-alpha1'
    10     compile 'com.android.support:cardview-v7:26.0.0-alpha1'
    11     compile 'com.jakewharton:butterknife:8.7.0'
    12     compile 'com.jakewharton:butterknife-compiler:8.7.0'
    13     compile 'de.greenrobot:eventbus:3.0.0-beta1'
    14 }

    创建activity的布局:

     1 <?xml version="1.0" encoding="utf-8"?>
     2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     3     xmlns:tools="http://schemas.android.com/tools"
     4     android:layout_width="match_parent"
     5     android:layout_height="match_parent"
     6     android:orientation="vertical"
     7     tools:context="recycler.huolongluo.hideonscrollexample.MainActivity">
     8 
     9     <android.support.v7.widget.Toolbar
    10         android:id="@+id/toolbar"
    11         android:layout_width="match_parent"
    12         android:layout_height="?attr/actionBarSize"
    13         android:background="@color/colorPrimaryDark"
    14         tools:layout_editor_absoluteX="8dp"
    15         tools:layout_editor_absoluteY="0dp" />
    16 
    17     <android.support.v7.widget.RecyclerView
    18         android:id="@+id/rv_content"
    19         android:layout_width="match_parent"
    20         android:layout_height="match_parent" />
    21 
    22 </LinearLayout>

    下面转向MainActivity的代码:

     1 package recycler.huolongluo.hideonscrollexample;
     2 
     3 import android.os.Bundle;
     4 import android.support.v7.app.AppCompatActivity;
     5 import android.support.v7.widget.LinearLayoutManager;
     6 import android.support.v7.widget.RecyclerView;
     7 import android.support.v7.widget.Toolbar;
     8 import android.util.Log;
     9 import android.view.View;
    10 import android.view.animation.AccelerateInterpolator;
    11 import android.view.animation.DecelerateInterpolator;
    12 
    13 import java.util.ArrayList;
    14 import java.util.List;
    15 
    16 import butterknife.BindView;
    17 import butterknife.ButterKnife;
    18 
    19 public class MainActivity extends AppCompatActivity {
    20     private static final String TAG = "MainActivity";
    21 
    22     @BindView(R.id.toolbar)
    23     Toolbar toolbar;
    24     @BindView(R.id.rv_content)
    25     RecyclerView rv_content;
    26 
    27     private List<String> datas;
    28     private MyAdapter myAdapter;
    29 
    30     @Override
    31     protected void onCreate(Bundle savedInstanceState) {
    32         super.onCreate(savedInstanceState);
    33         setContentView(R.layout.activity_main);
    34         ButterKnife.bind(this);
    35         initData();
    36         initView();
    37     }
    38 
    39     private void initView() {
    40         myAdapter = new MyAdapter(this, datas);
    41         rv_content.setLayoutManager(new LinearLayoutManager(this));
    42         rv_content.setAdapter(myAdapter);
    43 
    44         /**
    45          * 实现RecyclerView上下滑动的显示和隐藏
    46          * */
    47         rv_content.addOnScrollListener(new RecyclerView.OnScrollListener() {
    48             private static final int HIDE_THRESHOLD = 20;
    49             private int scrolledDistance = 0;
    50             private boolean controlsVisible = true;
    51 
    52             @Override
    53             public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
    54                 super.onScrolled(recyclerView, dx, dy);
    55                 Log.e(TAG, "onScrolled dy: " + dy);
    56                 Log.e(TAG, "onScrolled dx: " + dx);
    57                 Log.e(TAG, "-------------------- onScrolled: --------------------");
    58                 if (scrolledDistance > HIDE_THRESHOLD && controlsVisible) {
    59                     // TODO: 2017/7/16 0016 隐藏toolbar
    60                     hideViews();
    61 //                    toolbar.setVisibility(View.GONE);
    62                     controlsVisible = false;
    63                     scrolledDistance = 0;
    64                 } else if (scrolledDistance < -HIDE_THRESHOLD && !controlsVisible) {
    65                     // TODO: 2017/7/16 0016 显示toolbar
    66                     showViews();
    67 //                    toolbar.setVisibility(View.VISIBLE);
    68                     controlsVisible = true;
    69                     scrolledDistance = 0;
    70                 }
    71                 if ((controlsVisible && dy > 0) || (!controlsVisible && dy < 0)) {
    72                     scrolledDistance += dy;
    73                 }
    74             }
    75         });
    76     }
    77 
    78     private void hideViews() {
    79         toolbar.animate().translationY(-toolbar.getHeight()).setInterpolator(new AccelerateInterpolator(2));
    80     }
    81 
    82     private void showViews() {
    83         toolbar.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2));
    84     }
    85 
    86     private void initData() {
    87         if (datas == null) {
    88             datas = new ArrayList<>();
    89         }
    90         for (int i = 0; i < 20; i++) {
    91             datas.add("Item " + i);
    92         }
    93     }
    94 }

    如你所见,这是一个很小的类,只实现了onCreate,做了如下几件事情:

    1.初始化Toolbar

    2.初始化数据源

    3.初始化RecyclerView

    现在来看下适配器Adapter的写法,适配器前,我先把需要用到的两个不同布局的item画出来了。一个item作为RecyclerView的头部,一个item作为RecyclerView的内容。

    item_head.xml:

    1 <?xml version="1.0" encoding="utf-8"?>
    2 <View xmlns:android="http://schemas.android.com/apk/res/android"
    3     android:id="@+id/view_head"
    4     android:layout_width="match_parent"
    5     android:layout_height="?attr/actionBarSize"
    6     android:orientation="vertical" />

    item_content.xml:

     1 <?xml version="1.0" encoding="utf-8"?>
     2 <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
     3     xmlns:app="http://schemas.android.com/apk/res-auto"
     4     android:layout_width="match_parent"
     5     android:layout_height="wrap_content"
     6     android:layout_gravity="center"
     7     android:layout_margin="20dp"
     8     android:orientation="vertical"
     9     app:cardCornerRadius="10dp">
    10 
    11     <TextView
    12         android:id="@+id/tv_title"
    13         android:layout_width="match_parent"
    14         android:layout_height="wrap_content"
    15         android:padding="20dp"
    16         android:text="Item "
    17         android:textSize="20sp" />
    18 
    19 </android.support.v7.widget.CardView>

    布局很简单,只需注意其高度要和Toolbar一致。

    接下来我们看适配器MyAdapter.java:

     1 package recycler.huolongluo.hideonscrollexample;
     2 
     3 import android.content.Context;
     4 import android.support.v7.widget.RecyclerView;
     5 import android.view.LayoutInflater;
     6 import android.view.View;
     7 import android.view.ViewGroup;
     8 import android.widget.TextView;
     9 
    10 import java.util.List;
    11 
    12 /**
    13  * Created by Administrator on 2017/7/16 0016.
    14  */
    15 
    16 class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    17 
    18     private Context context;
    19     private List<String> datas;
    20     private LayoutInflater inflater;
    21 
    22     public MyAdapter(Context context, List<String> datas) {
    23         this.context = context;
    24         this.datas = datas;
    25         inflater = LayoutInflater.from(context);
    26     }
    27 
    28     @Override
    29     public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    30         if (viewType == 0) {
    31             return new ViewHolderHead(inflater.inflate(R.layout.item_head, parent, false));
    32         } else {
    33             return new ViewHolderContent(inflater.inflate(R.layout.item_content, parent, false));
    34         }
    35     }
    36 
    37     @Override
    38     public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    39         if (holder instanceof ViewHolderContent) {
    40             ((ViewHolderContent) holder).tv_title.setText(datas.get(position - 1));
    41         }
    42     }
    43 
    44     @Override
    45     public int getItemCount() {
    46         return datas.size() + 1;
    47     }
    48 
    49     @Override
    50     public int getItemViewType(int position) {
    51         if (position == 0) {
    52             return 0;
    53         } else {
    54             return 1;
    55         }
    56     }
    57 
    58     public class ViewHolderContent extends RecyclerView.ViewHolder {
    59         private TextView tv_title;
    60 
    61         public ViewHolderContent(View itemView) {
    62             super(itemView);
    63             tv_title = (TextView) itemView.findViewById(R.id.tv_title);
    64         }
    65     }
    66 
    67     public class ViewHolderHead extends RecyclerView.ViewHolder {
    68         private View view_head;
    69 
    70         public ViewHolderHead(View itemView) {
    71             super(itemView);
    72             view_head = (View) itemView.findViewById(R.id.view_head);
    73         }
    74     }
    75 }

    到这里就已经全部写完了,

    下面是关于上面代码的解释:

    1.需要定义Recycler显示的item的类型。RecyclerView是一个非常灵活的控件,当某些item的布局和其他item有区别的时候,我们一般要用到item类型。这也正是我们这里需要的-第一个item是header,不同于其他item。

    2.我们需要告诉Recycler,item想要显示的类型。getItemViewType方法将根据position返回一个item的类型(int类型,具体值由你根据项目需求自己定义)。

    3.需要修改onCreateViewHolder()onBindViewHolder()方法,在item类型为非0的时候绑定或者返回一个普通item,在item类型为0的时候返回或绑定一个header item。

    4.需要修改getItemCount()-在原有的基数上+1因为多了个header

    正如你所看到的,所有关键代码都在一个onScrolled()方法中。其dx, dy参数分别是横向和纵向的滚动距离,准确是的是两个滚动事件之间的偏移量,而不是总的滚动距离。

    基本的思路如下:

    1.计算出滚动的总距离(deltas相加),但是只在Toolbar隐藏且上滚或者Toolbar未隐藏且下滚的时候,因为我们只关心这两种情况。

    1 if((controlsVisible && dy>0) || (!controlsVisible && dy<0)) {
    2     scrolledDistance += dy;
    3 }

     2.如果总的滚动距离超多了一定值(这个值取决于你自己的设定,越大,需要滑动的距离越长才能显示或者隐藏),我们就根据其方向显示或者隐藏Toolbar(dy>0意味着下滚,dy<0意味着上滚)。

    1 if (scrolledDistance > HIDE_THRESHOLD && controlsVisible) {
    2     onHide();
    3     controlsVisible = false;
    4     scrolledDistance = 0;
    5 } else if (scrolledDistance < -HIDE_THRESHOLD && !controlsVisible) {
    6     onShow();
    7     controlsVisible = true;
    8     scrolledDistance = 0;
    9 }

    最后,toolbar的显示和隐藏是通过动画来控制的,一句搞定。

    基本上是正确的,但是还有点bug-如果你的滑动距离的触发值太小,在隐藏Toolbar的时候会在列表的顶部留下一段空白区域(最开始,随着滚动空白区域会消失),幸好解决起来也很简单。只需检测第一个item是否可见,只有当不可见的时候才执行上面的逻辑。

    于是,在重写的addOnScrollListener方法里面,最新的代码是这样的:

     1 /**
     2          * 实现RecyclerView上下滑动的显示和隐藏
     3          * */
     4         rv_content.addOnScrollListener(new RecyclerView.OnScrollListener() {
     5             private static final int HIDE_THRESHOLD = 20;
     6             private int scrolledDistance = 0;
     7             private boolean controlsVisible = true;
     8 
     9             int firstVisibleItem = ((LinearLayoutManager) rv_content.getLayoutManager()).findFirstVisibleItemPosition();
    10 
    11             @Override
    12             public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
    13                 super.onScrolled(recyclerView, dx, dy);
    14                 Log.e(TAG, "onScrolled dy: " + dy);
    15                 Log.e(TAG, "onScrolled dx: " + dx);
    16                 Log.e(TAG, "-------------------- onScrolled: --------------------");
    17                 if (firstVisibleItem == 0) {
    18                     if (!controlsVisible) {
    19                         showViews();
    20                         controlsVisible = true;
    21                     }
    22                 } else {
    23                     if (scrolledDistance > HIDE_THRESHOLD && controlsVisible) {
    24                         // TODO: 2017/7/16 0016 隐藏toolbar
    25                         hideViews();
    26 //                    toolbar.setVisibility(View.GONE);
    27                         controlsVisible = false;
    28                         scrolledDistance = 0;
    29                     } else if (scrolledDistance < -HIDE_THRESHOLD && !controlsVisible) {
    30                         // TODO: 2017/7/16 0016 显示toolbar
    31                         showViews();
    32 //                    toolbar.setVisibility(View.VISIBLE);
    33                         controlsVisible = true;
    34                         scrolledDistance = 0;
    35                     }
    36                 }
    37                 if ((controlsVisible && dy > 0) || (!controlsVisible && dy < 0)) {
    38                     scrolledDistance += dy;
    39                 }
    40             }
    41         });
  • 相关阅读:
    tp5.1 多级控制器
    JS中三个点(...)是什么鬼?
    vue reqwest与fetch的使用
    new Vue({ render: h => h(App), }).$mount('#app')到底什么意思
    ant design vue 表格和国际化的使用
    JAVA日报
    JAVA日报
    JAVA日报
    JAVA日报
    JAVA日报
  • 原文地址:https://www.cnblogs.com/huolongluo/p/7190992.html
Copyright © 2020-2023  润新知