账本App的制作教程

一、产品介绍

本产品名称为“极简账本”,布局主色调绿色,可为用户的财务支出提供相应的记录,求和、修改等服务。产品一共包含三个页面,分别是添加支出项页面,显示所有支出项页面(主页面)以及修改和删除支出项页面。

二、各个页面详细介绍

1、显示所有支出项页面(主页面)

借助一个recyclerView组件展示所有支出项,点击单个支出项进入删除和修改页面。页面下侧中间置放一个绿色的“➕”ImageButton时,进入增加支出项页面。右下侧包含一个显示所有支出项金额总和的TextView组件以及显示设计者标语的TextView组件。

2、添加支出项页面

页面上部分放置了两个TextView组件,分别让用户输入“消费目的”以及“消费金额”。页面中间包含一个DatePicker组件,方便用户通过滚轮选择目的年月日。下面是一个Button组件,用户点击后触发相应方法将写入的数据保存至数据库,这里要注意金额的选项不能为空,否则弹出toast窗口提醒。如果保存成功,同样弹出toast窗口提醒,并将页面转换至主页面。

3、修改删除支出项页面

页面上部分放置了两个TextView组件,分别让用户输入“消费目的”以及“消费金额”。页面中间包含一个DatePicker组件,方便用户通过滚轮选择目的年月日。下面有两个按钮,分别是删除按钮以及修改按钮。点击删除按钮后弹出提示框让用户进行二次确认,以防误操作;在点击修改按钮后,系统将用户修改后的数据获取,然后更新数据库中的数据。

三、产品涉及技术点

room数据库的使用、recyclerView适配器的编写、按钮(Button)功能的使用、TextView组件字符串的读取与设置、DatePicker组件数据的读取与设置,不同页面之间的切换(Intent)、数据的跨页面传输(Bundle)、无状态弹窗的实现(Toast)、有状态弹窗AlertDialog(需要用户二次确认)的实现、点击recyclerView单条项目的响应实现。

四、代码编写

1、Room数据库搭建

①导入依赖

    //在build.grade文件下补充以下代码
    def room_version = "2.4.3"
    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version"
    // optional - Test helpers
    testImplementation "androidx.room:room-testing:$room_version"

②编写实体

类上面加@Entity注解与数据库相绑定,类里面的属性相当于表上的列名称。
一个对象要包含id,对于这个属性,一般要加上@PrimaryKey(autoGenerate = true)注解;
对于普通的属性,可以加上@ColumnInfo(name = "xxx")注解对应表中的列名称,否则默认使用对应的类属性名;
最后补充相应的构造器以及getter和setter方法;

③编写接口

接口上面要加上@Dao注解以方便调用系统已经写好了的操作数据库的方法;
对于增删改方法上面要加上相应的注解,比如@Insert、@Update、@Delete,方法中的形参就是传入数据库进行操作的参数;
对于查询方法,因为可能牵涉到不同的查询规则,所以要对注解进行补充例如 @Query("select * from record order by id desc")。又因为用户对查询方法的使用频率较高,所以其方法的返回值类型建议写为LiveData>以实现用户在进行增删改操作后系统对数据进行自动的动态更新。

④数据库版本管理器

//以实现数据库结构发生变动时进行安全的数据迁移
@Database(entities = {Record.class},version = 1,exportSchema = false)
public abstract class RecordDatabase extends RoomDatabase {
    private static RecordDatabase INSTANCE;
    static synchronized RecordDatabase getDatabase(Context context){
        if(INSTANCE == null){
            INSTANCE = Room.databaseBuilder(context.getApplicationContext()
                    ,RecordDatabase.class,"record")
                    .build();
        }
        return INSTANCE;
    }
    public abstract RecordDao getRecordDao();
}

⑤RecordRepository3

