android开源库---线性图表SuitLines

最近看到一个合适的开源库,是线性图表的,感觉以后可能会用到,这里就写下,因为作者写的直接用类,自己也好修改。给你们链接吧点击打开链接

一、简介首先适用于API14以上的(感觉这点很致命,不知道能不能规避)

静态属性对应API说明
xySizesetXySizexy轴文字大小
xyColorsetXyColorxy轴文字的颜色,包含轴线
lineTypesetLineType指定line类型:CURVE / SEGMENT(曲线/线段)
StylesetLineStyle指定line的风格:DASHED / SOLID(虚线/实线)
needEdgeEffectdisableEdgeEffect关闭边缘效果,默认开启
colorEdgeEffectsetEdgeEffectColor指定边缘效果的颜色,默认为Color.GRAY
needClickHintdisableClickHint关闭点击提示信息,默认开启
colorHintsetHintColor设置提示辅助线、文字颜色
maxOfVisible/一组数据在可见区域中的最大可见点数,至少>=2
countOfY/y轴刻度数,至少>=1
/setLineSize设置line在非填充形态时的大小
/setLineForm设置line的形态:是否填充,默认为false

二、填充数据

对于一条line,可以直接调用feed或feedWithAnim方法:

List<Unit> lines = new ArrayList<>();
for (int i = 0; i < 14; i++) {
    lines.add(new Unit(new SecureRandom().nextInt(48), i + ""));
}
suitLines.feedWithAnim(lines);

如果是多条数据,则需要通过Builder来实现:

SuitLines.LineBuilder builder = new SuitLines.LineBuilder();
for (int j = 0; j < count; j++) {
    List<Unit> lines = new ArrayList<>();
    for (int i = 0; i < 50; i++) {
        lines.add(new Unit(new SecureRandom().nextInt(128), "" + i));
    }
    builder.add(lines, new int[]{...});//第一个参数是线,第二个参数是颜色
}
builder.build(suitLines, true);

三、测试代码

布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <com.example.admin.suitlines.SuitLines
        xmlns:line="http://schemas.android.com/apk/res-auto"
        android:id="@+id/suitlines"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:background="#dfdede"
        line:lineType="curve" />

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="add"
                android:id="@+id/btn_add"/>
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="sub"
                android:id="@+id/btn_sub"/>
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="type(曲线/线段)"
                android:id="@+id/btn_type"/>
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="type(虚/实)"
                android:id="@+id/btn_type2"/>
        </LinearLayout>

</LinearLayout>
代码
public class MainActivity extends AppCompatActivity implements View.OnClickListener{


    private int[] color = {Color.RED, Color.GRAY, 0xFFF76055, 0xFF9B3655, 0xFFF7A055};
    private SuitLines suitLines;
    private int count;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        suitLines = (SuitLines) findViewById(R.id.suitlines);

        findViewById(R.id.btn_add).setOnClickListener(this);
        findViewById(R.id.btn_sub).setOnClickListener(this);
        findViewById(R.id.btn_type).setOnClickListener(this);
        findViewById(R.id.btn_type2).setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.btn_add:
                count++;
                changData();
                break;
            case R.id.btn_sub:
                count--;
                changData();
                break;
            case R.id.btn_type:
                suitLines.setLineType(suitLines.getLineType() == SuitLines.CURVE ? SuitLines.SEGMENT : SuitLines.CURVE);
                break;
            case R.id.btn_type2:
                suitLines.setLineStyle(suitLines.isLineDashed()?SuitLines.SOLID:SuitLines.DASHED);
                break;
        }
    }
    public void changData(){
        Log.e("count是:",count+"");
        if (count <= 0) {
            count = 0;
        }
        if (count == 1) {
            List<Unit> lines = new ArrayList<>();
            for (int i = 0; i < 14; i++) {
                lines.add(new Unit(new SecureRandom().nextInt(48), i + "d"));
            }
            suitLines.feedWithAnim(lines);
            return;
        }
        SuitLines.LineBuilder builder = new SuitLines.LineBuilder();
        for (int j = 0; j < count; j++) {
            List<Unit> lines = new ArrayList<>();
            for (int i = 0; i < 50; i++) {
                lines.add(new Unit(new SecureRandom().nextInt(128), "" + i));
            }
            builder.add(lines, new int[]{color[new SecureRandom().nextInt(4)], color[new SecureRandom().nextInt(4)], color[new SecureRandom().nextInt(4)]});
        }
        builder.build(suitLines, true);
    }
}

四、需要用的类

Util
class Util {

    static int getCeil5(float num) {
        return ((int) (num + 4.9f)) / 5 * 5;
    }

    static float calcTextSuitBaseY(RectF rectF, Paint paint) {
        return rectF.top + rectF.height() / 2 -
                (paint.getFontMetrics().ascent + paint.getFontMetrics().descent) / 2;
    }

    static float size2sp(float sp, Context context) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                sp, context.getResources().getDisplayMetrics());
    }

    static int dip2px(float dipValue) {
        final float scale = Resources.getSystem().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    static float getTextHeight(Paint textPaint) {
        return -textPaint.ascent() - textPaint.descent();
    }

    static void trySetColorForEdgeEffect(EdgeEffect edgeEffect, int color) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            edgeEffect.setColor(color);
            return;
        }
        try {
            Field edgeField = EdgeEffect.class.getDeclaredField("mEdge");
            edgeField.setAccessible(true);
            Drawable mEdge = (Drawable) edgeField.get(edgeEffect);
            mEdge.setColorFilter(color, PorterDuff.Mode.SRC_IN);
            mEdge.setCallback(null);
            Field glowField = EdgeEffect.class.getDeclaredField("mGlow");
            glowField.setAccessible(true);
            Drawable mGlow = (Drawable) glowField.get(edgeEffect);
            mGlow.setColorFilter(color, PorterDuff.Mode.SRC_IN);
            mGlow.setCallback(null);
        } catch (Exception ignored) {
        }
    }
}
Unit
public class Unit implements Comparable<Unit>, Cloneable {

    static long DURATION = 800;
    private ValueAnimator VALUEANIMATOR = ValueAnimator.ofFloat(0, 1);

