Android AbsListView坐标体系解析

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 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月前
|
Java 开发工具 Android开发
Android与iOS开发环境搭建全解析####
本文深入探讨了Android与iOS两大移动操作系统的开发环境搭建流程,旨在为初学者及有一定基础的开发者提供详尽指南。我们将从开发工具的选择、环境配置到第一个简单应用的创建,一步步引导读者步入移动应用开发的殿堂。无论你是Android Studio的新手还是Xcode的探索者,本文都将为你扫清开发道路上的障碍,助你快速上手并享受跨平台移动开发的乐趣。 ####
|
1月前
|
存储 Linux API
深入探索Android系统架构:从内核到应用层的全面解析
本文旨在为读者提供一份详尽的Android系统架构分析,从底层的Linux内核到顶层的应用程序框架。我们将探讨Android系统的模块化设计、各层之间的交互机制以及它们如何共同协作以支持丰富多样的应用生态。通过本篇文章,开发者和爱好者可以更深入理解Android平台的工作原理,从而优化开发流程和提升应用性能。
|
1月前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
2月前
|
开发框架 Dart Android开发
安卓与iOS的跨平台开发:Flutter框架深度解析
在移动应用开发的海洋中,Flutter作为一艘灵活的帆船,正引领着开发者们驶向跨平台开发的新纪元。本文将揭开Flutter神秘的面纱,从其架构到核心特性,再到实际应用案例,我们将一同探索这个由谷歌打造的开源UI工具包如何让安卓与iOS应用开发变得更加高效而统一。你将看到,借助Flutter,打造精美、高性能的应用不再是难题,而是变成了一场创造性的旅程。
|
2月前
|
安全 Java Linux
深入解析Android系统架构及其对开发者的意义####
【10月更文挑战第21天】 本文旨在为读者揭开Android操作系统架构的神秘面纱,探讨其如何塑造现代移动应用开发格局。通过剖析Linux内核、硬件抽象层、运行时环境及应用程序框架等关键组件,揭示Android平台的强大功能与灵活性。文章强调了理解Android架构对于开发者优化应用性能、提升用户体验的重要性,并展望了未来技术趋势下Android的发展方向。 ####
64 0
|
3月前
|
开发工具 Android开发 iOS开发
深入解析安卓与iOS开发环境的优劣
【10月更文挑战第4天】 本文将深入探讨安卓和iOS两大主流移动操作系统的开发环境,从技术架构、开发工具、用户体验等方面进行详细比较。通过分析各自的优势和不足,帮助开发者更好地理解这两个平台的异同,从而为项目选择最合适的开发平台提供参考。
42 3
|
2月前
|
安全 5G Android开发
安卓与iOS的较量:技术深度解析
【10月更文挑战第24天】 在移动操作系统领域,安卓和iOS无疑是两大巨头。本文将深入探讨这两个系统的技术特点、优势和不足,以及它们在未来可能的发展方向。我们将通过对比分析,帮助读者更好地理解这两个系统的本质和内涵,从而引发对移动操作系统未来发展的深思。
67 0
|
3天前
|
缓存 前端开发 Android开发
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
|
7天前
|
Dart 前端开发 Android开发
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
1月前
|
搜索推荐 前端开发 API
探索安卓开发中的自定义视图:打造个性化用户界面
在安卓应用开发的广阔天地中,自定义视图是一块神奇的画布,让开发者能够突破标准控件的限制,绘制出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战技巧,逐步揭示如何在安卓平台上创建和运用自定义视图来提升用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你的应用在众多同质化产品中脱颖而出。
70 19

热门文章

最新文章

推荐镜像

更多