注意:本次修改,修补了之前的部分bug
android中展开动画主要有2中,Translate和Scale,但运行效果并不像javascript中jQuery(id).slideDown()那么完美,在这里,我们借助动画机制,实现ListView的展开Expand Open,收缩 Expand Close动画。
这些效果在《去哪儿旅行app》和《酷狗音乐app》被广泛使用,我们这里模仿ListView的展开收缩式动画。
先来看图说话,点击item,被点击的Item以下部分会慢慢下滑。
好了,上代码
dimens.xml,这里是重点,因为下滑的前提是,尺寸必须是已知的,否则无法正常下滑
<dimen name="bottom_item_height">50dip</dimen>
<!--防止布局bug,所以需要设置一个数值区间大于bottom_item_height的复数-->
<dimen name="_bottom_item_height">-50.5dip</dimen>
主布局文件listview_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:listSelector="@android:color/transparent"
android:dividerHeight="0.7dip"
android:choiceMode="none"
android:divider="#d7d7d7"
android:orientation="vertical" >
</ListView>
listview_item.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" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dip"
android:orientation="horizontal"
android:paddingLeft="15dip"
android:paddingRight="15dip"
>
<TextView
android:id="@+id/song_id_title_tv"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:text="一片艳阳天"
android:textColor="#333333"
android:textSize="18sp" />
<ImageView
android:id="@+id/song_id_switcher_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@drawable/kg_item_ic_btn_toggle_menu_default" />
</LinearLayout>
<LinearLayout
android:id="@+id/song_id_panel_li"
android:layout_width="match_parent"
android:layout_height="@@dimen/bottom_item_height"
android:layout_marginBottom="@dimen/_bottom_item_height"
android:background="#333333"
android:gravity="center_vertical"
android:orientation="horizontal" >
<ImageView
android:layout_weight="1"
android:layout_width="0dip"
android:id="@+id/imageView1"
android:layout_height="match_parent"
android:scaleType="center"
android:src="@drawable/kg_ic_player_menu_download" />
<ImageView
android:id="@+id/imageView2"
android:layout_weight="1"
android:layout_width="0dip"
android:layout_height="match_parent"
android:scaleType="center"
android:src="@drawable/audio_identify_add_press" />
<ImageView
android:id="@+id/imageView3"
android:layout_weight="1"
android:layout_width="0dip"
android:layout_height="match_parent"
android:scaleType="center"
android:src="@drawable/audio_identify_share_press" />
<ImageView
android:id="@+id/imageView4"
android:layout_weight="1"
android:layout_width="0dip"
android:layout_height="match_parent"
android:scaleType="center"
android:src="@drawable/fm_distinguish_favorite" />
</LinearLayout>
</LinearLayout>
自定义动画
package com.example.explistview;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.LinearLayout.LayoutParams;
import com.example.actiontabbar.R;
public class ExpandAnimation extends Animation {
private View mTargetView;
private boolean isExpandDown;
public ExpandAnimation(View mTargetView, boolean isExpandDown,int defaultHeight) {
super();
this.mTargetView = mTargetView;
this.isExpandDown = isExpandDown;
setDuration(500L);
setInterpolator(new AccelerateDecelerateInterpolator());
setFillAfter(true);
resetViewHeight(mTargetView, defaultHeight);
}
/**
* 动画开始前,务必保证targetView的高度是存在的
* @param v
* @param defaultHeight
*/
private void resetViewHeight(View v,int defaultHeight)
{
LayoutParams lp = (LayoutParams) v.getLayoutParams();
lp.height = v.getContext().getResources().getDimensionPixelSize(R.dimen.bottom_item_height);
v.setLayoutParams(lp);
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
int height = mTargetView.getHeight();
/**
* 注意 这里的 LayoutParams 类型应该和mTargetView的parentView相关,
* 比如LinearLayout,这里的类型应该是android.widget.LinearLayout.LayoutParams
* 不推荐使用 ViewGroup.LayoutParams,因为不能改变外边距
*/
LayoutParams layoutParams = (LayoutParams) mTargetView.getLayoutParams();
if(isExpandDown)
{
layoutParams.bottomMargin = -height + (int) (height*interpolatedTime);
}else{
layoutParams.bottomMargin = -(int) (height*interpolatedTime);
}
mTargetView.setLayoutParams(layoutParams);
mTargetView.getParent().requestLayout();
}
}
Activity文件
package com.example.explistview;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout.LayoutParams;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.example.actiontabbar.R;
import com.nineoldandroids.view.ViewHelper;
import com.nineoldandroids.view.ViewPropertyAnimator;
public class AnimExpandActivity extends ActionBarActivity {
private ListView mListView;
private final List<Song> songList = new ArrayList<AnimExpandActivity.Song>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.listview_layout);
mListView = (ListView) findViewById(R.id.main_listview);
loadTestData();
mListView.setAdapter(new ListSongItemAdapter(this, songList));
}
private void loadTestData() {
songList.add(new Song("苏有朋-珍惜", false));
songList.add(new Song("崔子格-卜卦", false));
songList.add(new Song("吴奇隆-祝你一路顺风", false));
songList.add(new Song("马天宇-该死的温柔", false));
songList.add(new Song("陈奕迅-梦想天空分外蓝", false));
songList.add(new Song("陈奕迅-爱情转移", false));
songList.add(new Song("陈奕迅-十年", false));
songList.add(new Song("张宇-雨一直下", false));
songList.add(new Song("王筝-我们都是好孩子", false));
songList.add(new Song("筷子兄弟-小苹果", false));
songList.add(new Song("筷子兄弟-老男孩", false));
songList.add(new Song("成龙-神话", false));
songList.add(new Song("金莎-星月神话", false));
songList.add(new Song("金莎-相思垢", false));
songList.add(new Song("许嵩-断桥残雪", false));
songList.add(new Song("许嵩-半城烟沙", false));
songList.add(new Song("许嵩-灰色头像", false));
songList.add(new Song("许嵩-庐州月", false));
songList.add(new Song("陈坤-好久没回家", false));
}
private class ListSongItemAdapter extends BaseAdapter {
private final List<Song> dataSource = new ArrayList<AnimExpandActivity.Song>();
private LayoutInflater mLayoutInflater = null;
private int dimensionPixelSize = 0;
public ListSongItemAdapter(Context cxt, List<Song> dataSource) {
this.dataSource.addAll(dataSource);
mLayoutInflater = LayoutInflater.from(cxt);
dimensionPixelSize = cxt.getResources().getDimensionPixelSize(R.dimen.bottom_item_height);
}
@Override
public int getCount() {
return dataSource.size();
}
@Override
public Object getItem(int position) {
return dataSource.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
SongViewHolder songViewHolder = null;
if (convertView == null
|| !SongViewHolder.class.isInstance(convertView.getTag())) {
convertView = mLayoutInflater.inflate(R.layout.listview_item,
null);
songViewHolder = new SongViewHolder(convertView);
convertView.setTag(songViewHolder);
} else {
songViewHolder = (SongViewHolder) convertView.getTag();
}
Song song = dataSource.get(position);
LayoutParams lp = (LayoutParams) songViewHolder.targetPanel.getLayoutParams();
if(song.isOpen)
{
ViewHelper.setRotation(songViewHolder.switcherIv, 180f);
lp.bottomMargin = 0;
lp.height = dimensionPixelSize;
}else{
ViewHelper.setRotation(songViewHolder.switcherIv, 0f);
lp.bottomMargin = - dimensionPixelSize;
//高度是0,lp.bottomMargin值无效,这里之所以设置高度为0,因为为了防止布局耗时导致绘制不及时,从而出现错误显示
lp.height = 0;
}
songViewHolder.targetPanel.setLayoutParams(lp);
songViewHolder.switcherIv.setOnClickListener(switcherClickListener);
songViewHolder.switcherIv.setTag(position);
songViewHolder.titleTv.setText(position + " " + song.name);
return convertView;
}
}
private class Song implements Serializable {
private String name;
private boolean isOpen;
public Song(String name, boolean isOpen) {
super();
this.name = name;
this.isOpen = isOpen;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setOpen(boolean isOpen) {
this.isOpen = isOpen;
}
public boolean isOpen() {
return isOpen;
}
@Override
public String toString() {
return "Song [name=" + name + ", isOpen=" + isOpen + "]";
}
}
private class SongViewHolder implements Serializable {
private View contentView;
private TextView titleTv;
private ImageView switcherIv;
private View targetPanel;
public SongViewHolder(View contentView) {
this.contentView = contentView;
titleTv = (TextView) contentView
.findViewById(R.id.song_id_title_tv);
switcherIv = (ImageView) contentView
.findViewById(R.id.song_id_switcher_btn);
targetPanel = contentView.findViewById(R.id.song_id_panel_li);
}
}
private View.OnClickListener switcherClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = (Integer) v.getTag();
final BaseAdapter adapter = (BaseAdapter) mListView.getAdapter();
Song item = (Song) adapter.getItem(position);
item.setOpen(!item.isOpen());
ViewGroup itemView = (ViewGroup) v.getParent().getParent();
SongViewHolder songViewHolder = (SongViewHolder) itemView.getTag();
Toast.makeText(AnimExpandActivity.this,"position=" + position + ",title=" + item.getName(),Toast.LENGTH_SHORT).show();
int defaultHeight = getResources().getDimensionPixelSize(R.dimen.bottom_item_height);
itemView.startAnimation(new ExpandAnimation(songViewHolder.targetPanel, item.isOpen(),defaultHeight));
rotateSwitcherIcon(v, item);
}
};
/**
* 旋转切换图标
* @param v
* @param item
*/
private void rotateSwitcherIcon(View v, Song item)
{
float degree = item.isOpen() ? 180 : 0;
ViewPropertyAnimator.animate(v)
.setDuration(500)
.setInterpolator(new AccelerateDecelerateInterpolator())
.rotation(degree)
.start();
}
}
----------------------------------------------------------
这里使用了较为原始的自定义动画的方式,推荐读者能够使用 属性动画自定义这种实现
可参考链接 http://blog.csdn.net/lingling1420q/article/details/38678493
目前为止,这个例子还需要进行优化,等时间再说吧。
try doing it!