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

自定义一个简单的3D柱状图

程序员文章站 2022-05-29 16:25:39
...

说是3D,其实暂时还跟3D绘图相关的API扯不上边,用的都是2D的API实现的。见图:
自定义一个简单的3D柱状图

详细代码

public class BarChat3DView extends View {
    private Context mContext;
    private PaintFlagsDrawFilter mDrawFilter;
    //画板宽度
    private int mCanvasWidth;
    //画板高度 todo 提供set方法
    private int mCanvasHeight;
    //柱子宽度 todo 提供set方法
    private int mBarWidth;
    //柱子最高高度
    private int mBarMaxHeight;
    //x轴高度
    private int mXAxisHeight;
    //x轴颜色
    private int mXAxisColor;

    private List<BarChat3DBean> mBarChat3DBeanList = new ArrayList<>();

    public BarChat3DView(Context context) {
        super(context);
        init(context, null);
    }

    public BarChat3DView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    private void init(Context context, @Nullable AttributeSet attrs) {
        mContext = context;
        mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
        mBarWidth = DisplayUtil.dip2px(context, 38);
        mXAxisHeight = DisplayUtil.dip2px(context, 1);
        mXAxisColor = Color.parseColor("#4723BE");
        if (attrs != null) {
            //todo xml中的个性化属性
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        //最低高度200
        if (height < 200) {
            height = 200;
        }
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mCanvasWidth = w;
        mCanvasHeight = h;
        //柱子最高高度为画板高度-50dp
        mBarMaxHeight = DisplayUtil.px2dip(mContext, mCanvasHeight - 50);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //        super.onDraw(canvas);
        canvas.setDrawFilter(mDrawFilter);
        //        canvas.drawColor(Color.parseColor("#ffffff"));
        //画x轴线
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(mXAxisColor);
        paint.setStrokeWidth(mXAxisHeight);

        //设计图x轴线距离底部24dp,24dp=柱子底部标题文字高度+柱子底部标题距x轴线的距离+x轴线高度
        Rect rect = new Rect(0, mCanvasHeight - DisplayUtil.dip2px(mContext, 24),
                mCanvasWidth, mCanvasHeight - DisplayUtil.dip2px(mContext, 23));
        canvas.drawRect(rect, paint);

        //循环画柱子、柱子底部标题、柱子顶部数值
        if (mBarChat3DBeanList.size() > 0) {
            //柱子最高高度为画板高度-50dp.取出真实数值中的最大值,根据两者计算y轴比例尺,从而计算每个柱子高度
            float yAxisPlottingScale = 1;
            float maxValue = 0;
            for (int i = 0; i < mBarChat3DBeanList.size(); i++) {
                BarChat3DBean barChat3DBean = mBarChat3DBeanList.get(i);
                float value = barChat3DBean.getValue();
                if (value > maxValue) {
                    maxValue = value;
                }
            }
            //为了避免只有一个柱状图数据,且数据小于mBarMaxHeight时,该柱子高度会画到最大高度的错误情况,
            //设maxValue=mBarMaxHeight,避免y轴比例尺大于1
            if ((maxValue < mBarMaxHeight) && mBarChat3DBeanList.size() == 1) {
                maxValue = mBarMaxHeight;
            }
            //y轴比例尺,因为是用最大值来计算比例尺,减50是给该最长的柱子留出空间,画柱子顶部的数值
            yAxisPlottingScale = (mBarMaxHeight - 50) / maxValue;

            for (int i = 0; i < mBarChat3DBeanList.size(); i++) {

                if (i == 0) {
                    canvas.translate(DisplayUtil.dip2px(mContext, 32),
                            -DisplayUtil.dip2px(mContext, 24));
                } else {
                    canvas.translate(DisplayUtil.dip2px(mContext, 27) + mBarWidth,
                            -DisplayUtil.dip2px(mContext, 24));
                }

                BarChat3DBean barChat3DBean = mBarChat3DBeanList.get(i);

                float value = barChat3DBean.getValue();
                //上移到x轴线上面
                //                value = value + 24;

                //有值才画柱子
                //                if (barChat3DBean.getValue() > 0) {

                //柱子start
                int barRealHeight = mCanvasHeight - DisplayUtil.dip2px(mContext,
                        value * yAxisPlottingScale);

                Rect barRect = new Rect(0,
                        barRealHeight,
                        mBarWidth, mCanvasHeight /*- DisplayUtil.dip2px(mContext, 24)*/);
                paint.setStyle(Paint.Style.FILL);
                LinearGradient barGradient = new LinearGradient(0,
                        (barRealHeight) / 2,
                        mBarWidth, (barRealHeight) / 2
                        , barChat3DBean.getShallowColor(), barChat3DBean.getDarkColor(),
                        Shader.TileMode.CLAMP);
                paint.setShader(barGradient);
                paint.setColor(barChat3DBean.getShallowColor());
                canvas.drawRect(barRect, paint);
                //柱子end

                //柱子顶部形成立体效果的椭圆start
                LinearGradient ovalGradient = new LinearGradient(0,
                        barRealHeight,
                        mBarWidth, mCanvasHeight /*- DisplayUtil.dip2px(mContext, 24)*/,
                        barChat3DBean.getDarkColor(), barChat3DBean.getShallowColor(),
                        Shader.TileMode.CLAMP);
                paint.setShader(ovalGradient);
                canvas.drawOval(0, mCanvasHeight - DisplayUtil.dip2px(mContext,
                        value * yAxisPlottingScale + 6), mBarWidth,
                        mCanvasHeight - DisplayUtil.dip2px(mContext,
                                value * yAxisPlottingScale - 6), paint);
                //柱子顶部形成立体效果的椭圆end

                //                }

                //柱子顶部数值start
                paint.setColor(Color.parseColor("#ffffff"));
                paint.setShader(null);
                paint.setTextSize(DisplayUtil.dip2px(mContext, 12));
                float beanValue = barChat3DBean.getValue();
                String valueOf = String.valueOf(Math.round(beanValue));
                int barValueWidth = getTextWidth(paint, valueOf);
                canvas.drawText(valueOf, (mBarWidth - barValueWidth) / 2,
                        mCanvasHeight - DisplayUtil.dip2px(mContext,
                                value * yAxisPlottingScale + 14), paint);
                //柱子顶部数值end

                canvas.translate(0, DisplayUtil.dip2px(mContext, 24));
                //柱子底部标题start
                int barTitleWidth = getTextWidth(paint, barChat3DBean.getBarTitle());
                canvas.drawText(barChat3DBean.getBarTitle(), (mBarWidth - barTitleWidth) / 2,
                        mCanvasHeight-DisplayUtil.dip2px(mContext,1), paint);
                //柱子底部标题end
            }
        }

    }

    /**
     * 获取字符串长度
     *
     * @param mPaint
     * @param str
     * @return
     */
    private int getTextWidth(Paint mPaint, String str) {
        float iSum = 0;
        if (str != null && !str.equals("")) {
            int len = str.length();
            float widths[] = new float[len];
            mPaint.getTextWidths(str, widths);
            for (int i = 0; i < len; i++) {
                iSum += Math.ceil(widths[i]);
            }
        }
        return (int) iSum;
    }


    public List<BarChat3DBean> getBarChat3DBeanList() {
        return mBarChat3DBeanList;
    }

    public void setBarChat3DBeanList(List<BarChat3DBean> barChat3DBeanList) {
        if (barChat3DBeanList != null && barChat3DBeanList.size() > 0) {
            mBarChat3DBeanList.clear();
            mBarChat3DBeanList.addAll(barChat3DBeanList);
            invalidate();
        }
    }
}

实体BarChat3DBean

public class BarChat3DBean {
    private String barTitle;
    private float value;//数值
    private int shallowColor;//浅色
    private int darkColor;//深色

    public String getBarTitle() {
        return barTitle;
    }

    public void setBarTitle(String barTitle) {
        this.barTitle = barTitle;
    }

    public float getValue() {
        return value;
    }

    public void setValue(float value) {
        this.value = value;
    }

    public int getShallowColor() {
        return shallowColor;
    }

    public void setShallowColor(int shallowColor) {
        this.shallowColor = shallowColor;
    }

    public int getDarkColor() {
        return darkColor;
    }

    public void setDarkColor(int darkColor) {
        this.darkColor = darkColor;
    }
}

Tip:

1、需要自定义View时,如果遇到感觉有些伤脑壳的图而又暂时找不到思路时,可以尝试下化整为零的战略,即把整图拆分成几个独立的部分,一部分一部分的画。这里的3D效果就是由矩形+椭圆组合实现的。

2、其实自定义View就像画家画画一个道理,要记得善于利用色差,这里的3D效果就是利用渐变色来实现的,矩形从左到右的颜色由潜到深,而椭圆从左到右的颜色由深到潜,两者组合起来就显示出了3D效果。

相关标签: 自定义控件