expand


[Android]界面的布局-ExpandableListView


http://ysl-paradise.blogspot.com/2011/05/listview-ii.html


1.可展开的ListView

在這一篇中,我們來看看如何自定义一个两层结构的ListView。如下图所示:

[Android]界面的布局-ExpandableListView
当点击People Names,就显示对应的子列表,如下图:

[Android]界面的布局-ExpandableListView
如何可以做到上面的效果呢?首先我们自定义一个MyExpandableListAdapter.它继承自BaseExpandableListAdpter.

    public class MyExpandableListAdapter extends BaseExpandableListAdapter {
        // Sample data set.  children[i] contains the children (String[]) for groups[i].
        private String[] groups = { "People Names", "Dog Names", "Cat Names", "Fish Names" };
        private String[][] children = {
                { "Arnold", "Barry", "Chuck", "David" },
                { "Ace", "Bandit", "Cha-Cha", "Deuce" },
                { "Fluffy", "Snuggles" },
                { "Goldy", "Bubbles" }
        };
      
        public Object getChild(int groupPosition, int childPosition) {
            return children[groupPosition][childPosition];
        }
      
        public long getChildId(int groupPosition, int childPosition) {
            return childPosition;
        }
      
        public int getChildrenCount(int groupPosition) {
            return children[groupPosition].length;
        }

        public TextView getGenericView() {
            // Layout parameters for the ExpandableListView
            AbsListView.LayoutParams lp = new AbsListView.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT, 64);

            TextView textView = new TextView(TestExpandableListActivity.this);
            textView.setLayoutParams(lp);
            // Center the text vertically
            textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
            // Set the text starting position
            textView.setPadding(60, 0, 0, 0); //第一层的textview,距离上下左右边界的距离。
            return textView;
        }
      
        public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
                View convertView, ViewGroup parent) {
            TextView textView = getGenericView();
            textView.setText(getChild(groupPosition, childPosition).toString());
            return textView;
        }
      
        public Object getGroup(int groupPosition) {
            return groups[groupPosition];
        }
      
        public int getGroupCount() {
            return groups.length;
        }
      
        public long getGroupId(int groupPosition) {
            return groupPosition;
        }

      
        public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
                ViewGroup parent) {
            TextView textView = getGenericView();
            textView.setText(getGroup(groupPosition).toString());
            return textView;
        }
      
        public boolean isChildSelectable(int groupPosition, int childPosition) {
            Toast.makeText(TestExpandableListActivity.this, getChild(groupPosition, childPosition).toString(), Toast.LENGTH_LONG).show();
            return true;
        }

        public boolean hasStableIds() {
            return true;
        }

    }

接着,新建一个工程,在主Activity里加入下面代码:
public class TestExpandableListActivity extends ExpandableListActivity  {
    ExpandableListAdapter mAdapter;
  
  
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.main);
        //set up our adapter
        mAdapter = new MyExpandableListAdapter();
        setListAdapter(mAdapter);
        registerForContextMenu(getExpandableListView());       
    }
}

上面的源代码在你本机c:\Program Files\Android\android-sdk\samples\android-10_1\ApiDemos\src\com\example\android\apis\view\ExpandableList1.java

里可以找到。

2. 自定义ExpandableListView的显示

上面的可展开的ListView黑乎乎的,且间隔很大。如何修改它的Layout呢?

2.1 修改父界面。效果如下图:

[Android]界面的布局-ExpandableListView

新建一个xml文件,用来显示父界面。xml内容如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent" android:orientation="horizontal"
    android:layout_height="wrap_content" android:background="#ccc"
    android:gravity="left|center_vertical" android:minHeight="40dip"
    android:paddingRight="20dip" android:paddingLeft="10dip">  
  
    <TextView android:id="@+id/txtGroupName"
            android:layout_height="wrap_content"
            android:layout_width="0dip"
            android:layout_weight="1"
            android:textSize="20dip"
            android:textColor="#000"  
            android:paddingLeft="3dip"      
            android:singleLine="true" />
  
</LinearLayout>

其实只有一个textview控件,用来显示A,B,C,D。。。左边的展开折叠按钮时系统提供的。现在就先不修改它了。

用这个xml文件,主要是想设置奇偶行的颜色,这样使用起来比较清楚。不然背景是黑乎乎一片,使用起来不是那么舒服。

