您的位置:首页 > 其它

(4.1.23.15)自定义控件三部曲之动画篇(十三)——实现ListView Item进入动画

2016-05-11 21:16 656 查看
相关文章:

Android自定义控件三部曲文章索引》: http://blog.csdn.net/harvic880925/article/details/50995268

前面两篇我们讲解了使用layoutAnimation和LayoutTransition实现ViewGroup中Item加载动画的方法,但他们都各自存在问题:

layoutAnimation虽然是API 1中就已经引入,但只能在动画初次创建时才能使用指定动画。控件创建以后,再往ViewGroup里加Item就不会再有动画。这显然是不合适的!

LayoutTransition能够实现无论何时往ViewGroup中添加控件都可以给其中控件使用动画。但最大的问题是,它的API等级是11。而且也没有兼容包可供我们使用这个函数。

这样问题就来了,如果我们想在兼容API 8以上的机型,完成ListView中各个Item进入时都添加动画,这要怎么来做呢?

今天我们要完成的效果图如下:



从效果图中可以看到,当每个Item进入的时候,都添加了动画。前面我们说了layoutAnimation和LayoutTransition所存在的问题,那抛开这两个函数,我们要如何实现Item进入动画呢?

别忘了,ListView在得到每个Item时会调用BaseAdapter的getView方法!getView中每一个convertView就是当前要显示的Item所对应的View,所以我们直接对convertView添加动画不就好了。

上面的原理理解起来并不难,下面我们就看看如何实现的吧。


一、搭框架

这部分,我们主要是先搭出来要实现的框架,把ListView填充起来,效果如下:



这里实现的效果就是把listview填充起来,总共用了九张图片,listview列表循环显示这九张图片,但我在每个图片上显示了当前item所在的位置。

好了,下面就来看代码吧


1、item布局(item_layout.xml)

我们先来看看listview的Item是怎么布局的:

[html] view
plain copy







<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="wrap_content">

<ImageView

android:id="@+id/img"

android:layout_width="fill_parent"

android:layout_height="250dp"

android:scaleType="centerCrop"

android:layout_margin="5dp"

android:layout_gravity="center"/>

<TextView

android:id="@+id/text"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:textSize="20dp"

android:layout_gravity="center"/>

</FrameLayout>

代码很好理解,从效果图中也可以看出,底部一个imageview,中间一个文字来表示当前item所在的位置。


2、ListAdapter

这里就是ListView的Adapter的代码位置了,完整的代码如下,然后再细讲:

[java] view
plain copy







public class ListAdapter extends BaseAdapter {

private List<Drawable> mDrawableList = new ArrayList<>();

private int mLength = 0;

private LayoutInflater mInflater;

private Context mContext;

private ListView mListView;

public ListAdapter(Context context, ListView listView, List<Drawable> drawables, int length) {

mDrawableList.addAll(drawables);

mLength = length;

mInflater = LayoutInflater.from(context);

mContext = context;

mListView = listView;

}

@Override

public int getCount() {

return mLength;

}

@Override

public Object getItem(int position) {

return mDrawableList.get(position % mDrawableList.size());

}

@Override

public long getItemId(int position) {

return position;

}

@Override

public View getView(int position, View convertView, ViewGroup parent) {

ViewHolder holder = null;

if (convertView == null) {

holder = new ViewHolder();

convertView = mInflater.inflate(R.layout.item_layout, null);

holder.mImageView = (ImageView) convertView.findViewById(R.id.img);

holder.mTextView = (TextView) convertView.findViewById(R.id.text);

} else {

holder = (ViewHolder) convertView.getTag();

}

convertView.setTag(holder);

holder.mImageView.setImageDrawable(mDrawableList.get(position % mDrawableList.size()));

holder.mTextView.setText(position+"");

return convertView;

}

public class ViewHolder {

public ImageView mImageView;

public TextView mTextView;

}

}

首先是构造函数:

[java] view
plain copy







public ListAdapter(Context context, ListView listView, List<Drawable> drawables, int length) {

mDrawableList.addAll(drawables);

mLength = length;

mInflater = LayoutInflater.from(context);

mContext = context;

mListView = listView;

}

首先是传进来的几个参数,List<Drawable> drawables是listview要循环显示的图片的Drawable对象列表,length表示当前listview要显示多少行。可能有些同学会注意到,我们还把ListView对象给传进来了,然后在上面的代码中并没有用到,其中把listview封装进Adapter是一个好习惯,因为我们可能会在Adapter中监听listview的状态从而改变item的显示情况。我们这里目前还没有用到Listview

