先看一下效果:

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

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

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

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

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

  2. 为列表加个header。

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

开始

首先添加必要的库

 dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
compile 'com.android.support:design:26.0.0-alpha1'
compile 'com.android.support:cardview-v7:26.0.0-alpha1'
compile 'com.jakewharton:butterknife:8.7.0'
compile 'com.jakewharton:butterknife-compiler:8.7.0'
compile 'de.greenrobot:eventbus:3.0.0-beta1'
}

创建activity的布局:

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="recycler.huolongluo.hideonscrollexample.MainActivity"> <android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimaryDark"
tools:layout_editor_absoluteX="8dp"
tools:layout_editor_absoluteY="0dp" /> <android.support.v7.widget.RecyclerView
android:id="@+id/rv_content"
android:layout_width="match_parent"
android:layout_height="match_parent" /> </LinearLayout>

下面转向MainActivity的代码:

 package recycler.huolongluo.hideonscrollexample;

 import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator; import java.util.ArrayList;
import java.util.List; import butterknife.BindView;
import butterknife.ButterKnife; public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity"; @BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.rv_content)
RecyclerView rv_content; private List<String> datas;
private MyAdapter myAdapter; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
initData();
initView();
} private void initView() {
myAdapter = new MyAdapter(this, datas);
rv_content.setLayoutManager(new LinearLayoutManager(this));
rv_content.setAdapter(myAdapter); /**
* 实现RecyclerView上下滑动的显示和隐藏
* */
rv_content.addOnScrollListener(new RecyclerView.OnScrollListener() {
private static final int HIDE_THRESHOLD = 20;
private int scrolledDistance = 0;
private boolean controlsVisible = true; @Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
Log.e(TAG, "onScrolled dy: " + dy);
Log.e(TAG, "onScrolled dx: " + dx);
Log.e(TAG, "-------------------- onScrolled: --------------------");
if (scrolledDistance > HIDE_THRESHOLD && controlsVisible) {
// TODO: 2017/7/16 0016 隐藏toolbar
hideViews();
// toolbar.setVisibility(View.GONE);
controlsVisible = false;
scrolledDistance = 0;
} else if (scrolledDistance < -HIDE_THRESHOLD && !controlsVisible) {
// TODO: 2017/7/16 0016 显示toolbar
showViews();
// toolbar.setVisibility(View.VISIBLE);
controlsVisible = true;
scrolledDistance = 0;
}
if ((controlsVisible && dy > 0) || (!controlsVisible && dy < 0)) {
scrolledDistance += dy;
}
}
});
} private void hideViews() {
toolbar.animate().translationY(-toolbar.getHeight()).setInterpolator(new AccelerateInterpolator(2));
} private void showViews() {
toolbar.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2));
} private void initData() {
if (datas == null) {
datas = new ArrayList<>();
}
for (int i = 0; i < 20; i++) {
datas.add("Item " + i);
}
}
}

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

1.初始化Toolbar

2.初始化数据源

3.初始化RecyclerView

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

item_head.xml:

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

item_content.xml:

 <?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="20dp"