//在安卓开发中,我们经常要进行一些耗时操作,比如数据库操作,获取网络资源,读取内存文件等等。当我们在处理这些耗时操作的时候,如果我们直接在UI主线程进行,那么可能会导致阻塞UI主线程,使得UI界面卡顿,带来很不好的用户体验,因此Android Studio提供了AsyncTask这个可以异步操作的类。它可以帮助我们把后台线程处理程序的结果发送给UI主线程,使UI线程得到更新。
//RecordRespository类就是将涉及到对数据库的增删改查操作都放到非主线程上进行操作,然后将操作的结果都同步到主线程上,防止UI页面卡顿,造成不好的用户体验
public class RecordRepository {
    //这两个属性存在的意义在于动态获取列表所需数据,但是第二个属性有另一个含义,也就是在对AsyncTask的对象进行初始化时进行赋值
    private LiveData>allRecords;//存储所有的实体数据
    private RecordDao recordDao;//通过这个接口的实现类对数据库进行操作
​
    public RecordRepository(Context context) {
        RecordDatabase recordDatabase = RecordDatabase.getDatabase(context.getApplicationContext());
        recordDao = recordDatabase.getRecordDao();
        allRecords = recordDao.getAllRecords();
    }//整个构造器的作用在于获取数据库中所有的实体数据
​
    //调用插入数据的方法,
    void insertRecord(Record... record) {
        //创建AsyncTask线程,recordDao在对类初始化时赋值,record相当于给doInBackground方法里面传过去的形参,因为在重写doInBackground方法时需要用到这个接口进行同数据库的操作
        new InsertAsyncTask(recordDao).execute(record);
    }
​
    void deleteRecord(Record... record) {
        new DeleteAsyncTask(recordDao).execute(record);
    }
​
    void updateRecord(Record... record) {
        new UpdateAsyncTask(recordDao).execute(record);
    }
​
    //使得其他类可以通过get方法获得全部用户数据,动态自动更新
    LiveData> getAllRecordLive() {
        return allRecords;
    }
                          //三个泛型分别是Params(任务参数),Progress(任务进度),Result(返回结果类型)
    static class InsertAsyncTask extends AsyncTask {
        private RecordDao recordDao;
        InsertAsyncTask(RecordDao recordDao) {
            this.recordDao = recordDao;
        }
​
        @Override
        protected Void doInBackground(Record... records) {
            recordDao.insertRecord(records);
            return null;
        }
    }
    //static相当于工具类,直接调用,不能被实例化(创建对象)
    static class DeleteAsyncTask extends AsyncTask {
        private RecordDao recordDao;
        DeleteAsyncTask(RecordDao recordDao) {
            this.recordDao = recordDao;
        }
​
        @Override
        protected Void doInBackground(Record... records) {
            recordDao.deleteRecord(records);
            return null;
        }
    }
    static class UpdateAsyncTask extends AsyncTask {
        private RecordDao recordDao;
        UpdateAsyncTask(RecordDao recordDao) {
            this.recordDao = recordDao;
        }
​
        @Override
        protected Void doInBackground(Record... records) {
            recordDao.updateRecord(records);
            return null;
        }
    }
}

2、业务层搭建

//业务层作为UI层与数据库的中介,提供业务逻辑以及数据的处理
public class RecordViewModel extends AndroidViewModel {
    private RecordRepository recordRepository;
​
    public RecordViewModel(@NonNull Application application) {
        super(application);
        recordRepository = new RecordRepository(application);
    }
    //初始化后,开始写对于数据库的所有操作方法
    LiveData> getAllRecordLive() {
        return recordRepository.getAllRecordLive();
    }
    void insertRecord(Record... records){
        recordRepository.insertRecord(records);
    }
    void deleteRecord(Record... records){
        recordRepository.deleteRecord(records);
    }
    void updateRecord(Record... records){
        recordRepository.updateRecord(records);
    }
}

3、View层搭建

①主页面

public class MainActivity extends AppCompatActivity {
    ImageButton imageButton;//支持跳转到新增开支项的按钮
    RecyclerView recyclerView;//展示所有数据的列表控件
    RecordViewModel recordViewModel;//处理逻辑和数据的类
    TextView textSum;//显示开支总额的文本
    MyAdapter myAdapter;//适配器
    private List allRecords;//存储所有列表数据
​
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
​
        //对文本组件进行初始化,绑定UI控件
        textSum = findViewById(R.id.textSum);
        
