首页 > 代码库 > 仿Expandablelistview效果的ListView(加入了子列表渐入渐出的动画)

仿Expandablelistview效果的ListView(加入了子列表渐入渐出的动画)

  新来的项目要求第一眼一看就是用Expandablelistview。效果图如下:

           

  其实本来希望直接使用Expandablelistview的,但是需求Expandablelistview在展开一个group时有个动画效果——该group的child一个一个滑动出来并且把下面的group“挤”下去。本以为这个Expandablelistview组件肯定有相关方法的,但竟然没有!网上居然也查不到(有很多人问同样的问题,答案却都是:继承Expandablelistview然后自定义这个动画,然后没了。究竟怎样自定义动画啊有没有搞错!)只好找了下Expandablelistview的方法,有个expandGroup()方法:

    /**
     * Expand a group in the grouped list view
     *
     * @param groupPos the group to be expanded
     * @return True if the group was expanded, false otherwise (if the group
     *         was already expanded, this will return false)
     */
    public boolean expandGroup(int groupPos) {
       return expandGroup(groupPos, false);
    }

  看到它其实是执行了expandGroup(groupPos, false)方法,鼠标挪到方法上一看

  心中一阵狂喜,第二个参数不是是否使用动画么?!赶紧点进去看,结果……

    /**
     * Expand a group in the grouped list view
     *
     * @param groupPos the group to be expanded
     * @param animate true if the expanding group should be animated in
     * @return True if the group was expanded, false otherwise (if the group
     *         was already expanded, this will return false)
     */
    public boolean expandGroup(int groupPos, boolean animate) {
        ExpandableListPosition elGroupPos = ExpandableListPosition.obtain(
                ExpandableListPosition.GROUP, groupPos, -1, -1);
        PositionMetadata pm = mConnector.getFlattenedPos(elGroupPos);
        elGroupPos.recycle();
        boolean retValue =http://www.mamicode.com/ mConnector.expandGroup(pm);

        if (mOnGroupExpandListener != null) {
            mOnGroupExpandListener.onGroupExpand(groupPos);
        }

        if (animate) {
            final int groupFlatPos = pm.position.flatListPos;

            final int shiftedGroupPosition = groupFlatPos + getHeaderViewsCount();
            smoothScrollToPosition(shiftedGroupPosition + mAdapter.getChildrenCount(groupPos),
                    shiftedGroupPosition);
        }
        pm.recycle();

        return retValue;
    }

  看到if(animate)语句瞬间无语了,只是执行了smoothScrollToPosition()就是加了动画效果?太坑了!无奈只好另辟蹊径来实现。

  (废话多了些,现在进入正题。)

  先在网上搜索看到一篇博文:http://blog.csdn.net/qingye_love/article/details/8858147。

  正是我想要的动画效果,写得很详细,不过他是弹出一个很短的操作界面(只有3个button),我想干脆用listView嵌套listView,然后把它的效果拿来用好了。

  主布局文件list_list_layout.xml,很简单,就一个ListView,这个ListView的每个子项对应Expandablelistview的一个Group项:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <ListView 
        android:id="@+id/lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:focusable="false"
        ></ListView>

</RelativeLayout>

  然后是每个ListView子项布局list_item_layout.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    
    <RelativeLayout 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >
        
        <ImageView 
            android:id="@+id/listview_item_icon"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_margin="5dp"
            android:layout_centerVertical="true"
            />
        
        <TextView 
            android:id="@+id/listview_item_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:singleLine="true"
            android:textColor="#000"
            android:textSize="20dp"
            android:layout_marginLeft="60dp"
            android:layout_alignBaseline="@id/listview_item_icon"
            />
    </RelativeLayout>
    
    <RelativeLayout 
        android:id="@+id/listview_item_footer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:descendantFocusability="blocksDescendants"
        android:focusable="false"
        >
        <ListView 
            android:id="@+id/listview_item_lv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:focusable="false"
            ></ListView>
    </RelativeLayout>

