0%

Android开发:创建自定义视图–自定义绘画

翻自:http://developer.android.com/training/custom-views/custom-drawing.html 对一个自定义视图来说,最重要的是它的外观,根据你的应用需求,自定义绘画可以很简单也可以很复杂。本课覆盖了最普通的一些操作。 覆盖onDraw()方法 绘制一个自定义视图,最重要的一步是覆盖onDraw()方法。onDraw()方法的参数是一个View可以用于绘制自己的Canvas对象,Canvas类定义了绘制文本,线条,位图和其他很多图元。你可以在onDraw()中用这些方法创建自己的UI。 在你调用任何绘画方法前,创建一个Paint对象是很必要的。下一节详细讨论Paint. 创建绘画对象 android.graphics 框架把绘画分成两块。 画什么,由Canvas决定 怎么画,由Paint控制 举例来说,Canvas提供了一个画线的方法,Paint提供了定义线的颜色的方法。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));

   ...

先创建对象是一个很重要的优化。View重绘非常频繁。很多绘图对象需要昂贵的初始化。在onDraw()里创建绘图对象意味着性能降低,会让你的UI卡顿。 处理布局事件 为了合适地绘制你的自定义View,你需要知道它的尺寸。复杂的自定义View经常要执行多个布局计算,通过他们在屏幕上上位置,形状,大小。你不应该假定你的View在屏幕上的位置。即使只有一个app使用你的View,这个app也需要处理不同的屏幕尺寸,多样的屏幕密度,各种屏幕宽高比(包括横竖屏模式) View有很多方法用来测量,他们中的大多数不需要覆盖。如果你不需要在它的尺寸上做特别的控制,你只需要覆盖一个方法:onSizeChanged() onSizeChanged() 在你的View第一次被分配一个大小时调用,因为某个原因改变了View的大小时再次被调用。在onSizeChanged()中计算位置,大小,还有其他跟View尺寸相关的值,取代在每次onDraw中重新计算。 在PieChart案例中,onSizeChanged()是计算饼图矩形边界和text Lable以及其他可见对象相对位置的地方。 当你的View被分配一个尺寸时,布局管理器假定这个尺寸包括了所有内边距。当你计算你的View尺寸时,必须处理padding值。下面是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布局参数,实现onMeasure()方法。这个方法的参数是View.MeasureSpec,它告诉你,你的View的父View希望你的View有多大,这个尺寸是一个硬性的最大值还是仅仅是一个建议。作为优化,这些值被保存成封装过的整数,你可以使用View.MeasureSpec的静态方法读取保存在每个整数中的信息。这是一个实现onMeasure()方法的例子,在这个实现中,PieChart尝试让他的区域足够大,以使饼和它的标签一样大。

@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所需的尺寸规格,返回了合适的View.MeasureSpec值给onMeasure(); onMeasure()没有返回值,作为替代,这个方法通过调用setMeasuredDimension()通知他的结果。调用这个方法是强制的,如果你忽略这个调用,View类会抛出runtion异常。 绘画! 一旦你的类被创建,测量代码被定义,你就可以实现onDraw()方法,每个View实现onDraw()代码不同,但有一些大多数View可以共用的操作。 画线使用drawText(),通过调用setTypeface()指定字体,通过setColor()指定颜色 画原始形状,使用drawRect()矩形, drawOval()椭圆, drawArc()圆弧,不论是改变形状是否填充,轮廓,或者两者,都调用setStyle()。 画更复杂的图形使用Path类。通过添加直线和曲线到一个Path对象来定义一个形状,然后用drawPath()画出这个形状。就像原始形状一样,Path也可以有轮廓,填充,或者两都都有,取决定于 setStyle() 通过创建LinearGradient对象定义一个梯度填充(渐变填充),在需要填充的shape上调用 setShader()来填充。 绘制位置使用drawBitmap(). 下面是PieChart中的代码,混合了文本,直线和形状

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);
}