Androidアプリ Spectrum Analyzer

# 0. はじめに
マイクから音を取得して周波数解析し、スペクトルを表示するSpectrum AnalyzerをAndroidアプリで作成。（リリースはしていません、リリースするレベルの物を作れないので）
**しかも問題点が残ったまま。**

グラフ描画ライブラリ【 [MPAndroidChart](https://github.com/PhilJay/MPAndroidChart) 】を使用。

# 1. プロジェクトにMPAndroidChartを導入
build.gradleを編集してMPAndroidChartを導入する。
build.gradleはプロジェクトのルート直下とappフォルダ内の2種類がある。
ルート直下のbuild.gradleには以下を追加

```build.gradle(Project)
allprojects {
    repositories {
        maven {
            url "https://jitpack.io"
        }
    }
}
```


appフォルダ内のbuild.gradleには以下を追加

```build.gradle(app)
dependencies {
    implementation 'com.github.PhilJay:MPAndroidChart:v3.0.3'
}
```

#2.  ソースコード

```java
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;

//MainActivity内
public LineChart lineChart;

//onCreate内
lineChart = findViewById(R.id.line_chart);
initChart();

//onCheckedChanged内（Switchを使っているので）
fft = new Thread(new Runnable() {
    @Override 
    public void run() {

        //フーリエ変換等処理後
        setData(decibelFrequencySpectrum);
    }
});
fft.start();
```

# 3. 初期化
グラフを初期化するinitChartメソッド

```java:initChart
public void initChart() {

    lineChart.setTouchEnabled(true);
    lineChart.setDragEnabled(false);

    // Grid背景色
    lineChart.setDrawGridBackground(true);

    // no description text
    lineChart.getDescription().setEnabled(true);

    lineChart.setBackgroundColor(Color.LTGRAY);

    LineData data = new LineData();
    data.setValueTextColor(Color.BLACK);

    // add empty data
    lineChart.setData(data);

    // Grid縦軸を破線
    XAxis xAxis = lineChart.getXAxis();
    xAxis.setAxisMaximum(2048);
    xAxis.setAxisMinimum(0);
    xAxis.enableGridDashedLine(10f, 10f, 0f);
    xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
    YAxis leftAxis = lineChart.getAxisLeft();
    // Y軸最大最小設定
    leftAxis.setAxisMaximum(0f);
    leftAxis.setAxisMinimum(-150f);
    // Grid横軸を破線
    leftAxis.enableGridDashedLine(10f, 10f, 0f);
    leftAxis.setDrawZeroLine(true);

    // 右側の目盛り
    lineChart.getAxisRight().setEnabled(false);
}
```

# 4. 描画
マイクで録音し、フーリエ変換したデータを描画するsetDataメソッド

```java:setData
public void setData(double[] data) {

    ArrayList<Entry> values = new ArrayList<>();

    for (int i = 0; i < data.length; i++) {
        values.add(new Entry(i, (int)data[i], null, null));
    }

    LineDataSet set1;

    if (lineChart.getData() != null && lineChart.getData().getDataSetCount() > 0) {
        set1 = (LineDataSet) lineChart.getData().getDataSetByIndex(0);
        set1.setValues(values);
        lineChart.getData().notifyDataChanged();
        lineChart.notifyDataSetChanged();
    } else {
        // create a dataset and give it a type
        set1 = new LineDataSet(values, "Spectrum");

        set1.setDrawIcons(false);
        set1.setColor(Color.rgb(0, 0, 240));
        set1. setDrawCircles(false);
        set1.setLineWidth(0.5f);
        set1.setValueTextSize(0f);
        set1.setDrawFilled(false);
        set1.setFormLineWidth(1f);
        set1.setFormLineDashEffect(new DashPathEffect(new float[]{10f, 5f}, 0f));
        set1.setFormSize(15.f);
        set1.setDrawValues(true);

        ArrayList<ILineDataSet> dataSets = new ArrayList<>();
        dataSets.add(set1); // add the datasets

        // create a data object with the datasets
        LineData lineData = new LineData(dataSets);

        // set data
        lineChart.setData(lineData);
    }
}
```

# 5. アプリ画面

### 5-1. 開始画面
ちゃんとグラフが表示。左上のSwitchを押すと録音が開始されてスペクトルが描画される。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276354/433f018d-e403-692e-b415-162327e9a44f.jpeg" width=30%/>

### 5-2. 録音時画面
Switchを押すと録音が開始されたがグラフが描画されない。Why?
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276354/1cc30aeb-6ca6-0bdb-7867-561bf799976c.jpeg" width=30%/>

### 5-3. グラフを触ってみると
描画された。手を離すとグラフが動かなくなる。また手を触れるとグラフが更新される。Why?
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276354/9e6a943c-0326-77c1-f2d5-284a6b9a263b.jpeg" width=30%/>

## 6. 改善方法を知りたい
initChart内の`lineChart.setTouchEnabled(True)`を`False`に変えてみたら、今度は全く表示されなくなった。グラフを触っても変化なし。
~~改善方法を教えてください~~

## 7. 改善した (2019/07/29)
fftスレッド内のsetDataメソッドにグラフを更新する`lineChart.invalidate()`を追加。
それでもエラーが出た。原因はメインスレッド以外のスレッドでUIを変更しようとしたことによるエラー。
そこで`Handler`を使用してメインスレッドにsetDataメソッドの処理を依頼することで解決。

新しいコードはこちら

```java
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;

//MainActivity内
public LineChart lineChart;

//onCreate内
lineChart = findViewById(R.id.line_chart);
initChart();

//onCheckedChanged内（Switchを使っているので）
fft = new Thread(new Runnable() {
    @Override 
    public void run() {

        //フーリエ変換等処理後
        handler.post(new Runnable() {
            @Override 
            public void run() {
                setData(decibelFrequencySpectrum);
            }
        });
    }
});
fft.start();
```

```java:setData
public void setData(double[] data) {

    ArrayList<Entry> values = new ArrayList<>();

    for (int i = 0; i < data.length; i++) {
        values.add(new Entry(i, (int)data[i], null, null));
    }

    LineDataSet set1;

    if (lineChart.getData() != null && lineChart.getData().getDataSetCount() > 0) {
        set1 = (LineDataSet) lineChart.getData().getDataSetByIndex(0);
        set1.setValues(values);
        lineChart.getData().notifyDataChanged();
        lineChart.notifyDataSetChanged();
        lineChart.invalidate();
    } else {
        // create a dataset and give it a type
        set1 = new LineDataSet(values, "Spectrum");

        set1.setDrawIcons(false);
        set1.setColor(Color.rgb(0, 0, 240));
        set1. setDrawCircles(false);
        set1.setLineWidth(0.5f);
        set1.setValueTextSize(0f);
        set1.setDrawFilled(false);
        set1.setFormLineWidth(1f);
        set1.setFormLineDashEffect(new DashPathEffect(new float[]{10f, 5f}, 0f));
        set1.setFormSize(15.f);
        set1.setDrawValues(true);

        ArrayList<ILineDataSet> dataSets = new ArrayList<>();
        dataSets.add(set1); // add the datasets

        // create a data object with the datasets
        LineData lineData = new LineData(dataSets);

        // set data
        lineChart.setData(lineData);
    }
}
```