判断Listview滑到顶部的最精准方案,解决Listview设置EmptyView与SwipeRefreshLayout冲突

本文主要是介绍判断Listview滑到顶部的最精准方案,解决Listview设置EmptyView与SwipeRefreshLayout冲突,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

故事发生的背景

SwipeRefreshLayout是谷歌自家控件,提供下拉刷新的功能。然而这个控件简单易用的同时也有一个令人头疼的缺点,那就是它里面只能包含一个子View!有一天,需求来了,需要在为Listview添加EmptyView和下拉刷新,同时当显示EmptyView时也要求有下拉刷新。

尝试与探索

大家都知道,设置EmptyView需要把它放在一个容器内。这还不简单,SwipeRefreshLayout不就很好的容器吗?

    <android.support.v4.widget.SwipeRefreshLayoutandroid:id="@+id/refrashLayout"android:layout_width="match_parent"android:layout_height="match_parent"><ListViewandroid:id="@+id/listview"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_gravity="center_horizontal"android:clipToPadding="false"android:divider="#E1E6E9"android:dividerHeight="20dp"android:padding="20dp"android:scrollbars="none" /><ScrollViewandroid:id="@+id/emptyView"android:layout_width="match_parent"android:layout_height="match_parent"android:fillViewport="true"><TextViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:text="Nothing here..." /></ScrollView></android.support.v4.widget.SwipeRefreshLayout>

故事就这样结束了吗?NO,这样写了后果是,当Listview没有数据显示的时候,EmptyView没有显示出来。

后来,我Google一下,在stackoverflow上看到很多解决方案,找到几种比较靠谱的方案,有一些说需要重写自定义Listview,个人觉得不够优雅就不列举出来了。

方案一:

把EmptyView放到SwipeRefreshLayout外面,这似乎是一个很完美的方案,同时也是最简单的,然而,这样的话EmptyView没有下拉刷新了。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#E1E6E9"android:orientation="vertical"><ScrollView
        android:id="@+id/emptyView"android:layout_width="match_parent"android:layout_height="match_parent"android:visibility="gone"android:fillViewport="true"><TextView
            android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:text="Nothing here..." /></ScrollView><android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/refrashLayout"android:layout_width="match_parent"android:layout_height="match_parent"><ListView
            android:id="@+id/listview"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_gravity="center_horizontal"android:clipToPadding="false"android:divider="#E1E6E9"android:dividerHeight="20dp"android:padding="20dp"android:scrollbars="none" /></android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>

方案二

为了给EmptyView加下拉刷新,在把方案一中的EmptyView套上另一个SwipeRefreshLayout。这样的话就需要两份处理SwipeRefreshLayout的代码了,个人觉得比较麻烦,不够优雅,但也不失为一个完整的解决方案。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#E1E6E9"android:orientation="vertical"><android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/emptyView"android:layout_width=""android:layout_height=""><TextView
            android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:text="Nothing here..." /></android.support.v4.widget.SwipeRefreshLayout><android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/refrashLayout"android:layout_width="match_parent"android:layout_height="match_parent"><ListView
            android:id="@+id/listview"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_gravity="center_horizontal"android:clipToPadding="false"android:divider="#E1E6E9"android:dividerHeight="20dp"android:padding="20dp"android:scrollbars="none" /></android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>

方案三

我一向不喜欢简单粗暴,推崇优雅的解决方法。既然你SwipeRefreshLayout只能包含一个子View,那么我用FrameLayout wrap them all together不就行了? 为什么用FrameLayout? 因为FrameLayout相比于其他几个开销更低。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#E1E6E9"android:orientation="vertical"><android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/refrashLayout"android:layout_width="match_parent"android:layout_height="match_parent"><FrameLayout
            android:layout_width="match_parent"android:layout_height="match_parent"><ListView
                android:id="@+id/listview"android:layout_width="match_parent"android:layout_height="match_parent"android:padding="20dp"android:clipToPadding="false"android:layout_gravity="center_horizontal"android:scrollbars="none"android:divider="#E1E6E9"android:dividerHeight="20dp" /><ScrollView
                android:id="@+id/emptyView"android:layout_width="match_parent"android:layout_height="match_parent"android:fillViewport="true"><TextView
                    android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:text="Nothing here..." /></ScrollView></FrameLayout></android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>

很合理的方案,然而故事并没有这么轻易结束,运行效果如图。
这里写图片描述
LIstview没有到达顶部下拉就会刷新!显然这个bug是无法接受的。我们需要在listview没有到达顶部的时候禁用SwipeRefreshLayout,在listview到达顶部的时候恢复SwipeRefreshLayout,就可以解决这个问题了。显然我们需要判断listview是否到达顶部的方法。

判断Listview滑到顶部的方案

我们网上一搜索,方法都是前篇一律的方案一。

方案一:简洁而粗糙

listview.setEmptyView(emptyView);
//防止SwipeRefreshLayout过早触发
listview.setOnScrollListener(new AbsListView.OnScrollListener() {@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {}@Overridepublic void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {if (firstVisibleItem==0)refrashLayout.setEnabled(true);else refrashLayout.setEnabled(false);}
});

我天真地以为故事真的这样结束了,然而….
这里写图片描述
显然这是最简单的方案了,但却不能精确地判断是否真的滑到顶部!对于一些item高度比较大时,用这种方法判断似乎毫无意义。

终极方案

想到这个方法,我还是从Listview源码里面找到启发(源码真是最好的老师)。当Listview捕捉到ACTION_MOVE滑动动作时会调用trackMotionScroll方法,在里面有这样一段代码,判断子view是否移出屏幕。

