Android AbsListView坐标体系解析

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: Android AbsListView坐标体系解析Android的AbsListView与Android ListView不同,AbsListView代表了一个抽象的列表View。


Android AbsListView坐标体系解析

Android的AbsListView与Android ListView不同,AbsListView代表了一个抽象的列表View。在实际的开发中直接使用Android ListView几乎可以完全完成所有与List这类View相关的开发任务,但在极个别情况下, 需要深入到Android的AbsListView中进行仔细的坐标定位。
为了探究Android的AbsListView,先写一个简单的ListView这样的代码:

package zhangphil.listview;

import android.app.ListActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

public class MainActivity extends ListActivity {

	private ListView listView;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		// setContentView(R.layout.activity_main);

		// 测试数据源
		String[] data = new String[50];
		for (int i = 0; i < data.length; i++) {
			data[i] = "child view:" + i;
		}

		ArrayAdapter adapter = new ArrayAdapter(this, R.layout.item, R.id.textView, data);
		this.setListAdapter(adapter);

		listView = this.getListView();

		// 设置ListView灰色分割线的高度,单位是pix像素
		// this.getListView().setDividerHeight(20);

		listView.setOnScrollListener(new OnScrollListener() {

			private int firstVisibleItem;
			private int totalItemCount;

			@Override
			public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
				this.firstVisibleItem = firstVisibleItem;
				this.totalItemCount = totalItemCount;
			}

			@Override
			public void onScrollStateChanged(AbsListView view, int scrollState) {
				if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {

					Log.d("ListView",
							"ListView: getTop():" + listView.getTop() + " , getBottom():" + listView.getBottom()
									+ " , getY()" + listView.getY() + " , Height:" + listView.getHeight());

					int cnt = view.getChildCount();
					for (int i = 0; i < cnt; i++) {
						View v = view.getChildAt(i);

						// 为了便于分析结果,把child
						// view的position和初始化的那些数据源一一对应起来:i+firstVisibleItem
						Log.d("child view:" + (i + firstVisibleItem),
								"getTop():" + v.getTop() + " , getBottom():" + v.getBottom() + " , getY():" + v.getY());
					}

					if (firstVisibleItem == 0 && isTop(view)) {
						Toast.makeText(getApplicationContext(), "完全见顶!", Toast.LENGTH_SHORT).show();
					}

					if (listView.getLastVisiblePosition() == (totalItemCount - 1) && isBottom(view)) {
						Toast.makeText(getApplicationContext(), "完全见底!", Toast.LENGTH_SHORT).show();
					}
				}
			}
		});
	}

	private boolean isTop(AbsListView view) {
		View v = view.getChildAt(0);
		return v.getTop() == 0;
	}

	private boolean isBottom(AbsListView view) {
		int cnt = view.getChildCount();
		View v = view.getChildAt(cnt - 1);
		return v.getBottom() == listView.getBottom();
	}
}


特别的,把Android ListView需要加载到adapter中的item设置成高度为100pix的子view:

<?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="100px"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView" />

</LinearLayout>

给这个ListView设置setOnScrollListener(new OnScrollListener(){})监听事件,制造实验结果,代码跑起来后滚动然后让ListView见顶后的图(1):



这是代码跑起来后,任意滑动该Listview但最终下滑见顶时候的logcat输出结果(1):


AbsListView与ListView不同,AbsListView代表当前屏幕视野可见范围内从上向下的“一组”view集合。一个ListView理论上讲可以拥有成千上万个子item,但是AbsListView所拥有,仅仅是当前屏幕可见视野范围内从上往下的一组子view集合,从某种角度上讲,AbsListView可以认为是ListView的某一段View子集。
假设Android的listview有n个子item。ListView从position=0开始,直到最后一个子item元素position=n-1结束,但是AbsListView则始终保持当前可见视野范围内的11个子item元素(注意:这在不同的设备结果不同,因为不同的设备屏幕高度不同,所以计算并加载相应个子item。数量到底是多少可以从AbsListView的getChildCount()获得,事实上也完全可以根据屏幕宽度自己手动计算出来)。
图(1)中的11个元素是AbsListView拥有的全部子元素集合,可以从AbsListView的getChildAt(int index)遍历出来每一个子集合。
在前面,故意给ListView的子item设置成100pix高度,Logcat输出结果(1)可以看到,ListView的高度是1038(pix),Listview的getTop()返回的数值和getY()坐标值相同。由于在本例中我故意把子Listview的item设置成100pix,那么第0个子item,在y坐标轴上的占据的高度是0到100pix,position=2的第二个子item是从102pix开始,为何是从102pix开始?因为ListView的默认的灰色分割线要用去1pix的高度。
为了让Logcat输出的结果和listView的position一一对应起来便于分析输出结果,每次在遍历AbsListView的子view时候,在Logcat的tag字段位置以firstVisibleItem为基数,这样就完全和ListView的adapter中的position对应起来。事实上,从一定意义上讲,在OnScrollListener里面onScroll回调得到的firstVisibleItem虽然是ListView中适配器中position,但它就是AbsListView的第一个子元素,visibleItemCount就是AbsListView所拥有的子元素总数,visibleItemCount和AbsListView的getChildCount()相等。
Logcat输出的结果(1)最后一个结果很有趣:


