今天实现记账本的最后一个功能,账单详情页面的展示,点击账单详情之后,会出现如下界面
这个布局由三部分组成,整体布局+下面的条目+表格。
首先来做整体布局,在Layout制作布局
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/grey_f3f3f3" android:orientation="vertical"> <RelativeLayout android:layout_width="match_parent" android:layout_height="50dp"> <ImageView android:id="@+id/chart_iv_back" android:layout_width="wrap_content" android:layout_height="match_parent" android:src="@mipmap/it_back" android:layout_marginLeft="10dp" android:onClick="onClick"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="@string/chart_info" android:textSize="18sp" android:textStyle="bold"/> <ImageView android:id="@+id/chart_iv_rili" android:layout_width="wrap_content" android:layout_height="match_parent" android:src="@mipmap/it_rili" android:layout_alignParentRight="true" android:layout_marginRight="10dp" android:onClick="onClick"/> </RelativeLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:background="@color/white" android:padding="10dp" android:layout_marginTop="20dp"> <TextView android:id="@+id/chart_tv_date" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="2022年2月账单" android:textColor="@color/black" android:textStyle="bold" android:textSize="18sp"/> <TextView android:id="@+id/chart_tv_out" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="共1笔支出 ¥25.0" android:textColor="@color/black" android:textSize="15sp"/> <TextView android:id="@+id/chart_tv_in" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="共1笔收入 ¥3000.0" android:textColor="@color/black" android:textSize="15sp"/> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_margin="10dp" android:gravity="center"> <Button android:id="@+id/chart_btn_out" android:layout_width="wrap_content" android:layout_height="30dp" android:text="@string/out" android:background="@drawable/main_record_bg" android:textColor="@color/white" android:textStyle="bold" android:onClick="onClick" android:layout_marginRight="10dp"/> <Button android:id="@+id/chart_btn_in" android:layout_width="wrap_content" android:layout_height="30dp" android:text="@string/in" android:background="@drawable/dialog_btn_bg" android:textColor="@color/black" android:textStyle="bold" android:onClick="onClick" android:layout_marginLeft="10dp"/> </LinearLayout> <androidx.viewpager.widget.ViewPager android:id="@+id/chart_vp" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout>
它主要显示某年某月的账单这一布局。同时这里面有返回和日历两个点击事件。
接下来实现记账条目的布局。
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="10dp" android:background="@color/white"> <ImageView android:id="@+id/item_chartfrag_iv" android:layout_width="35dp" android:layout_height="35dp" android:src="@mipmap/ic_yanjiu_fs"/> <TextView android:id="@+id/item_chartfrag_tv_type" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="烟酒茶" android:textSize="16sp" android:layout_toRightOf="@id/item_chartfrag_iv" android:layout_centerVertical="true" android:layout_marginLeft="10dp" android:textStyle="bold"/> <TextView android:id="@+id/item_chartfrag_tv_percent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="25.0%" android:textSize="16sp" android:layout_centerInParent="true"/> <TextView android:id="@+id/item_chartfrag_tv_sum" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="¥150.0" android:textSize="16sp" android:layout_centerVertical="true" android:layout_alignParentRight="true"/> </RelativeLayout>
这一布局展示账目的种类,钱数多少,以及其所占比。
最后一个布局是表格,布局如下,
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content"> <com.github.mikephil.charting.charts.BarChart android:id="@+id/item_chartfrag_chart" android:layout_width="match_parent" android:layout_height="300dp" /> <TextView android:id="@+id/item_chartfrag_top_tv" android:layout_width="match_parent" android:layout_height="300dp" android:text="@string/notdata" android:textSize="20sp" android:textStyle="bold" android:gravity="center" android:visibility="gone"/> </RelativeLayout>
表格布局制定完之后,还需要在后台代码中进行完善,定义x和y轴...
首先来完成返回和日历的点击事件,返回点击事件,直接finish(),日历的点击事件在上一篇博客中有详细记录。
加下来完成记账条目的后台代码。对于记账条目在DBManger中定义方法对其完成操作
/* 查询指定年份和月份的收入或支出每一种类型的总钱数 **/ public static List<ChartItemBean> getChartListFromAccounttb(int year,int month,int kind){ List<ChartItemBean> list = new ArrayList<>(); float sumMoneyOneMonth = getSumMoneyOneMonth(year, month, kind);//求出支出或收入总钱数 String sql = "select typename,simageId,sum(money)as total from accounttb where year=? and month=? and kind=?" + "group by typename order by total desc"; Cursor cursor = db.rawQuery(sql,new String[]{year+"",month+"",kind+""}); while(cursor.moveToNext()){ int simageId = cursor.getInt(cursor.getColumnIndex("simageId")); String typename = cursor.getString(cursor.getColumnIndex("typename")); float total = cursor.getFloat(cursor.getColumnIndex("total")); //计算所占百分比 total/sumMonth float ratio = FloatUtils.div(total,sumMoneyOneMonth); ChartItemBean bean = new ChartItemBean(simageId,typename,ratio,total); list.add(bean); } return list; }
由于需要计算百分比,所以将除法计算包装成一个类
import java.math.BigDecimal; public class FloatUtils { /** * 进行除法运算 * **/ public static float div(float v1,float v2){ float v3 = v1/v2; BigDecimal b1 = new BigDecimal(v3); float val = b1.setScale(4,4).floatValue(); return val; } //将浮点数转换成百分比显示 public static String ratioToPercent(float val){ float v = val*100; BigDecimal b1 = new BigDecimal(v); float v1 = b1.setScale(2,4).floatValue(); String per = v1+"%"; return per; } }
设置完这些之后,设置内容显示,同时柱状图的设置也在这里完成。
import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ListView; import android.widget.TextView; import androidx.fragment.app.Fragment; import com.example.bookeep.R; import com.example.bookeep.adaptor.ChartItemAdapter; import com.example.bookeep.db.ChartItemBean; import com.example.bookeep.db.DBManger; import com.github.mikephil.charting.charts.BarChart; import com.github.mikephil.charting.components.AxisBase; import com.github.mikephil.charting.components.XAxis; import com.github.mikephil.charting.formatter.IAxisValueFormatter; import java.util.ArrayList; import java.util.List; /** * A simple {@link Fragment} subclass. */ abstract public class BaseFragment extends Fragment { ListView chartLv; public int year; public int month; List<ChartItemBean> mDatas;//数据源 private ChartItemAdapter itemAdapter; BarChart barChart;//柱状图控件 TextView chartTv;//如果没有收支情况,显示的文本 @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View view = inflater.inflate(R.layout.fragment_income, container, false); chartLv = view.findViewById(R.id.frag_chart_lv); //获取Activity传递的数据 Bundle bundle = getArguments(); year = bundle.getInt("year"); month = bundle.getInt("month"); //设置数据源 mDatas = new ArrayList<>(); //设置适配器 itemAdapter = new ChartItemAdapter(getContext(), mDatas); chartLv.setAdapter(itemAdapter); //添加头布局 addLVHeaderView(); return view; } protected void addLVHeaderView(){ //将布局转换成View对象 View headerView = getLayoutInflater().inflate(R.layout.item_chartfrag_top,null); //将ew添加到ListView的头布局 chartLv.addHeaderView(headerView); //查找头布局当中包含的控件 barChart = headerView.findViewById(R.id.item_chartfrag_chart); chartTv = headerView.findViewById(R.id.item_chartfrag_top_tv); //设定柱状图不显示描述 barChart.getDescription().setEnabled(false); //设置柱状图的内边距 barChart.setExtraOffsets(20,20,20,20); setAxis(year,month); //设置坐标轴显示的数据 setAxisData(year,month); } //设置坐标轴显示的数据 protected abstract void setAxisData(int year, int month); //设置柱状图坐标轴的显示,方法必须重新 protected void setAxis(int year, final int month){ //设置x轴 XAxis xAxis = barChart.getXAxis(); xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);//设置X轴显示在下方 xAxis.setDrawGridLines(true);//设置绘制该轴的网格线 //设置x轴标签的个数 xAxis.setLabelCount(31); xAxis.setTextSize(12f);//x轴标签大小 //设置X轴显示值的格式 xAxis.setValueFormatter(new IAxisValueFormatter() { @Override public String getFormattedValue(float value, AxisBase axis) { int val = (int) value; if (val == 0) { return month + "-1"; } if (val == 14) { return month + "-15"; } //根据不同月份,显示最后一天 if (month == 2) { if (val == 27) { return month + "-28"; } }else if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) { if (val == 30) { return month + "-31"; } } else if (month == 4 || month == 6 || month == 9 || month == 11) { if (val == 29) { return month + "-30"; } } return ""; } }); xAxis.setYOffset(10);//设置标签对x轴的偏移量,垂直方向 //y轴在子类的设置 setYAxis(year,month); } //设置y轴,高度不统一,在子类实现 protected abstract void setYAxis(int year,int month); public void setDate(int year,int month){ this.year = year; this.month = month; //清空柱状图当中的数据 barChart.clear(); barChart.invalidate();//重新绘制柱状图 setAxis(year,month); setAxisData(year, month); } public void loadData(int year, int month, int kind) { List<ChartItemBean> list = DBManger.getChartListFromAccounttb(year, month, kind); mDatas.clear(); mDatas.addAll(list); itemAdapter.notifyDataSetChanged(); } }
对于收入和支出,直接继承该类即可
import android.graphics.Color; import android.os.Bundle; import androidx.fragment.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ListView; import com.example.bookeep.R; import com.example.bookeep.adaptor.ChartItemAdapter; import com.example.bookeep.db.BarChartItemBean; import com.example.bookeep.db.ChartItemBean; import com.example.bookeep.db.DBManger; import com.github.mikephil.charting.components.Legend; import com.github.mikephil.charting.components.YAxis; import com.github.mikephil.charting.data.BarData; import com.github.mikephil.charting.data.BarDataSet; import com.github.mikephil.charting.data.BarEntry; import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.formatter.IValueFormatter; import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; import com.github.mikephil.charting.utils.ViewPortHandler; import java.util.ArrayList; import java.util.List; /** * A simple {@link Fragment} subclass. */ public class IncomeFragment extends BaseFragment { int kind = 1; @Override public void onResume() { super.onResume(); loadData(year,month,kind); } @Override protected void setAxisData(int year, int month) { List<IBarDataSet> sets = new ArrayList<>(); //获取这个月每天的支出总金额 List<BarChartItemBean> list = DBManger.getSumMoneyOneDayInMonth(year,month,kind); if (list.size()==0) { barChart.setVisibility(View.GONE); chartTv.setVisibility(View.VISIBLE); }else{ barChart.setVisibility(View.VISIBLE); chartTv.setVisibility(View.GONE); //设置有多少根柱子 List<BarEntry> barEntries1 = new ArrayList<>(); for (int i = 0; i <31 ; i++) { //初始化没一根柱子,添加到柱状图中 BarEntry entry = new BarEntry(i,0.0f); barEntries1.add(entry); } for (int i = 0; i <list.size() ; i++) { BarChartItemBean itemBean = list.get(i); int day = itemBean.getDay();//获取日期 //根据天数获取x轴的位置 int xIndex = day-1; BarEntry barEntry = barEntries1.get(xIndex); barEntry.setY(itemBean.getSummoney()); } BarDataSet barDataSet1 = new BarDataSet(barEntries1, ""); barDataSet1.setValueTextColor(Color.BLACK); // 值的颜色 barDataSet1.setValueTextSize(8f); // 值的大小 barDataSet1.setColor(Color.parseColor("#006400")); // 柱子的颜色 // 设置柱子上数据显示的格式 barDataSet1.setValueFormatter(new IValueFormatter() { @Override public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) { // 此处的value默认保存一位小数 if (value==0) { return ""; } return value + ""; } }); sets.add(barDataSet1); BarData barData = new BarData(sets); barData.setBarWidth(0.2f); // 设置柱子的宽度 barChart.setData(barData); } } @Override protected void setYAxis(int year, int month) { //获取本月收入最高的一天为多少,设为最大值 float maxMoney = DBManger.getMaxMoneyOneDayInMonth(year,month,kind); float max = (float) Math.ceil(maxMoney); //设置y轴 YAxis yAxis_right = barChart.getAxisRight(); yAxis_right.setAxisMaximum(max); // 设置y轴的最大值 yAxis_right.setAxisMinimum(0f); // 设置y轴的最小值 yAxis_right.setEnabled(false); // 不显示右边的y轴 YAxis yAxis_left = barChart.getAxisLeft(); yAxis_left.setAxisMaximum(max); yAxis_left.setAxisMinimum(0f); yAxis_left.setEnabled(false); //设置不显示图例 Legend legend = barChart.getLegend(); legend.setEnabled(false); } @Override public void setDate(int year, int month) { super.setDate(year, month); loadData(year,month,kind); } }
import android.graphics.Color; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.fragment.app.Fragment; import com.example.bookeep.R; import com.example.bookeep.db.BarChartItemBean; import com.example.bookeep.db.DBManger; import com.github.mikephil.charting.components.Legend; import com.github.mikephil.charting.components.YAxis; import com.github.mikephil.charting.data.BarData; import com.github.mikephil.charting.data.BarDataSet; import com.github.mikephil.charting.data.BarEntry; import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.formatter.IValueFormatter; import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; import com.github.mikephil.charting.utils.ViewPortHandler; import java.util.ArrayList; import java.util.List; /** * A simple {@link Fragment} subclass. */ public class OutcomeFragment extends BaseFragment { int kind = 0; @Override public void onResume() { super.onResume(); loadData(year,month,kind); } @Override protected void setAxisData(int year, int month) { List<IBarDataSet> sets = new ArrayList<>(); //获取这个月每天的支出总金额 List<BarChartItemBean> list = DBManger.getSumMoneyOneDayInMonth(year,month,kind); if (list.size()==0) { barChart.setVisibility(View.GONE); chartTv.setVisibility(View.VISIBLE); }else{ barChart.setVisibility(View.VISIBLE); chartTv.setVisibility(View.GONE); //设置有多少根柱子 List<BarEntry> barEntries1 = new ArrayList<>(); for (int i = 0; i <31 ; i++) { //初始化没一根柱子,添加到柱状图中 BarEntry entry = new BarEntry(i,0.0f); barEntries1.add(entry); } for (int i = 0; i <list.size() ; i++) { BarChartItemBean itemBean = list.get(i); int day = itemBean.getDay();//获取日期 //根据天数获取x轴的位置 int xIndex = day-1; BarEntry barEntry = barEntries1.get(xIndex); barEntry.setY(itemBean.getSummoney()); } BarDataSet barDataSet1 = new BarDataSet(barEntries1, ""); barDataSet1.setValueTextColor(Color.BLACK); // 值的颜色 barDataSet1.setValueTextSize(8f); // 值的大小 barDataSet1.setColor(Color.RED); // 柱子的颜色 // 设置柱子上数据显示的格式 barDataSet1.setValueFormatter(new IValueFormatter() { @Override public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) { // 此处的value默认保存一位小数 if (value==0) { return ""; } return value + ""; } }); sets.add(barDataSet1); BarData barData = new BarData(sets); barData.setBarWidth(0.2f); // 设置柱子的宽度 barChart.setData(barData); } } @Override protected void setYAxis(int year, int month) { //获取本月收入最高的一天为多少,设为最大值 float maxMoney = DBManger.getMaxMoneyOneDayInMonth(year,month,kind); float max = (float) Math.ceil(maxMoney); //设置y轴 YAxis yAxis_right = barChart.getAxisRight(); yAxis_right.setAxisMaximum(max); // 设置y轴的最大值 yAxis_right.setAxisMinimum(0f); // 设置y轴的最小值 yAxis_right.setEnabled(false); // 不显示右边的y轴 YAxis yAxis_left = barChart.getAxisLeft(); yAxis_left.setAxisMaximum(max); yAxis_left.setAxisMinimum(0f); yAxis_left.setEnabled(false); //设置不显示图例 Legend legend = barChart.getLegend(); legend.setEnabled(false); } @Override public void setDate(int year, int month) { super.setDate(year, month); loadData(year,month,kind); } }
最终将这些代码进行组装,这个小程序就做好了。
明天将对这个程序做一个总结。