本文考虑把账单界面整理下,实现如下图中的功能。做之前感觉应该不难,但实际做时发现排列界面布局甚至比编写程序代码还要复杂。网上搜索发现,关于这种布局的资料能用的很少,Google Demo中用的最多的就是Listview了,但本实例的界面似乎要复杂一些。
spinner和cursor如何配合使用成了完成此实例过程中的难点,本来应该很简单,但却把我郁闷坏了。
先给大家贴上最终的效果图片:

界面的xml:
XML/HTML代码
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_height="fill_parent" android:layout_width="fill_parent"> <LinearLayout android:id="@+id/LinearLayout01" android:orientation="vertical" android:layout_height="fill_parent" android:layout_width="fill_parent"><LinearLayout android:id="@+id/LinearLayout02" android:layout_width="wrap_content" android:layout_height="wrap_content"><TextView android:id="@+id/TextView01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="选择账目" android:minWidth="80dip" android:textAppearance="?android:attr/textAppearanceLarge"></TextView><EditText android:id="@+id/edittext_acctitem" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width="200dip" android:maxLines="1" android:editable="false" android:cursorVisible="false"></EditText> </LinearLayout><View android:layout_width="fill_parent" android:layout_height="1dip" android:background="?android:attr/listDivider"/><LinearLayout android:id="@+id/LinearLayout03" android:layout_width="wrap_content" android:layout_height="wrap_content"><TextView android:id="@+id/TextView03" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="填入费用" android:minWidth="80dip" android:textAppearance="?android:attr/textAppearanceLarge"></TextView><EditText android:id="@+id/Fee" android:layout_width="wrap_content" android:layout_height="wrap_content" android:numeric="decimal" android:width="160dip"></EditText><TextView android:id="@+id/TextView13" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="(元)" android:textAppearance="?android:attr/textAppearanceLarge"></TextView></LinearLayout><View android:layout_width="fill_parent" android:layout_height="1dip" android:background="?android:attr/listDivider"/><LinearLayout android:id="@+id/LinearLayout04" android:layout_height="wrap_content" android:layout_width="fill_parent"><TextView android:id="@+id/TextView02" android:layout_height="wrap_content" android:text="选择时间" android:layout_width="fill_parent" android:fadingEdge="horizontal" android:height="24dip" android:drawablePadding="2dip"></TextView></LinearLayout> <LinearLayout android:id="@+id/LinearLayout05" android:layout_width="wrap_content" android:layout_height="wrap_content"><TextView android:id="@+id/vdate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceLarge" android:width="120dip"></TextView><Button android:id="@+id/BtnDate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="+" android:textStyle="bold" android:textSize="24dip" android:height="30dip" android:width="30dip"></Button><TextView android:id="@+id/vtime" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceLarge" android:width="80dip" android:gravity="center_horizontal"></TextView><Button android:id="@+id/BtnTime" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="+" android:textStyle="bold" android:textSize="24dip"></Button></LinearLayout><View android:layout_width="fill_parent" android:layout_height="1dip" android:background="?android:attr/listDivider"/><LinearLayout android:id="@+id/LinearLayout06" android:layout_height="wrap_content" android:layout_width="fill_parent"><TextView android:id="@+id/TextView01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="账目类型" android:minWidth="80dip" android:textAppearance="?android:attr/textAppearanceLarge"></TextView> <Spinner android:id="@+id/Spinner01" android:layout_height="wrap_content" android:minWidth="200dip" android:layout_width="wrap_content"></Spinner></LinearLayout><View android:layout_width="fill_parent" android:layout_height="1dip" android:background="?android:attr/listDivider"/><TextView android:id="@+id/TextView07" android:layout_height="wrap_content" android:text="填写备注" android:layout_width="fill_parent" android:height="24dip" ></TextView><EditText android:id="@+id/EditTextDESC" android:layout_width="fill_parent" android:layout_height="wrap_content" android:lines="4" android:gravity="top"></EditText><View android:layout_width="fill_parent" android:layout_height="1dip" android:background="?android:attr/listDivider"/><LinearLayout android:id="@+id/LinearLayout08" android:layout_height="wrap_content" android:layout_width="fill_parent"><Button android:id="@+id/BtnSave" android:width="160dip" android:text="保 存" android:textStyle="bold" android:textSize="24dip" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button><Button android:id="@+id/BtnCancel" android:width="160dip" android:text="取 消" android:textStyle="bold" android:textSize="24dip" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button></LinearLayout><View android:layout_width="fill_parent" android:layout_height="1dip" android:background="?android:attr/listDivider"/> </LinearLayout> </ScrollView>
下面我们来看下spinner和cursor的用法。
主要就是一个SimpleCursorAdapter。
代码如下:
Java代码
s1=(Spinner) findViewById(R.id.Spinner01); String[] from= new String[]{"caption"};//需要显示游标里面的字段 int[] to=new int[]{android.R.id.text1}; Cursor cur=billdb.getUserid(); SimpleCursorAdapter mAdapter=new SimpleCursorAdapter(this,android.R.layout.simple_spinner_item, cur,from, to); mAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); s1.setAdapter(mAdapter);
我在这儿居然搞了2天,其实写法一直没错,可是每次报未知的行 _ID。这个错误我也知道就是使用SimpleCursorAdapter 该方法的游标里面必须包括一个_ID的字段,可是我的表里面肯定有的,在我重试了无数次后发现,区分大小写,我倒!
而事实上我建表的语句是:
Java代码
db.execSQL("Create table tusers (_id integer primary key autoincrement," +"caption text not null)");
而我在函数getUserid 里面cursor定义是:
Java代码
public Cursor getUserid(){Log.v("cola","run get users cursor");return db.query("tusers", new String[]{"_ID", "caption" }, null, null, null, null, null);}
你单独测试这个cursor是没有问题的。
这都没用问题,也就是在这儿是不区分大小写的。但是如果你用这个cursor 绑定到SimpleCursorAdapter 这个里面去,一定要和建表语句的一致,不然就出错。这儿把我郁闷坏了。
上面界面布局和这个spinner 搞定后,后面就是完善代码,完善界面的功能,没有新的地方了。
在用户选择完账目,填写费用,选择时间,账目类型后就保存进数据库bills表。
附最新的代码Frm_Addbills.java:
Java代码
package com.cola.ui; import java.util.Calendar; import java.util.TimeZone; import android.app.Activity; import android.app.AlertDialog; import android.app.DatePickerDialog; import android.app.Dialog; import android.app.TimePickerDialog; import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; import android.os.Bundle; import android.util.Log; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.DatePicker; import android.widget.EditText; import android.widget.SimpleCursorAdapter; import android.widget.Spinner; import android.widget.TextView; import android.widget.TimePicker; import android.widget.Toast; public class Frm_Addbills extends Activity implements OnClickListener {EditText edittext_acctitem,EditTextDESC,Fee;TextView mDate;TextView mTime;static final int RG_REQUEST = 0; private int mYear;private int mMonth;private int mDay;private int mHour;private int mMinute;Spinner s1;Button BtnDate,BtnTime;Button BtnCancel,BtnSave; BilldbHelper billdb; int acctitemid=-1;public void onCreate(Bundle icicle) {super.onCreate(icicle);setTitle("ColaBox-添加账单");setContentView(R.layout.frm_addbills); edittext_acctitem = (EditText)findViewById(R.id.edittext_acctitem);edittext_acctitem.setOnClickListener(this); EditTextDESC=(EditText)findViewById(R.id.EditTextDESC);Fee=(EditText)findViewById(R.id.Fee); BtnDate=(Button)findViewById(R.id.BtnDate);BtnDate.setOnClickListener(this);BtnTime=(Button)findViewById(R.id.BtnTime);BtnTime.setOnClickListener(this); BtnCancel=(Button)findViewById(R.id.BtnCancel);BtnCancel.setOnClickListener(this);BtnSave=(Button)findViewById(R.id.BtnSave);BtnSave.setOnClickListener(this); mDate = (TextView) findViewById(R.id.vdate);mTime = (TextView) findViewById(R.id.vtime); //Calendar c=Calendar.getInstance(Locale.CHINA);initTime();setDatetime();billdb = new BilldbHelper(this);s1=(Spinner) findViewById(R.id.Spinner01);String[] from= new String[]{"caption"};int[] to=new int[]{android.R.id.text1};Cursor cur=billdb.getUserid();SimpleCursorAdapter mAdapter=new SimpleCursorAdapter(this,android.R.layout.simple_spinner_item, cur,from, to);mAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);s1.setAdapter(mAdapter);}public boolean onCreateOptionsMenu(Menu menu) {super.onCreateOptionsMenu(menu);menu.add(0, 1, 0, "账目明细").setIcon(R.drawable.editbills);menu.add(0, 2, 0, "账目统计").setIcon(R.drawable.editbills2);menu.add(0, 3, 0, "账目报表").setIcon(R.drawable.billsum1);menu.add(0, 4, 0, "退 出").setIcon(R.drawable.quit); return true;}public void onClick(View v) {if (v.equals(edittext_acctitem)) {Log.v("ColaBox", "cmd=edittext_acctitem");Intent intent = new Intent();intent.setClass(Frm_Addbills.this, Frm_Editacctitem.class);startActivityForResult(intent, RG_REQUEST);} else if (v.equals(BtnTime)){showDialog(1);} else if (v.equals(BtnDate)){showDialog(2);} else if (v.equals(BtnCancel)){cancel();} else if (v.equals(BtnSave)){save();} }public boolean onOptionsItemSelected(MenuItem item) {//Log.v("ColaBox", "getmenuitemid=" + item.getItemId());switch (item.getItemId()) {case 1:return true;case 2: return true;case 3:return true;case 4:QuitApp();return true;}return false;}public void QuitApp() {new AlertDialog.Builder(Frm_Addbills.this).setTitle("提示").setMessage("确定退出?").setIcon(R.drawable.quit).setPositiveButton("确定",new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { billdb.close(); finish(); }}).setNegativeButton("取消",new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { }}).show();}protected void onActivityResult(int requestCode, int resultCode, Intent data) {if (requestCode == RG_REQUEST) {if (resultCode == RESULT_CANCELED) {// setTitle("Canceled...");} else if (resultCode == RESULT_OK) {// setTitle((String)data.getCharSequenceExtra("DataKey"));edittext_acctitem.setText((String) data.getCharSequenceExtra("name"));acctitemid=Integer.parseInt((String)data.getCharSequenceExtra("id"));Log.v("cola","get acctitemid="+acctitemid);}}} private void cancel(){Log.v("cola","u put cancel btn");edittext_acctitem.setText("");Fee.setText("");acctitemid=-1;initTime();setDatetime();EditTextDESC.setText("");}private void save(){Log.v("cola","u put save btn");if (acctitemid==-1){new AlertDialog.Builder(this).setMessage("请首先选择账目.").show();return;}int fee=0;String s=Fee.getText().toString();int pos=s.indexOf(".");//Log.v("cola","i="+(s.length()-pos));if (pos>0){if (s.length()-pos<3){s=s+"0";}fee=Integer.parseInt(s.substring(0,pos)+s.substring(pos+1,pos+3));}else{fee=Integer.parseInt(s)*100; }Log.v("cola","u put save btn");if (billdb.Bills_save(acctitemid,fee,(int)s1.getSelectedItemId(), ((TextView)mDate).getText().toString(), ((TextView)mTime).getText().toString(),EditTextDESC.getText().toString())){Toast.makeText(this, "保存成功.", Toast.LENGTH_SHORT).show();cancel();}else{Toast.makeText(this, "保存失败,请检查数据.", Toast.LENGTH_SHORT).show();}} public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) {case KeyEvent.KEYCODE_BACK:QuitApp();return true; }return false;}private void initTime(){Calendar c = Calendar. getInstance(TimeZone.getTimeZone("GMT+08:00"));mYear = c.get(Calendar.YEAR);mMonth = c.get(Calendar.MONTH);mDay = c.get(Calendar.DAY_OF_MONTH);mHour = c.get(Calendar.HOUR_OF_DAY);mMinute = c.get(Calendar.MINUTE);} private void setDatetime(){mDate.setText(mYear+"-"+mMonth+"-"+mDay);mTime.setText(pad(mHour)+":"+pad(mMinute));} @Overrideprotected Dialog onCreateDialog(int id) {switch (id) {case 1:return new TimePickerDialog(this, mTimeSetListener, mHour, mMinute, false);case 2:return new DatePickerDialog(this, mDateSetListener, mYear, mMonth, mDay);}return null;}@Overrideprotected void onPrepareDialog(int id, Dialog dialog) {switch (id) {case 1:((TimePickerDialog) dialog).updateTime(mHour, mMinute);break;case 2:((DatePickerDialog) dialog).updateDate(mYear, mMonth, mDay);break;}} private DatePickerDialog.OnDateSetListener mDateSetListener =new DatePickerDialog.OnDateSetListener() {public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {mYear = year;mMonth = monthOfYear;mDay = dayOfMonth;setDatetime();}};private TimePickerDialog.OnTimeSetListener mTimeSetListener =new TimePickerDialog.OnTimeSetListener() {public void onTimeSet(TimePicker view, int hourOfDay, int minute) {mHour = hourOfDay;mMinute = minute;setDatetime();}};private static String pad(int c) {if (c >= 10)return String.valueOf(c);elsereturn "0" + String.valueOf(c);} }
最新的billdbhelper.java :
Java代码
package com.cola.ui; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.util.Log; /*** Provides access to a database of notes. Each note has a title, the note* itself, a creation date and a modified data.*/ public class BilldbHelper {private static final String TAG = "Cola_BilldbHelper";private static final String DATABASE_NAME = "cola.db"; SQLiteDatabase db;Context context; BilldbHelper(Context _context) {context=_context;db=context.openOrCreateDatabase(DATABASE_NAME, 0, null);Log.v(TAG,"db path="+db.getPath());} public void CreateTable_acctitem() {try{db.execSQL("CREATE TABLE acctitem (" + "_ID INTEGER PRIMARY KEY," + "PID integer," + "NAME TEXT"+ ");");Log.v("cola","Create Table acctitem ok");}catch(Exception e){Log.v("cola","Create Table acctitem err,table exists.");}} public void CreateTable_bills() {try{db.execSQL("CREATE TABLE bills (" + "_ID INTEGER primary key autoincrement," +" acctitemid integer," + "fee integer," + "userid integer," + "sdate TEXT," + "stime TEXT," + "desc TEXT"+ ");"); Log.v("cola","Create Table acctitem ok");}catch(Exception e){Log.v("cola","Create Table acctitem err,table exists.");}} public boolean Bills_save(int acctid,int fee,int userid,String date,String time,String text){String sql="";try{sql="insert into bills values(null,"+acctid+","+fee+","+userid+",""+date+"",""+time+"",""+text+"")";db.execSQL(sql); Log.v("cola","insert Table bills ok");return true; }catch(Exception e){Log.v("cola","insert Table bills err="+sql);return false;}} public void CreateTable_colaconfig() {try{db.execSQL("CREATE TABLE colaconfig (" + "_ID INTEGER PRIMARY KEY," + "NAME TEXT" + ");");Log.v("cola","Create Table colaconfig ok");}catch(Exception e){Log.v("cola","Create Table acctitem err,table exists.");}} public void CreateTable_users() {try{db.execSQL("Create table tusers (_id integer primary key autoincrement," + "caption text not null)");Log.v("cola","Create Table users ok");db.execSQL("insert into tusers values (null,"个人")");db.execSQL("insert into tusers values (null,"公司")");}catch(Exception e){Log.v("cola","Create Table tusers err,table exists.");}} public void InitAcctitem() {try{//s.getBytes(encoding);db.execSQL("insert into acctitem values (1,null,"收入")");db.execSQL("insert into acctitem values (2,1,"工资")");db.execSQL("insert into acctitem values (9998,1,"其他")");db.execSQL("insert into acctitem values (0,null,"支出")");db.execSQL("insert into acctitem values (3,0,"生活用品")");db.execSQL("insert into acctitem values (4,0,"水电煤气费")");db.execSQL("insert into acctitem values (5,0,"汽油费")");db.execSQL("insert into acctitem values (9999,0,"其他")"); //db.execSQL("insert into bills values(100,135,10000,"","","备注")");Log.v("cola","insert into ok");}catch(Exception e){Log.v("cola","init acctitem e="+e.getMessage());} }public void Acctitem_newitem(String text,int type){ Cursor c =db.query("acctitem", new String[]{"max(_id)+1"}, "_id is not null and _id<9998", null, null, null, null);c.moveToFirst();int maxid=c.getInt(0);String sql="insert into acctitem values ("+maxid+","+type+",""+text+"")";db.execSQL(sql);Log.v("cola","newitem ok text="+text+" id="+type+" sql="+sql); } public void Acctitem_edititem(String text,int id){db.execSQL("update acctitem set name=""+text+"" where _id="+id);Log.v("cola","edititem ok text="+text+" id="+id);} public void Acctitem_delitem(int id){ db.execSQL("delete from acctitem where _id="+id);Log.v("cola","delitem ok id="+id);} public void QueryTable_acctitem(){ } public void FirstStart(){try{String col[] = {"type", "name" };Cursor c =db.query("sqlite_master", col, "name="colaconfig"", null, null, null, null);int n=c.getCount();if (c.getCount()==0){CreateTable_acctitem();CreateTable_colaconfig();CreateTable_bills();CreateTable_users();InitAcctitem();}//test();Log.v("cola","c.getCount="+n+"");}catch(Exception e){Log.v("cola","e="+e.getMessage());}}public void close(){db.close();} public Cursor getParentNode(){return db.query("acctitem", new String[]{"_id", "name" }, "pid is null", null, null, null, "pid,_id");} public Cursor getChildenNode(String pid){Log.v("cola","run getchildenNode");return db.query("acctitem", new String[]{"_id", "name" }, "pid="+pid, null, null, null, "_id");} public Cursor getUserid(){Log.v("cola","run get users cursor");return db.query("tusers", new String[]{"_id", "caption" }, null, null, null, null, null);} public String test(){try{ Cursor c2 =getUserid();String ss="";c2.moveToFirst();while(!c2.isAfterLast()){ ss = c2.getString(0) +", "+ c2.getString(1);//byte b[]=c2.getString(1).getBytes();c2.moveToNext();Log.v("cola","ss="+ss+"");} return ss;}catch(Exception e){Log.v("cola","e="+e.getMessage());return "err";}} }
系列文章:
Android 个人理财工具六:显示账单明细 下
Android 个人理财工具五:显示账单明细 上
Android 个人理财工具四:添加账单页面 下
Android 个人理财工具三:添加账单页面 上
Android 个人理财工具二:使用SQLite实现启动时初始化数据
Android 个人理财工具一:项目概述与启动界面的实现
以上就Android 理财工具详情页面的开发,后续继续补充,其他功能,谢谢大家对本站的支持!