网络资源模板--基于Android Studio 实现的记账本App

发布于:2025-08-14 ⋅ 阅读:(14) ⋅ 点赞:(0)

目录

一、测试环境说明

二、项目简介

三、项目演示

四、部设计详情(部分)

首页

五、项目源码 


一、测试环境说明

电脑环境

Windows 11

编写语言

JAVA

开发软件

Android Studio  (2020)

开发软件只要大于等于测试版本即可(近几年官网直接下载也可以),若是版本低于测试版本请自行测试。项目需要根据你的软件自行适配

二、项目简介

该项目简介来自网络,具体内容需要自行测试

该记账本App基于Android Studio开发,采用Java语言和Room数据库实现,具备完善的收支管理功能。应用通过三个核心页面(记账、查询、统计)实现数据记录、筛选分析和可视化展示,采用BottomNavigationLayout实现流畅的页面切换。

系统支持分类记录收支数据,提供灵活的增删改查功能,并通过TabLayout实现收入/支出分类展示。创新的联动选择器和条件筛选功能(日期范围、金额区间、类别)显著提升了数据查询效率。

数据可视化是项目的突出亮点,通过折线图动态展示年/月结余趋势,结合饼图直观呈现日支出构成。严格的输入校验机制(如金额格式限制)和人性化交互设计(自定义对话框、日历控件)保障了用户体验。

该应用将数据库操作(RoomDB)、UI组件(ViewPager、碎片)和图表库深度整合,体现了完整的Android开发技术栈应用。项目不仅满足基础记账需求,更通过多维统计功能帮助用户掌握财务动向,具有实用性和创新性。

三、项目演示

网络资源模板--基于Android studio 记账本App

四、部设计详情(部分)

首页

1. 页面的结构

该页面采用经典的底部导航+顶部标签页布局。底部通过BottomNavigationView实现主页、查询、统计三大模块切换,顶部使用TabLayout与ViewPager2联动,分为"全部"、"支出"、"收入"三个标签页。

核心区域为可横向滑动的碎片视图,每个标签对应独立的Fragment展示数据列表。

右下角悬浮按钮触发添加记账的弹窗对话框,该对话框内嵌二级标签页区分收支类型,包含金额输入、日期选择、备注填写等功能区域。

2. 使用到的技术

采用Jetpack组件库构建现代化UI:通过ViewPager2实现横向滑动交互,配合TabLayoutMediator完成标签联动;使用Room数据库进行本地数据持久化,结合LiveData实现数据驱动UI更新;

对话框采用自定义布局与窗口动画,金额输入框通过TextWatcher实现实时校验(限制8位整数+2位小数);日期选择集成原生DatePickerDialog,多线程处理数据库操作避免主线程阻塞,通过Handler实现线程间通信刷新界面。

3. 页面详细介绍

主页功能页作为应用核心入口,采用三层视觉层级:顶部标签分类展示流水记录,中间区域动态加载列表碎片,底部悬浮按钮提供快捷操作。

列表项展示简化的金额、类型和日期信息,支持点击查看详情与长按删除。添加记账时通过智能输入校验(如自动补全小数点、拦截非法输入)提升用户体验,日期选择默认当天并支持历史记录补登。

数据变动后自动同步更新所有关联视图,保持多标签页数据一致性,整体交互流畅符合Material Design规范。

package com.cqu.accountbook.ui.home;

import static android.content.Context.MODE_PRIVATE;

import android.app.DatePickerDialog;
import android.app.Dialog;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Looper;
import android.os.Message;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;

import com.cqu.accountbook.R;
import com.cqu.accountbook.database.DatabaseAction;
import com.cqu.accountbook.database.MyDatabase;
import com.cqu.accountbook.databinding.FragmentHomeBinding;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;

import java.util.Calendar;

public class HomeFragment extends Fragment {

