Android AbsListView坐标体系解析

简介: 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

相关文章
|
18天前
|
XML Java Android开发
Android实现自定义进度条(源码+解析)
Android实现自定义进度条(源码+解析)
49 1
|
3月前
|
存储 算法 安全
AVB数据解析:Android verified boot 2.0 vbmeta 数据结构解析
AVB数据解析:Android verified boot 2.0 vbmeta 数据结构解析
124 0
|
1月前
|
编译器 开发工具 Android开发
Android 12 新特性深度解析
【2月更文挑战第15天】 随着移动操作系统的不断进化,Android 12带来了一系列创新功能与性能提升。本文将深入剖析Android 12的核心新特性,包括隐私仪表盘、通知管理、设备控制以及性能优化等方面,为开发者和用户提供全面的更新指南。
|
3月前
|
JSON Java Android开发
Android网络和数据交互: 请解释Android中的JSON解析库,如Gson。
Android网络和数据交互: 请解释Android中的JSON解析库,如Gson。
24 0
|
4月前
|
Android开发 容器
[Android]View的事件分发机制(源码解析)
[Android]View的事件分发机制(源码解析)
36 0

推荐镜像

更多