Android的viewHolder模式

标签: android viewholder 模式 | 发表时间:2015-06-02 08:00 | 作者:
出处:http://hellosure.github.io

深入浅出(这部分总结的非常好)

ListView之BaseAdapter的基本使用以及ViewHolder模式。

话说开发用了各种Adapter之后感觉用的最舒服的还是BaseAdapter,尽管使用起来比其他适配器有些麻烦,但是使用它却能实现很多自己喜欢的列表布局,比如ListView、GridView、Gallery、Spinner等等。它是直接继承自接口类Adapter的,使用BaseAdapter时需要重写很多方法,其中最重要的当属getView,因为这会涉及到ListView优化等问题,其他的方法可以参考链接的文章

BaseAdapter与其他Adapter有些不一样,其他的Adapter可以直接在其构造方法中进行数据的设置,比如

   SimpleAdapter adapter = new SimpleAdapter(this, getData(), R.layout.list_item, new String[]{"img","title","info",new int[]{R.id.img, R.id.title, R.id.info}});

但是在BaseAdapter中需要实现一个继承自BaseAdapter的类,并且重写里面的很多方法,例如

   class MyAdapter extends BaseAdapter
    {
        private Context context;
        public MyAdapter(Context context)
        {
            this.context = context;
        }
        @Override
        public int getCount() {
            // How many items are in the data set represented by this Adapter.(在此适配器中所代表的数据集中的条目数)
            return 0;
        }
        @Override
        public Object getItem(int position) {
            // Get the data item associated with the specified position in the data set.(获取数据集中与指定索引对应的数据项)
            return null;
        }
        @Override
        public long getItemId(int position) {
            // Get the row id associated with the specified position in the list.(取在列表中与指定索引对应的行id)
            return 0;
        }
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            // Get a View that displays the data at the specified position in the data set.
            return null;
        } 
    }

这里面没什么难度,但是这个getView方法必须好好处理,也是最麻烦的

第一种:没有任何处理,不建议这样写。如果数据量少看将就,但是如果列表项数据量很大的时候,会每次都重新创建View,设置资源,严重影响性能,所以从一开始就不要用这种方式

   @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View item = mInflater.inflate(R.layout.list_item, null);
            ImageView img = (ImageView)item.findViewById(R.id.img)
            TextView title = (TextView)item.findViewById(R.id.title);
            TextView info = (TextView)item.findViewById(R.id.info);
            img.setImageResource(R.drawable.ic_launcher);
            title.setText("Hello");
            info.setText("world");
                                                                                                                         
            return item;
        }

第二种ListView优化:通过缓存convertView,这种利用缓存contentView的方式可以判断如果缓存中不存在View才创建View,如果已经存在可以利用缓存中的View,提升了性能

   public View getView(int position, View convertView, ViewGroup parent) {
            if(convertView == null)
            {
                convertView = mInflater.inflate(R.layout.list_item, null);
            }
                                                                                             
            ImageView img = (ImageView)convertView.findViewById(R.id.img)
            TextView title = (TextView)convertView.findViewById(R.id.title);
            TextView info = (TextView)ConvertView.findViewById(R.id.info);
            img.setImageResource(R.drawable.ic_launcher);
            title.setText("Hello");
            info.setText("world");
                                                                                             
            return convertView;
        }

第三种ListView优化:通过convertView+ViewHolder来实现,ViewHolder就是一个静态类,使用 ViewHolder 的关键好处是缓存了显示数据的视图(View),加快了 UI 的响应速度。

当我们判断 convertView == null 的时候,如果为空,就会根据设计好的List的Item布局(XML),来为convertView赋值,并生成一个viewHolder来绑定converView里面的各个View控件(XML布局里面的那些控件)。再用convertView的setTag将viewHolder设置到Tag中,以便系统第二次绘制ListView时从Tag中取出。(看下面代码中)

如果convertView不为空的时候,就会直接用convertView的getTag(),来获得一个ViewHolder。

   //在外面先定义,ViewHolder静态类
