最经常使用和最难用的控件java
因为手机屏幕空间有限,没法显示所有内容。当有大量数据须要展现的时候,借助列表控件。经过手指上下滑动,使得屏幕内外的数据不断进出。android
最基本的列表工做模式须要列表控件、数据源,列表控件可以进行交互和展现数据。可是列表控件不与数据源直接打交道,Adapter 接口充当桥梁,关联数据源与列表控件,加强可扩展性,适配不一样数据类型数据源。例如:ArrayAdapter 数组、CursorAdapter 游标。数据库
数据源可能来自:数组
ListView extends AdapterView extends ViewGroup.缓存
数据没法直接传递给 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 更新数据从新绘制视图。
避免在 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; }
ListView 即便加载成百上千条数据,依然不会发生 OOM 的缘由——RecycleBin 机制。
RecycleBin 类中存在两个重要的数组:
当 ListView 子项 View 进入屏幕可视区域时候,从 RecycleBin 的 mScrapViews 获取 View 做为 convertView 参数传递给 Adapter 的 getView()
方法。
ListView 有如 View 通常执行视图绘制流程 onMeasure()
、onLayout()
、onDraw()
。在 onLayout()
方法中会调用一个关键方法 layoutChildren()
,该方法由 ListView 具体实现进行子元素的布局,同时完成 ListView 对子项 View 的添加和删除操做。
layoutChildren()
方法主要逻辑:
detachAllViewsFromParent()
方法解除子项 View 与 ListView 之间的关联。fillDown()
方法将子 View 从指定的 position 自上而下填充 ListView,fillUp()
则相反自下而上进行填充。适配器继承自 RecyclerView.Adapter<>
,并将泛型指定为内部类 Adapter.ViewHolder。
重写一组父类方法:
onCreateViewHolder()
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; } }
固有的 ViewHolder 模式规范
RecyclerView.Adapter 默认采用 ViewHolder 模式,减小 findViewById()
方法获取控件实例的调用次数。
使用 LayoutManager 支持多种布局方式
RecyclerView 借助 LayoutManager 可以灵活地将列表控件放入不一样的容器(LinearLayout, GridLayout)。
ListView 布局只能实现纵向排列,而 RecyclerView 将排列工做 setLayoutManager()
交给 LayoutManager 布局排列接口,所以能够定制出不一样排列方式(横向、瀑布流布局)。
通知 Adapter 的数据变化更加灵活
不只 notifyDataSetChange()
方法,RecyclerView 能够使用 notifyItemRangeChanged()
等方法实现局部更新数据并重绘视图。
子项视图的动画效果更容易实现