簡單的橫向ListView實現(version 1.0),listviewversion
最近工作不是很忙,就隨便按照自己的意願胡亂整了個小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();}
當然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;}@Overrideprotected 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."); } ........ }
通過原始碼的追蹤可以發現,最終addView會調用addViewInner方法,而在此方法中如果chid.getParent()!=null,就會拋出異常。話說回來了,為什麼getParent()不會空呢?其實很簡單,因為在Adapter的getView(position,convertView,parentView)的第三個參數我們傳了this,在inflate方法中逐步追蹤會發現(相關可參考此博文:
//切記,此時root為null,attachToRoot在源碼中為root!=null,所以此處為falsepublic 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" >
@Overridepublic 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的時候會逐步完善。此處是原始碼下載連結
項目源碼