简单的横向ListView实现(version 3.0)

版本2只是简单的实现了当手指按下的时候listView的Item向左移动一定的距离,并没有随着手指的左右移动而左右滚动,在这个版本3.0中将会实现随着手指的移动而滚动的目标:当手指向左移动的时候,listView向左滚动;当手指向右移动的时候,listView向右滚动;在开始进入正题之前,先说说预备的知识和涉及到的方法。

在version2.0之前添加View的时候用的都是addView最终辗转调用了addViewInner方法,经过查询viewGroup的源码发现有一个addViewInLayout方法,api的说明如下:Adds a view during layout,this is usefule if in your onLayout method.而我们这个横向listView正好是在onLayout里面添加的View,所以从现在开始添加View的时候就改为addViewInLayout了。ViewGrouup里面提供一个View的数组mChildren,在addView方法中调用了addVew(view
view,index)index默认传的是-1,表明添加到View数组mChildren最后面。而addViewInLaout的方法的第二个参数就是这个index,如果index为负数的话就把该View方法数组的最后面(实际代码处理中就是数组的插入操作,很简单的源码),当index为正数的时候就把该View插入到数组中的index的位置,相应的View数组mChildren里面的元素后移,当然对应的删除View的时候也有removeViewInLayout方法;其实getChildAt(int index)就是从数组mChildrenCount获取返回对应索引的View,所以左右滑动的核心思想就出来了:

1)当向左滚动的时候,把右边即将要滚动出来的View添加到mChildren数组的最后面,在页面上的显示为在屏幕的右边的View,同时提供一个rightIndex来表示adapter.getView的position。即addViewInlayout(view,-1,view.getLayoutParams,true),

2)当向右滚动的时候,把左边即将滚动出来的View添加的mChildren数组的最前面数组0的位置,在页面上的显示为在屏幕左边的View,同事提供一个left来表示adapter.getView 的position参数。即addViewInlayout(view,0,view.getLayoutParams,true),用代码体现如下

private void addRightChildViews(int dianceX) {
        // 2.让屏幕尽可能的显示Item。注意刚开始的时候是没有
        View rightChildView = getChildAt(getChildCount() - 1);
        // 获取此childView右边框距离parentView左边框的距离
        int rightEdge = rightChildView != null ? rightChildView.getRight() : 0;
        while (rightEdge + dianceX < getWidth()
                && rightIndex < listAdapter.getCount()) {
            View child = listAdapter.getView(rightIndex, null, null);
            child = measureChild(child);
            addViewInLayout(child, -1, child.getLayoutParams(), true);
            rightEdge += child.getMeasuredWidth();
            rightIndex++;
        }
    }

    private void addLeftChildViews(int dianceX) {
        View leftChildView = getChildAt(0);
        int leftEdge = leftChildView != null ? leftChildView.getLeft() : 0;
        while (leftEdge + dianceX > 0 && leftIndex >= 0) {

            View child = listAdapter.getView(leftIndex, null, null);
            child = measureChild(child);
            addViewInLayout(child, 0, child.getLayoutParams(), true);
            leftEdge -= child.getMeasuredWidth();
            leftIndex--;
            //此处省略了一个重要的代码
        }
    }

