资讯专栏INFORMATION COLUMN

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

MartinDai / 2957人阅读

摘要:方法根据子项所处的位置判断具体类型并返回。调用方法解除子项与之间的关联。自定义适配器适配器继承自,并将泛型指定为内部类。使用支持多种布局方式借助能够灵活地将列表控件放入不同的容器。

ListView 和 RecyclerView

最常用和最难用的控件

由于手机屏幕空间有限,无法显示全部内容。当有大量数据需要展示的时候,借助列表控件。通过手指上下滑动,使得屏幕内外的数据不断进出。

最基本的列表工作模式需要列表控件数据源,列表控件能够进行交互和展示数据。但是列表控件不与数据源直接打交道,Adapter 接口充当桥梁,关联数据源与列表控件,增强可扩展性,适配不同数据类型数据源。例如:ArrayAdapter 数组、CursorAdapter 游标。

数据源可能来自:

静态数据

网络数据

数据库

ListView

ListView extends AdapterView extends ViewGroup.

Adapter 管理数据源

AdapterView 展示数据并处理交互

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

ListView listView = (ListView) findViewById(R.id.listview);
listView.setAdapter(adapter);
自定义适配器

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

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

ArrayAdapter(Context context, int resource, int textViewResourceId, List objects)

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

程序示例:

public class MySimpleArrayAdapter extends ArrayAdapter {
    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 {
    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;
}

ListView 的 RecycleBin 机制

ListView 即使加载成百上千条数据,依然不会发生 OOM 的原因——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() 方法主要逻辑:

若 Adapter 中的数据集发生变化,则将 ListView 中的所有子项 View 放到 RecycleBin 中的 mScrapViews 废弃 View 集合。
若 Adapter 中的数据集无变化,则将 ListView 中的所有子项 View 放到 RecycleBin 中的 mActiveViews 激活 View 集合。

调用 detachAllViewsFromParent() 方法解除子项 View 与 ListView 之间的关联。

重新将子项 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 {
    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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/72020.html

相关文章

  • Android 初级面试拾遗前台界面 ListView RecyclerView

    摘要:方法根据子项所处的位置判断具体类型并返回。调用方法解除子项与之间的关联。自定义适配器适配器继承自,并将泛型指定为内部类。使用支持多种布局方式借助能够灵活地将列表控件放入不同的容器。 ListView 和 RecyclerView 最常用和最难用的控件 由于手机屏幕空间有限,无法显示全部内容。当有大量数据需要展示的时候,借助列表控件。通过手指上下滑动,使得屏幕内外的数据不断进出。 最基本...

    wangzy2019 评论0 收藏0
  • Android 初级面试拾遗前台界面 Activity Fragment

    摘要:四种状态运行状态暂停状态停止状态销毁状态运行状态处于返回栈的栈顶位置,正在运行与用户发生着交互,系统不愿回收此种状态的。和都是为了解决重复创建问题,的作用域是栈顶,的作用域是整个返回栈。 Context Android 系统组件不同于普通类对象,能够直接创建实例,需要各自的上下文环境——Context。 Context 上下文环境确保 Android 系统组件(Activity、Se...

    jone5679 评论0 收藏0
  • Android 初级面试拾遗前台界面 Activity Fragment

    摘要:四种状态运行状态暂停状态停止状态销毁状态运行状态处于返回栈的栈顶位置,正在运行与用户发生着交互,系统不愿回收此种状态的。和都是为了解决重复创建问题,的作用域是栈顶,的作用域是整个返回栈。 Context Android 系统组件不同于普通类对象,能够直接创建实例,需要各自的上下文环境——Context。 Context 上下文环境确保 Android 系统组件(Activity、Se...

    BlackHole1 评论0 收藏0
  • Android 初级面试拾遗前台界面 View ViewGroup

    摘要:层面中可以通过方法拦截事件传递,返回代表同一事件列不再向下传递给子,返回代表事件继续传递,默认返回。同时注册两者事件传递顺序,方法将会先于方法执行,并且方法可能执行多次事件。如此反复执行初始化布局绘制过程容易造成性能问题。 View 和 ViewGroup View 是 Android 中最基本的 UI 组件,在屏幕上绘制一块矩形区域。 ViewGroup 是一种特殊的 View,它...

    sugarmo 评论0 收藏0
  • Android 初级面试拾遗前台界面 View ViewGroup

    摘要:层面中可以通过方法拦截事件传递,返回代表同一事件列不再向下传递给子,返回代表事件继续传递,默认返回。同时注册两者事件传递顺序,方法将会先于方法执行,并且方法可能执行多次事件。如此反复执行初始化布局绘制过程容易造成性能问题。 View 和 ViewGroup View 是 Android 中最基本的 UI 组件,在屏幕上绘制一块矩形区域。 ViewGroup 是一种特殊的 View,它...

    malakashi 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<