static class ViewHolder
{
    public ImageView img;
    public TextView title;
    public TextView info;
}
//然后重写getView
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder;
    if(convertView == null)
    {
        holder = new ViewHolder();
        convertView = mInflater.inflate(R.layout.list_item, null);
        holder.img = (ImageView)item.findViewById(R.id.img)
        holder.title = (TextView)item.findViewById(R.id.title);
        holder.info = (TextView)item.findViewById(R.id.info);
        convertView.setTag(holder);
    }else
    {
        holder = (ViewHolder)convertView.getTag();
    }
        holder.img.setImageResource(R.drawable.ic_launcher);
        holder.title.setText("Hello");
        holder.info.setText("World");
    }
                                                                                                 
    return convertView;
}

到这里,可能会有人问ViewHolder静态类结合缓存convertView与直接使用convertView有什么区别吗,是否重复了?

在这里,官方给出了解释

提升Adapter的两种方法

  To work efficiently the adapter implemented here uses two techniques:
-It reuses the convertView passed to getView() to avoid inflating View when it is not necessary

(译:重用缓存convertView传递给getView()方法来避免填充不必要的视图)

-It uses the ViewHolder pattern to avoid calling findViewById() when it is not necessary

(译:使用ViewHolder模式来避免没有必要的调用findViewById():因为太多的findViewById也会影响性能)

ViewHolder类的作用

-The ViewHolder pattern consists in storing a data structure in the tag of the view returned by getView().This data structures contains references to the views we want to bind data to, thus avoiding calling to findViewById() every time getView() is invoked

(译:ViewHolder模式通过getView()方法返回的视图的标签(Tag)中存储一个数据结构,这个数据结构包含了指向我们要绑定数据的视图的引用,从而避免每次调用getView()的时候调用findViewById())

实例:用BaseAdapter来自定义ListView布局 main.xml

   <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <ListView
        android:id="@+id/lv"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:fastScrollEnabled="true"
        />
</LinearLayout>

list_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="match_parent"
    android:orientation="horizontal" >
    <ImageView
        android:id="@+id/img"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        >
        <TextView
            android:id="@+id/tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20sp"
        />
        <TextView
            android:id="@+id/info"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="14sp"
            />
    </LinearLayout>
                                                             
</LinearLayout>

Activity

   public class Demo17Activity extends Activity {
    private ListView lv;
    private List<Map<String, Object>> data;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        lv = (ListView)findViewById(R.id.lv);
        //获取将要绑定的数据设置到data中
        data = getData();
        MyAdapter adapter = new MyAdapter(this);
        lv.setAdapter(adapter);
    }
                                                     
    private List<Map<String, Object>> getData()
    {
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
        Map<String, Object> map;
        for(int i=0;i<10;i++)
        {
            map = new HashMap<String, Object>();
            map.put("img", R.drawable.ic_launcher);
            map.put("title", "跆拳道");
            map.put("info", "快乐源于生活...");
            list.add(map);
        }
        return list;
    }
                                                     
    //ViewHolder静态类
    static class ViewHolder
    {
        public ImageView img;
        public TextView title;
        public TextView info;
    }
                                                     
    public class MyAdapter extends BaseAdapter
    {   
        private LayoutInflater mInflater = null;
        private MyAdapter(Context context)
        {
            //根据context上下文加载布局,这里的是Demo17Activity本身,即this
            this.mInflater = LayoutInflater.from(context);
        }
        @Override
        public int getCount() {
            //How many items are in the data set represented by this Adapter.
            //在此适配器中所代表的数据集中的条目数
            return data.size();
        }
        @Override
        public Object getItem(int position) {
            // Get the data item associated with the specified position in the data set.
            //获取数据集中与指定索引对应的数据项
            return position;
        }
        @Override
        public long getItemId(int position) {
            //Get the row id associated with the specified position in the list.
            //获取在列表中与指定索引对应的行id
            return position;
        }
                                                         
        //Get a View that displays the data at the specified position in the data set.
        //获取一个在数据集中指定索引的视图来显示数据
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder = null;
            //如果缓存convertView为空,则需要创建View
            if(convertView == null)
            {
                holder = new ViewHolder();
                //根据自定义的Item布局加载布局
                convertView = mInflater.inflate(R.layout.list_item, null);
                holder.img = (ImageView)convertView.findViewById(R.id.img);
                holder.title = (TextView)convertView.findViewById(R.id.tv);
                holder.info = (TextView)convertView.findViewById(R.id.info);
                //将设置好的布局保存到缓存中,并将其设置在Tag里,以便后面方便取出Tag
                convertView.setTag(holder);
            }else
            {
                holder = (ViewHolder)convertView.getTag();
            }
            holder.img.setBackgroundResource((Integer)data.get(position).get("img"));
            holder.title.setText((String)data.get(position).get("title"));
            holder.info.setText((String)data.get(position).get("info"));
                                                             
            return convertView;
        }
                                                         
    }
}

