Android之绘制动态折线图

所谓动态折线图,就是折线图能随着手指的滑动进行动态绘制,这里很定会产生动画效果。基于这个效果,这里使用SurfaceView进行制图。

实现步奏如下:

(1): 这里新建一个绘图ChartView,继承SurfaceView并实现SurfaceHolder.Callback , Runnable接口,主要绘图工作在子线程中完成。
(2):现实 SurfaceHolder.Callback接口的三个方法,并在 surfaceCreated中开启子线程进行绘图。
(3):重写onTouchEvent方法,在Move事件中,根据手指的滑动距离计算偏移量,具体实现请看代码。
(4): 这里的折线图的坐标值是随意添加的,可以在实际项目中根据需求自己添加。
(5):此例中有大量从集合中添加和删除元素,建议使用LinkedList来进行保存数据。

自定义ChartView:

public class ChartView extends SurfaceView implements SurfaceHolder.Callback , Runnable
{
    private Context mContext;
    private Paint mPaint;
    private Resources res;
    private DisplayMetrics dm;

    private int canvasHeight;
    private int canvasWidth;
    private int bHeight = 0;
    private int bWidth;
    private boolean isMeasure = true;
    private boolean canScrollRight = true;
    private boolean canScrollLeft = true;

    //y轴最大值
    private int maxValue;
    //y轴间隔值
    private int averageValue;
    private int marginTop = 20;
    private int marginBottom = 80;

    //曲线上的总点数
    private Point[] mPoints;
    //纵坐标值
    private LinkedList<Double> yRawData;
    //横坐标值
    private LinkedList<String> xRawData;
    //根据间隔计算出的每个X的值
    private LinkedList<Integer> xList = new LinkedList<>();
    private LinkedList<String> xPreData = new LinkedList<>();
    private LinkedList<Double> yPreData = new LinkedList<>();

    private LinkedList<String> xLastData = new LinkedList<>();
    private LinkedList<Double> yLastData = new LinkedList<>();
    private int spacingHeight;

    private SurfaceHolder holder;
    private boolean isRunning = true;
    private int lastX;
    private int offSet;
    private Rect mRect;

    private int xAverageValue = 0;


    public ChartView(Context context)
    {
        this(context , null);
    }

    public ChartView(Context context , AttributeSet attrs)
    {
        super(context, attrs);
        this.mContext = context;
        initView();
    }

    private void initView()
    {
        this.res = mContext.getResources();
        this.mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        dm = new DisplayMetrics();
        WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        wm.getDefaultDisplay().getMetrics(dm);

        xPreData.add("05-18");
        xPreData.add("05-17");
        xPreData.add("05-16");
        xPreData.add("05-15");
        xPreData.add("05-14");
        xPreData.add("05-13");

        yPreData.add(4.53);
        yPreData.add(3.45);
        yPreData.add(6.78);
        yPreData.add(5.21);
        yPreData.add(2.34);
        yPreData.add(6.32);

        xLastData.add("05-26");
        xLastData.add("05-27");
        xLastData.add("05-28");
        xLastData.add("05-29");
        xLastData.add("05-30");
        xLastData.add("05-31");

        yLastData.add(2.35);
        yLastData.add(5.43);
        yLastData.add(6.23);
        yLastData.add(7.33);
        yLastData.add(3.45);
        yLastData.add(2.45);

        holder = this.getHolder();
        holder.addCallback(this);
    }

    @Override
    protected void onSizeChanged(int w , int h , int oldW , int oldH)
    {
        if (isMeasure)
        {
            this.canvasHeight = getHeight();
            this.canvasWidth = getWidth();
            if (bHeight == 0)
            {
                bHeight = canvasHeight - marginBottom;
            }
            bWidth = dip2px(30);
            xAverageValue = (canvasWidth - bWidth) / 7;
            isMeasure = false;
        }
    }


