Android 绘制折线并移动动画效果

QQ视频20170811134913.gif
我们来看看启StartAnimationRunnable 做了什么,从这里我们可以看到nowIndex初始化为0,limitX初始化为左边距 ,然后调用AnimationRunnable。

AnimationRunnable
nowIndex 现在要绘制的终点位置角标
remainder 取的角标余数

Paste_Image.png

说这么多我们看来看log日志,根据这个日志我们可以看到remainder 这个值 就是两点之间的比例
nowIndex 就是我们当前终点角标

Paste_Image.png

接下来我们画线的时候 使用到这些参数来完成动画效果,这里我们可以看到 nowIndex 绘制线的数量,通过判断 只有最后一条的时候要根据取到的余数 继续后面的绘制,noew跟数据的长度一样,最后这里就不需要绘制了。
我们可以看到 取到两点的x,y距离
然后根据remainder 计算出我现在应该处于的位置,在原有的x,y基础上+计算出的x,y 距离

Paste_Image.png

MoveTheLineChart

package com.xiaoxiongchart.chart;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.util.AttributeSet;

import com.xiaoxiongchart.utils.ChartUtil;

public class MoveTheLineChart extends AbsChart {
    private  Paint mLinePaint;

    public void setSpeed(int speed){
        this.speed=speed;
    }
    public MoveTheLineChart(Context context) {
        super(context);
        initMoveLinePaint();
    }

    @Override
    public void setValues(float[] values) {
        super.setValues(values);
        nowIndex=values.length;
    }

    public MoveTheLineChart(Context context, AttributeSet attrs) {
        super(context, attrs);
        initMoveLinePaint();
    }
    private void initMoveLinePaint() {
        if(mLinePaint==null){
            mLinePaint=new Paint();
        }
        mLinePaint.setAntiAlias(true);
        mLinePaint.setColor(Color.BLUE);
        mLinePaint.setStyle(Paint.Style.STROKE);
        mLinePaint.setStrokeWidth(ChartUtil.dip2px(3,getContext()));
        mLinePaint.setShader(getShader(new int[]{0x00ffffff,Color.BLUE}));
        mLinePaint.setStrokeJoin(Paint.Join.ROUND);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        if(mPoints==null)return;
        initLineDraw(canvas);
        initLine(canvas);
    }


    private void initLine(Canvas canvas) {
        Path path=new Path();
        mLinePaint.setAlpha(255);
        for (int i = 0; i < nowIndex; i++) {
            Point mPoint =mPoints[i];
            float x=mPoint.x;
            float y=mPoint.y;
            if(i==0){
                path.moveTo(x,y);
            }else {
                path.lineTo(x,y);
            }
            if(i==nowIndex-1 &&i!=mPoints.length-1){
                /**获取两点之间的x距离*/
                float vx=mPoints[1].x-mPoints[0].x;
                /**获取两点之间的y距离*/
                float vy=mPoints[i+1].y-mPoints[i].y;
                /**绘制最后一条线的时候根据remainder 绘制剩余部分*/
                float x1=x+vx*remainder;
                float y1=y+vy*remainder;
                path.lineTo(x1,y1);
            }
        }


        canvas.drawPath(path,mLinePaint);

        Path path1=new Path();
        for (int i = 0; i < mPoints.length; i++) {
            Point mPoint =mPoints[i];
            float x=mPoint.x;
            float y=mPoint.y;
            if(i==0){
                path1.moveTo(x,y);
            }else {
                path1.lineTo(x,y);
            }
        }
        mLinePaint.setAlpha(100);
        canvas.drawPath(path1,mLinePaint);
    }
    /**启动动画*/
    public void  StartAnimationRunnable(){
        this.nowIndex=0;
        this.limitX=l;
        postDelayed(new AnimationRunnable(),100);
    }

    /**限制x坐标最大值*/
    private float limitX;
    /**余数绘制线时用的*/
    private float remainder;
    /**目前要绘制的线数量*/
    private int nowIndex;
    /**移动的速度,越大 移动越快*/
    private int speed=5;
    private class  AnimationRunnable implements  Runnable{
        @Override
        public void run() {
            if(mPoints!=null)
                if(mPoints.length>nowIndex){
                    limitX=limitX+speed;
                    /**每条数据的间距*/
                    float pading =mPoints[1].x-mPoints[0].x;
                    float index=(limitX-l)/pading;
                    remainder= (float) (index-Math.floor(index));
                    nowIndex= (int) ((limitX-l)/pading);
                    postDelayed(this,15);
                    postInvalidate();
                }

        }
    }
}

