Android AbsListView坐标体系解析
2018-01-04 00:00
183 查看
Android AbsListView坐标体系解析
Android的AbsListView与Android ListView不同,AbsListView代表了一个抽象的列表View。在实际的开发中直接使用Android ListView几乎可以完全完成所有与List这类View相关的开发任务,但在极个别情况下, 需要深入到Android的AbsListView中进行仔细的坐标定位。
为了探究Android的AbsListView,先写一个简单的ListView这样的代码:
特别的,把Android ListView需要加载到adapter中的item设置成高度为100pix的子view:
给这个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
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
相关文章推荐
- Android AbsListView坐标体系解析
- 打造Android集合控件数据绑定(支持添加监听,支持AbsListView与RecycleView,支持异步加载等)(二)ORM注解以及解析
- Android View体系(六)从源码解析Activity的构成
- ListView的一个典型crash cannot be cast to android.widget.AbsListView$LayoutParams
- android中listview卡顿的终极原因解析
- android中网络请求数据,解析并添加到Listview中
- Android AbsListView 的item动画类库 —— JazzyListView
- Android AbsListView 的item动画类库 —— JazzyListView
- 打造Android集合控件数据绑定(支持添加监听,支持AbsListView与RecycleView,支持异步加载等)(三)具体实现
- java.lang.ClassCastException: android.widget.RelativeLayout$LayoutParams cannot be cast to android.widget.AbsListView$LayoutPara
- Android ListView工作原理完全解析,带你从源码的角度彻底理解
- Android学习 (十八) 用GSON解析JSON数据并在ListView中显示
- android UI 优化之 AbsListView之深度优化
- AndroidListView工作理完全解析 带你从源码的角度彻底理解
- Android 对Layout_weight属性完全解析以及使用ListView来实现表格(自定义适配器)
- android结合异步任务,动态加载图片,Json解析数据展示在ListView,并且实现按日期分类展示,借口回调
- Android AbsListView Abs前缀
- Android解析XML(PULL)展示到ListView
- Android View体系(七)从源码解析View的measure流程
- Android 轻量级万能适配器,通吃所有的 AbsListView、RecyclerView