使用ValueAnimator自定义动态XY图表View

使用ValueAnimator自定义动态XY图表View

效果

无废话,先上图:
使用ValueAnimator自定义动态XY图表View_第1张图片

分析需求

-1、x轴展示七天日期,y轴展示七天日期对应的值。
-2、需要一个动画,顺序的每天的数据展示出来。
-3、需要另一个动画,某一天的数据是从底部向上平衡到实际值。

实现思路

  • 使用drawLine画x轴与y轴的带箭头的线
  • 使用drawLine画x轴与y轴的刻度线及刻度值
  • 使用ValueAnimator产生个时间差依次准备去画点
  • 使用另个ValueAnimator产生个时间差drawCircle画红点
  • 将每个点用Path存储起来,然后drawPath即可画连起来的折线

实现

使用drawLine画x轴与y轴的带箭头的线

x轴,以左下角的起点作为起点。那么起点的x轴上的坐标是竖向坐标靠近左边半个箭头宽度mArrowSize+本身的getPaddingLeft。y轴可以同理推出为控件的高getHeight-底部的getPaddingBottom及横向坐标下半个箭头的高度mArrowSize

//画x轴
        int startX1 = getPaddingLeft() + mArrowSize;
        int startY1 = getHeight() - getPaddingBottom() - mArrowSize;//以左下角原点坐为起点
        int stopX1 = getWidth() - getPaddingRight();
        canvas.drawLine(startX1, startY1, stopX1, startY1,mXYPaint);
        canvas.drawLine(stopX1-mArrowSize,getHeight()-getPaddingBottom()-2*mArrowSize, stopX1, startY1,mXYPaint);
        canvas.drawLine(stopX1-mArrowSize,getHeight()-getPaddingBottom(), stopX1, startY1,mXYPaint);

画y轴

//y轴
canvas.drawLine(startX1, startY1, startX1,getPaddingTop(),mXYPaint);
        canvas.drawLine(getPaddingLeft(),getPaddingTop()+mArrowSize, startX1,getPaddingTop(),mXYPaint);
        canvas.drawLine(getPaddingLeft()+2*mArrowSize,getPaddingTop()+mArrowSize, startX1,getPaddingTop(),mXYPaint);

使用drawLine画x轴与y轴的刻度线及刻度值

需要注意的是x轴以为数据集大小来平分,为了箭头与最后一个刻盘预留些空间,需要将宽度减掉预留空间mRemainSpaceSize
示例起见y轴固定分成5个部分,为了让y轴上的坐标值看起来尽量大些,使用y轴上的实际数据最大值-实际数据最小值除3得出每份perGapY,分布上面4个部分的值,而y轴上的最小值就是实际数据最小值+每份perGapY(最小刻度值在最下面)

//画x轴的刻度
        int realWidth = getWidth() - getPaddingLeft() - getPaddingRight() - mArrowSize - mRemainSpaceSize;
        int perWidthSize = dataSize != 0? realWidth/dataSize : 0;
        for (int i=0;i<dataSize;i++){
            int startX = getPaddingLeft() + mArrowSize + perWidthSize * (i + 1);
            int startY = startY1;
            canvas.drawLine(startX, startY, startX, startY - mMarkLineSize, mXYPaint);
            String xData = formateDate(xDatas.get(i));
            Rect textRect = new Rect();
            mXYTextPaint.getTextBounds(xData, 0, xData.length(), textRect);

            int textStartX = startX - textRect.width() / 2;
            int textStartY = startY + textRect.height()  + 10;
            canvas.drawText(xData,textStartX,textStartY,mXYTextPaint);
        }


        int realStartY = 0;
        //画y轴的刻度
        //y轴上分多少个刻度
        mYDataSize = 5;
        double perGapY = (maxYValue - minYValue) / (mYDataSize-2);
        int realHeight = getHeight() - getPaddingTop() - getPaddingBottom() - mArrowSize - mRemainSpaceSize;
        int perHeightSize = mYDataSize != 0? realHeight/ mYDataSize : 0;
        for (int i=1;i<=mYDataSize;i++){
            int startX = startX1;
            int startY = getHeight() - getPaddingBottom() - mArrowSize - perHeightSize * i;
            canvas.drawLine(startX, startY, startX + mMarkLineSize, startY, mXYPaint);
            if (i == 2){
                realStartY = startY;  //min y轴 开始的坐标位置
            }

            String yData = decimalFormatFor2Rate(minYValue + (i - 2) * perGapY);
            Rect textRect = new Rect();
            mXYTextPaint.getTextBounds(yData,0,yData.length(),textRect);

            int textStartX = startX - textRect.width() - 10;
            int textStartY = startY + textRect.height() / 2;
            canvas.drawText(yData,textStartX,textStartY,mXYTextPaint);
        }

