自定义绘制
对于一个自定义视图最重要的一部分就是它的显示。
重写onDraw()
对于绘制一个自定义的view最重要的一步就是重写onDraw()函数。onDraw()函数的参数
是一个Canvas对象,view可以使用它来绘制自己。Canvas类定义了一个方法来绘制文本,线,
图片和其他几何图像。你可以在onDra()方法中使用这些方法来创建自定义的用户界面。
在你调用任何绘制方法之前,需要创建一个Paint对象。
创建绘制对象
android.graphics框架将绘制分为两个方面:
1.绘制什么,由Canvas负责
2.如何绘制,由Paint负责
例如,Canvas提供了一个方法来绘制一条线,然而Paint提供方法来定义线的颜色。Canva有一
个方法来绘制一个矩形,而Paint定义将矩形填满颜色还是留空白。简单来说,Canvas定义了你
要绘制的形状,而Paint定义了你要绘制的图像的颜色,样式,字体等属性。
因此,在你绘制之前,你需要创建一个或多个Paint对象。
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)); ...
在使用之前创建对象是一个重要的优化方式。对View的重新绘制是非常频繁的,很多的绘制对
象的初始化需要花费很高代价。在onDraw函数中创建绘制对象将会严重影响成像的性能。
处理Layout事件
为了恰当的绘制自定义的视图,你需要知道它的大小。复杂的自定义视图基于它的大小和形状,
通常需要执行多次布局计算。你不应该假设屏幕上视图的大小。即使只有一个应用程序使用你
的视图,这个应用程序需要处理不同的屏幕大小,多种屏幕分辨率,以及不同的屏幕方向模式。
尽管View有很多处理测量的方法,但他们中的大多数都不需要被重写。如果你的view不需要对
它的大小做特别的控制,你只需要重写一个方法:onSizeChanged()。
当你的view被首次分配一个大小,或者因为任何原因你的view的大小发生了改变,都会调用
onSizeChanged()函数。在onSizeChanged()方法中计算位置,维度和其他与你view大小
相关的值,而不是在每次绘制的时候进行计算。
当你的view被分配一个大小,布局管理器假设这个值包含了view的padding。你必须在计算view
大小的时候处理padding的值。
// 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的布局参数,实现onMeasure()方法。这个方法的参数是View.MeasureSpec,
它会告诉你,你的view的父容器期望你的view大小是多少,而且这个值是一个硬性值,或者
仅仅是一个建议值。作为一种优化,这些值被存储在一个包装的Integer中,你可以使用View.MesureSpec
来解压缩存储在每个integer中的信息。
@Overrideprotected 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); }
在上面的代码中你需要注意下面几点:
1.计算考虑了view的padding
2.辅助方法resolveSizeAndState()被用来创建最终的宽度和高度。这个辅助方法通过比较
view的所需要的大小和传递到onMeasure()中的spec,返回一个合适的View.MeasureSpec
值。
3.onMeasure方法没有返回值。该方法通过调用setMeasuredDimension()方法来与它的结果
进行通信。调用此方法是强制性的,如果你省略了此方法的调用,View类在运行时将会跑出
异常。
绘制
一旦你有了定义创建和测量的代码,你可以实现onDraw()方法了。每一个view都会实现不
同的onDraw方法,但他们之间都会存在一些相同的操作。
1.绘制文本使用drawText()。通过调用setTypeface()指定字体,调用setColor()指定
文本的颜色。
2.使用drawRect()、drawOval()和drawArc()方法绘制原始图形。
3.使用Path类绘制负责的图形。通过向Path对象添加线和曲线定义形状,然后使用drawPath()
绘制图形。
4.通过创建LinearGradient对象来定义gradient。
5.使用drawBitmap()绘制图像。
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); }