自定义绘图
自定义View最重要的一部分就是它的外观了。自定义绘图可以使简单的,也可以是复杂的,这取决于你的应用需求。这篇教程将包含一些最常用的操作。
覆写 onDraw()
绘制自定义View最重要的一步就是覆写onDraw()
方法。Ondraw()
方法的参数是一个可以用于绘制自身的canvas
对象。Canvas
定义了很多绘制方法,比如:Text, lines, bitmaps,还有一些其他的显示基类。你能够在onDraw()
中使用这些方法来创建你的自定义UI。
在你调用任何一个绘制方法之前,你必须创建一个Paint
对象。下一个章节将会更加详细地讨论Paint
。
创建绘制对象
android.graphics
框架被分为了两部分:
- 绘制什么,由
Canvas
来操作 - 怎样绘制,由
Paint
来操作
具体来说,Canvas
提供绘制line的方法,而Paint
来提供line的颜色定义。Canvas
有绘制矩形的方法,而Paint
定义了是否使用颜色来填充矩形或者就让它置空。简单的说,Canvas
定义你在屏幕上所绘制的形状,而Paint
定义颜色,样式,字体。
因此,在你绘制任何东西之前,你需要创建一个或者多个Paint
对象,作为例子,PieChart展示了一个叫做init
的方法,它是在构造器中调用的:
private void init() {
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setColor(mTextColor);
if (mTextHeight == 0) {
mTextHeight = mTextPaint.getTextSize();
} else {
mTextPaint.setTextSize(mTextHeight);
}
mPiePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPiePaint.setStyle(Paint.Style.FILL);
mPiePaint.setTextSize(mTextHeight);
mShadowPaint = new Paint(0);
mShadowPaint.setColor(0xff101010);
mShadowPaint.setMaskFilter(new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL));
提前创建对象是一种很重要的优化方式。Views会经常重绘,并且很多绘制对象需要很繁重的初始化。在onDraw()
中创建绘制对象会显著地降低性能,让你的UI变得很缓慢。
处理Layout事件
为了适当地绘制你的自定义view,你需要知道它的size有多大。复杂的自定义view经常会需要执行多次layout计算,者取决于它们屏幕区域的大小,形状。你永远也不要去假设你的view在屏幕上的大小,即使只有一个APP使用你的view。这个APP需要处理不同的屏幕大小,不同的屏幕密度,以及各种多样的比例和landscape模式。
即使View有很多方法来处理测量过程,它们其中的绝大部分不需要覆写。加入你的view不需要特别去控制它的大小,你只需要覆写一个方法onSizeChanged()
。
onSizeChanged()
在你的view第一次被分配大小的时候调用,并且在你的view大小因为任何原因发生了变化的时候再一次调用。你可以在onSizeChanged()
中计算你的view的位置,尺寸以及其他任何和你的view大小有关系的值。,不同于在你每次绘制的时候重新计算,在PieChart这个例子中,onSizeChanged()
才是计算这些的地方。
当你的view被分配了大小的时候,Layout管理器会假定大小包含了所有的view的padding值。你必须处理你的padding值,在你计算你的view的大小的时候。以下的代码块是来自PieChart.onSizeChanged()
,它是这么处理的:
// Account for padding
float xpad = (float)(getPaddingLeft() + getPaddingRight());
float ypad = (float)(getPaddingTop() + getPaddingBottom());
// Account for the label
if (mShowText) xpad += mTextWidth;
float ww = (float)w - xpad;
float hh = (float)h - ypad;
// Figure out how big we can make the pie.
float diameter = Math.min(ww, hh);
假如你需要更好地去控制你的view的Layout参数,那么实现onMeasure()
方法。这个方法的参数是View.MeasureSpec
,这是用来告诉你,你的view的父类希望你的view是多大,和这个大小是真正的最大值还是建议值。作为一种优化,这些值被打包储存在了integer之中,你需要使用静态方法View.MeasureSpec
去解包出存储在每一个Integer中的信息。
以下是一个实现了onMeasure()
的例子,在这个实现中,PieChart 企图让它的大小足够大以此让pie达到和它的标签中所描述的设定的大小:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Try for a width based on our minimum
int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
int w = resolveSizeAndState(minw, widthMeasureSpec, 1);
// Whatever the width ends up being, ask for a height that would let the pie
// get as big as it can
int minh = MeasureSpec.getSize(w) - (int)mTextWidth + getPaddingBottom() + getPaddingTop();
int h = resolveSizeAndState(MeasureSpec.getSize(w) - (int)mTextWidth, heightMeasureSpec, 0);
setMeasuredDimension(w, h);
}
在以上的代码中有几个值得注意的东西:
- 计算中包含了view的padding值,就和前面所提到的那样,这是一个view的责任。
- 辅助类
resolveSizeAndState()
用来创建最终的宽,高值。这个辅助类通过比较view在onMeasure()
中想要的大小返回了一个适当的View.MeasureSpec
值。 onMeasure()
并没有返回值,相对地,这个方法调用了setMeasuredDimension()
以用来传递它的结果值。这个方法的调用时强制性的,如果你忽略了这个调用,那么,view类将会抛出运行时异常。
Draw!
一旦你定义好了你的对象的创建以及测量代码,你就能实现 onDraw()
.每个view的onDraw()
实现都不相同,但是在它们之间还是有一些相同的操作的,绝大部分view都会这么做:
- 使用
drawText()
来绘制text.用setTypeface()
来指定字体,用setColor()
来指定text的颜色。 - 原始的形状绘制使用
drawRect()
,drawOval()
,drawArc()
。用setStyle()
来改变形状是否填充,轮廓。 - 用
LinearGradient
对象来定义梯度值,调用setShader()
来使用你定义的梯度值到你定义的形状中去。 - 用
drawBitmap()
来绘制Bitmap。
举个例子吧,以下的代码是用来进行绘制的:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Draw the shadow
canvas.drawOval(
mShadowBounds,
mShadowPaint
);
// Draw the label text
canvas.drawText(mData.get(mCurrentItem).mLabel, mTextX, mTextY, mTextPaint);
// Draw the pie slices
for (int i = 0; i < mData.size(); ++i) {
Item it = mData.get(i);
mPiePaint.setShader(it.mShader);
canvas.drawArc(mBounds,
360 - it.mEndAngle,
it.mEndAngle - it.mStartAngle,
true, mPiePaint);
}
// Draw the pointer
canvas.drawLine(mTextX, mPointerY, mPointerX, mPointerY, mTextPaint);
canvas.drawCircle(mPointerX, mPointerY, mPointerSize, mTextPaint);
}