目的

减少findViewById()使用,如果不用viewholder的话,So if you have 1000 rows in List and 990 rows will be out of View then 990 times will be called findViewById() again.

Holder design pattern is used for View caching - Holder (arbitrary) object holds child widgets of each row and when row is out of View then findViewById() won’t be called but View will be recycled and widgets will be obtained from Holder.

定义为static

静态内部类不需要持有外部对象的引用,我理解这个好处和holder也最好为static内部类差不多,不然有可能造成外部对象的内存泄露。

viewHolder的一种简洁写法

   ViewHolder holder = null;  
        if(convertView == null){  
                convertView = mInflater.inflate(R.layout.xxx null);  
                holder = new ViewHolder();   
                holder.tvXXX = (TextView)findViewById(R.id.xxx);  
                //...一连串的findViewById  
        }  
        else{  
                holder = (ViewHolder) convertView.getTag();    
        }  
           
           
           
        private static class ViewHolder{  
                TextView tvXXX;  
                //很多view的定义  
        }

这么写一次还行,但问题是总有很多很多的ViewAdapter要这么写,每次都repeat,repeat,repeat累啊。所以,有这么一种简洁的写法分享给大家,先声明,从国外网站上看的,不是自己原创的,但确实很喜欢这个简洁的设计。

ViewHolder这么写(只提供一个静态方法,其实可以加一个私有构造函数防止外部实例化),代码很简单,看过就明白了

   public class ViewHolder {  
    // I added a generic return type to reduce the casting noise in client code  
    @SuppressWarnings("unchecked")  
    public static <T extends View> T get(View view, int id) {  
        SparseArray<View> viewHolder = (SparseArray<View>) view.getTag();  
        if (viewHolder == null) {  
            viewHolder = new SparseArray<View>();  
            view.setTag(viewHolder);  
        }  
        View childView = viewHolder.get(id);  
        if (childView == null) {  
            childView = view.findViewById(id);  
            viewHolder.put(id, childView);  
        }  
        return (T) childView;  
    }  
}

在getView里这样

   @Override  
public View getView(int position, View convertView, ViewGroup parent) {  
   
    if (convertView == null) {  
        convertView = LayoutInflater.from(context)  
          .inflate(R.layout.banana_phone, parent, false);  
    }  
   
    ImageView bananaView = ViewHolder.get(convertView, R.id.banana);  
    TextView phoneView = ViewHolder.get(convertView, R.id.phone);  
   
    BananaPhone bananaPhone = getItem(position);  
    phoneView.setText(bananaPhone.getPhone());  
    bananaView.setImageResource(bananaPhone.getBanana());  
   
    return convertView;  
}

RecyclerView.ViewHolder

提供了非常简便的写法,不用去判断是否需要使用viewholder,recyclerview会自动判断。

   @Override 
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
   View root = LayoutInflater.from(mContext).inflate(myLayout, parent, false);
   return new ViewHolder(root);
}

@Override public void onBindViewHolder(ViewHolder holder, int position) {
   Item item = mItems.get(position);
   holder.title.setText(item.getTitle());
}

@Override public int getItemCount() {
   return mItems != null ? mItems.size() : 0;
}

-EOF-

相关 [android viewholder 模式] 推荐:

Android的viewHolder模式

- - HelloSure
深入浅出(这部分总结的非常好). ListView之BaseAdapter的基本使用以及ViewHolder模式. 话说开发用了各种Adapter之后感觉用的最舒服的还是BaseAdapter,尽管使用起来比其他适配器有些麻烦,但是使用它却能实现很多自己喜欢的列表布局,比如ListView、GridView、Gallery、Spinner等等.

