Android高级UI开发(十八)监听列表的滑动从而隐藏或显示Toolbar与Fab浮动按钮
周末好, 今天我们来讲一个动画效果的实现,当上滑列表时让Toolbar(工具栏)与FAB浮动按钮(实质还是一个按钮)隐藏,当下滑时又显示回来。实现这个动画有两种方法,今天我们先讲第一种最常规的做法,其实第二种方法才是正道,它更符合Google Material Design的规范,敬请期待下一篇Android高级进阶(十九),但是我们今天还是要执意讲解第一种方法的目的就是为正道铺路,将让您有一个对比。人间正道是沧桑,该走的弯路我们还是有必要走一次,否则就没有“更好”这个词语了,请谨记,没有最好的自己只有更好的自己,让我们每一天都进步一点点。 动画效果如下:
一、第一种方法实现思路:
1. 创建一个列表
2. 监听列表的滑动事件
--当滚动条滑动的dx>0时,即y1-y0 > 0 说明手指在屏幕上正在上滑,同时滚动条在屏幕上是一直往下移动。这时执行隐藏Toolbar与Fab浮动按钮。
--当滚动条滑动的dx<0时,即y1-y0 < 0 说明手指在屏幕上正在下滑,同时滚动条在屏幕上是一直往上移动。这时执行显示Toolbar与Fab浮动按钮。
--有以上2条还不够,我们不能直接判断dx>0或dx<0就让它执行动画,我们需要dx积累到一定距离再执行动画,否则手指刚接触到屏幕按钮就被隐藏了,这样用户体验太差。
举个例子,当我们手指在屏幕上正在上滑时,y0 = 1, y1 = 2, (dx = 1) >0,滚动条才向下滚动了1个像素就隐藏Toolbar与Fab,这体验太差了。我们最起码让 (distance += dx) > 20的时候再让其执行动画,也就是说滚动条每次变化的dx0,dx1,dx2,dx3.....dxn相加,累积到20像素的时候,就要开始执行动画了,否则不执行隐藏动画。在这里就会有人问,为何不直接判断 dx > 20不就OK了?其实dx的含义并不是 最后一个Y值减去最初的Y值,它代表的是滚动条在滚动过程中 一个一个微小的距离差,我们试着回忆一下高等数学里的微积分中的dx,我们在计算一个曲面的面积时通常把它细分成一个一个小的矩形面积。
二、代码实现
根据上述分析,我们来看看具体如何实现
1. 布局文件
-
<RelativeLayout 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"
-
xmlns:app="http://schemas.android.com/apk/res-auto">
-
-
<android.support.v7.widget.RecyclerView
-
android:id="@+id/recyclerview"
-
android:layout_width="match_parent"
-
android:layout_height="match_parent"
-
android:clipToPadding="false"
-
android:clipChildren="false"
-
android:paddingTop="?attr/actionBarSize"
-
/>
-
<android.support.v7.widget.Toolbar
-
android:elevation="4dp"
-
app:titleTextColor="@color/white"
-
android:id="@+id/toolbar"
-
android:layout_width="match_parent"
-
android:layout_height="?attr/actionBarSize"
-
android:background="?attr/colorPrimary"/>
-
<ImageButton
-
android:id="@+id/fab"
-
android:layout_width="58dp"
-
android:layout_height="58dp"
-
android:layout_margin="16dp"
-
android:layout_alignParentRight="true"
-
android:layout_alignParentBottom="true"
-
android:background="@drawable/fab_bg"
-
android:src="@drawable/ic_favorite_outline_white_24dp"
-
/>
-
-
</RelativeLayout>
2. 代码
2.1 先创建一个列表:
MainActivity.java代码:
-
package com.example.administrator.fabtoolbarscrollhide;
-
-
import java.util.ArrayList;
-
import java.util.List;
-
-
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.view.animation.AccelerateInterpolator;
-
import android.view.animation.DecelerateInterpolator;
-
import android.widget.ImageButton;
-
import android.widget.RelativeLayout;
-
import android.widget.RelativeLayout.LayoutParams;
-
-
-
public class MainActivity extends AppCompatActivity implements HideScrollListener{
-
-
private RecyclerView recyclerview;
-
private ImageButton fab;
-
private Toolbar toolbar;
-
-
@Override
-
protected void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.activity_main);
-
-
recyclerview = (RecyclerView)findViewById(R.id.recyclerview);
-
fab = (ImageButton)findViewById(R.id.fab);
-
-
toolbar = (Toolbar)findViewById(R.id.toolbar);
-
setSupportActionBar(toolbar);
-
setTitle("滑动隐藏");
-
-
recyclerview.setLayoutManager(new LinearLayoutManager(this));
-
List<String> list = new ArrayList<>();
-
for (int i = 0; i < 50; i++) {
-
list.add("条目"+i);
-
}
-
RecyclerView.Adapter adapter = new FabRecyclerAdapter(list );
-
recyclerview.setAdapter(adapter );
-
-
}
列表适配器代码:
-
package com.example.administrator.fabtoolbarscrollhide;
-
-
import java.util.List;
-
-
import android.support.v7.widget.RecyclerView;
-
import android.support.v7.widget.RecyclerView.Adapter;
-
import android.support.v7.widget.RecyclerView.ViewHolder;
-
import android.view.LayoutInflater;
-
import android.view.View;
-
import android.view.ViewGroup;
-
import android.widget.TextView;
-
-
public class RecyclerAdapter extends Adapter<RecyclerView.ViewHolder> {
-
-
private List<String> list;
-
-
public RecyclerAdapter(List<String> list) {
-
// TODO Auto-generated constructor stub
-
this.list = list;
-
}
-
-
@Override
-
public int getItemCount() {
-
// TODO Auto-generated method stub
-
return list.size();
-
}
-
-
@Override
-
public void onBindViewHolder(ViewHolder viewHolder, int position) {
-
String str = list.get(position);
-
MyViewHolder holder = (MyViewHolder) viewHolder;
-
holder.tv.setText(str);
-
}
-
-
@Override
-
public ViewHolder onCreateViewHolder(ViewGroup arg0, int arg1) {
-
// TODO Auto-generated method stub
-
View view = LayoutInflater.from(arg0.getContext()).inflate(R.layout.listitem, arg0, false);
-
return new MyViewHolder(view);
-
}
-
-
class MyViewHolder extends ViewHolder{
-
-
private TextView tv;
-
-
public MyViewHolder(View itemView) {
-
super(itemView);
-
tv = (TextView) itemView.findViewById(R.id.tv);
-
}
-
-
}
-
-
}
2.2 监听列表滑动:
-
recyclerview.addOnScrollListener(new MyScrollListener(this));其中MyScrollListener继承于RecyclerView.OnScrollListener,我们为什么要自定义MyScrollListener,是因为我们要在这里处理dx,正如我们第一节思路中所说的我们要判断dx>0还是dx<0从而判断是上滑还是下滑,还要累积distance才让其执行隐藏或显示动画。
-
MyScrollListener.java的代码如下:
-
-
package com.example.administrator.fabtoolbarscrollhide;
-
-
import android.support.v7.widget.RecyclerView;
-
import android.support.v7.widget.RecyclerView.OnScrollListener;
-
-
public class MyScrollListener extends OnScrollListener {
-
private static final int THRESHOLD = 20;
-
private int distance = 0;
-
private HideScrollListener hideListener;
-
private boolean visible = true;
-
-
public MyScrollListener(HideScrollListener hideScrollListener) {
-
// TODO Auto-generated constructor stub
-
this.hideListener = hideScrollListener;
-
}
-
@Override
-
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
-
super.onScrolled(recyclerView, dx, dy);
-
-
if(distance>THRESHOLD){
-
hideListener.onHide();
-
distance = 0;
-
}else if(distance<-THRESHOLD)
-
hideListener.onShow();
-
distance = 0;
-
}
-
-
distance += dy;
-
-
}
-
-
}
我们自定义的MyScrollListener中重写了onScrolled函数,其中distance += dy,计算累积的dy.
if(distance>THRESHOLD)表示dy>0且累积已经>20像素了,所以正在上滑列表同时执行隐藏动画hideListener.onHide();这个onHide()隐藏动画的处理我们到后面分析。
if(distance < -THRESHOLD)表示dy<0且再负方向上的累积已经>20像素了,所以正在下滑列表同时执行显示动画hideListener.onShow();这个onShow()显示动画的处理我们到后面分析。
我们先来看一下目前的执行效果:
我们会发现问题,当我们刚开始上滑动时隐藏动画,下滑时显示动画,这个没问题。但是,一旦我们先下滑然后再上滑,2次动作衔接的比较紧密时动画就会出现问题:底下的FAB浮动按钮就会先探个头,然后又被立即隐藏了。也就是说显示动画还没有执行完全时,隐藏动画就开始执行了,所以导致了这种现象。
那我们现在解决这个问题的思路是:
隐藏动画的执行,还得加一个条件,那就是目前按钮已经执行完了显示动画,即按钮处于显示状态时再执行隐藏动画
显示动画的执行,还得加一个条件,那就是目前按钮已经执行完了隐藏动画,即按钮处于隐藏状态时再执行显示动画
现在MyScrollListener的代码应该是这样的:
-
package com.example.administrator.fabtoolbarscrollhide;
-
-
import android.support.v7.widget.RecyclerView;
-
import android.support.v7.widget.RecyclerView.OnScrollListener;
-
-
class MyScrollListener extends OnScrollListener {
-
private static final int THRESHOLD = 20;
-
private int distance = 0;
-
private HideScrollListener hideListener;
-
private boolean visible = true;
-
-
public MyScrollListener(HideScrollListener hideScrollListener) {
-
// TODO Auto-generated constructor stub
-
this.hideListener = hideScrollListener;
-
}
-
@Override
-
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
-
super.onScrolled(recyclerView, dx, dy);
-
-
if(distance>THRESHOLD&&visible){
-
hideListener.onHide();
-
visible = false;
-
distance = 0;
-
}else if(distance<-20&&!visible){
-
hideListener.onShow();
-
visible = true;
-
distance = 0;
-
}
-
if(visible&&dy>0||(!visible&&dy<0)){
-
distance += dy;
-
}
-
}
-
-
}
以上代码,当我们手指下滑(滚动条向上移动)时,调用hideListener.onShow()显示Toolbar与FAB按钮,然后赋值boolean变量visible = true表示已显示。这时,当我们紧急着手指上滑(滚动条向下移动)时,会多判断一个条件就是当visible = true的条件下再执行隐藏动画,这样既保证了显示动画执行完全后再隐藏(解决了前面讲的动画探头问题),同时也避免执行没必要的隐藏动画,何为没必要的隐藏动画,指的是当我们的界面已经处于隐藏状态下的时候,这时我们虽然手指上滑的距离达到了20px,我们再去执行隐藏动画等于多此一举。同理,我们为了避免执行没必要的distance += dy, 我们用了以下代码:
if(visible&&dy>0||(!visible&&dy<0)){
distance += dy;
}
即当界面已经处于显示状态下,如果手指还在继续上滑(dy < 0),那么我们没必要再去计算distance += dy了,因为我们手指上滑隐藏界面的目的已经达到了。distance再累计还是一个比负20更小的一个值了,仍是在重复满足 “显示界面” 的条件。
所以在界面处于显示状态,visible = true时,我们更关注的是什么是时候要隐藏,所以我们添加了if(visible && dy >0) 的条件。因为dy>0时才有可能隐藏,这时计算distance才有意义。 (!visible&&dy<0)这个条件同理,只是它更关注什么时候显示界面而已。
2.3 实现显示和隐藏动画
hideListener.onShow()与hideListener.onHide()函数其实是我们回调了MainAcitivity主页类中的接口实现函数。
2.3.1接口
-
package com.example.administrator.fabtoolbarscrollhide;
-
-
public interface HideScrollListener {
-
-
public void onHide();
-
public void onShow();
-
-
}
2.3.2 MainActivity.java
在这里主要创建了列表,并设置列表监听滚动的类为我们自定义的MyScrollListener,同时传递接口HideScrollListener 的实例给MyScrollListener,以供回调MainActivity中的接口实现函数public void onHide()与 public void onShow();
-
package com.example.administrator.fabtoolbarscrollhide;
-
-
import java.util.ArrayList;
-
import java.util.List;
-
-
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.view.animation.AccelerateInterpolator;
-
import android.view.animation.DecelerateInterpolator;
-
import android.widget.ImageButton;
-
import android.widget.RelativeLayout;
-
import android.widget.RelativeLayout.LayoutParams;
-
-
-
public class MainActivity extends AppCompatActivity implements HideScrollListener{
-
-
private RecyclerView recyclerview;
-
private ImageButton fab;
-
private Toolbar toolbar;
-
-
@Override
-
protected void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.activity_main);
-
-
recyclerview = (RecyclerView)findViewById(R.id.recyclerview);
-
fab = (ImageButton)findViewById(R.id.fab);
-
-
toolbar = (Toolbar)findViewById(R.id.toolbar);
-
setSupportActionBar(toolbar);
-
setTitle("滑动隐藏");
-
// recyclerview.setOnScrollListener(null);
-
recyclerview.addOnScrollListener(new MyScrollListener(this));
-
-
recyclerview.setLayoutManager(new LinearLayoutManager(this));
-
List<String> list = new ArrayList<>();
-
for (int i = 0; i < 50; i++) {
-
list.add("条目"+i);
-
}
-
RecyclerView.Adapter adapter = new RecyclerAdapter(list );
-
recyclerview.setAdapter(adapter );
-
-
}
-
-
@Override
-
public void onHide() {
-
// 隐藏动画--属性动画
-
toolbar.animate().translationY(-toolbar.getHeight()).setInterpolator(new AccelerateInterpolator(3));
-
RelativeLayout.LayoutParams layoutParams = (LayoutParams) fab.getLayoutParams();
-
-
fab.animate().translationY(fab.getHeight()+layoutParams.bottomMargin).setInterpolator(new AccelerateInterpolator(3));
-
}
-
-
@Override
-
public void onShow() {
-
// 显示动画--属性动画
-
toolbar.animate().translationY(0).setInterpolator(new DecelerateInterpolator(3));
-
-
RelativeLayout.LayoutParams layoutParams = (LayoutParams) fab.getLayoutParams();
-
fab.animate().translationY(0).setInterpolator(new DecelerateInterpolator(3));
-
-
}
-
-
}
在这里我们主要讲一下onHide与onShow的代码:
2.3.2.1. onHide函数
(1)隐藏Toolbar
toolbar.animate().translationY(-toolbar.getHeight()).setInterpolator(new AccelerateInterpolator(3));
toolbar在垂直方向上,从当前顶部位置再往上 移动 一段距离(toolbar的高度),这样toobar就相当于从屏幕顶部移出去了。
其中AccelerateInterpolator是平移动画的速度模式,当前为加速模式,就是先慢后快。
(2)隐藏浮动按钮FAB
RelativeLayout.LayoutParams layoutParams = (LayoutParams) fab.getLayoutParams();
fab.animate().translationY(fab.getHeight()+layoutParams.bottomMargin).setInterpolator(new AccelerateInterpolator(3));
从FAB按钮顶部开始,在垂直正方向上移动(Y轴向下移动)一段距离。往下平移的距离是按钮自身的高度与距离屏幕底部的距离,这样就刚好把FAB浮动按钮从屏幕底端移出去了。
2.3.2.2 onShow函数
(1)显示Toolbar
toolbar.animate().translationY(0)
(2)显示浮动按钮FAB
fab.animate().translationY(0)
移动到初始位置,所以参数为0.
再回顾一下最终效果:
OK,通过监听列表滑动来隐藏或显示Toolbar等UI元素,到这里已全部讲解完毕。按照惯例给出源码下载地址:
https://download.csdn.net/download/gaoxiaoweiandy/10865071
文章来源: blog.csdn.net,作者:冉航--小虾米,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/gaoxiaoweiandy/article/details/85016776
- 点赞
- 收藏
- 关注作者
评论(0)