搜索框悬浮并根据RecyclerView滑动显示隐藏

需求

最近遇到了一个需求,需求的界面是类似这样的


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">
<span class="hljs-tag">&lt;<span class="hljs-name">android.support.v4.widget.SwipeRefreshLayout</span>
    <span class="hljs-attr">android:id</span>=<span class="hljs-string">"@+id/refresh_layout"</span>
    <span class="hljs-attr">android:layout_width</span>=<span class="hljs-string">"match_parent"</span>
    <span class="hljs-attr">android:layout_height</span>=<span class="hljs-string">"match_parent"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">android.support.v7.widget.RecyclerView</span>
        <span class="hljs-attr">android:id</span>=<span class="hljs-string">"@+id/recycler"</span>
        <span class="hljs-attr">android:layout_width</span>=<span class="hljs-string">"match_parent"</span>
        <span class="hljs-attr">android:layout_height</span>=<span class="hljs-string">"wrap_content"</span>/&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">android.support.v4.widget.SwipeRefreshLayout</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">LinearLayout</span>
    <span class="hljs-attr">android:id</span>=<span class="hljs-string">"@+id/ll_search"</span>
    <span class="hljs-attr">android:layout_width</span>=<span class="hljs-string">"match_parent"</span>
    <span class="hljs-attr">android:layout_height</span>=<span class="hljs-string">"50dp"</span>
    <span class="hljs-attr">android:background</span>=<span class="hljs-string">"@color/white"</span>
    <span class="hljs-attr">android:elevation</span>=<span class="hljs-string">"3dp"</span>
    <span class="hljs-attr">android:paddingBottom</span>=<span class="hljs-string">"8dp"</span>
    <span class="hljs-attr">android:paddingTop</span>=<span class="hljs-string">"10dp"</span>
    <span class="hljs-attr">android:visibility</span>=<span class="hljs-string">"invisible"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">LinearLayout</span>
        <span class="hljs-attr">android:layout_width</span>=<span class="hljs-string">"match_parent"</span>
        <span class="hljs-attr">android:layout_height</span>=<span class="hljs-string">"32dp"</span>
        <span class="hljs-attr">android:layout_marginLeft</span>=<span class="hljs-string">"12dp"</span>
        <span class="hljs-attr">android:layout_marginRight</span>=<span class="hljs-string">"12dp"</span>
        <span class="hljs-attr">android:background</span>=<span class="hljs-string">"@drawable/home_search_shape"</span>
        <span class="hljs-attr">android:gravity</span>=<span class="hljs-string">"center"</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">LinearLayout</span>
            <span class="hljs-attr">android:layout_width</span>=<span class="hljs-string">"wrap_content"</span>
            <span class="hljs-attr">android:layout_height</span>=<span class="hljs-string">"wrap_content"</span>
            <span class="hljs-attr">android:gravity</span>=<span class="hljs-string">"center_vertical"</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">ImageView</span>
                <span class="hljs-attr">android:layout_width</span>=<span class="hljs-string">"wrap_content"</span>
                <span class="hljs-attr">android:layout_height</span>=<span class="hljs-string">"wrap_content"</span>
                <span class="hljs-attr">android:src</span>=<span class="hljs-string">"@drawable/icon_search"</span> /&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">TextView</span>
                <span class="hljs-attr">android:layout_width</span>=<span class="hljs-string">"wrap_content"</span>
                <span class="hljs-attr">android:layout_height</span>=<span class="hljs-string">"wrap_content"</span>
                <span class="hljs-attr">android:layout_marginLeft</span>=<span class="hljs-string">"6dp"</span>
                <span class="hljs-attr">android:text</span>=<span class="hljs-string">"搜索"</span>
                <span class="hljs-attr">android:textColor</span>=<span class="hljs-string">"@color/cccccc"</span>
                <span class="hljs-attr">android:textSize</span>=<span class="hljs-string">"14sp"</span> /&gt;</span>

        <span class="hljs-tag">&lt;/<span class="hljs-name">LinearLayout</span>&gt;</span>

    <span class="hljs-tag">&lt;/<span class="hljs-name">LinearLayout</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">LinearLayout</span>&gt;</span>

</RelativeLayout>

布局很简单,如果搜索框没要求 高度阴影,可以使用 include 标签 复用搜索框的布局
android:elevation="3dp"设置视图的高度,并且加上阴影

2、Adapter
public class TestAdapter extends RecyclerView.Adapter<TestAdapter.ViewHolder> {
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> TYPE_SEARCH = <span class="hljs-number">0</span>;
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> TYPE_NORMAL = <span class="hljs-number">1</span>;

<span class="hljs-keyword">private</span> List&lt;String&gt; mDatas;

<span class="hljs-keyword">private</span> LayoutInflater mLayoutInflater;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">TestAdapter</span><span class="hljs-params">(Context context, List&lt;String&gt; datas)</span> </span>{
    mDatas = datas;
    mLayoutInflater = LayoutInflater.from(context);
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getItemViewType</span><span class="hljs-params">(<span class="hljs-keyword">int</span> position)</span> </span>{
    <span class="hljs-keyword">return</span> position == <span class="hljs-number">0</span> ? TYPE_SEARCH : TYPE_NORMAL;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> ViewHolder <span class="hljs-title">onCreateViewHolder</span><span class="hljs-params">(ViewGroup parent, <span class="hljs-keyword">int</span> viewType)</span> </span>{
    <span class="hljs-keyword">switch</span> (viewType) {
        <span class="hljs-keyword">case</span> TYPE_NORMAL:
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ViewHolder(mLayoutInflater.inflate(R.layout.item_normal_list, parent, <span class="hljs-keyword">false</span>));
        <span class="hljs-keyword">case</span> TYPE_SEARCH:

            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ViewHolder(mLayoutInflater.inflate(R.layout.item_search_list, parent, <span class="hljs-keyword">false</span>));
    }
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onBindViewHolder</span><span class="hljs-params">(ViewHolder holder, <span class="hljs-keyword">int</span> position)</span> </span>{

}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getItemCount</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">return</span> mDatas == <span class="hljs-keyword">null</span> ? <span class="hljs-number">0</span> : mDatas.size();
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ViewHolder</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">RecyclerView</span>.<span class="hljs-title">ViewHolder</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ViewHolder</span><span class="hljs-params">(View itemView)</span> </span>{
        <span class="hljs-keyword">super</span>(itemView);
    }
}

}

