前面兩篇我們講解了使用layoutAnimation和LayoutTransition實現(xiàn)ViewGroup中Item加載動畫的方法,但他們都各自存在問題:
layoutAnimation雖然是API 1中就已經(jīng)引入,但只能在動畫初次創(chuàng)建時才能使用指定動畫??丶?chuàng)建以后,再往ViewGroup里加Item就不會再有動畫。這顯然是不合適的!
LayoutTransition能夠?qū)崿F(xiàn)無論何時往ViewGroup中添加控件都可以給其中控件使用動畫。但最大的問題是,它的API等級是11。而且也沒有兼容包可供我們使用這個函數(shù)。
這樣問題就來了,如果我們想在兼容API 8以上的機型,完成ListView中各個Item進入時都添加動畫,這要怎么來做呢?
今天我們要完成的效果圖如下:
http://wiki.jikexueyuan.com/project/android-animation/images/20160327211048457.gif" alt="" />
從效果圖中可以看到,當每個Item進入的時候,都添加了動畫。前面我們說了layoutAnimation和LayoutTransition所存在的問題,那拋開這兩個函數(shù),我們要如何實現(xiàn)Item進入動畫呢?
別忘了,ListView在得到每個Item時會調(diào)用BaseAdapter的getView方法!getView中每一個convertView就是當前要顯示的Item所對應(yīng)的View,所以我們直接對convertView添加動畫不就好了。
上面的原理理解起來并不難,下面我們就看看如何實現(xiàn)的吧。
這部分,我們主要是先搭出來要實現(xiàn)的框架,把ListView填充起來,效果如下:
http://wiki.jikexueyuan.com/project/android-animation/images/20160327211257367.gif" alt="" />
這里實現(xiàn)的效果就是把listview填充起來,總共用了九張圖片,listview列表循環(huán)顯示這九張圖片,但我在每個圖片上顯示了當前item所在的位置。
好了,下面就來看代碼吧
我們先來看看listview的Item是怎么布局的:
<?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所在的位置。
這里就是ListView的Adapter的代碼位置了,完整的代碼如下,然后再細講:
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;
}
}
首先是構(gòu)造函數(shù):
public ListAdapter(Context context, ListView listView, List<Drawable> drawables, int length) {
mDrawableList.addAll(drawables);
mLength = length;
mInflater = LayoutInflater.from(context);
mContext = context;
mListView = listView;
}
首先是傳進來的幾個參數(shù),List
然后是getItem函數(shù):
@Override
public Object getItem(int position) {
return mDrawableList.get(position % mDrawableList.size());
}
我們知道本身的BaseAdapter并沒有使用getItem(int position)函數(shù),重寫這個函數(shù)是為了讓我們在BaseAdapter實例中,可以通過getItem來獲取我們想要的實例(類似下面這樣):
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) {
//關(guān)鍵在這里哦
Drawable drawable = (Drawable) adapter.getItem(position);
}
});
這里我們將當前位置所對應(yīng)的圖片Drawable傳過去:
public Object getItem(int position) {
return mDrawableList.get(position % mDrawableList.size());
}
大家可能會疑問:為什么要用position % mDrawableList.size()來得到當前圖片在圖片列表中的索引?因為我們是循環(huán)顯示的圖片的,比如我們總共有九張圖片,那當前是第12個item時,顯示的應(yīng)當是第3張圖了,position的值為12(因為Adapter的position是從0開始的),所以12%9 = 3;這就對上了。理解不了的同學,多拿幾個數(shù)來算算,比如當顯示到第15張時,position是多少,對應(yīng)的圖片應(yīng)該是哪一張呢?
最后getView()函數(shù)
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);的方式來重復(fù)使用convertView,可以參考這篇文章《BaseAdapter——convertView回收機制與動態(tài)控件響應(yīng)》 在理解了convertview回收機制以后,這里最難的地方應(yīng)該就是賦值的位置了:
holder.mImageView.setImageDrawable(mDrawableList.get(position % mDrawableList.size()));
holder.mTextView.setText(position+"");
根據(jù)位置找到圖片的drawable我們上面已經(jīng)講過了,這也就沒什么難度了,就不再細講了。
主布局非常簡單,就只有一個ListView控件:
<?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>
最后是在MyActivity中構(gòu)造ListAdapter并設(shè)置進listview的過程了:
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);
}
}
這段代碼很簡單了,就是先構(gòu)造圖片所對應(yīng)的Drawable列表,然后構(gòu)造ListAdapter實例,最后設(shè)置進Listview將其顯示出來,沒什么難度,也沒什么好講了。
到這里,我們listview就構(gòu)造完成了。下面就看如何向其中的item添加動畫的環(huán)節(jié)了。
先定義從底部進入的動畫:
<?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;
首先,我們在ListAdapter中初始化的時候,加載動畫:
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添加上動畫
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開始動畫非常簡單,只需要調(diào)用convertView.startAnimation(animation);即可;這樣就可以實現(xiàn)在構(gòu)造item的時候就開始動畫
ListAdapter完整代碼如下
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;
}
}
效果圖為:
http://wiki.jikexueyuan.com/project/android-animation/images/20160327212235020.gif" alt="" />
從效果圖中可以看出,我們初步實現(xiàn)了在item生成的時候添加進入動畫
上面雖然解決了進入時添加動畫的問題,但仔細的同學可以看出,在這個效果圖中還存在幾個問題,可能上面的效果圖還看不清楚具體存在的問題
首先,解決第二個問題比較簡單,只需要判斷當前手指是上滑還上下滑就可以了,只有當手指向上滑的時候,才對底部出現(xiàn)的item添加上入場動畫,其它時間不添加動畫即可 第一個問題其實也比較容易解決,我們可以通過listview.getChildCount()得到當前ListView中有多少個item。我們只給最后一個添加動畫即可。其它的就直接顯示就好了。
首先,我們首先解決上下滑動問題,這個問題其實比較好解決,只需要監(jiān)聽listview的OnScrollListener,根據(jù)它判斷出當前ListView是上滑還是下滑就可以了。
先看我們在onScrollListner中都有哪些參數(shù):
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) {
}
};
我們主要關(guān)注onScroll的監(jiān)聽,
onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
有四個參數(shù):
在理解了上面四個參數(shù)以后,我們再來看看下移的情況;
向下移動包括兩種情況:
第一:屏幕中第一個item或前幾個item一起移出屏幕,在這種情況下,我們只需要判斷firstVisibleItem是否比上次的值大即可。即第一個顯示的item是不是已經(jīng)向下移了
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
/**
* firstVisibleItem > mFirstPosition表示向下滑動一個或多個Item
*/
isScrollDown = firstVisibleItem > mFirstPosition;
mFirstPosition = firstVisibleItem;
}
第二:可能用戶并沒有一次性移一整條item,而是僅讓當前item向上移了一點點。這里,由于當前可見的第一個item的位置仍然是firstVisibleItem,只是它的top值變了。
如下圖:
http://wiki.jikexueyuan.com/project/android-animation/images/20160327212539122.png" alt="" />
在這個效果圖中,只是將第一個item向上移動了一點點,第一個item左上角的坐標從(0,-100)變成了(0,-200);所以從這個圖中,我們可以得到如何計算在這種情況下是否上移。
@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)函數(shù)得到的。我們上面已經(jīng)講到,ListView.getChildAt(int position)中參數(shù)position表示的是當前item所在屏幕顯示區(qū)域中的索引,屏幕中第一個item的索引是0 所以我們將這兩種情況進行合并,得到完整的onScrollListener:
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的時候,判斷如果是向下滑動,就添加動畫
@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;
}
由于我們只能在Item生成時給這個Item添加動畫,所以要解決多個item同時移動的問題,我們只能給最后一個Item添加動畫,其它item不給他們添加;但我們怎么知道當前這個item是不是要顯示的最后一個item呢?無法得各,所以一個中轉(zhuǎn)方案是,在每一個item在添加動畫前,都把當前顯示區(qū)域內(nèi)所有item動畫給取消,然后給當前convertView添加上動畫;當listview滾動到最后一個Item的時候,自然,同樣也是先把所有動畫取消,然后給他自己添加上動畫,所以這樣看起來就好像是只給他自己添加了動畫,之前滾動的item是沒有動畫的。 代碼如下:
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();
}
//清除當前顯示區(qū)域中所有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;
}
在這段代碼中,相比上面的代碼,我們只添加了一段語句:
for (int i=0;i<mListView.getChildCount();i++){
View view = mListView.getChildAt(i);
view.clearAnimation();
}
上面我們講了mListView.getChildAt(int position)的用法,說到它的參數(shù)position表示的是在當前屏幕顯示區(qū)域中當前item的索引。所以在當前屏幕中第一個item的索引是0;而mListView.getChildCount()則表示當前屏幕顯示區(qū)域中,總共有多少個item。所以我們利用上面的代碼,對屏幕顯示區(qū)域中每個item進行索引,然后取消他們的動畫即可。
此時ListAdapter完整的代碼為:
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();
}
//清除當前顯示區(qū)域中所有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;
}
}
到這里,有關(guān)listview中添加動畫的部分就結(jié)束了,下面來看一下最終的效果圖吧:
http://wiki.jikexueyuan.com/project/android-animation/images/20160327213050624.gif" alt="" />
源碼在文章底部給出