然后是getItem函数:

[java] view
plain copy







@Override

public Object getItem(int position) {

return mDrawableList.get(position % mDrawableList.size());

}

我们知道本身的BaseAdapter并没有使用getItem(int position)函数,重写这个函数是为了让我们在BaseAdapter实例中,可以通过getItem来获取我们想要的实例(类似下面这样):

[java] view
plain copy







ListView listView = (ListView)findViewById(R.id.list);

final ListAdapter adapter = new ListAdapter(this,listView,drawables,300);

listView.setAdapter(adapter);

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

@Override

public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

//关键在这里哦

Drawable drawable = (Drawable) adapter.getItem(position);

}

});

这里我们将当前位置所对应的图片Drawable传过去:

[java] view
plain copy







public Object getItem(int position) {

return mDrawableList.get(position % mDrawableList.size());

}

大家可能会疑问:为什么要用position % mDrawableList.size()来得到当前图片在图片列表中的索引?因为我们是循环显示的图片的,比如我们总共有九张图片,那当前是第12个item时,显示的应当是第3张图了,position的值为12(因为Adapter的position是从0开始的),所以12%9 = 3;这就对上了。理解不了的同学,多拿几个数来算算,比如当显示到第15张时,position是多少,对应的图片应该是哪一张呢?

最后getView()函数

[java] view
plain copy







public View getView(int position, View convertView, ViewGroup parent) {

ViewHolder holder = null;

if (convertView == null) {

holder = new ViewHolder();

convertView = mInflater.inflate(R.layout.item_layout, null);

holder.mImageView = (ImageView) convertView.findViewById(R.id.img);

holder.mTextView = (TextView) convertView.findViewById(R.id.text);

} else {

holder = (ViewHolder) convertView.getTag();

}

convertView.setTag(holder);

holder.mImageView.setImageDrawable(mDrawableList.get(position % mDrawableList.size()));

holder.mTextView.setText(position+"");

return convertView;

}

这段代码就不用我讲了吧,就是基本的getView用法,如果大家不理解为什么要利用 convertView.setTag(holder);的方式来重复使用convertView,可以参考这篇文章《BaseAdapter——convertView回收机制与动态控件响应》

在理解了convertview回收机制以后,这里最难的地方应该就是赋值的位置了:

[java] view
plain copy







holder.mImageView.setImageDrawable(mDrawableList.get(position % mDrawableList.size()));

holder.mTextView.setText(position+"");

根据位置找到图片的drawable我们上面已经讲过了,这也就没什么难度了,就不再细讲了。


3、主布局(main.xml)

主布局非常简单,就只有一个ListView控件:

[html] view
plain copy







<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:background="#ffffff">

<ListView

android:id="@+id/list"

android:layout_width="fill_parent"

android:layout_height="fill_parent"/>

</LinearLayout>


4、MyActivity.java

最后是在MyActivity中构造ListAdapter并设置进listview的过程了:

[java] view
plain copy







public class MyActivity extends Activity {

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

List<Drawable> drawables = new ArrayList<>();

drawables.add(getResources().getDrawable(R.drawable.pic1));

drawables.add(getResources().getDrawable(R.drawable.pic2));

drawables.add(getResources().getDrawable(R.drawable.pic3));

drawables.add(getResources().getDrawable(R.drawable.pic4));

drawables.add(getResources().getDrawable(R.drawable.pic5));

drawables.add(getResources().getDrawable(R.drawable.pic6));

drawables.add(getResources().getDrawable(R.drawable.pic7));

drawables.add(getResources().getDrawable(R.drawable.pic8));

drawables.add(getResources().getDrawable(R.drawable.pic9));

ListView listView = (ListView)findViewById(R.id.list);

ListAdapter adapter = new ListAdapter(this,listView,drawables,300);

listView.setAdapter(adapter);

}

}

这段代码很简单了,就是先构造图片所对应的Drawable列表,然后构造ListAdapter实例,最后设置进Listview将其显示出来,没什么难度,也没什么好讲了。

到这里,我们listview就构造完成了。下面就看如何向其中的item添加动画的环节了。


二、Item添加动画——初步实现


1、动画文件(bottom_in_anim.xml)

先定义从底部进入的动画:

[html] view
plain copy







<?xml version="1.0" encoding="utf-8"?>

