当前位置: 移动技术网 > IT编程>移动开发>Android > Android 自定义控件教程:绘制一个会动的能力分布图

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

2017年12月23日  | 移动技术网IT编程  | 我要评论

儿童绘画作品,东海龙网同花顺,凤禽麒兽

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();
    }

比较简单,按着顺序一步一步画下来就可以了
效果图:
这里写图片描述
最后一步画出

    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);
        }

    }

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

实际效果图:
这里写图片描述

好了!大功告成!

整个操作流程就是调用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);
    }

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网