源代码里只需要修改一个函数。如下:
public View getGroupView(int groupPosition, boolean isExpanded,
                View convertView, ViewGroup parent) {
            String strGroup = mCategoryList.get(groupPosition);  //自己取得要显示内容。mCategoryList其实就是一个List<String>里面存放了26个字母而已。
            //设置Layout
            LayoutInflater inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            LinearLayout ll = (LinearLayout)inflater.inflate(R.layout.artistlist_group, null);
            TextView tv = (TextView)ll.findViewById(R.id.txtGroupName);
            tv.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
            // 设置字符开始的位置,如果不够大的话,字符就和左边的展开/折叠图标重叠了。自己可以改改数字,看看效果就明白了。
            tv.setPadding(60, 0, 0, 0);
            // 设置显示的内容
            tv.setText(strGroup);
            // 设置奇偶行的背景交错
            ll.setBackgroundColor(groupPosition % 2 == 0 ? Color.argb(250, 255, 255, 255) : Color.argb(250, 229, 229, 229));
            return ll;
        }

2.2 修改展开后的子界面。效果如下图:

[Android]界面的布局-ExpandableListView

其实和上面未展开前的父界面差不多,是不是?

新建一个xml文件,用来显示子界面。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent" android:orientation="horizontal"
    android:layout_height="wrap_content" android:background="#fff"
    android:gravity="left|center_vertical" android:minHeight="40dip"
    android:paddingRight="20dip" android:paddingLeft="10dip">
  
  
    <TextView android:id="@+id/txtArtistName"
            android:layout_height="wrap_content"
            android:layout_width="0dip"
            android:layout_weight="1"
            android:textSize="16dip"
            android:textColor="#000"  
            android:paddingLeft="3dip"      
            android:singleLine="true" />  
          
    <ImageView android:id="@+id/imgArtist"
        android:src="@drawable/arrow_right_rest"
        android:paddingRight="5dip"
        android:layout_width="24dip"
        android:layout_height="24dip"      
        android:contentDescription="@string/artist" />
</LinearLayout>

从上面可以看出,textview用来显示文字,图片就是右边的那个箭头符号。

源代码如下:
public View getChildView(int groupPosition, int childPosition,
               boolean isLastChild, View convertView, ViewGroup parent) {
           //取得要展开的子条目的内容
           String strArtistName = mArtistList.get(groupPosition).get(childPosition); //mArtistList其实就是一个List<List<String>>
           //设置界面
           LayoutInflater inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
           LinearLayout ll = (LinearLayout)inflater.inflate(R.layout.artistlist_row, null);
           TextView tv = (TextView)ll.findViewById(R.id.txtArtistName);
           //设置内容
           tv.setText((childPosition + 1) + "." + strArtistName);
           //设置奇偶行。因为父界面也是奇偶行分割的,为了和父条目的颜色呼应,所以这里要判断一下父条目的颜色。
           //即父条目为灰色,那么第一个子条目应该是白色。如果两个都是灰色,就连成一片了。
           if(groupPosition % 2 == 0)
               ll.setBackgroundColor(childPosition % 2 == 0 ? Color.argb(250, 229, 229, 229) : Color.argb(250, 255, 255, 255));
           else
               ll.setBackgroundColor(childPosition % 2 == 0 ? Color.argb(250, 255, 255, 255) : Color.argb(250, 229, 229, 229));
           return ll;
       }

这样就完成了自定义展开前和展开后的效果了。

3. 点击某个父条目时,如何收起其他已经展开的条目?

当我们点击某个父条目时,如果这个条目本身是没有展开的,我们需要做两个动作:

1.折叠其他已经展开的条目(为什么?实际应用中需要。不需要的话可以忽略此步)

2. 展开这个条目

然而在实现过程中,总是出现异常。

03-15 17:18:13.090: ERROR/AndroidRuntime(16202): FATAL EXCEPTION: main

03-15 17:18:13.090: ERROR/AndroidRuntime(16202): java.lang.IndexOutOfBoundsException: Invalid index 1, size is 0

真是头晕啊。不知道哪里越界了呢?怎么会size为0呢?跟了好久都不知道为何。看了这篇博文,http://qtcstation.com/2011/03/working-with-the-expandablelistview-part-1/

