Android實現(xiàn)圖表實時更新

項目里需要App端不斷地從服務(wù)器獲取數(shù)據(jù),實時生成圖表。圖表控件使用的是MPAndroidChart。自己寫了些實時更新折線圖的demo,數(shù)據(jù)是線程隨機生成的,不是后臺數(shù)據(jù)。

1、Message配合Handler實現(xiàn)

效果如下


Message配合Handler實現(xiàn).gif

在MainActivity中創(chuàng)建一個產(chǎn)生隨機數(shù)據(jù)的線程,每產(chǎn)生一個數(shù)據(jù)發(fā)送一個Message,Handler收到Message之后更新折線圖。

MainActivity代碼如下:

public class MainActivity extends AppCompatActivity {
    private static final int TAG = 1;//Message的what標識
    private TextView mTextView;
    private Button mStartButton;

    private LineChart mLineChart;
    private Data[] mDatas;
    private List<Entry> mEntries = new ArrayList<>();

    private Thread mThread;
    private Handler mHandler;
    private Random mRandom;

    private StringBuilder mStringBuilder;
    private int mEndIndex;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextView = findViewById(R.id.test_txt);
        mStartButton = findViewById(R.id.start_button);
        mLineChart = findViewById(R.id.line_chart);

        mRandom = new Random();
        mStringBuilder = new StringBuilder("現(xiàn)在Y軸數(shù)字是0哦");
        mEndIndex = 1;

        //先創(chuàng)建5個Data數(shù)據(jù)
        mDatas = new Data[]{new Data(1,5),new Data(2,8),
               new Data(3,10),new Data(4,13),new Data(5,16)};
        for (Data data :mDatas){
            mEntries.add(new Entry(data.getValueX(),data.getValueY()));
        }
        LineDataSet dataSet = new LineDataSet(mEntries,"number");
        LineData lineData = new LineData(dataSet);
        mLineChart.setData(lineData);
        mLineChart.invalidate();


        mHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if (msg.what==TAG){
                    updateTxt(msg);
                    updateChart(msg);
                }
            }
        };

        mThread = new Thread(new Runnable() {
            @Override
            public void run() {
                int corrX = 6;//已經(jīng)有了五個數(shù)據(jù),下一個數(shù)據(jù)的x坐標從6開始
                while (true){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    int corrY = mRandom.nextInt(20) + 5;
                    Message message = Message.obtain();
                    message.arg1 = corrY;
                    message.arg2 = corrX;
                    message.what = TAG;
                    mHandler.sendMessage(message);
                    corrX += 1;
                }
            }
        });

        mStartButton.setOnClickListener((View v) -> mThread.start());

    }

    //更新SpannableString類型的文本需要用該函數(shù)判斷更新數(shù)字的位數(shù)
    private int endIndex(int i){
        int index = 0;
        while (i!=0){
            i = i/10;
            index += 1;
        }
        return index;
    }

    //更新顯示當前值的TextView
    private void updateTxt(Message msg){
        mStringBuilder.replace(7,7 + mEndIndex, msg.arg1 + "");//將原來的數(shù)字替換

        SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(mStringBuilder);
        ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(Color.BLUE);
        RelativeSizeSpan relativeSizeSpan = new RelativeSizeSpan(1.5f);

        mEndIndex = endIndex(msg.arg1);//新的y值的位數(shù)

        spannableStringBuilder.setSpan(foregroundColorSpan,7,7 + mEndIndex, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableStringBuilder.setSpan(relativeSizeSpan,7,7 + mEndIndex, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        mTextView.setText(spannableStringBuilder);

    }

    //刷新折線圖
    private void updateChart(Message msg){
        mEntries.add(new Entry(msg.arg2,msg.arg1));
        LineDataSet dataSet = new LineDataSet(mEntries,"number");
        LineData lineData = new LineData(dataSet);
        mLineChart.setData(lineData);
        mLineChart.invalidate();
    }

}

Data類如下

public class Data {
    private int valueX;
    private int valueY;

    public Data(int x,int y){
        this.valueX = x;
        this.valueY = y;
    }

    public int getValueX() {
        return valueX;
    }

    public void setValueX(int valueX) {
        this.valueX = valueX;
    }

    public int getValueY() {
        return valueY;
    }

    public void setValueY(int valueY) {
        this.valueY = valueY;
    }
}
2、RxJava實現(xiàn)

Rxjava在處理復(fù)雜的多線程事件邏輯時比Handler/Async等要簡單易用可靠。用來寫這個demo算是大炮打蚊子,純當練手了。

MainActivity

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private LineChart mLineChart;
    private TextView mTextView;
    private Button mStartButton;
    private List<Entry> mEntryList = new ArrayList<>();

    private Random mRandom;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mLineChart = findViewById(R.id.line_chart);
        mStartButton = findViewById(R.id.start_button);
        mTextView = findViewById(R.id.value_txt);

        mStartButton.setOnClickListener((View v) -> intervalObservable());
    }

    private void intervalObservable() {
        mRandom = new Random();
        Observable.interval(1000, 1000, TimeUnit.MILLISECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Long>() {
                    @Override
                    public void accept(Long aLong) throws Exception {
                        Log.e("intervalObservable", Thread.currentThread().getName());
                        Long x = aLong;
                        int y = mRandom.nextInt(10);
                        Data data = new Data(x, y);
                        x++;

                        mEntryList.add(new Entry(data.getX(), data.getY()));
                        LineDataSet dataSet = new LineDataSet(mEntryList, "label");
                        LineData lineData = new LineData(dataSet);
                        mLineChart.setData(lineData);
                        mLineChart.invalidate();
                        mTextView.setText("當前y值為" + y);
                    }
                });
    }
}

寫的時候發(fā)現(xiàn)如果直接用Observable.create()生成數(shù)據(jù)的話,速度太快,MPAndroidChart刷新不過來,一片空白。所以改用Observable.interval(),每個1秒生成一個,但是這個函數(shù)只能返回一個Observable<Long>的對象,每次發(fā)射的都是Long類型的數(shù)據(jù),所以把Data類型中的x值改成了Long類型。
還要注意的是,Observable.interval()默認訂閱Schedulers.computation這個線程,如果有UI更新的話,需要在主線程中進行觀察,即調(diào)用observeOn(AndroidSchedulers.mainThread())。

但是我發(fā)現(xiàn)一個很神奇的事,MPAndroidChart可以在非UI線程中進行刷新。

難道只能用Observal.interval()嗎?其實不是的,我發(fā)現(xiàn)只要使被觀察者線程休眠一小段時間,就能讓折線圖刷新出來,代碼如下

Observable.create(new ObservableOnSubscribe<Data>() {
            @Override
            public void subscribe(@NonNull ObservableEmitter<Data> e) throws Exception {
                Long x = 0l;
                Random random = new Random();
                while(x < 1000) {
                    int y = random.nextInt(10);
                    Data data = new Data(x, y);
                    e.onNext(data);
                    x++;
                    Thread.sleep(1000);//休眠1秒
                }
                Log.e(TAG, "Observable thread is : " + Thread.currentThread().getName());
            }
        }).subscribeOn(Schedulers.newThread())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(data -> {
                Log.i("onNext(Data data)", data.toString());
                Log.e(TAG, "Observer thread is :" + Thread.currentThread().getName());
                mEntryList.add(new Entry(data.getX(), data.getY()));
                LineDataSet dataSet = new LineDataSet(mEntryList, "label");
                LineData lineData = new LineData(dataSet);
                mLineChart.setData(lineData);
                mLineChart.invalidate();
                mTextView.setText("當前值為" + data.getY());
            });

其實在訂閱者線程中休眠也可以正常接收到數(shù)據(jù),但訂閱者線程一般是UI線程,休眠的話,UI就不會更新了。
另外,被觀察者的線程沒有休眠的話,即使被觀察者數(shù)據(jù)發(fā)送的很快,訂閱者在onNext()即使進行了線程休眠,數(shù)據(jù)也能全部接收到,不會出現(xiàn)事件丟失的情況,這一點讓我比較疑惑。
如果被觀察者的線程調(diào)用了Thread.sleep(1),而觀察者在onNext()中調(diào)用了Thread.sleep(1000),那么會出現(xiàn)上下游事件處理速率不匹配,事件丟失,OOM等情況。
這個時候就要用支持背壓的Flowable了。
其實用Flowable同樣可以實現(xiàn)折線圖更新,代碼如下:

Flowable.create(new FlowableOnSubscribe<Data>() {
            @Override
            public void subscribe(FlowableEmitter<Data> e) throws Exception{
                Long x = 0l;
                Random random = new Random();
                while(x < 1000) {
                    int y = random.nextInt(10);
                    Data data = new Data(x, y);
                    e.onNext(data);
                    x++;
                    Thread.sleep(1000);
                }
                Log.e(TAG, "Observable thread is : " + Thread.currentThread().getName());
            }
        }, BackpressureStrategy.BUFFER).subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Data>() {
                    Subscription mSubscription;
                    @Override
                    public void onSubscribe(Subscription s) {
                        mSubscription = s;
                        s.request(1);
                    }

                    @Override
                    public void onNext(Data data) {
                        Log.i("onNext(Data data)", data.toString());
                        Log.e(TAG, "Observer thread is :" + Thread.currentThread().getName());
                        mEntryList.add(new Entry(data.getX(), data.getY()));
                        LineDataSet dataSet = new LineDataSet(mEntryList, "label");
                        LineData lineData = new LineData(dataSet);
                        mLineChart.setData(lineData);
                        mLineChart.invalidate();
                        mTextView.setText("當前值為" + data.getY());

                        mSubscription.request(1);
                    }

                    @Override
                    public void onError(Throwable t) {

                    }

                    @Override
                    public void onComplete() {

                    }
                });
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 響應(yīng)式編程簡介 響應(yīng)式編程是一種基于異步數(shù)據(jù)流概念的編程模式。數(shù)據(jù)流就像一條河:它可以被觀測,被過濾,被操作,或者...
    說碼解字閱讀 3,557評論 0 5
  • http://blog.csdn.net/yyh352091626/article/details/5330472...
    奈何心善閱讀 3,651評論 0 0
  • 一、RxJava操作符概述 RxJava中的操作符就是為了提供函數(shù)式的特性,函數(shù)式最大的好處就是使得數(shù)據(jù)處理簡潔易...
    BrotherChen閱讀 1,782評論 0 10
  • 一、RxJava操作符概述 RxJava中的操作符就是為了提供函數(shù)式的特性,函數(shù)式最大的好處就是使得數(shù)據(jù)處理簡潔易...
    無求_95dd閱讀 3,495評論 0 21
  • 一、RxJava操作符概述 RxJava中的操作符就是為了提供函數(shù)式的特性,函數(shù)式最大的好處就是使得數(shù)據(jù)處理簡潔易...
    測天測地測空氣閱讀 685評論 0 1

友情鏈接更多精彩內(nèi)容