</LinearLayout>

  每个Group项由一个ImageView和一个TextView组成,然后下面有个RelativeLayout,id为listview_item_footer,这个RelativeLayout里有个listView,这个就是每个Group下的子列表了。

  对应每个子ListView,也就是没一个Group,适配器写法与普通无异:

import java.util.List;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class ItemAdapter extends BaseAdapter{
    
    private List<SeletorDataInfo> devList;
    private LayoutInflater mInflater;
    
    public ItemAdapter(Context mContext, List<SeletorDataInfo> devList){
        this.devList = devList;
        mInflater = (LayoutInflater) mContext
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        if(null == devList)
            return 0;
        else {
            return devList.size();
        }
    }

    @Override
    public SeletorDataInfo getItem(int position) {
        // TODO Auto-generated method stub
        if(null == devList)
            return null;
        else {
            return devList.get(position);
        }
    }

    @Override
    public long getItemId(int position) {
        // TODO Auto-generated method stub
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // TODO Auto-generated method stub
        ItemHolder itemHolder = null;
        if (null == convertView) {
            itemHolder = new ItemHolder();
            convertView = mInflater.inflate(
                    R.layout.item_item_layout, null);

            itemHolder.name = (TextView) convertView
                    .findViewById(R.id.item_item_name);
            itemHolder.icon = (ImageView) convertView
                    .findViewById(R.id.item_item_icon);

            convertView.setTag(itemHolder);
        } else {
            itemHolder = (ItemHolder) convertView.getTag();
        }

        SeletorDataInfo mSelfData = getItem(position);
        if (null != mSelfData) {
            itemHolder.name.setText(mSelfData.getName());
            itemHolder.icon.setBackground(mSelfData.getIcon());
        }
        return convertView;
    }
    
    private class ItemHolder {
        ImageView icon;
        TextView name;
    }

}

  其中SeletorDataInfo是我自己定义的数据类。然后是所有Group的适配器:

import java.util.List;

import android.content.Context;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class ListViewAdapter extends BaseAdapter{
    
    private Context mContext;
    private List<SeletorDataInfo> roomList;
    private List<List<SeletorDataInfo>> allList;
    private LayoutInflater mInflater;
    private int mLcdWidth = 0;  
    private float mDensity = 0; 
    private final int itemWidth;
    
    public ListViewAdapter(Context mContext, List<SeletorDataInfo> roomList, List<List<SeletorDataInfo>> allList){
        this.mContext = mContext;
        this.roomList = roomList;
        this.allList = allList;
        mInflater = (LayoutInflater) mContext
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        DisplayMetrics dm = mContext.getResources().getDisplayMetrics();  
        mLcdWidth = dm.widthPixels;  
        mDensity = dm.density; 
        //这里我每个列表项高度是59dp。
        itemWidth = (int) (59 * mDensity);
    }

    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        if(null == roomList)
            return 0;
        else {
            return roomList.size();
        }
    }

    @Override
    public SeletorDataInfo getItem(int position) {
        // TODO Auto-generated method stub
        if(null == roomList)
            return null;
        else {
            return roomList.get(position);
        }
    }

    @Override
    public long getItemId(int position) {
        // TODO Auto-generated method stub
        return position;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        // TODO Auto-generated method stub
        ViewHolder viewHolder = null;
        if (null == convertView) {
            viewHolder = new ViewHolder();
            convertView = mInflater.inflate(
                    R.layout.list_item_layout, null);

            viewHolder.name = (TextView) convertView
                    .findViewById(R.id.listview_item_name);
            viewHolder.icon = (ImageView) convertView
                    .findViewById(R.id.listview_item_icon);
            viewHolder.lv = (ListView) convertView
                    .findViewById(R.id.listview_item_lv);

            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }

        SeletorDataInfo mSelfData = roomList.get(position);
        if (null != mSelfData) {
            viewHolder.name.setText(mSelfData.getName());
            viewHolder.icon.setBackground(mSelfData.getIcon());
            viewHolder.lv.setAdapter(new ItemAdapter(mContext, allList.get(position)));
        }
        
        //**********************************************************************************************************
        RelativeLayout footer = (RelativeLayout) convertView.findViewById(R.id.listview_item_footer); 
        //不明白为什么宽度被设成:屏宽减去10dp(mLcdWidth - 10 * mDensity),不过不去深究这个,因为我们关心的是高度。
        int widthSpec = MeasureSpec.makeMeasureSpec((int) (mLcdWidth - 10 * mDensity), MeasureSpec.EXACTLY);
        //然后,调用measure()方法,宽度被设成上面的widthSpec,而高度传了个0,不过没有关系因为高度下面才会设置
        footer.measure(widthSpec, 0);  
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) footer.getLayoutParams();
        //在此设置高度为:该组(Group)的项目数 * 每一项的高度。
        //本来我参看的那篇博文用的是params.bottomMargin = -footer.getMeasuredHeight(); 
        //但我使用时取footer.getMeasuredHeight(); 总出问题,第一次取只有listView一项的高度,后面高度也不匹配
        //不知道是listView缓存机制带来的问题还是什么,这里如果知道没一个列表项的高度,照现在的写法也没有问题。
        params.height = (allList.get(position).size() * itemWidth);
        if(roomList.get(position).state == 0) {
            params.bottomMargin = - params.height;
            footer.setVisibility(View.GONE);
        } else {
            params.bottomMargin = 0;
            footer.setVisibility(View.VISIBLE);
        }
     //**********************************************************************************************************
        return convertView;
    }
    
    private class ViewHolder {
        ImageView icon;
        TextView name;
        ListView lv;
    }

}

  与之前的adapter不同的地方主要在星号之间的代码,原理其实很简单,先测出你子ListView(比如名为mListView)所占的高度(比如高度为mHeight),然后把这个mListView的LayoutParams.bottomMargin = -mHeight;这样,其实mListView正好在其父布局的外面(其父布局正是footer)。然后下面的动画类中,不断设置这个LayoutParams.bottomMargin的值,让它从-mHeight逐渐变为0。那么,这个mListView就好像从两个Group项中“挤出来”的感觉一样。

  然后是自定义动画:

import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.LinearLayout.LayoutParams;

public class ViewExpandAnimation extends Animation {

    private View mAnimationView = null;
    private LayoutParams mViewLayoutParams = null;
    private int mStart = 0;
    private int mEnd = 0;
    
    public ViewExpandAnimation(View view){
        animationSettings(view, 500);
    }

    public ViewExpandAnimation(View view, int duration){
        animationSettings(view, duration);
    }
    
    private void animationSettings(View view, int duration){
        setDuration(duration);
        mAnimationView = view;
        mViewLayoutParams = (LayoutParams) view.getLayoutParams();
        mStart = mViewLayoutParams.bottomMargin;
        mEnd = (mStart == 0 ? (0 - view.getHeight()) : 0);
        view.setVisibility(View.VISIBLE);
    }
    
    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        super.applyTransformation(interpolatedTime, t);
        
        if(interpolatedTime < 1.0f){
            mViewLayoutParams.bottomMargin = mStart + (int) ((mEnd - mStart) * interpolatedTime);
            // invalidate
            mAnimationView.requestLayout();
        }else{
            mViewLayoutParams.bottomMargin = mEnd;
            mAnimationView.requestLayout();
            if(mEnd != 0){
                mAnimationView.setVisibility(View.GONE);
            }
        }
    }
}

  activity中加入如下片段即可:

        mListViewAdapter = new ListViewAdapter(this, roomList, allList);
        mListView.setAdapter(mListViewAdapter);
        mListView.setOnItemClickListener(new OnItemClickListener(){  
            @Override  
            public void onItemClick(AdapterView<?> arg0, View v, int pos,  
                    long arg3) {  
                View footer = v.findViewById(R.id.listview_item_footer);  
                footer.startAnimation(new ViewExpandAnimation(footer));  
                if(roomList.get(pos).state == 0) {
                    roomList.get(pos).state = 1;
                } else {
                    roomList.get(pos).state = 0;
                }
            }  
        });