最近做数独Android程序,在选择数独题目时用到了ListActivity,ListActivity比较特别,在从一个Activity跳转到ListActivity时,结果报了异常,
上网查了下,原来虽然listactivity是Activity子类,但listactivity必须与布局文件(ListView)配合起来才能正常跳转,且ListView中android:id必须为@id/android:list。
使用了ListActivity类后,如果整个屏幕上只需显示一个列表,我们甚至可以把setContentView一行注释掉,不用定义列表的XML说明文件。因为ListActivity类已经默认绑定了一个ListView(列表视图)界面组件。
Sudoku_list.xml文件如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ListView android:id="@id/android:list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
</LinearLayout>
如果使用自定义的list的话,我们还可以对每个list_item进行修改,设计出相应的界面。这时候需要定义sudoku_list_item.xmllayout文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="4sp">
<TextView
android:id="@+id/puzzle_id"
android:text="@string/puzzle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:textAppearance="?android:attr/textAppearanceMedium"/>
<TextView
android:id="@+id/rowId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toRightOf="@id/puzzle_id"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
<TextView
android:id="@+id/state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toRightOf="@id/rowId"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
<TextView android:id="@+id/past_time"
android:text="@string/past_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/state"
android:layout_marginLeft="4sp"
android:layout_marginRight="8sp"
android:textAppearance="?android:attr/textAppearanceMedium"/>
"
<TextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/past_time"
android:layout_alignTop="@id/past_time"
android:layout_marginLeft="4sp"
android:layout_marginRight="8sp"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
<TextView
android:id="@+id/best_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_marginLeft="4sp"
android:layout_marginRight="8sp"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
<TextView android:id="@+id/best"
android:text="@string/best_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/best_time"
android:layout_marginLeft="4sp"
android:layout_marginRight="8sp"
android:textAppearance="?android:attr/textAppearanceMedium"/>
</RelativeLayout>
结果,一运行虽然能进去,但是每次运行后数据库里面的数据文件就会改变,我希望ListActivity的每个list_item也会随着改变,不幸的是list_item一直没有变化,只有当ListActivity重新onCreate()调用数据库时才能显示相应的改变。
上网查了下,有的说用handler启动一个新的线程对ListActivity进行实时更新,效果都不好。
最后我选择用SimpleCursorAdapter,完美解决实时更新问题,并且对于数据库很适合使用
public SimpleCursorAdapter (Context context, int layout, Cursor c, String[] from, int[] to)
第一个参数是当前Context提供了关于应用环境全局信息的接口,第二个参数是定义的list_item.xml文件,第三个参数是数据库的游标,如果还没有应用的话可以用null代替,第四个参数是数据库列表的每列的名称,第五个参数是对应的每个Item所要展示的TextView的id,并且只能是TextView,与第四个参数的对应的数据库里面的信息相对应。
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.sudoku_list);
//setListAdapter(new ArrayAdapter<String>(this,
// android.R.layout.simple_list_item_1, puz));
diff = getIntent().getIntExtra(Game.KEY_DIFFICULTY,
Game.DIFFICULTY_KIDS);
db = new DBAdapter(getApplicationContext());
adapter = new SimpleCursorAdapter(this, R.layout.sudoku_list_item,
null, new String[]{DBAdapter.KEY_ROWID, DBAdapter.KEY_IS_COMPLETE,
DBAdapter.KEY_COST_TIME, DBAdapter.KEY_BEST_TIME},
new int[]{R.id.rowId, R.id.state, R.id.time, R.id.best_time});
adapter.setViewBinder(new SudokuListViewBinder(this));
updateList();
setListAdapter(adapter);
}
updateList方法用于实时更新所有的list
private void updateList(){
if (mCursor != null) {
stopManagingCursor(mCursor);
}
mCursor = getSudokuList();
//updateInfo(mCursor);
startManagingCursor(mCursor);
adapter.changeCursor(mCursor);
}
最后一运行,虽然可以实时更新,但是因为SimpleCursorAdapter里面每个Item里面的TextView有一个是表示这个数独谜题所在的id,所以在程序里面显示的就是该谜题的id,因为第二个等级难度的数独题目的id是接在第一个等级数独题目的后面,也就是说,第二个等级的第一个题目本来应该显示Puzzle1,可是它显示的是Puzzle65(每个等级有64道题),SimpleCursorAdapter可以很好的解决ListActivity的实时更新问题,可是它显示的就是游标对应的数据不能更改,最后运行效果如下
相应的问题也有很多,比如数据库里面有个数据是isComplete,它是long型值,如果是0,就输出playing,如果是1,就输出solved。
最后参考了些开源的代码,找到解决办法了,使用了setViewBinder方法
public void setViewBinder (SimpleCursorAdapter.ViewBinder viewBinder)
Sets the binder used to bind data to views.
Parameters
viewBinder | the binder used to bind data to views, can be null to remove the existing binder |
在程序里面定义了一个内部类,实现了ViewBinder接口,重写public boolean setViewValue(View view, Cursor c, int columnIndex)方法
private static class SudokuListViewBinder implements ViewBinder {
private Context mContext;
public SudokuListViewBinder(Context context) {
mContext = context;
}
@Override
public boolean setViewValue(View view, Cursor c, int columnIndex) {
TextView label = null;
switch (view.getId()) {
case R.id.rowId:
int id = c.getInt(c.getColumnIndex(DBAdapter.KEY_ROWID));
int puzzle = id - diff * NUM + 1;
label = (TextView) view;
label.setText(puzzle + "");
// TODO: still can be faster, I don't have to call initCollection and read notes
break;
case R.id.state:
label = ((TextView) view);
long state = c.getLong(c.getColumnIndex(DBAdapter.KEY_IS_COMPLETE));
label.setText(state == 1 ? " " + mContext.getString(R.string.Solved) :
" " + mContext.getString(R.string.playing));
break;
case R.id.time:
String cost_time = mGameTimeFormatter.format(
c.getLong(c.getColumnIndex(DBAdapter.KEY_COST_TIME)));
label = ((TextView) view);
if(cost_time.equals("00:00")){
label.setText(mContext.getString(R.string.default_time));
}else{
label.setText(cost_time);
}
break;
case R.id.best_time:
String bestTime = mGameTimeFormatter.format(
c.getLong(c.getColumnIndex(DBAdapter.KEY_BEST_TIME)));
label = ((TextView) view);
if(bestTime.equals("00:00")){
label.setText(mContext.getString(R.string.default_time));
}else{
label.setText(bestTime);
}
break;
}
return true;
}
}
最后问题圆满解决,运行效果