最經常使用和最難用的控件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()
等方法實現局部更新數據並重繪視圖。
子項視圖的動畫效果更容易實現