Android使用canvas绘制动效雷达图和柱状图

1.雷达图

思路:定义半径来确定各个点的坐标,先绘制虚线背景再绘制两个覆盖区域。通过改变半径值刷新canvas形成动画,也可以加入其它插值。

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import java.util.List;
/**
 * @description: 自定义雷达图
 * @author: fanrunqi
 * @date: 2018/6/8 09:06
 */
public class RadarView extends View{

        private int count = 4;
        private float radius;
        private float radiusStorage;
        private int centerX;
        private int centerY;
        private Paint mainPaint;
        private Paint valuePaint;
        private Paint valuePaint2;
        private List<Double> data;
        private List<Double> data1;
        private float maxValue = 100;
        private float angle;

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

        public RadarView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }

        public RadarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }

        private void init() {
            //设置虚线的间隔和点的长度
            PathEffect effects = new DashPathEffect(new float[]{20f,10f}, 0);
            //背景画笔
            mainPaint = new Paint();
            mainPaint.setPathEffect(effects);
            mainPaint.setColor(Color.GRAY);
            mainPaint.setAntiAlias(true);
            mainPaint.setStrokeWidth(1);
            mainPaint.setStyle(Paint.Style.STROKE);
            //第一个图层画笔
            valuePaint=new Paint();
            valuePaint.setColor(Color.RED);
            valuePaint.setAntiAlias(true);
            valuePaint.setStyle(Paint.Style.FILL);
            //第二个图层画笔
            valuePaint2=new Paint();
            valuePaint2.setColor(Color.BLUE);
            valuePaint2.setAntiAlias(true);
            valuePaint2.setStyle(Paint.Style.FILL);

        }

        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            radiusStorage = radius = Math.min(w, h) / 2 * 1f;
            centerX = w / 2;
            centerY = h / 2;
            super.onSizeChanged(w, h, oldw, oldh);
            startAnim();
        }

        @Override
        protected void onDraw(Canvas canvas) {
            drawPolygon(canvas);
            drawRegion(valuePaint,data,canvas);
            drawRegion(valuePaint2,data1,canvas);
        }

        /**
         * 绘制背景多边形
         */
        private void drawPolygon(Canvas canvas) {
            Path path = new Path();

            angle = (float) (2 * Math.PI / count);
                path.reset();
                for (int j = 0; j < count; j++) {
                    if (j == 0) {
                        path.moveTo(centerX + radiusStorage, centerY);
                    } else {
                        /**
                         * 直角三角形:
                         * sin(x)是对边比斜边
                         * cos(x)是底边比斜边
                         * tan(x)是对边比底边
                         * 推导出:底边(x坐标)=斜边(半径)*cos(夹角角度),对边(y坐标)=斜边(半径)*sin(夹角角度)
                         */
                        float x = (float) (centerX + radiusStorage * Math.cos(angle * j));
                        float y = (float) (centerY + radiusStorage * Math.sin(angle * j));
                        path.lineTo(x, y);
                    }
                }
                path.close();
                canvas.drawPath(path, mainPaint);

            //绘制虚线
            for (int i = 0; i < count*2; i++) {
                path.reset();
                path.moveTo(centerX, centerY);
                float x = (float) (centerX + (radiusStorage+18) * Math.cos(angle/2 * i));
                float y = (float) (centerY + (radiusStorage+18) * Math.sin(angle/2 * i));
                path.lineTo(x, y);
                canvas.drawPath(path, mainPaint);
            }
        }

        /**
         * 绘制覆盖区域
         */
        int Alpha=128;
        private void drawRegion(Paint paint,List<Double> data,Canvas canvas){
            Path path=new Path();
            for (int i = 0; i < count; i++) {
                Double perCenter = data.get(i)/maxValue;
                double perRadius=perCenter*radius;
                float x = (float) (centerX + perRadius * Math.cos(angle * i));
                float y = (float) (centerY + perRadius * Math.sin(angle * i));
                if(i==0){
                    path.moveTo(x,y);
                }else {
                    path.lineTo(x,y);
                }
            }
            //闭合覆盖区域
            path.close();
            //填充覆盖区域
            paint.setAlpha(Alpha);
            paint.setStyle(Paint.Style.FILL);
            canvas.drawPath(path,paint);
        }

        public void startAnim(){

            ValueAnimator animator =  ValueAnimator.ofFloat(0f,radiusStorage);
            animator.setDuration(400);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    radius = (Float) animation.getAnimatedValue();
                    Alpha = (int)(radius/radiusStorage*128);
                    invalidate();
                }
            });
            animator.start();

        }

        //设置数值
        public void setData(List<Double> data,List<Double> data1) {
            this.data = data;
            this.data1=data1;
        }

    }


