需求
最近遇到了一个需求,需求的界面是类似这样的
xuqiu.png
页面:列表头部有一个搜索框,并且搜索框可跟随RecyclerView 上下滑动,下拉刷新控件在界面最顶部。
交互效果:当列表向下滑动,搜索框浮起,当列表项滑动到界面顶部后,搜索框向上滑动隐藏。搜索框隐藏后再次下拉,搜索框向下滑动出现。
实现的效果如下:
录制1.gif
实现分析
界面分析
fenxi.png最简单快速的实现方式,蓝色区域 作为RecyclerView的一个Item,红色区域是正常的列表项 ,最外层布局使用FrameLayout或RelativeLayout,在RecyclerView外层浮动一个与列表项相同的搜索框布局初始状态为不可见,当RecyclerView向上滑动时,将外层浮动搜索框布局显示,当RecyclerView再次滑动到顶部时,将外层浮动布局隐藏。同时在滑动距离超出列表中搜索框高度时,向上滑动,搜索框开启向上平移动画隐藏,向下滑动时,搜索框开启向下平移动画隐藏。
实现
1、界面布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
tools:context="com.art88.scwen.searchsuspendscroll.MainActivity">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.v4.widget.SwipeRefreshLayout>
<LinearLayout
android:id="@+id/ll_search"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/white"
android:elevation="3dp"
android:paddingBottom="8dp"
android:paddingTop="10dp"
android:visibility="invisible">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="32dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:background="@drawable/home_search_shape"
android:gravity="center">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon_search" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="6dp"
android:text="搜索"
android:textColor="@color/cccccc"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
布局很简单,如果搜索框没要求 高度阴影,可以使用 include 标签 复用搜索框的布局
android:elevation="3dp"
设置视图的高度,并且加上阴影
2、Adapter
public class TestAdapter extends RecyclerView.Adapter<TestAdapter.ViewHolder> {
public static final int TYPE_SEARCH = 0;
public static final int TYPE_NORMAL = 1;
private List<String> mDatas;
private LayoutInflater mLayoutInflater;
public TestAdapter(Context context, List<String> datas) {
mDatas = datas;
mLayoutInflater = LayoutInflater.from(context);
}
@Override
public int getItemViewType(int position) {
return position == 0 ? TYPE_SEARCH : TYPE_NORMAL;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case TYPE_NORMAL:
return new ViewHolder(mLayoutInflater.inflate(R.layout.item_normal_list, parent, false));
case TYPE_SEARCH:
return new ViewHolder(mLayoutInflater.inflate(R.layout.item_search_list, parent, false));
}
return null;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
}
@Override
public int getItemCount() {
return mDatas == null ? 0 : mDatas.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View itemView) {
super(itemView);
}
}
}
Adapter的代码也很简单,一个测试使用的分类型adapter,搜索框类型和列表项类型。
3、实现滑动
重点来了,需要实现滑动交互,需要监听RecyclerView的滑动,计算滑动距离,判断滑动方向,并根据距离和方向改变外层搜索框的可见性并开启动画。
需要的变量:
private LinearLayout ll_search; //外层的搜索框控件
private int mLlSearchHeight; // 搜索框的高度
private int mScrollY; //recyclerview 滑动的距离
private boolean isShow = true; //搜索框是否显示
private boolean isAnimmating;//是否正在进行动画
主要实现代码
recycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//dy是垂直滚动距离,手指上滑动的时候为正,手指下滑的时候为负
//需要获取llSearch 的高度作为 判断条件 所以布局文件中 llSearch的 visiable属性 不能设置为 gone
// 设置为 gone之后,llSearch 不进行渲染 获取不到高度
if (mLlSearchHeight == 0) {
mLlSearchHeight = ll_search.getHeight();
}
//记录滑动的距离
mScrollY += dy;
if (mScrollY <= 0) {
ll_search.setVisibility(View.INVISIBLE);
} else {
ll_search.setVisibility(View.VISIBLE);
}
if (isAnimmating || (mScrollY <= mLlSearchHeight)) {
return;
}
if (dy < 0) {
if (isShow) {
return;
}
ObjectAnimator animator = ObjectAnimator.ofFloat(ll_search, "translationY", -mLlSearchHeight, 0);
animator.setDuration(300);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
isShow = true;
isAnimmating = false;
animation.removeAllListeners();
}
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
isAnimmating = true;
}
});
animator.start();
} else {
if (!isShow) {
return;
}
ObjectAnimator animator = ObjectAnimator.ofFloat(ll_search, "translationY", 0, -mLlSearchHeight);
animator.setDuration(300);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
isShow = false;
isAnimmating = false;
animation.removeAllListeners();
}
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
isAnimmating = true;
}
});
animator.start();
}
}
});
}
1、监听滑动的方向并计算滑动的距离
RecyclerView可以通过onScrolled方法参数中的 dy 来判断滑动的方向,手指向上滑动 dy 为正,手指向下滑动dy 为负数。并且这个方法调用非常频繁,当界面发生滚动,方法就会被调用,dy表示竖直方向上视图滚动的差值,通过mScrollY += dy
记录RecyclerView滑动的距离。
2、根据滑动的距离改变ll_search 的可见性
当RecyclerView 滑动到界面最顶部,设置ll_search 为不可见状态,否则设置为可见状态。
if (mScrollY <= 0) {
ll_search.setVisibility(View.INVISIBLE);
} else {
ll_search.setVisibility(View.VISIBLE);
}
3、根据滑动的距离和方向进行动画
只有当滑动超过 ll_search 的高度之后才进行动画
if (mScrollY <= mLlSearchHeight) {
return;
}
根据方向执行动画
if (dy < 0) {
ObjectAnimator animator = ObjectAnimator.ofFloat(ll_search, "translationY", -mLlSearchHeight, 0);
animator.setDuration(300);
animator.start();
} else {
ObjectAnimator animator = ObjectAnimator.ofFloat(ll_search, "translationY", 0, -mLlSearchHeight);
animator.setDuration(300);
animator.start();
}
当ll_search 已经处于显示状态,屏蔽下移动画,当ll_search 处于隐藏状态,屏蔽上移动画
if (dy < 0) {
if (isShow) {
return;
}
else{
if (!isShow) {
return;
}
}
当正在执行动画,屏蔽开启动画的操作
if (isAnimmating || (mScrollY <= mLlSearchHeight)) {
return;
}
至此,实现效果代码分析完毕。
网友评论