最近工作不是很忙,就随便按照自己的意愿胡乱整了个小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 += childWidth+child.getPaddingRight(); }
for(int i=0;i<listAdapter.getCount();i++) { View child = listAdapter.getView(i, null, this); //注意在addview之前要对view进行measure操作 child = measureChild(child); addView(child); }
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 += 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."); } ........ }
//切记,此时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; } }
也许会有读者说把HListView方法中addView去掉直接改成如下的代码不也是解决的办法吗?改动的代码如下:
for(int i=0;i<listAdapter.getCount();i++) { //通过传递this,让解析infate的时候调用addView View child = listAdapter.getView(i, null, this); child = measureChild(child); }
为毛?其实看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的时候会逐步完善。此处是源代码下载链接,version2.0的实现点击此处
项目源码