child view 10的getTop返回1020意为从屏幕的1020pix开始,但为何getBottom得到的是1120?要知道,整个listView才不过1038pix的像素高度!为何child view 10竟然超出整个ListView的高度!?
这正是AbsListView特殊的地方,AbsListView是抽象的,在AbsListView看来,child view 10虽然没有完全显示在屏幕上(因为屏幕高度总是有限,不可能无限高容纳所有的子元素),但它依然会被归属到AbsListView中,child view 10从1020pix开始,到AbsListView虚拟抽象出来的1120坐标位置结束。child view 10整体没有显示出来,但child view 10只要有一丁点儿显示在屏幕上,AbsListView就会把它作为子view,此时的child view 10底部被抽象、虚拟的认为跨出ListView的坐标系而存在。(1120-1020=100刚好就是我在布局文件写死的item高度的100pix)


再看一个实验,如图(2):


故意把child view 1不完全显示、遮掩住一部分。


此时Logcat输出结果(2):


注意看第一个输出结果:


child view 1的getTop()也即Y坐标轴上的值竟然是负值!这是AbsListView的坐标体系模型。在AbsListView看来,此时的child view 1被滚出了ListView,但child view 1仍然有一部分显示在屏幕中(71pix高度的部分),而另外一部分(29pix)被滚出ListView而不可见,但AbsListView仍然抽象的认为child view 1依然存在在自己的集合中,要凑足该子item view的高度( 刚好就是我在item布局文件中写死的71-(-29)=100pix ),只是一部分不可见了,不可见的部分由于是头部处于ListView的顶部不可见,那么给其坐标Y赋予负值(-29)以示区别。
由上可知AbsListView会自始至终加载一定数量(假设m)的子item,这些段m个子元素,是ListView全部n个子item顺序中的某一小段。m <= n。
AbsListView将最顶部滚出ListView可见区域的部分子item的Y坐标值赋予负值(虚拟的、抽象的),而在最底部不可见的子item那部分顺次迭加坐标值(虚拟的、抽象的)。这样最顶和最低都能凑成完整的item高度。

意义:明白了AbsListView的虚拟、抽象坐标体系后,其中一个意义就是利用这一点,判断一个ListView是否彻底的由于向下滚动而见顶,以及是否彻底向上滚动测底见底。这在一些常见的下拉、上拉刷新ListView中非常有用。
在扩展ListView功能添加下拉上拉刷新事件时,如何判断一个ListView是否彻底已经见顶或者见底,依靠一些常规的手段比较难解决。如果引入了AbsListView,就把问题的解决变得容易了。
具体结合本文例子加以说明。本文例子中有50个子item,初始化后即可任意滚动。如果换作其他情况更复杂,情况将变化(比如初始化状态无数据或者只有一两个子item根本没铺满ListView),但基本原理相同一致。
(1) 判断ListView滚动到最顶部。
首先在ListView的OnScrollListener里面取出firstVisibleItem是否等于0,如果等于0,那么表示此时的ListView的顶部可能见顶了(为什么说可能呢?因为只要ListView的第0条item只要出现在ListView的最顶部,OnScrollListener就将firstVisibleItem赋值0,无法判断firstVisibleItem到底是全部还是部分出现在ListView最顶部)。在本例中,ListView第0条子item在滚动状态中进入最顶部只有一种情况:从超出ListView顶部的部分渐渐滚入,也即getTop()的值逐渐从负值变成0。ListView的第一个item在完全贴合ListView最顶部的时候其getTop()也就是Y坐标值是0。
接着,此时判断firstVisibleItem的getTop()是否等于0,如果等于0,那么就可以认为此时的ListView最顶部的firstVisibleItem与屏幕的最顶部无缝贴合在一起了,此时可以启动下拉见顶加载更多这样的事件处理业务逻辑。
(2) 判断ListView滚动到最底部。
当ListView最后最末尾一个item完全贴合ListView时候,此时,该item的getBottom()也就是Y坐标轴刚好就是ListView的Y坐标值或者高度值(ListView的getBottom()或者ListView的getHeight(),注意:getHeight()在此处作为判断条件要小心使用,假设一个ListView只有几个item而没有铺满整个布局,但ListView的高度是match_parent,那么此时就要出问题)。ListView本身的设计使得不管如何ListView最后一个item总能在贴合ListView的底部紧密咬合在一起。