该篇博客就是围绕着这两个核心思路进行拓展和修改进而实现左右滚动的。在版本2.0中向左移动的时候需要在合适的时机移除左边符合某些条件的View(具体见简单的横向ListView实现(version 2.0) 同样,在想右滚动的时候也需要把超出右边屏幕的View从Viewgroup里面删除出去(当child.getLeft()+移动偏移量大于parent.getWidth()的时候进行删除):简而言之就是向左滚动时,需要删除滚动过后左边的看不见View;当向右滚动时,需要删除滚动过后右边的看不见的View。用代码表示如下:

    
/* 删除看不见的view
     */
    private void removeAnvisiableViews(int dianceX) {
        // 移除左边看不到的view
        View firtVisiableView = getChildAt(0);
        if (firtVisiableView != null
                && dianceX + firtVisiableView.getRight() <= 0) {
            removeViewInLayout(firtVisiableView);
            leftIndex++;
            //此处省略了一行重要的代码
        }

        // 移除右边看不到的view
        View lastVisialbeView = getChildAt(getChildCount() - 1);
        if (lastVisialbeView != null
                && lastVisialbeView.getLeft() + dianceX >= getWidth()) {
            removeViewInLayout(lastVisialbeView);
            rightIndex--;
        }

        
    }

到此为止在考虑左右滚动的条件下什么时候添加左边的View、什么时候添加右边的View以及在什么时候删除左右两边看不见的View的思路以及代码都实现了。之前的版本的滚动值都是写死的,现在这个版本将用GestureDetector来处理手指的移动,至于GestureDetector的用法在这里不再赘述,网上好多资料可以查阅在.SimpleOnGestureListener()里面有一个 onScroll(MotionEvent e1, MotionEvent e2,  float distanceX, float
distanceY)方法,因为是水平滚动,所以第三个参数distanceX使我们所需要的,该参数指的是距离上次调用onScroll方法的时候x轴滚动的距离,所以在代码中我用变量distanceX来记录下了:

	public boolean onScroll(MotionEvent e1, MotionEvent e2,
				float distanceX, float distanceY) {
                         //记录滚动的距离
                         HListView.this.distanceX= (int) distanceX;
			// 仍然需要重绘
			requestLayout();
			return true;

		};

这里有个需要注意的小地方,手指向左的时候distanceX>0,而手指向右的时候distanceX<0.所以在用的时候我们要取反,所以在onLayout里面这么调用(较版本2.0之前对这个方法进行了重构处理):

@Override
	protected void onLayout(boolean changed, int left, int top, int right,
			int bottom) {

		if (listAdapter == null) {
			return;
		}
		removeAnvisiableViews(-distanceX);
		addRightChildViews(-distanceX);
		addLeftChildViews(-distanceX);
		layoutChildViews(-distanceX);

	}

到此为止基本上该说的都说了,运行一把试试,我屮艸芔茻,发现动不了,怎么回事?各种抓耳挠腮。最终吃饭的时候灵光一闪知道问题出现在哪儿了!我们知道横向listView滚动的逻辑就是重复addView和removeView的操作,但是最重要的一点就是最终都要调用layoutChildViews方法进行处理,而在version2.0版本的代码中layoutChildViews代码是这么样的:

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();
		}
		

layout方法的第一个三处我们总是让它从0开始!!!!问题就出在这儿!!!简单的想象一下,当向右滚动的时候左边会出现半个Item的情况(或者不完整的Item),怎么可能是从0开始呢,肯定会出现childLeft<0的情况。所以当处理左边第一个可以看见的子View的时候,layout第一个参数是随着滚动而发生变化的,并不是固定为0的死的值!其实上面的代码中我有写到:此处省略了一行重要的代码,其实这行代码就是解决这个问题的!

我在类里面定义了个leftOffset=0的变量来进行控制表示下一个即将添加的子View的起始位置(注意要加上distanceX),(这个变量的作用想了半天不好用语言描述,郁闷).当调用addleftChildView方法的时候每添加一个View时对该变量进行leftOffset -= child.getMeasuredWidth();。相应的当删除左边的一个View的时候leftOffset += child.getMeasuredWidth();所以layoutchildView的代码修改为:

private void layoutChildViews(int distanceX) {
		if(getChildCount()==0) {
			return;
		}
		leftOffset += distanceX;
		int childLeft = leftOffset;
		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();
		}

	}