2. 柱状图

思路:先绘制文字和间隔线,再绘制柱状图,通过柱状图最大高度变化形成加载动画,根据点击位置再画一层点击后放大效果。

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Typeface;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.example.duia.zqdemo.R;
import com.example.duia.zqdemo.bean.LeanRecordBean;
import com.example.duia.zqdemo.utils.DensityUtil;
import java.util.ArrayList;
import java.util.List;


/**
 * @description:  自定义柱状图
 * @author: [email protected]
 * @date: 2018/6/9 11:46
 */
public class LearnRecordView extends View {
    //柱状图之间的间隔距离
    private float interval=2;
    //间距的颜色
    private String intervalColor ="#f0f0f2";
    //view 的宽度
    private float viewWidth;
    //view 的高度
    private float viewHeight;
    //数字距离顶部的距离
    private float numFromTop;
    //文字距离顶部的距离
    private float textFromTop;
    //柱状图的最大高度;
    private float HistogramMaxHeight;
    //每个柱状图的宽度
    private float Histogramwidth;
    //柱状图的背景色
    private String HistogramColor ="#f0f0f2";

    //数字颜色
    private String numColor="#222222";
    //文字颜色
    private String textColor="#a9a9a9";

    //间隔线画笔
    private Paint intervalPaint;
    //柱状图画笔
    private Paint HistogramPaint;
    //数字画笔
    private Paint numPaint;
    //文字画笔
    private Paint textPaint;

    List<LeanRecordBean> datas;

    Context ctx;

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

