版本1.0的横向listView核心只是简单的用layout来进行横向的布局,并没有实现基本的滚动操作,整个屏幕智能显示固定的数目的Item,且Adapter中剩余的View虽然添加到了viewGroup中但是并由于没法滚动无法显示出来,这个版本的横向listView将简单的实现滚动的功能。再说滚动之前的时候需要准备的知识资料如下:
如上图,外层蓝色的矩形框为parentView,黑色的矩形框为childView.其中parentView的左上角是相对于child的坐标原点(0,0);在android里面子view在父view里面可以调用getLeft(),getRight(),getBottom()和getTop来确定子view在父View中的位置。其中getRight() == child.getWidth()+getLeft(); getBottom() == child.getTop() + child.getHeight();
这个版本version 2.0的目标也很简单,只是让listView滚动起来就算达成目标;滚动的操作也很简单,当手指按下的时候listView的Item向左移动一定的距离(在这个版本中是写死的值)。所以该版本滚动既没有用Scroller也没有用GestureDetector,只是简单的响应一下MotionEvent.ACTION_DOWN,然后对页面进行重绘就偶了,当然在version3.0将做较大改进,会让整个左右滚动都显示完全,在3.0之前还是一步步慢慢来(同样在最后会将源代码奉上,若是有什么不正确的地方欢迎批评指正)。
版本运行效果:手机点击屏幕的时候,整个listView向左移动显示其余的Item
关键思路:
1)动态加载childView:在屏幕宽度的范围内能显示多少个childView就添加多少个,adapter里面其余剩下的childView就随着滚动调用requestLayout的时候在onLayout里面动态的添加到viewGroup.这时有一个关键的问题就出来了,怎么判断手机屏幕宽度的范围中显示满了,没法显示多余的Item了呢?此时child.getRight()有了用武之地:每次取viewGroup中最后一个子childView也就是parentView.getChildAt(getChildCount()-1),调用childView.getRight(),如果childView.getRight()<parentView.getWidth()就继续addView添加下一个childView;并重复childView.getRight()<parentView.getWidth();如果不成立的话说明屏幕中显示满了。啰嗦了这么多不是很清晰,下面用代码来表示就是如下(该段代码在parentView的onLayout中调用):
//获取最右边的那个view,刚开始的时候是为null的 View rightChildView = getChildAt(getChildCount()-1); //获取此childView右边框距离parentView左边框的距离 int rightEdge = rightChildView!=null? rightChildView.getRight():0; //做一个循环 while(rightEdge <getWidth()&&index<listAdapter.getCount()) { View child = listAdapter.getView(index, null, null); child = measureChild(child); addView(child); rightEdge += child.getMeasuredWidth(); index++; }
每一层的while循环用画图直观分析就是如下(偷个懒,直接用手画的):
每次for循环过后,最右边的那个childView就是while变量中的rightChildView.在上图当添加到四个view的时候屏幕的宽度区域已经沾满了(此时rightChildView.getRight>parentView.getWidt(),所以此时添加到的子view的个数为4个,而不是version1.0版本中的全部的childview.
2) 滚动逻辑:该版本是向左移动的,所以移动的时候某个坐标点在滚动停止的时候是该坐标点是变小的,中间的差值或者说运动的距离是负值(假设此处用leftSrcollDistance =-500)。在这里简单的就定义为-500;关键点:滚动的时候,确切地说是向左边滚动的时候.rightChildView的getRight()是越来越小的,当getRight()+leftSrcollDistance <parentView.getWidth()的时候,说明屏幕的右边是由空余的空间的,此时就可以动态的添加并显示下一条Item了,具体的代码如下:
public class HListView extends ViewGroup{ /**存储数据用的Adapter**/ private ListAdapter listAdapter; //保存adapter中View的索引 private int index = 0; public HListView(Context context) { super(context); } public HListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public HListView(Context context, AttributeSet attrs) { super(context, attrs); } public ListAdapter getAdapter() { return listAdapter; } public void setAdapter(ListAdapter adapter) { this.listAdapter = adapter; } /** * 手指向左移动时滚动的距离,因为向左移动,所以移动的终点坐标和起点坐标 * 的差值为赋值,随着移动,最右边的view个的getRight会越来越小,当小于getWidht()的时候 * 就可以动态添加下一个了,这就是该横向listView的核心思想 */ private int leftSrcollDistance = -500; @Override public boolean onTouchEvent(MotionEvent event) { int eventAction = event.getAction(); switch (eventAction) { case MotionEvent.ACTION_DOWN://手指滑动的时候 requestLayout(); break; } //注意此处要返回true return true; } /** * 测量每个child的宽和高 * @param view * @return */ private View measureChild(View view) { LayoutParams params = view.getLayoutParams(); if(params==null) { params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); view.setLayoutParams(params); } view.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST)); return view; } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if(listAdapter==null) { return; } /*for(;index<listAdapter.getCount();index++) { View child = listAdapter.getView(index, null, null); child = measureChild(child); addView(child); }*/ //注意刚开始的时候是null View rightChildView = getChildAt(getChildCount()-1); //获取此childView右边框距离parentView左边框的距离 int rightEdge = rightChildView!=null? rightChildView.getRight():0; while(rightEdge+leftSrcollDistance<getWidth()&&index<listAdapter.getCount()) { View child = listAdapter.getView(index, null, null); child = measureChild(child); addView(child); rightEdge += child.getMeasuredWidth(); index++; } Log.e("HListView", "getChildCount=="+getChildCount()); //把childview通过layout布局到viewGroup中 int childLeft = 0; for(int i=0;i<getChildCount();i++) { View child = getChildAt(i); int childWidth = child.getMeasuredWidth(); child.layout(childLeft, 0, childWidth+childLeft, child.getMeasuredHeight()); //不过最好的写法是 childLeft += childWidth+child.getPaddingRight(); } } }
运行一把发现此时是不会运动的,为什么?很简单,因为之前这段代码没有把最左边的View在合适的时机从ViewGroup里面删除,导致虽然动态添加了view但是并没有多余的空间让新加的view显示出来。这个合适的时机就是当最左边的chilView的getRight()<=0的时候,因为是滚动确切地说是getRight()+leftSrcollDistance()<=0的时候,于是乎修改后的onLayout(该方法最好重构一下,添加,测量等弄到子程序里面最好):
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if(listAdapter==null) { return; } /*for(;index<listAdapter.getCount();index++) { View child = listAdapter.getView(index, null, null); child = measureChild(child); addView(child); }*/ //1.先删除最左边看不见的Item View firtVisiableView = getChildAt(0); if(firtVisiableView!=null&&leftSrcollDistance+firtVisiableView.getRight()<=0) { removeView(firtVisiableView); } //2.让屏幕尽可能的显示Item。注意刚开始的时候是没有 View rightChildView = getChildAt(getChildCount()-1); //获取此childView右边框距离parentView左边框的距离 int rightEdge = rightChildView!=null? rightChildView.getRight():0; while(rightEdge+leftSrcollDistance<getWidth()&&index<listAdapter.getCount()) { View child = listAdapter.getView(index, null, null); child = measureChild(child); addView(child); rightEdge += child.getMeasuredWidth(); index++; } //打印此时添加了多少个childView Log.e("HListView", "getChildCount=="+getChildCount()); //3.把步骤2添加的view通过Layout布局到parentView中 int childLeft = 0; for(int i=0;i<getChildCount();i++) { View child = getChildAt(i); int childWidth = child.getMeasuredWidth(); child.layout(childLeft, 0, childWidth+childLeft, child.getMeasuredHeight()); //不过最好的写法是 childLeft += childWidth+child.getPaddingRight(); } }
到此为止,这个仍然存在如开头所说问题的横向可滚动listView算是实现了:简单的总结一句话就是,总的核心就是计算坐标和requestLayout过程,难度不是很大。通过写这个小东西自己倒是又更多的掌握了写东西,希望对大家有帮助。在版本verson3.0的时候会对此版本进行全面的修改,未完待续吧!(此处为源代码)