Android 初级面试者拾遗(前台界面篇)之 ListView 和 RecyclerView

ListView 和 RecyclerView

最经常使用和最难用的控件java

因为手机屏幕空间有限,没法显示所有内容。当有大量数据须要展现的时候,借助列表控件。经过手指上下滑动,使得屏幕内外的数据不断进出。android

最基本的列表工做模式须要列表控件数据源,列表控件可以进行交互和展现数据。可是列表控件不与数据源直接打交道,Adapter 接口充当桥梁,关联数据源与列表控件,加强可扩展性,适配不一样数据类型数据源。例如:ArrayAdapter 数组、CursorAdapter 游标。数据库

数据源可能来自:数组

  • 静态数据
  • 网络数据
  • 数据库

ListView

ListView extends AdapterView extends ViewGroup.缓存

  • Adapter 管理数据源
  • AdapterView 展现数据并处理交互

数据没法直接传递给 ListView,须要借助 setAdapter() 适配器来完成。例如 ArrayAdapter<> 泛型指定要适配的数据类型。网络

ListView listView = (ListView) findViewById(R.id.listview);
listView.setAdapter(adapter);

自定义适配器

适配数据源并重写一组父类方法:ide

构造函数:例如 ArrayAdapter 依次传入当前上下文、ListView 子项布局 id、数据源。函数

ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objects)

getView() 方法:用于每一个子项(单行)进入屏幕可视区域时候调用,根据数据源绘制子项布局。布局

程序示例:性能

public class MySimpleArrayAdapter extends ArrayAdapter<String> {
    private final Context context;
    private final String[] values;

    public MySimpleArrayAdapter(Context context, String[] values) {
        super(context, R.layout.rowlayout, values);
        this.context = context;
        this.values = values;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        LayoutInflater inflater = (LayoutInflater) context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View rowView = inflater.inflate(R.layout.rowlayout, parent, false);
        TextView textView = (TextView) rowView.findViewById(R.id.label);
        ImageView imageView = (ImageView) rowView.findViewById(R.id.icon);
        textView.setText(values[position]);
        // Change the icon for Windows and iPhone
        String s = values[position];
        if (s.startsWith("Windows7") || s.startsWith("iPhone")
                || s.startsWith("Solaris")) {
            imageView.setImageResource(R.drawable.no);
        } else {
            imageView.setImageResource(R.drawable.ok);
        }

        return rowView;
    }
}

其余经常使用方法:

getCount() 方法

返回适配器表示的数据源中一共有多少项数据。

notifyDataSetChanged() 方法

数据源的数据发生变化,通知 ListView 更新数据从新绘制视图。

提高 ListView 运行效率

避免在 Adapter 的 getView() 方法中从新加载布局(子项布局)

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

convertView 用于将加载好的布局进行缓存,根据 convertView 是否为空,判断可否重用布局,减小 LayoutInflater.inflate() 调用次数从而提高性能。

减小 findViewById() 方法获取控件实例的调用次数

经过内部类 ViewHolder 对控件实例进行缓存,调用 View 的 setTag() 方法,将 ViewHolder 对象存储在 View 中。

程序示例:

public class MyPerformanceArrayAdapter extends ArrayAdapter<String> {
    private final Activity context;
    private final String[] names;

    static class ViewHolder {
        public TextView text;
        public ImageView image;
    }

    public MyPerformanceArrayAdapter(Activity context, String[] names) {
        super(context, R.layout.rowlayout, names);
        this.context = context;
        this.names = names;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View rowView = convertView;
        // reuse views
        if (rowView == null) {
            LayoutInflater inflater = context.getLayoutInflater();
            rowView = inflater.inflate(R.layout.rowlayout, null);
            // configure view holder
            ViewHolder viewHolder = new ViewHolder();
            viewHolder.text = (TextView) rowView.findViewById(R.id.TextView01);
            viewHolder.image = (ImageView) rowView
                    .findViewById(R.id.ImageView01);
            rowView.setTag(viewHolder);
        }

        // fill data
        ViewHolder holder = (ViewHolder) rowView.getTag();
        String s = names[position];
        holder.text.setText(s);
        if (s.startsWith("Windows7") || s.startsWith("iPhone")
                || s.startsWith("Solaris")) {
            holder.image.setImageResource(R.drawable.no);
        } else {
            holder.image.setImageResource(R.drawable.ok);
        }

        return rowView;
    }
}

存在多种类型的子项布局的场景

基本实现方式:

  • 定义视图类型常量
  • 重写 getViewTypeCount() 方法和 getItemViewType(int position) 方法
  • 重写 getView() 方法

getViewTypeCount() 方法

返回一共有多少个不一样的视图类型(布局),这些视图将由 getView() 方法建立。

getItemViewType(int position) 方法

根据子项所处的位置判断具体类型并返回。

程序示例:

@Override
public int getViewTypeCount() {
  return 2;
}

