1. 程式人生 > >Android自己定義組件系列【5】——進階實踐(2)

Android自己定義組件系列【5】——進階實踐(2)

col fonts tle 適配 pack tom ica void log

上一篇《Android自己定義組件系列【5】——進階實踐(1)》中對任老師的《可下拉的PinnedHeaderExpandableListView的實現》前一部分進行了實現,這一篇我們來看看ExpandableListView的使用並實現剩下的部分。

原文出處:http://blog.csdn.net/singwhatiwanna/article/details/25546871

一、ExpandableListView的使用方法

ExpandableListView是ListView的子類,它在普通ListView的基礎上進行了擴展,適配器為ExpandableListAdapter。

技術分享

與Adapter相似的是。實現ExpandableListAdapter也有例如以下方式:

1、擴展BaseExpandableListAdapter實現ExpandableListAdapter

2、使用SimpleExpandableListAdapter將兩個List集合包裝成ExpandableListAdapter

3、使用SimpleCursorTreeAdapter將Cursor中的數據包裝成SimpleCursorTreeAdapter

接下來用第一種方式來做個小樣例。來看看ExpandableListView的使用

		ExpandableListAdapter adapter = new BaseExpandableListAdapter() {
			
			@Override
			public boolean isChildSelectable(int arg0, int arg1) {
				// TODO Auto-generated method stub
				return false;
			}
			
			@Override
			public boolean hasStableIds() {
				// TODO Auto-generated method stub
				return false;
			}
			
			@Override
			public View getGroupView(int arg0, boolean arg1, View arg2, ViewGroup arg3) {
				// TODO Auto-generated method stub
				return null;
			}
			
			@Override
			public long getGroupId(int arg0) {
				// TODO Auto-generated method stub
				return 0;
			}
			
			@Override
			public int getGroupCount() {
				// TODO Auto-generated method stub
				return 0;
			}
			
			@Override
			public Object getGroup(int arg0) {
				// TODO Auto-generated method stub
				return null;
			}
			
			@Override
			public int getChildrenCount(int arg0) {
				// TODO Auto-generated method stub
				return 0;
			}
			
			@Override
			public View getChildView(int arg0, int arg1, boolean arg2, View arg3,
					ViewGroup arg4) {
				// TODO Auto-generated method stub
				return null;
			}
			
			@Override
			public long getChildId(int arg0, int arg1) {
				// TODO Auto-generated method stub
				return 0;
			}
			
			@Override
			public Object getChild(int arg0, int arg1) {
				// TODO Auto-generated method stub
				return null;
			}
		};
能夠看到BaseExpandableListApdater中的方法非常多,主要方法介紹例如以下:

getGroupCount():返回組列表數量

getGroupView():返回的View作為組列表項

getChildrenCount():返回子列表項的數量

getChildView():返回的View作為特定組、特定位置的子列表項

package com.example.expandablelistviewtest;