if(down){  //手指向下滑动final int top = listPadding.top - incrementalDeltaY;for (int i = 0; i < childCount; i++) {final View child = getChildAt(i);if (child.getBottom() >= top) {break;} else {  //子View已经移出了屏幕count++;int position = firstPosition + i;if (position >= headerViewsCount && position < footerViewsStart) {mRecycler.addScrapView(child);}}}}else{final int bottom = getHeight() - listPadding.bottom - incrementalDeltaY;for (int i = childCount - 1; i >= 0; i--) {final View child = getChildAt(i);if (child.getTop() <= bottom) {break;} else {start = i;count++;int position = firstPosition + i;if (position >= headerViewsCount && position < footerViewsStart) {mRecycler.addScrapView(child);}}}}

那么给listview设置监听器,只要listview的第一个item的顶部y坐标值等于listview的顶部y坐标值表明listview到达了顶部
下面就show you the code!

        listview.setEmptyView(emptyView);listview.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {if (event.getAction() == MotionEvent.ACTION_MOVE) {Log.d("Measure","listview.getListPaddingTop():"+listview.getListPaddingTop()+" listview.getTop():"+listview.getTop()+"listview.getChildAt(0).getTop():"+listview.getChildAt(0).getTop());if (listview.getFirstVisiblePosition() == 0 &&listview.getChildAt(0).getTop() >= listview.getListPaddingTop()) {refrashLayout.setEnabled(true);Log.d("TAG", "reach top!!!");}else refrashLayout.setEnabled(false);}return false;}});

这里写图片描述
EmptyView:
这里写图片描述

在情人节写代码,写博客,其实我是拒绝的~~不说了,完整代码已上传至Github,项目当中其他类不用管,相关代码在MainActivity中。

这篇关于判断Listview滑到顶部的最精准方案,解决Listview设置EmptyView与SwipeRefreshLayout冲突的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/838249

相关文章

Python判断for循环最后一次的6种方法

《Python判断for循环最后一次的6种方法》在Python中,通常我们不会直接判断for循环是否正在执行最后一次迭代,因为Python的for循环是基于可迭代对象的,它不知道也不关心迭代的内部状态... 目录1.使用enuhttp://www.chinasem.cnmerate()和len()来判断for

Java循环创建对象内存溢出的解决方法

《Java循环创建对象内存溢出的解决方法》在Java中,如果在循环中不当地创建大量对象而不及时释放内存,很容易导致内存溢出(OutOfMemoryError),所以本文给大家介绍了Java循环创建对象... 目录问题1. 解决方案2. 示例代码2.1 原始版本(可能导致内存溢出)2.2 修改后的版本问题在

MySQL分表自动化创建的实现方案

《MySQL分表自动化创建的实现方案》在数据库应用场景中,随着数据量的不断增长,单表存储数据可能会面临性能瓶颈,例如查询、插入、更新等操作的效率会逐渐降低,分表是一种有效的优化策略,它将数据分散存储在... 目录一、项目目的二、实现过程(一)mysql 事件调度器结合存储过程方式1. 开启事件调度器2. 创

大数据小内存排序问题如何巧妙解决

《大数据小内存排序问题如何巧妙解决》文章介绍了大数据小内存排序的三种方法:数据库排序、分治法和位图法,数据库排序简单但速度慢,对设备要求高;分治法高效但实现复杂;位图法可读性差,但存储空间受限... 目录三种方法:方法概要数据库排序(http://www.chinasem.cn对数据库设备要求较高)分治法(常

Vue项目中Element UI组件未注册的问题原因及解决方法

《Vue项目中ElementUI组件未注册的问题原因及解决方法》在Vue项目中使用ElementUI组件库时,开发者可能会遇到一些常见问题,例如组件未正确注册导致的警告或错误,本文将详细探讨这些问题... 目录引言一、问题背景1.1 错误信息分析1.2 问题原因二、解决方法2.1 全局引入 Element

如何设置vim永久显示行号

《如何设置vim永久显示行号》在Linux环境下,vim默认不显示行号,这在程序编译出错时定位错误语句非常不便,通过修改vim配置文件vimrc,可以在每次打开vim时永久显示行号... 目录设置vim永久显示行号1.临时显示行号2.永www.chinasem.cn久显示行号总结设置vim永久显示行号在li

linux报错INFO:task xxxxxx:634 blocked for more than 120 seconds.三种解决方式

《linux报错INFO:taskxxxxxx:634blockedformorethan120seconds.三种解决方式》文章描述了一个Linux最小系统运行时出现的“hung_ta... 目录1.问题描述2.解决办法2.1 缩小文件系统缓存大小2.2 修改系统IO调度策略2.3 取消120秒时间限制3

Linux:alias如何设置永久生效

《Linux:alias如何设置永久生效》在Linux中设置别名永久生效的步骤包括:在/root/.bashrc文件中配置别名,保存并退出,然后使用source命令(或点命令)使配置立即生效,这样,别... 目录linux:alias设置永久生效步骤保存退出后功能总结Linux:alias设置永久生效步骤

C#使用HttpClient进行Post请求出现超时问题的解决及优化

《C#使用HttpClient进行Post请求出现超时问题的解决及优化》最近我的控制台程序发现有时候总是出现请求超时等问题,通常好几分钟最多只有3-4个请求,在使用apipost发现并发10个5分钟也... 目录优化结论单例HttpClient连接池耗尽和并发并发异步最终优化后优化结论我直接上优化结论吧,

Spring MVC如何设置响应

《SpringMVC如何设置响应》本文介绍了如何在Spring框架中设置响应,并通过不同的注解返回静态页面、HTML片段和JSON数据,此外,还讲解了如何设置响应的状态码和Header... 目录1. 返回静态页面1.1 Spring 默认扫描路径1.2 @RestController2. 返回 html2