2017年2月11日 星期六

ListView 與 RecyclerView 解決滑動問題 (Solve scrolling problems)

在寫Android 程式中,不管做清單還是客製化List,都會想要紀錄所選到的值,滑動的過程中在Android 的緩存機制裡,會進行回收至緩存即從緩存中讀取(這邊不細談),所以導致在回收的過程中可能點選位置1後,往下滑卻發現位置12也勾選起來了!我想大部分的人都有遇過這些問題,問題就是控制項沒有初始化以及對控制的位置儲存值,以上述來看,位置12出現的過程中是復用位置1,所以位置12的初始值就會跟位置1一樣,講了這麼多,就來看看如何為這些位置儲存。
最下面有程式碼下載分享

1.初始化控制項:創建一個Model class 來記住值
我客製化的Listview 有 TextView 及 CheckBox

Model.java 

public class ModelBean {

 private String name;
 private boolean selected;

 public ModelBean(String name) {
 this.name = name;
 }
 
 public String getName() {
 return name;
 }

 public boolean isSelected() {
 return selected;
 }

 public void setSelected(boolean selected) {
 this.selected = selected;
 }

2.實作

RecyclerView 的作法

MainActivity.java

public class MainActivity extends Activity{


public ListView list_service;
private RecyclerView mRecyclerView;
private RecyclerView.LayoutManager mLayoutManager;

@Override
protected void onCreate(Bundle savedInstanceState) {
mRecyclerView = (RecyclerView)findViewById(R.id.recycle_list);
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
RecycleAdapter adapter = new RecycleAdapter(getContext(),getModel());
mRecyclerView.setAdapter(adapter);
}

private List<ModelBean> getModel() {
 list.add(new ModelBean("Linux"));
 list.add(new ModelBean("Windows7"));
 list.add(new ModelBean("Suse"));
 list.add(new ModelBean("Eclipse"));
 list.add(new ModelBean("Ubuntu"));
 list.add(new ModelBean("Solaris"));
 list.add(new ModelBean("Android"));
 list.add(new ModelBean("iPhone"));
 list.add(new ModelBean("Java"));
 list.add(new ModelBean(".Net"));
 list.add(new ModelBean("PHP"));
 list.add(new ModelBean("ios"));
 return list;
}

 private class RecycleAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
 private List<ModelBean> mlist;
 private LayoutInflater mLayoutInflater;

 public RecycleAdapter(Context context, List<ModelBean> list) {
 this.mlist = list;
 mLayoutInflater = LayoutInflater.from(context);
 }

 public class ViewHolder0 extends RecyclerView.ViewHolder {
 TextView tv_content;
 CheckBox cb;
 public ViewHolder0(View itemView) {
 super(itemView);
 tv_content = (TextView)itemView.findViewById(R.id.service_content);
 cb = (CheckBox)itemView.findViewById(R.id.cb_service);
  }
 }

 @Override
 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
 if(viewType == 0){
 View v = mLayoutInflater.inflate(R.layout.service_list, parent, false);
 return new ViewHolder0(v);
  }
 }

 @Override
 public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
 if (holder instanceof ViewHolder0){
 ((ViewHolder0)holder).tv_content.setText(mlist.get(position).getName());
 //in some cases, it will prevent unwanted situations
 ((ViewHolder0)holder).cb.setOnCheckedChangeListener(null);
 //if true, your checkbox will be selected, else unselected
 ((ViewHolder0)holder).cb.setChecked(mlist.get(position).isSelected());
 ((ViewHolder0)holder).cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener()
   {
    @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
    { mlist.get(holder.getAdapterPosition()).setSelected(isChecked);}
   });
  }
 }

 @Override
 public int getItemCount() {
 return mlist.size();
 }

 @Override
 public int getItemViewType(int position) {
 return 0;
  }
 }
}

ListView 作法

MyAdapter.java

public class MyAdapter extends ArrayAdapter<Model> {