import android.app.Activity;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		ExpandableListAdapter adapter = new BaseExpandableListAdapter() {
			
			int[] logos = new int[] {
				R.drawable.ic_launcher,
				R.drawable.ic_launcher,
				R.drawable.ic_launcher
			};
			
			private String[] groupTypes = new String[]{
				"計算機語言", "人類語言", "動物語言"	
			};
			
			private String[][] childTypes = new String[][] {
					{"Java", "C++", "C", "PHP"},
					{"漢語", "英語", "日語", "法語"},
					{"咕咕", "汪汪", "喵喵"}
			};
			
			// 獲取指定組位置、指定子列表項處的子列表項數據
			@Override
			public Object getChild(int groupPosition, int childPosition) {
				return childTypes[groupPosition][childPosition];
			}

			@Override
			public long getChildId(int groupPosition, int childPosition) {
				return childPosition;
			}

			@Override
			public int getChildrenCount(int groupPosition) {
				return childTypes[groupPosition].length;
			}

			private TextView getTextView() {
				AbsListView.LayoutParams lp = new AbsListView.LayoutParams(
						ViewGroup.LayoutParams.MATCH_PARENT, 64);
				TextView textView = new TextView(MainActivity.this);
				textView.setLayoutParams(lp);
				textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
				textView.setPadding(36, 0, 0, 0);
				textView.setTextSize(20);
				return textView;
			}

			// 該方法決定每一個子選項的外觀
			@Override
			public View getChildView(int groupPosition, int childPosition,
					boolean isLastChild, View convertView, ViewGroup parent) {
				TextView textView = getTextView();
				textView.setText(getChild(groupPosition, childPosition)
						.toString());
				return textView;
			}

			// 獲取指定組位置處的組數據
			@Override
			public Object getGroup(int groupPosition) {
				return groupTypes[groupPosition];
			}

			@Override
			public int getGroupCount() {
				return groupTypes.length;
			}

			@Override
			public long getGroupId(int groupPosition) {
				return groupPosition;
			}

			// 該方法決定每一個組選項的外觀
			@Override
			public View getGroupView(int groupPosition, boolean isExpanded,
					View convertView, ViewGroup parent) {
				LinearLayout ll = new LinearLayout(MainActivity.this);
				ll.setOrientation(0);
				ImageView logo = new ImageView(MainActivity.this);
				logo.setImageResource(logos[groupPosition]);
				ll.addView(logo);
				TextView textView = getTextView();
				textView.setText(getGroup(groupPosition).toString());
				ll.addView(textView);
				return ll;
			}

			@Override
			public boolean isChildSelectable(int groupPosition,
					int childPosition) {
				return true;
			}

			@Override
			public boolean hasStableIds() {
				return true;
			}
		};
		ExpandableListView expandListView = (ExpandableListView) findViewById(R.id.list);
		expandListView.setAdapter(adapter);
	}

}
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >
	<ExpandableListView 
	    android:id="@+id/list"
	    android:layout_width="match_parent"
	    android:layout_height="wrap_content"/>

</RelativeLayout>

技術分享

二、代碼分析

首先看onCreate方法:

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        expandableListView = (PinnedHeaderExpandableListView) findViewById(R.id.expandablelist);
        stickyLayout = (StickyLayout)findViewById(R.id.sticky_layout);
        initData();

        adapter = new MyexpandableListAdapter(this);
        expandableListView.setAdapter(adapter);

        // 展開全部group
        for (int i = 0, count = expandableListView.getCount(); i < count; i++) {
            expandableListView.expandGroup(i);
        }

        expandableListView.setOnHeaderUpdateListener(this);
        expandableListView.setOnChildClickListener(this);
        expandableListView.setOnGroupClickListener(this);
        stickyLayout.setOnGiveUpTouchEventListener(this);

    }
前面幾行非常easy。和上面的樣例差點兒一樣。我們僅僅須要再關註一下initData()方法和以下的幾行監聽函數。

initData()中是模擬的數據。例如以下:

    void initData() {
        groupList = new ArrayList<Group>();
        Group group = null;
        for (int i = 0; i < 3; i++) {
            group = new Group();
            group.setTitle("group-" + i);
            groupList.add(group);
        }

        childList = new ArrayList<List<People>>();
        for (int i = 0; i < groupList.size(); i++) {
            ArrayList<People> childTemp;
            if (i == 0) {
                childTemp = new ArrayList<People>();
                for (int j = 0; j < 13; j++) {
                    People people = new People();
                    people.setName("yy-" + j);
                    people.setAge(30);
                    people.setAddress("sh-" + j);

                    childTemp.add(people);
                }
            } else if (i == 1) {
                childTemp = new ArrayList<People>();
                for (int j = 0; j < 8; j++) {
                    People people = new People();
                    people.setName("ff-" + j);
                    people.setAge(40);
                    people.setAddress("sh-" + j);

                    childTemp.add(people);
                }
            } else {
                childTemp = new ArrayList<People>();
                for (int j = 0; j < 23; j++) {
                    People people = new People();
                    people.setName("hh-" + j);
                    people.setAge(20);
                    people.setAddress("sh-" + j);

                    childTemp.add(people);
                }
            }
            childList.add(childTemp);
        }

    }
接下來我們看看監聽這幾個監聽函數