    private FragmentHomeBinding binding;
    public static String type = "餐饮";
    public static boolean inOut = true;
    public static String remark = "";
    public static long moneyAcc = 0;
    public static int[] date = {0, 0, 0};

    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        binding = FragmentHomeBinding.inflate(inflater, container, false);
        View root = binding.getRoot();
        binding.floatingAddButton.setOnClickListener(view -> show());
        ViewPager2 viewPager2 = root.findViewById(R.id.viewpager);
        viewPager2.setAdapter(new ViewPagerAdapter(this));
        TabLayout tabLayout = root.findViewById(R.id.tabs);
        TabLayoutMediator mediator = new TabLayoutMediator(tabLayout, viewPager2, (tab, position) -> {
            switch (position) {
                case 0:
                    tab.setText("全部");
                    tab.setIcon(R.drawable.ic_all_items);
                    break;
                case 1:
                    tab.setText("支出");
                    tab.setIcon(R.drawable.ic_expenditure);
                    break;
                default:
                    tab.setText("收入");
                    tab.setIcon(R.drawable.ic_income);
                    break;
            }

        });
        mediator.attach();
        return root;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        binding = null;
    }

    private void show() {
        type = "餐饮";
        inOut = true;
        Dialog dialog = new Dialog(this.getContext());
        //去掉标题线
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        View view = LayoutInflater.from(this.getContext()).inflate(R.layout.add_dialog, null, false);
        dialog.setContentView(view);
        //背景透明
        dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
        dialog.show();

        Window window = dialog.getWindow();
        WindowManager.LayoutParams lp = window.getAttributes();
        lp.gravity = Gravity.CENTER; // 居中位置
        lp.width = WindowManager.LayoutParams.MATCH_PARENT;
        lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
        window.setAttributes(lp);
        window.setWindowAnimations(R.style.animStyle);  //添加动画

        ViewPager2 viewPager3 = view.findViewById(R.id.typePage);
        viewPager3.setAdapter(new TypePageAdapter(this));
        TabLayout tabLayout1 = view.findViewById(R.id.typeTab);
        TabLayoutMediator mediator = new TabLayoutMediator(tabLayout1, viewPager3, (tab, position) -> {
            if (position == 0)
                tab.setText("支出");
            else
                tab.setText("收入");
        });
        mediator.attach();


        TextView chooseDate = view.findViewById(R.id.chooseDate);
        EditText moneyTxt = view.findViewById(R.id.money);
        EditText remarkTxt = view.findViewById(R.id.remarkText);
        Button addBt = view.findViewById(R.id.addBt);
        chooseDate.setOnClickListener(v -> {
            //获取实例,包含当前年月日
            Calendar calendar = Calendar.getInstance();
            DatePickerDialog dialog1 = new DatePickerDialog(getActivity(), (view1, year, month, dayOfMonth) -> {
                chooseDate.setText(String.format(getString(R.string.chooseDate), year, month + 1, dayOfMonth));
//                Toast.makeText(getContext(), year + "-" + (month + 1) + "-" + dayOfMonth, Toast.LENGTH_SHORT).show();
                date[0] = year;
                date[1] = month + 1;
                date[2] = dayOfMonth;
            },
                    calendar.get(Calendar.YEAR),
                    calendar.get(Calendar.MONTH),
                    calendar.get(Calendar.DAY_OF_MONTH));
            dialog1.show();
        });
        moneyTxt.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                //如果第一个数字为0,第二个不为点,就不允许输入
                if (s.toString().startsWith("0") && s.toString().trim().length() > 1) {
                    if (s.toString().charAt(1) != '.') {
                        moneyTxt.setText(s.subSequence(0, 1));
                        moneyTxt.setSelection(1);
                        return;
                    }
                }
                //如果第一为点,直接显示0.
                if (s.toString().startsWith(".")) {
                    moneyTxt.setText("0.");
                    moneyTxt.setSelection(2);
                    return;
                }
                //限制输入小数位数(2位)
                if (s.toString().contains(".")) {
                    if (s.length() - 1 - s.toString().indexOf(".") > 2) {
                        s = s.toString().subSequence(0, s.toString().indexOf(".") + 2 + 1);
                        moneyTxt.setText(s);
                        moneyTxt.setSelection(s.length());
                    }
                    if (s.toString().indexOf(".") > 8) {
                        s = s.toString().substring(0, s.toString().indexOf(".") - 1) + s.toString().substring(s.toString().indexOf("."), s.length());
                        moneyTxt.setText(s);
                        moneyTxt.setSelection(s.length());
                    }
                }
                //限制整数部分长度
                if (!s.toString().contains(".")) {
                    if (s.length() > 8) {
                        moneyTxt.setText(s.subSequence(0, s.length() - 1));
                        moneyTxt.setSelection(s.length() - 1);
                    }
                }
            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });
        addBt.setOnClickListener(v -> {
            if (String.valueOf(chooseDate.getText()).equals("今天")) {
                date[0] = Calendar.getInstance().get(Calendar.YEAR);
                date[1] = Calendar.getInstance().get(Calendar.MONTH) + 1;
                date[2] = Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
            }
            remark = String.valueOf(remarkTxt.getText());
            double mn = String.valueOf(moneyTxt.getText()).equals("") ? 0 : Double.parseDouble(String.valueOf(moneyTxt.getText()));
            moneyAcc = (long) (mn * 100);
            Log.d("TAG", "onClick: " + mn + "  " + moneyAcc);
            new Thread(() -> {
                // 获取 SharedPreferences 实例
                SharedPreferences sharedPreferences = requireActivity().getSharedPreferences("MyPreferences", MODE_PRIVATE);
                String account = sharedPreferences.getString("account", null); // 如果键不存在,返回 null
                String password = sharedPreferences.getString("password", null); // 如果键不存在,返回 null
                if (account != null && password != null) {
                    DatabaseAction.getInstance(getContext()).getAllIncomesDao().insert(new MyDatabase(moneyAcc, date[0], date[1], date[2], type, remark, account, inOut));
                    AllItems.allItemList = DatabaseAction.getInstance(getContext()).getAllIncomesDao().getAllAccounts(account);
                    Message msg = new Message();
                    msg.what = AllItems.COMPLETED;
                    AllItems.handler.sendMessage(msg);
                    Message msg2 = new Message();
                    msg2.what = AllItems.COMPLETED;
                    if (inOut) {
                        Expenditure.expenditureList = DatabaseAction.getInstance(getContext()).getAllIncomesDao().getAllExpense(account);
                        Expenditure.handler.sendMessage(msg2);
                    } else {
                        Income.incomeList = DatabaseAction.getInstance(getContext()).getAllIncomesDao().getAllIncomes(account);
                        Income.handler.sendMessage(msg2);
                    }
                    Looper.prepare();
                    Toast.makeText(getContext(), "添加成功", Toast.LENGTH_SHORT).show();
                    Looper.loop();
                } else {
                }

            }).start();
            dialog.dismiss();
        });
    }
}

class ViewPagerAdapter extends FragmentStateAdapter {

    public ViewPagerAdapter(@NonNull Fragment fragment) {
        super(fragment);
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        switch (position) {
            case 0:
                return AllItems.newInstance("fragment1", "f1");
            case 1:
                return Expenditure.newInstance("fragment2", "f2");
            default:
                return Income.newInstance("fragment3", "f3");
        }
    }

    @Override
    public int getItemCount() {
        return 3;
    }
}

class TypePageAdapter extends FragmentStateAdapter {

    public TypePageAdapter(@NonNull Fragment fragment) {
        super(fragment);
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        return (position == 0) ? AddExpenditure.newInstance("fragment4", "f4") : AddIncome.newInstance("fragment5", "f5");
    }

    @Override
    public int getItemCount() {
        return 2;
    }
}

五、项目源码 

👇👇👇👇👇快捷方式👇👇👇👇👇