您的位置:首页 > 移动开发 > Android开发

一步一步带你实现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显示和隐藏菜单的实现

也许你会想,这不是很简单吗?设置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处于打开状态。

在这里,我们需要自定义listview

public 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打开,使关闭。

效果。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息