首页
网站首页
公司简介
资讯中心
推荐内容
返回顶部
葡京赌场网址兼容库的使用,总结和分析几种判断RecyclerView到达底部的方法
发布时间:2020-03-23 10:17
浏览次数:

前言:近来学习了Android Material Design 兼容库,为了把这个弄懂,才有了这篇博客,这里先推荐两篇博客:1.Android Material Design 兼容库的使用详解2.Android应用Design Support Library完全使用实例 第一篇博客是这个兼容库的详细解析,我参考了里面的许多内容,第二篇是兼容库的大致介绍,如果你能把这两篇全部弄懂,我这篇也没有必要看了。说了这么多,开始正文吧,大家有什么问题或建议,欢迎留言交流。

本文是这个系列的第三篇,不出意外也是终结篇。因为使用经过重构后的控件已经可以快速实现市面上带 索引导航、悬停分组的列表界面了。在前两篇里,我们从0开始,一步一步实现了仿微信通讯录、饿了么选餐界面。(第一篇戳我 第二篇戳我)这篇文章作为终结篇,和前文相比,主要涉及以下内容:

上一篇文章我讲到用事件分发的原理结合SwipeRefreshLayout写一个RecyclerView的上下拉,里面有一个判断RecyclerView是否到达底部的方法isBottom。我的同事用了这个上下拉之后发现有些小bug,没考虑周全,譬如各个子项高度不统一的时候,然后我找到原因是因为这个判断上下拉的问题。所以,我就去网上查到几种判断RecyclerView到达底部的方法,发现各有千秋。以下的分析都以上一篇文章的SwipeRecyclerView为例。

博文出处:Volley框架源码解析,欢迎大家关注我的博客,谢谢!

文章所用的示例地址:[mecury/MeterialDesignSupportLibrary]

  • 重构悬停分组,将TitleItemDecoration更名为SuspensionDecoration,数据源依赖ISuspensionInterface接口。
  • 重构索引导航,将IndexBar对数据源的操作,如排序,转拼音等分离出去,以接口IIndexBarDataHelper通信。
  • 有N多兄弟给我留言、加QQ问的:如何实现美团选择城市列表页面,
  • 添加一个不带悬停分组的HeaderView
1.lastVisibleItemPosition == totalItemCount - 1判断;2.computeVerticalScrollRange()等三个方法判断;3.canScrollVertically判断;4.利用RecyclerView的LinearLayoutManager几个方法判断。

在 2013 年的 Google I/O 大会上,Volley 网络通信框架正式发布。Volley 框架被设计为适用于网络请求非常频繁但是数据量并不是特别大的情景,正如它的名字一样。Volley 相比其他网络框架而言,采用了在 Android 2.3 以下使用 HttpClient ,而 Android 2.3 及以上使用 HttpUrlConnection 的方案。这是因为在 Android 2.3 以下时,HttpUrlConnection 并不完善,有很多 bug 存在。因此在 Android 2.3 以下最好使用 HttpClient 来进行网络通信;而在 Android 2.3 及以上,HttpUrlConnection 比起 HttpClient 来说更加简单易用,修复了之前的 bug 。所以在 Android 2.3 及以上我们使用 HttpUrlConnection 来进行网络通信。

在2015年,google官方升级了Design Support Library,为开发者增加了不少可用的开发控件。

代码传送门:喜欢的话,随手点个star。多谢https://github.com/mcxtzhang/SuspensionIndexBar

其实,第2和第3种是属于同一种方法,在下面的分析会讲到。

除此之外,Volley 框架还具有优先级处理、可扩展性强等特点。虽然现在有 Retrofit 、OkHttp 等十分优秀的网络通信框架,但是深入理解 Volley 框架内部的思想可以大大提高我们自身的技术水平,毕竟仅仅停留在只会使用的阶段是不行的哦。那么,下面就进入我们今天的正题吧!( ps :本文篇幅过长,可能会引起不适,请在家长的陪同下观看)

支持Android 2.1以上设备。Gradle build script dependency:

老规矩,先上图:。

一、首先,我们来介绍和分析一下第一种方法,也是网上最多人用的方法:

在长篇大论地解析 Volley 框架源码之前,我们先来看看平时是怎样使用 Volley 的。(大牛可直接跳过 -_- )

compile 'com.android.support:design:22.2.0' //可修改版本号为自己匹配

葡京赌场网址 1美团选择城市界面,先刷新Body主体数据,再定向刷新头部数据葡京赌场网址 2微信通讯录界面葡京赌场网址 3配合我另一个库组装的效果(SuspensionIndexBar

public static boolean isVisBottom(RecyclerView recyclerView){ LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); //屏幕中最后一个可见子项的position int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition(); //当前屏幕所看到的子项个数 int visibleItemCount = layoutManager.getChildCount(); //当前RecyclerView的所有子项个数 int totalItemCount = layoutManager.getItemCount(); //RecyclerView的滑动状态 int state = recyclerView.getScrollState(); if(visibleItemCount > 0 && lastVisibleItemPosition == totalItemCount - 1 && state == recyclerView.SCROLL_STATE_IDLE){ return true; }else { return false; }}
RequestQueue mQueue = Volley.newRequestQueue;JsonObjectRequest request = new JsonObjectRequest(url, null, new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject jsonObject) { // TODO } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { // TODO }});mQueue.add;

Design Support Library包含8个控件,具体如下:

很明显,当屏幕中最后一个子项lastVisibleItemPosition等于所有子项个数totalItemCount

1,那么RecyclerView就到达了底部。但是,我在这种方法中发现了极为极端的情况,就是当totalItemCount等于1,而这个子项的高度比屏幕还要高。

葡京赌场网址 4item_recycleview.png

看看效果图:

葡京赌场网址 5效果图.png

我们可以发现这个子项没完全显示出来就已经被判断为拉到底部。当然,这种方法一般情况下都能满足开发者的需求,只是遇到了强迫症的我~

二、下面我们介绍第二种方法:

public static boolean isSlideToBottom(RecyclerView recyclerView) { if (recyclerView == null) return false; if (recyclerView.computeVerticalScrollExtent() + recyclerView.computeVerticalScrollOffset() >= recyclerView.computeVerticalScrollRange return true; return false;}

这种方法原理其实很简单,而且也是View自带的方法。

葡京赌场网址 6原理图.png

这样就很清晰明了,computeVerticalScrollExtent()是当前屏幕显示的区域高度,computeVerticalScrollOffset() 是当前屏幕之前滑过的距离,而computeVerticalScrollRange()是整个View控件的高度。这种方法经过测试,暂时还没发现有bug,而且它用的是View自带的方法,所以个人觉得比较靠谱。

三、下面讲讲第三种方法:

RecyclerView.canScrollVertically的值表示是否能向上滚动,false表示已经滚动到底部RecyclerView.canScrollVertically的值表示是否能向下滚动,false表示已经滚动到顶部

这种方法更简单,就通过简单的调用方法,就可以得到你想要的结果。我一讲过这种方法与第二种方法其实是同一种方法,那下面来分析一下,看看canScrollVertically的源码:

葡京赌场网址 7canScrollVertically的源码是不是一目鸟然了,canScrollVertically方法的实现实际上运用到的是方法二的三个函数,只是这个方法Android已经帮我们封装好了,原理一模一样的。本人现在也是运用了这种方法做判断的懒人工具类都省了~

四、最后一种方法其实是比较呆板的,就是利用LinearLayoutManager的几个方法,1.算出已经滑过的子项的距离,2.算出屏幕的高度,3.算出RecyclerView的总高度。然后用他们做比较,原理类似于方法二。

public static int getItemHeight(RecyclerView recyclerView) { int itemHeight = 0; View child = null; LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); int firstPos = layoutManager.findFirstCompletelyVisibleItemPosition(); int lastPos = layoutManager.findLastCompletelyVisibleItemPosition(); child = layoutManager.findViewByPosition; if (child != null) { RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); itemHeight = child.getHeight() + params.topMargin + params.bottomMargin; } return itemHeight;}

算出一个子项的高度

public static int getLinearScrollY(RecyclerView recyclerView) { int scrollY = 0; LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); int headerCildHeight = getHeaderHeight(recyclerView); int firstPos = layoutManager.findFirstVisibleItemPosition(); View child = layoutManager.findViewByPosition; int itemHeight = getItemHeight(recyclerView); if (child != null) { int firstItemBottom = layoutManager.getDecoratedBottom; scrollY = headerCildHeight + itemHeight * firstPos - firstItemBottom; if(scrollY < 0){ scrollY = 0; } } return scrollY;}

算出滑过的子项的总距离

public static int getLinearTotalHeight(RecyclerView recyclerView) { int totalHeight = 0; LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); View child = layoutManager.findViewByPosition(layoutManager.findFirstVisibleItemPosition; int headerCildHeight = getHeaderHeight(recyclerView); if (child != null) { int itemHeight = getItemHeight(recyclerView); int childCount = layoutManager.getItemCount(); totalHeight = headerCildHeight + (childCount - 1) * itemHeight; } return totalHeight;}

算出所有子项的总高度

public static boolean isLinearBottom(RecyclerView recyclerView) { boolean isBottom = true; int scrollY = getLinearScrollY(recyclerView); int totalHeight = getLinearTotalHeight(recyclerView); int height = recyclerView.getHeight(); // Log.e("height","scrollY " + scrollY + " totalHeight " + totalHeight + " recyclerHeight " + height); if (scrollY + height < totalHeight) { isBottom = false; } return isBottom;}

高度作比较虽然这种方法看上去比较呆板的同时考虑不很周全,但这种方法可以对RecylerView的LinearLayoutManager有深一步的理解,这也是我的师兄给我提供的一个借鉴的类,我非常感谢他!有兴趣的同学可以去下载源码的做进一步的研究,发现有更好玩的方法可以一起研究!源码下载链接

我们通过 Volley.newRequestQueue 来得到一个请求队列的对象 mQueue,在队列中暂存了我们所有 add 进去的 request ,之后一个个取出 request 来进行网络通信。一般来说,在一个应用程序中,只保持一个请求队列的对象。

  • android.support.design.widget.TextInputLayout强大带提示的MD风格的EditText
  • android.support.design.widget.FloatingActionButtonMD风格的圆形按钮,来自于ImageView+android.support.design.widget.Snackbar类似Toast,添加了简单的单个Action
  • android.support.design.widget.TabLayout选项卡
  • android.support.design.widget.NavigationViewDrawerLayout的SlideMenu
  • android.support.design.widget.CoordinatorLayout超级FrameLayout
  • android.support.design.widget.AppBarLayoutMD风格的滑动Layout
  • android.support.design.widget.CollapsingToolbarLayout可折叠MD风格ToolbarLayout

本文将先举例子如何写,并对其中涉及到的重构部分进行讲解。如有不明者,建议先观看(第一篇戳我 第二篇戳我),以及下载Demo,边看代码边阅读,效果更佳。

之后创建了 JsonObjectRequest 对象用来请求 JSON 数据,并把它加入 mQueue 的队列中。Volley 框架的使用方法非常简单,并且有多种 request 请求方式可以选择,使用方法都是和上面类似的。

注意事项:这里需要添加额外的命名空间appNs(xmlns:app="http://schemas.android.com/apk/res-auto" )

转载请标明出处: http://www.jianshu.com/p/813e2f7a9e51本文出自: (http://www.jianshu.com/users/8e91ff99b072/latest_articles)代码传送门:喜欢的话,随手点个star。多谢https://github.com/mcxtzhang/SuspensionIndexBar

在这先把 Volley 框架中几个重要的类的作用讲一下,以便看源码时能够更加明白:

在介绍各个控件之前,大家先看一下预览图,这里我自己写了一个示例,看起来不太好,但是用来介绍是够了,下面看图。

先从简单的用法看起,微信通讯录界面和普通的 **分组悬停&索引导航 **的列表相比:

  • RequestQueue :这个大家一看都明白,用来缓存 request 的请求队列,根据优先级高低排列;
  • Request :表示网络请求,本身是一个抽象类,子类有 StringRequest 、JsonRequest 、ImageRequest 等;
  • Response :表示网络请求后的响应,也是一个抽象类。内部定义了 Listener 、ErrorListener 接口;
  • NetworkResponse :对返回的 HttpResponse 内容进行了封装,虽然类名和 Response 差不多,但是不是 Response 的子类;
  • CacheDispatcher :一个处理请求缓存的线程。不断从 RequestQueue 中取出 Request ,然后取得该 Request 对应的缓存,若缓存存在就调用 ResponseDelivery 做后续分发处理;如果没有缓存或者缓存失效需要进入 NetworkDispatcher 中从网络上获取结果;
  • NetworkDispatcher :一个处理网络请求的线程。和 CacheDispatcher 类似,从网络上得到响应后调用 ResponseDelivery 做后续分发处理。而且根据需求判断是否需要做缓存处理;
  • ResponseDelivery :用作分发处理。利用 Handler 把结果回调到主线程中,即 Listener 、ErrorListener 接口。主要实现类为 ExecutorDelivery ;
  • HttpStack :主要作用就是发起 Http 请求。子类分为 HurlStack 和 HttpClientStack ,分别对应着 HttpUrlConnection 和 HttpClient ;
  • Network :处理 Stack 发起的 Http 请求,把 Request 转换为 Response ,主要实现类为 BasicNetwork ;
  • RetryPolicy :请求重试策略。主要实现类为 DefaultRetryPolicy ;
  • Cache :网络请求的缓存。在 CacheDispatcher 中获取 Cache ,在 NetworkDispatcher 中判断是否保存 Cache 。主要实现类为 DiskBasedCache ,缓存在磁盘中。

葡京赌场网址 8演示动画.gif

  • 多了四个HeaderView
  • 这些HeaderView布局和主体Item一样
  • 这些HeaderView 没有分组悬停title
  • 这些HeaderView是一组的,索引title自定义

看完了之后,我们就要开始源码解析。我们入手点就是 Volley.newRequestQueue 了。

2.1 FLoatingActionButton

演示gif

![FloatingActionButton]NU.png](http://upload-images.jianshu.io/upload_images/1916953-89275ffd20b1f6e5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

一个负责显示界面基本操作的圆形按钮。Design library中的FloatingActionButton 实现了一个默认颜色为主题中colorAccent的悬浮操作按钮。FloatingActionButton继承自ImageView,你可以通过android:src或者 ImageView的任意方法,比如setImageDrawable()来设置FloatingActionButton里面的图标。下面介绍几种常用的设置属性:

  • app:fabSize 设置按钮的大小,有mini和normal可选,默认为normal
  • app:elevation 设置按钮的高度,影响阴影范围的显示
  • app:rippleColor 设置涟漪效果的颜色(当点击按钮时的水波纹)

code:<code><android.support.design.widget.FloatingActionButton android:android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@mipmap/ic_launcher"app:elevation="5dp"app:fabSize="normal" /></code>

无特别注意项,和普通控件类似。

实现:HeaderView不是本文讨论重点,随意实现之。我用的是我自己之前写的,戳我

public class Volley { /** 默认的磁盘缓存目录名 */ private static final String DEFAULT_CACHE_DIR = "volley"; public static RequestQueue newRequestQueue(Context context, HttpStack stack) { File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR); // 设置 UA String userAgent = "volley/0"; try { String packageName = context.getPackageName(); PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); userAgent = packageName + "/" + info.versionCode; } catch (NameNotFoundException e) { } // 根据 Android SDK 版本设置 HttpStack ,分为 HurlStack 和 HttpClientStack // 分别对应着 HttpUrlConnection 和 HttpClient if (stack == null) { if (Build.VERSION.SDK_INT >= 9) { stack = new HurlStack(); } else { // Prior to Gingerbread, HttpUrlConnection was unreliable. // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent)); } } // 得到 network Network network = new BasicNetwork; // 创建 RequestQueue RequestQueue queue = new RequestQueue(new DiskBasedCache, network); queue.start(); return queue; } public static RequestQueue newRequestQueue(Context context) { return newRequestQueue(context, null); }}

2.2 TextInputLayout

演示gif:

葡京赌场网址 9TextInputLayout.gif

在MD中,使用TextInputLayout将EditText进行了封装。其最大的改变就是为EditText添加了两个提示效果。一个显示在上面,提示用户输入的是什么,另一个可以设为Error提示,当输入错误的时候显示出来。

code:

<android.support.design.widget.TextInputLayout android: android:layout_width="match_parent" android:layout_height="wrap_content"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" /> </android.support.design.widget.TextInputLayout>

在代码中使用的是时候,需要监听EditText的变化。

editText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { Log.e("TAG",s.length; if (s.length{//字符超过5个时,出现EditText提示 inputLayout.setError("字符不能超过5个"); inputLayout.setErrorEnabled; }else{ inputLayout.setErrorEnabled; } } @Override public void afterTextChanged(Editable s) { } });```**注意项**TextInputLayout中至少嵌套一个EditText。### 2.3 SnackBar**演示gif:**![SnackBar.gif](http://upload-images.jianshu.io/upload_images/1916953-062d5cf239d70d6b.gif?imageMogr2/auto-orient/strip)SnackBar提供一个轻量级的、快速的反馈。它可以说是一个增强功能的Toast,不同的是SnackBar被固定在底部(Snackbar试图找到一个合适的父亲以确保自己是被放置于底部),包含一段文本信息与一个可选的操作按钮。它会在时间到达时删除,或者被用户点击按钮提前删除。**code**SnackBar的使用十分简单,和Toast十分类似,使用下面代码就可以创建一个SnackBar:

final Snackbar snackbar = Snackbar.make(view,"你点击按钮",Snackbar.LENGTH_SHORT);snackbar.show();

此时如果,你想添加在SnackBar上添加一个按钮,你可以这样:

snackbar.setAction("知道了", new View.OnClickListener() {@Overridepublic void onClick {snackbar.dismiss;

上面代码放在一起,就是动态图的效果。**无特别注意项,和普通控件类似。** ###CoordinatorLayout![CoordinatorLayout.gif](http://upload-images.jianshu.io/upload_images/1916953-bc762c058ab1fde0.gif?imageMogr2/auto-orient/strip)我们来看看官方对他的描述:- CoordinatorLayout is a super-poweredFrameLayout.- CoordinatorLayout is intended for two primary use cases:1.As a top-level application decor or chrome layout2.As a container for a specific interaction with one or more child views从这里我们可以知道它是一个增强版的FrameLayout,他可以协调其子View的交互,控制手势机滚动技巧。这个控件十分的强大,用处也十分的广泛。就比如刚才说的FloatingActionButton如果用CoordinatorLayout 作为FloatingActionButton的父布局,它将很好的协调Snackbar的显示与FloatingActionButton(见上图,可以见到FloatingActionButton随着SnackBar的出现而移动),在Snackbar以动画效果进入的时候自动向上移动让出位置,并且在Snackbar动画地消失的时候回到原来的位置,不需要额外的代码。 CoordinatorLayout的另一个用例是ActionBar与滚动技巧。你可能已经在自己的布局中使用了Toolbar ,它允许你更加自由的自定义其外观与布局的其余部分融为一体。Design library把这种设计带到了更高的水平,使用AppBarLayout可以让你的Toolbar与其他View(比如TabLayout的选项卡)能响应被标记了ScrollingViewBehavior的View的滚动事件。**code**

<?xml version="1.0" encoding="utf-8"?><android.support.design.widget.CoordinatorLayout xmlns:androxmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"&gt;&lt;TextViewandroid:android:layout_width="match_parent"android:layout_height="wrap_content"android:text="CoordinatorLayout展示界面,当点击右下角的FloatingActionButton时,可以看到明显的移动。另外:点击下面的按钮跳转到CoordinatorLayout,AppbarLayout,toolbar等演示界面:" /><android.support.v7.widget.AppCompatButtonandroid:android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="100dp"android:text="跳转"/><android.support.design.widget.FloatingActionButtonandroid:android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="bottom|right"android:layout_margin="10dp"android:src="@mipmap/ic_launcher"app:fabSize="normal"/></android.support.design.widget.CoordinatorLayout>

**无特别注意项,和普通控件类似。** ###NavigationView**演示gif**这个在上面的录制过程中忘了录了,这里也录的有点小瑕疵,大家见谅啊。![Navigation.gif](http://upload-images.jianshu.io/upload_images/1916953-4af0a684c8aaa30a.gif?imageMogr2/auto-orient/strip)NavgationView是一个抽屉式的导航控件,它可以让我们很方便的建立一个滑动菜单。关于用法,以上图为例,见下面代码:**code**navigationview_layout.xml:

<?xml version="1.0" encoding="utf-8"?><android.support.v4.widget.DrawerLayout xmlns:androxmlns:app="http://schemas.android.com/apk/res-auto"android:android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"&gt;

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

<android.support.design.widget.NavigationViewandroid:android:layout_width="wrap_content"android:layout_height="match_parent"android:layout_gravity="start"android:fitsSystemWindows="true"app:headerLayout="@layout/head"app:menu="@menu/navigationview_item"/></android.support.v4.widget.DrawerLayout>

里面内容区的布局,就是侧滑不滑动时显示的布局。里面headerLayout和menu分别为侧滑导航中的头部和item。这里我头部用了一张图片:head.xml:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:androandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical">

<ImageViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:adjustViewBounds="true"android:src="@drawable/header"/></LinearLayout>

对于menu,就是菜单项的配置:navigationview_item.xml:

<?xml version="1.0" encoding="utf-8"?><menu xmlns:androxmlns:app="http://schemas.android.com/apk/res-auto"&gt;&lt;groupandroid:checkableBehavior="single"&gt;&lt;itemandroid:android:icon="@mipmap/ic_home_white"android:title="首页"app:showAsAction="always"/&gt;&lt;itemandroid:android:icon="@mipmap/ic_about"android:title="关于"app:showAsAction="always"/&gt;&lt;/group&gt;&lt;/menu&gt;

上面的布局已经完成了,如果我们添加抽屉导航的Item的点击事件,那又该怎么做呢?下面简单介绍一下:

private void navigationViewListener(){NavigationView mNavigationView = (NavigationView) findViewById(R.id.navigationView);mNavigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {@Overridepublic boolean onNavigationItemSelected(MenuItem item) {switch(item.getItemId{case R.id.menu_main:Toast.makeText(MainActivity.this, "点击了首页", Toast.LENGTH_SHORT).show();break;case R.id.menu_about:Toast.makeText(MainActivity.this, "点击了关于", Toast.LENGTH_SHORT).show();break;}item.setChecked;mDrawerLayout.closeDrawer(Gravity.LEFT); //关闭侧滑菜单return false;}});}看了上面的代码,大家应该很明白应该怎么做了。

###TabLayout![TabLayout.png](http://upload-images.jianshu.io/upload_images/1916953-4fcc58c38233c0dd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)通过选项卡的方式切换View并不是MD中才有的新概念,它们和顶层导航模式或者组织app中不同分组内容(比如,不同风格的音乐)是同一个概念。 Design library的TabLayout 既实现了固定的选项卡(View的宽度平均分配),也实现了可滚动的选项卡(View宽度不固定同时可以横向滚动)。如果你使用ViewPager在 tab之间横向切换,你可以直接从PagerAdapter的getPageTitle() 中创建选项卡,然后使用setupWithViewPager()将两者联系在一起。它可以使tab的选中事件能更新ViewPager,同时 ViewPager的页面改变能更新tab的选中状态。

ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager);FragmentAdapter mAdapter = new FragmentAdapter(getSupportFragmentManager(), titles, fragments);viewPager.setAdapter;tabLayout.setupWithViewPager(viewPager);tabLayout.setTabsFromPagerAdapter;

**注意事项**如果你使用ViewPager在tab之间横向切换,切记可以直接从PagerAdapter的getPageTitle() 中创建选项卡,然后使用setupWithViewPager()将两者联系在一起。 ###AppBarLayout、CollapsingToolbarLayout这介绍这两个之前,大家要明白什么是ToolBar。ToolBar:这代表一个标题栏,图中的绿色区域就是ToolBar。AppBarLayout:其继承于LinearLayout,使用AppBarLayout可以让包含在其中的子控件能响应被标记了ScrollingViewBehavior的的滚动事件(要与CoordinatorLayout 一起使用),利用它我们可以很容易的去实现视差滚动效果,见下图(蓝色区域就是AppBarLayout)。![AppBarLayout.gif](http://upload-images.jianshu.io/upload_images/1916953-5b9a1ffec9403a9f.gif?imageMogr2/auto-orient/strip)CollapsingToolbarLayout:可伸缩折叠的Toolbar (Collapsing Toolbar),直接添加Toolbar到AppBarLayout可以让你使用enterAlwaysCollapsed和 exitUntilCollapsedscroll标志,但是无法控制不同元素如何响应collapsing的细节。当你让CollapsingToolbarLayout和Toolbar在一起使用的时候,title 会在展开的时候自动变得大些,而在折叠的时候让字体过渡到默认值。这里面有一些属性十分重要:这里先介绍一下:**1.app:layout_collapseMode****pin:**来确保Toolbar在view折叠的时候仍然被固定在屏幕的顶部**parallax:**见3**2. app:layout_scrollFlags****scroll**: 所有想滚动出屏幕的view都需要设置这个flag,没有设置这个flag的view将被固定在屏幕顶部。**enterAlways**: 这个flag让任意向下的滚动都会导致该view变为可见,当向上滑的时候Toolbar就隐藏,下滑的时候显示。**enterAlwaysCollapsed**: 顾名思义,这个flag定义的是何时进入(已经消失之后何时再次显示)。假设你定义了一个最小高度(minHeight)同时enterAlways也定义了,那么view将在到达这个最小高度的时候开始显示,并且从这个时候开始慢慢展开,当滚动到顶部的时候展开完。**exitUntilCollapsed**: 同样顾名思义,这个flag时定义何时退出,当你定义了一个minHeight,这个view将在滚动到达这个最小高度的时候消失。**3.定位到ImageView,有这两个属性是我们平常用没看到的,说明我写在注释上了**

app:layout_collapseMode="parallax"//这个是配置当ImageView消失或者显示时候有一种视差滚动效果app:layout_collapseParallaxMultiplier="0.7"//视差因子,越大视差特效越明显,最大为1

**code**xml文件:

<?xml version="1.0" encoding="utf-8"?><android.support.design.widget.CoordinatorLayout xmlns:androxmlns:app="http://schemas.android.com/apk/res-auto"android:android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"&gt;

<!--toolbar--><android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="300dp"> <android.support.design.widget.CollapsingToolbarLayout android: android:layout_width="match_parent" android:layout_height="match_parent" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <android.support.v7.widget.Toolbar android: android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="#77db93" app:layout_collapseMode="pin" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" /> </android.support.design.widget.CollapsingToolbarLayout></android.support.design.widget.AppBarLayout><android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical">

<ImageViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:src="@mipmap/ic_launcher" />

 </LinearLayout></android.support.v4.widget.NestedScrollView>

</android.support.design.widget.CoordinatorLayout>

activity:

public class CoordinationActivity extends AppCompatActivity {

private Toolbar toolbar;private CollapsingToolbarLayout collapsingToolbarLayout;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_coordinator); toolbar =  findViewById(R.id.toolbar); setSupportActionBar; collapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.collapsingtoolbarlayout); collapsingToolbarLayout.setTitle; //设置toolbar的标题 //使用右上角的返回按钮 getSupportActionBar().setHomeButtonEnabled; getSupportActionBar().setDisplayHomeAsUpEnabled; toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick { onBackPressed;}

}

**附(一些控件使用使用应注意的地方):**1.在使用CardView的时候,一定要注意,当CardView的宽和高填充父容器的时候,CardView的margin最好要比cardElevation大,不然就看不到立体的效果。2.我们知道ListView有一个onItemClick的事件,但是RecyclerView却没有,那么我们应该怎样去设置呢?其实很简单,关于RecyclerView设置item的点击事件,只需在创建ViewHolder的时候,给填充的View设置单击事件即可。3.在使用android.support.design.widget.AppBarLayout的时候内容区最好使用android.support.v4.widget.NestedScrollView,之前我的内容区用的是ScrollView,在往上拉的时候AppBarLayout一直没有动画效果,折腾了几个小时都没找到原因。最后逼不得用Android Studio创建了一个他自带的关于AppBarLayout的模板项目,看到他用的是NestedScrollView作为内容区,我果断就把我的内容区换成了这个,立马就有动画效果了。NestedScrollView官方的描述:NestedScrollView is just likeScrollView, but it supports acting as both a nested scrolling parent and child on both new and old versions of Android. Nested scrolling is enabled by default.如果感觉还不错的就给个喜欢支持一下吧,有问题请留言,谢谢最后附一张MD的主题色解析图:![主题色.jpg](http://upload-images.jianshu.io/upload_images/1916953-5a1442f9d9add28c.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)**参考**1.[Android Material Design 兼容库的使用详解](http://www.jianshu.com/p/1e6eed09d48b)2.[Android应用Design Support Library完全使用实例 ](http://www.open-open.com/lib/view/open1433385856119.html)

布局和主体Item一致

由于布局一致,则我们肯定偷懒直接用主体Item的Bean,将city设置为相应的数据即可,如 “新的朋友”:

public class CityBean extends BaseIndexPinyinBean { private String city;//城市名字

从上面 Volley 类的源码中可知,Volley 类主要就是用来创建 RequestQueue 的。我们之前使用的 newRequestQueue(Context context) 方法最终会调用 newRequestQueue(Context context, HttpStack stack) 。Volley 允许我们使用自定义的 HttpStack ,从这也可以看出 Volley 具有很强的扩展性。

没有分组悬停

去掉分组悬停,我们需要重写isShowSuspension()方法,返回false。

接下来继续跟踪 RequestQueue 构造方法的代码。

索引title自定义

它们是一组的,则索引title一致,且需要自定义。四个头部的Bean调用setBaseIndexTag()方法,set自定义的title,且一致即可。

 mDatas.add( new CityBean.setTop.setBaseIndexTag(INDEX_STRING_TOP)); mDatas.add( new CityBean.setTop.setBaseIndexTag(INDEX_STRING_TOP)); mDatas.add( new CityBean.setTop.setBaseIndexTag(INDEX_STRING_TOP)); mDatas.add( new CityBean.setTop.setBaseIndexTag(INDEX_STRING_TOP));
// 默认线程池数量为 4private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;public RequestQueue(Cache cache, Network network) { this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);}public RequestQueue(Cache cache, Network network, int threadPoolSize) { this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper;}public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) { mCache = cache; mNetwork = network; mDispatchers = new NetworkDispatcher[threadPoolSize]; mDelivery = delivery;}

核心代码:

CityBean里引入一个字段 isTop

public class CityBean extends BaseIndexPinyinBean { private String city;//城市名字 private boolean isTop;//是否是最上面的 不需要被转化成拼音的 ... @Override public String getTarget() { return city; } @Override public boolean isNeedToPinyin() { return !isTop; } @Override public boolean isShowSuspension() { return !isTop; }}

初始化:

 mRv.addItemDecoration(mDecoration = new SuspensionDecoration(this, mDatas)); //indexbar初始化 mIndexBar.setmPressedShowTextView(mTvSideBarHint)//设置HintTextView .setNeedRealIndex//设置需要真实的索引 .setmLayoutManager;//设置RecyclerView的LayoutManager

数据加载:

 mDatas = new ArrayList<>(); //微信的头部 也是可以右侧IndexBar导航索引的, // 但是它不需要被ItemDecoration设一个标题titile mDatas.add( new CityBean.setTop.setBaseIndexTag(INDEX_STRING_TOP)); mDatas.add( new CityBean.setTop.setBaseIndexTag(INDEX_STRING_TOP)); mDatas.add( new CityBean.setTop.setBaseIndexTag(INDEX_STRING_TOP)); mDatas.add( new CityBean.setTop.setBaseIndexTag(INDEX_STRING_TOP)); for (int i = 0; i < data.length; i++) { CityBean cityBean = new CityBean(); cityBean.setCity;//设置城市名称 mDatas.add; } ... mIndexBar.setmSourceDatas//设置数据 .invalidate(); mDecoration.setmDatas;

在构造方法中创建了 ExecutorDelivery 对象,ExecutorDelivery 中传入的 Handler 为主线程的,方便得到 Response 后回调;NetworkDispatcher[] 数组对象,默认数组的长度为 4 ,也就意味着默认处理请求的线程最多为 4 个。

涉及到的重构代码

上文提到,重构后,SuspensionDecoration数据源依赖的接口是ISuspensionInterface,如下:

public interface ISuspensionInterface { //是否需要显示悬停title boolean isShowSuspension(); //悬停的title String getSuspensionTag();}

BaseIndexBean里实现,默认显示悬停,分组title和IndexBar的Tag是一样的。

public abstract class BaseIndexBean implements ISuspensionInterface { private String baseIndexTag;//所属的分类(城市的汉语拼音首字母) @Override public String getSuspensionTag() { return baseIndexTag; } @Override public boolean isShowSuspension() { return true; }}

BaseIndexPinyinBean类,现在如下:

public abstract class BaseIndexPinyinBean extends BaseIndexBean { private String baseIndexPinyin;//城市的拼音 //是否需要被转化成拼音, 类似微信头部那种就不需要 美团的也不需要 //微信的头部 不需要显示索引 //美团的头部 索引自定义 //默认应该是需要的 public boolean isNeedToPinyin() { return true; } //需要转化成拼音的目标字段 public abstract String getTarget();}

所以我们需要实现微信那种效果,只需要重写isShowSuspension()isNeedToPinyin()这两个方法,并setBaseIndexTag()直接设置tag即可。

这个页面还是挺麻烦的,所以步骤也最多。建议结合代码阅读Demo及库地址。分析美团选择城市列表:

  • 主体部分仍旧是一个普通的 **分组悬停&索引导航 **的列表。
  • 头部是由若干复杂HeaderView组成。
  • 从右侧索引栏可以看出,定位、最近、热门这三个Item对应了列表三个HeaderView。
  • 最顶部的HeaderView是不需要分组,也不需要索引的。

那么逐一实现:

Volley.newRequestQueue(Context context, HttpStack stack) 中创建完 RequestQueue 对象 queue 之后,还调用了 queue.start() 方法。主要用于启动 queue 中的 mCacheDispatchermDispatchers

主体部分

很简单,根据前文最后的封装,如果只有主体部分,我们需要让主体部分的JavaBean继承自BaseIndexPinyinBean,然后正常构建数据,最终设置给IndexBar和SuspensionDecoration即可。

public class MeiTuanBean extends BaseIndexPinyinBean { private String city;//城市名字 ... @Override public String getTarget() { return city; }}
/** 请求缓存队列 */private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<?>>();/** 网络请求队列 */private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<Request<?>>();public void start() { stop(); // 确保当前 RequestQueue 中的 mCacheDispatcher 和 mDispatchers[] 是关闭的 // 创建 mCacheDispatcher ,并且开启 mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery); mCacheDispatcher.start(); // 根据 mDispatchers[] 数组的长度创建 networkDispatcher ,并且开启 for (int i = 0; i < mDispatchers.length; i++) { NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery); mDispatchers[i] = networkDispatcher; networkDispatcher.start(); }}// 关闭当前的 mCacheDispatcher 和 mDispatchers[]public void stop() { if (mCacheDispatcher != null) { mCacheDispatcher.quit(); } for (int i = 0; i < mDispatchers.length; i++) { if (mDispatchers[i] != null) { mDispatchers[i].quit(); } }}void finish(Request<?> request) { // Remove from the set of requests currently being processed. synchronized (mCurrentRequests) { // 从 mCurrentRequests 中移除该 request mCurrentRequests.remove; } // 如果 request 是可以被缓存的,那么从 mWaitingRequests 中移除,加入到 mCacheQueue 中 if (request.shouldCache { synchronized (mWaitingRequests) { String cacheKey = request.getCacheKey(); Queue<Request<?>> waitingRequests = mWaitingRequests.remove; if (waitingRequests != null) { if (VolleyLog.DEBUG) { VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.", waitingRequests.size(), cacheKey); } // Process all queued up requests. They won't be considered as in flight, but // that's not a problem as the cache has been primed by 'request'. mCacheQueue.addAll(waitingRequests); } } }}

头部若干HeaderViews

这里不管是通过HeaderView添加进来头部布局,还是通过itemViewType自己去实现,核心都是通过itemViewType去做的。也就是说头部的HeaderView也是RecyclerView的Item。既然是Item一定对应着相应的JavaBean。我们需要针对这些JavaBean让其分别继承BaseIndexPinyinBean。具体怎么实现头部布局不是本文重点,不再赘述,Demo里有代码可细看Demo及库地址。

那么看到这里我们意识到有必要看一下 CacheDispatcher 和 NetworkDispatcher 的代码。我们先暂且放一下,来看看 RequestQueue 的 add 方法。add 方法就是把 Request 加入到 RequestQueue 中了:

定、近、热三个HeaderView的处理

定、近、热三个HeaderView有如下特点:

  • 右侧导航索引的title 为自定义,不是拼音首字母则也不需要排序。
  • 悬停分组的title 和 右侧导航索引的title 不一样,则悬停分组的title也需要自定义

做法:不过既然是RecyclerView里的Item,又有 悬停分组、索引导航 特性。那么就要继承BaseIndexPinyinBean

  • 不需要转化成拼音且不排序,则重写isNeedToPinyin()返回false,并调用setBaseIndexTag(indexBarTag)给右侧索引赋值。
  • 需要自定义悬停分组的title,则重写getSuspensionTag()返回title。
public class MeituanHeaderBean extends BaseIndexPinyinBean { private List<String> cityList; //悬停ItemDecoration显示的Tag private String suspensionTag; public MeituanHeaderBean(List<String> cityList, String suspensionTag, String indexBarTag) { this.cityList = cityList; this.suspensionTag = suspensionTag; this.setBaseIndexTag(indexBarTag); } @Override public String getTarget() { return null; } @Override public boolean isNeedToPinyin() { return false; } @Override public String getSuspensionTag() { return suspensionTag; }}

private List<MeituanHeaderBean> mHeaderDatas; 保存定、近、热头部数据源,最终需要将其设置给IndexBarSuspensionDecoration

 mHeaderDatas = new ArrayList<>(); List<String> locationCity = new ArrayList<>(); locationCity.add; mHeaderDatas.add(new MeituanHeaderBean(locationCity, "定位城市", "定")); List<String> recentCitys = new ArrayList<>(); mHeaderDatas.add(new MeituanHeaderBean(recentCitys, "最近访问城市", "近")); List<String> hotCitys = new ArrayList<>(); mHeaderDatas.add(new MeituanHeaderBean(hotCitys, "热门城市", "热"));
private final Map<String, Queue<Request<?>>> mWaitingRequests = new HashMap<String, Queue<Request<?>>>();// 当前正在请求的 Set 集合private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();public <T> Request<T> add(Request<T> request) { // Tag the request as belonging to this queue and add it to the set of current requests. request.setRequestQueue; synchronized (mCurrentRequests) { //加入到当前请求队列中 mCurrentRequests.add; } // 设置序列号,该序列号为 AtomInteger 自增的值 request.setSequence(getSequenceNumber; request.addMarker("add-to-queue"); // 如果该 request 不该缓存,则直接加入 mNetworkQueue ,跳过下面的步骤 if (!request.shouldCache { mNetworkQueue.add; return request; } synchronized (mWaitingRequests) { // 其实 cacheKey 就是 request 的 url String cacheKey = request.getCacheKey(); // 如果该 mWaitingRequests 已经包含了有该 cacheKey if (mWaitingRequests.containsKey) { // 得到该 cacheKey 对应的 Queue Queue<Request<?>> stagedRequests = mWaitingRequests.get; if (stagedRequests == null) { stagedRequests = new LinkedList<Request<?>>(); } stagedRequests.add; // 把该 request 加入到 mWaitingRequests mWaitingRequests.put(cacheKey, stagedRequests); if (VolleyLog.DEBUG) { VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey); } } else { // 如果没有,那么将该 request 加入到 mCacheQueue 中 mWaitingRequests.put(cacheKey, null); mCacheQueue.add; } return request; }}

最顶部的HeaderView

最顶部的HeaderView,由于不需要右侧索引,也没有悬停分组。它只是一个普通的HeaderView即可。对于这种需求的HeaderView,只需要将它们的数量传给IndexBarSuspensionDecoration 即可。在内部我已经做了处理,保证联动坐标和数据源下标的正确。

mDecoration.setHeaderViewCount(mHeaderAdapter.getHeaderViewCount() - mHeaderDatas.size;mIndexBar.setHeaderViewCount(mHeaderAdapter.getHeaderViewCount() - mHeaderDatas.size;

这里用headerView一共的count=4,减去上步中mHeaderDatas的size =3,得出不需要右侧索引,也没有悬停分组 头部的数量。

add(Request<T> request) 方法中,额外使用了两个集合来维护 Request ,其中

将主体数据集和头部数据集合并

我们前几步中,设计到了三部分数据集,一部分是主体数据集,

 //主体部分数据源 private List<MeiTuanBean> mBodyDatas;

第二部分是需要特性的头部数据集

 //头部数据源 private List<MeituanHeaderBean> mHeaderDatas;

第三部分是不需要特性的数据集,这里忽略。我们只用到它的count。我们需要将第一和第二部分融合,并且设置给IndexBarSuspensionDecoration。则我们利用它们共同的基类,BaseIndexPinyinBean来存储。核心代码如下:

 //设置给InexBar、ItemDecoration的完整数据集 private List<BaseIndexPinyinBean> mSourceDatas; mSourceDatas.addAll(mHeaderDatas); mSourceDatas.addAll(mBodyDatas);

设置给IndexBar

 mIndexBar.setmPressedShowTextView(mTvSideBarHint)//设置HintTextView .setNeedRealIndex//设置需要真实的索引 .setmLayoutManager//设置RecyclerView的LayoutManager .setHeaderViewCount(mHeaderAdapter.getHeaderViewCount() - mHeaderDatas.size; .setmSourceDatas(mSourceDatas)//设置数据

设置给SuspensionDecoration

 mRv.addItemDecoration(new SuspensionDecoration(this, mSourceDatas) .setHeaderViewCount(mHeaderAdapter.getHeaderViewCount() - mHeaderDatas.size;

效果图如文首。

  • mCurrentRequests :用来维护正在做请求操作的 Request;
  • mWaitingRequests :主要作用是如果当前有一个 Request 正在请求并且是可以缓存的,那么 Volley 会去 mWaitingRequests 中根据该 cacheKey 查询之前有没有一样的 Request 被加入到 mWaitingRequests 中。若有,那么该 Request 就不需要再被缓存了;若没有就加入到 mCacheQueue 中进行后续操作。

核心代码

这里再提一点,我已经将排序功能抽离至IndexBarIIndexBarDataHelper类型变量中去做,在mIndexBar.setmSourceDatas(mSourceDatas)时会自动排序。也可以手动调用mIndexBar.getDataHelper().sortSourceDatas(mBodyDatas);排序。像本节的案例,可以选择先排序bodyDatas,然后再合并至sourceDatas,最终设置给IndexBarSuspensionDecoration。如:

 //先排序 mIndexBar.getDataHelper().sortSourceDatas(mBodyDatas); mSourceDatas.addAll(mBodyDatas); mIndexBar.setmSourceDatas(mSourceDatas)//设置数据 .invalidate(); mDecoration.setmDatas(mSourceDatas);

现在我们来看看 CacheDispatcher 和 NetworkDispatcher 类的源码。

涉及到的重构代码:

除了上节提到的那些数据结构的重构,我还将以前在IndexBar里完成的:

  • 1 将汉语转成拼音
  • 2 填充indexTag
  • 3 排序源数据源
  • 4 根据排序后的源数据源->indexBar的数据源

抽成一个接口表示,与IndexBar分离。

/** * 介绍:IndexBar 的 数据相关帮助类 * 1 将汉语转成拼音 * 2 填充indexTag * 3 排序源数据源 * 4 根据排序后的源数据源->indexBar的数据源 * 作者:zhangxutong * 邮箱:mcxtzhang@163.com * 主页:http://blog.csdn.net/zxt0601 * 时间: 2016/11/28. */public interface IIndexBarDataHelper { //汉语-》拼音 IIndexBarDataHelper convert(List<? extends BaseIndexPinyinBean> data); //拼音->tag IIndexBarDataHelper fillInexTag(List<? extends BaseIndexPinyinBean> data); //对源数据进行排序(RecyclerView) IIndexBarDataHelper sortSourceDatas(List<? extends BaseIndexPinyinBean> datas); //对IndexBar的数据源进行排序,在 sortSourceDatas 方法后调用 IIndexBarDataHelper getSortedIndexDatas(List<? extends BaseIndexPinyinBean> sourceDatas, List<String> datas);}

IndexBar内部持有这个接口的变量,调用其中方法完成需求:

 public IndexBar setmSourceDatas(List<? extends BaseIndexPinyinBean> mSourceDatas) { this.mSourceDatas = mSourceDatas; initSourceDatas();//对数据源进行初始化 return this; } /** * 初始化原始数据源,并取出索引数据源 * * @return */ private void initSourceDatas() { //add by zhangxutong 2016 09 08 :解决源数据为空 或者size为0的情况, if (null == mSourceDatas || mSourceDatas.isEmpty { return; } if (!isSourceDatasAlreadySorted) { //排序sourceDatas mDataHelper.sortSourceDatas(mSourceDatas); } else { //汉语->拼音 mDataHelper.convert(mSourceDatas); //拼音->tag mDataHelper.fillInexTag(mSourceDatas); } if (isNeedRealIndex) { mDataHelper.getSortedIndexDatas(mSourceDatas, mIndexDatas); computeGapHeight(); } }

我在sortSourceDatas()实现里,已经调用了convert;fillInexTag;

 @Override public IIndexBarDataHelper sortSourceDatas(List<? extends BaseIndexPinyinBean> datas) { if (null == datas || datas.isEmpty { return this; } convert; fillInexTag; //对数据源进行排序 Collections.sort(datas, new Comparator<BaseIndexPinyinBean>() { @Override public int compare(BaseIndexPinyinBean lhs, BaseIndexPinyinBean rhs) { if (!lhs.isNeedToPinyin { return 0; } else if (!rhs.isNeedToPinyin { return 0; } else if (lhs.getBaseIndexTag().equals { return 1; } else if (rhs.getBaseIndexTag().equals { return -1; } else { return lhs.getBaseIndexPinyin().compareTo(rhs.getBaseIndexPinyin; } } }); return this; }

通过如下变量控制,是否需要排序,是否需要提取索引:

 //是否需要根据实际的数据来生成索引数据源(例如 只有 A B C 三种tag,那么索引栏就 A B C 三项) private boolean isNeedRealIndex; //源数据 已经有序? private boolean isSourceDatasAlreadySorted;

首先是 CacheDispatcher 的:

好处

这样做的好处是,当你不喜欢我这种排序方式,亦或你想自定义特殊字符的索引,现在是"#",你都可以通过继承重写IndexBarDataHelperImpl类的方法来完成。或者干脆实现IIndexBarDataHelper接口,这就能满足扩展和不同的定制需求,不用每次修改IndexBar类。

灵活重写ISuspensionInterface接口中的方法,可控制:

  • 是否需要显示悬停title
  • 悬停显示的titles

灵活重写BaseIndexPinyinBean中的方法,可控制:

  • 是否需要被转化成拼音, 类似微信头部那种就不需要 美团的也不需要
  • 微信的头部 不需要显示索引
  • 美团的头部 索引自定义
  • 默认应该是需要的
  • isNeedToPinyin()返回false时,不要忘了手动setBaseIndexTag()设置IndexBar的Tag值.

IndexBarIIndexBarDataHelper都提供了setHeaderViewCount(int headerViewCount)方法,供设置 不需要右侧索引,也没有悬停分组的HeaderView数量。

转载请标明出处: http://www.jianshu.com/p/813e2f7a9e51本文出自: (http://www.jianshu.com/users/8e91ff99b072/latest_articles)代码传送门:喜欢的话,随手点个star。多谢https://github.com/mcxtzhang/SuspensionIndexBar

public class CacheDispatcher extends Thread { ...... // 省略部分源码 // 结束当前线程 public void quit() { mQuit = true; interrupt(); } @Override public void run() { if  VolleyLog.v("start new dispatcher"); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); // 初始化 mCache ,读取磁盘中的缓存文件,加载到 mCache 中的 map 中 // 会造成线程阻塞,要在子线程中调用 mCache.initialize(); while  { try { // 从缓存队列中取出 request ,若没有则会阻塞 final Request<?> request = mCacheQueue.take(); request.addMarker("cache-queue-take"); // 如果该 request 被标记为取消,则跳过该 request ,不分发 if (request.isCanceled { request.finish("cache-discard-canceled"); continue; } // 根据 request 的 url 去获得缓存 Cache.Entry entry = mCache.get(request.getCacheKey; if (entry == null) { request.addMarker("cache-miss"); // 没有缓存,把 Request 放入网络请求队列中 mNetworkQueue.put; continue; } // 若缓存失效,也放入网络请求队列中 if (entry.isExpired { request.addMarker("cache-hit-expired"); request.setCacheEntry; mNetworkQueue.put; continue; } // 缓存存在,把缓存转换为 Response request.addMarker("cache-hit"); Response<?> response = request.parseNetworkResponse( new NetworkResponse(entry.data, entry.responseHeaders)); request.addMarker("cache-hit-parsed"); // 判断缓存是否需要刷新 if (!entry.refreshNeeded { // 不需要刷新就直接让 mDelivery 分发 mDelivery.postResponse(request, response); } else { // 需要刷新缓存 request.addMarker("cache-hit-refresh-needed"); request.setCacheEntry; // 先设置一个标志,表明该缓存可以先分发,之后需要重新刷新 response.intermediate = true; // 利用 mDelivery 先把 response 分发下去,之后还要把该 request 加入到 mNetworkQueue 重新请求一遍 mDelivery.postResponse(request, response, new Runnable() { @Override public void run() { try { mNetworkQueue.put; } catch (InterruptedException e) { // Not much we can do about this. } } }); } } catch (InterruptedException e) { // We may have been interrupted because it was time to quit. if  { return; } continue; } } }}

CacheDispatcher 类主要的代码就如上面所示了,在主要的 run() 方法中都添加了注释,阅读起来应该比较简单。那么在这里就贡献一张 CacheDispatcher 类的流程图:

葡京赌场网址 10CacheDispatcher 类的流程图

友情链接: 网站地图
Copyright © 2015-2019 http://www.nflfreepicks.net. 新葡萄京娱乐场网址有限公司 版权所有