使用ValueAnimator产生个时间差依次准备去画点

使用mDrawDotIndex来表示即将画的点的位置,只有在未开始即将开始时启动动画,否则会无限循环启动动画,不停的invalidate不停的画。

if (-1 == mDrawDotIndex){
            mAnimator = ValueAnimator.ofInt(0, dataSize * 100);
            mAnimator.setDuration(5000);
            mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    int animatedValue = (Integer) animation.getAnimatedValue();
                    int i = animatedValue / 100;
                    if (!mReadedNumber.contains(i)&&i<dataSize) {
                        mReadedNumber.add(i);
                        //准备画第i个动画
                        mDrawDotIndex = i;
                        mPercent = -1;
                        postInvalidate();
                    }
                    isDrawing = true;
                }
            });
            mAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    mDrawDotIndex = dataSize - 1;
                    isDrawing = false;
                    mReadedNumber.clear();
                    mPath.reset();
                    mPathReadedIndex.clear();
                    mAnimator.removeAllUpdateListeners();
                }

                @Override
                public void onAnimationCancel(Animator animation) {
                    mDrawDotIndex = dataSize - 1;
                    isDrawing = false;
                }

                @Override
                public void onAnimationRepeat(Animator animation) {
                }
            });
            mAnimator.start();
        }

使用另个ValueAnimator产生个时间差drawCircle画红点

这里简单起点直接指定红点的半径10,将即将要画的最后一个点mDrawDotIndex使用动画从底部往上画出来,使用drawCircle不断的变化y轴即可形成从底部往上平衡的效果。

//画点
        int radius = 10;
        for (int i=0;i<dataSize;i++){
            if (i<=mDrawDotIndex){
                double yValue = yDatas.get(i);
                double number =  (yValue - minYValue) / perGapY;
                final int startX = getPaddingLeft() + mArrowSize + perWidthSize * (i + 1) - radius/2;
                final int startY = (int) (realStartY - number*perHeightSize - radius/2);
                if (i != mDrawDotIndex){
                    canvas.drawCircle(startX, startY, radius, mDotPaint);
                    if (i != 0 && !mPathReadedIndex.contains(i)){
                        mPath.lineTo(startX, startY);
                        mPathReadedIndex.add(i);
                    }
                }else{
                    //动画效果
                    int fromX = startY1;

                    if (-1 == mPercent){
                        mDotAnimator = ValueAnimator.ofFloat(0, 1);
                        mDotAnimator.setDuration(1000);
                        mDotAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                            @Override
                            public void onAnimationUpdate(ValueAnimator animation) {
                                mPercent = (float) animation.getAnimatedValue();
                                invalidate();
                            }
                        });
                        mDotAnimator.addListener(new ValueAnimator.AnimatorListener() {
                            @Override
                            public void onAnimationStart(Animator animation) {
                                mAnimator.pause();
                            }

                            @Override
                            public void onAnimationEnd(Animator animation) {
                                mAnimator.resume();
                                mPercent = 1;
                                if (mDrawDotIndex != 0 && !mPathReadedIndex.contains(mDrawDotIndex)) {
                                    mPath.lineTo(startX, startY);
                                    mPathReadedIndex.add(mDrawDotIndex);
                                }
                                animation.removeAllListeners();
                            }

                            @Override
                            public void onAnimationCancel(Animator animation) {
                                mPercent = 1;
                            }

                            @Override
                            public void onAnimationRepeat(Animator animation) {
                            }
                        });
                        mDotAnimator.start();
                    }else{
                        int gapY = startY - fromX;
                        int thisStartX = (int) (startY - (1-mPercent)*gapY);
                        canvas.drawCircle(startX, thisStartX, radius, mDotPaint);
                    }
                }

                if (i == 0&&!mPathReadedIndex.contains(i)) {
                    mPath.moveTo(startX,startY);
                    mPathReadedIndex.add(i);
                }

            }
        }

将每个点用Path存储起来,然后drawPath即可画连起来的折线

上面已经了个成员变量Path,最后一个点可以在mDotAnimator动画结束时添加进去。这样本次动画mAnimator的最后一个点在结束平稳效果后添加进path中,再将其drawPath画出来。

canvas.drawPath(mPath,mFoldLinePaint);

完整代码

自定义DynamicXYChartView

