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


    前言:宝剑锋从磨砺出,梅花香自苦寒来

    相关文章:

    《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是怎么布局的:

    <?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的代码位置了,完整的代码例如以下,然后再细讲:
    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;
        }
    }
    首先是构造函数:
    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函数:
    @Override
    public Object getItem(int position) {
        return mDrawableList.get(position % mDrawableList.size());
    }
    我们知道本身的BaseAdapter并没有使用getItem(int position)函数,重写这个函数是为了让我们在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) { //关键在这里哦 Drawable drawable = (Drawable) adapter.getItem(position); } });

    这里我们将当前位置所相应的图片Drawable传过去:
    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()函数
    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回收机制以后。这里最难的地方应该就是赋值的位置了:
    holder.mImageView.setImageDrawable(mDrawableList.get(position % mDrawableList.size()));
    holder.mTextView.setText(position+"");
    依据位置找到图片的drawable我们上面已经讲过了,这也就没什么难度了,就不再细讲了。

    3、主布局(main.xml)

    主布局非常easy,就仅仅有一个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>

    4、MyActivity.java

    最后是在MyActivity中构造ListAdapter并设置进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);
        }
    }
    这段代码非常easy了,就是先构造图片所相应的Drawable列表,然后构造ListAdapter实例。最后设置进Listview将其显示出来。没什么难度。也没什么好讲了。
    到这里,我们listview就构造完毕了。以下就看怎样向当中的item加入动画的环节了。


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

    1、动画文件(bottom_in_anim.xml)

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

    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中初始化的时候。载入动画:
    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開始动画非常easy,仅仅须要调用convertView.startAnimation(animation);就可以;这样就能够实如今构造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;
        }
    }
    效果图为:

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

    三、优化

    上面尽管攻克了进入时加入动画的问题,但细致的同学能够看出,在这个效果图中还存在几个问题。可能上面的效果图还看不清楚详细存在的问题
    1、假设上拉的时候。一下上拉了几个item,那些要显示的item会一起从底部出现
    2、在下拉的时候。上部出现的item也会应用上动画

    首先,解决第二个问题比較简单,仅仅须要推断当前手指是上滑还上下滑就能够了,仅仅有当手指向上滑的时候,才对底部出现的item加入上入场动画,其他时间不加入动画就可以
    第一个问题事实上也比較easy解决。我们能够通过listview.getChildCount()得到当前ListView中有多少个item。我们仅仅给最后一个加入动画就可以。其他的就直接显示就好了。

    1、上下滑动问题

    首先。我们首先解决上下滑动问题,这个问题事实上比較好解决,仅仅须要监听listview的OnScrollListener,依据它推断出当前ListView是上滑还是下滑就能够了。
    先看我们在onScrollListner中都有哪些參数:

    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的监听。
    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是不是已经向下移了
    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)。所以从这个图中,我们能够得到怎样计算在这样的情况下是否上移。

    @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:

    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;
    }

    2、多个item同一时候动画问题

    由于我们仅仅能在Item生成时给这个Item加入动画,所以要解决多个item同一时候移动的问题,我们仅仅能给最后一个Item加入动画,其他item不给他们加入。但我们怎么知道当前这个item是不是要显示的最后一个item呢?无法得各,所以一个中转方案是。在每一个item在加入动画前。都把当前显示区域内全部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();
        }
    
        //清除当前显示区域中全部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)的使用方法。说到它的參数position表示的是在当前屏幕显示区域中当前item的索引。所以在当前屏幕中第一个item的索引是0;而mListView.getChildCount()则表示当前屏幕显示区域中,总共同拥有多少个item。所以我们利用上面的代码。对屏幕显示区域中每一个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();
            }
    
            //清除当前显示区域中全部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 谢谢


  • 相关阅读:
    bzoj1901 Zju2112 Dynamic Rankings
    bzoj3932 [CQOI2015]任务查询系统
    poj2104 K-th Number
    splay模板整理
    bzoj1500 [NOI2005]维修数列
    bzoj3223 Tyvj 1729 文艺平衡树
    bzoj1503 [NOI2004]郁闷的出纳员
    bzoj3224 Tyvj 1728 普通平衡树
    用CSS截断字符串
    发布一款仿天猫产品放大镜JQuery插件
  • 原文地址:https://www.cnblogs.com/yjbjingcha/p/7228745.html
Copyright © 2020-2023  润新知