新来的项目要求第一眼一看就是用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 = 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;
}
}
});
仿Expandablelistview效果的ListView(加入了子列表渐入渐出的动画),布布扣,bubuko.com