欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android 自定义控件教程:绘制一个会动的能力分布图

程序员文章站 2022-07-03 20:27:32
Android 自定义控件教程:绘制一个会动的能力分布图。 一、准备工作 新建类,继承View,修改构造方法为: public AbilityView(Conte...

Android 自定义控件教程:绘制一个会动的能力分布图。

一、准备工作

新建类,继承View,修改构造方法为:

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

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

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

即一参和二参方法都调用三参的方法。

二、初始化

全局变量:

    //数据
    private AbilityBean data;
    //角的个数
    private int n;
    //顶点到中心点的距离
    private float radius;
    //半径分为几段
    private int intervalCount;
    //间隔的角度
    private float angle;
    //画线的笔
    private Paint linePaint;
    //画文字的笔
    private Paint textPaint;
    //存储多边形顶点数组的数组
    private List> pointsArrayList;
    //存储能力点的数组
    private ArrayList abilityPoints;
    //控制变化的参数
    private static int count = 0 ;
    private int times = 40;
    private int intervalTime = 25;
    //计时线程池
    private ScheduledExecutorService scheduledExecutorService;
    private MyHandler myHandler;

初始化方法:

 private void init(Context context, AttributeSet attrs){
        // 获取自定义的属性
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.AbilityView);
        n = typedArray.getInteger(R.styleable.AbilityView_corner,7);
        radius = typedArray.getDimension(R.styleable.AbilityView_radius,dp2px(context,100));
        intervalCount = typedArray.getInteger(R.styleable.AbilityView_intervalCount,4);
        typedArray.recycle();
        // 算出间隔角度
        angle = (float) ((2 * Math.PI)/n);
        // 初始化集合存储各个顶点
        pointsArrayList = new ArrayList<>();
        float x;
        float y;
        for (int i = 0; i < intervalCount; i++) {
            List pointFList = new ArrayList<>();
            float r = radius * ((float)(intervalCount-i)/intervalCount);
            for (int j = 0; j < n; j++) {
                // 此处减去π/2是为了让点逆时针旋转90度(为了让图是立着的 更加美观)
                x = (float) (r * Math.cos(j * angle - Math.PI/2));
                y = (float) (r * Math.sin(j * angle - Math.PI/2));
                pointFList.add(new PointF(x,y));
            }
            pointsArrayList.add(pointFList);
        }
        abilityPoints = new ArrayList<>();
        // 初始化画笔
        linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        linePaint.setStrokeWidth(dp2px(context,1f));
        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setTextSize(sp2px(context,14));
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.setColor(Color.BLACK);
        // 初始化线程池 用于动画变动
        scheduledExecutorService = Executors.newScheduledThreadPool(1);
        myHandler = new MyHandler(this);
    }

在各个部分都给出了注释。

另外还有Handler的类:

    private static class MyHandler extends Handler{
        WeakReference weakReference;

        public MyHandler(View view){
            weakReference = new WeakReference<>(view);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            count++;
            weakReference.get().invalidate();
        }
    }

注意内存泄露的隐患

