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

最近工作不是很忙,就随便按照自己的意愿胡乱整了个小demo,简单的实现横向的ListView;   功能随着版本的增加而完善,包括左右滚动、itemClick等等listView的基本功能;

虽然说demo超级简单,但是真正的写起来倒是废了不小的功夫。废话少说,开始吧。

ListView肯定需要一个Adapter对象,这个小demo的核心原理就是循环遍历Adapter里面的子View,并调用子view的layout方法使其按计算的位置横向的排列在ViewGroup上面,就构成了一个横向的一排View;很简单吧,看到这如果觉得对你们没什么帮助的话,浏览器上本页面上右边的那个x,看到没,点击一下就ok了。

layout(int left,int top,int right,int bottom)方法是View的一个方法,该方法的四个参数决定了childView在parentView中的位置,其中(left,top)决定了childView在parentView中左上角的坐标,而(right,bottom)则决定了childView在parentView 右下角的位置,其图示如下:

其中right和bottom一般需要经过如下运算:right = left + view.getMeasureWidth(),bottom = top + view.getMeasureHeight();所以简单的实现横向的ListView还是很容易的;如下图:

如上图,假设item的宽度和高度为width和height,那么在不考虑padding的情况下就有如下的关系:

A.layout(0,0,width,height),

B..layout(width,0,2*width,height)

C.layout(2*width,0,3*width,heigth);

用代码体现出来就是:

	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 = child.getLeft();
			childLeft +=  childWidth+child.getPaddingRight();
		}

当然layout之前需要通过addView把Adapter.getView生成的View添加的viewGroup中去的,代码如下:

for(int i=0;i<listAdapter.getCount();i++) {
			View child = listAdapter.getView(i, null, this);
                //注意在addview之前要对view进行measure操作
                child = measureChild(child);
		    addView(child);
		}

所以简单的横向ListView的代码可以出炉了:

public class HListView extends ViewGroup{
	/**存储数据用的Adapter**/
	private ListAdapter listAdapter;

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

	/**
	 * 测量每个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;
		}
         //把listView中的子view添加到viewGroup中
		for(int i=0;i<listAdapter.getCount();i++) {
			View child = listAdapter.getView(i, null, this);
			child = measureChild(child);
		    addView(child);
		}

		//设置子view在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 = child.getLeft();
			//childLeft += childWidth;
			childLeft +=  childWidth+child.getPaddingRight();
		}
	}
}

运行一把,看看效果吧

我屮艸芔茻,报错了;错误代码定位到addView上面,追踪源代码addView的方法如下:

public void addView(View child) {
        addView(child, -1);
    }

 public void addView(View child, int index) {
        ......
        addView(child, index, params);
    }

	 public void addView(View child, int index, LayoutParams params) {
         ....
        requestLayout();
        invalidate(true);
        addViewInner(child, index, params, false);
    }

	private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {

         .......
        if (child.getParent() != null) {
            throw new IllegalStateException("The specified child already has a parent. " +
                    "You must call removeView() on the child's parent first.");
        }

        ........
    }

通过源代码的追踪可以发现,最终addView会调用addViewInner方法,而在此方法中如果chid.getParent()!=null,就会抛出异常。话说回来了,为什么getParent()不会空呢?其实很简单,因为在Adapter的getView(position,convertView,parentView)的第三个参数我们传了this,在inflate方法中逐步追踪会发现(相关可参考此博文:

//切记,此时root为null,attachToRoot在源码中为root!=null,所以此处为false
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            ......
            View result = root;  //根View ,为null
            try {
                 ....
                final String name = parser.getName();  //节点名,如果是自定义View的话 就是全限定名
               //该if语句暂不用看
               if (TAG_MERGE.equals(name)) { // 处理<merge />标签
                   ...
                } else {
                    // Temp is the root view that was found in the xml
                    //这个是xml文件对应的那个根View,在item.xml文件中就是RelativeLayout
                    View temp = createViewFromTag(name, attrs);   

                    ViewGroup.LayoutParams params = null;
                    //因为root==null,if条件不成立
                    if (root != null) {
                        // Create layout params that match root, if supplied
                        //根据AttributeSet属性获得一个LayoutParams实例,记住调用者为root。
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) { //重新设置temp的LayoutParams
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }
                    // Inflate all children under temp
                    //遍历temp下所有的子节点,也就是xml文件跟文件中的所有字View
                    rInflate(parser, temp, attrs);  

                    //把xml文件的根节点以及根节点中的子节点的view都添加到root中,当然此时root等于null的情况下此处是不执行的
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
                    //如果根节点为null,就直接返回xml中根节点以及根节点的子节点组成的View
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }
            }
            ...
            return result;
        }
    }

如果(root!=null)会调用root.addView(temp,params)而这个root正是我们传的this,所以在inflate解析xml文件的时候会把convertView 通过调用addView而添加到parentview中,所以getParent()不为null.那这样改起来就简单了,只要把getView(position,convetView,parentView)把parentView传一个null就可以了。

也许会有读者说把HListView方法中addView去掉直接改成如下的代码不也是解决的办法吗?改动的代码如下:

for(int i=0;i<listAdapter.getCount();i++) {
      //通过传递this,让解析infate的时候调用addView
      View child = listAdapter.getView(i, null, this);
      child = measureChild(child);
}

运行一把试试看,结果是手机屏幕上什么都没有,只有HlistView显示的backgroud设置的颜色:

为毛?其实看infate方法的话,你就可以发现如果parentView传的值不为null的话,getView方法返回的这个View就是parentView,通过打印listAdapter.getView(i,null,this) == this,可以看到返回的是true;所以我们本来要的是childView,你返回的是parentView是几个意思,问题查到了,解决这个问题很简单,就是在getView返回的时候,把return convertView,改成
return convertView.findViewById(R.id.item);item为xml配置文件的根节点的id

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/item"
    android:layout_width="100dp"
    android:layout_height="200dp"
     >
@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		HolderView holderView = null;
		if(convertView == null ){
			holderView = new HolderView();
			convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item, parent);

			holderView.imageView =(ImageView) convertView.findViewById(R.id.imageView);
			holderView.textView = (TextView) convertView.findViewById(R.id.textView);

			convertView.setTag(holderView);
		}else{
			holderView = (HolderView) convertView.getTag();
		}

		holderView.imageView.setImageResource((Integer) mList.get(position).get("img"));
		holderView.textView.setText((String) mList.get(position).get("index"));

		return convertView.findViewById(R.id.item);

	}

最终的运行效果如下:

简单横向listVIew version1.0版就出来了;不过现在试试最最简单功能,看看页面效果都还有些图片没有显示完全,也无法滚动,点击也没有任何响应。至于功能的完善,在version2.0的时候会逐步完善。此处是源代码下载链接源码

时间: 2024-08-29 22:32:42

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

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

版本2只是简单的实现了当手指按下的时候listView的Item向左移动一定的距离,并没有随着手指的左右移动而左右滚动,在这个版本3.0中将会实现随着手指的移动而滚动的目标:当手指向左移动的时候,listView向左滚动:当手指向右移动的时候,listView向右滚动:在开始进入正题之前,先说说预备的知识和涉及到的方法. 在version2.0之前添加View的时候用的都是addView最终辗转调用了addViewInner方法,经过查询viewGroup的源码发现有一个addViewInLay

简单的横向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... 然后我们再创建