AbsChart主要绘制一些公用部分

package com.xiaoxiongchart.chart;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.ViewGroup;

import com.xiaoxiongchart.utils.ChartUtil;


public class AbsChart extends ViewGroup {
    /**左上右下 边距*/
    public int  l,t,r,b;

    /**整个View的宽,高*/
    public int  width,height;
    /**要绘制的值*/
    private float[]  values;
    /**最终得x,y*/
    public Point[] mPoints;
    /**x线画笔*/
    private Paint mLinePaint;
    /**文本画笔*/
    private Paint mTextPaint;
    /**图表的高度*/
    private float mNeedDrawHeight;
    /**图表的宽度*/
    private float mNeedDrawWidth;
    /**最大值,最小值,总数值*/
    private float maxVlaue,minValue,calculateValue;
    /**每条横线之前的间距,线的条数*/
    private float averageLineValue;
    private  int  numberLine=5;
    /**设置最大值。最小值*/
    public  void  setMaxAndMin(float maxVlaue,float minValue){
        this.maxVlaue=maxVlaue;
        this.minValue=minValue;
    }
    public void setNumberLine(int numberLine){
        this.numberLine=numberLine;
    }
    /**设置左上右下边距*/
    public  void setChartPading(int l,int t,int r,int b){
        Context context=getContext();
        this.l=ChartUtil.dip2px(l,context);
        this.t=ChartUtil.dip2px(t,context);
        this.r=ChartUtil.dip2px(r,context);
        this.b=ChartUtil.dip2px(b,context);
    }
    public  void setValues(float[] values){
        this.values=values;
        mPoints=getPoints(values);
    }

    public AbsChart(Context context) {
        super(context);
        initPaint();
    }
    private   void  initPoint(){
         mPoints=getPoints(values);
    }

    public AbsChart(Context context, AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }
    public void  initPaint(){
        if(mLinePaint==null) {
            mLinePaint = new Paint();
            mLinePaint.setTextSize(ChartUtil.dip2px(13,getContext()));
            mLinePaint.setColor(0x66888888);
            mLinePaint.setAntiAlias(true);
            mLinePaint.setStyle(Paint.Style.STROKE);
        }
        if(mTextPaint==null) {
            mTextPaint = new Paint();
            mTextPaint.setTextSize(ChartUtil.dip2px(13,getContext()));
            mTextPaint.setColor(Color.BLACK);
            mTextPaint.setAntiAlias(true);
            mTextPaint.setTextAlign(Paint.Align.RIGHT);
            mTextPaint.setStyle(Paint.Style.FILL);
        }
    }
    /**获取线画笔*/
    public Paint  getLinePaint(){
        return mLinePaint;
    }
    /**获取文本画笔*/
    public Paint getTextPaint(){
        return mTextPaint;
    }

    /**初始化绘制的宽高*/
    private void initNeedDrawWidthAndHeight(){
        this.mNeedDrawWidth = width-l-r;
        this.mNeedDrawHeight = height-t-b;
    }
    public LinearGradient getShader(int[] color){
        return new LinearGradient(l-1 ,t,l,t,color, null, Shader.TileMode.CLAMP);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        this.width=getMeasuredWidth();
        this.height=getMeasuredHeight();
        initNeedDrawWidthAndHeight();
        /**计算总值*/
        calculateValue=maxVlaue-minValue;
        /**计算框线横线间隔的数据平均值*/
        averageLineValue = calculateValue/(numberLine-1);
        initPoint();
    }
    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {

    }

    /**绘制图标基础线,文本*/
    public  void  initLineDraw(Canvas canvas){
        DrawHorizontalLine(canvas);
        DrawVerticalLine(canvas);
        DrawLineAndText(canvas);
    }