三、绘制
@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 将画布移动到中心
        canvas.translate(getWidth()/2,getHeight()/2);
        // 画每个面
        drawPolygon(canvas);
        // 勾勒外围轮廓线
        drawOutLine(canvas);
        // 绘制文本
        drawText(canvas);
        // 画出能力值的线
        drawAbility(canvas);
    }
    private void drawPolygon(Canvas canvas) {
        canvas.save();
        linePaint.setStyle(Paint.Style.FILL_AND_STROKE);
        Path path = new Path();
        for (int i = 0; i < intervalCount; i++) {
            switch (i) {
                case 0:
                    linePaint.setColor(Color.parseColor("#D4F0F3"));
                    break;
                case 1:
                    linePaint.setColor(Color.parseColor("#99DCE2"));
                    break;
                case 2:
                    linePaint.setColor(Color.parseColor("#56C1C7"));
                    break;
                case 3:
                    linePaint.setColor(Color.parseColor("#278891"));
                    break;
                default:
            }
            for (int j = 0; j < n; j++) {
                float x = pointsArrayList.get(i).get(j).x;
                float y = pointsArrayList.get(i).get(j).y;
                if (j == 0) {
                    path.moveTo(x,y);
                }else{
                    path.lineTo(x,y);
                }
            }
            path.close();
            canvas.drawPath(path,linePaint);
            path.reset();
        }
        canvas.restore();
    }

    private void drawOutLine(Canvas canvas) {
        canvas.save();
        Path path = new Path();
        linePaint.setColor(Color.parseColor("#99DCE2"));
        linePaint.setStyle(Paint.Style.STROKE);
        for (int i = 0; i < n; i++) {
            float x = pointsArrayList.get(0).get(i).x;
            float y = pointsArrayList.get(0).get(i).y;
            if (i==0) {
                path.moveTo(x,y);
            }else{
                path.lineTo(x,y);
            }
            canvas.drawLine(0,0,x,y,linePaint);
        }
        path.close();
        canvas.drawPath(path,linePaint);
        canvas.restore();
    }

    private void drawText(Canvas canvas) {
        canvas.save();
        Paint pointPaint = new Paint();
        pointPaint.setStrokeWidth(dp2px(getContext(),2f));
        pointPaint.setStyle(Paint.Style.STROKE);
        float r = radius + dp2px(getContext(), 15f);
        Paint.FontMetrics metrics = textPaint.getFontMetrics();
        float textFont = (metrics.descent + metrics.ascent) / 2f;
        for (int i = 0; i < n; i++) {
            float x = (float) (r * Math.cos(angle * i - Math.PI/2));
            float y = (float) (r * Math.sin(angle * i - Math.PI/2)) - textFont ;
            canvas.drawText(AbilityBean.ABILITY[i],x,y,textPaint);
        }
        canvas.restore();
    }

比较简单,按着顺序一步一步画下来就可以了
效果图:
Android 自定义控件教程:绘制一个会动的能力分布图
最后一步画出

    private void drawAbility(final Canvas canvas) {
        if (data == null) {
            return;
        }
        linePaint.setColor(Color.parseColor("#E96153"));
        linePaint.setStyle(Paint.Style.STROKE);
        linePaint.setStrokeWidth(dp2px(getContext(),2f));
        // 获取数据的点
        int[] abilityDataArray = data.getAbilityDataArray();
        for (int j = 0; j < n; j++) {
            float percent = abilityDataArray[j] / 100f;
            float x = pointsArrayList.get(0).get(j).x * percent;
            float y = pointsArrayList.get(0).get(j).y * percent;
            abilityPoints.add(new PointF(x,y));
        }
        // 画本次的一圈
        Path path = new Path();
        for (int j = 0; j < n; j++) {
            float x =  (count * abilityPoints.get(j).x)/times;
            float y =  (count * abilityPoints.get(j).y)/times;
            if (j==0) {
                path.moveTo(x,y);
            } else {
                path.lineTo(x,y);
            }
        }
        path.close();
        canvas.drawPath(path,linePaint);
        path.reset();
    }

将每次要化的线分为times(属性)次,间隔为intervalTime(属性) ms。通过多次绘制来达到动画效果。
提供给外部的接口:

 public void setData(AbilityBean data) {
        if (data == null) {
            return;
        }
        // count 未重置之前 跳过本次调用
        if( count == 0 && !data.equalsAbility(this.data)){
            this.data = data;
            if (scheduledExecutorService.isShutdown()) {
                scheduledExecutorService = Executors.newScheduledThreadPool(1);
            }
            count = 0;
            scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    if(count < times){
                        Message message = new Message();
                        myHandler.sendMessage(message);
                    } else {
                        scheduledExecutorService.shutdown();
                        count = 0;
                        abilityPoints.clear();
                    }
                }
            }, intervalTime, intervalTime, TimeUnit.MILLISECONDS);
        }

    }

每次外部调用这个方法 传入数据 就开始绘制了。
另外加入了一些限制,为了让每次动画能动完之后再接收下一次的数据开始下一次动画。当然在实际使用中只需要展示一次的话并不需要作这个限制了。

实际效果图:
Android 自定义控件教程:绘制一个会动的能力分布图

好了!大功告成!

整个操作流程就是调用abilityMapView.setData();传入数据,然后根据设置好的间隔和次数来draw;

附:
两个dp sp px转换的方法:

    private int dp2px(Context context, float f){
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (f * scale + 0.5f);
    }

    private int sp2px(Context context, float f){
        final float scale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (f * scale + 0.5f);
    }