android:orientation="vertical"
app:cardCornerRadius="10dp"> <TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="Item "
android:textSize="20sp" /> </android.support.v7.widget.CardView>

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

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

 package recycler.huolongluo.hideonscrollexample;

 import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView; import java.util.List; /**
* Created by Administrator on 2017/7/16 0016.
*/ class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private Context context;
private List<String> datas;
private LayoutInflater inflater; public MyAdapter(Context context, List<String> datas) {
this.context = context;
this.datas = datas;
inflater = LayoutInflater.from(context);
} @Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == 0) {
return new ViewHolderHead(inflater.inflate(R.layout.item_head, parent, false));
} else {
return new ViewHolderContent(inflater.inflate(R.layout.item_content, parent, false));
}
} @Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof ViewHolderContent) {
((ViewHolderContent) holder).tv_title.setText(datas.get(position - 1));
}
} @Override
public int getItemCount() {
return datas.size() + 1;
} @Override
public int getItemViewType(int position) {
if (position == 0) {
return 0;
} else {
return 1;
}
} public class ViewHolderContent extends RecyclerView.ViewHolder {
private TextView tv_title; public ViewHolderContent(View itemView) {
super(itemView);
tv_title = (TextView) itemView.findViewById(R.id.tv_title);
}
} public class ViewHolderHead extends RecyclerView.ViewHolder {
private View view_head; public ViewHolderHead(View itemView) {
super(itemView);
view_head = (View) itemView.findViewById(R.id.view_head);
}
}
}

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

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

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未隐藏且下滚的时候,因为我们只关心这两种情况。

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

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

 if (scrolledDistance > HIDE_THRESHOLD && controlsVisible) {
onHide();
controlsVisible = false;
scrolledDistance = 0;
} else if (scrolledDistance < -HIDE_THRESHOLD && !controlsVisible) {
onShow();
controlsVisible = true;
scrolledDistance = 0;
}

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

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

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

 /**
* 实现RecyclerView上下滑动的显示和隐藏
* */
rv_content.addOnScrollListener(new RecyclerView.OnScrollListener() {
private static final int HIDE_THRESHOLD = 20;
private int scrolledDistance = 0;
private boolean controlsVisible = true; int firstVisibleItem = ((LinearLayoutManager) rv_content.getLayoutManager()).findFirstVisibleItemPosition(); @Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
Log.e(TAG, "onScrolled dy: " + dy);
Log.e(TAG, "onScrolled dx: " + dx);
Log.e(TAG, "-------------------- onScrolled: --------------------");
if (firstVisibleItem == 0) {
if (!controlsVisible) {
showViews();
controlsVisible = true;
}
} else {
if (scrolledDistance > HIDE_THRESHOLD && controlsVisible) {
// TODO: 2017/7/16 0016 隐藏toolbar
hideViews();
// toolbar.setVisibility(View.GONE);
controlsVisible = false;
scrolledDistance = 0;
} else if (scrolledDistance < -HIDE_THRESHOLD && !controlsVisible) {
// TODO: 2017/7/16 0016 显示toolbar
showViews();
// toolbar.setVisibility(View.VISIBLE);
controlsVisible = true;
scrolledDistance = 0;
}
}
if ((controlsVisible && dy > 0) || (!controlsVisible && dy < 0)) {
scrolledDistance += dy;
}
}
});

