Copy this link when reproducing:
http://www.casperlee.com/en/y/blog/205
With 2 user-defined widgets I've created, it is easy to create the Statistics Module now. The code is very straightforward, so there is no extra explanations needed, I'll just list all the changes of the code in this article, in stead of explaining too much. Just like before, let's take it easy and enjoy a few beautiful photos first.
1. Open the file "app -> res -> values -> strings.xml", add the following strings into it:
<string name="mi_statistics_title">Statistics</string>
<string name="caption_current_month">This Month</string>
<string name="caption_current_year">This Year</string>
<string name="caption_custom">Custom</string>
<string name="caption_by_category">By Category</string>
<string name="caption_by_time">By Time</string>
<string name="caption_to">" To "</string>
2. Open the file "app -> res -> values -> string.xml (zh)", add the following strings into it:
<string name="mi_statistics_title">统计</string>
<string name="caption_current_month">当月</string>
<string name="caption_current_year">当年</string>
<string name="caption_custom">特定</string>
<string name="caption_by_category">按分类</string>
<string name="caption_by_time">按时间</string>
<string name="caption_to">" 至 "</string>
3. Open the file "app -> java -> com.casperlee.personalexpense -> dal -> ExpenditureDal", add the following code:
...
import com.casperlee.personalexpense.widgets.PieViewDataItem;
import java.util.ArrayList;
import java.util.List;
...
public List<PieViewDataItem> getAccumulateByCategory(int startYear, int startMonth, int stopYear, int stopMonth) {
List<PieViewDataItem> es = new ArrayList<PieViewDataItem>();
String start = String.format("%04d%02d%02d", startYear, startMonth, 1);
String stop = String.format("%04d%02d%02d", stopYear, stopMonth, 31);
String sql = "select a.Name, sum(b.Amount) from Category a, Expenditure b"
+ " where a.ID = b.Category"
+ " and b.Day between '" + start + "' and '" + stop + "'"
+ " group by a.Name"
+ " order by a.ID";
Cursor cursor = db.rawQuery(sql, null);
try {
if (cursor.getCount() <= 0) {
} else {
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
PieViewDataItem e = new PieViewDataItem(cursor.getString(0), cursor.getDouble(1));
es.add(e);
cursor.moveToNext();
}
}
return es;
} finally {
cursor.close();
}
}
4. Open the file "app -> java -> com.casperlee.personalexpense -> bll -> ExpenditureBll", add the following code:
...
import com.casperlee.personalexpense.widgets.PieViewDataItem;
import java.util.Collections;
import java.util.Comparator;
...
public List<PieViewDataItem> getAccumulateByCategory(int startYear, int startMonth, int stopYear, int stopMonth) {
List<PieViewDataItem> ret = ExpenditureDal.getInstance().getAccumulateByCategory(startYear, startMonth, stopYear, stopMonth);
Collections.sort(ret, new Comparator<PieViewDataItem>() {
@Override
public int compare(PieViewDataItem arg0, PieViewDataItem arg1) {
if (arg0.Value == arg1.Value) {
return 0;
} else if (arg0.Value < arg1.Value) {
return 1;
} else {
return -1;
}
}
});
return ret;
}
5. Add a new class named "StatisticsActivity" in the folder "app -> java -> com.casperlee.personalexpense", and put the following code in:
package com.casperlee.personalexpense;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.RadioGroup;
import android.widget.Spinner;
import com.casperlee.personalexpense.bll.ExpenditureBll;
import com.casperlee.personalexpense.global.GlobalVars;
import com.casperlee.personalexpense.widgets.PieViewAdapter;
import com.casperlee.personalexpense.widgets.PieViewDataItem;
import com.casperlee.personalexpense.widgets.TabHeaderView;
import com.casperlee.personalexpense.widgets.PieView;
import java.util.List;
public class StatisticsActivity extends AppCompatActivity {
// Constants
public static final int REQUEST_SELECT_DATE_RANGE = 0x1000;
// Fields
private int currentYear;
private int currentMonth;
private int currentDay;
private Layout ui;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_statistics);
this.ui = new Layout();
this.readExtras();
this.initWidgets();
this.setListeners();
}
@Override
protected void onResume() {
if (getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
super.onResume();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case REQUEST_SELECT_DATE_RANGE:
int startYear = data.getIntExtra(SelectDateRangeActivity.KEY_START_YEAR, this.currentYear);
int startMonth = data.getIntExtra(SelectDateRangeActivity.KEY_START_MONTH, this.currentMonth);
int stopYear = data.getIntExtra(SelectDateRangeActivity.KEY_STOP_YEAR, this.currentYear);
int stopMonth = data.getIntExtra(SelectDateRangeActivity.KEY_STOP_MONTH, this.currentMonth);
this.doCategoryAccumulation(startYear, startMonth, stopYear, stopMonth);
break;
default:
break;
}
}
// Private classes
private void readExtras() {
Intent intent = this.getIntent();
this.currentYear = intent.getIntExtra(GlobalVars.KEY_YEAR, 1978);
this.currentMonth = intent.getIntExtra(GlobalVars.KEY_MONTH, 9);
this.currentDay = intent.getIntExtra(GlobalVars.KEY_DAY, 24);
}
private void initWidgets() {
ui.TabHeader.setTabCount(2);
ui.TabHeader.setTabCaption(
0,
getApplicationContext().getResources().getString(R.string.caption_by_category));
ui.TabHeader.setTabCaption(
1,
getApplicationContext().getResources().getString(R.string.caption_by_time));
ui.TabHeader.setVisibility(View.GONE);
ui.CategorySpinner.setVisibility(View.GONE);
this.performRadioButtonCheckedChanged(R.id.CurrentMonthRadioButton);
}
private void setListeners() {
OnMyCheckedChangeListener l = new OnMyCheckedChangeListener();
ui.RangeRadioGroup.setOnCheckedChangeListener(l);
OnMyTabChangeListener l2 = new OnMyTabChangeListener();
ui.TabHeader.setTabChangeListener(l2);
}
private void doCategoryAccumulation(int startYear, int startMonth, int stopYear, int stopMonth) {
List<PieViewDataItem> data = ExpenditureBll.getInstance().getAccumulateByCategory(startYear, startMonth, stopYear, stopMonth);
PieViewAdapter adapter = new PieViewAdapter(data);
this.ui.PieChart.setAdapter(adapter);
adapter.setTitle(String.format("%04d.%02d - %04d.%02d", startYear, startMonth, stopYear, stopMonth));
adapter.notifyDatasetChanged();
}
private void performRadioButtonCheckedChanged(int checkedId) {
switch (checkedId) {
case R.id.CurrentMonthRadioButton:
this.doCategoryAccumulation(this.currentYear, this.currentMonth, this.currentYear, this.currentMonth);
break;
case R.id.CurrentYearRadioButton:
this.doCategoryAccumulation(this.currentYear, 1, this.currentYear, 12);
break;
case R.id.CustomRadioButton:
Intent intent = new Intent();
intent.setClass(StatisticsActivity.this, SelectDateRangeActivity.class);
intent.putExtra(SelectDateRangeActivity.KEY_START_YEAR, this.currentYear);
intent.putExtra(SelectDateRangeActivity.KEY_START_MONTH, 1);
intent.putExtra(SelectDateRangeActivity.KEY_START_DAY, 1);
intent.putExtra(SelectDateRangeActivity.KEY_STOP_YEAR, this.currentYear);
intent.putExtra(SelectDateRangeActivity.KEY_STOP_MONTH, 12);
intent.putExtra(SelectDateRangeActivity.KEY_STOP_DAY, 1);
intent.putExtra(SelectDateRangeActivity.KEY_IGNORE_DAY, true);
this.startActivityForResult(intent, REQUEST_SELECT_DATE_RANGE);
break;
}
ui.PieChart.getAdapter().notifyDatasetChanged();
}
private void performTabChanged(int tabIndex) {
switch (tabIndex) {
case 0:
ui.CategorySpinner.setVisibility(View.GONE);
break;
case 1:
ui.CategorySpinner.setVisibility(View.VISIBLE);
break;
}
}
// Classes
private class Layout {
// Fields
public TabHeaderView TabHeader = null;
public RadioGroup RangeRadioGroup = null;
public Spinner CategorySpinner = null;
public PieView PieChart = null;
// Constructors
public Layout() {
this.initialize();
this.bindSpinners();
}
// Methods
private void initialize() {
this.TabHeader = (TabHeaderView)StatisticsActivity.this.findViewById(R.id.TabHeader);
this.RangeRadioGroup = (RadioGroup)StatisticsActivity.this.findViewById(R.id.RangeRadioGroup);
this.CategorySpinner = (Spinner)StatisticsActivity.this.findViewById(R.id.CategorySpinner);
this.PieChart = (PieView)StatisticsActivity.this.findViewById(R.id.PieChart);
}
private void bindSpinners() {
String[] items = new String[GlobalVars.Categories.length];
items[0] = "所有";
for (int i = 1; i < items.length; i++) {
items[i] = GlobalVars.Categories[i - 1].getName();
}
this.bindSpinner(this.CategorySpinner, items);
}
private void bindSpinner(Spinner spinner, String[] items) {
ArrayAdapter<String> adapter = new ArrayAdapter<String>(
StatisticsActivity.this,
android.R.layout.simple_spinner_item, items);
adapter.setDropDownViewResource(android.R.layout.select_dialog_singlechoice);
spinner.setAdapter(adapter);
}
}
private class OnMyCheckedChangeListener implements RadioGroup.OnCheckedChangeListener {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (group == StatisticsActivity.this.ui.RangeRadioGroup) {
StatisticsActivity.this.performRadioButtonCheckedChanged(checkedId);
}
}
}
private class OnMyTabChangeListener implements TabHeaderView.OnTabChangeListener {
@Override
public boolean onTabChanging(int tabIndex) {
return true;
}
@Override
public void onTabChanged(int tabIndex) {
StatisticsActivity.this.performTabChanged(tabIndex);
}
}
}
Here is the content of its layout ("app -> res -> layout -> activity_statistics.xml"):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_statistics"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF000000"
android:orientation="vertical"
android:padding="3dp"
tools:context="com.casperlee.personalexpense.StatisticsActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:padding="6dp" >
<com.casperlee.personalexpense.widgets.TabHeaderView
android:id="@+id/TabHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
</com.casperlee.personalexpense.widgets.TabHeaderView>
</LinearLayout>
<RadioGroup
android:id="@+id/RangeRadioGroup"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp">
<RadioButton
style="@style/MediumWhiteText"
android:id="@+id/CurrentMonthRadioButton"
android:checked="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/caption_current_month" />
<RadioButton
style="@style/MediumWhiteText"
android:id="@+id/CurrentYearRadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/caption_current_year" />
<RadioButton
style="@style/MediumWhiteText"
android:id="@+id/CustomRadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/caption_custom" />
</RadioGroup>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<Spinner
android:id="@+id/CategorySpinner"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="6dp">
<com.casperlee.personalexpense.widgets.PieView
android:id="@+id/PieChart"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="6dp">
</com.casperlee.personalexpense.widgets.PieView>
</LinearLayout>
</LinearLayout>
6. Add a new class named "SelectDateRangeActivity" in the folder "app -> java -> com.casperlee.personalexpense", and put the following code in:
package com.casperlee.personalexpense;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
public class SelectDateRangeActivity extends AppCompatActivity {
// Constants
public static final String KEY_START_YEAR = "StartYear";
public static final String KEY_START_MONTH = "StartMonth";
public static final String KEY_START_DAY = "StartDay";
public static final String KEY_STOP_YEAR = "StopYear";
public static final String KEY_STOP_MONTH = "StopMonth";
public static final String KEY_STOP_DAY = "StopDay";
public static final String KEY_IGNORE_DAY = "IgnoreDay";
public static final int RESULT_DATE_RANGE = 0x1001;
// Fields
private int startYear;
private int startMonth;
private int startDay;
private int stopYear;
private int stopMonth;
private int stopDay;
private boolean ignoreDay;
private Layout ui;
private Intent intent;
// Entrances
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_select_date_range);
this.ui = new Layout();
this.readExtras();
this.initWidgets();
this.setListeners();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
this.doSetResult();
}
return super.onKeyDown(keyCode, event);
}
// Private methods
private void readExtras() {
intent = this.getIntent();
this.startYear = intent.getIntExtra(SelectDateRangeActivity.KEY_START_YEAR, 1978);
this.startMonth = intent.getIntExtra(SelectDateRangeActivity.KEY_START_MONTH, 9);
this.startDay = intent.getIntExtra(SelectDateRangeActivity.KEY_START_DAY, 24);
this.stopYear = intent.getIntExtra(SelectDateRangeActivity.KEY_STOP_YEAR, 1978);
this.stopMonth = intent.getIntExtra(SelectDateRangeActivity.KEY_STOP_MONTH, 9);
this.stopDay = intent.getIntExtra(SelectDateRangeActivity.KEY_STOP_DAY, 24);
this.ignoreDay = intent.getBooleanExtra(SelectDateRangeActivity.KEY_IGNORE_DAY, true);
}
private void initWidgets() {
if (this.ignoreDay) {
ui.StartDaySpinner.setVisibility(View.GONE);
ui.StopDaySpinner.setVisibility(View.GONE);
} else {
ui.StartDaySpinner.setVisibility(View.VISIBLE);
ui.StopDaySpinner.setVisibility(View.VISIBLE);
}
ui.StartYearSpinner.setSelection(this.startYear - 2000);
ui.StartMonthSpinner.setSelection(this.startMonth - 1);
ui.StartDaySpinner.setSelection(this.startDay - 1);
ui.StopYearSpinner.setSelection(this.stopYear - 2000);
ui.StopMonthSpinner.setSelection(this.stopMonth - 1);
ui.StopDaySpinner.setSelection(this.stopDay - 1);
}
private void setListeners() {
this.setSpinnerItemSelectedListeners();
this.setViewClickListeners();
}
private void setSpinnerItemSelectedListeners() {
OnMyItemSelectedListener l = new OnMyItemSelectedListener();
this.ui.StartYearSpinner.setOnItemSelectedListener(l);
this.ui.StartMonthSpinner.setOnItemSelectedListener(l);
this.ui.StartDaySpinner.setOnItemSelectedListener(l);
this.ui.StopYearSpinner.setOnItemSelectedListener(l);
this.ui.StopMonthSpinner.setOnItemSelectedListener(l);
this.ui.StopDaySpinner.setOnItemSelectedListener(l);
}
private void setViewClickListeners() {
OnMyViewClickListener l = new OnMyViewClickListener();
this.ui.OkButton.setOnClickListener(l);
}
private void doSetResult() {
intent.putExtra(SelectDateRangeActivity.KEY_START_YEAR, ui.StartYearSpinner.getSelectedItemPosition() + 2000);
intent.putExtra(SelectDateRangeActivity.KEY_START_MONTH, ui.StartMonthSpinner.getSelectedItemPosition() + 1);
intent.putExtra(SelectDateRangeActivity.KEY_START_DAY, ui.StartDaySpinner.getSelectedItemPosition() + 1);
intent.putExtra(SelectDateRangeActivity.KEY_STOP_YEAR, ui.StopYearSpinner.getSelectedItemPosition() + 2000);
intent.putExtra(SelectDateRangeActivity.KEY_STOP_MONTH, ui.StopMonthSpinner.getSelectedItemPosition() + 1);
intent.putExtra(SelectDateRangeActivity.KEY_STOP_DAY, ui.StopDaySpinner.getSelectedItemPosition() + 1);
this.setResult(RESULT_DATE_RANGE, this.intent);
}
// Private classes
private class Layout {
// Widgets
public Spinner StartYearSpinner = null;
public Spinner StartMonthSpinner = null;
public Spinner StartDaySpinner = null;
public Spinner StopYearSpinner = null;
public Spinner StopMonthSpinner = null;
public Spinner StopDaySpinner = null;
public Button OkButton = null;
// Constructors
public Layout() {
this.initialize();
this.bindSpinners();
}
// Private methods
private void initialize() {
this.StartYearSpinner = (Spinner)SelectDateRangeActivity.this.findViewById(R.id.StartYearSpinner);
this.StartMonthSpinner = (Spinner)SelectDateRangeActivity.this.findViewById(R.id.StartMonthSpinner);
this.StartDaySpinner = (Spinner)SelectDateRangeActivity.this.findViewById(R.id.StartDaySpinner);
this.StopYearSpinner = (Spinner)SelectDateRangeActivity.this.findViewById(R.id.StopYearSpinner);
this.StopMonthSpinner = (Spinner)SelectDateRangeActivity.this.findViewById(R.id.StopMonthSpinner);
this.StopDaySpinner = (Spinner)SelectDateRangeActivity.this.findViewById(R.id.StopDaySpinner);
this.OkButton = (Button)SelectDateRangeActivity.this.findViewById(R.id.OkButton);
}
private void bindSpinners() {
String[] years = new String[100];
for (int i = 0; i < 100; i++) {
years[i] = String.format("%04d", i + 2000);
}
String[] months = new String[12];
for (int i = 0; i < 12; i++) {
months[i] = String.format("%02d", i + 1);
}
String[] days = new String[31];
for (int i = 0; i < 31; i++) {
days[i] = String.format("%02d", i + 1);
}
this.bindSpinner(this.StartYearSpinner, years);
this.bindSpinner(this.StartMonthSpinner, months);
this.bindSpinner(this.StartDaySpinner, days);
this.bindSpinner(this.StopYearSpinner, years);
this.bindSpinner(this.StopMonthSpinner, months);
this.bindSpinner(this.StopDaySpinner, days);
}
private void bindSpinner(Spinner spinner, String[] items) {
ArrayAdapter<String> adapter = new ArrayAdapter<String>(
SelectDateRangeActivity.this,
android.R.layout.simple_spinner_item, items);
adapter.setDropDownViewResource(android.R.layout.select_dialog_singlechoice);
spinner.setAdapter(adapter);
}
}
private class OnMyItemSelectedListener implements AdapterView.OnItemSelectedListener {
@Override
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
}
private class OnMyViewClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
SelectDateRangeActivity.this.doSetResult();
SelectDateRangeActivity.this.finish();
}
}
}
Here is the content of its layout ("app -> res -> layout -> activity_select_date_range.xml"):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_select_date_range"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#FF000000"
android:gravity="center"
tools:context="com.casperlee.personalexpense.SelectDateRangeActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<Spinner
android:id="@+id/StartYearSpinner"
android:theme="@android:style/Theme.Light"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Spinner
android:id="@+id/StartMonthSpinner"
android:theme="@android:style/Theme.Light"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Spinner
android:id="@+id/StartDaySpinner"
android:theme="@android:style/Theme.Light"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:padding="32dp">
<TextView
style="@style/LargeWhiteText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/caption_to" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<Spinner
android:id="@+id/StopYearSpinner"
android:theme="@android:style/Theme.Light"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Spinner
android:id="@+id/StopMonthSpinner"
android:theme="@android:style/Theme.Light"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Spinner
android:id="@+id/StopDaySpinner"
android:theme="@android:style/Theme.Light"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:padding="32dp">
<Button
android:id="@+id/OkButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btn_ok_caption"
android:textAppearance="?android:attr/textAppearanceMedium"/>
</LinearLayout>
</LinearLayout>
7. Open the file "app -> res -> menu-> main.xml", add the following menu item into it:
<item android:id="@+id/miStatistics" android:title="@string/mi_statistics_title"/>
8. Open the file "app -> java -> com.casperlee.personalexpense -> AddExpenseActivity", add the following code:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Intent intent;
switch (item.getItemId()) {
case R.id.miStatistics:
intent = new Intent();
intent.setClass(AddExpenseActivity.this, StatisticsActivity.class);
intent.putExtra(GlobalVars.KEY_YEAR, this.currentYear);
intent.putExtra(GlobalVars.KEY_MONTH, this.currentMonth);
intent.putExtra(GlobalVars.KEY_DAY, this.currentDay);
this.startActivity(intent);
break;
...
}
...
private void PerformTotalMoneyTextClick() {
...
if (this.currentYear == c.get(Calendar.YEAR)
&& this.currentMonth == c.get(Calendar.MONTH) + 1
&& this.currentDay == c.get(Calendar.DAY_OF_MONTH)) {
...
} else {
intent.setClass(this, StatisticsActivity.class);
}
...
}
9. Done!