ともちゃんのアプリ開発日記

組込みC言語プログラマだったともちゃんが、四苦八苦しながら、AndroidのJAVA/Kotlin、iOSのSwiftUIを習得して行きます。ともちゃんの備忘録も兼ねています。

プログレスダイアログ

1.前処理

progressDialog = new ProgressDialog(dataCenter.getExportImportContext());
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setMessage("処理を実行しています");
progressDialog.setCancelable(true);
progressDialog.show();

 

 2.実行中

progressDialog.setMax(200);
int count;
for (count=1; count<=200; count++ ) {
progressDialog.setProgress(count);
}

 

 3.後処理

progressDialog.dismiss();

 

 

マルチスレッド

1.マルチスレッドのクラス定義

static class AsyncExportProgress extends AsyncTask<Void, Void, String> {

ProgressDialog progressDialog;

@Override
protected void onPreExecute() {
// ここに前処理を記述します
// 例) プログレスダイアログ表示
DataCenter dataCenter = new DataCenter();
progressDialog = new ProgressDialog(dataCenter.getExportImportContext());
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setMessage("処理を実行しています");
progressDialog.setCancelable(true);
progressDialog.show();
}
@Override
protected String doInBackground(Void... arg0) {
FileIO fileIO = new FileIO();
DataCenter dataCenter = new DataCenter();
fileIO.exportFile(dataCenter.getExportImportContext(), progressDialog);
return "Thread Samples";
}
@Override
protected void onPostExecute(String result) {
// バックグランド処理終了後の処理をここに記述します
// 例) プログレスダイアログ終了
// UIコンポーネントへの処理
progressDialog.dismiss();
}
}

 

 2.スレッドの呼び出し側

AsyncExportProgress asyncExportProgress = new AsyncExportProgress();
asyncExportProgress.execute();

 

 

暗黙的インテントによるファイルの読み込み

1.マニフェスト

CSVファイルを選択したときに、アプリが起動するようにするために。

<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/*" />
</intent-filter>

 

これだと、テキストファイ選択したときにアプリが起動するんだけど、CSVファイルに限定するやり方がわからなかった。

 

2.インポートメソッドソースコード

public int importFile(Context context, Uri importUri, ProgressDialog progressDialog) {
// File srcFile = new File(CSVfile);
// InputStream inputFile;
try {
InputStream inputStream = context.getContentResolver().openInputStream(importUri);
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader reader = new BufferedReader(inputStreamReader);
String AcpplicationTitle = reader.readLine();
if (!AcpplicationTitle.equals(context.getText(R.string.app_name).toString())) {
return NOT_DIET_REC_FILE;
}
String strVersionCode = reader.readLine();

String[] strs;
strs = strVersionCode.split(",");
int version = Integer.valueOf(strs[1]);
switch (version) {
case 1:
String PrefecensesTitle = reader.readLine();
String PrefecensesData = reader.readLine();
strs = PrefecensesData.split(",");
if (strs.length != 4) {
return INVALID_PREFERENCE_DATA_COUNT;
}
float targetTall = Float.valueOf(strs[0]);
float targetWeight = Float.valueOf(strs[1]);
float targetBodyFatPercentage = Float.valueOf(strs[2]);
float targetBMI = Float.valueOf(strs[3]);
SharedPreferences prefs = context.getSharedPreferences("setting", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putFloat("TargetTall", targetTall);
editor.putFloat("TargetWeight", targetWeight);
editor.putFloat("TargetBodyFatPercentage", targetBodyFatPercentage);
editor.putFloat("TargetBMI", targetBMI);
// editor.commit();
editor.apply();

String strDataCount = reader.readLine();
strs = strDataCount.split(",");
long dataCount = Long.valueOf(strs[1]);
String DataTitle = reader.readLine();
DatabaseManager databaseManager = new DatabaseManager();
progressDialog.setMax((int) dataCount);
for (long i = 0; i < dataCount; i++) {
String strData = reader.readLine();
strs = strData.split(",");
switch (strs.length) {
case 11:
databaseManager.replaceDatabase(
strs[0], // 日付
Float.valueOf(strs[1]), // 体重
Float.valueOf(strs[2]), // 体脂肪率
Float.valueOf(strs[3]), // BMI
Float.valueOf(strs[4]), // 目標体重
Float.valueOf(strs[5]), // 目標体脂肪率
Float.valueOf(strs[6]), // 目標BMI
Integer.valueOf(strs[7]), // スタンプ●
Integer.valueOf(strs[8]), // スタンプ×
Integer.valueOf(strs[9]), // スタンプ▲
Integer.valueOf(strs[10]), // スタンプ★
""); // メモ
break;
case 12:
databaseManager.replaceDatabase(
strs[0], // 日付
Float.valueOf(strs[1]), // 体重
Float.valueOf(strs[2]), // 体脂肪率
Float.valueOf(strs[3]), // BMI
Float.valueOf(strs[4]), // 目標体重
Float.valueOf(strs[5]), // 目標体脂肪率
Float.valueOf(strs[6]), // 目標BMI
Integer.valueOf(strs[7]), // スタンプ●
Integer.valueOf(strs[8]), // スタンプ×
Integer.valueOf(strs[9]), // スタンプ▲
Integer.valueOf(strs[10]), // スタンプ★
strs[11]); // メモ
break;
default:
return INVALID_DB_DATA_COUNT;
}
progressDialog.setProgress((int) i);
try {
Thread.sleep(WAIT_TIME); //200ミリ秒Sleepする
} catch (InterruptedException e) {
}
}
break;
}
} catch(FileNotFoundException e) {
return FAIL_IN_FILE_OPEN;
} catch(IOException e) {
return FAIL_IN_FILE_READ;
}

return SUCCESS;
}

 

 3.MainActivityのonCreateの追加部分

if (getIntent().getAction() == Intent.ACTION_VIEW) {
Uri uri = getIntent().getData();
dataCenter.setImportUri(uri);
ExportImportFragment.AsyncImportProgress asyncImportProgress =new ExportImportFragment.AsyncImportProgress();
asyncImportProgress.execute();
}

 

 4.インポートのAsyncタスク部分

public static class AsyncImportProgress extends AsyncTask<Void, Void, String> {

ProgressDialog progressDialog;

@Override
protected void onPreExecute() {
// ここに前処理を記述します
// 例) プログレスダイアログ表示
DataCenter dataCenter = new DataCenter();
progressDialog = new ProgressDialog(dataCenter.getImportContext());
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setMessage("処理を実行しています");
progressDialog.setCancelable(false);
progressDialog.show();
}
@Override
protected String doInBackground(Void... arg0) {
FileIO fileIO = new FileIO();
DataCenter dataCenter = new DataCenter();
int result = fileIO.importFile(dataCenter.getImportContext(), dataCenter.getImportUri(), progressDialog);

switch (result) {
case FAIL_IN_FILE_OPEN:
return "ファイルをオープンできませんでした";
case FAIL_IN_FILE_READ:
return "ファイルを読み込みできませんでした";
case NOT_DIET_REC_FILE:
return "ダイエットレコーダーのデータファイルではありません";
case INVALID_PREFERENCE_DATA_COUNT:
return "設定データの個数が不正です";
case INVALID_DB_DATA_COUNT:
return "データベースのデータの個数が不正です";
default:
return "成功";
}
}

@Override
protected void onPostExecute(String result) {
// バックグランド処理終了後の処理をここに記述します
// 例) プログレスダイアログ終了
// UIコンポーネントへの処理
progressDialog.dismiss();

DataCenter dataCenter = new DataCenter();
Toast.makeText(dataCenter.getImportContext(), result, Toast.LENGTH_LONG).show();

SimpleDateFormat sdf = new SimpleDateFormat("yyyy/M/d");
Date date = new Date();
String strDate = sdf.format(date);
DataMainFragment dataMainFragment = new DataMainFragment();
// DataCenter dataCenter = new DataCenter();
dataMainFragment.displayData(dataCenter.getDataMainView(), strDate);

}
}

 

 

データファイルの書き出しとファイル添付

1.マニフェスト

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

 

 2.ソースコード

public static class FileIO {

private final String EXPORT_FILE ="DietRecData.csv";

public FileIO () {
}

public void exportFile (Context context) {

String dir1 = Environment.getExternalStorageDirectory().getPath() + "/Android/data/"+ context.getPackageName();
File fDir1 = new File(dir1);
if (!fDir1.exists()) {
boolean result = fDir1.mkdir();
if (!result) {
Toast.makeText(context, "フォルダ作成失敗", Toast.LENGTH_LONG).show();
return;
}
}

String dir2 = dir1 + "/files";
File fDir2 = new File(dir2);
if (!fDir2.exists()) {
boolean result = fDir2.mkdir();
if (!result) {
Toast.makeText(context, "フォルダ作成失敗", Toast.LENGTH_LONG).show();
return;
}
}

String filePath = dir2 + "/" + EXPORT_FILE;
File dstFile = new File(filePath);
OutputStream outputFile ;
try {
outputFile = new FileOutputStream(dstFile);
PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputFile, "UTF-8"));
writer.append("Diet Rec\n");
writer.append("データファイルバージョンコード=1\n");
writer.close();
} catch (FileNotFoundException e) {
Toast.makeText(context, "ファイルをオープン出来ませんでした。",Toast.LENGTH_SHORT).show();
} catch (IOException e) {
Toast.makeText(context, "ファイルに書き込み出来ませんでした。", Toast.LENGTH_SHORT).show();
}

Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.setData(Uri.parse("mailto:"));
intent.putExtra(Intent.EXTRA_SUBJECT, "Diet Rec データ");
// CSVファイルを添付
intent.setType("text/plain");
String filePath2 = "file://" + filePath;
intent.putExtra(Intent.EXTRA_STREAM, Uri.parse(filePath2));
context.startActivity(intent);

}
}

 

 フォルダは1階層ごとに作成しないと、エラーになります。

AChartEngineでグラフを描く

グラフィックライブラリに、AFrecchartを使っていたのですが、どうしてもデータのある箇所に点(Shape)が出せなかったのと、ライセンスがLGPLであり、Androidアプリだとソースを公開しなければならない可能性もあるので、AChartEngineに変更しました。

0.準備

http://repository-achartengine.forge.cloudbees.com/snapshot/org/achartengine/achartengine/1.2.0/

から

achartengine-1.2.0.jar

をダウンロードし、プロジェクトのlibsフォルダにコピーする。

 

1.build.gradleへの追加

dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:design:23.1.1'
compile 'com.android.support:support-v4:23.1.1'
compile files('libs/achartengine-1.2.0.jar')
}

 

2.XML

LinearLayoutを作ればいいです。

<LinearLayout
android:id="@+id/chart"
android:layout_width="wrap_content"
android:layout_height="350dp"
android:orientation="horizontal"
android:layout_marginTop="100dp" >
</LinearLayout>

 

 

3.ソースコード

public class MainActivity extends AppCompatActivity {

private final int HALF_DAY = 12 * 3600 * 1000;

private LinearLayout chartlayout;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

chartlayout = (LinearLayout) findViewById(R.id.chart);
GraphicalView graphicalView = TimeChartView();
chartlayout.removeAllViews();
chartlayout.addView(graphicalView);

}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();

//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}

return super.onOptionsItemSelected(item);
}

private GraphicalView TimeChartView() {
// (1)グラフデータの準備
// X軸 日付
String [] xStrValue={ "2015/12/22","2015/12/21","2015/12/20","2015/12/18" };
// Y軸 体重
double[] yDoubleValue={ 71.2, 71.8, 70.8, 70.4 };
// X軸 日付
String [] xStrValueTarget={ "2015/12/22","2015/12/21","2015/12/20","2015/12/19", "2015/12/18"};
// Y軸 目標体重
double[] yDoubleValueTarget={ 70.3, 70.3, 70.3, 70.3, 70.3 };

// 日付を文字形式から Date型へ変換
int DataCount=xStrValue.length;
Date[] xDateValue = new Date[DataCount];
for (int i = 0; i < DataCount; i++) {
xDateValue[i] =String2date(xStrValue[i]);
}

int DataCountTarget=xStrValueTarget.length;
Date[] xDateValueTarget = new Date[DataCountTarget];
for (int i = 0; i < DataCountTarget; i++) {
xDateValueTarget[i] =String2date(xStrValueTarget[i]);
}

// (2) グラフのタイトル、X軸Y軸ラベル、色等の設定
XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer();
renderer.setChartTitle("体重"); // グラフタイトル
renderer.setChartTitleTextSize(25); //
renderer.setXTitle("日付"); // X軸タイトル
renderer.setYTitle("体重(Kg)"); // Y軸タイトル
renderer.setAxisTitleTextSize(25); //
renderer.setLegendTextSize(25); // 凡例 テキストサイズ
renderer.setPointSize(7f); // ポイントマーカーサイズ
renderer.setXAxisMin(xDateValue[xDateValue.length - 1].getTime() - HALF_DAY); // X軸最小値
renderer.setXAxisMax(xDateValue[0].getTime() + HALF_DAY); // X軸最大値
renderer.setYAxisMin(70.0f); // Y軸最小値
renderer.setYAxisMax(72.0f); // Y軸最大値
renderer.setXLabelsAlign(Paint.Align.CENTER); // X軸ラベル配置位置
renderer.setYLabelsAlign(Paint.Align.RIGHT); // Y軸ラベル配置位置
renderer.setAxesColor(Color.BLACK); // X軸、Y軸カラー
renderer.setLabelsColor(Color.BLACK); // ラベルカラー
renderer.setXLabelsColor(Color.BLACK);
renderer.setYLabelsColor(0, Color.BLACK);
renderer.setXLabels(5); // X軸ラベルのおおよその数
renderer.setYLabels(5); // Y軸ラベルのおおよその数
renderer.setLabelsTextSize(25); // ラベルサイズ
// グリッド表示
renderer.setShowGrid(true);
// グリッド色
renderer.setGridColor(Color.parseColor("#808080")); // グリッドカラー
// グラフ描画領域マージン top, left, bottom, right
renderer.setMargins(new int[]{40, 100, 15, 40}); //
// 凡例非表示
// renderer.setShowLegend(false);
renderer.setMarginsColor(Color.rgb(128, 128, 128));
// マージンを凡例にフィットさせる
renderer.setFitLegend(true);

// (3) データ系列の色、マーク等の設定
XYSeriesRenderer r1 = new XYSeriesRenderer();
r1.setColor(Color.rgb(255, 166, 0));
r1.setPointStyle(PointStyle.CIRCLE);
r1.setLineWidth(5f);
r1.setFillPoints(true);
renderer.addSeriesRenderer(r1);
XYSeriesRenderer r2 = new XYSeriesRenderer();
r2.setColor(Color.rgb(255, 0, 0));
r2.setPointStyle(PointStyle.SQUARE);
r2.setLineWidth(5f);
r2.setFillPoints(true);
renderer.addSeriesRenderer(r2);


// (4) データ系列 データの設定 (体重)
XYMultipleSeriesDataset dataset = new XYMultipleSeriesDataset();
TimeSeries series1 = new TimeSeries("体重"); // データ系列凡例
for (int i = 0; i < DataCount; i++) {
series1.add(xDateValue[i], yDoubleValue[i]);
}
dataset.addSeries(series1);

// (4) データ系列 データの設定 (目標値)
TimeSeries seriesTarget = new TimeSeries("目標値"); //
for (int i = 0; i < DataCountTarget; i++) {
seriesTarget.add(xDateValueTarget[i], yDoubleValueTarget[i]);
}
dataset.addSeries(seriesTarget);

// (5)タイムチャートグラフの作成
GraphicalView mChartView= ChartFactory.getTimeChartView(this, dataset, renderer, "M/d");

return mChartView;

}

/*
* 日付時刻文字列を Date型に変換
*/
private Date String2date(String strDate) {
Date dateDate=null;
// 日付文字列→date型変換フォーマットを指定して
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy/M/d");

try {
dateDate = sdf1.parse(strDate);
}
catch (ParseException e) {
Toast.makeText(this, "正しい日付を入力して下さい", Toast.LENGTH_SHORT).show();
}
return dateDate;
}

}

 

 これで描いたグラフが下記です。

f:id:uchida001tmhr:20151222201408p:plain

列挙型enumの使い方

enumの定義は以下の様に行います。

private enum CalendarAction {
NEXT,
PREVIOUS,
TODAY,
STAY
}

 

 使用時には、以下の様です。

switch (action) {
case NEXT:
cal_start.add(Calendar.DATE, DISPLAY_PERIOD_DAY);
break;
case PREVIOUS:
cal_start.add(Calendar.DATE, -(DISPLAY_PERIOD_DAY));
break;
case TODAY:
cal_start = dataCenter.newCalendarGrpah();
break;
}