用 RecyclerView 实现『贝壳单词』英语角的 Quick Return 效果

今天把 贝壳单词 中英语角的 quick return 效果剥离出来写了个 Demo,讲解使用 RecyclerView 写快速返回菜单,效果如下(分别是贝壳单词和 Demo 的截图):

贝壳单词英语角   device-2015-02-14-143056

通过这篇文章你将了解到的知识有:

  • RecyclerView 和其适配器的基本使用
  • RV 适配多种 Item View 类型写法
  • mRecyclerView.setOnScrollListener()
  • nineoldandroids 这个强大 View 操作库的使用


首先要在 layout 布局中新建 RecyclerView (以下称 RV) 布局,需要两个层次,一层 RV 置于底层,一层头部的 quick return View,没难度,不细说了,布局如下:

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <include layout="@layout/view_header"/>

</FrameLayout>

与这个布局相关的实例化关联代码我也都不再说明了,这些简单琐碎的细节如果不懂可以直接看源码,源码在文章末尾会给出。

关键点1:给第 0 位 item 设置空白占位 View

说明:为什么要使用这样的方式,因为像 ListView、RecyclerView 这类控件,如果动态改变它整体位置会很卡很糟糕,因此只能将第 0 位 item 设置为空白占位 View,其之上将覆盖那个能够快速返回复出的 Header View。所以这里需要在一开始把这个空白 view 量出来,并且设定进去:

LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(linearLayoutManager);
mDataList = new ArrayList<>();

View paddingView = new View(this);
AbsListView.LayoutParams params = new AbsListView.LayoutParams(
        AbsListView.LayoutParams.MATCH_PARENT, mFlexibleSpaceOffset
);
paddingView.setLayoutParams(params);
paddingView.setBackgroundColor(Color.WHITE);
mListAdapter = new MyListAdapter(mDataList);
mListAdapter.addHeaderView(paddingView);
mRecyclerView.setAdapter(mListAdapter);

以上是给 RV 设置布局管理器,和设置 paddingView, 即我在上面说明的空白占位 View。

关键点2:给 RV 设置滚动监听器

说明 RV 自带有不错的滚动监听器设置方法,值得一说的是 onScrolled 方法的 dx、dy 参数,差不多可以说是单位时间内滚动的距离,所以我们如果要获取一次滚动操作的总距离,就要把这一次的全部 dy 叠加起来。官方文档对于这两个参数的解释是:

dx The amount of horizontal scroll.
dy The amount of vertical scroll.

我的理解就是单位时间内滚动的距离(可正可负),其命名 d,也和数学的微分表达一样。

在明白了这两个参数之后,就很好办了,代码如下:

mRecyclerView.setOnScrollListener(
        new RecyclerView.OnScrollListener() {
            boolean isIdle;
            int scrollY;

            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                isIdle = newState == RecyclerView.SCROLL_STATE_IDLE;
                if (isIdle) {
                    scrollY = 0;
                }
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                scrollY += dy;
                // show or hide header view
                if (scrollY > 12) {
                    hideHeader();
                } else {
                    showHeader();
                }
            }
        }
);

顶部 View 收起和复出的代码,其实很简单,看了就知道了,这个 ViewPropertyAnimator 工具类就是来自 nineoldandroids.

private void showHeader() {
    if (!mHeaderIsShown) {
        ViewPropertyAnimator.animate(mHeader).cancel();
        ViewPropertyAnimator.animate(mHeader).translationY(0).setDuration(200).start();
        mHeaderIsShown = true;
    }
}

private void hideHeader() {
    if (mHeaderIsShown) {
        ViewPropertyAnimator.animate(mHeader).cancel();
        ViewPropertyAnimator.animate(mHeader).translationY(-mFlexibleSpaceOffset).setDuration(200).start();
        mHeaderIsShown = false;
    }
}

关键点3:Adapter 多类型