    /**
     * 当前点的值
     */
    private float value;
    // 当前点的额外信息(可选,x轴)
    private String extX;
    /**
     * 当前点的坐标信息,都是相对的canvas而不是linesArea
     */
    private PointF xy;

    /**
     * 当前点的动画进度,
     * 默认为1表示无动画
     */
    private float percent = 1f;

    public Unit(float value) {
        this.value = value;
    }

    public Unit(float value, String extX) {
        this.value = value;
        this.extX = extX;
    }


    public float getValue() {
        return value;
    }
    void setXY(PointF xy) {
        this.xy = xy;
    }
    PointF getXY() {
        return xy;
    }
    void setPercent(float percent) {
        this.percent = percent;
    }

    float getPercent() {
        return percent;
    }

    public void setExtX(String extX) {
        this.extX = extX;
    }

    String getExtX() {
        return extX;
    }


    void cancelToEndAnim() {
        if (VALUEANIMATOR.isRunning()) {
            VALUEANIMATOR.cancel();
        }
        percent = 1f;
    }

    void startAnim(TimeInterpolator value) {
        // 如果value小于一定阈值就不开启动画
        if (VALUEANIMATOR.isRunning() || (int)this.value <= 1) {
            return;
        }
        VALUEANIMATOR.setFloatValues(0, 1);
        VALUEANIMATOR.setDuration(DURATION);
        VALUEANIMATOR.setInterpolator(value);
        VALUEANIMATOR.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                percent = (float) animation.getAnimatedValue();
            }
        });
        VALUEANIMATOR.start();
    }



    @Override
    public int compareTo(Unit o) {
        if (value == o.value) {
            return 0;
        } else if (value > o.value) {
            return 1;
        } else {
            return -1;
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Unit)) return false;
        Unit unit = (Unit) obj;
        return value == unit.value
                && (extX == unit.extX) || (extX != null && extX.equals(unit.extX));
    }

    @Override
    public String toString() {
        return "Unit{" +
                "xy=" + xy +
                '}';
    }

    @Override
    protected Unit clone() {// 转化为深度拷贝,防止在集合中排序时的引用问题
        try {
            return (Unit) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}
SuitLines

public class SuitLines extends View {

    public static final String TAG = SuitLines.class.getSimpleName();

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

    public SuitLines(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SuitLines(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initOptionalState(context, attrs);

        basePadding = Util.dip2px(basePadding);
        maxVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity();
        clickSlop = ViewConfiguration.get(context).getScaledEdgeSlop();
        scroller = new Scroller(context);
        edgeEffectLeft = new EdgeEffect(context);
        edgeEffectRight = new EdgeEffect(context);
        setEdgeEffectColor(edgeEffectColor);

        basePaint.setColor(defaultLineColor[0]);
        basePaint.setStyle(Paint.Style.STROKE);
        basePaint.setStrokeWidth(4);
        setLineStyle(SOLID);
        xyPaint.setTextSize(Util.size2sp(defaultXySize, getContext()));
        xyPaint.setColor(defaultXyColor);
        hintPaint.setTextSize(Util.size2sp(12, getContext()));
        hintPaint.setColor(hintColor);
        hintPaint.setStyle(Paint.Style.STROKE);
        hintPaint.setStrokeWidth(2);
        hintPaint.setTextAlign(Paint.Align.CENTER);
    }

    private void initOptionalState(Context ctx, AttributeSet attrs) {
        TypedArray ta = ctx.obtainStyledAttributes(attrs, R.styleable.suitlines);
        defaultXySize = ta.getFloat(R.styleable.suitlines_xySize, defaultXySize);
        defaultXyColor = ta.getColor(R.styleable.suitlines_xyColor, defaultXyColor);
        lineType = ta.getInt(R.styleable.suitlines_lineType, CURVE);
        lineStyle = ta.getInt(R.styleable.suitlines_lineStyle, SOLID);
        needEdgeEffect = ta.getBoolean(R.styleable.suitlines_needEdgeEffect, needEdgeEffect);
        edgeEffectColor = ta.getColor(R.styleable.suitlines_colorEdgeEffect, edgeEffectColor);
        needShowHint = ta.getBoolean(R.styleable.suitlines_needClickHint, needShowHint);
        hintColor = ta.getColor(R.styleable.suitlines_colorHint, hintColor);
        maxOfVisible = ta.getInt(R.styleable.suitlines_maxOfVisible, maxOfVisible);
        countOfY = ta.getInt(R.styleable.suitlines_countOfY, countOfY);
        ta.recycle();
    }


    // 创建自己的Handler,与ViewRootImpl的Handler隔离,方便detach时remove。
    private Handler handler = new Handler(Looper.getMainLooper());
    // 遍历线上点的动画插值器
    private TimeInterpolator linearInterpolator = new LinearInterpolator();
    // 每个数据点的动画插值
    private TimeInterpolator pointInterpolator = new OvershootInterpolator(3);
    private RectF linesArea, xArea, yArea, hintArea;
    /**
     * 默认画笔
     */
    private Paint basePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    /**
     * x,y轴对应的画笔
     */
    private Paint xyPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    /**
     * 点击提示的画笔
     */
    private Paint hintPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    /**
     * 默认画笔的颜色,索引0位置为画笔颜色,整个数组为shader颜色
     */
    private int[] defaultLineColor = {Color.RED, 0xFFF35E54, Color.YELLOW};
    private int hintColor = Color.RED;
    /**
     * xy轴文字颜色和大小
     */
    private int defaultXyColor = Color.GRAY;
    private float defaultXySize = 8;
    /**
     * 每根画笔对应一条线
     */
    private List<Paint> paints = new ArrayList<>();
    private List<Path> paths = new ArrayList<>();
    /**
     * 约定:如果需要实现多组数据,那么每组数据的长度必须相同!
     * 多组数据的数据池;
     * Key:一组数据的唯一标识,注意:要求连续且从0开始
     * value:一组数据
     */
    private Map<Integer, List<Unit>> datas = new HashMap<>();

    /**
     * 所有数据集的动画
     */
    private List<ValueAnimator> animators = new ArrayList<>();
    /**
     * line的点击效果
     */
    private ValueAnimator clickHintAnimator;
    /**
     * 当前正在动画的那组数据
     */
    private int curAnimLine;
    /**
     * 整体动画的起始时间
     */
    private long startTimeOfAnim;
    /**
     * 是否正在整体动画中
     */
    private boolean isAniming;
    /**
     * 两个点之间的动画启动间隔,大于0时仅当总数据点<可见点数时有效
     */
    private long intervalOfAnimCost = 100;
    /**
     * 可见区域中,将一组数据遍历完总共花费的最大时间
     */
    private long maxOfAnimCost = 1000;
    /**
     * 一组数据在可见区域中的最大可见点数,至少>=2
     */
    private int maxOfVisible = 7;
    /**
     * 文本之间/图表之间的间距
     */
    private int basePadding = 4;
    /**
     * y轴刻度数,至少>=1
     */
    private int countOfY = 5;

    /**
     * y轴的缓存,提高移动效率
     */
    private Bitmap yAreaBuffer;

    /**
     * y轴的最大刻度值,保留一位小数
     */
    private float maxValueOfY;

    /**
     * 根据可见点数计算出的两点之间的距离
     */
    private float realBetween;
    /**
     * 手指/fling的上次位置
     */
    private float lastX;
    /**
     * 滚动当前偏移量
     */
    private float offset;
    /**
     * 滚动上一次的偏移量
     */
    private float lastOffset;
    /**
     * 滚动偏移量的边界
     */
    private float maxOffset;
    /**
     * fling最大速度
     */
    private int maxVelocity;
    // 点击y的误差
    private int clickSlop;
    /**
     * 判断左/右方向,当在边缘就不触发fling,以优化性能
     */
    float orientationX;
    private VelocityTracker velocityTracker;
    private Scroller scroller;

    private EdgeEffect edgeEffectLeft, edgeEffectRight;
    // 对于fling,仅吸收到达边缘时的速度
    private boolean hasAbsorbLeft, hasAbsorbRight;
    /**
     * 是否需要边缘反馈效果
     */
    private boolean needEdgeEffect = true;
    private int edgeEffectColor = Color.GRAY;
    /**
     * 点击是否弹出额外信息
     */
    private boolean needShowHint = true;
    /**
     * 实际的点击位置,0为x索引,1为某条line
     */
    private int[] clickIndexs;
    private float firstX, firstY;
    /**
     * 控制是否强制重新生成path,当改变lineType/paint时需要
     */
    private boolean forceToDraw;

    /**
     * lines在当前可见区域的边缘点
     */
    private int[] suitEdge;

    // 曲线、线段
    public static final int CURVE = 0;
    public static final int SEGMENT = 1;
    private int lineType = CURVE;
    public static final int SOLID = 0;
    public static final int DASHED = 1;
    private int lineStyle = SOLID;


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        calcAreas();
        basePaint.setShader(buildPaintColor(defaultLineColor));
        if (!datas.isEmpty()) {
            calcUnitXY();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (datas.isEmpty() || isAniming) {
            recycleVelocityTracker();
            return false;
        }
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                firstX = lastX = event.getX();
                firstY = event.getY();
                scroller.abortAnimation();
                initOrResetVelocityTracker();
                velocityTracker.addMovement(event);
                super.onTouchEvent(event);
                return true;
            case MotionEvent.ACTION_POINTER_DOWN:
                lastX = event.getX(0);
                break;
            case MotionEvent.ACTION_MOVE:
                orientationX = event.getX() - lastX;
                onScroll(orientationX);
                lastX = event.getX();
                velocityTracker.addMovement(event);
                if (needEdgeEffect && datas.get(0).size() > maxOfVisible) {
                    if (isArriveAtLeftEdge()) {
                        edgeEffectLeft.onPull(Math.abs(orientationX) / linesArea.height());
                    } else if (isArriveAtRightEdge()) {
                        edgeEffectRight.onPull(Math.abs(orientationX) / linesArea.height());
                    }
                }
                break;
            case MotionEvent.ACTION_POINTER_UP: // 计算出正确的追踪手指
                int minID = event.getPointerId(0);
                for (int i = 0; i < event.getPointerCount(); i++) {
                    if (event.getPointerId(i) <= minID) {
                        minID = event.getPointerId(i);
                    }
                }
                if (event.getPointerId(event.getActionIndex()) == minID) {
                    minID = event.getPointerId(event.getActionIndex() + 1);
                }
                lastX = event.getX(event.findPointerIndex(minID));
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                if (needShowHint && event.getAction() == MotionEvent.ACTION_UP) {
                    boolean canCallTap = Math.abs(event.getX() - firstX) < 2
                            && Math.abs(event.getY() - firstY) < 2;
                    if (canCallTap) {
                        onTap(event.getX(), event.getY());
                    }
                }
                velocityTracker.addMovement(event);
                velocityTracker.computeCurrentVelocity(1000, maxVelocity);
                int initialVelocity = (int) velocityTracker.getXVelocity();
                velocityTracker.clear();
                if (!isArriveAtLeftEdge() && !isArriveAtRightEdge()) {
                    scroller.fling((int) event.getX(), (int) event.getY(), initialVelocity / 2,
                            0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
                    invalidate();
                } else {
                    edgeEffectLeft.onRelease();
                    edgeEffectRight.onRelease();
                }
                lastX = event.getX();
                break;
        }

        return super.onTouchEvent(event);
    }


    @Override
    public void computeScroll() {
        if (scroller.computeScrollOffset()) {
            onScroll(scroller.getCurrX() - lastX);
            lastX = scroller.getCurrX();
            if (needEdgeEffect) {
                if (!hasAbsorbLeft && isArriveAtLeftEdge()) {
                    hasAbsorbLeft = true;
                    edgeEffectLeft.onAbsorb((int) scroller.getCurrVelocity());
                } else if (!hasAbsorbRight && isArriveAtRightEdge()) {
                    hasAbsorbRight = true;
                    edgeEffectRight.onAbsorb((int) scroller.getCurrVelocity());
                }
            }
            postInvalidate();
        } else {
            hasAbsorbLeft = false;
            hasAbsorbRight = false;
        }
    }


    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        if (datas.isEmpty()) return;
        if (!needEdgeEffect) return;
        if (!edgeEffectLeft.isFinished()) {
            canvas.save();
            canvas.rotate(-90);
            canvas.translate(-linesArea.bottom, linesArea.left);
            edgeEffectLeft.setSize((int) linesArea.height(), (int) linesArea.height());
            if (edgeEffectLeft.draw(canvas)) {
                postInvalidate();
            }
            canvas.restore();
        }

        if (!edgeEffectRight.isFinished()) {
            canvas.save();
            canvas.rotate(90);
            canvas.translate(linesArea.top, -linesArea.right);
            edgeEffectRight.setSize((int) linesArea.height(), (int) linesArea.height());
            if (edgeEffectRight.draw(canvas)) {
                postInvalidate();
            }
            canvas.restore();
        }
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (datas.isEmpty()) return;
        // lines
        canvas.save();
        canvas.clipRect(linesArea.left, linesArea.top, linesArea.right, linesArea.bottom+xArea.height());
        canvas.translate(offset, 0);
        // 当滑动到边缘 或 上次与本次结果相同 或 不需要计算边缘点 的时候就不再计算,直接draw已有的path
        if (!paths.isEmpty() && !forceToDraw && !isAniming && (lastOffset == offset || noNeedCalcEdge(offset))) {
            drawExsitDirectly(canvas);
            // hint
            if (clickIndexs != null) {
                drawClickHint(canvas);
            }
        } else {
            // 因为手指或fling计算出的offset不是连续按1px递增/减的,即无法准确地确定当前suitEdge和linesArea之间的相对位置
            // 所以不适合直接加减suitEdge来划定数据区间
            suitEdge = findSuitEdgeInVisual2();
            drawLines(canvas, suitEdge[0], suitEdge[1]);
        }
        // x 蓝色会稍增加
        drawX(canvas, suitEdge[0], suitEdge[1]);
        if (lastOffset != offset) {
            clickIndexs = null;
        }
        lastOffset = offset;
        forceToDraw = false;
        canvas.restore();
        // y
        drawY(canvas);
    }

    /**
     * 边缘点在可见区域两侧时不需要重新计算<br>
     * 但是手指滑动越快,该分支的有效效果越差
     * @param offset
     * @return
     */
    private boolean noNeedCalcEdge(float offset) {
        return suitEdge != null
                && datas.get(0).get(suitEdge[0]).getXY().x <= linesArea.left - offset
                && datas.get(0).get(suitEdge[1]).getXY().x >= linesArea.right - offset;
    }

    /**
     * 滑动方法,同时检测边缘条件
     *
     * @param deltaX
     */
    private void onScroll(float deltaX) {
        offset += deltaX;
        offset = offset > 0 ? 0 : (Math.abs(offset) > maxOffset) ? -maxOffset : offset;
        invalidate();
    }


    private void onTap(float upX, float upY) {
        upX -= offset;
        RectF bak = new RectF(linesArea);
        bak.offset(-offset,0);
        if (datas.isEmpty() || !bak.contains(upX, upY)) {
            return;
        }
        float index = (upX - linesArea.left) / realBetween;
        int realIndex = -1;
        if ((index - (int) index) > 0.6f) {
            realIndex = (int) index + 1;
        } else if ((index - (int) index) < 0.4f) {
            realIndex = (int) index;
        }
        if (realIndex != -1) {
            int mostMatchY = -1;
            for (int i = 0; i < datas.size(); i++) {
                float cur = Math.abs(datas.get(i).get(realIndex).getXY().y - upY);
                if (cur <= clickSlop) {
                    if (mostMatchY != -1) {
                        if (Math.abs(datas.get(mostMatchY).get(realIndex).getXY().y - upY) > cur) {
                            mostMatchY = i;
                        }
                    } else {
                        mostMatchY = i;
                    }
                }
            }
            if (mostMatchY != -1) {
                if (clickHintAnimator != null && clickHintAnimator.isRunning()) {
                    clickHintAnimator.removeAllUpdateListeners();
                    clickHintAnimator.cancel();
                    hintPaint.setAlpha(100);
                    clickIndexs = null;
                    invalidate();
                }
                clickIndexs = new int[]{realIndex, mostMatchY};
                clickHintAnimator = ValueAnimator.ofInt(100, 30);
                clickHintAnimator.setDuration(800);
                clickHintAnimator.setInterpolator(linearInterpolator);
                clickHintAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        int cur = (Integer) animation.getAnimatedValue();
                        if (cur <= 30) {
                            hintPaint.setAlpha(100);
                            clickIndexs = null;
                        } else {
                            hintPaint.setAlpha(cur);
                        }
                        postInvalidate();
                    }
                });
                clickHintAnimator.start();
            }
        }
    }

    private void initOrResetVelocityTracker() {
        if (velocityTracker == null) {
            velocityTracker = VelocityTracker.obtain();
        } else {
            velocityTracker.clear();
        }
    }

    private void recycleVelocityTracker() {
        if (velocityTracker != null) {
            velocityTracker.recycle();
            velocityTracker = null;
        }
    }

    /**
     * 是否滑动到了左边缘,注意,并非指可视区域的边缘,下同
     *
     * @return
     */
    private boolean isArriveAtLeftEdge() {
        return offset == 0 && orientationX > 0;
    }

    /**
     * 是否滑动到了右边缘
     *
     * @return
     */
    private boolean isArriveAtRightEdge() {
        return Math.abs(offset) == Math.abs(maxOffset) && orientationX < 0;
    }

    /**
     * 找到当前可见区间内合适的两个边缘点,注意如果边缘点不在可见区间的边缘,则需要包含下一个不可见的点
     *
     * @return
     */
    private int[] findSuitEdgeInVisual() {
        int startIndex = 0, endIndex = datas.get(0).size() - 1;
        if (offset == 0) {// 不可滑动或当前位于最左边
            startIndex = 0;
            endIndex = Math.min(datas.get(0).size() - 1, maxOfVisible - 1);
        } else if (Math.abs(offset) == maxOffset) {// 可滑动且当前位于最右边
            endIndex = datas.get(0).size() - 1;
            startIndex = endIndex - maxOfVisible + 1;
        } else {
            float startX = linesArea.left - offset;
            float endX = linesArea.right - offset;
            if (datas.get(0).size() > maxOfVisible) {
                // 找到指定区间的第一个被发现的点
                int suitKey = 0;
                int low = 0;
                int high = datas.get(0).size() - 1;
                List<Unit> i = datas.get(0);
                while (low <= high) {
                    int mid = (low + high) >>> 1;
                    Unit midVal = i.get(mid);
                    if (midVal.getXY().x < startX) {
                        low = mid + 1;
                    } else if (midVal.getXY().x > endX) {
                        high = mid - 1;
                    } else {
                        suitKey = mid;
                        break;
                    }
                }
                int bakKey = suitKey;
                // 先左边
                while (suitKey >= 0) {
                    startIndex = suitKey;
                    if (datas.get(0).get(suitKey).getXY().x <= startX) {
                        break;
                    }
                    suitKey--;
                }
                suitKey = bakKey;
                // 再右边
                while (suitKey < datas.get(0).size()) {
                    endIndex = suitKey;
                    if (datas.get(0).get(suitKey).getXY().x >= endX) {
                        break;
                    }
                    suitKey++;
                }
            }
        }
        return new int[]{startIndex, endIndex};
    }

    /**
     * 1. ax+b >= y
     * 2. a(x+1)+b <= y
     * 得到: (int)x = (y-b) / a
     * 由于 y = b - offset
     * 所以:(int)x = |offset| / a
     * @return
     */
    private int[] findSuitEdgeInVisual2() {
        int startIndex, endIndex;
        if (offset == 0) {// 不可滑动或当前位于最左边
            startIndex = 0;
            endIndex = Math.min(datas.get(0).size() - 1, maxOfVisible - 1);
        } else if (Math.abs(offset) == maxOffset) {// 可滑动且当前位于最右边
            endIndex = datas.get(0).size() - 1;
            startIndex = endIndex - maxOfVisible + 1;
        } else {
            startIndex = (int) (Math.abs(offset) / realBetween);
            endIndex = startIndex + maxOfVisible;
        }
        return new int[]{startIndex, endIndex};
    }

    /**
     * 开始连接每条线的各个点<br>
     * 最耗费性能的地方:canvas.drawPath
     * @param canvas
     * @param startIndex
     * @param endIndex
     */
    private void drawLines(Canvas canvas, int startIndex, int endIndex) {
        for (int i = 0; i < paths.size(); i++) {
            paths.get(i).reset();
        }
        for (int i = startIndex; i <= endIndex; i++) {
            for (int j = 0; j < datas.size(); j++) {
                Unit current = datas.get(j).get(i);
                float curY = linesArea.bottom - (linesArea.bottom - current.getXY().y) * current.getPercent();
                if (i == startIndex) {
                    paths.get(j).moveTo(current.getXY().x, curY);
                    continue;
                }
                if (lineType == SEGMENT) {
                    paths.get(j).lineTo(current.getXY().x, curY);
                } else if (lineType == CURVE) {
                    // 到这里肯定不是起始点,所以可以减1
                    Unit previous = datas.get(j).get(i - 1);
                    // 两个锚点的坐标x为中点的x,y分别是两个连接点的y
                    paths.get(j).cubicTo((previous.getXY().x + current.getXY().x) / 2,
                            linesArea.bottom - (linesArea.bottom - previous.getXY().y) * previous.getPercent(),
                            (previous.getXY().x + current.getXY().x) / 2, curY,
                            current.getXY().x, curY);
                }
                if (isLineFill() && i == endIndex) {
                    paths.get(j).lineTo(current.getXY().x, linesArea.bottom);
                    paths.get(j).lineTo(datas.get(j).get(startIndex).getXY().x, linesArea.bottom);
                    paths.get(j).close();
                }
            }
        }
        drawExsitDirectly(canvas);
    }

    /**
     * 直接draw现成的
     * @param canvas
     */
    private void drawExsitDirectly(Canvas canvas) {
        // TODO 需要优化
        for (int j = 0; j < datas.size(); j++) {
            canvas.drawPath(paths.get(j), paints.get(j));
        }
        // TODO 画点
    }

    /**
     * 画提示文本和辅助线
     * @param canvas
     */
    private void drawClickHint(Canvas canvas) {
        Unit cur = datas.get(clickIndexs[1]).get(clickIndexs[0]);
        canvas.drawLine(datas.get(clickIndexs[1]).get(suitEdge[0]).getXY().x,cur.getXY().y,
                datas.get(clickIndexs[1]).get(suitEdge[1]).getXY().x,cur.getXY().y, hintPaint);
        canvas.drawLine(cur.getXY().x,linesArea.bottom,
                cur.getXY().x,linesArea.top, hintPaint);
        RectF bak = new RectF(hintArea);
        bak.offset(-offset, 0);
        hintPaint.setAlpha(100);
        hintPaint.setStyle(Paint.Style.FILL);
        canvas.drawRect(bak, hintPaint);
        hintPaint.setColor(Color.WHITE);
        if (!TextUtils.isEmpty(cur.getExtX())) {
            canvas.drawText("x : " + cur.getExtX(), bak.centerX(), bak.centerY() - 12, hintPaint);
        }
        canvas.drawText("y : " + cur.getValue(), bak.centerX(),
                bak.centerY() + 12 + Util.getTextHeight(hintPaint), hintPaint);
        hintPaint.setColor(hintColor);
    }

    /**
     * 画x轴,默认取第一条线的值
     * @param canvas
     * @param startIndex
     * @param endIndex
     */
    private void drawX(Canvas canvas, int startIndex, int endIndex) {
        canvas.drawLine(datas.get(0).get(startIndex).getXY().x, xArea.top,
                datas.get(0).get(endIndex).getXY().x, xArea.top, xyPaint);
        for (int i = startIndex; i <= endIndex; i++) {
            String extX = datas.get(0).get(i).getExtX();
            if (TextUtils.isEmpty(extX)) {
                continue;
            }
            if (i == startIndex && startIndex == 0) {
                xyPaint.setTextAlign(Paint.Align.LEFT);
            } else if (i == endIndex && endIndex == datas.get(0).size()-1) {
                xyPaint.setTextAlign(Paint.Align.RIGHT);
            } else {
                xyPaint.setTextAlign(Paint.Align.CENTER);
            }
            canvas.drawText(extX, datas.get(0).get(i).getXY().x, Util.calcTextSuitBaseY(xArea, xyPaint), xyPaint);
        }
    }


    private void drawY(Canvas canvas) {
        if (yAreaBuffer == null) {
            yAreaBuffer = Bitmap.createBitmap((int)yArea.width(), (int)yArea.height(), Bitmap.Config.ARGB_8888);
            Rect yRect = new Rect(0, 0, yAreaBuffer.getWidth(), yAreaBuffer.getHeight());
            Canvas yCanvas = new Canvas(yAreaBuffer);
            yCanvas.drawLine(yRect.right, yRect.bottom, yRect.right, yRect.top, xyPaint);
            for (int i = 0; i < countOfY; i++) {
                xyPaint.setTextAlign(Paint.Align.RIGHT);
                float extY;
                float y;
                if (i == 0) {
                    extY = 0;
                    y = yRect.bottom;
                } else if (i == countOfY - 1) {
                    extY = maxValueOfY;
                    y = yRect.top + Util.getTextHeight(xyPaint) + 3;
                } else {
                    extY = maxValueOfY / (countOfY - 1) * i;
                    y = yRect.bottom - yRect.height() / (countOfY - 1) * i + Util.getTextHeight(xyPaint)/2;
                }
                yCanvas.drawText(new DecimalFormat("##.#").format(extY), yRect.right - basePadding, y, xyPaint);
            }
        }
        canvas.drawBitmap(yAreaBuffer,yArea.left,yArea.top,null);


    }

    /**
     *
     * @param color 不能为null
     * @return
     */
    private LinearGradient buildPaintColor(int[] color) {
        int[] bakColor = color;
        if (color != null && color.length < 2) {
            bakColor = new int[2];
            bakColor[0] = color[0];
            bakColor[1] = color[0];
        }
        return new LinearGradient(linesArea.left, linesArea.top,
                linesArea.left, linesArea.bottom, bakColor, null, Shader.TileMode.CLAMP);
    }

    /**
     * 基于orgPaint的clone
     *
     * @return
     */
    private Paint buildNewPaint() {
        Paint paint = new Paint();
        paint.set(basePaint);
        return paint;
    }


    private void feedInternal(Map<Integer, List<Unit>> entry, List<Paint> entryPaints, boolean needAnim) {
        cancelAllAnims();
        reset(); // 该方法调用了datas.clear();
        if (entry.isEmpty()) {
            invalidate();
            return;
        }
        if (entry.size() != entryPaints.size()) {
            throw new IllegalArgumentException("线的数量应该和画笔数量对应");
        } else {
            paints.clear();
            paints.addAll(entryPaints);
        }
        if (entry.size() != paths.size()) {
            paths.clear();
            for (int i = 0; i < entry.size(); i++) {
                paths.add(new Path());
            }
        }
        datas.putAll(entry);
        calcMaxUnit(datas);
        calcAreas();
        calcUnitXY();
        if (needAnim) {
            showWithAnims();
        } else {
            forceToDraw = true;
            invalidate();
        }
    }

    /**
     * 得到maxValueOfY
     * @param datas
     */
    private void calcMaxUnit(Map<Integer, List<Unit>> datas) {
        // 先“扁平”
        List<Unit> allUnits = new ArrayList<>();
        for (List<Unit> line : datas.values()) {
            allUnits.addAll(line);
        }
        // 再拷贝,防止引用问题
        List<Unit> bakUnits = new ArrayList<>();
        for (int i = 0; i < allUnits.size(); i++) {
            bakUnits.add(allUnits.get(i).clone());
        }
        // 最后排序,得到最大值
        Collections.sort(bakUnits);
        Unit maxUnit = bakUnits.get(bakUnits.size() - 1);
        maxValueOfY = Util.getCeil5(maxUnit.getValue());
    }

    /**
     * 重新计算三个区域的大小
     */
    private void calcAreas() {
        String baseY = "00";
        if (maxValueOfY > 0) {
            baseY = String.valueOf(maxValueOfY);
        }
        RectF validArea = new RectF(getPaddingLeft() + basePadding, getPaddingTop() + basePadding,
                getMeasuredWidth() - getPaddingRight() - basePadding, getMeasuredHeight() - getPaddingBottom());
        yArea = new RectF(validArea.left, validArea.top,
                validArea.left + xyPaint.measureText(baseY) + basePadding,
                validArea.bottom - Util.getTextHeight(xyPaint) - basePadding * 2);
        xArea = new RectF(yArea.right, yArea.bottom, validArea.right, validArea.bottom);
        linesArea = new RectF(yArea.right+1, yArea.top, xArea.right, yArea.bottom);
        hintArea = new RectF(linesArea.right-linesArea.right/4,linesArea.top,
                linesArea.right,linesArea.top + linesArea.height()/4);
    }

    /**
     * 计算所有点的坐标
     * <br>同时得到了realBetween,maxOffset
     */
    private void calcUnitXY() {
        int realNum = Math.min(datas.get(0).size(), maxOfVisible);
        realBetween = linesArea.width() / (realNum - 1);
        for (int i = 0; i < datas.get(0).size(); i++) {
            for (int j = 0; j < datas.size(); j++) {
                datas.get(j).get(i).setXY(new PointF(linesArea.left + realBetween * i,
                        linesArea.top + linesArea.height() * (1 - datas.get(j).get(i).getValue() / maxValueOfY)));
                if (i == datas.get(0).size() - 1) {
                    maxOffset = Math.abs(datas.get(j).get(i).getXY().x) - linesArea.width() - linesArea.left;
                }
            }
        }
    }

    /**
     * 取消所有正在执行的动画,若存在的话;
     * 在 重新填充数据 / dettach-view 时调用
     */
    private void cancelAllAnims() {
        // 不使用ViewRootImpl的getHandler(),否则影响其事件分发
        handler.removeCallbacksAndMessages(null);
        scroller.abortAnimation();
        if (clickHintAnimator != null && clickHintAnimator.isRunning()) {
            clickHintAnimator.removeAllUpdateListeners();
            clickHintAnimator.cancel();
            hintPaint.setAlpha(100);
            clickHintAnimator = null;
        }
        if (!animators.isEmpty()) {
            for (int i = 0; i < animators.size(); i++) {
                animators.get(i).removeAllUpdateListeners();
                if (animators.get(i).isRunning()) {
                    animators.get(i).cancel();
                }
            }
            animators.clear();
        }
        if (!datas.isEmpty()) {
            for (List<Unit> line : datas.values()) {
                for (int i = 0; i < line.size(); i++) {
                    line.get(i).cancelToEndAnim();
                }
            }
        }
        for (int i = 0; i < paths.size(); i++) {
            paths.get(i).reset();
        }
        invalidate();
    }

    // 每个1/x启动下一条line的动画
    private int percentOfStartNextLineAnim = 3;
    /**
     * 约定每间隔一组数据遍历总时间的一半就启动下一组数据的遍历
     *
     * @return 遍历时间+最后一组数据的等待时间+最后一个点的动画时间+缓冲时间
     */
    private long calcTotalCost() {
        if (datas.isEmpty() || datas.get(0).isEmpty()) return 0;
        long oneLineCost = calcVisibleLineCost();
        return oneLineCost + oneLineCost / percentOfStartNextLineAnim * (datas.size() - 1) + Unit.DURATION + 16;
    }

    /**
     * 一条线遍历完的时间,
     *
     * @return
     */
    private long calcVisibleLineCost() {
        if (intervalOfAnimCost > 0) {
            if (maxOfVisible < datas.get(0).size()) {
                return maxOfAnimCost;
            }
            long oneLineCost = intervalOfAnimCost * (datas.get(0).size() - 1);
            oneLineCost = Math.min(maxOfAnimCost, oneLineCost);
            return oneLineCost;
        } else {
            return 0;
        }
    }

    private void showWithAnims() {
        if (datas.isEmpty()) return;
        curAnimLine = 0;
        startTimeOfAnim = System.currentTimeMillis();
        int[] suitEdge = findSuitEdgeInVisual();

        // 重置所有可见点的percent
        for (int i = suitEdge[0]; i <= suitEdge[1]; i++) {
            for (List<Unit> item : datas.values()) {
                item.get(i).setPercent(0);
            }
        }
        startLinesAnimOrderly(suitEdge[0], suitEdge[1]);
        autoInvalidate();
    }

    /**
     * 开启自动刷新
     */
    private void autoInvalidate() {
        isAniming = true;
        invalidate();
        if (System.currentTimeMillis() - startTimeOfAnim > calcTotalCost()) {
            isAniming = false;
            return;
        }
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                autoInvalidate();
            }
        }, 16);
    }

    /**
     * 间隔指定时间依次启动每条线
     */
    private void startLinesAnimOrderly(final int startIndex, final int endIndex) {
        startLineAnim(startIndex, endIndex);
        if (curAnimLine >= datas.size() - 1) return;
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                curAnimLine++;
                startLinesAnimOrderly(startIndex, endIndex);
            }
        }, calcVisibleLineCost() / percentOfStartNextLineAnim);
    }

    /**
     * 依次启动指定label的线的每个可见点的动画;
     *
     * @param startIndex
     * @param endIndex
     */
    private void startLineAnim(int startIndex, int endIndex) {
        final List<Unit> line = datas.get(curAnimLine);
        long duration = calcVisibleLineCost();
        if (duration > 0) {
            ValueAnimator animator = ValueAnimator.ofInt(startIndex, endIndex);
            animator.setDuration(duration);
            animator.setInterpolator(linearInterpolator);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    line.get((Integer) animation.getAnimatedValue()).startAnim(pointInterpolator);
                }
            });
            animator.start();
            animators.add(animator);
        } else {
            for (int i = startIndex; i <= endIndex; i++) {
                line.get(i).startAnim(pointInterpolator);
            }
        }
    }

    /**
     * 重置相关状态
     */
    private void reset() {
        invaliateYBuffer();
        offset = 0;
        realBetween = 0;
        suitEdge = null;
        clickIndexs = null;
        datas.clear();
    }

    private void invaliateYBuffer() {
        if (yAreaBuffer != null) {
            yAreaBuffer.recycle();
            yAreaBuffer = null;
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        cancelAllAnims();
        reset();
    }

    ///APIs/

    /**
     * 设置默认一条line时的颜色
     * @param colors    默认为defaultLineColor
     */
    public void setDefaultOneLineColor(int...colors) {
        if (colors == null || colors.length < 1) return;
        defaultLineColor = colors;
        basePaint.setColor(colors[0]);
        basePaint.setShader(buildPaintColor(colors));
        if (!datas.isEmpty() && datas.size() == 1) {
            paints.get(0).set(basePaint);
            postInvalidate();
        }
    }

    /**
     * 设置提示辅助线、文字颜色
     * @param hintColor
     */
    public void setHintColor(int hintColor) {
        needShowHint = true;
        this.hintColor = hintColor;
        hintPaint.setColor(hintColor);
        if (!datas.isEmpty()) {
            if (clickIndexs != null) {
                postInvalidate();
            }
        }
    }

    /**
     * 设置xy轴文字的颜色
     * @param color 默认为Color.GRAY
     */
    public void setXyColor(int color) {
        defaultXyColor = color;
        xyPaint.setColor(defaultXyColor);
        if (!datas.isEmpty()) {
            invaliateYBuffer();
            forceToDraw = true;
            postInvalidate();
        }
    }

    /**
     * 设置xy轴文字大小
     * @param sp
     */
    public void setXySize(float sp) {
        defaultXySize = sp;
        xyPaint.setTextSize(Util.size2sp(defaultXySize, getContext()));
        if (!datas.isEmpty()) {
            invaliateYBuffer();
            calcAreas();
            calcUnitXY();
            offset = 0;// fix bug.
            forceToDraw = true;
            postInvalidate();
        }
    }


    /**
     * 设置line的SEGMENT时的大小
     * @param lineSize
     */
    public void setLineSize(float lineSize) {
        basePaint.setStyle(Paint.Style.STROKE);
        basePaint.setStrokeWidth(lineSize);
        // 同时更新当前已存在的paint
        for (int i = 0; i < paints.size(); i++) {
            forceToDraw = true;
            paints.get(i).setStyle(basePaint.getStyle());
            paints.get(i).setStrokeWidth(lineSize);
        }
        postInvalidate();
    }

    /**
     * 指定line类型:CURVE / SEGMENT
     * @param lineType  默认CURVE
     */
    public void setLineType(int lineType) {
        this.lineType = lineType;
        forceToDraw = true;
        postInvalidate();
    }

    public int getLineType() {
        return lineType;
    }

    /**
     * 设置line的形态:是否填充
     * @param isFill    默认为false
     */
    public void setLineForm(boolean isFill) {
        if (isFill) {
            basePaint.setStyle(Paint.Style.FILL);
        } else {
            basePaint.setStyle(Paint.Style.STROKE);
        }
        if (!datas.isEmpty()) {
            // 同时更新当前已存在的paint
            for (int i = 0; i < paints.size(); i++) {
                forceToDraw = true;
                paints.get(i).setStyle(basePaint.getStyle());
            }
            postInvalidate();
        }
    }

    public boolean isLineFill() {
        return basePaint.getStyle() == Paint.Style.FILL;
    }

    public void setLineStyle(int style) {
        lineStyle = style;
        basePaint.setPathEffect(lineStyle == DASHED ? new DashPathEffect(new float[]{Util.dip2px(3),Util.dip2px(6)},0) : null);
        if (!datas.isEmpty()) {
            // 同时更新当前已存在的paint
            for (int i = 0; i < paints.size(); i++) {
                forceToDraw = true;
                paints.get(i).setPathEffect(basePaint.getPathEffect());
            }
            postInvalidate();
        }
    }

    public boolean isLineDashed() {
        return basePaint.getPathEffect() != null;
    }

    /**
     * 关闭边缘效果,默认开启
     */
    public void disableEdgeEffect() {
        needEdgeEffect = false;
        postInvalidate();
    }

    /**
     * 关闭点击提示信息,默认开启
     */
    public void disableClickHint() {
        needShowHint = false;
    }

    /**
     * 指定边缘效果的颜色
     * @param color 默认为Color.GRAY
     */
    public void setEdgeEffectColor(int color) {
        needEdgeEffect = true;
        edgeEffectColor = color;
        Util.trySetColorForEdgeEffect(edgeEffectLeft, edgeEffectColor);
        Util.trySetColorForEdgeEffect(edgeEffectRight, edgeEffectColor);
        postInvalidate();
    }


    /**
     * 本方式仅支持一条线,若需要支持多条线,请采用Builder方式
     *
     * @param line
     */
    public void feedWithAnim(List<Unit> line) {
        if (line == null || line.isEmpty()) return;
        final Map<Integer, List<Unit>> entry = new HashMap<>();
        entry.put(0, line);
        handler.post(new Runnable() {
            @Override
            public void run() {
                feedInternal(entry, Arrays.asList(buildNewPaint()), true);
            }
        });
    }

    /**
     * 本方式仅支持一条线,若需要支持多条线,请采用Builder方式
     *
     * @param line
     */
    public void feed(List<Unit> line) {
        if (line == null || line.isEmpty()) return;
        final Map<Integer, List<Unit>> entry = new HashMap<>();
        entry.put(0, line);
        handler.post(new Runnable() {
            @Override
            public void run() {
                feedInternal(entry, Arrays.asList(buildNewPaint()), false);
            }
        });
    }

    public void anim() {
        if (datas.isEmpty()) return;
        handler.post(new Runnable() {
            @Override
            public void run() {
                cancelAllAnims();
                showWithAnims();
            }
        });
    }

    public void postAction(Runnable runnable) {
        handler.post(runnable);
    }

    

    // 多条线的情况应该采用该构建方式
    public static class LineBuilder {
        private int curIndex;
        private Map<Integer, List<Unit>> datas;
        private Map<Integer, int[]> colors;


        public LineBuilder() {
            datas = new HashMap<>();
            colors = new HashMap<>();
        }

        /**
         * 该方式是用于构建多条line,单条line可使用lineGraph#feed
         * @param data  单条line的数据集合
         * @param color 指定当前line的颜色。默认取数组的第一个颜色;另外如果开启了填充,则整个数组颜色作为填充色的渐变。
         * @return
         */
        public LineBuilder add(List<Unit> data, int... color) {
            if (data == null || data.isEmpty() || color == null || color.length <= 0) {
                throw new IllegalArgumentException("无效参数data或color");
            }
            int bakIndex = curIndex;
            datas.put(bakIndex, data);
            colors.put(bakIndex, color);
            curIndex++;
            return this;
        }

        /**
         * 调用该方法开始填充数据
         * @param suitLines 需要被填充的图表
         * @param needAnim  是否需要动画
         */
        public void build(final SuitLines suitLines, final boolean needAnim) {
            final List<Paint> tmpPaints = new ArrayList<>();
            for (int i = 0; i < colors.size(); i++) {
                Paint paint = suitLines.buildNewPaint();
                paint.setColor(colors.get(0)[0]);
                paint.setShader(suitLines.buildPaintColor(colors.get(i)));
                tmpPaints.add(i, paint);
            }
            suitLines.postAction(new Runnable() {
                @Override
                public void run() {
                    suitLines.feedInternal(datas, tmpPaints, needAnim);
                }
            });
        }
    }
}


好了具体还是看别人的源码,我也是储备下(->.->)





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值