在RecyclerView列表滚动的时候显示或者隐藏Toolbar的更多相关文章

  1. Android下拉上滑显示与隐藏Toolbar另一种实现

    public abstract class RecyclerViewScrollListener extends RecyclerView.OnScrollListener { private sta ...

  2. RecyclerView的滚动事件OnScrollListener研究

      (1)滚动事件分类 列表的滚动一般分为两种: 1.手指按下 -> 手指拖拽列表移动 -> 手指停止拖拽 -> 抬起手指 2.手指按下 -> 手指快速拖拽后抬起手指 -> ...

  3. 仿网易/QQ空间视频列表滚动连播炫酷效果

    代码地址如下:http://www.demodashi.com/demo/11201.html 一.准备工作 AndroidStudio 开发环境 需要下载七牛的开源播放器SDK 本例子实现了仿网易/ ...

  4. RecyclerView上拉隐藏Toolbar,下拉显示

    RecyclerView下拉隐藏Toolbar,上拉显示效果图 先说个事:最近我准备做个开源的博客园android客户端!符合Google最新的material design设计风格的!不知道有没有小 ...

  5. RecyclerView的滚动事件分析

    列表的滚动一般分为两种: 手指按下 -> 手指拖拽列表移动 -> 手指停止拖拽 -> 抬起手指 手指按下 -> 手指快速拖拽后抬起手指 -> 列表继续滚动 -> 停 ...

  6. 死磕到底RecyclerView | RecyclerView 的滚动是怎么实现的?

    RecyclerView 是一个展示列表的控件,其中的子控件可以被滚动.这是怎么实现的?以走查源码的方式一探究竟. 切入点:滚动事件 阅读源码时,如何在浩瀚的源码中选择合适的切入点很重要,选好了能少走 ...

  7. JQuery实现页面企业广告图片切换和新闻列表滚动效果

    最近用到一个页面上图片左右切换和新闻列表滚动呈现的效果,整理如下: 前段代码: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transit ...

  8. iOS:自定义导航栏,随着tableView滚动显示和隐藏

    自定义导航栏,随着tableView滚动显示和隐藏 一.介绍 自定义导航栏是APP中很常用的一个功能,通过自定义可以灵活的实现动画隐藏和显示效果.虽然处理系统的导航栏也可以实现,但是这个是有弊端的,因 ...

  9. dedecms后台每页文章条数如何修改(“文档列表”每一页显示的文档条数)

    小明在学习采集,弄了个dedecms作为发布平台,几个小时后跑来报喜说好简单,但又不想制造那么多spam,每个分类只保留几条就好.在后台删除这些文章,每页只显示30个,看了下有100多页,立马沮丧了, ...

随机推荐

  1. leetcode-179-Largest Number(理解规则,自定义cmp函数进行排序)

    题目描述: 给定一组非负整数,重新排列它们的顺序使之组成一个最大的整数. 示例 1: 输入: [10,2] 输出: 210 示例 2: 输入: [3,30,34,5,9] 输出: 9534330 说明 ...

  2. JS: RegExp(正则表达式)

    RegExp (包含ES2018新特性) 注意:本次所有代码都仅在Chrome 70中进行测试 正则表达式是什么? 正则表达式是用于匹配字符串中字符组合的模式.(mdn) 简单来说,正则表达式是用来提 ...

  3. Php开发银行接口之浦发银行

    Php开发银行接口之浦发银行 (提示:下面的经验都是按照开发文档一步一步踩坑过来的,但是不能不看开发文档!!!) 第一步:开发准备 1,安装java,百度下载JDK很方便(我自己网盘有,然后配置环境变 ...

  4. android的几种“通知”方式简单实现(Notification&NotificationManager)

    关于通知Notification相信大家都不陌生了,平时上QQ的时候有消息来了或者有收到了短信,手机顶部就会显示有新消息什么的,就类似这种.今天就稍微记录下几种Notification的用法.3.0以 ...

  5. Javac语法糖之增强for循环

    加强的for循环有两种,遍历数组和实现了Iterable接口的容器.javac通过visitForeachLoop()方法来实现解语法糖,代码如下: /** Translate away the fo ...

  6. 编写dimgr脚本学到的知识及技巧

    编写dimgr是为了管理手机上的镜像,在此总结下过程中学到的知识及技巧(不讨论具体用法). 参数处理 以往处理脚本参数直接用循环加判断语句,若是脚本只有简单参数,这无疑是简便可行的方法.但当需要处理复 ...

  7. Java虚拟机(四):JVM类加载机制

    1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构 ...

  8. android studio 3.1.4下载安装配置(附旧版本下载地址)

    windows下安装android studio.当前时间2018年9月. 最新版本的android studio3.2.0-release出来了,拥有许多新的特性 可能我是一个业余的android开 ...

  9. google glog 使用方法

    #include <glog/logging.h> int main(int argc,char* argv[]) { google::ParseCommandLineFlags(& ...

  10. 多表连接的三种方式详解 HASH JOIN MERGE JOIN NESTED LOOP

    在多表联合查询的时候,如果我们查看它的执行计划,就会发现里面有多表之间的连接方式. 之前打算在sqlplus中用执行计划的,但是格式看起来有点乱,就用Toad 做了3个截图. 从3张图里我们看到了几点 ...