package com.afeita.test;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/** * <br /> author: chenshufei * <br /> date: 15/10/28 * <br /> email: [email protected] */
public class DynamicXYChartView extends View {

    private int mWidthSize = 0;
    private int mHeightSize = 0;

    private Map<Date,Double> mData;
    private Paint mDotPaint;
    private Paint mFoldLinePaint;
    private Paint mXYPaint;
    private int mArrowSize;
    private int mRemainSpaceSize;
    private int mMarkLineSize;
    private int mXYTextSize;
    private Paint mXYTextPaint;
    private int mYDataSize;
    private int mDrawDotIndex = -1;
    private ValueAnimator mAnimator;

    private boolean isDrawing = false;
    private float mPercent = -1;
    private List<Integer> mReadedNumber;
    private List<Integer> mPathReadedIndex;
    private Path mPath;
    private ValueAnimator mDotAnimator;

    public interface OnDrawStataListener{
        void onDrawStata(boolean isDrawing);
    }

    public DynamicXYChartView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context,attrs);
    }

    public DynamicXYChartView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    public void setData(Map<Date, Double> data,OnDrawStataListener listener) {
        if (isDrawing){
            if (null != listener){
                listener.onDrawStata(isDrawing);
            }
        }else{
            this.mData = data;
            mDrawDotIndex = -1;
            postInvalidate();
        }
    }

    private void init(Context context, AttributeSet attrs) {
        //当未指定宽与高时,即是at_most模式时,设置其默认的宽与高
        mWidthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,500,getResources().getDisplayMetrics());
        mHeightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,300,getResources().getDisplayMetrics());
        mArrowSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, getResources().getDisplayMetrics());
        //x与y轴留出空白空间,实际
        mRemainSpaceSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, getResources().getDisplayMetrics());
        mMarkLineSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, getResources().getDisplayMetrics());

        mXYTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, getResources().getDisplayMetrics());
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //判断是否是Almost模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        switch (widthMode){
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                widthSize = mWidthSize;
                break;
        }

        switch (heightMode){
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                heightSize = mHeightSize;
                break;
        }

        setMeasuredDimension(widthSize, heightSize);

    }


    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        //初始化 painter等
        //点的
        mDotPaint = new Paint();
        mDotPaint.setAntiAlias(true);
        mDotPaint.setStyle(Paint.Style.FILL);
        mDotPaint.setDither(true);
        mDotPaint.setColor(Color.RED);

        //折线
        mFoldLinePaint = new Paint();
        mFoldLinePaint.setAntiAlias(true);
        mFoldLinePaint.setStyle(Paint.Style.STROKE);
        mFoldLinePaint.setDither(true);
        mFoldLinePaint.setStrokeWidth(5);
        mFoldLinePaint.setColor(Color.BLACK);

        //坐标
        mXYPaint = new Paint();
        mXYPaint.setAntiAlias(true);
        mXYPaint.setStyle(Paint.Style.FILL);
        mXYPaint.setDither(true);
        mXYPaint.setStrokeWidth(5);
        mXYPaint.setColor(Color.BLACK);

        //坐标上的文字
        mXYTextPaint = new Paint();
        mXYTextPaint.setAntiAlias(true);
        mXYTextPaint.setStyle(Paint.Style.FILL);
        mXYTextPaint.setDither(true);
        mXYTextPaint.setColor(Color.BLACK);
        mXYTextPaint.setTextSize(mXYTextSize);

        mReadedNumber = new ArrayList<Integer>();
        mPath = new Path();
        mPathReadedIndex = new ArrayList<Integer>();

    }


    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        // 释放资源
        if (null != mAnimator){
            mAnimator.cancel();
            mAnimator.removeAllListeners();
            mAnimator = null;
        }
        if (null != mDotAnimator){
            mDotAnimator.cancel();
            mDotAnimator.removeAllUpdateListeners();;
            mDotAnimator = null;
        }
    }

    @Override
    protected void onDraw(final Canvas canvas) {
        super.onDraw(canvas);
        if (mData==null){
            return;
        }
        //获取多少对数据,准备组织其坐标点位置
        final int dataSize = mData!=null ? mData.size() : 0;
        //将Map转成x与y轴上的list数据
        List<Date> xDatas = new ArrayList<>();
        List<Double> yDatas = new ArrayList<>();
        double minYValue = Double.MAX_VALUE;
        double maxYValue = 0;
        for(Map.Entry<Date,Double> entry : mData.entrySet()){
            xDatas.add(entry.getKey());
            if (entry.getValue()<minYValue){
                minYValue = entry.getValue();
            }
            if (entry.getValue()>maxYValue){
                maxYValue = entry.getValue();
            }
            yDatas.add(entry.getValue());
        }


        //不管什么情况,先把x与y轴画出来
        //x轴
        int startX1 = getPaddingLeft() + mArrowSize;
        int startY1 = getHeight() - getPaddingBottom() - mArrowSize;
        int stopX1 = getWidth() - getPaddingRight();
        canvas.drawLine(startX1, startY1, stopX1, startY1,mXYPaint);
        canvas.drawLine(stopX1-mArrowSize,getHeight()-getPaddingBottom()-2*mArrowSize, stopX1, startY1,mXYPaint);
        canvas.drawLine(stopX1-mArrowSize,getHeight()-getPaddingBottom(), stopX1, startY1,mXYPaint);
        //y轴
        canvas.drawLine(startX1, startY1, startX1,getPaddingTop(),mXYPaint);
        canvas.drawLine(getPaddingLeft(),getPaddingTop()+mArrowSize, startX1,getPaddingTop(),mXYPaint);
        canvas.drawLine(getPaddingLeft()+2*mArrowSize,getPaddingTop()+mArrowSize, startX1,getPaddingTop(),mXYPaint);


        //画x轴的刻度
        int realWidth = getWidth() - getPaddingLeft() - getPaddingRight() - mArrowSize - mRemainSpaceSize;
        int perWidthSize = dataSize != 0? realWidth/dataSize : 0;
        for (int i=0;i<dataSize;i++){
            int startX = getPaddingLeft() + mArrowSize + perWidthSize * (i + 1);
            int startY = startY1;
            canvas.drawLine(startX, startY, startX, startY - mMarkLineSize, mXYPaint);
            String xData = formateDate(xDatas.get(i));
            Rect textRect = new Rect();
            mXYTextPaint.getTextBounds(xData, 0, xData.length(), textRect);

            int textStartX = startX - textRect.width() / 2;
            int textStartY = startY + textRect.height()  + 10;
            canvas.drawText(xData,textStartX,textStartY,mXYTextPaint);
        }


        int realStartY = 0;
        //画y轴的刻度
        //y轴上分多少个刻度
        mYDataSize = 5;
        double perGapY = (maxYValue - minYValue) / (mYDataSize-2);
        int realHeight = getHeight() - getPaddingTop() - getPaddingBottom() - mArrowSize - mRemainSpaceSize;
        int perHeightSize = mYDataSize != 0? realHeight/ mYDataSize : 0;
        for (int i=1;i<=mYDataSize;i++){
            int startX = startX1;
            int startY = getHeight() - getPaddingBottom() - mArrowSize - perHeightSize * i;
            canvas.drawLine(startX, startY, startX + mMarkLineSize, startY, mXYPaint);
            if (i == 2){
                realStartY = startY;  //min y轴 开始的坐标位置
            }

            String yData = decimalFormatFor2Rate(minYValue + (i - 2) * perGapY);
            Rect textRect = new Rect();
            mXYTextPaint.getTextBounds(yData,0,yData.length(),textRect);

            int textStartX = startX - textRect.width() - 10;
            int textStartY = startY + textRect.height() / 2;
            canvas.drawText(yData,textStartX,textStartY,mXYTextPaint);
        }

        if (-1 == mDrawDotIndex){
            mAnimator = ValueAnimator.ofInt(0, dataSize * 100);
            mAnimator.setDuration(5000);
            mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    int animatedValue = (Integer) animation.getAnimatedValue();
                    int i = animatedValue / 100;
                    if (!mReadedNumber.contains(i)&&i<dataSize) {
                        mReadedNumber.add(i);
                        //准备画第i个动画
                        mDrawDotIndex = i;
                        mPercent = -1;
                        postInvalidate();
                    }
                    isDrawing = true;
                }
            });
            mAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    mDrawDotIndex = dataSize - 1;
                    isDrawing = false;
                    mReadedNumber.clear();
                    mPath.reset();
                    mPathReadedIndex.clear();
                    mAnimator.removeAllUpdateListeners();
                }

                @Override
                public void onAnimationCancel(Animator animation) {
                    mDrawDotIndex = dataSize - 1;
                    isDrawing = false;
                }

                @Override
                public void onAnimationRepeat(Animator animation) {
                }
            });
            mAnimator.start();
        }


        //画点
        int radius = 10;
        for (int i=0;i<dataSize;i++){
            if (i<=mDrawDotIndex){
                double yValue = yDatas.get(i);
                double number =  (yValue - minYValue) / perGapY;
                final int startX = getPaddingLeft() + mArrowSize + perWidthSize * (i + 1) - radius/2;
                final int startY = (int) (realStartY - number*perHeightSize - radius/2);
                if (i != mDrawDotIndex){
                    canvas.drawCircle(startX, startY, radius, mDotPaint);
                    if (i != 0 && !mPathReadedIndex.contains(i)){
                        mPath.lineTo(startX, startY);
                        mPathReadedIndex.add(i);
                    }
                }else{
                    //动画效果
                    int fromX = startY1;

                    if (-1 == mPercent){
                        mDotAnimator = ValueAnimator.ofFloat(0, 1);
                        mDotAnimator.setDuration(1000);
                        mDotAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                            @Override
                            public void onAnimationUpdate(ValueAnimator animation) {
                                mPercent = (float) animation.getAnimatedValue();
                                invalidate();
                            }
                        });
                        mDotAnimator.addListener(new ValueAnimator.AnimatorListener() {
                            @Override
                            public void onAnimationStart(Animator animation) {
                                mAnimator.pause();
                            }

                            @Override
                            public void onAnimationEnd(Animator animation) {
                                mAnimator.resume();
                                mPercent = 1;
                                if (mDrawDotIndex != 0 && !mPathReadedIndex.contains(mDrawDotIndex)) {
                                    mPath.lineTo(startX, startY);
                                    mPathReadedIndex.add(mDrawDotIndex);
                                }
                                animation.removeAllListeners();
                            }

                            @Override
                            public void onAnimationCancel(Animator animation) {
                                mPercent = 1;
                            }

                            @Override
                            public void onAnimationRepeat(Animator animation) {
                            }
                        });
                        mDotAnimator.start();
                    }else{
                        int gapY = startY - fromX;
                        int thisStartX = (int) (startY - (1-mPercent)*gapY);
                        canvas.drawCircle(startX, thisStartX, radius, mDotPaint);
                    }
                }

                if (i == 0&&!mPathReadedIndex.contains(i)) {
                    mPath.moveTo(startX,startY);
                    mPathReadedIndex.add(i);
                }

            }
        }

        canvas.drawPath(mPath,mFoldLinePaint);
    }



    public static String decimalFormatFor2Rate(double rate) {
        return new DecimalFormat("#,##0.00").format(rate);
    }

    public static String formateDate(Date date){
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM-dd");
        return simpleDateFormat.format(date);
    }

}