    public LearnRecordView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LearnRecordView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        ctx =context;
        init();
    }
    //构造函数初始化
    private void init() {

        //数字画笔
        numPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        numPaint.setTypeface(Typeface.DEFAULT_BOLD);
        numPaint.setColor(Color.parseColor(numColor)); // 设置paint颜色
        numPaint.setAntiAlias(true);       // 设置抗锯齿
        numPaint.setTextAlign(Paint.Align.CENTER); // 设置字体居中

        //文字画笔
        textPaint = new TextPaint();
        textPaint.setColor(Color.parseColor(textColor));
        textPaint.setAntiAlias(true);
        textPaint.setTextAlign(Paint.Align.CENTER);

        //间隔线画笔,粗线条
        intervalPaint=new Paint();
        intervalPaint.setColor(Color.parseColor(intervalColor));
        intervalPaint.setAntiAlias(true);
        intervalPaint.setStrokeCap( Paint.Cap.SQUARE);
        intervalPaint.setStrokeWidth(interval);

        //柱状图画笔,画路径连线
        HistogramPaint=new Paint();
        HistogramPaint.setColor(Color.parseColor(HistogramColor));
        HistogramPaint.setAntiAlias(true);

    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        setAxis();
        startAnim();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        drawBg(canvas);

        drawHistogram(canvas);

        if(curItemClick!=-1){
            //画点击效果
            drawClickedItem(canvas);

        }
    }

    int curItemClick = -1;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        /**
         * 判断点击位置
         */
        if(event.getAction() == MotionEvent.ACTION_DOWN){
            float curX = (int)event.getX();
            int distance = (int)(interval+Histogramwidth);
            float i = curX/distance;
            float j = curX%distance;
            if (j>interval){
                if(curItemClick == (int)i){
                    curItemClick = -1;
                }else {
                    curItemClick =(int)i;
                    if(curItemClick>4){
                        curItemClick=4;
                    }
                }
            }
            startAnim2();
        }

        return super.onTouchEvent(event);

    }

    //阴影的宽度
    private float shadowHeight = 30;
    //点击放大的距离
    private float overflowLength=6;
    //间距
    private float space = shadowHeight+overflowLength;
    //红色画布padding距离
    private float padding=20;
    /**
     *  画点击后的视图
     * @param canvas
     */

    String [] itemColors={"#fdd124","#17cff9","#ff3f7c"};
    String [] itemShadow={"#feefe1","#e1f2fe","#fee1ea"};
    private void drawClickedItem(Canvas canvas){

        //需要绘制图标的个数
        List<Bitmap> list = new ArrayList<>();

        if(datas.get(curItemClick).getQuestionFlag()){
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.question_white);
            list.add(setImgSize(bitmap,imgSize,imgSize));
        }
        if(datas.get(curItemClick).getVideoFlag()){
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.video_white);
            list.add(setImgSize(bitmap,imgSize,imgSize));
        }
        if(datas.get(curItemClick).getLiveFlag()){
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.live_white);
            list.add(setImgSize(bitmap,imgSize,imgSize));
        }
        float redHeight = (HistogramMaxHeight/3)*list.size();

        Path path = new Path();
        path.moveTo(x1,y1);
        path.lineTo(x2,y1);
        path.lineTo(x2,y2);
        path.lineTo(x1,y2);
        path.close();
        Paint mPaint =new Paint();
        mPaint.setShadowLayer(shadowHeight,0,3,Color.parseColor(itemShadow[list.size()-1]));
        this.setLayerType(View.LAYER_TYPE_SOFTWARE,mPaint);//设置为SOFTWARE才会实现阴影
        mPaint.setColor(Color.WHITE);
        mPaint.setAntiAlias(true);
        canvas.drawPath(path,mPaint);

        //文字和数字
        numPaint.setTextSize(DensityUtil.sp2px(ctx,18));       // 设置字体大小DP
        textPaint.setTextSize(DensityUtil.sp2px(ctx,12));
        drawTextAndNum(curItemClick,canvas);


        //绘制布
        Path redPath = new Path();
        float z1 = x1+padding;
        float z2 =  x2-padding;
        float w2 = y2-padding;
        float w1 = w2-redHeight;

        redPath.moveTo(z1,w1);
        redPath.lineTo(z2,w1);
        redPath.lineTo(z2,w2);
        redPath.lineTo(z1,w2);
        redPath.close();
        Paint redPaint =new Paint();
        redPaint.setColor(Color.parseColor(itemColors[list.size()-1]));
        redPaint.setAntiAlias(true);
        canvas.drawPath(redPath,redPaint);


        Paint mBitPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mBitPaint.setFilterBitmap(true);
        mBitPaint.setDither(true);


        float distance = (redHeight-imgSize*(list.size()) )/(list.size()+1);
        for(int i=0;i<list.size();i++){
            int left = (int)( (z2-z1-imgSize)/2+z1 );
            int top = (int) ((w2-distance-imgSize)-i*(distance+imgSize) );
            canvas.drawBitmap(list.get(i), left, top, mBitPaint);
        }

    }
    private int imgSize=46;
    /**
     *  重设图片大小
     * @param bm
     * @param newWidth
     * @param newHeight
     * @return
     */
    public Bitmap setImgSize(Bitmap bm, int newWidth ,int newHeight){
        // 获得图片的宽高.
        int width = bm.getWidth();
        int height = bm.getHeight();
        // 计算缩放比例.
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;
        // 取得想要缩放的matrix参数.
        Matrix matrix = new Matrix();
        matrix.postScale(scaleWidth, scaleHeight);
        // 得到新的图片.
        Bitmap newBm = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true);
        return newBm;
    }

    /**
     * 画柱状图
     * @param canvas
     */
    private void drawHistogram(Canvas canvas){

        float HMH = 0;
        Path path = new Path();
        for(int i=0;i<5;i++){
            if(0==i){
                HMH = HistogramMaxHeight0;
            } else if(1==i){
                HMH = HistogramMaxHeight1;
            }else if(2==i){
                HMH = HistogramMaxHeight2;
            }else if(3==i ){
                HMH = HistogramMaxHeight3;
            }else if(4==i){
                HMH = HistogramMaxHeight4;
            }

            float per = datas.get(i).getValue()/3f;
            path.moveTo(space+interval+i*(interval+Histogramwidth),space+viewHeight-(per*HMH));
            path.lineTo(space+interval+i*(interval+Histogramwidth)+Histogramwidth,space+viewHeight-(per*HMH));
            path.lineTo(space+interval+i*(interval+Histogramwidth)+Histogramwidth,space+viewHeight);
            path.lineTo(space+interval+i*(interval+Histogramwidth),space+viewHeight);
            path.close();
            canvas.drawPath(path,HistogramPaint);
        }


    }
    /**
     * 画背景
     * @param canvas
     */
    private void drawBg(Canvas canvas){

        for(int i=0;i<5;i++){
            numPaint.setTextSize(DensityUtil.sp2px(ctx,15));       // 设置字体大小DP
            textPaint.setTextSize(DensityUtil.sp2px(ctx,9));
            drawTextAndNum(i,canvas);
            //--------------------------画间隔线---------------------
            canvas.drawLine( interval/2+i*(Histogramwidth+interval)+space,space,
                    i*(Histogramwidth+interval)+space, space+viewHeight, intervalPaint );
            if(i==4){
                i+=1;
                canvas.drawLine( space+i*(Histogramwidth+interval),space,
                        i*(Histogramwidth+interval)+space, space+viewHeight, intervalPaint );
            }
        }
    }

    private void drawTextAndNum(int i , Canvas canvas){
        //--------------------------画数字---------------------

//            float stringWidth = numPaint.measureText(datas.get(i).getDay());
        Paint.FontMetrics fontMetrics = numPaint.getFontMetrics();
        float stringHeight =  (Math.abs(fontMetrics.ascent) - fontMetrics.descent);
        float x = interval + Histogramwidth/2+ i*(Histogramwidth+interval)+space;
        float y = numFromTop +stringHeight;
        canvas.drawText(datas.get(i).getDay(), x, y, numPaint);

        //--------------------------画文字---------------------

        Paint.FontMetrics fontMetrics1 = textPaint.getFontMetrics();
        float stringHeight1 =  (Math.abs(fontMetrics.ascent) - fontMetrics.descent);
        float x1 = interval + Histogramwidth/2+ i*(Histogramwidth+interval)+space;
        float y1 = textFromTop +stringHeight1;
        canvas.drawText(datas.get(i).getXinqi(), x1, y1, textPaint);

    }

    /**
     * 初始获取画布坐标
     */
    private void setAxis(){
        viewWidth = getWidth()-2*space;
        viewHeight = getHeight()-2*space;

        numFromTop = (float) (viewHeight*0.043478)+space;
        textFromTop = (float)(viewHeight*0.162857)+space;
        HistogramMaxHeight = (float)(viewHeight*0.707764);

        Histogramwidth = (float)((viewWidth-6*interval)/5.0);

    }

    /**
     * 点击放大动画
     */
    float x1,x2,y1,y2;
    float x11,x22;
    public void startAnim2(){
        x11=x1 =space+ curItemClick*(interval+Histogramwidth)-overflowLength;
        x22=x2 = space+(1+curItemClick)*(interval+Histogramwidth)+overflowLength;
        y1 = shadowHeight;
        y2 = getHeight()-shadowHeight;

        ValueAnimator animator = ValueAnimator.ofFloat(0,100);
        animator.setDuration(300);
        animator.setInterpolator(new AccelerateDecelerateInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float v = (float)animation.getAnimatedValue();
                x1 = x11+overflowLength-overflowLength*(v/100);
                x2 = x22-overflowLength+overflowLength*(v/100);
                y1 = 30+overflowLength - overflowLength*(v/100);
                y2 = getHeight()-30-overflowLength+ overflowLength*(v/100);

                shadowHeight = 30*(v/100);
                invalidate();
            }
        });
        animator.start();
    }

    /**
     * 柱状图加载动画
     */
    float HistogramMaxHeight0,HistogramMaxHeight1,HistogramMaxHeight2,HistogramMaxHeight3,HistogramMaxHeight4 =0;
    public void startAnim(){

        final Handler handler=new Handler();
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                synAnim(count);
                if (count < 5) {
                    handler.postDelayed(this, 100);
                    count++;
                }
            }
        };
        handler.postDelayed(runnable, 100);
    }
    int count=0;
    public void synAnim(final int i){
        ValueAnimator animator = ValueAnimator.ofFloat(0,HistogramMaxHeight);
        animator.setDuration(700-i*100);
        animator.setInterpolator(new AccelerateDecelerateInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                setHistogramMaxHeight((float)animation.getAnimatedValue(),i);
                invalidate();
            }
        });
        animator.start();

    }
    private void setHistogramMaxHeight(float H,int i){
        switch (i){
            case 0:
                HistogramMaxHeight0 = H;
                break;
            case 1:
                HistogramMaxHeight1 = H;
                break;
            case 2:
                HistogramMaxHeight2 = H;
                break;
            case 3:
                HistogramMaxHeight3 = H;
                break;
            case 4:
                HistogramMaxHeight4 = H;
                break;

        }
    }


    public void setData(List<LeanRecordBean> datas){
        this.datas=datas;
    }
}

  1. 布局和activity设置

