注意:本次修改,修补了之前的部分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!