    @Override
    public void run()
    {
        while (isRunning)
        {
            drawView();
            try
            {
                Thread.sleep(100);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }

    private void drawView()
    {
        Canvas canvas = holder.lockCanvas();
        canvas.drawColor(Color.WHITE);
        mPaint.setColor(res.getColor(R.color.color_f2f2f2));
        drawAllXLine(canvas);
        mRect = new Rect(bWidth - 3,  marginTop - 5 ,
                bWidth + (canvasWidth - bWidth) / yRawData.size() * (yRawData.size() - 1) + 3, bHeight + marginTop + marginBottom);
        //锁定画图区域
        canvas.clipRect(mRect);
        drawAllYLine(canvas);

        mPoints = getPoints();

        mPaint.setColor(res.getColor(R.color.color_ff4631));
        mPaint.setStrokeWidth(dip2px(2.5f));
        mPaint.setStyle(Paint.Style.STROKE);
        drawLine(canvas);

        mPaint.setStyle(Paint.Style.FILL);
        for (int i = 0 ; i < mPoints.length ; i++)
        {
            canvas.drawCircle(mPoints[i].x , mPoints[i].y , 5 , mPaint);
        }

        holder.unlockCanvasAndPost(canvas);
    }

    //绘制折线图
    private void drawLine(Canvas canvas)
    {
        Point startP = null;
        Point endP = null;
        for (int i = 0 ; i < mPoints.length - 1; i++)
        {
            startP = mPoints[i];
            endP = mPoints[i + 1];
            canvas.drawLine(startP.x , startP.y , endP.x , endP.y , mPaint);
        }
    }

    //绘制所有的纵向分割线
    private void drawAllYLine(Canvas canvas)
    {
        for (int i = 0 ; i < yRawData.size() ; i++)
        {
            if (i == 0)
            {
                canvas.drawLine(bWidth, marginTop , bWidth, bHeight + marginTop , mPaint);
            }
            if (i == yRawData.size() - 1)
            {
                canvas.drawLine(bWidth + xAverageValue * i, marginTop , bWidth + xAverageValue * i , bHeight + marginTop , mPaint);
            }
            xList.add(bWidth + xAverageValue * i);
            canvas.drawLine(bWidth + xAverageValue * i + offSet, marginTop , bWidth + xAverageValue * i + offSet , bHeight + marginTop , mPaint);
            drawText(xRawData.get(i) , bWidth + xAverageValue * i - 30 + offSet, bHeight + dip2px(26) , canvas);

        }
    }

    //绘制所有的横向分割线
    private void drawAllXLine(Canvas canvas)
    {
        for (int i = 0 ; i < spacingHeight + 1 ; i++)
        {
            canvas.drawLine(bWidth , bHeight - (bHeight / spacingHeight) * i + marginTop ,
                    bWidth + xAverageValue * (yRawData.size() - 1) , bHeight - (bHeight / spacingHeight) * i + marginTop , mPaint);
            drawText(String.valueOf(averageValue * i) , bWidth / 2 , bHeight - (bHeight / spacingHeight) * i + marginTop, canvas);
        }
    }

    //绘制坐标值
    private void drawText(String text , int x , int y , Canvas canvas)
    {
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        p.setTextSize(dip2px(12));
        p.setColor(res.getColor(R.color.color_999999));
        p.setTextAlign(Paint.Align.LEFT);
        canvas.drawText(text , x , y , p);
    }

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder)
    {
        new Thread(this).start();
        Log.d("OOK" , "Created");
    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2)
    {
        Log.d("OOK" , "Changed");
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder)
    {
        isRunning = false;
        try
        {
            Thread.sleep(500);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        int action = event.getAction();
        int rawX = (int) event.getX();
        switch (action)
        {
            case MotionEvent.ACTION_DOWN:
                lastX = rawX;
                break;
            case MotionEvent.ACTION_MOVE:
                int offsetX = rawX - lastX;
                if (xPreData.size() == 0 && offSet > 0)
                {
                    offSet = 0;
                    canScrollRight = false;
                }
                if (xLastData.size() == 0 && offSet < 0)
                {
                    offSet = 0;
                    canScrollLeft = false;
                }
                offSet = offSet + offsetX;
                if (offSet >  xAverageValue && canScrollRight)
                {
                    offSet = offSet % xAverageValue;
                    xRawData.addFirst(xPreData.pollFirst());
                    yRawData.addFirst(yPreData.pollFirst());
                    xLastData.addFirst(xRawData.removeLast());
                    yLastData.addFirst(yRawData.removeLast());
                    canScrollLeft = true;
                }


                if (offSet < -xAverageValue && canScrollLeft)
                {
                    offSet = offSet % xAverageValue;
                    xRawData.addLast(xLastData.pollFirst());
                    yRawData.addLast(yLastData.pollFirst());
                    xPreData.addFirst(xRawData.removeFirst());
                    yPreData.addFirst(yRawData.removeFirst());
                    canScrollRight = true;
                }
                lastX = rawX;
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return true;
    }

    private Point[] getPoints()
    {
        Point[] points = new Point[yRawData.size()];
        for (int i = 0 ; i < yRawData.size() ; i++)
        {
            int ph = bHeight - (int)(bHeight * (yRawData.get(i) / maxValue));

            points[i] = new Point(xList.get(i) + offSet , ph + marginTop);
        }
        return points;
    }

    public void setData(LinkedList<Double> yRawData , LinkedList<String> xRawData , int maxValue , int averageValue)
    {
        this.maxValue = maxValue;
        this.averageValue = averageValue;
        this.mPoints = new Point[yRawData.size()];
        this.yRawData = yRawData;
        this.xRawData = xRawData;
        this.spacingHeight = maxValue / averageValue;
    }

    private int dip2px(float dpValue)
    {
        return (int) (dpValue * dm.density + 0.5f);
    }
}

MainActivity代码:

public class MainActivity extends Activity
{
    LinkedList<Double> yList;
    LinkedList<String> xRawData;
    ChartView chartView;
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_activity);
        chartView = (ChartView) findViewById(R.id.chartView);

        yList = new LinkedList<>();
        yList.add(2.203);
        yList.add(4.05);
        yList.add(6.60);
        yList.add(3.08);
        yList.add(4.32);
        yList.add(2.0);
        yList.add(5.0);

        xRawData = new LinkedList<>();
        xRawData.add("05-19");
        xRawData.add("05-20");
        xRawData.add("05-21");
        xRawData.add("05-22");
        xRawData.add("05-23");
        xRawData.add("05-24");
        xRawData.add("05-25");

        chartView.setData(yList , xRawData , 8 , 2);
    }
}

此例页面布局比较简单,就是在主页面布局中添加一个自定义的ChartView即可,这里不再贴出。可能写得有点仓促,如果不妥之处,请大家批评指正,谢谢!

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值