首页 > 代码库 > 仿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; } } });