布局:

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:clipChildren="false"
    android:clipToPadding="false"
    android:background="#fff"
    >
    <com.example.duia.zqdemo.view.RadarView
        android:layout_margin="20dp"
        android:id="@+id/radarview"
        android:layout_width="157dp"
        android:layout_height="157dp" />


    <com.example.duia.zqdemo.view.LearnRecordView
        android:layout_marginTop="20dp"
        android:id="@+id/learnRecordView"
        android:layout_width="300dp"
        android:layout_height="188dp" />


</LinearLayout>

Activity:

public class MainActivity extends AppCompatActivity {

RadarView radarView;

LearnRecordView lrview;
List<LeanRecordBean> datas;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    //--------------------------------雷达图-----------------------------------------------------------
    radarView = findViewById(R.id.radarview);

    List<Double> data=new ArrayList<>();
    data.add(40.0);
    data.add(80.0);
    data.add(80.0);
    data.add(80.0);

    List<Double> data1=new ArrayList<>();
    data1.add(80.0);
    data1.add(70.0);
    data1.add(30.0);
    data1.add(80.0);

    radarView.setData(data,data1);

    //----------------------------------柱状图---------------------------------------------------------

    lrview = findViewById(R.id.learnRecordView);
    int[] values = {1,2,3,1,2};
    String [] days ={"Mon","Tue","Wed","Thu","Fri"};
    String [] xins ={"21","22","23","24","25"};
    datas = new ArrayList<>();
    for (int i=0;i<5;i++){
        LeanRecordBean bean = new LeanRecordBean();
        bean.setDay(xins[i]);
        bean.setXinqi(days[i]);
        bean.setValue(values[i]);
        if(values[i]==1){
            bean.setLiveFlag(true);
        }else if(values[i]==2){
            bean.setLiveFlag(true);
            bean.setQuestionFlag(true);
        }else if(values[i]==3){
            bean.setLiveFlag(true);
            bean.setQuestionFlag(true);
            bean.setVideoFlag(true);
        }
        datas.add(bean);
    }
    lrview.setData(datas);
}

}