@Override
public int getItemViewType(int position) {
  return (contactList.get(position).getContactType() == ContactType.CONTACT_WITH_IMAGE) ? 0 : 1;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
  View v = convertView;
  int type = getItemViewType(position);
  if (v == null) {
    // Inflate the layout according to the view type
   LayoutInflater inflater = (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   if (type == 0) {
     // Inflate the layout with image
     v = inflater.inflate(R.layout.image_contact_layout, parent, false);
  }
  else {
    v = inflater.inflate(R.layout.simple_contact_layout, parent, false);
  }
 }
 // fill data
 Contact c = contactList.get(position);

 TextView surname = (TextView) v.findViewById(R.id.surname);
 TextView name = (TextView) v.findViewById(R.id.name);
 TextView email = (TextView) v.findViewById(R.id.email);

 if (type == 0) {
   ImageView img = (ImageView) v.findViewById(R.id.img);
   img.setImageResource(c.imageId);
 }

 surname.setText(c.surname);
 name.setText(c.name);
 email.setText(c.email);

 return v;
}

android_listview_multiple_layout

ListView 的 RecycleBin 机制

ListView 即便加载成百上千条数据,依然不会发生 OOM 的缘由——RecycleBin 机制。

RecycleBin

RecycleBin 类中存在两个重要的数组:

  • mActiveViews 屏幕上可见的 View
  • mScrapViews 屏幕外不可见的 View

当 ListView 子项 View 进入屏幕可视区域时候,从 RecycleBin 的 mScrapViews 获取 View 做为 convertView 参数传递给 Adapter 的 getView() 方法。

ListView 有如 View 通常执行视图绘制流程 onMeasure()onLayout()onDraw()。在 onLayout() 方法中会调用一个关键方法 layoutChildren(),该方法由 ListView 具体实现进行子元素的布局,同时完成 ListView 对子项 View 的添加和删除操做。

layoutChildren() 方法主要逻辑:

  1. 若 Adapter 中的数据集发生变化,则将 ListView 中的全部子项 View 放到 RecycleBin 中的 mScrapViews 废弃 View 集合。
    若 Adapter 中的数据集无变化,则将 ListView 中的全部子项 View 放到 RecycleBin 中的 mActiveViews 激活 View 集合。
  2. 调用 detachAllViewsFromParent() 方法解除子项 View 与 ListView 之间的关联。
  3. 从新将子项 View 添加到 ListView 中。根据 mLayoutMode 判断如何进行添加,fillDown() 方法将子 View 从指定的 position 自上而下填充 ListView,fillUp() 则相反自下而上进行填充。

RecyclerView

自定义适配器

适配器继承自 RecyclerView.Adapter<>,并将泛型指定为内部类 Adapter.ViewHolder。

重写一组父类方法:

  • onCreateViewHolder()
    加载子项布局(LayoutInflater inflate()),建立 ViewHolder 实例。
  • onBindViewHolder()
    用于每一个子项(单行)进入屏幕可视区域时候调用,根据数据源位置绘制子项布局。
  • getItemCount()
    返回数据源的长度

程序示例:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    private String[] mDataset;

    // Provide a reference to the views for each data item
    // Complex data items may need more than one view per item, and
    // you provide access to all the views for a data item in a view holder
    public static class MyViewHolder extends RecyclerView.ViewHolder {
        // each data item is just a string in this case
        public TextView mTextView;
        public MyViewHolder(TextView v) {
            super(v);
            mTextView = v;
        }
    }

    // Provide a suitable constructor (depends on the kind of dataset)
    public MyAdapter(String[] myDataset) {
        mDataset = myDataset;
    }

    // Create new views (invoked by the layout manager)
    @Override
    public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent,
                                                   int viewType) {
        // create a new view
        TextView v = (TextView) LayoutInflater.from(parent.getContext())
                .inflate(R.layout.my_text_view, parent, false);
        ...
        MyViewHolder vh = new MyViewHolder(v);
        return vh;
    }

    // Replace the contents of a view (invoked by the layout manager)
    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        // - get element from your dataset at this position
        // - replace the contents of the view with that element
        holder.mTextView.setText(mDataset[position]);

    }

    // Return the size of your dataset (invoked by the layout manager)
    @Override
    public int getItemCount() {
        return mDataset.length;
    }
}

RecyclerView vs ListView

固有的 ViewHolder 模式规范

RecyclerView.Adapter 默认采用 ViewHolder 模式,减小 findViewById() 方法获取控件实例的调用次数。

使用 LayoutManager 支持多种布局方式

RecyclerView 借助 LayoutManager 可以灵活地将列表控件放入不一样的容器(LinearLayout, GridLayout)。

ListView 布局只能实现纵向排列,而 RecyclerView 将排列工做 setLayoutManager() 交给 LayoutManager 布局排列接口,所以能够定制出不一样排列方式(横向、瀑布流布局)。

通知 Adapter 的数据变化更加灵活

不只 notifyDataSetChange() 方法,RecyclerView 能够使用 notifyItemRangeChanged() 等方法实现局部更新数据并重绘视图。

子项视图的动画效果更容易实现

  • RecyclerView.ItemAnimator
  • RecyclerView.ItemDecoration
相关文章
相关标签/搜索