    /**绘制横线*/
    public  void  DrawHorizontalLine(Canvas canvas){
        canvas.drawLine(l,height-b,width,height-b,mLinePaint);
    }
    /**绘制竖线*/
    public  void DrawVerticalLine(Canvas canvas){
        canvas.drawLine(l,t,l,height-b,mLinePaint);
    }
    /**绘制边框线和边框文本*/
    public void DrawLineAndText(Canvas canvas) {
        /**绘制边框分段横线与分段文本*/
        float averageHeight=mNeedDrawHeight/(numberLine-1);
        for (int i = 0; i < numberLine; i++) {
            float nowadayHeight= averageHeight*i;
            float v=averageLineValue*(numberLine-1-i)+minValue;
            canvas.drawText(v+"",l-ChartUtil.dip2px(2,getContext()),nowadayHeight+t,mTextPaint);
        }
    }
    /**根据值计算在该值的 x,y坐标*/
    public Point[] getPoints(float[] values){
        return ChartUtil.getPoints(values,mNeedDrawHeight,mNeedDrawWidth,calculateValue,minValue,l,t);
    }



}

ChartUtil

package com.xiaoxiongchart.utils;

import android.content.Context;
import android.graphics.Point;



public class ChartUtil {
    /**
     *根据值计算在该值的 x,y坐标
     * @param values float值数组
     * @param height 要计算的高度
     * @param width  要计算的高度
     * @param max    最大值
     * @param min    最小值
     * @param left   x轴左边距
     * @param top    y 上边距
     * @return
     */
    public static  Point[] getPoints(float[] values, float height, float width, float max , float min, float left, float top) {
        float leftPading = width / (values.length-1);//绘制边距
        Point[] points = new Point[values.length];
        for (int i = 0; i < values.length; i++) {
            double value = values[i]-min;
            //计算每点高度所以对应的值
            double mean = (double) max/height;
            //获取要绘制的高度
            float drawHeight = (float) (value / mean);
            int pointY = (int) (height+top  - drawHeight);
            int pointX = (int) (leftPading * i + left);
            Point point = new Point(pointX, pointY);
            points[i] = point;
        }
        return points;
    }
    public  static int dip2px(float dipValue ,Context context) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }
}

Activity 调用

package com.xiaoxiongchart;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;

import com.xiaoxiongchart.chart.MoveTheLineChart;

import java.util.Random;

public class MainActivity extends Activity implements OnClickListener {

    private MoveTheLineChart mMoveTheLineChart;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mMoveTheLineChart =findViewById(R.id.move_line);
        mMoveTheLineChart.setChartPading(40,20,10,20);
        mMoveTheLineChart.setMaxAndMin(60,-10);
        mMoveTheLineChart.setValues(getFloat(10,60));
        mMoveTheLineChart.setSpeed(3);
        mMoveTheLineChart.postInvalidate();
        mMoveTheLineChart.StartAnimationRunnable();

    }
    private  float[]  getFloat(int  length,int  max){
        Random random=new Random();
        float[] floats=new float[length];
        for (int i = 0; i < floats.length; i++) {
            float f=  random.nextFloat();
            floats[i]=f*max;
        }
        return floats;
    }

    @Override
    public void onClick(View view) {
        mMoveTheLineChart.StartAnimationRunnable();
    }
}

Xml 布局

<?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">
    <com.xiaoxiongchart.chart.MoveTheLineChart
        android:background="@android:color/white"
        android:id="@+id/move_line"
        android:layout_width="match_parent"
        android:layout_height="200dp">
    </com.xiaoxiongchart.chart.MoveTheLineChart>
    <Button
        android:onClick="onClick"
        android:id="@+id/start"
        android:text="启动动画"
        android:layout_width="match_parent"
        android:layout_height="50dp"/>

</LinearLayout>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,398评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,585评论 18 139
  • 今天是第一次上简书,还没用过这个。按我一般的习惯来说,我会忌讳在做一件新事情的时候,找这样不好的开端。今天也算是破...
    d48749ed04b6阅读 178评论 0 0
  • 阿明是我的良师益友,他经常把他个人的观点告诉我,不知不觉他说了很多话。有的我觉得好的,有的我觉得不对,有的我一时不...
    辰戌丑未阅读 606评论 0 1
  • 蒋旻和小君拖着鱼竿一摇一摆的回家时,太阳已经落山了。落日余晖,光亮还在。罗一苇和村里面的几个小伙伴正在外面跳皮筋,...
    阿洙AZ阅读 293评论 0 1