        recordViewModel = new ViewModelProvider(this).get(RecordViewModel.class);//**
        //列表展示控件cyclerView使用步骤
        recyclerView = findViewById(R.id.recyclerView);
        //给列表数据添加分割线
        recyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));//**
        myAdapter = new MyAdapter(recordViewModel);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(myAdapter);
        
        //设置监听事件
        myAdapter.setmOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                //Bundle实现数据的跨页面传输
                Bundle bundle = new Bundle();
                bundle.putString("id", String.valueOf(allRecords.get(position).getId()));
                bundle.putString("item", allRecords.get(position).getItem());
                bundle.putString("price", String.valueOf(allRecords.get(position).getPrice()));
                bundle.putString("date",allRecords.get(position).getDate());
                //Intent实现页面切换
                Intent intent = new Intent(MainActivity.this,UDActivity.class);
                intent.putExtras(bundle);//bundle放到intent里面
                startActivity(intent);
            }
        });
        imageButton = findViewById(R.id.add);
​
        //当数据库数据发生变动时,自动更新列表
        recordViewModel.getAllRecordLive().observe(this, new Observer>() {
            @Override
            public void onChanged(List records) {
                myAdapter.setAllRecords(records);
                myAdapter.notifyDataSetChanged();
                allRecords=records;
                float sum = allRecordsSum();
                textSum.setText("总支出:"+sum+"¥");
            }
        });
        imageButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,NewRecordActivity.class);
                startActivity(intent);
            }
        });
​
    }
    //计算总支出
    private float allRecordsSum() {
        float sum = 0;
        for (int i = 0; i < allRecords.size(); i++) {
            sum += allRecords.get(i).getPrice();
        }
        return sum;
    }
​
}

②适配器

public class MyAdapter extends RecyclerView.Adapter {
    List allRecords = new ArrayList<>();
    //在此类的初始化中,引用别处的已创建好了的RecordViewModel对象,可防止多次重复创建,消耗内存
    RecordViewModel recordViewModel;
    //创建单击回调接口
    private OnItemClickListener mOnItemClickListener;//**
​
    public MyAdapter(RecordViewModel recordViewModel) {
        this.recordViewModel = recordViewModel;
    }
​
    public void setAllRecords(List allRecords) {
        this.allRecords = allRecords;
    }
​
    public void setmOnItemClickListener(OnItemClickListener mOnItemClickListener) {
        this.mOnItemClickListener = mOnItemClickListener;
    }
​
    @NonNull
    @Override
    public MyViewHoler onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
        View itemView=layoutInflater.inflate(R.layout.list_item,parent,false);
        return new MyViewHoler(itemView);
    }
​
    @Override
    public void onBindViewHolder(@NonNull MyViewHoler holder, @SuppressLint("RecyclerView") final int position) {
        Record record = allRecords.get(position);
        holder.textItem.setText(record.getItem());
        holder.textDate.setText(record.getDate());
        holder.textPrice.setText(String.valueOf(record.getPrice()));
​
        //设置点击回调
        if (mOnItemClickListener !=null){
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mOnItemClickListener.onItemClick(holder.itemView,position);
                }
            });
        }
    }
​
    @Override
    public int getItemCount() {
        return allRecords.size();
    }
​
    //设定类并绑定相关UI
    class MyViewHoler extends RecyclerView.ViewHolder{
        //此类用于管理所创建的list_item内资源
        private TextView textItem,textDate,textPrice;
        public MyViewHoler(@NonNull View itemView) {
            super(itemView);
            textItem = itemView.findViewById(R.id.item);
            textDate = itemView.findViewById(R.id.date);
            textPrice = itemView.findViewById(R.id.price);
        }
    }
}

③新增开支页面