 private final List<Model> list;
 private final Activity context;
 boolean checkAll_flag = false;
 boolean checkItem_flag = false;

 public MyAdapter(Activity context, List<Model> list) {
 super(context, R.layout.row, list);
 this.context = context;
 this.list = list;
 }
 
 static class ViewHolder {
 protected TextView text;
 protected CheckBox checkbox;
 }
 
 @Override
 public View getView(int position, View convertView, ViewGroup parent) {
 
 ViewHolder viewHolder = null;
 if (convertView == null) {
 LayoutInflater inflator = context.getLayoutInflater();
 convertView = inflator.inflate(R.layout.row, null);
 viewHolder = new ViewHolder();
 viewHolder.text = (TextView) convertView.findViewById(R.id.label);
 viewHolder.checkbox = (CheckBox) convertView.findViewById(R.id.check);
 viewHolder.checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {

 @Override
 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
 int getPosition = (Integer) buttonView.getTag(); // Here we get the position that we have set for the checkbox using setTag.
 list.get(getPosition).setSelected(buttonView.isChecked()); // Set the value of checkbox to maintain its state.
 }
 });
 convertView.setTag(viewHolder);
 convertView.setTag(R.id.label, viewHolder.text);
 convertView.setTag(R.id.check, viewHolder.checkbox);
 } else {
 viewHolder = (ViewHolder) convertView.getTag();
 }
 viewHolder.checkbox.setTag(position); // This line is important.
 
 viewHolder.text.setText(list.get(position).getName());
 viewHolder.checkbox.setChecked(list.get(position).isSelected()); 
 
 return convertView;
 }
}

MainActivity.java

public class MainActivity extends Activity implements OnItemClickListener{
 
 ListView listView;
 ArrayAdapter<Model> adapter;
 List<Model> list = new ArrayList<Model>();
 
 public void onCreate(Bundle icicle) {
 super.onCreate(icicle);
 setContentView(R.layout.main);
 
 listView = (ListView) findViewById(R.id.my_list);
 adapter = new MyAdapter(this,getModel());
 listView.setAdapter(adapter);
 listView.setOnItemClickListener(this);
 }
 
 @Override
 public void onItemClick(AdapterView<?> arg0, View v, int position, long arg3) {
 TextView label = (TextView) v.getTag(R.id.label);
 CheckBox checkbox = (CheckBox) v.getTag(R.id.check);
 Toast.makeText(v.getContext(), label.getText().toString()+" "+isCheckedOrNot(checkbox), Toast.LENGTH_LONG).show();
 }
 
 private String isCheckedOrNot(CheckBox checkbox) {
 if(checkbox.isChecked())
 return "is checked";
 else
 return "is not checked";
 }

 private List<Model> getModel() {
 list.add(new Model("Linux"));
 list.add(new Model("Windows7"));
 list.add(new Model("Suse"));
 list.add(new Model("Eclipse"));
 list.add(new Model("Ubuntu"));
 list.add(new Model("Solaris"));
 list.add(new Model("Android"));
 list.add(new Model("iPhone"));
 list.add(new Model("Java"));
 list.add(new Model(".Net"));
 list.add(new Model("PHP"));
 list.add(new Model("ios"));
 return list;
 }
}
仔細看程式碼會發現不管是ListView或RecyclerView在ViewHolder裡除了一開始的findview以外,
就是從初始過後的Model要值,然後值有變就會儲存在Model裡,當被回收又再次從緩存裡拿出來
變會從Model裡拿值。

附上ListView程式碼
https://github.com/andy086912597/ListViewCheckBox

歡迎大家有問題互相討論

references
http://blog.csdn.net/guolin_blog/article/details/44996879
https://dev.qq.com/topic/5811d3e3ab10c62013697408
http://stackoverflow.com/questions/32427889/checkbox-in-recyclerview-keeps-on-checking-different-items
http://lalit3686.blogspot.tw/2012/06/today-i-am-going-to-show-how-to-deal.html