使用DynamicXYChartView的activity代码如下:

package com.afeita.test;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import com.example.chenshufei.myapplication.R;

import java.util.Calendar;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;

/** * <br /> author: chenshufei * <br /> date: 15/10/21 * <br /> email: [email protected] */
public class SecondActivity extends Activity implements View.OnClickListener {

    private DynamicXYChartView mDynamicXYChartView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_second);

        findViewById(R.id.btn_repaint).setOnClickListener(SecondActivity.this);

        Map<Date,Double> dateDoubleMap = emulateData();
        mDynamicXYChartView = (DynamicXYChartView) findViewById(R.id.dxycv_data);
        mDynamicXYChartView.setData(dateDoubleMap,null);

    }

    private Map<Date, Double> emulateData() {
        Map<Date, Double> dateDoubleMap = new LinkedHashMap<>();
        Random random = new Random();
        Calendar calendar = Calendar.getInstance();
        for (int i = 6;i>=0;i--){
            calendar.add(Calendar.DATE,-1*i);
            double value = random.nextDouble() * 10 + 5;

            dateDoubleMap.put(calendar.getTime(), value);
            calendar = Calendar.getInstance();
        }
        return dateDoubleMap;
    }


    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_repaint:
                mDynamicXYChartView.setData(emulateData(), new DynamicXYChartView.OnDrawStataListener() {
                    @Override
                    public void onDrawStata(boolean isDrawing) {
                        Toast.makeText(SecondActivity.this, "正在绘制中,请稍候...", Toast.LENGTH_SHORT).show();
                    }
                });
                break;
        }
    }
}

布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">


    <Button  android:id="@+id/btn_repaint" android:layout_marginTop="30dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="重新绘制图表" android:layout_gravity="center_horizontal"/>

    <com.afeita.test.DynamicXYChartView  android:id="@+id/dxycv_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="16dp" />
</LinearLayout>

注意:为了使用DynamicXYChartView在布局xml中支持wrap_content,这时指定了当计算出是AT_MOST时,分别指定了宽为500dp,高为300dp。

你可能感兴趣的:(自定义view)