public class NewRecordActivity extends AppCompatActivity {
    private EditText item,price;
    private DatePicker date;
    RecordViewModel recordViewModel;//定义业务处理类
    Button save;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_new_cost);

        initView();

        save.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String itemStr = item.getText().toString().trim();
                String priceStr = price.getText().toString().trim();
                String dateStr = date.getYear()+"-"+(date.getMonth()+1)+
                        "-"+(date.getDayOfMonth());

                if("".equals(priceStr)){
                    Toast toast = Toast.makeText(NewRecordActivity
                            .this,"培阳提醒:请填写金额",Toast.LENGTH_SHORT);
                    toast.setGravity(Gravity.CENTER,0,0);
                    toast.show();
                } else {
                    Record record = new Record(itemStr, dateStr, Float.valueOf((priceStr)));
                    recordViewModel = new ViewModelProvider(NewRecordActivity.this).get(RecordViewModel.class);
                    recordViewModel.insertRecord(record);

                    Toast toast = Toast.makeText(NewRecordActivity.this
                    ,"添加成功!",Toast.LENGTH_SHORT);
                    toast.setGravity((Gravity.CENTER),0,0);
                    toast.show();
                    Intent intent = new Intent(NewRecordActivity.this, MainActivity.class);
                    startActivity(intent);
                }
            }
        });

    }

    private void initView() {
        item = findViewById(R.id.item_change);
        date = findViewById(R.id.date_change);
        price = findViewById(R.id.price_change);
        save = (Button)findViewById(R.id.deleteOrUpdate);
    }
}

④修改删除开支项页面

public class UDActivity extends AppCompatActivity {
    EditText textItem,textPrice;
    DatePicker datePicker;
    Button deleteButton,updateButton;
    RecordViewModel recordViewModel;
    static Record record;

    private void changePage() {
        Intent intent = new Intent(UDActivity.this, MainActivity.class);
        startActivity(intent);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.au_change);

        intiDate();
        DialogInterface.OnClickListener okListenner = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                recordViewModel.deleteRecord(record);
                Toast toast = Toast.makeText(UDActivity.this
                        ,"删除成功!",Toast.LENGTH_SHORT);
                toast.setGravity((Gravity.CENTER),0,0);
                toast.show();
                changePage();
            }
        };
        recordViewModel = new ViewModelProvider(UDActivity.this).get(RecordViewModel.class);
        Intent intent = getIntent();
        Bundle bundle = intent.getExtras();
        textItem.setText(bundle.getString("item"));
        String item = String.valueOf(textItem.getText());
        textPrice.setText(bundle.getString("price"));
        int id = Integer.parseInt(bundle.getString("id"));
        String date = bundle.getString("date");
        float price = Float.valueOf(String.valueOf(textPrice.getText()));
        int year = Integer.parseInt(date.substring(0,4));
        int month,day;
        if(date.length()==10){
            month = Integer.parseInt(date.substring(5,7));
            day = Integer.parseInt(date.substring(8));
        }else {
            month = Integer.parseInt(date.substring(5,6));
            day = Integer.parseInt(date.substring(7));
        }

        record = new Record(item,date,price);
        record.setId(id);
        month--;
        datePicker.init(year,month,day,new DatePicker.OnDateChangedListener(){
            @Override
            public void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
            }
        });
        deleteButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //建立对话框
                AlertDialog.Builder builder = new AlertDialog.Builder(UDActivity.this);
                //设置提示信息
                builder.setTitle("系统提醒");
                builder.setMessage("确认删除吗?");
                //设置按钮
                builder.setPositiveButton("确定",okListenner);
                builder.setNegativeButton("取消",null);
                //显示
                builder.show();
            }
        });
        updateButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if("".equals(textPrice.getText().toString().trim()))
                {
                    Toast toast = Toast.makeText(UDActivity.this
                            ,"金额不能为空!",Toast.LENGTH_SHORT);
                    toast.setGravity((Gravity.CENTER),0,0);
                    toast.show();
                    return;
                }
                record.setItem(String.valueOf(textItem.getText()));
                record.setPrice(Float.valueOf(String.valueOf(textPrice.getText())));
                String dateStr = datePicker.getYear()+"-"+(datePicker.getMonth()+1)+
                        "-"+(datePicker.getDayOfMonth());
                record.setDate(dateStr);
                recordViewModel.updateRecord(record);
                Toast toast = Toast.makeText(UDActivity.this
                        ,"修改成功!",Toast.LENGTH_SHORT);
                toast.setGravity((Gravity.CENTER),0,0);
                toast.show();
                changePage();
            }
        });

    }

     void intiDate() {
        textItem = findViewById(R.id.item_change);
        datePicker = findViewById(R.id.date_change);
        textPrice = findViewById(R.id.price_change);
        deleteButton = findViewById(R.id.deleteButton);
        updateButton = findViewById(R.id.updateButton);
    }
}