Adapter的代码也很简单,一个测试使用的分类型adapter,搜索框类型和列表项类型。

3、实现滑动

重点来了,需要实现滑动交互,需要监听RecyclerView的滑动,计算滑动距离,判断滑动方向,并根据距离和方向改变外层搜索框的可见性并开启动画。
需要的变量:

    private LinearLayout ll_search;  //外层的搜索框控件
<span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> mLlSearchHeight; <span class="hljs-comment">// 搜索框的高度</span>

<span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> mScrollY;  <span class="hljs-comment">//recyclerview 滑动的距离</span>

<span class="hljs-keyword">private</span> <span class="hljs-keyword">boolean</span> isShow = <span class="hljs-keyword">true</span>;  <span class="hljs-comment">//搜索框是否显示</span>

<span class="hljs-keyword">private</span> <span class="hljs-keyword">boolean</span> isAnimmating;<span class="hljs-comment">//是否正在进行动画</span>

主要实现代码

 recycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                //dy是垂直滚动距离,手指上滑动的时候为正,手指下滑的时候为负
            <span class="hljs-comment">//需要获取llSearch 的高度作为 判断条件   所以布局文件中  llSearch的 visiable属性  不能设置为 gone</span>
            <span class="hljs-comment">// 设置为  gone之后,llSearch 不进行渲染  获取不到高度</span>
            <span class="hljs-keyword">if</span> (mLlSearchHeight == <span class="hljs-number">0</span>) {
                mLlSearchHeight = ll_search.getHeight();
            }
            <span class="hljs-comment">//记录滑动的距离</span>
            mScrollY += dy;

            <span class="hljs-keyword">if</span> (mScrollY &lt;= <span class="hljs-number">0</span>) {
                ll_search.setVisibility(View.INVISIBLE);
            } <span class="hljs-keyword">else</span> {
                ll_search.setVisibility(View.VISIBLE);
            }

            <span class="hljs-keyword">if</span> (isAnimmating || (mScrollY &lt;= mLlSearchHeight)) {
                <span class="hljs-keyword">return</span>;
            }

            <span class="hljs-keyword">if</span> (dy &lt; <span class="hljs-number">0</span>) {
              
                <span class="hljs-keyword">if</span> (isShow) {
                    <span class="hljs-keyword">return</span>;
                }
           
                ObjectAnimator animator = ObjectAnimator.ofFloat(ll_search, <span class="hljs-string">"translationY"</span>, -mLlSearchHeight, <span class="hljs-number">0</span>);
                animator.setDuration(<span class="hljs-number">300</span>);
                animator.addListener(<span class="hljs-keyword">new</span> AnimatorListenerAdapter() {
                    <span class="hljs-meta">@Override</span>
                    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onAnimationEnd</span><span class="hljs-params">(Animator animation)</span> </span>{
                        <span class="hljs-keyword">super</span>.onAnimationEnd(animation);
                        isShow = <span class="hljs-keyword">true</span>;
                        isAnimmating = <span class="hljs-keyword">false</span>;
                        animation.removeAllListeners();
                    }

                    <span class="hljs-meta">@Override</span>
                    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onAnimationStart</span><span class="hljs-params">(Animator animation)</span> </span>{
                        <span class="hljs-keyword">super</span>.onAnimationStart(animation);
                        isAnimmating = <span class="hljs-keyword">true</span>;

                    }
                });
                animator.start();
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">if</span> (!isShow) {
                    <span class="hljs-keyword">return</span>;
                }
                ObjectAnimator animator = ObjectAnimator.ofFloat(ll_search, <span class="hljs-string">"translationY"</span>, <span class="hljs-number">0</span>, -mLlSearchHeight);
                animator.setDuration(<span class="hljs-number">300</span>);
                animator.addListener(<span class="hljs-keyword">new</span> AnimatorListenerAdapter() {
                    <span class="hljs-meta">@Override</span>
                    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onAnimationEnd</span><span class="hljs-params">(Animator animation)</span> </span>{
                        <span class="hljs-keyword">super</span>.onAnimationEnd(animation);
                        isShow = <span class="hljs-keyword">false</span>;
                        isAnimmating = <span class="hljs-keyword">false</span>;
                        animation.removeAllListeners();
                    }

                    <span class="hljs-meta">@Override</span>
                    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onAnimationStart</span><span class="hljs-params">(Animator animation)</span> </span>{
                        <span class="hljs-keyword">super</span>.onAnimationStart(animation);
                        isAnimmating = <span class="hljs-keyword">true</span>;

                    }
                });
                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, <span class="hljs-string">"translationY"</span>, -mLlSearchHeight, 0);
                animator.setDuration(300);
                animator.start();
            } <span class="hljs-keyword">else</span> {
              
                ObjectAnimator animator = ObjectAnimator.ofFloat(ll_search, <span class="hljs-string">"translationY"</span>, 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;
  }

至此,实现效果代码分析完毕。

      </div>
    </div>

转自:https://www.jianshu.com/p/70953f931f74

猜你喜欢

转载自blog.csdn.net/u013651026/article/details/88952229
今日推荐