自定义绘制
自定义视图最重要的一件事是怎么让它在视图界面上显现,自定义绘制的复杂程度取决于你的应用的要求,下面我们来看看具体的步骤
覆写onDraw()方法
覆写onDraw()方法是绘制自定义视图的最重要的一步,onDraw()方法中的参数是一个
Canvas 对象,视图可以使用
Canvas 对象来绘制自己,
Canvas 类定义了一些方法用来绘制文本、线条、图片和许多其它的图元,你可以在onDraw()方法中使用这些方法来创建你自定义视图的界面,在你能够调用任何绘制方法前,你需要先创建一个
Paint
对象
创建绘制对象
比如,
Canvas 提供一个方法绘制一条线,而
Paint 负责定义这条线的颜色,
Canvas 提供一个方法绘制个矩形,而
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)); ...
提早创建对象是一个很重要的优化方法, 视图要非常频繁的重绘自己,许多绘制对象的初始化是很昂贵的,如果在你的onDraw()方法里去创建那些绘制对象,意味着程序性能的降低和缓慢的运行速度,所以你可以在View创建的时候去初始化那些绘制对象,而不要在onDraw()方法中
处理布局事件
为了正确的绘制我们自定义的视图,我们需要知道视图准确的大小,复杂的自定义视图经常需要在知道视图大小和形状的基础上进行一些复杂的计算,你永远不要去估量你的视图在屏幕上显示的大小,即使你自定义的视图只被一个应用使用,因为那个应用也需要处理不同屏幕大小的设备,多种屏幕密度和在横屏、竖屏模式下的各种横竖屏比率
尽管视图有很多方法来处理测量,当大多数你都不用进行覆写重新实现,如果你的视图不需要特殊的去控制大小,那么你只需要覆写
onSizeChanged()
一个方法
onSizeChanged()
方法在视图第一次分配大小的时候会调用,然后视图大小每次改变都会进行回调, 我们可以在
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);
如果你想很好的控制你的视图的布局参数,需要实现覆写
onMeasure()
,从中我们可以得知自定义视图的父类控件期望我们的视图要显示成什么样和我们的视图大小的最大值或一个建议值,因为优化,传递给我们的信息被包装成了一个整数值,需要使用
View.MeasureSpec 来进行解包获取其中实际值
@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、大小的计算考虑到了视图的填充区大小,正如前面说的,这个是视图的义务
3、
onMeasure()
方法没有返回值,为了向上反映自己计算出的大小值,就需要调用
setMeasuredDimension() 方法,这个操作是强制性的,如果不这样做,视图类会抛出一个运行时异常
绘制
覆写
onDraw()
方法,并且在里面编写绘制代码
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);}