本Demo主要目的为学习及研究自定义View,通过实现一个图表的数据展示功能,熟悉和了解View的绘制过程
产品需求 产品需求先看一下产品需求
- X轴和Y轴坐标分别表示时间及对应的数值
- Y轴坐标依数据显示5-10行,Y轴辅助线显示3-5条
- X轴依时间文字的长短进行展示,要求X轴坐标值不重合
- 各坐标点用直线相连,且连线与X轴区域添加渐变色
- 添加touch时间,当触摸至坐标点时显示文本框,显示说明文本,绘制坐标点圆圈及X、Y轴辅助线
功能分析
为完成产品的需求,我们需要解决如下的6个问题:
1.首先,我们需要计算出绘图区域及坐标轴文字显示区域;
2.绘制坐标轴文字;
3.绘制平行于X轴的辅助线;
4.计算各坐标点位置;
5.连接各坐标点并绘制渐变区域;
6.捕捉touch事件并添加回调;
7.依据回调设置提示框内容并绘制提示框。
代码实现
因为要展示数据,所以需要自定义View暴露对外的设置数据的接口,同时数据需要如下三个属性:颜色(绘制连接线时连接线的颜色)、Y轴坐标(选用string类型,因为横坐标可能是周一、二……)、X轴坐标值(这里选用double类型)。因此,在自定义View中可以使用内部类Units来作为坐标点,同时用Map<Color,Units>来保存需要展示的数据。
//坐标点位置
public static class Units {
public double y;
String x;
public Units(double y, String x) {
this.x = x;
this.y = y;
}
}
//对外暴露的接口,用以设置数据
public void resetData(Map<Integer, List<Units>> map) {
this.mDatas.clear();
Iterator<Integer> it = map.keySet().iterator();
while (it.hasNext()) {
Integer color = it.next();
mDatas.put(color, map.get(color));
}
invalidate();
}
我们知道,因为是自定义View,所以我们需要添加一些atrrs属性,便于对View进行一些设置;
本demo中添加的一些属性如下
属性名 | 类型 | 说明 |
---|---|---|
min_size | integer | view最小尺寸 |
base_stroke_width | integer | 基础线条宽度 |
base_stroke_color | color | 基础线条颜色 |
base_text_size | integer | 坐标文字大小 |
help_text_size | integer | 弹出提示框文字大小 |
help_text_margin | integer | 弹出提示框Margin |
text_margin_y | integer | Y方向文字与表格间距 |
point_size | integer | 触摸时显示坐标点的大小 |
point_touch_size | integer | 触摸范围 |
text_margin_x | integer | X方向文字与表格间距 |
zero_start | boolean | Y轴是否从零开始 |
help_text_bg_res | reference | 触摸响应说明背景 |
shader | boolean | 是否添加X坐标与连线间的渐变 |
有了如上属性,我们在自定义初始化的初始化这些属性,同时初始化Paint
private void init(AttributeSet attrs) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.RouteeFormView);
mMinSize = a.getInteger(R.styleable.RouteeFormView_min_size, 0);
mBaseColor = a.getColor(R.styleable.RouteeFormView_base_stroke_color, Color.parseColor("#d0d0d0"));
mBaseStrokeWidth = a.getInteger(R.styleable.RouteeFormView_base_stroke_width, 1);
mBaseTextSize = a.getInteger(R.styleable.RouteeFormView_base_text_size, 12);
mHelpTextSize = a.getInteger(R.styleable.RouteeFormView_help_text_size, 14);
mHelpTextMargin = a.getInteger(R.styleable.RouteeFormView_help_text_margin, 8);
mTextMarginX = DisplayUtils.dp2px(getContext(), a.getInteger(R.styleable.RouteeFormView_text_margin_x, 4));
mTextMarginY = DisplayUtils.dp2px(getContext(), a.getInteger(R.styleable.RouteeFormView_text_margin_y, 4));
mHelpTextBgResId = a.getResourceId(R.styleable.RouteeFormView_help_text_bg_res, R.drawable.bg_routee_form_view_help_text);
mNeedDrawShader = a.getBoolean(R.styleable.RouteeFormView_shader, false);
mPointWidth = DisplayUtils.dp2px(getContext(), a.getInteger(R.styleable.RouteeFormView_point_size, 2));
mPointTouchWith = DisplayUtils.dp2px(getContext(), a.getInteger(R.styleable.RouteeFormView_point_touch_size, 10));
isStartZero = a.getBoolean(R.styleable.RouteeFormView_zero_start, false);
a.recycle();
mPaint = new Paint();
mPaint.setAntiAlias(true);
}
然后,我们需要重写我们的onMeasure方法,计算自定义View的大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == AT_MOST && heightSpecMode == AT_MOST) {
setMeasuredDimension(mMinSize, mMinSize);
} else if (widthMeasureSpec == AT_MOST) {
setMeasuredDimension(mMinSize, heightSpecSize);
} else if (heightMeasureSpec == AT_MOST) {
setMeasuredDimension(widthSpecSize, mMinSize);
}
}
在计算出View的尺寸后,我们需要开始完成自定View最重要的一步绘制,也就是重写onDraw(Canvas canvas)方法,依据需求分析,我们需要进行一些列的计算再去按如下顺序去绘制View的不同部分:
效果图
网友评论