初学自定义View,没什么好的想法(其实是难的做不来,简单的不想做),加上以前想做个天气APP的项目,所以就做了个这个练练手。
初次学习使用,自定义View的很多东西还不是很熟悉,大家凑合着看吧。
先来看看效果:
控件内容比较简单,就是一个普通的折线图,上下分别带有数字。
因为这里不需要考虑 wrap_content 的情况,所以onMeasure
方法不需重写,关键的是onDraw
,而onDraw
方法其实也不困难,只需要确定好每个点的具体位置就好,因为连线也是需要点的坐标,代码逻辑有点混乱,可以略过,这里我没有用drawLine
直接绘制线条,而是记录折线要经过的路径然后再绘制,代码如下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mWeathers == null || mWeathers.size() == 0) {
return;
}
max = getMaxTmp();
min = getMinTmp();
//每个像素占的比率
float ratio = (float) mHeight / (max - min);
xInterval = (float) mWidth / (mWeathers.size() - 2);
float X, Y;
float Y1;
String tmp;
//记录高温折线路径
for (int i = 1; i < mWeathers.size() - 1; i++) {
tmp = mWeathers.get(i).getHighTmp() + "°";
X = (i - 1) * xInterval + xInterval / 2;
Y = (max - mWeathers.get(i).getHighTmp()) * ratio + mMargin + DensityUtils.sp2px(getContext(), mTmpTextSize);
if (i == 1) {
Y1 = ((max - mWeathers.get(0).getHighTmp()) * ratio + mMargin + Y) / 2;
mLinePath.moveTo(0, Y1);
}
mLinePath.lineTo(X, Y);
if (i == mWeathers.size() - 2) {
Y1 = ((max - mWeathers.get(i + 1).getHighTmp()) * ratio + mMargin + Y) / 2;
mLinePath.lineTo(getWidth(), Y1);
}
//绘制每一个温度点
canvas.drawCircle(X, Y, mCircleRadius, mCirclePaint);
//绘制分隔线
canvas.drawLine(X + xInterval / 2, 0,
X + xInterval / 2, getHeight(),
mAxesPaint);
//绘制温度
canvas.drawText(tmp, X - mTextPaint.measureText(tmp) / 2, Y - 2 * mCircleRadius, mTextPaint);
//绘制时间
canvas.drawText(mWeathers.get(i).getWeek(),
X - mTextPaint.measureText(mWeathers.get(i).getWeek()) / 2,
DensityUtils.sp2px(getContext(), mTmpTextSize),
mTextPaint);
//绘制天气所对应的图片
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.sunny);
switch (mWeathers.get(i).getCond()) {
case "晴":
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.sunny);
break;
case "阴":
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.overcast);
break;
case "雨":
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.rain);
break;
case "多云":
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.cloudy);
break;
}
canvas.drawBitmap(bitmap, X - bitmap.getWidth() / 2, 0, mCirclePaint);
if (!bitmap.isRecycled()) {
bitmap.recycle();
}
}
//记录低温折线路径
for (int i = mWeathers.size() - 2; i > 0; i--) {
tmp = mWeathers.get(i).getLowTmp() + "°";
X = (i - 1) * xInterval + xInterval / 2;
Y = (max - mWeathers.get(i).getLowTmp()) * ratio + mMargin + DensityUtils.sp2px(getContext(), mTmpTextSize);
if (i == mWeathers.size() - 2) {
Y1 = ((max - mWeathers.get(i + 1).getLowTmp()) * ratio + mMargin + Y) / 2;
mLinePath.lineTo(getWidth(), Y1);
}
mLinePath.lineTo(X, Y);
if (i == 1) {
Y1 = ((max - mWeathers.get(0).getLowTmp()) * ratio + mMargin + Y) / 2;
mLinePath.lineTo(0, Y1);
mLinePath.lineTo(0, 0);
mLinePath.close();
}
canvas.drawCircle(X, Y, mCircleRadius, mCirclePaint);
canvas.drawText(tmp,
X - mTextPaint.measureText(tmp) / 2,
Y + 4 * mCircleRadius,
mTextPaint);
}
canvas.drawPath(mLinePath, mPaintShader);
}
考虑到需要允许用户进行简单的设置,例如点的大小,文字大小等等,所以定义一些自定义属性(res/values/attr.xml):
< attr name="circle_radius" format="dimension"/>< !--坐标点半径-->
< attr name="circle_color" format="color"/>< !--坐标点颜色-->
< attr name="line_color" format="color"/>< !--折线颜色-->
< attr name="line_width" format="dimension"/>< !--折线宽度-->
< attr name="axes_color" format="color"/> < !--坐标轴颜色-->
< attr name="axes_width" format="dimension"/>< !--坐标轴宽度-->
< attr name="shadow_color" format="color"/> < !--坐标轴颜色-->
< attr name="tmp_text_size" format="dimension"/>
format指该属性的格式,指定为dimension则是尺寸,取值单位是dp、sp或px等等,color是颜色,还有其他类型,可以自行查找文档。
对自定义属性进行解析得到,这个解析需要在构造方法中进行,代码如下:
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.WeatherTrendGraph);
mLineColor = typedArray.getColor(R.styleable.WeatherTrendGraph_line_color, Color.BLACK);
mLineWidth = typedArray.getDimensionPixelSize(R.styleable.WeatherTrendGraph_line_width, 2);
mAxesColor = typedArray.getColor(R.styleable.WeatherTrendGraph_axes_color, Color.BLACK);
mAxesWidth = typedArray.getDimensionPixelSize(R.styleable.WeatherTrendGraph_axes_width, 1);
mCircleRadius = typedArray.getDimensionPixelSize(R.styleable.WeatherTrendGraph_circle_radius, 12);
mCircleColor = typedArray.getColor(R.styleable.WeatherTrendGraph_circle_color, Color.GRAY);
mShaderColor = typedArray.getColor(R.styleable.WeatherTrendGraph_shadow_color, Color.GRAY);
mTmpTextSize = typedArray.getDimension(R.styleable.WeatherTrendGraph_tmp_text_size, 35);
typedArray.recycle();
getDimensionPixelSize方法则是通过传入的值,转换为具体的像素(px)值,也就免去我们手动转换的麻烦。但是要注意,参数中的defaultValue依然是px。
天气实体类:
public class Weather {
private int highTmp; //高温
private int lowTmp; //低温
private String date; //日期
private String cond; //天气状况
public Weather(int highTmp, int lowTmp) {
this.highTmp = highTmp;
this.lowTmp = lowTmp;
}
public Weather(int highTmp, int lowTmp, String date) {
this.highTmp = highTmp;
this.lowTmp = lowTmp;
this.date = date;
}
public Weather(int highTmp, int lowTmp, String date, String cond) {
this.highTmp = highTmp;
this.lowTmp = lowTmp;
this.date = date;
this.cond = cond;
}
public int getHighTmp() {
return highTmp;
}
public void setHighTmp(int highTmp) {
this.highTmp = highTmp;
}
public int getLowTmp() {
return lowTmp;
}
public void setLowTmp(int lowTmp) {
this.lowTmp = lowTmp;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getCond() {
return cond;
}
public void setCond(String cond) {
this.cond = cond;
}
public String getWeek() {
String[] weeks = {"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
try {
date = sdf.parse(this.date);
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
int week_index = calendar.get(Calendar.DAY_OF_WEEK) - 1;
if (week_index < 0) {
week_index = 0;
}
return weeks[week_index];
} catch (ParseException e) {
e.printStackTrace();
}
return this.date;
}
}
在MainActivity中使用:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWeatherTrendGraph = (WeatherTrendGraph) findViewById(R.id.weatherTrendGraph);
weather = new Weather(28, 16, "2017-04-18", "晴");
mWeathers.add(weather);
weather = new Weather(30, 17, "2017-04-19", "晴");
mWeathers.add(weather);
weather = new Weather(23, 13, "2017-04-20", "阴");
mWeathers.add(weather);
weather = new Weather(20, 13, "2017-04-21", "多云");
mWeathers.add(weather);
weather = new Weather(25, 17, "2017-04-22", "雨");
mWeathers.add(weather);
weather = new Weather(27, 17, "2017-04-23", "晴");
mWeathers.add(weather);
weather = new Weather(22, 16, "2017-04-24", "阴");
mWeathers.add(weather);
weather = new Weather(20, 14, "2017-04-25", "晴");
mWeathers.add(weather);
mWeatherTrendGraph.setWeathers(mWeathers);
}
整个项目源码地址:Github地址