小哈记账是一款用于记账APP,基于Android Studio开发工具,采用Java语言进行开发,同时使用litepal和阿里云数据库进行数据的增删查改,以图标的形式在App的界面上显示。App可以清晰显示收支情况,并以图表的形式展示每月收支情况;同时可以记录消费用途,项目能够精确到每一个款项的收入支出时间、结余并且可以设置每月的预算。超出预算提醒,并且有每周,每月及每年的账单统计,较为清晰明确帮助用户分析自己当前的支出和收入。同时拥有用户登录、注册、修改头像等功能。
本项目中使用到的技术:LitePal数据库,阿里云RDS数据库,MPAndroidChart图表库,部分自定义控件等。
支持Android5.0以上版本。
本文将介绍小哈记账的首页页面制作。
首先,让我们介绍一下首页页面的模块组成。页面最顶部是一个卡片,其中展示了当前月份以及本月的收入、支出和结余情况。接下来,我们可以看到一个本月支出预算的模块,其中展示了当前支出的金额以及占总预算的百分比。第三部分是本月收支趋势图,其中展示了当前月份每日的收入和支出情况。最后,我们还可以看到今日、本周和本年的收入和支出情况,这一部分的信息也非常有用。
第一步:本月统计样式的编写main.xml
背景图片为Adobe Illustrator制作。其余部分为LinearLayout和TextView组成,比较简单。
第二步:功能实现,编写MainActivity.java文件
该部分比较简单,从数据库获取数据,求和赋值给TextView即可。
public int year = MyUtils.initYear();
public int month = MyUtils.initMonth();
public int day = MyUtils.initDay();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
mActivity = new WeakReference(this);
initView();//初始化控件
main_month.setText(year+"年"+month+"月");//为卡片的当前月份赋值
}
@Override
protected void onStart() {
super.onStart();
if(is_add || is_delete){
progressBarLayout.setVisibility(View.VISIBLE);
main_layout.setVisibility(View.GONE);
Message msg = new Message();
sum_ThisMonth(month);//更新数据
setThisDate();//添加当前数据
handlerProgress.sendEmptyMessageDelayed(1,2000);
}
}
/**
* 求出数据和
* @param mmonth 本月月份
*/
private void sum_ThisMonth(int mmonth){
zc_week_money = 0;
zc_day_money = 0;
zc_year_money = 0;
sr_week_money = 0;
sr_day_money = 0;
sr_year_money = 0;
used_money = 0;
sr_money = 0;
jy_money = 0;
lineChartMoney_zc.clear();
lineChartMoney_zc = new ArrayList(Collections.nCopies(31, 0.0));
lineChartMoney_sr.clear();
lineChartMoney_sr = new ArrayList(Collections.nCopies(31, 0.0));
List accDetails = LitePal.where("user_id = ?", String.valueOf(LoginActivity.mid))
.find(AccDetails.class);
for(AccDetails accDetails1 : accDetails){
String[] date = accDetails1.getDet_date().split("-");
/*获取本年收支*/
if(Integer.valueOf(date[0]) == year && accDetails1.getDet_type_int() == 0){
if(MyUtils.getTypeInt(accDetails1.getDet_type()) != -1){
zc_year_money += accDetails1.getDet_money();
/*获取本月支出*/
if(Integer.valueOf(date[1]) == mmonth){
used_money += accDetails1.getDet_money();
lineChartMoney_zc.set(Integer.valueOf(date[2])-1, accDetails1.getDet_money()+lineChartMoney_zc.get(Integer.valueOf(date[2])-1));
if(Integer.valueOf(date[2]) == day){
zc_day_money += accDetails1.getDet_money();
}
}
/*获取本周支出*/
for(int i = 0; i<7;i++){
if(String.valueOf(Integer.valueOf(date[1])+"-"+Integer.valueOf(date[2])).equals(MyUtils.weekDay.get(i))){
zc_week_money += accDetails1.getDet_money();
}
}
}
} else if(Integer.valueOf(date[0]) == year && accDetails1.getDet_type_int() == 1){
if(MyUtils.getTypeIncomeInt(accDetails1.getDet_type()) != -1){
sr_year_money += accDetails1.getDet_money();
/*获取本月收入*/
if(Integer.valueOf(date[1]) == mmonth){
sr_money += accDetails1.getDet_money();
lineChartMoney_sr.set(Integer.valueOf(date[2])-1, accDetails1.getDet_money()+lineChartMoney_sr.get(Integer.valueOf(date[2])-1));
if(Integer.valueOf(date[2]) == day){
sr_day_money += accDetails1.getDet_money();
}
}
/*获取本周收入*/
for(int i = 0; i<7;i++){
if(String.valueOf(Integer.valueOf(date[1])+"-"+Integer.valueOf(date[2])).equals(MyUtils.weekDay.get(i))){
sr_week_money += accDetails1.getDet_money();
}
}
}
}
}
jy_money = sr_money - used_money;//本月结余
if(ys_money != -1){
used_per = (int) Math.round(used_money / ys_money*100);//四舍五入取整
if(ys_money > used_money){
ky_money = ys_money - used_money;
} else {
ky_money = used_money - ys_money;
}
}
if(used_money == 0){
ys_used_text.setText("已用 0.00");
main_zc_money.setText("0.00");
} else {
ys_used_text.setText("已用 "+df.format(used_money));
main_zc_money.setText(df.format(used_money));
}
if(sr_money == 0){
main_sr_money.setText("0.00");
} else {
main_sr_money.setText(df.format(sr_money));
}
if(jy_money == 0){
main_jy_money.setText("0.00");
} else {
main_jy_money.setText(df.format(jy_money));
}
if(ys_money != -1){
ys_text.setText("总支出预算 "+df.format(ys_money));
ys_used_per.setText(used_per+"%");
if(ky_money == 0){
ys_ky_text.setText("可用 0.00");
} else {
if(used_per >= 80 && used_per <= 100){
ys_ky_text.setText("可用 "+df.format(ky_money));
ys_ky_text.setTextColor(Color.parseColor("#e8b004"));
ys_used_per.setTextColor(Color.parseColor("#e8b004"));
} else if(used_per > 100){
ys_ky_text.setText("已超出预算 "+df.format(ky_money));
ys_ky_text.setTextColor(Color.parseColor("#ee3f4d"));
ys_used_per.setTextColor(Color.parseColor("#ee3f4d"));
} else {
ys_ky_text.setText("可用 "+df.format(ky_money));
ys_ky_text.setTextColor(Color.parseColor("#666666"));
ys_used_per.setTextColor(Color.parseColor("#666666"));
}
}
}
}
首先需要自定义一个进度条控件。
自定义进度条java文件:
package com.example.xiaohaaccounting.myView;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import com.example.xiaohaaccounting.R;
public class CircleProgressBar extends View {
private Context mContext;
private Paint mPaint;
private int mProgress = 0;
private static int MAX_PROGRESS = 100;
/**
* 弧度
*/
private int mAngle;
/**
* 中间的文字
*/
private String mText;
/**
* 外圆颜色
*/
private int outRoundColor;
/**
* 内圆的颜色
*/
private int inRoundColor;
/**
* 线的宽度
*/
private int roundWidth;
private int style;
/**
* 字体颜色
*/
private int textColor;
/**
* 字体大小
*/
private float textSize;
/**
* 字体是否加粗
*/
private boolean isBold;
/**
* 进度条颜色
*/
private int progressBarColor;
public CircleProgressBar(Context context) {
this(context, null);
}
public CircleProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
init(attrs);
}
@TargetApi(21)
public CircleProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mContext = context;
init(attrs);
}
/**
* 解析自定义属性
*
* @param attrs
*/
public void init(AttributeSet attrs) {
mPaint = new Paint();
TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.CircleProgressBar);
outRoundColor = typedArray.getColor(R.styleable.CircleProgressBar_outCircleColor, getResources().getColor(R.color.grey_e));
inRoundColor = typedArray.getColor(R.styleable.CircleProgressBar_inCircleColor, getResources().getColor(R.color.white));
progressBarColor = typedArray.getColor(R.styleable.CircleProgressBar_progressColor, getResources().getColor(R.color.xiaoha));
isBold = typedArray.getBoolean(R.styleable.CircleProgressBar_textBold, false);
textColor = typedArray.getColor(R.styleable.CircleProgressBar_textColor, Color.BLACK);
roundWidth = typedArray.getDimensionPixelOffset(R.styleable.CircleProgressBar_lineWidth, 20);
typedArray.recycle();
}
@Override
protected void onDraw(Canvas canvas) {
/**
* 画外圆
*/
super.onDraw(canvas);
int center = getWidth() / 2; //圆心
int radius = (center - roundWidth / 2); //半径
mPaint.setColor(outRoundColor); //外圆颜色
mPaint.setStrokeWidth(roundWidth); //线的宽度
mPaint.setStyle(Paint.Style.STROKE); //空心圆
mPaint.setAntiAlias(true); //消除锯齿
canvas.drawCircle(center, center, radius, mPaint);
//内圆
mPaint.setColor(inRoundColor);
radius = radius - roundWidth;
canvas.drawCircle(center, center, radius, mPaint);
//画进度是一个弧线
mPaint.setColor(progressBarColor);
RectF rectF = new RectF(center - radius, center - radius, center + radius, center + radius);//圆弧范围的外接矩形
canvas.drawArc(rectF, -90, mAngle, false, mPaint);
canvas.save(); //平移画布之前保存之前画的
//画进度终点的小球,旋转画布的方式实现
mPaint.setStyle(Paint.Style.FILL);
//将画布坐标原点移动至圆心
canvas.translate(center, center);
//旋转和进度相同的角度,因为进度是从-90度开始的所以-90度
canvas.rotate(mAngle - 90);
//同理从圆心出发直接将原点平移至要画小球的位置
canvas.translate(radius, 0);
canvas.drawCircle(0, 0, roundWidth, mPaint);
//画完之后恢复画布坐标
canvas.restore();
//画文字将坐标平移至圆心
canvas.translate(center, center);
mPaint.setStrokeWidth(0);
mPaint.setColor(textColor);
if (isBold) {
//字体加粗
mPaint.setTypeface(Typeface.DEFAULT_BOLD);
}
if (TextUtils.isEmpty(mText)) {
mText = mProgress + "%";
}
//动态设置文字长为圆半径,计算字体大小
float textLength = mText.length();
textSize = radius / textLength*2;
mPaint.setTextSize(textSize);
//将文字画到中间
float textWidth = mPaint.measureText(mText);
canvas.drawText(mText, -textWidth / 2, textSize / 2, mPaint);
}
public int getmProgress() {
return mProgress;
}
/**
* 设置进度
*
* @return
*/
public void setmProgress(int p) {
if (p > MAX_PROGRESS) {
mProgress = MAX_PROGRESS;
mAngle = 360;
} else {
mProgress = p;
mAngle = 360 * p / MAX_PROGRESS;
}
}
public String getmText() {
return mText;
}
/**
* 设置文本
*
* @param mText
*/
public void setmText(String mText) {
this.mText = mText;
}
/**
* 设置带动画的进度
* @param p
*/
public void setAnimProgress(int p) {
if (p > MAX_PROGRESS) {
mProgress = MAX_PROGRESS;
} else {
mProgress = p;
}
//设置属性动画
ValueAnimator valueAnimator = new ValueAnimator().ofInt(0, p);
//动画从快到慢
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.setDuration(1200);
//监听值的变化
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int currentV = (Integer) animation.getAnimatedValue();
Log.e("fwc", "current" + currentV);
mAngle = 360 * currentV / MAX_PROGRESS;
mText = currentV + "%";
invalidate();
}
});
valueAnimator.start();
}
}
arrts.xml文件:
整体样式xml如下
设置预算功能
/*设置预算*/
ys_layout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//点击弹出对话框
final EditDialog editDialog = new EditDialog(MainActivity.this);
editDialog.setTitle("请输入每月预算");
editDialog.setYesOnclickListener("确定", new EditDialog.onYesOnclickListener() {
@Override
public void onYesClick(String ys) {
if (TextUtils.isEmpty(ys)) {
Toast.makeText(MainActivity.this, "请输入每月预算", Toast.LENGTH_SHORT).show();
} else if(Double.parseDouble(ys) <= 0){
Toast.makeText(MainActivity.this, "预算必须大于0", Toast.LENGTH_SHORT).show();
} else {
Message msg = new Message();
ys_text.setText(ys);
ys_money = Double.parseDouble(ys);
save_User(ys_money);
sum_ThisMonth(month);
handlerProgress.sendEmptyMessageDelayed(1,0);
editDialog.dismiss();
/*//让软键盘隐藏
InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getView().getApplicationWindowToken(), 0);*/
}
}
});
editDialog.setNoOnclickListener("取消", new EditDialog.onNoOnclickListener() {
@Override
public void onNoClick() {
editDialog.dismiss();
}
});
editDialog.show();
}
});
本部分也比较简单,和第一部分思路相同,直接上代码
xml代码
java代码同第一部分。
本文介绍了首页的三个模块的代码,下一篇(记账APP:小哈记账5——记账首页页面的制作(2))将介绍首页中的收支趋势图的制作,使用MPAndroidChat技术实现。首页完整代码也将在下一篇文章中给出。