自定义一个简单的3D柱状图
程序员文章站
2022-05-29 16:25:39
...
说是3D,其实暂时还跟3D绘图相关的API扯不上边,用的都是2D的API实现的。见图:
详细代码
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效果。
下一篇: char判断字符串中是否含有某个字符