public class MainActivity extends Activity implements
        ExpandableListView.OnChildClickListener,
        ExpandableListView.OnGroupClickListener,
        OnHeaderUpdateListener, OnGiveUpTouchEventListener {
從Activity的繼承關系上看。OnChildClickListener和OnGroupClickListener是ExpandableListView類的監聽函數。

    @Override
    public boolean onGroupClick(final ExpandableListView parent, final View v,
            int groupPosition, final long id) {

        return false;
    }

    @Override
    public boolean onChildClick(ExpandableListView parent, View v,
            int groupPosition, int childPosition, long id) {
        Toast.makeText(MainActivity.this,
                childList.get(groupPosition).get(childPosition).getName(), 1)
                .show();

        return false;
    }
再來看看OnHeaderUpdateListener,這個監聽函數實際上是在重寫(自己定義)的ExpandableListView中自己定義的監聽。

    public void setOnHeaderUpdateListener(OnHeaderUpdateListener listener) {
        mHeaderUpdateListener = listener;
        if (listener == null) {
            return;
        }
        mHeaderView = listener.getPinnedHeader();
        int firstVisiblePos = getFirstVisiblePosition();
        int firstVisibleGroupPos = getPackedPositionGroup(getExpandableListPosition(firstVisiblePos));
        listener.updatePinnedHeader(firstVisibleGroupPos);
        requestLayout();
        postInvalidate();
    }

getPinnedHeader()方法創建(得到)一個列表頭。然後通過updatePinnedHeader方法設置當前可見的子列表元素的組名稱。

requestLayout()方法的作用是當view確定自身已經不再適合現有的區域時,該view本身調用這種方法要求parent view又一次調用他的onMeasure onLayout來對又一次設置自己位置。特別的當view的layoutparameter發生改變,而且它的值還沒能應用到view上,這時候適合調用這種方法。

postInvalidate()方法的作用是實現界面刷新。

   public interface OnHeaderUpdateListener {
        /**
         * 採用單例模式返回同一個view對象就可以
         * 註意:view必須要有LayoutParams
         */
        public View getPinnedHeader();

        public void updatePinnedHeader(int firstVisibleGroupPos);
    }
從OnHeaderUpdateListener監聽函數的定義上看。當觸發監聽後會調用兩個方法的實現例如以下:

    @Override
    public View getPinnedHeader() {
        if (mHeaderView == null) {
            mHeaderView = (ViewGroup) getLayoutInflater().inflate(R.layout.group, null);
            mHeaderView.setLayoutParams(new LayoutParams(
                    LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
        }
        return mHeaderView;
    }

    @Override
    public void updatePinnedHeader(int firstVisibleGroupPos) {
        Group firstVisibleGroup = (Group) adapter.getGroup(firstVisibleGroupPos);
        TextView textView = (TextView) getPinnedHeader().findViewById(R.id.group);
        textView.setText(firstVisibleGroup.getTitle());
    }
接下來我們須要知道什麽情況下回觸發這個監聽函數。

   protected void refreshHeader() {
        if (mHeaderView == null) {
            return;
        }
        int firstVisiblePos = getFirstVisiblePosition();
        int pos = firstVisiblePos + 1;
        int firstVisibleGroupPos = getPackedPositionGroup(getExpandableListPosition(firstVisiblePos));
        int group = getPackedPositionGroup(getExpandableListPosition(pos));

        if (group == firstVisibleGroupPos + 1) {
            View view = getChildAt(1);
            if (view.getTop() <= mHeaderHeight) {
                int delta = mHeaderHeight - view.getTop();
                mHeaderView.layout(0, -delta, mHeaderWidth, mHeaderHeight - delta);
            }
        } else {
            mHeaderView.layout(0, 0, mHeaderWidth, mHeaderHeight);
        }

        if (mHeaderUpdateListener != null) {
            mHeaderUpdateListener.updatePinnedHeader(firstVisibleGroupPos);
        }
    }
能夠看到再調用refreshHeader()方法的時候會觸發updatePinnerHeader方法。

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
            int visibleItemCount, int totalItemCount) {
        if (totalItemCount > 0) {
            refreshHeader();
        }
        if (mScrollListener != null) {
            mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
        }
    }
呵呵,這下最終恍然大悟了。在onScroll方法中調用了refreshHeader,這就是說在滑動屏幕的時候OnHeaderUpdateListener監聽會觸發。會不斷的推斷是否應該改變列名稱。

快淩晨1點鐘了,今天就分析到這吧。明天繼續。


再次聲明一下,本文是為了學習Android自己定義組件,對任老師博客《可下拉的PinnedHeaderExpandableListView的實現》進行具體解讀。假設有問題希望指出。

原文出處:http://blog.csdn.net/singwhatiwanna/article/details/25546871













Android自己定義組件系列【5】——進階實踐(2)