开头
界面的酷炫程度越来越高时,自定义View势在必行。
第一步,定义和使用自定义属性
在values/attrs.xml,添加需要的styleable和item。
这个并不是必须,有些自定义view没有自定义属性,也活着好好的。
<declare-styleable name="LineChart">
<attr name="pointHeightList" format="string"/>
<attr name="pointPadding" format="integer"/>
</declare-styleable>
LineChart是自定View名称,一般就写名称,容易辨识。
format,期望被赋值的类型。
主要有下面几种。
reference: @drawable/icon
color:#fff
boolean:false
dimension:15px
float:1.5
integer:2
string:121sdxjflasdf
fraction: 20%
enum:
flag:
PS:同时可以加多个format,format=”reference|color”
使用时,需引入命名空间。如下:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools=http://schemas.android.com/tools
xmlns:linechart="http://schemas.android.com/apk/res/com.example.demo"
android:layout_width="wrap_content"
android:layout_height="match_parent" >
<com.example.demo.LineChart
android:layout_width="match_parent"
android:layout_height="match_parent"
linechart:pointHeightList="10 50 25 60 100 0 77 9"
linechart:pointPadding="20"/>
</RelativeLayout>
第二步,获取自定义属性
在构造方法中,通过TypedArray获取定义在布局中的属性。
最后别忘了把TypedArray给recycle了。
重写onMeasure
大多数情况下,不重写onMeasure,View显示的也很溜。
不过做一个小实验就会发现问题,如上布局中。LineChart使用match_parent作为宽度和高度,显示完全没问题。可,当使用wrap_content后就会发现,他的状态还是match_parent的。
原因在于MeasureSpec的Mode。
UNSPECIFIED、EXACTLY、AT_MOST
这边做法是当Mode不是EXACTLY时,重写手动去计算View宽度和高度。
重写onDraw
主要通过Canvas和Path来实现。就是通过数学画画,配合一些花里胡哨的位移之类的,就能做比较酷炫的特效了。
实例:
package com.example.demo;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
public class LineChart extends View {
private int pointPadding;
private int[] pointHeightList;
private Paint mPaint;
private Path mPath;
public LineChart(Context context) {
this(context, null);
}
public LineChart(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LineChart(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.LineChart, defStyleAttr, 0);
int count = ta.getIndexCount();
for (int i=0; i<count; ++i) {
int attr = ta.getIndex(i);
switch (attr) {
case R.styleable.LineChart_pointHeightList:
String list = ta.getString(i);
String items[] = list.split(" ");
int length = items.length;
pointHeightList = new int[length];
for (int j=0; j<length; ++j) {
pointHeightList[j] = Integer.valueOf(items[j]);
Log.d(TAG, "pointHeightList["+j+"]="+pointHeightList[j]);
}
break;
case R.styleable.LineChart_pointPadding:
pointPadding = ta.getInteger(i, 20);
break;
}
}
ta.recycle();
Log.d(TAG, "pointPadding="+pointPadding);
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(4);
mPaint.setStyle(Paint.Style.STROKE);
mPath = new Path();
mPath.moveTo(0, pointHeightList[0]);
for (int i=1; i<pointHeightList.length; ++i) {
mPath.lineTo(pointPadding * (i+1), pointHeightList[i]);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
if (MeasureSpec.EXACTLY == widthMode) {
width = widthSize;
} else {
width = (pointHeightList.length - 1) * pointPadding
+ getPaddingLeft() + getPaddingRight();
}
if (MeasureSpec.EXACTLY == heightMode) {
height = heightSize;
} else {
height = 0;
for(int h:pointHeightList) {
if (h>height) {
height = h;
}
}
height = height + getPaddingTop() + getPaddingBottom();
}
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(mPath, mPaint);
}
}