notifydatasetchanged()无效?——java存储机制问题?
之前一段时间在做列表的交互功能,UI要求做成GridView,后来又要求做成每一页固定的可以翻页的GridView。下面贴个Demo:
Demo:可翻页的GridView(错误)
import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import android.os.Bundle; import android.app.Activity; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.GridView; import android.widget.SimpleAdapter; public class MainActivity extends Activity { int line = 2; int column =6; int page = 1; ArrayList<Map<String,Object>> sublist = new ArrayList<Map<String,Object>>(); SimpleAdapter adapter_01; GridView gv; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final Button pagedown = (Button)findViewById(R.id.pagedown); final Button pageup = (Button)findViewById(R.id.pageup); gv = (GridView)findViewById(R.id.gv); gv.setNumColumns(column); gv.setVerticalSpacing(50); final String [] number = new String[] { "1","2","3","4","5","6" ,"7","8","9","10", "11","12","13","14","15","16","17","18","19","20", "21","22","23","24","25","26" ,"27","28","29","30", "31","32","33","34","35","36","37","38","39","40" }; final ArrayList<Map<String,Object>> list = new ArrayList<Map<String,Object>>(); for(int i=0;i<number.length;i++) { Map<String,Object> listitem = new HashMap<String,Object>(); listitem.put("id", number[i]); list.add(listitem); } sublist = getlist(list, page, line, column); pageup.setClickable(false); if(sublist.size()<line*column) pagedown.setClickable(false); adapter_01 = new SimpleAdapter(this ,sublist ,R.layout.list ,new String[]{"id"} ,new int[]{R.id.tv}); gv.setAdapter(adapter_01); pagedown.setOnClickListener(new OnClickListener() { public void onClick(View v) { page++; sublist.clear(); sublist = getlist(list, page, line, column); gv.setAdapter(adapter_01); adapter_01.notifyDataSetChanged(); Log.v("sublist",""+sublist); pageup.setClickable(true); if(sublist.size()<line*column) { pagedown.setClickable(false); } else { pagedown.setClickable(true); } } }); pageup.setOnClickListener(new OnClickListener() { public void onClick(View v) { page--; sublist.clear(); sublist = getlist(list, page, line, column); gv.setAdapter(adapter_01); adapter_01.notifyDataSetChanged(); Log.v("sublist",""+sublist); pagedown.setClickable(true); if(page==1) { pageup.setClickable(false); } else { pageup.setClickable(true); } } }); } public ArrayList<Map<String,Object>> getlist(ArrayList<Map<String,Object>> list,int page,int line,int column) { ArrayList<Map<String,Object>> sublist = new ArrayList<Map<String,Object>>(); for(int k=0,i=(page-1)*line*column;i<list.size()&&k<line*column;i++,k++) { sublist.add(list.get(i)); } return sublist; } }
以上程序本来是要实现GridView翻页显示,每一页显示一个2行6列的子GridView——即第一页显示1~12,第二页显示2~24,以此类推。但是单击pagedown按钮翻页,发现显示了一个空白页。查看sublist的Log输出:
sublist的内容确实已经更新了,可是为什么没有显示出来?怀疑的对象也只能是adapter了。之前也用过adapter的notifydatasetchanged()方法更新list,并没有出现类似的问题,百思不得其解后,先找了个方法先把功能实现了,以进行后续的工作:
1 pagedown.setOnClickListener(new OnClickListener() 2 { 3 public void onClick(View v) 4 { 5 page++; 6 sublist = getlist(list, page, line, column); 7 SimpleAdapter adapter_01 = new SimpleAdapter(MainActivity.this 8 ,sublist 9 ,R.layout.list 10 ,new String[]{"id"} 11 ,new int[]{R.id.tv}); 12 gv.setAdapter(adapter_01); 13 adapter_01.notifyDataSetChanged(); 14 pageup.setClickable(true); 15 if(sublist.size()<line*column) 16 { 17 pagedown.setClickable(false); 18 } 19 else 20 { 21 pagedown.setClickable(true); 22 } 23 } 24 });
看到这不要笑我,我也知道每翻一页就新建一个adapter,效率之低下。。。只是为了不打断后面的工作,硬着头皮写上去的。这样写之后,也确实可以实现预期的功能了。
然而出来混,总是要还的,终于问题到了非解决不可得时候。我将代码与之前成功的例子仔细比较,发现以前的更新,都是为列表添加/删除操作——即add,remove之类,而这次的关键语句是通过getlist方法直接获得下一页的子表。问题就出在这里。
1)sublist这个名字,表示的是一个引用,存储在栈内存空间中,当它第一次调用getlist方法时,它就指向了堆内存的某个空间,这个空间存储的是getlist方法获得的第一个子表(第一页);
2)创建adapter,将sublist作为其第二个参数存入,关键就是这里,原本以为adapter存入的是sublist这个引用,但这只是自己一厢情愿。先看SimpleAdapter源码:
1 public class SimpleAdapter extends BaseAdapter implements Filterable { 2 private int[] mTo; 3 private String[] mFrom; 4 private ViewBinder mViewBinder; 5 6 private List<? extends Map<String, ?>> mData; 7 8 private int mResource; 9 private int mDropDownResource; 10 private LayoutInflater mInflater; 11 12 private SimpleFilter mFilter; 13 private ArrayList<Map<String, ?>> mUnfilteredData; 14 15 /** 16 * Constructor 17 * 18 * @param context The context where the View associated with this SimpleAdapter is running 19 * @param data A List of Maps. Each entry in the List corresponds to one row in the list. The 20 * Maps contain the data for each row, and should include all the entries specified in 21 * "from" 22 * @param resource Resource identifier of a view layout that defines the views for this list 23 * item. The layout file should include at least those named views defined in "to" 24 * @param from A list of column names that will be added to the Map associated with each 25 * item. 26 * @param to The views that should display column in the "from" parameter. These should all be 27 * TextViews. The first N views in this list are given the values of the first N columns 28 * in the from parameter. 29 */ 30 public SimpleAdapter(Context context, List<? extends Map<String, ?>> data, 31 int resource, String[] from, int[] to) { 32 mData = data; 33 mResource = mDropDownResource = resource; 34 mFrom = from; 35 mTo = to; 36 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 37 } 38 ...... 39 }
看到了吧,上面SimpleAdapter的构造器,把传入的第二个参数赋给了mData,也就此时mData指向了第一个子表(第一页)的堆内存空间,然而改变了sublist之后,sublist已经指向了第二个子表(第二页)的堆内存空间,可是mData仍然指向原来的对内存空间,adapter的notifydatasetchanged()方法仅仅是更新mData指向的数据,所以出现了上述现象。
下面来验证这个说法。先吐槽一下,java中没有类似C语言的‘&’——地址操作符,个人感觉不是很方便啊。不过还好Object中使用equals方法,比较的就是地址,可以用这个方法判断sublist=getlist()前后是否改变了指向的地址。
将程序改为
1 pageup.setOnClickListener(new OnClickListener() 2 { 3 public void onClick(View v) 4 { 5 page--; 6 ArrayList<Map<String,Object>> a = sublist; 7 sublist.clear(); 8 Log.v("before:a.equals(sublist)",""+a.equals(sublist)); 9 sublist = getlist(list, page, line, column); 10 Log.v("before:a.equals(sublist)",""+a.equals(sublist)); 11 SimpleAdapter adapter_01 = new SimpleAdapter(MainActivity.this 12 ,sublist 13 ,R.layout.list 14 ,new String[]{"id"} 15 ,new int[]{R.id.tv}); 16 gv.setAdapter(adapter_01); 17 adapter_01.notifyDataSetChanged(); 18 pagedown.setClickable(true); 19 if(page==1) 20 { 21 pageup.setClickable(false); 22 } 23 else 24 { 25 pageup.setClickable(true); 26 } 27 } 28 });
其日志打印为
果然前后两次不同了。在将程序进行修改:
1 pagedown.setOnClickListener(new OnClickListener() 2 { 3 public void onClick(View v) 4 { 5 page++; 6 ArrayList<Map<String,Object>> a = sublist; 7 sublist.clear(); 8 Log.v("before:a.equals(sublist)",""+a.equals(sublist)); 9 for(int i = 0;i < getlist(list, page, line, column).size(); i++) 10 { 11 sublist.add(getlist(list, page, line, column).get(i)); 12 } 13 Log.v("before:a.equals(sublist)",""+a.equals(sublist)); 14 adapter_01.notifyDataSetChanged(); 15 pageup.setClickable(true); 16 if(sublist.size()<line*column) 17 { 18 pagedown.setClickable(false); 19 } 20 else 21 { 22 pagedown.setClickable(true); 23 } 24 } 25 });
这就实现了不改变sublist的指向,从程序的运行结果看果然
java的基础才是对android的最可靠保证啊,对于java看了1个月就学android的我来说,尤其如此。。