<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Android仿墨跡天氣折線圖效果

          共 8008字,需瀏覽 17分鐘

           ·

          2021-07-12 14:55

          一、效果展示


          二、分析

          我們可以下載一個(gè)墨跡天氣app,然后首頁(yè)下拉到天氣走勢(shì)圖這里,可以體驗(yàn)一下他們的折線圖,非常的順滑,那么怎么實(shí)現(xiàn)的呢? 不用說,肯定是自定義View。


          自定義View的核心是什么? 那就是計(jì)算每一個(gè)要繪制的圖案的位置。

          自定義View從什么地方入手? 對(duì)象。


          在我們這個(gè)Demo里面,View首先可以看作是一個(gè)對(duì)象,

          然后我們可以列出這個(gè)對(duì)象的屬性:


          • backColor;      //背景色

          • barColor;       //柱狀圖色

          • lineColor;      //線色

          • dotColor;       //點(diǎn)色

          • topHeight;      //頂部偏移

          • lineHeight;     //行高

          • lineWidth;      //柱狀圖寬

          • lineSpace;      //柱狀圖之間的縫隙

          • viewWidth;      //view寬

          • viewHeight;     //view高

          • MAX_SCROLL_X;   //最大偏移


          當(dāng)然最開始肯定分析不出這么多屬性,慢慢摸索。 繪制的每一個(gè)柱形也是一個(gè)對(duì)象,這個(gè)對(duì)象比較重要,需要保存繪制的所有信息,我們先看這個(gè)Bar怎么定義的:
          public class Bar {    private int left;    private int top;    private int right;    private int bottom;
          //中心點(diǎn)的位置 private int mx; private int my;
          private String number; private String title;}


          柱形的位置信息,柱頂中心點(diǎn)的坐標(biāo),可以通過位置信息計(jì)算得到,然后是number,這個(gè)數(shù)字是代表柱形的高度,是相對(duì)左邊刻度的高度,然后title是底部的描述文字,這里我并沒有用到,也可以計(jì)算之后繪制在圖表底部。


          三、繪制


          我們首先繪制柱形,折線圖暫時(shí)不去管,看代碼:
          String data = "[{\"title\": \"今天\", \"number\": \"42\"}, {\"title\": \"06:00\", \"number\": \"24\"}, {\"title\": \"12:00\", \"number\": \"8\"}, {\"title\": \"18:00\", \"number\": \"42\"}, {\"title\": \"明天\", \"number\": \"12\"}, {\"title\": \"06:00\", \"number\": \"36\"}, {\"title\": \"12:00\", \"number\": \"4\"}, {\"title\": \"18:00\", \"number\": \"16\"}, {\"title\": \"后天\", \"number\": \"50\"}, {\"title\": \"06:00\", \"number\": \"24\"}, {\"title\": \"12:00\", \"number\": \"42\"}, {\"title\": \"18:00\", \"number\": \"25\"}]";
          bars = JSONArray.parseArray(data, Bar.class);


          自定義一個(gè)json串,得到柱形的model集合,然后計(jì)算每一個(gè)柱形的位置:
          /**     * 得到數(shù)據(jù)計(jì)算每一個(gè)柱狀圖的位置     */    private void compute() {        if (bars == null || bars.size() == 0) {            return;        }        int size = bars.size();        for (int i = 0; i < size; i++) {            Bar bar = bars.get(i);            bar.setLeft(i * (lineWidth + lineSpace));            bar.setTop((int) (viewHeight - (lineHeight * Integer.parseInt(bar.getNumber())) * 1.0 / 25));            bar.setRight((i + 1) * lineWidth + i * lineSpace);            bar.setBottom(viewHeight);            bar.computeMidDot();            if (i == size - 1) {                MAX_SCROLL_X = bar.getRight() - viewWidth;            }        }    }

          上下左右的計(jì)算方法都很好理解。這個(gè)計(jì)算我們?cè)谑裁磿r(shí)候計(jì)算呢?
          最好在 onMeasure() 之后調(diào)用:
          @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        viewWidth = MeasureSpec.getSize(widthMeasureSpec);        viewHeight = topHeight + lineHeight * 2;        setMeasuredDimension(viewWidth, viewHeight);
          compute(); computeY();    }


          確定完View的寬高以及每一個(gè)柱形圖的位置,就是繪制了:

          /**     * 繪制柱狀圖     *     * @param canvas     */    private void drawBar(Canvas canvas) {        for (Bar bar : bars) {            canvas.drawRect(new Rect(bar.getLeft(), bar.getTop(), bar.getRight(), bar.getBottom()), barPaint);        }    }


          永遠(yuǎn)記住一條,onDraw() 里面只負(fù)責(zé)繪制,不要做多余的操作,什么計(jì)算了啊,new 一個(gè)對(duì)象啊,這些都會(huì)嚴(yán)重拖慢你的View的順滑度,內(nèi)存吃緊,體驗(yàn)特別差。

          然后我們還想要View能支持滑動(dòng),復(fù)寫 onTouchEvent() ,
          這里就比較簡(jiǎn)單了,很常見:
          @Override    public boolean onTouchEvent(MotionEvent event) {        obtainVelocityTracker();        float currentX = event.getX();        float currentY = event.getY();        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                downX = currentX;                downY = currentY;                if (!mScroller.isFinished()) {                    mScroller.abortAnimation();                }                break;            case MotionEvent.ACTION_MOVE:                float dx = currentX - downX;                float dy = currentY - downY;                if(Math.abs(dx) > Math.abs(dy)){                    getParent().requestDisallowInterceptTouchEvent(true);                    scrollTo((int) (getScrollX() - dx), 0);                    computeY();                    invalidate();                    downX = currentX;                    downY = currentY;                }                break;            case MotionEvent.ACTION_UP:            case MotionEvent.ACTION_CANCEL:                mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);                int initialVelocity = (int) mVelocityTracker.getXVelocity();                if ((Math.abs(initialVelocity) > mMinimumVelocity)) {                    flingX(-initialVelocity);                }                releaseVelocityTracker();                break;        }        if (mVelocityTracker != null) {            mVelocityTracker.addMovement(event);        }        return true;    }


          這里面我們加入了慣性滑動(dòng),如果不清楚流程,可以找參考文章。

          到這里我們就已經(jīng)完成了可滑動(dòng)的柱形圖,但是離墨跡天氣的折線圖還差的很遠(yuǎn),不著急,我們希望可以自由切換柱形圖和折線圖。那么我們需要設(shè)置一個(gè)類型標(biāo)記:
              //兩種類型 折線 柱狀    public static final int TYPE_LINE = 1;    public static final int TYPE_BAR = 2;    private int type = TYPE_BAR;


          通過這個(gè)我們?nèi)ダL制不同的圖形,很簡(jiǎn)單的思路,那么如何繪制折線圖呢?很簡(jiǎn)單,我們之前計(jì)算的柱形圖的中心點(diǎn)就用上了,只要連接這些中心點(diǎn)就可以了,但是我們能直線連接嗎?可以,但是很丑。觀察墨跡天氣不難看出他們使用的是三階貝塞爾曲線去柔和的連接這些點(diǎn):
              @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        if (getType() == TYPE_BAR) {            drawBar(canvas);        } else if (getType() == TYPE_LINE) {            drawLine(canvas);            drawDialog(canvas);        }    }


          我們?cè)?onDraw() 里面判斷一下,方便我們切換視圖。
          /**     * 繪制折線圖     *     * @param canvas     */    private void drawLine(Canvas canvas) {        int size = bars.size();        for (int i = 0; i < size; i++) {            Bar bar = bars.get(i);            canvas.drawCircle(bar.getMx(), bar.getMy(), dp2px(mContext, 4), dotPaint);            if (i != size - 1) {                mPath.reset();                Bar nextBar = bars.get(i + 1);                boolean isUp = Integer.parseInt(nextBar.getNumber()) > Integer.parseInt(bar.getNumber());                mPath.moveTo(bar.getMx(), bar.getMy());                int midX = (bar.getMx() + nextBar.getMx()) / 2;                int midY = (bar.getMy() + nextBar.getMy()) / 2;
          int p1x = (bar.getMx() + midX) / 2; int p1y = (bar.getMy() + midY) / 2 + (isUp ? 50 : -50);
          int p2x = (midX + nextBar.getMx()) / 2; int p2y = (midY + nextBar.getMy()) / 2 + (isUp ? -50 : 50);
          mPath.cubicTo(p1x, p1y, p2x, p2y, nextBar.getMx(), nextBar.getMy()); canvas.drawPath(mPath, linePaint); }        } }


          貝塞爾曲線就完成了。完美了嗎?沒有。墨跡天氣還有一個(gè)很神奇的跟著手指在貝塞爾線上滑動(dòng)的類似dialog的指示器,怎么實(shí)現(xiàn)的?我實(shí)在是想不到思路。然后百度了一個(gè)Demo,了解了思路。指示器的坐標(biāo) (x,y) 是計(jì)算出來的,x 很好計(jì)算,按照比例得出公式:
              /**     * 計(jì)算指示器的位置     */    private void computeXY() {        int scrollX = getScrollX();        dialogX = (int) (scrollX * 1.0 / MAX_SCROLL_X * (viewWidth - lineWidth)) + scrollX + lineWidth / 2;        int size = bars.size();        int currentIndex = 0;        for (int i = 0; i < size; i++) {            if(i != size - 1){                Bar currentBar = bars.get(i);                Bar nextBar = bars.get(i + 1);                if (currentBar.getMx() < dialogX && nextBar.getMx() > dialogX) {                    currentIndex = i;                    break;                }            }        }        if (currentIndex < size - 1) {            Bar currentBar = bars.get(currentIndex);            Bar nextBar = bars.get(currentIndex + 1);            dialogY = (int) ((dialogX - currentBar.getMx()) * (nextBar.getMy() - currentBar.getMy()) * 1.0 / (nextBar.getMx() - currentBar.getMx()) + currentBar.getMy());        }    }


          我這里使用一個(gè)簡(jiǎn)單的藍(lán)點(diǎn)代表,可以是繪制任何的圖形:
              private void drawDialog(Canvas canvas) {        canvas.drawCircle(dialogX, dialogY, 20, tipPaint);    }


          到這里,整個(gè)demo就完成了,邊界判斷我也沒有加。
          糙了一點(diǎn),思路倒是沒有錯(cuò)。


          源碼地址:
          https://github.com/rjpacket/LineChartProject

          到這里就結(jié)束啦。
          瀏覽 120
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  18禁天堂 | 99久久精品国产色欲 | 国产黄色 操B | 国产寡妇淫乱高清视频 | 豆花视频网站免费 |