<set xmlns:android="http://schemas.android.com/apk/res/android"

android:duration="1000">

<translate android:fromYDelta="100%" android:toYDelta="0"/>

<alpha android:fromAlpha="0" android:toAlpha="1"/>

</set>

这段动画不难理解,效果是从底部进入,alpha值从0变到1;


2、在Adapter中添加动画代码

首先,我们在ListAdapter中初始化的时候,加载动画:

[java] view
plain copy







public class ListAdapter extends BaseAdapter {

…………

private Animation animation;

public ListAdapter(Context context, ListView listView, List<Drawable> drawables, int length) {

…………

animation = AnimationUtils.loadAnimation(mContext,R.anim.bottom_in_anim);

}

…………

}

然后在getView的时候为每个convertView添加上动画

[java] view
plain copy







public View getView(int position, View convertView, ViewGroup parent) {

ViewHolder holder = null;

if (convertView == null) {

holder = new ViewHolder();

convertView = mInflater.inflate(R.layout.item_layout, null);

holder.mImageView = (ImageView) convertView.findViewById(R.id.img);

holder.mTextView = (TextView) convertView.findViewById(R.id.text);

} else {

holder = (ViewHolder) convertView.getTag();

}

convertView.startAnimation(animation);

convertView.setTag(holder);

holder.mImageView.setImageDrawable(mDrawableList.get(position % mDrawableList.size()));

holder.mTextView.setText(position+"");

return convertView;

}

让一个view开始动画非常简单,只需要调用convertView.startAnimation(animation);即可;这样就可以实现在构造item的时候就开始动画

ListAdapter完整代码如下

[java] view
plain copy







public class ListAdapter extends BaseAdapter {

private List<Drawable> mDrawableList = new ArrayList<>();

private int mLength = 0;

private LayoutInflater mInflater;

private Context mContext;

private ListView mListView;

private Animation animation;

public ListAdapter(Context context, ListView listView, List<Drawable> drawables, int length) {

mDrawableList.addAll(drawables);

mLength = length;

mInflater = LayoutInflater.from(context);

mContext = context;

mListView = listView;

animation = AnimationUtils.loadAnimation(mContext,R.anim.bottom_in_anim);

}

@Override

public int getCount() {

return mLength;

}

@Override

public Object getItem(int position) {

return mDrawableList.get(position % mDrawableList.size());

}

@Override

public long getItemId(int position) {

return position;

}

@Override

public View getView(int position, View convertView, ViewGroup parent) {

ViewHolder holder = null;

if (convertView == null) {

holder = new ViewHolder();

convertView = mInflater.inflate(R.layout.item_layout, null);

holder.mImageView = (ImageView) convertView.findViewById(R.id.img);

holder.mTextView = (TextView) convertView.findViewById(R.id.text);

} else {

holder = (ViewHolder) convertView.getTag();

}

convertView.startAnimation(animation);

convertView.setTag(holder);

holder.mImageView.setImageDrawable(mDrawableList.get(position % mDrawableList.size()));

holder.mTextView.setText(position+"");

return convertView;

}

public class ViewHolder {

public ImageView mImageView;

public TextView mTextView;

}

}

效果图为:



从效果图中可以看出,我们初步实现了在item生成的时候添加进入动画


三、优化

上面虽然解决了进入时添加动画的问题,但仔细的同学可以看出,在这个效果图中还存在几个问题,可能上面的效果图还看不清楚具体存在的问题

1、如果上拉的时候,一下上拉了几个item,那些要显示的item会一起从底部出现

2、在下拉的时候,上部出现的item也会应用上动画

首先,解决第二个问题比较简单,只需要判断当前手指是上滑还上下滑就可以了,只有当手指向上滑的时候,才对底部出现的item添加上入场动画,其它时间不添加动画即可

第一个问题其实也比较容易解决,我们可以通过listview.getChildCount()得到当前ListView中有多少个item。我们只给最后一个添加动画即可。其它的就直接显示就好了。


1、上下滑动问题

首先,我们首先解决上下滑动问题,这个问题其实比较好解决,只需要监听listview的OnScrollListener,根据它判断出当前ListView是上滑还是下滑就可以了。

先看我们在onScrollListner中都有哪些参数:

[java] view
plain copy







AbsListView.OnScrollListener mOnScrollListener = new AbsListView.OnScrollListener() {

@Override

public void onScrollStateChanged(AbsListView view, int scrollState) {

}

@Override

public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

}

};