说明:由于第 0 个位置我们已经加入了空白 View 占位了,所以实际上,这个 RV 中是有两种 View 的,一种就是那个空白 View,它将被 quick return 覆盖,一种是从第 1 个位置开始的正常 item view。

所以要重写 getItemViewType 方法:

return position == 0 ? TYPE_HEADER : TYPE_CHILD;

然后父类的 onCreateViewHolder 会调用到上面这个方法,判断 type 然后返回相应的 View:

@Override
public MyListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (mView != null && viewType == TYPE_HEADER) {
        return new ViewHolder(mView);
    } else {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list, parent, false);
        ViewHolder holder = new ViewHolder(v);
        holder.content = (TextView) v.findViewById(R.id.content);
        return holder;
    }
}

@Override
public void onBindViewHolder(MyListAdapter.ViewHolder holder, int position) {
    if (holder.getItemViewType() == TYPE_HEADER) return;
    if (mView != null) {
        position = position - 1;
    }
    final String string = mList.get(position);
    holder.content.setText(string);
}

值得注意的是,我预留了一个 public void addHeaderView(View view) 的方法,用来给外部调用设置这个空白 View,也就是我们一开始使用的那个方法,并且在这个 Adapter 中如果这个 HeaderView 不为空,就是有空白 View,就要在 getItemCount 方法中返回的时候,+1:

@Override
public int getItemCount() {
    return mView != null ? mList.size() + 1 : mList.size();
}

至此,基本所有的关键的都讲完了,如果还有细节问题,可以再看看我的源代码:

https://github.com/drakeet/RecyclerQuickReturnDemo

(注意:我使用的是最新的 Android Studio 最新的 Gradle,如果你编译或载入不了,也不用太在意,直接用文本编辑器什么的打开 java 源代码文件阅读就好,不一定非要用 IDE)

ic_launcher Demo apk 下载:

https://github.com/drakeet/RecyclerQuickReturnDemo/raw/master/app-demo.apk

  1. 这样根据dy的和来控制显隐感觉有一点问题,比如在顶栏隐藏时,从屏幕顶部手指下滑,顶栏出现,可如果想在手指不抬起的情况下上滑收回顶栏必须把手指移到最初的位置之上。如果最初按下的点比较靠上的话顶栏此时就很难收回,这与下滑顶栏出现,上滑顶栏隐藏的直觉相违背。

  2. 请教,配图的GIF是怎么录制的?手机端有这种专门的录屏工具嘛?

  3. Pingback: web design services Melbourne

  4. Pingback: assurance vie luxembourg

  5. Pingback: digital agency Melbourne

  6. Pingback: make money through udemy

  7. Pingback: Motogolf

  8. Pingback: Internet Services in Australia

  9. Pingback: Entertainment and Movie reviews with tips on how to get Website Traffic and Make Money Online.

  10. Pingback: para para dinle

  11. Pingback: ankara escort

  12. Pingback: tania de saram

  13. Pingback: Best Newspaper in India

  14. Pingback: para kazanmak

  15. Pingback: rhodium

  16. Pingback: joseph shihara rukshan de saram

  17. Pingback: smart mode tøj

  18. Pingback: economics tuition

  19. Pingback: kimim ben

  20. Pingback: xnxx

  21. Pingback: click here to find a lawyer

  22. Pingback: lawyers

  23. Pingback: where can i buy testosterone cypionate

  24. Pingback: tri-tren 150

  25. Pingback: http://www.godwinsremovals.co.uk/international-removals/northern-ireland

  26. Pingback: boldenon

  27. Pingback: apk downloads

  28. Pingback: download free

  29. Pingback: 100 layer of glue

  30. Pingback: para herseydir

  31. Pingback: bluetooth speaker box

  32. Pingback: financial fraud

  33. Pingback: trump for children

  34. Pingback: Bilskrot Göteborg

  35. Pingback: foundation

  36. Pingback: www.friv.run

  37. Pingback: juegosfriv.one/

  38. Pingback: tivibu

  39. Pingback: senior portraits

  40. Pingback: group tour