终于明白了。我也是将折叠其他条目写在onGroupClicl里了.改写在onGroupExpand就没事了。唉唉。
private ExpandableListView.OnGroupExpandListener mGroupExpandListener = new ExpandableListView.OnGroupExpandListener() {
     
       public void onGroupExpand(int groupPosition) {
           int len = mAdapter.getGroupCount();
           for(int i=0; i<len; i++)
               if(i != groupPosition)
                   mExpandListView.collapseGroup(i);          
       }
   };
 
   private ExpandableListView.OnGroupClickListener mGroupClickListener = new ExpandableListView.OnGroupClickListener() {
     
       public boolean onGroupClick(ExpandableListView parent, View v,
               int groupPosition, long id) {
           //collapseAllExpanded();            就是在这里折叠条目出错了。千万不能写在这里。写到onGroupExpand里。
           startSearch(groupPosition); //自己写的动态从网上搜索要显示的子条目。改成自己的函数即可。
           return false;
       }
   };

4. 旋转时如何让展开的条目保持展开状态并显示它的子条目?

当我们由横屏转竖屏,或竖屏转横屏时,App会有个重新初始化的过程。这时候不做任何处理的话,展开的条目就会丢失它的子条目。这样的显示效果显然是不友好的。旋转后如何保持刚才的内容呢?

下面这篇文章非常有用。

http://stackoverflow.com/questions/4184556/save-and-restore-expanded-collapsed-state-of-an-expandablelistactivity

我们需要用一个数组来记住已经展开的group的id。在onSaveInstanceState函数中记住展开的id。在onRestoreInstanceState函数中恢复这些id,将它们展开。

还要在onStart中检查是否有展开的id,如果有的话,展开这些id。

在onStop中记得储存这些展开的id。
private long[] mExpandedIds; //save expanded group id

@Override
protected void onStart() {
    super.onStart();
    if(this.mExpandedIds != null)
        restoreExpandedState(mExpandedIds);
}
  
@Override
protected void onStop() {
    super.onStop();
    mExpandedIds = getExpandedIds();
}  

@Override
protected void onSaveInstanceState(Bundle outState)
{
    super.onSaveInstanceState(outState);
    this.mExpandedIds = getExpandedIds();
    outState.putLongArray("ExpandedIds", this.mExpandedIds);
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState)
{
    super.onRestoreInstanceState(savedInstanceState);
    long[] expandedIds = savedInstanceState.getLongArray("ExpandedIds");
    if(expandedIds != null)
        restoreExpandedState(expandedIds);
}
//取得所有展开的group的id
private long[] getExpandedIds() {
    if(mAdapter != null)
    {
        int length = mAdapter.getGroupCount();
        ArrayList<Long> expandedIds = new ArrayList<Long>();
        for(int i=0; i<length; i++)
        {
            //判断这个group是否展开。如果展开,就记下来
            if(mExpandListView.isGroupExpanded(i))
            {
                expandedIds.add(mAdapter.getGroupId(i));
            }
        }
        return toLongArray(expandedIds);
    }
    else
        return null;
}
//恢复到展开状态
private void restoreExpandedState(long[] expandedIds)
{
    this.mExpandedIds = expandedIds;
    if(mExpandedIds != null)
    {  
        if(mAdapter != null)
        {
            for(int i=0; i<mAdapter.getGroupCount(); i++)
            {
                long id=mAdapter.getGroupId(i);                  
                if(inArray(expandedIds, id))
                {
                    startSearch((int)id); //自己写的动态寻找对应的子条目的函数。改成自己的函数即可。
                    //如果这个group已经是展开的,那么折叠它,再展开。否则显示的子条目内容在旋转前后不一样。
                    if(!mExpandListView.expandGroup((int)id))
                    {  
                        mExpandListView.collapseGroup((int)id);
                        mExpandListView.expandGroup((int)id);
                    }
                }
            }
        }
    }
}

private static boolean inArray(long[] array, long element) {
    for (long l : array) {
        if (l == element) {
            return true;
        }
    }
    return false;
}

private static long[] toLongArray(List<Long> list)  {
    long[] ret = new long[list.size()];
    int i = 0;
    for (Long e : list)
        ret[i++] = e.longValue();
    return ret;
}




猜你喜欢

转载自myhearsnow.iteye.com/blog/1612947