⑤实现recyclerView的单击功能接口

public interface OnItemClickListener {
    //单击回调方法
    void onItemClick(View view, int position);
}

五、具体问题的代码实现

1.适配器

①recyclerView适配器的单击功能的实现

//1.编写接口
public interface OnItemClickListener {
    //单击回调方法
    void onItemClick(View view, int position);
}
//2.在适配器内对接口进行实现
private OnItemClickListener mOnItemClickListener;
public void setmOnItemClickListener(OnItemClickListener mOnItemClickListener) {
        this.mOnItemClickListener = mOnItemClickListener;
    }
//3.在onBindViewholder方法体内对相应方法进行编写
if (mOnItemClickListener != null){
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mOnItemClickListener.onItemClick(holder.itemView,position);
                }
            });
        }

②recyclerView适配器编写

//1.定义一个List数组的属性用来存储所有将要放入recyclerView中的数据
List allRecords = new ArrayList<>();
//2.写Getter和Setter方法以及构造函数
//3.编写MyViewHoler内部类用以绑定所有单个项目内所有组件资源
//4.重写getItemCount、onBindViewHolder、onCreateViewHolder方法分别获取项目数,将数组列表里面的数据依次读取到recyclerView中以及绑定recyclerView中的单个开支项模板

③给所有单个开支项设置一个分割线

recyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));

④适配器在主函数中的用法

//1.新建适配器
myAdapter = new MyAdapter();
//2.设置适配器布局
recyclerView.setLayoutManager(new LinearLayoutManager(this));
//3.给recyclerView组件设置自己编写的适配器
recyclerView.setAdapter(myAdapter);
//4.可选。设置单击监听事件,重写项目单击方法
myAdapter.setmOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
            }
});

⑤数据库数据发生变化时,recyclerView列表里面的数据也要做相应变化

ecordViewModel.getAllRecordLive().observe(this, new Observer>() {
            @Override
            public void onChanged(List records) {
                myAdapter.setAllRecords(records);
                myAdapter.notifyDataSetChanged();
            }
});

2.按钮单击与相应

//一共有四种方法,这里只选择其中一种
imageButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {  
            }
});

3.实现页面跳转

Intent intent = new Intent(MainActivity.this,NewRecordActivity.class);
startActivity(intent);

4.实现数据跨页面传输(要和Intent类一起使用)

//放数据
Bundle bundle = new Bundle();
bundle.putString("name", "PeiYang");
Intent intent = new Intent(MainActivity.this,UDActivity.class);
intent.putExtras(bundle);//bundle放到intent里面
startActivity(intent);
//取数据
Intent intent = getIntent();
Bundle bundle = intent.getExtras();
textItem.setText(bundle.getString("name"));

5.针对DatePicker组件的数据读写

//1.读数据
String dateStr = datePicker.getYear()+"-"+(datePicker.getMonth()+1)+"-"+(datePicker.getDayOfMonth());
//2.写数据
datePicker.init(year,month,day,new DatePicker.OnDateChangedListener(){//year,month,day即自己设定传参
            @Override
            public void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
                
            }
});

6.消息提醒组件Toast的使用

Toast toast = Toast.makeText(UDActivity.this,"金额不能为空!",Toast.LENGTH_SHORT);
toast.setGravity((Gravity.CENTER),0,0);
toast.show();

7.二次确认提示框使用

//1.建立对话框
AlertDialog.Builder builder = new AlertDialog.Builder(UDActivity.this);
//2.设置提示信息
builder.setTitle("系统提醒");
builder.setMessage("确认删除吗?");
//3.设置按钮
builder.setPositiveButton("确定",okListenner);//设置文字以及监听器
builder.setNegativeButton("取消",null);//不设置点击方法默认不显示弹窗
//4.显示
builder.show();

··································································································
    
//5.监听方法的编写
DialogInterface.OnClickListener okListenner = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) { 
            }
};

你可能感兴趣的:(java,开发语言)