我们主要关注onScroll的监听,

[java] view
plain copy







onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)

有四个参数:

第一个参数AbsListView view:是当前listview的对象
第二个参数int firstVisibleItem:表示当前第一个可见的item在listview所有item中的索引,这里需要非常注意,firstVisibleItem与getChildAt(int position)中的参数position的意义不同,firstVisibleItem是指在整个ListView中的位置。而getChildAt(int position)中参数position传的是当前屏幕显示区域中item的索引,屏幕中第一个item的view可以通过getChildAt(0)得到。
第三个参数int visibleItemCount:表示当前屏幕中可见的有几条item
第四个参数int totalItemCount:表示当前listview总共有多少条item,得到的值与adapter.getCount()的值相同。

在理解了上面四个参数以后,我们再来看看下移的情况;

向下移动包括两种情况:

第一:屏幕中第一个item或前几个item一起移出屏幕,在这种情况下,我们只需要判断firstVisibleItem是否比上次的值大即可。即第一个显示的item是不是已经向下移了

[java] view
plain copy







public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

/**

* firstVisibleItem > mFirstPosition表示向下滑动一个或多个Item

*/

isScrollDown = firstVisibleItem > mFirstPosition;

mFirstPosition = firstVisibleItem;

}

第二:可能用户并没有一次性移一整条item,而是仅让当前item向上移了一点点。这里,由于当前可见的第一个item的位置仍然是firstVisibleItem,只是它的top值变了。

如下图:



在这个效果图中,只是将第一个item向上移动了一点点,第一个item左上角的坐标从(0,-100)变成了(0,-200);所以从这个图中,我们可以得到如何计算在这种情况下是否上移。

[java] view
plain copy







@Override

public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

View firstChild = view.getChildAt(0);

if (firstChild == null) return;

int top = firstChild.getTop();

/**

* mFirstTop > top表示在当前这个item中滑动

*/

isScrollDown = mFirstTop > top;

mFirstTop = top;

}

只需要判断当前第一个item的左上角坐标是不是变小了即可。这里需要注意,得到屏幕中显示的第一个Item是通过ListView.getChildAt(int position)函数得到的。我们上面已经讲到,ListView.getChildAt(int position)中参数position表示的是当前item所在屏幕显示区域中的索引,屏幕中第一个item的索引是0

所以我们将这两种情况进行合并,得到完整的onScrollListener:

[java] view
plain copy







AbsListView.OnScrollListener mOnScrollListener = new AbsListView.OnScrollListener() {

@Override

public void onScrollStateChanged(AbsListView view, int scrollState) {

}

@Override

public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

View firstChild = view.getChildAt(0);

if (firstChild == null) return;

int top = firstChild.getTop();

/**

* firstVisibleItem > mFirstPosition表示向下滑动一整个Item

* mFirstTop > top表示在当前这个item中滑动

*/

isScrollDown = firstVisibleItem > mFirstPosition || mFirstTop > top;

mFirstTop = top;

mFirstPosition = firstVisibleItem;

}

};

然后在getView的时候,判断如果是向下滑动,就添加动画

[java] view
plain copy







@Override

public View getView(int position, View convertView, ViewGroup parent) {

ViewHolder holder = null;

if (convertView == null) {

holder = new ViewHolder();

convertView = mInflater.inflate(R.layout.item_layout, null);

holder.mImageView = (ImageView) convertView.findViewById(R.id.img);

holder.mTextView = (TextView) convertView.findViewById(R.id.text);

} else {

holder = (ViewHolder) convertView.getTag();

}

if (isScrollDown) {

convertView.startAnimation(animation);

}

convertView.setTag(holder);

holder.mImageView.setImageDrawable(mDrawableList.get(position % mDrawableList.size()));

holder.mTextView.setText(position + "");

return convertView;

}


2、多个item同时动画问题

由于我们只能在Item生成时给这个Item添加动画,所以要解决多个item同时移动的问题,我们只能给最后一个Item添加动画,其它item不给他们添加;但我们怎么知道当前这个item是不是要显示的最后一个item呢?无法得各,所以一个中转方案是,在每一个item在添加动画前,都把当前显示区域内所有item动画给取消,然后给当前convertView添加上动画;当listview滚动到最后一个Item的时候,自然,同样也是先把所有动画取消,然后给他自己添加上动画,所以这样看起来就好像是只给他自己添加了动画,之前滚动的item是没有动画的。

代码如下:

[java] view
plain copy







public View getView(int position, View convertView, ViewGroup parent) {

ViewHolder holder = null;

if (convertView == null) {

holder = new ViewHolder();

convertView = mInflater.inflate(R.layout.item_layout, null);

holder.mImageView = (ImageView) convertView.findViewById(R.id.img);

holder.mTextView = (TextView) convertView.findViewById(R.id.text);

} else {

holder = (ViewHolder) convertView.getTag();

}

//清除当前显示区域中所有item的动画

for (int i=0;i<mListView.getChildCount();i++){

View view = mListView.getChildAt(i);

view.clearAnimation();

}

//然后给当前item添加上动画

if (isScrollDown) {

convertView.startAnimation(animation);

}

convertView.setTag(holder);

holder.mImageView.setImageDrawable(mDrawableList.get(position % mDrawableList.size()));

holder.mTextView.setText(position + "");

return convertView;

}

在这段代码中,相比上面的代码,我们只添加了一段语句:

[java] view
plain copy







for (int i=0;i<mListView.getChildCount();i++){

View view = mListView.getChildAt(i);

view.clearAnimation();

}

上面我们讲了mListView.getChildAt(int position)的用法,说到它的参数position表示的是在当前屏幕显示区域中当前item的索引。所以在当前屏幕中第一个item的索引是0;而mListView.getChildCount()则表示当前屏幕显示区域中,总共有多少个item。所以我们利用上面的代码,对屏幕显示区域中每个item进行索引,然后取消他们的动画即可。

此时ListAdapter完整的代码为:

[java] view
plain copy







public class ListAdapter extends BaseAdapter {

private List<Drawable> mDrawableList = new ArrayList<>();

private int mLength = 0;

private LayoutInflater mInflater;

private Context mContext;

private ListView mListView;

private Animation animation;

private int mFirstTop, mFirstPosition;

private boolean isScrollDown;

public ListAdapter(Context context, ListView listView, List<Drawable> drawables, int length) {

mDrawableList.addAll(drawables);

mLength = length;

mInflater = LayoutInflater.from(context);

mContext = context;

mListView = listView;

animation = AnimationUtils.loadAnimation(mContext, R.anim.bottom_in_anim);

mListView.setOnScrollListener(mOnScrollListener);

}

AbsListView.OnScrollListener mOnScrollListener = new AbsListView.OnScrollListener() {

@Override

public void onScrollStateChanged(AbsListView view, int scrollState) {

}

@Override

public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

View firstChild = view.getChildAt(0);

if (firstChild == null) return;

int top = firstChild.getTop();

/**

* firstVisibleItem > mFirstPosition表示向下滑动一整个Item

* mFirstTop > top表示在当前这个item中滑动

*/

isScrollDown = firstVisibleItem > mFirstPosition || mFirstTop > top;

mFirstTop = top;

mFirstPosition = firstVisibleItem;

}

};

@Override

public int getCount() {

return mLength;

}

@Override

public Object getItem(int position) {

return mDrawableList.get(position % mDrawableList.size());

}

@Override

public long getItemId(int position) {

return position;

}

@Override

public View getView(int position, View convertView, ViewGroup parent) {

ViewHolder holder = null;

if (convertView == null) {

holder = new ViewHolder();

convertView = mInflater.inflate(R.layout.item_layout, null);

holder.mImageView = (ImageView) convertView.findViewById(R.id.img);

holder.mTextView = (TextView) convertView.findViewById(R.id.text);

} else {

holder = (ViewHolder) convertView.getTag();

}

//清除当前显示区域中所有item的动画

for (int i=0;i<mListView.getChildCount();i++){

View view = mListView.getChildAt(i);

view.clearAnimation();

}

//然后给当前item添加上动画

if (isScrollDown) {

convertView.startAnimation(animation);

}

convertView.setTag(holder);

holder.mImageView.setImageDrawable(mDrawableList.get(position % mDrawableList.size()));

holder.mTextView.setText(position + "");

return convertView;

}

public class ViewHolder {

public ImageView mImageView;

public TextView mTextView;

}

}

到这里,有关listview中添加动画的部分就结束了,下面来看一下最终的效果图吧:



源码在文章底部给出

如果本文有帮到你,记得加关注哦

源码下载地址:http://download.csdn.net/detail/harvic880925/9474080

请大家尊重原创者版权,转载请标明出处: http://blog.csdn.net/harvic880925/article/details/50988685 谢谢
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: