這篇再來寫一次圖表!
不過這次的內容是"實時"圖表~
什麼是"實時"圖表呢?
就是指此圖表在某些變數產生的當下會瞬間反應成曲線的圖表,常見的功能像是音量感測、光線感測等等
那麼像我待的這種成天在搞Sensor的公司來說
除了要有什麼溫濕曲線圖之外,實時的噪音曲線開發或是光感曲線開發也是hen重要滴(-‿◦)
好喔,那麼廢話也不講太多,來看看範例就知道囉
Github在這邊
→https://github.com/thumbb13555/RealtimeChartDemo/tree/master
1. 載入庫/畫介面
今天的功能是採用一款名為"MPChart"的第三方函式庫完成的功能
我在之前的文章中有淺談過這個UI框架,可以去看看
→碼農日常-『Android studio』使用MPChart第三方資料庫在Android裝置上繪製圖表
在這篇內容中,我主要展示的是基本的如何使用這個MPChart製作一個圖表
而在當時我也已經說過了我未來可能~或許~Maybe~おそらく~かも知れない
會寫一篇關於"即時更新"圖表的文章
所以,今天就來拆掉這個Flag了(;´д`)ゞ
1-1 載入庫
好,那就一樣來載入函式庫吧
首先這是MPChart的Github
→https://github.com/PhilJay/MPAndroidChart
然後,請開啟Android Projects 中的build.gradle
載入以下粉底白字內容
apply plugin: 'com.android.application' android { compileSdkVersion 29 buildToolsVersion "29.0.3" defaultConfig { applicationId "com.noahliu.realtimechartdemo" minSdkVersion 23 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } repositories { maven { url 'https://jitpack.io' } } dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' }
注意⚠️
repositories {
maven { url 'https://jitpack.io' }
}
這個一定要加入哦,不然會無法編譯
最後一樣按下Sync now 完成
1-2 畫介面
這次的介面如下
基本上就是一個圖表元件+三個按鈕
我就直接貼囉
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <com.github.mikephil.charting.charts.LineChart android:layout_width="0dp" android:layout_height="0dp" android:id="@+id/lineChart" app:layout_constraintBottom_toTopOf="@+id/guideline" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.9" /> <Button android:id="@+id/button_RunData" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:text="跑隨機數據" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@+id/guideline" /> <Button android:id="@+id/button_Stop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="8dp" android:text="停止" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/button_RunData" app:layout_constraintTop_toTopOf="@+id/guideline" /> <Button android:id="@+id/button_Reset" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:text="重置" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/button_Stop" app:layout_constraintTop_toTopOf="@+id/guideline" /> </androidx.constraintlayout.widget.ConstraintLayout>
2. 初始化圖表
Okay,旁枝末節都搞定了
接下來就來搞定圖表本身吧(・ω・)b
簡單地講述一下實現邏輯,在開始之前首先可以看一下這張圖
這是我們在整個圖表中所會用到的所有的類別
而整體動態圖表的實現邏輯大約如下
1. 初始化一個圖表,並在初始化同時設置一條new一個LineData
2. 獲取即時資料後,將資料先製作成Entry類
3. 最後將製作好的Entry類加入到LineData類,並執行圖表更新
好的,接著就來看一看實現的方式吧
首先來到MainActivity類
先來初始化圖表,如下
public class MainActivity extends AppCompatActivity { private boolean isRunning = false; private LineChart chart; private Thread thread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); chart = findViewById(R.id.lineChart); /**載入圖表*/ initChart(); Button btStop,btStart,btReset; btStart = findViewById(R.id.button_RunData); btStop = findViewById(R.id.button_Stop); btReset = findViewById(R.id.button_Reset); /**開始跑圖表*/ btStart.setOnClickListener(v->{ }); /**停止跑圖表*/ btStop.setOnClickListener(v->{ isRunning = false; }); /**重置圖表*/ btReset.setOnClickListener(v->{ chart.clear(); initChart(); }); } /**載入圖表*/ private void initChart(){ chart.getDescription().setEnabled(false);//設置不要圖表標籤 chart.setTouchEnabled(false);//設置不可觸碰 chart.setDragEnabled(false);//設置不可互動 //設置單一線數據 LineData data = new LineData(); data.setValueTextColor(Color.BLACK); chart.setData(data); //設置左下角標籤 Legend l = chart.getLegend(); l.setForm(Legend.LegendForm.LINE); l.setTextColor(Color.BLACK); //設置X軸 XAxis x = chart.getXAxis(); x.setTextColor(Color.BLACK); x.setDrawGridLines(true);//畫X軸線 x.setPosition(XAxis.XAxisPosition.BOTTOM);//把標籤放底部 x.setLabelCount(5,true);//設置顯示5個標籤 //設置X軸標籤內容物 x.setValueFormatter(new ValueFormatter() { @Override public String getFormattedValue(float value) { return "No. "+Math.round(value); } }); // YAxis y = chart.getAxisLeft(); y.setTextColor(Color.BLACK); y.setDrawGridLines(true); y.setAxisMaximum(100);//最高100 y.setAxisMinimum(0);//最低0 chart.getAxisRight().setEnabled(false);//右邊Y軸不可視 chart.setVisibleXRange(0,50);//設置顯示範圍 } }
至此可按下執行,就可以看得到有空圖表出現了(・ωー)~☆
3. 填入數值
接下來是第二步驟-填入數值
填入數值其實邏輯上簡單,不過因為牽涉到使用非同步執行緒的緣故
所以感覺會有點複雜Σ(・口・)
不過這邊會盡可能一步一步做,那麼開始吧
首先我們先來決定數據線的樣式
請先輸入以下內容
/**設置數據線的樣式*/ private LineDataSet createSet() { LineDataSet set = new LineDataSet(null, "隨機數據"); set.setAxisDependency(YAxis.AxisDependency.LEFT); set.setColor(Color.GRAY); set.setLineWidth(2); set.setDrawCircles(false); set.setFillColor(Color.RED); set.setFillAlpha(50); set.setDrawFilled(true); set.setValueTextColor(Color.BLACK); set.setDrawValues(false); return set; }
這裡是設置數據線要長什麼樣子,包含顏色、寬度等等資訊
再來寫關於新增數據的程式,請寫以下內容
/**新增資料*/ private void addData(float inputData){ LineData data = chart.getData();//取得原數據 ILineDataSet set = data.getDataSetByIndex(0);//取得曲線(因為只有一條,故為0,若有多條則需指定) if (set == null){ set = createSet(); data.addDataSet(set);//如果是第一次跑則需要載入數據 } data.addEntry(new Entry(set.getEntryCount(),inputData),0);//新增數據點 //更新圖表 data.notifyDataChanged(); chart.notifyDataSetChanged(); chart.setVisibleXRange(0,50);//設置可見範圍 chart.moveViewToX(data.getEntryCount());//將可視焦點放在最新一個數據,使圖表可移動 }
倒數第二步,是執行的程式
/**開始跑圖表*/ private void startRun(){ if (isRunning)return; if (thread != null) thread.interrupt(); // Runnable runnable = new Runnable() {@Override public void run() {}}; //簡略寫法 isRunning = true; Runnable runnable = ()->{ //取亂數 addData((int)(Math.random()*(60-40+1))+40); }; // thread = new Thread(new Runnable() // {@Override public void run() {runOnUiThread(runnable);}}); //簡略寫法 thread = new Thread(()->{ while (isRunning) { runOnUiThread(runnable); if (!isRunning)break; try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); }
特別注意這裡有非同步執行緒的寫法
詳情可參考這篇
→碼農日常-『Android studio』Android背景執行之Thread、Handler及AsyncTask的基礎用法
最後,使用它吧
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); chart = findViewById(R.id.lineChart); /**載入圖表*/ initChart(); Button btStop,btStart,btReset; btStart = findViewById(R.id.button_RunData); btStop = findViewById(R.id.button_Stop); btReset = findViewById(R.id.button_Reset); /**開始跑圖表*/ btStart.setOnClickListener(v->{ startRun(); }); /**停止跑圖表*/ btStop.setOnClickListener(v->{ isRunning = false; }); /**重置圖表*/ btReset.setOnClickListener(v->{ chart.clear(); initChart(); }); }
以下是全部程式
public class MainActivity extends AppCompatActivity { private boolean isRunning = false; private LineChart chart; private Thread thread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); chart = findViewById(R.id.lineChart); /**載入圖表*/ initChart(); Button btStop,btStart,btReset; btStart = findViewById(R.id.button_RunData); btStop = findViewById(R.id.button_Stop); btReset = findViewById(R.id.button_Reset); /**開始跑圖表*/ btStart.setOnClickListener(v->{ startRun(); }); /**停止跑圖表*/ btStop.setOnClickListener(v->{ isRunning = false; }); /**重置圖表*/ btReset.setOnClickListener(v->{ chart.clear(); initChart(); }); } /**開始跑圖表*/ private void startRun(){ if (isRunning)return; if (thread != null) thread.interrupt(); // Runnable runnable = new Runnable() {@Override public void run() {}}; //簡略寫法 isRunning = true; Runnable runnable = ()->{ //取亂數 addData((int)(Math.random()*(60-40+1))+40); }; // thread = new Thread(new Runnable() // {@Override public void run() {runOnUiThread(runnable);}}); //簡略寫法 thread = new Thread(()->{ while (isRunning) { runOnUiThread(runnable); if (!isRunning)break; try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); } /**載入圖表*/ private void initChart(){ chart.getDescription().setEnabled(false);//設置不要圖表標籤 chart.setTouchEnabled(false);//設置不可觸碰 chart.setDragEnabled(false);//設置不可互動 //設置單一線數據 LineData data = new LineData(); data.setValueTextColor(Color.BLACK); chart.setData(data); //設置左下角標籤 Legend l = chart.getLegend(); l.setForm(Legend.LegendForm.LINE); l.setTextColor(Color.BLACK); //設置X軸 XAxis x = chart.getXAxis(); x.setTextColor(Color.BLACK); x.setDrawGridLines(true);//畫X軸線 x.setPosition(XAxis.XAxisPosition.BOTTOM);//把標籤放底部 x.setLabelCount(5,true);//設置顯示5個標籤 //設置X軸標籤內容物 x.setValueFormatter(new ValueFormatter() { @Override public String getFormattedValue(float value) { return "No. "+Math.round(value); } }); // YAxis y = chart.getAxisLeft(); y.setTextColor(Color.BLACK); y.setDrawGridLines(true); y.setAxisMaximum(100);//最高100 y.setAxisMinimum(0);//最低0 chart.getAxisRight().setEnabled(false);//右邊Y軸不可視 chart.setVisibleXRange(0,50);//設置顯示範圍 } /**新增資料*/ private void addData(float inputData){ LineData data = chart.getData();//取得原數據 ILineDataSet set = data.getDataSetByIndex(0);//取得曲線(因為只有一條,故為0,若有多條則需指定) if (set == null){ set = createSet(); data.addDataSet(set);//如果是第一次跑則需要載入數據 } data.addEntry(new Entry(set.getEntryCount(),inputData),0);//新增數據點 // data.notifyDataChanged(); chart.notifyDataSetChanged(); chart.setVisibleXRange(0,50);//設置可見範圍 chart.moveViewToX(data.getEntryCount());//將可視焦點放在最新一個數據,使圖表可移動 } /**設置數據線的樣式*/ private LineDataSet createSet() { LineDataSet set = new LineDataSet(null, "隨機數據"); set.setAxisDependency(YAxis.AxisDependency.LEFT); set.setColor(Color.GRAY); set.setLineWidth(2); set.setDrawCircles(false); set.setFillColor(Color.RED); set.setFillAlpha(50); set.setDrawFilled(true); set.setValueTextColor(Color.BLACK); set.setDrawValues(false); return set; } }
其實前陣子有人留言給我,說是要做一個即時更新的圖表
而也就這麼剛好~我這陣子正在寫一個噪音偵測的軟體
所以就非~常順便地將實時圖表寫進去了
然後也因為最近沒有什麼靈感,所以就拿來寫了XD
那麼今天的文章到這,希望本文對你有幫助