工具类:

import android.content.Context;

public class DensityUtil {

/**
 * 根据手机的分辨率从 dip 的单位 转成为 px(像素)
 */
public static int dip2px(Context context, float dpValue) {
    final float scale = context.getResources().getDisplayMetrics().density;
    return (int) (dpValue * scale + 0.5f);
}

/**
 * 根据手机的分辨率从 px(像素) 的单位 转成为 dp==dip
 */
public static int px2dip(Context context, float pxValue) {
    final float scale = context.getResources().getDisplayMetrics().density;
    return (int) (pxValue / scale + 0.5f);
}
/**
 * 将px值转换为sp值,保证文字大小不变
 *
 * @param pxValue
 *            (DisplayMetrics类中属性scaledDensity)
 * @return
 */
public static int px2sp(Context context, float pxValue) {
    final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
    return (int) (pxValue / fontScale + 0.5f);
}
/**
 * 将sp值转换为px值,保证文字大小不变
 *
 * @param spValue
 *            (DisplayMetrics类中属性scaledDensity)
 * @return
 */
public static int sp2px(Context context, float spValue) {
    final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
    return (int) (spValue * fontScale + 0.5f);
}

}


猜你喜欢

转载自blog.csdn.net/qq_41145352/article/details/80673637
今日推荐