一步一步带你实现ListView动画展开布局, ExpandableLayout实现
2016-05-05 16:59
726 查看
做项目的时候,需要一种listview,点击item的时候在item的下方展开一个菜单,于是在gituhub上找到了源码: ExpandableLayout,地址: https://github.com/traex/ExpandableLayout
这个项目实现的效果如下:
上一篇我已经讲解了这个项目的原理,有兴趣的同学可以点击这里看源码解析:
http://blog.csdn.net/u010335298/article/details/51193565
今天我们主要是从开发者的角度一步一步的实现一个类似的效果。
也许你会想,这不是很简单吗?设置listview的item的点击事件控制view的显示隐藏就可以了。真的可以吗?我们来试试。
我们先定义adapter使用的xml , expandable_layout_item_layout.xml如下:
可以看到我们从上而下分别展示了item_layout和menu_layout,menu_layout不可见
接下来实现我们的adapter,ExpandableLayoutAdapter
在getView中,我们给item设置了点击事件,判断menu的可见性来设置menu是否可见,如果menu可见,点击item就让menu不可见;如果menu不可见,点击item就让menu可见。
好了,我们给listview设置上adapter
看一下运行效果。
看了效果之后,我们会发现两个问题:
1.点开和关闭menu没有动画
2.应该只有一个menu是打开状态的。这里显然不符合。
3.滚动的时候保存打开或者关闭的状态。
接下来,我们来优化一下。
先写一个打开的动画函数:
这里,我们用到的是属性动画,不了解属性动画的可以先看看属性动画。这个动画实现的是在500毫秒内,改变动画的值,从0到height,改变的时候,设置动画的值为view的layout params的height。
同理,我们实现隐藏的动画。
在getView的时候,点击item调用show和hide
来看效果。
动画的效果不错。接下来,我们来进行第二个优化。
思路,点击item的时候,先关闭所有打开的menu,再根据点击之前这个item的menu的状态决定要打开还是关闭menu。
这里,我们发现如果在MainActivity中设置listView.setOnItemClickListener(… …),这样ListView独立实现这个功能,所以我们把这些操作都在list view中进行。
我们把之前在adapter设置的打开关闭的动画都放在这个CustomExpandableLayoutItem.
attrl.xml
定义adapter,ExpandableLayoutAdapter2
adapter使用的expandable_layout_item_layout2.xml
custom_expandable_item_view.xml
custom_expandable_menu_view.xml
MainActivity
好了,到这里实现的和我们优化一完成的时候的效果是一样的…. ….
到这里,我们才开始优化二的开始… …
在这里,我们需要自定义listview
可以看到,我们继承ListView,实现了onItemClick和onScroll,页就是说我们要在onItemClick和onScroll做一些事情。
我们在onItemClick的时候,得到点击的item,如果item是打开状态,就关闭menu;否则,遍历所有的可见的item,关闭打开的item,之后,打开点击item的menu.
在onScrollStateChanged的时候,记录scrollState,
在滚动的时候,如果是scrollState是滚动状态,做一些事情。
1.如果menu的item在可见item当中(currentOpenId >= firstVisibleItem && currentOpenId <= firstVisibleItem+visibleItemCount),直接使menu打开。
2.否则,遍历所有可见的item,如果其menu打开,使关闭。
效果。
这个项目实现的效果如下:
上一篇我已经讲解了这个项目的原理,有兴趣的同学可以点击这里看源码解析:
http://blog.csdn.net/u010335298/article/details/51193565
今天我们主要是从开发者的角度一步一步的实现一个类似的效果。
点击listview的item显示和隐藏菜单的实现
也许你会想,这不是很简单吗?设置listview的item的点击事件控制view的显示隐藏就可以了。真的可以吗?我们来试试。我们先定义adapter使用的xml , expandable_layout_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="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/item_layout" android:layout_width="match_parent" android:layout_height="50dp" android:gravity="center" android:textColor="@color/gray_333333" android:textStyle="bold" android:paddingLeft="20dp" android:background="@color/gray_light"/> <FrameLayout android:id="@+id/menu_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="gone"> <TextView android:id="@+id/menu_tv" android:layout_width="match_parent" android:layout_height="50dp" android:gravity="center" android:textColor="@color/gray_333333" android:textStyle="bold" android:paddingLeft="20dp" android:background="@color/green_light" /> </FrameLayout> </LinearLayout>
可以看到我们从上而下分别展示了item_layout和menu_layout,menu_layout不可见
接下来实现我们的adapter,ExpandableLayoutAdapter
package com.example.myapp.adapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.Filter; import android.widget.Filterable; import android.widget.FrameLayout; import android.widget.TextView; import com.example.myapp.R; import com.example.myapp.util.Methods; import java.util.ArrayList; import java.util.List; /** * Created by zyr * DATE: 15-11-26 * Time: 下午2:52 * Email: yanru.zhang@renren-inc.com */ public class ExpandableLayoutAdapter extends BaseAdapter{ public List<String> arrayList = new ArrayList<String>(); private Context context; public ExpandableLayoutAdapter(Context context){ this.context = context; } public ExpandableLayoutAdapter(Context context, List<String> arrayList){ this.context = context; this.arrayList = new ArrayList<String>(arrayList); } public void setData(List<String> array){ if(array ==null){ return; } arrayList = new ArrayList<String>(array); notifyDataSetChanged(); } @Override public int getCount() { return arrayList.size(); } @Override public Object getItem(int position) { return arrayList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(final int position, View convertView, ViewGroup parent) { final ViewHolder viewHolder; if(convertView ==null){ convertView = LayoutInflater.from(context).inflate(R.layout.expandable_layout_item_layout,null); viewHolder = new ViewHolder(convertView); convertView.setTag(viewHolder); }else{ viewHolder = (ViewHolder)convertView.getTag(); } viewHolder.itemTv.setText(arrayList.get(position)); viewHolder.menuTv.setText("menu " + position + "!!!!") ; viewHolder.itemTv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(viewHolder.menuLy.getVisibility() == View.GONE){ viewHolder.menuLy.setVisibility(View.VISIBLE); }else{ viewHolder.menuLy.setVisibility(View.GONE); } } }); return convertView; } class ViewHolder{ View rootView; TextView itemTv; TextView menuTv; FrameLayout menuLy; public ViewHolder(View view){ rootView = view; itemTv = (TextView)view.findViewById(R.id.item_layout); menuTv = (TextView) view.findViewById(R.id.menu_tv); menuLy = (FrameLayout) view.findViewById(R.id.menu_layout); } } }
在getView中,我们给item设置了点击事件,判断menu的可见性来设置menu是否可见,如果menu可见,点击item就让menu不可见;如果menu不可见,点击item就让menu可见。
好了,我们给listview设置上adapter
listView = (ListView) findViewById(R.id.lv); for(int i=0;i<30;i++){ strings.add("zyr" + i); } adapter = new ExpandableLayoutAdapter(this,strings); listView.setAdapter(adapter);
看一下运行效果。
看了效果之后,我们会发现两个问题:
1.点开和关闭menu没有动画
2.应该只有一个menu是打开状态的。这里显然不符合。
3.滚动的时候保存打开或者关闭的状态。
接下来,我们来优化一下。
优化一,添加打开关闭动画。
先写一个打开的动画函数:public void show(final View v ,int height){ v.setVisibility(View.VISIBLE); ValueAnimator animator = ValueAnimator.ofInt(0,height); animator.setDuration(500); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int value = (Integer) animation.getAnimatedValue(); v.getLayoutParams().height = value; v.setLayoutParams(v.getLayoutParams()); } }); animator.start(); }
这里,我们用到的是属性动画,不了解属性动画的可以先看看属性动画。这个动画实现的是在500毫秒内,改变动画的值,从0到height,改变的时候,设置动画的值为view的layout params的height。
同理,我们实现隐藏的动画。
public void dismiss(final View v ,int height){ ValueAnimator animator = ValueAnimator.ofInt(height,0); animator.setDuration(500); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int value = (Integer) animation.getAnimatedValue(); if (value == 0) { v.setVisibility(View.GONE); } v.getLayoutParams().height = value; v.setLayoutParams(v.getLayoutParams()); } }); animator.start(); }
在getView的时候,点击item调用show和hide
@Override public View getView(final int position, View convertView, ViewGroup parent) { final ViewHolder viewHolder; if(convertView ==null){ convertView = LayoutInflater.from(context).inflate(R.layout.expandable_layout_item_layout,null); viewHolder = new ViewHolder(convertView); convertView.setTag(viewHolder); }else{ viewHolder = (ViewHolder)convertView.getTag(); } viewHolder.itemTv.setText(arrayList.get(position)); viewHolder.menuTv.setText("menu " + position + "!!!!") ; viewHolder.menuLy.measure(0, 0); final int height = viewHolder.menuLy.getMeasuredHeight(); viewHolder.itemTv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (viewHolder.menuLy.getVisibility() == View.GONE) { show(viewHolder.menuLy, height); } else { dismiss(viewHolder.menuLy, height); } } }); return convertView; }
来看效果。
动画的效果不错。接下来,我们来进行第二个优化。
优化二,只有一个item的menu处于打开状态。
思路,点击item的时候,先关闭所有打开的menu,再根据点击之前这个item的menu的状态决定要打开还是关闭menu。这里,我们发现如果在MainActivity中设置listView.setOnItemClickListener(… …),这样ListView独立实现这个功能,所以我们把这些操作都在list view中进行。
定义ExpandableLayoutItem作为listview的item.
我们把之前在adapter设置的打开关闭的动画都放在这个CustomExpandableLayoutItem.package com.example.myapp.view; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.FrameLayout; import android.widget.RelativeLayout; import com.example.myapp.R; /** * Created by zyr * DATE: 16-4-19 * Time: 下午6:33 * Email: yanru.zhang@renren-inc.com */ public class CustomExpandableLayoutItem extends RelativeLayout{ private Context mContext; private int menuViewId, itemViewId; private View menuView, itemView; private FrameLayout menuLayout, itemLayout; private boolean isAnimating = false; private boolean isOpen = false; private int menuLayoutHeight; private static final int DURATION = 500; public CustomExpandableLayoutItem(Context context) { this(context, null); } public CustomExpandableLayoutItem(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CustomExpandableLayoutItem(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mContext = context; TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomExpandableLayoutItem); for(int i=0;i<typedArray.length();i++){ int attr = typedArray.getIndex(i); switch (attr){ case R.styleable.CustomExpandableLayoutItem_itemLayout: itemViewId = typedArray.getResourceId(R.styleable.CustomExpandableLayoutItem_itemLayout,0); break; case R.styleable.CustomExpandableLayoutItem_menuLayout: menuViewId = typedArray.getResourceId(R.styleable.CustomExpandableLayoutItem_menuLayout,0); break; } } typedArray.recycle(); // Log.d("zyr", "itemViewId :" + itemViewId + " menuViewId :" + menuViewId); getItemView(); getMenuView(); // Log.d("zyr", "itemView :" + (itemView == null) + " menuView :" + (menuView == null)); init(); } private void init() { View rootView = LayoutInflater.from(mContext).inflate(R.layout.expandable_layout_root_view, this); itemLayout = (FrameLayout)rootView.findViewById(R.id.expandable_header_layout); menuLayout = (FrameLayout)rootView.findViewById(R.id.expandable_content_layout); if(itemView !=null){ itemLayout.addView(itemView); } if(menuView !=null){ menuLayout.addView(menuView); } menuLayout.measure(0, 0); menuLayoutHeight = menuLayout.getMeasuredHeight(); menuLayout.setVisibility(GONE); } public void hide() { if(isAnimating || !isOpen){ return; } ValueAnimator valueAnimator = ValueAnimator.ofInt(menuLayoutHeight,0); valueAnimator.setDuration(DURATION); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int value = (Integer) animation.getAnimatedValue(); if (value == 0) { menuLayout.setVisibility(GONE); isOpen = false; } menuLayout.getLayoutParams().height = value; menuLayout.setLayoutParams(menuLayout.getLayoutParams()); } }); valueAnimator.start(); } public void show() { if(isAnimating){ return; } ValueAnimator valueAnimator = ValueAnimator.ofInt(0, menuLayoutHeight); valueAnimator.setDuration(DURATION); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int value = (Integer) animation.getAnimatedValue(); if (value == 0) { menuLayout.setVisibility(VISIBLE); } if (value == menuLayoutHeight) { isOpen = true; } menuLayout.getLayoutParams().height = value; menuLayout.setLayoutParams(menuLayout.getLayoutParams()); } }); valueAnimator.start(); } public void showRightNow(){ if(menuLayout.getLayoutParams().height == menuLayoutHeight){ return; } menuLayout.setVisibility(VISIBLE); menuLayout.getLayoutParams().height = menuLayoutHeight; menuLayout.setLayoutParams(menuLayout.getLayoutParams()); isOpen = true; invalidate(); } public void hideRightNow(){ if(menuLayout.getLayoutParams().height == 0){ return; } menuLayout.setVisibility(GONE); menuLayout.getLayoutParams().height = 0; menuLayout.setLayoutParams(menuLayout.getLayoutParams()); isOpen = false; invalidate(); } public View getMenuView() { if(menuView == null){ if(menuViewId !=0){ menuView = View.inflate(mContext, menuViewId,null); } } return menuView; } public View getItemView() { if(itemView == null){ if(itemViewId !=0){ itemView = View.inflate(mContext, itemViewId,null); } } return itemView; } public boolean isOpen() { return isOpen; } }
attrl.xml
<declare-styleable name="CustomExpandableLayoutItem"> <attr name="itemLayout" format="reference"/> <attr name="menuLayout" format="reference"/> </declare-styleable>
定义adapter,ExpandableLayoutAdapter2
package com.example.myapp.adapter; import android.animation.ValueAnimator; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.FrameLayout; import android.widget.TextView; import com.example.myapp.R; import com.example.myapp.view.CustomExpandableLayoutItem; import java.util.ArrayList; import java.util.List; /** * Created by zyr * DATE: 15-11-26 * Time: 下午2:52 * Email: yanru.zhang@renren-inc.com */ public class ExpandableLayoutAdapter2 extends BaseAdapter{ public List<String> arrayList = new ArrayList<String>(); private Context context; public ExpandableLayoutAdapter2(Context context){ this.context = context; } public ExpandableLayoutAdapter2(Context context, List<String> arrayList){ this.context = context; this.arrayList = new ArrayList<String>(arrayList); } public void setData(List<String> array){ if(array ==null){ return; } arrayList = new ArrayList<String>(array); notifyDataSetChanged(); } @Override public int getCount() { return arrayList.size(); } @Override public Object getItem(int position) { return arrayList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(final int position, View convertView, ViewGroup parent) { final ViewHolder viewHolder; if(convertView ==null){ convertView = LayoutInflater.from(context).inflate(R.layout.expandable_layout_item_layout2,null); viewHolder = new ViewHolder(convertView); convertView.setTag(viewHolder); }else{ viewHolder = (ViewHolder)convertView.getTag(); } viewHolder.itemTv.setText("item " + position); viewHolder.menuTv.setText("menu" + position + "..."); return convertView; } class ViewHolder{ CustomExpandableLayoutItem expandableLayoutItem; View itemView; View menuView; TextView itemTv; TextView menuTv; public ViewHolder(View view){ expandableLayoutItem = (CustomExpandableLayoutItem) view.findViewById(R.id.custom_expandable_layout); itemView = expandableLayoutItem.getItemView(); menuView = expandableLayoutItem.getMenuView(); itemTv = (TextView) itemView.findViewById(R.id.item_tv); menuTv = (TextView) menuView.findViewById(R.id.menu_tv); } } }
adapter使用的expandable_layout_item_layout2.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content"> <com.example.myapp.view.CustomExpandableLayoutItem android:id="@+id/custom_expandable_layout" android:layout_width="match_parent" android:layout_height="wrap_content" app:itemLayout="@layout/custom_expandable_item_view" app:menuLayout="@layout/custom_expandable_menu_view"> </com.example.myapp.view.CustomExpandableLayoutItem> </RelativeLayout>
custom_expandable_item_view.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/item_tv" android:layout_width="match_parent" android:layout_height="50dp" android:background="@color/gray_light" android:textColor="@color/gray_333333" android:textSize="20sp" android:text="header" android:gravity="center"/> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/gray_333333"/> </LinearLayout>
custom_expandable_menu_view.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/menu_tv" android:layout_width="match_parent" android:layout_height="50dp" android:background="@color/green_light" android:textColor="@color/gray_333333" android:textSize="20sp" android:text="content" android:gravity="center"/> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/gray_333333"/> </LinearLayout>
MainActivity
listView = (CustomExpandableListView) findViewById(R.id.lv); for(int i=0;i<30;i++){ strings.add("zyr" + i); } adapter = new ExpandableLayoutAdapter2(this,strings); listView.setAdapter(adapter);
好了,到这里实现的和我们优化一完成的时候的效果是一样的…. ….
到这里,我们才开始优化二的开始… …
只有一个item处于打开状态。
在这里,我们需要自定义listviewpublic class CustomExpandableListView extends ListView implements AdapterView.OnItemClickListener,AbsListView.OnScrollListener{
可以看到,我们继承ListView,实现了onItemClick和onScroll,页就是说我们要在onItemClick和onScroll做一些事情。
@Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { //get click view if(getChildAt(position - getFirstVisiblePosition()) instanceof CustomExpandableLayoutItem){ CustomExpandableLayoutItem expandableLayout = (CustomExpandableLayoutItem) getChildAt(position - getFirstVisiblePosition()); if(expandableLayout.isOpen()){ expandableLayout.hide(); currentOpenId = -1; }else{ //close all menu for(int i=getFirstVisiblePosition();i<=getLastVisiblePosition();i++){ CustomExpandableLayoutItem item = (CustomExpandableLayoutItem) getChildAt(i - getFirstVisiblePosition()); item.hide(); } expandableLayout.show(); currentOpenId = position; } } }
我们在onItemClick的时候,得到点击的item,如果item是打开状态,就关闭menu;否则,遍历所有的可见的item,关闭打开的item,之后,打开点击item的menu.
@Override public void onScrollStateChanged(AbsListView view, int scrollState) { this.scrollState = scrollState; }
在onScrollStateChanged的时候,记录scrollState,
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if(scrollState == SCROLL_STATE_IDLE){ return; } Log.d("zyr","--------firstVisibleItem :" + firstVisibleItem); if(currentOpenId >= firstVisibleItem && currentOpenId <= firstVisibleItem+visibleItemCount){ CustomExpandableLayoutItem expandableLayout = (CustomExpandableLayoutItem) getChildAt(currentOpenId - getFirstVisiblePosition()); if(expandableLayout!=null && !expandableLayout.isOpen()){ expandableLayout.showRightNow(); Log.d("zyr", "--------show :" + currentOpenId); } }else{ for(int i=firstVisibleItem;i<firstVisibleItem+visibleItemCount;i++){ CustomExpandableLayoutItem expandableLayout = (CustomExpandableLayoutItem) getChildAt(i-firstVisibleItem); if(expandableLayout!=null && expandableLayout.isOpen()){ expandableLayout.hideRightNow(); Log.d("zyr", "--------hide :" + i); } } } invalidate(); }
在滚动的时候,如果是scrollState是滚动状态,做一些事情。
1.如果menu的item在可见item当中(currentOpenId >= firstVisibleItem && currentOpenId <= firstVisibleItem+visibleItemCount),直接使menu打开。
2.否则,遍历所有可见的item,如果其menu打开,使关闭。
效果。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories