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

項(xiàng)目里需要App端不斷地從服務(wù)器獲取數(shù)據(jù)虑椎,實(shí)時(shí)生成圖表榆鼠。圖表控件使用的是MPAndroidChart纲爸。自己寫了些實(shí)時(shí)更新折線圖的demo,數(shù)據(jù)是線程隨機(jī)生成的妆够,不是后臺(tái)數(shù)據(jù)识啦。

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

效果如下


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

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

MainActivity代碼如下:

public class MainActivity extends AppCompatActivity {
    private static final int TAG = 1;//Message的what標(biāo)識(shí)
    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個(gè)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)有了五個(gè)數(shù)據(jù)灾螃,下一個(gè)數(shù)據(jù)的x坐標(biāo)從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;
    }

    //更新顯示當(dāng)前值的TextView
    private void updateTxt(Message msg){
        mStringBuilder.replace(7,7 + mEndIndex, msg.arg1 + "");//將原來(lái)的數(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實(shí)現(xiàn)

Rxjava在處理復(fù)雜的多線程事件邏輯時(shí)比Handler/Async等要簡(jiǎn)單易用可靠。用來(lái)寫這個(gè)demo算是大炮打蚊子腰鬼,純當(dāng)練手了嵌赠。

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("當(dāng)前y值為" + y);
                    }
                });
    }
}

寫的時(shí)候發(fā)現(xiàn)如果直接用Observable.create()生成數(shù)據(jù)的話,速度太快熄赡,MPAndroidChart刷新不過來(lái)姜挺,一片空白。所以改用Observable.interval()彼硫,每個(gè)1秒生成一個(gè)炊豪,但是這個(gè)函數(shù)只能返回一個(gè)Observable<Long>的對(duì)象,每次發(fā)射的都是Long類型的數(shù)據(jù)拧篮,所以把Data類型中的x值改成了Long類型词渤。
還要注意的是,Observable.interval()默認(rèn)訂閱Schedulers.computation這個(gè)線程串绩,如果有UI更新的話缺虐,需要在主線程中進(jìn)行觀察,即調(diào)用observeOn(AndroidSchedulers.mainThread())礁凡。

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

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

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("當(dāng)前值為" + data.getY());
            });

其實(shí)在訂閱者線程中休眠也可以正常接收到數(shù)據(jù)窟蓝,但訂閱者線程一般是UI線程罪裹,休眠的話,UI就不會(huì)更新了。
另外坊谁,被觀察者的線程沒有休眠的話费彼,即使被觀察者數(shù)據(jù)發(fā)送的很快,訂閱者在onNext()即使進(jìn)行了線程休眠口芍,數(shù)據(jù)也能全部接收到箍铲,不會(huì)出現(xiàn)事件丟失的情況,這一點(diǎn)讓我比較疑惑鬓椭。
如果被觀察者的線程調(diào)用了Thread.sleep(1)颠猴,而觀察者在onNext()中調(diào)用了Thread.sleep(1000),那么會(huì)出現(xiàn)上下游事件處理速率不匹配小染,事件丟失翘瓮,OOM等情況。
這個(gè)時(shí)候就要用支持背壓的Flowable了裤翩。
其實(shí)用Flowable同樣可以實(shí)現(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("當(dāng)前值為" + data.getY());

                        mSubscription.request(1);
                    }

                    @Override
                    public void onError(Throwable t) {

                    }

                    @Override
                    public void onComplete() {

                    }
                });
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市踊赠,隨后出現(xiàn)的幾起案子呵扛,更是在濱河造成了極大的恐慌,老刑警劉巖筐带,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件今穿,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡伦籍,警方通過查閱死者的電腦和手機(jī)蓝晒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)帖鸦,“玉大人芝薇,你說(shuō)我怎么就攤上這事「恍睿” “怎么了剩燥?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)立倍。 經(jīng)常有香客問我,道長(zhǎng)侣滩,這世上最難降的妖魔是什么口注? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮君珠,結(jié)果婚禮上寝志,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好材部,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布毫缆。 她就那樣靜靜地躺著,像睡著了一般乐导。 火紅的嫁衣襯著肌膚如雪苦丁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天物臂,我揣著相機(jī)與錄音旺拉,去河邊找鬼。 笑死棵磷,一個(gè)胖子當(dāng)著我的面吹牛蛾狗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播仪媒,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼沉桌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了算吩?” 一聲冷哼從身側(cè)響起蒲牧,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赌莺,沒想到半個(gè)月后冰抢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡艘狭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年挎扰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片巢音。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡遵倦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出官撼,到底是詐尸還是另有隱情梧躺,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布傲绣,位于F島的核電站掠哥,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏秃诵。R本人自食惡果不足惜续搀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望菠净。 院中可真熱鬧禁舷,春花似錦彪杉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至洁桌,卻和暖如春渴丸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背战坤。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工曙强, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人途茫。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓碟嘴,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親囊卜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子娜扇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 響應(yīng)式編程簡(jiǎn)介 響應(yīng)式編程是一種基于異步數(shù)據(jù)流概念的編程模式。數(shù)據(jù)流就像一條河:它可以被觀測(cè)栅组,被過濾雀瓢,被操作,或者...
    說(shuō)碼解字閱讀 3,056評(píng)論 0 5
  • http://blog.csdn.net/yyh352091626/article/details/5330472...
    奈何心善閱讀 3,546評(píng)論 0 0
  • 一玉掸、RxJava操作符概述 RxJava中的操作符就是為了提供函數(shù)式的特性刃麸,函數(shù)式最大的好處就是使得數(shù)據(jù)處理簡(jiǎn)潔易...
    BrotherChen閱讀 1,601評(píng)論 0 10
  • 一、RxJava操作符概述 RxJava中的操作符就是為了提供函數(shù)式的特性司浪,函數(shù)式最大的好處就是使得數(shù)據(jù)處理簡(jiǎn)潔易...
    無(wú)求_95dd閱讀 2,990評(píng)論 0 21
  • 一泊业、RxJava操作符概述 RxJava中的操作符就是為了提供函數(shù)式的特性,函數(shù)式最大的好處就是使得數(shù)據(jù)處理簡(jiǎn)潔易...
    測(cè)天測(cè)地測(cè)空氣閱讀 626評(píng)論 0 1