运行一把,OK,爽歪歪!version3.0到此位置完成了!不过功能仍然不够完善:比如当左边已经是第一个Item的时候仍然可以想右边滚动,同理当右边是最后一个View的时候仍然可以向左滚动、同样的当手指离开的时候没有惯性滚动,比如每次getView的时候都需要对xml文件进行解析,没有很好的重用已经解析过的xml等,这些丰富的功能将在下一个版本中实现。(此处为源代码

时间: 2024-11-04 22:02:49

简单的横向ListView实现(version 3.0)的相关文章

简单的横向ListView实现(version 1.0)

最近工作不是很忙,就随便按照自己的意愿胡乱整了个小demo,简单的实现横向的ListView:   功能随着版本的增加而完善,包括左右滚动.itemClick等等listView的基本功能: 虽然说demo超级简单,但是真正的写起来倒是废了不小的功夫.废话少说,开始吧. ListView肯定需要一个Adapter对象,这个小demo的核心原理就是循环遍历Adapter里面的子View,并调用子view的layout方法使其按计算的位置横向的排列在ViewGroup上面,就构成了一个横向的一排Vi

简单的横向ListView实现(version 2.0)

版本1.0的横向listView核心只是简单的用layout来进行横向的布局,并没有实现基本的滚动操作,整个屏幕智能显示固定的数目的Item,且Adapter中剩余的View虽然添加到了viewGroup中但是并由于没法滚动无法显示出来,这个版本的横向listView将简单的实现滚动的功能.再说滚动之前的时候需要准备的知识资料如下: 如上图,外层蓝色的矩形框为parentView,黑色的矩形框为childView.其中parentView的左上角是相对于child的坐标原点(0,0);在andr

简单的横向ListView实现(version 4.0)

这个版本的博客写起来颇费口舌,有些代码自己语言组织能力有限,感觉描述起来很费劲,前前后后改了五六遍稿子还是不尽人意 ,不过我还是坚持写出来自己当初的思路,如果看得不明白的地方我在文章最后仍然会上传源代码,可以直接运行看效果,看过运行的效果后对文中有些别扭的语言估计会能直观的了解.在版本3.0的虽然实现了随着手指的左右移动listView中的item也随着滚动,但是会出现如下的情况: 当左边已经是第一个的时候,会出现如下的情况(仍然可以向右移动): 当右边是adapter最后一个item的时候,会

一个简单的loading动画,version 1.0

一个简单的loading动画:如图 点我查看

Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)

Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载) POSTED ON 2014年6月27日 BY 天边的星星 本文内容: 1.横向ListView的所有实现思路; 2.其中一个最通用的思路HorizontalListView,并基于横向ListView开发一个简单的相册: 3.实现的横向ListView在点击.浏览时item背景会变色,并解决了listview里setSelected造成item的选择状态混乱的问题.

Android-搭建简单服务端+ListView异步加载数据

Android-搭建简单服务端+ListView异步加载数据 2014年5月6日 本篇博文带给大家的是教大家如何在MyEclipse中搭建一个服务端,并通过手机端与其通信,异步加载数据. 笔者使用的是MyEclipse,各位也可以直接用Eclipse创建Java Web项目,谷歌提供的ADT Bundle是不能创建Web项目,读者可以下载Eclipse For JaveEE Developer这个IDE. 下面来介绍如何在MyEclipse创建一个Web项目,并部署到Tomcat当中,关于Tom

项目总结--Version 1.0(三)

可扩展性决定了项目能走多远,可复用行决定了项目走的是否轻快. 本文主要讨论1.0版本的项目在进行设计时对可复用性和可扩展性的思考,涉及了整个项目分层的所有层(想查阅分层相关部分的可以点这:项目总结--Version 1.0(一)和项目总结--Version 1.0(二)). 由于经验有限,做过多的扩展容易误导其他人,所以本文所做的一些讨论总结只限在本项目范围内进行,对这个项目在进行架构设计的时候遇到的问题和自己的一些想法在此做一些梳理和总结. 同样的,先从最底层-数据持久层开始说起.先看一下数据

PopupWindow弹出产品属性+横向ListView HorizontialList实现产品属性

先来张效果图吧~ 先说下思路吧: 这是个商品详情页,然后商品页面里面使用layoutInflater获取出要弹出框框的view,当然了,这里面参数的加载数据也就写在这个popwindow里面啦. 开始贴代码了 商品弹出框布局:(下面的购物车和购买偷懒直接设定了宽度) activity_product_attribute.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:an

必须添加对程序集“EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089”的引用。

我在使用mvc时候.遇到的一个问题...  必须添加对程序集"EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"的引用. 下面是解决方法 关于我们在使用ASP.NET  MVC架构时候...在Model中创建了实体框架EF...如图 我们需要在DAL中使用Entity来操作数据库,执行一些查询等crud操作的时候... 我们在dal中获的entity... 然后我们再创建