Android应用盈利模式的转变

- 高春辉 - XJP的碎碎念
赚钱始终是应用开发者最关注的问题,iOS成功打造了“AppStore – iPhone – Users”的产业模式,让无数个人开发者获得可观收入,更捧红了Angry Bird、Fruit Slice这样的明星级应用,创造了一个全新的行业标准. 在Google Android成功突破iOS封锁获得市场青睐后,怎样让应用开发者获得合理回报是需要思考的问题.

Android 之 Activity的加载模式

- - 移动开发 - ITeye博客
         加载模式决定以哪种方式启动一个跳转到原来某个Activity实例;.         在Android 开发中,activity 之间的跳转可能需要有多种方式,有时是普通的生成一个新实例,有时希望跳转到原来某个activity实例,而不是生成大量的重复的activity;. 3)  加载模式的种类:.

浅学设计模式之观察者模式及在android中的应用

- - CSDN博客推荐文章
        最近在学习下设计模式,而加深学习的不错的方法就是把心得写出来吧. 现在自己看的书是《head.Frist设计模式》这本书.         观察者模式(有时又被称为发布-订阅模式、模型-视图模式、源-收听者模式或从属者模式)是 软件设计模式的一种.

Android系统免费授权模式让谷歌陷困境

- - 行业资讯
  1月24日消息,据国外媒体报道,一些公司纷纷仿效亚马逊的做法,在推出的设备中使用谷歌Android操作系统,但删除系统中预装的各种应用,这对谷歌的广告营收带来了影响.   谷歌在2007年推出其Android操作系统后,对该系统的经营战略很简单:给予开发者免费使用,通过消费者点击网络或应用上的广告来获得收入.

Android盈利新模式:移动应用分销平台StartApp

- - 移动App
将搜索服务与Android应用结合起来,让开发商即使是推出免费应用也可以通过被下载而赚钱. StartApp公司今日宣布,其近期推出的安卓应用货币化及销售平台组件已被450余个手机应用安装,自身下载次数已过千万. StartApp于今年9月发布,它将搜索服务与Android应用结合起来,让免费应用也可以通过被下载而赚钱.

Android下的Activity的四种启动模式

- - CSDN博客推荐文章
一:Standard的启动模式.  Standard是默认的模式每开始一个activity,就会在栈中加一个activity,相同的也会加,. 所以加多少个,就要按多少次返回键才能回到最初的界面. 二:singleTop的启动模式.     Singletop:如果任务栈的栈顶已经存在这个activity的实例,.

ViBe 讓Android手機也有 iPhone iOS5 來電自定震動模式

- 雪 - 電腦玩物
前一陣子的iOS 5系統更新,除了我在「iOS 5 更新之後,對我來說的10個最佳新功能與使用心得」裡提到的一些改變外,在iPhone上也新增了一個「自定震動模式」功能,可以為每個不同聯絡人自訂不同的來電震動感應,例如來自情人的電話是心跳般的震動,而來自公司的電話是急促的震動. 這樣的小功能,除了好玩之外,也可以增加接聽來電前的辨認速度(尤其針對重點來電),而像是不方便開啟手機鈴聲時就可以透過特殊震動來認出重要電話,對於聽力不好的朋友來說不同震動模式也有助於更快辨認來電.

Android应用赚钱新模式:1亿App下载=430万美元

- - ITeye资讯频道
StartApp 是一款移动应用分销平台,它将搜索服务与Android应用结合起来, 让开发者即使是推出免费应用也可以通过被下载而赚钱. 目前它已经帮Android应用开发者赚得了430万美元. StartApp开发了自己的搜索引擎,移动应用开发者只需把应用跟这个引擎绑定,当用户通过该搜索引擎点击应用时,就是开发者赚钱的时候.

Android 遥控车

- CasparZ - LinuxTOY
您确定您真的会用 Android 手机玩赛车. 16 岁的法国学生 Jonathan Rico 使用 Android 手机通过蓝牙实现了对改装玩具汽车的遥控. 操控的方式和那些标榜的智能手机游戏一样,使用重力感应,差别是这次控制的是现实世界中的遥控汽车. 收藏到 del.icio.us |.