Android 实现ListView展开item折叠式动画

注意:本次修改,修补了之前的部分bug

android中展开动画主要有2中,Translate和Scale,但运行效果并不像javascript中jQuery(id).slideDown()那么完美,在这里,我们借助动画机制,实现ListView的展开Expand Open,收缩 Expand Close动画。

这些效果在《去哪儿旅行app》和《酷狗音乐app》被广泛使用,我们这里模仿ListView的展开收缩式动画。

先来看图说话,点击item,被点击的Item以下部分会慢慢下滑。

133351_fsJh_2256215.gif

好了,上代码

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>

自定义动画



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文件


public class AnimExpandActivity extends Activity {

    private ListView mListView;

    private final List<Song> songList = new ArrayList<Song>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_listview);
        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));
        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 static class ListSongItemAdapter extends BaseAdapter implements View.OnClickListener {

        private final List<Song> dataSource = new ArrayList<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 Song 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 = getItem(position);
            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) songViewHolder.targetPanel.getLayoutParams();

songViewHolder.itemRoot.clearAnimation();  //防止复用过程动画还在执行

            if (song.isOpen()) {

                setRotation(songViewHolder.switcherIv, 180f);
                lp.bottomMargin = 0;
                lp.height = dimensionPixelSize;

            } else {
                setRotation(songViewHolder.switcherIv, 0f);
                lp.bottomMargin = -dimensionPixelSize;
                //高度是0,lp.bottomMargin值无效,这里之所以设置高度为0,因为为了防止布局耗时导致绘制不及时,从而出现错误显示
                lp.height = 0;
            }
            songViewHolder.targetPanel.setLayoutParams(lp);
            songViewHolder.switcherIv.setOnClickListener(this);
            songViewHolder.switcherIv.setTag(position);
            songViewHolder.titleTv.setText(position + " " + song.getName());

            return convertView;
        }


        @Override
        public void onClick(View v) {

            int position = (Integer) v.getTag();
            Context context = v.getContext();

            Song item = (Song) getItem(position);
            item.setOpen(!item.isOpen());

            ViewGroup itemView = (ViewGroup) v.getParent().getParent();
            SongViewHolder songViewHolder = (SongViewHolder) itemView.getTag();

            Toast.makeText(context, "position=" + position + ",title=" + item.getName(), Toast.LENGTH_SHORT).show();

            int defaultHeight = context.getResources().getDimensionPixelSize(R.dimen.bottom_item_height);
            itemView.startAnimation(new ExpandAnimation(songViewHolder.targetPanel, item.isOpen(), defaultHeight));

            rotateSwitcherIcon(v, item);

        }
    }


public static class SongViewHolder {
    public  View itemRoot;
    public View targetPanel;
    public View switcherIv;
    public TextView titleTv;

    public SongViewHolder(View convertView) {
        targetPanel = convertView.findViewById(R.id.song_id_panel_li);
        switcherIv = convertView.findViewById(R.id.song_id_switcher_btn);
        titleTv = convertView.findViewById(R.id.song_id_title_tv);
        itemRoot = convertView.findViewById(R.id.item_root);
    }
}


    /**
     * 旋转切换图标
     *
     * @param v
     * @param item
     */
    private static void rotateSwitcherIcon(View v, Song item) {
        float degree = item.isOpen() ? 180 : 0;
        v.animate()
                .setDuration(500)
                .setInterpolator(new AccelerateDecelerateInterpolator())
                .rotation(degree)
                .start();
    }

    private static void setRotation(View widget, float v) {
        widget.setRotation(v);
    }
}

Song Model

public 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 + "]";
    }

}

----------------------------------------------------------

这里使用了较为原始的自定义动画的方式,推荐读者能够使用 属性动画 自定义这种实现

可参考链接 http://blog.csdn.net/lingling1420q/article/details/38678493

目前为止,这个例子还需要进行优化,等时间再说吧。

try doing it!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值