附录一些我写的相关文章,均在我的csdn博客中:
1、《Android判断ListView滚动到最顶部第0条item完全完整可见及最底部最后一条item完全完整可见》链接地址:http://blog.csdn.net/zhangphil/article/details/50329601
2、《Android ListView下拉/上拉刷新:设计原理与实现》链接地址:http://blog.csdn.net/zhangphil/article/details/47036177
3、《Android View滚动、拉伸到顶/底部弹性回弹复位》链接地址:http://blog.csdn.net/zhangphil/article/details/47333845
4,《Android ListView拉到顶/底部,像橡皮筋一样弹性回弹复位》链接地址:http://blog.csdn.net/zhangphil/article/details/47311155

相关文章
|
2月前
|
IDE Android开发 iOS开发
深入解析Android与iOS的系统架构及开发环境差异
本文旨在探讨Android和iOS两大主流移动操作系统在系统架构、开发环境和用户体验方面的显著差异。通过对比分析,我们将揭示这两种系统在设计理念、技术实现以及市场策略上的不同路径,帮助开发者更好地理解其特点,从而做出更合适的开发决策。
126 2
|
22天前
|
开发工具 Android开发 iOS开发
深入解析安卓与iOS开发环境的优劣
【10月更文挑战第4天】 本文将深入探讨安卓和iOS两大主流移动操作系统的开发环境,从技术架构、开发工具、用户体验等方面进行详细比较。通过分析各自的优势和不足,帮助开发者更好地理解这两个平台的异同,从而为项目选择最合适的开发平台提供参考。
17 3
|
1天前
|
安全 5G Android开发
安卓与iOS的较量:技术深度解析
【10月更文挑战第24天】 在移动操作系统领域,安卓和iOS无疑是两大巨头。本文将深入探讨这两个系统的技术特点、优势和不足,以及它们在未来可能的发展方向。我们将通过对比分析,帮助读者更好地理解这两个系统的本质和内涵,从而引发对移动操作系统未来发展的深思。
5 0
|
28天前
|
安全 Android开发 iOS开发
深入解析:安卓与iOS的系统架构及其对应用开发的影响
本文旨在探讨安卓与iOS两大主流操作系统的架构差异,并分析这些差异如何影响应用开发的策略和实践。通过对比两者的设计哲学、安全机制、开发环境及性能优化等方面,本文揭示了各自的特点和优势,为开发者在选择平台和制定开发计划时提供参考依据。
37 4
|
30天前
|
测试技术 数据库 Android开发
深入解析Android架构组件——Jetpack的使用与实践
本文旨在探讨谷歌推出的Android架构组件——Jetpack,在现代Android开发中的应用。Jetpack作为一系列库和工具的集合,旨在帮助开发者更轻松地编写出健壮、可维护且性能优异的应用。通过详细解析各个组件如Lifecycle、ViewModel、LiveData等,我们将了解其原理和使用场景,并结合实例展示如何在实际项目中应用这些组件,提升开发效率和应用质量。
34 6
|
2月前
|
存储 开发框架 数据可视化
深入解析Android应用开发中的四大核心组件
本文将探讨Android开发中的四大核心组件——Activity、Service、BroadcastReceiver和ContentProvider。我们将深入了解每个组件的定义、作用、使用方法及它们之间的交互方式,以帮助开发者更好地理解和应用这些组件,提升Android应用开发的能力和效率。
134 5
|
24天前
|
安全 网络安全 Android开发
深度解析:利用Universal Links与Android App Links实现无缝网页至应用跳转的安全考量
【10月更文挑战第2天】在移动互联网时代,用户经常需要从网页无缝跳转到移动应用中。这种跳转不仅需要提供流畅的用户体验,还要确保安全性。本文将深入探讨如何利用Universal Links(仅限于iOS)和Android App Links技术实现这一目标,并分析其安全性。
134 0
|
19天前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
39 0
|
19天前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
29 0
|
19天前
|
存储 Java C++
Collection-PriorityQueue源码解析
Collection-PriorityQueue源码解析
33 0

推荐镜像

更多