android自定义View之(二) Custom Drawing

The most important part of a custom view is its appearance. Custom drawing can be easy or complex according to your application’s needs. This lesson covers some of the most common operations.
自定义View中最终要的部分之一是他的外观。自定义drawing 根据你的应用程序的需求可以很容易也可以很复杂。这一节包含一些最普遍的操作。


Override onDraw() 重写

The most important step in drawing a custom view is to override the onDraw() method. The parameter to onDraw() is a Canvas object that the view can use to draw itself. The Canvas class defines methods for drawing text, lines, bitmaps, and many other graphics primitives. You can use these methods in onDraw() to create your custom user interface (UI).
draw 一个自定义view最重要的一步是重写onDraw()方法。 onDraw()参数是一个Canvas对象,可以让view自己画。Canvas 类定义了 draw text ,lines ,bitmaps 和其他一些基本图形的方法。你可以在onDraw()方法中使用这些方法来创建你的自定义UI.

Before you can call any drawing methods, though, it’s necessary to create a Paint object. The next section discusses Paint in more detail.
在你调用任何drawing 方法之前,though,有必要创建一个Paint对象。下面会详细讨论Paint.

Create Drawing Objects

The android.graphics framework divides drawing into two areas:
1. What to draw, handled by Canvas
2. How to draw, handled by Paint.
android.graphics 框架把 drawing 分成两部分:
1. draw什么, Canvas 处理
2. 怎么 draw, Paint 处理
For instance, Canvas provides a method to draw a line, while Paint provides methods to define that line’s color. Canvas has a method to draw a rectangle, while Paint defines whether to fill that rectangle with a color or leave it empty. Simply put, Canvas defines shapes that you can draw on the screen, while Paint defines the color, style, font, and so forth of each shape you draw.
举个例子,Canvas 提供画线的方法,Paint 提供定义线的颜色。 Canvas 有画矩形的方法,而Paint 有是否用颜色填充矩形或让它空白的方法。简言之,Canvas定义你在屏幕上能画的形状,Paint 定义了它的颜色,样式,字体,
So, before you draw anything, you need to create one or more Paint objects. The PieChart example does this in a method called init, which is called from the constructor:
所以在你画任何东西之前,你需创建一个或者多个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));

   ...

Creating objects ahead of time is an important optimization. Views are redrawn very frequently, and many drawing objects require expensive initialization. Creating drawing objects within your onDraw() method significantly reduces performance and can make your UI appear sluggish.
提现创建对象是一个非常重要的优化,Views会频繁的重新绘制,并且很多对象绘画需要昂贵的初始化。
在你的onDraw()方法里创建对象是非常影响性能的并且可能使你的UI表现萧条(卡顿)。

Handle Layout Events 处理layout事件

In order to properly draw your custom view, you need to know what size it is. Complex custom views often need to perform multiple layout calculations depending on the size and shape of their area on screen. You should never make assumptions about the size of your view on the screen. Even if only one app uses your view, that app needs to handle different screen sizes, multiple screen densities, and various aspect ratios in both portrait and landscape mode.
为了适当的画你的自定义view,你需要知道它的尺寸。复杂的自定义view经常需要执行多layout 计算,依赖于他们在屏幕上的地方跟大小。你不能够估计你的view在屏幕上的尺寸。即使是只有一个app使用你的view.app需要处理不同的屏幕大小,多种屏幕密度,还有竖屏模式跟横屏模式的多方向比率。

Although View has many methods for handling measurement, most of them do not need to be overridden. If your view doesn’t need special control over its size, you only need to override one method: onSizeChanged().
虽然View有许多处理测量的方法,大部分不需要重写。如果你的view不需要特别控制他的尺寸,你只需要重写一个方法:onSizeChanged().

onSizeChanged() is called when your view is first assigned a size, and again if the size of your view changes for any reason. Calculate positions, dimensions, and any other values related to your view’s size in onSizeChanged(), instead of recalculating them every time you draw. In the PieChart example, onSizeChanged() is where the PieChart view calculates the bounding rectangle of the pie chart and the relative position of the text label and other visual elements.
onSizeChanged()方法在你的view第一次分配尺寸的时候被调用,你的view在任何原因改变大小的情况下会重新调用。在onSizeChanded()方法中计算位置,距离,和其他跟你view大小相关的value,而不是在每次draw的时候计算他们。

When your view is assigned a size, the layout manager assumes that the size includes all of the view’s padding. You must handle the padding values when you calculate your view’s size. Here’s a snippet from PieChart.onSizeChanged() that shows how to do this:
当你的view被分配一个尺寸,layout manager 假设大小包含了所有的view的内边距(padding).当你计算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);

If you need finer control over your view’s layout parameters, implement onMeasure(). This method’s parameters are View.MeasureSpec values that tell you how big your view’s parent wants your view to be, and whether that size is a hard maximum or just a suggestion. As an optimization, these values are stored as packed integers, and you use the static methods of View.MeasureSpec to unpack the information stored in each integer.
如果你需要更好的控制你view的布局参数,实现 onMeasure().这个方法的蚕食是 View.MeasureSpec 值,告诉你你的view的父亲需要你的view有多大,是否是硬性最大值或者只是一个建议。作为优化,这些值做为
包装int 被存储起来,你可以使用View.MeasureSped的静态方法获取每个interger的信息 。

Here’s an example implementation of onMeasure(). In this implementation, PieChart attempts to make its area big enough to make the pie as big as its label:
这是一个 onMeasure()的实现例子,在这个实现中,PieChart 尝试着让它的地方变得足够大使pie跟label一样大:

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

There are three important things to note in this code:

  1. The calculations take into account the view’s padding. As mentioned earlier, this is the view’s responsibility.
  2. The helper method resolveSizeAndState() is used to create the final width and height values. This helper returns an appropriate View.MeasureSpec value by comparing the view’s desired size to the spec passed into onMeasure().
  3. onMeasure() has no return value. Instead, the method communicates its results by calling setMeasuredDimension(). Calling this method is mandatory. If you omit this call, the View class throws a runtime exception.

代码里有三点需要注意的:
1. 计算加入了 view的padding! 正如前面提到的这是view的责任。
2. 榜示方法 resolveSizeAndState() 用来创建最终的宽度跟高度值。这个方法通过比较view需要的尺寸返回一个合适的View.MeasureSpec发送到发送到onMeasure().
3. onMeasure()没有返回值,通过setMeasuredDimension()方法交流他的结果,setMeasuredDimension()方法是强制调用的,如果省略,View类就会抛runtime 异常。

Draw! 画吧~

Once you have your object creation and measuring code defined, you can implement onDraw(). Every view implements onDraw() differently, but there are some common operations that most views share:
一旦你创建测量完成代码,你可以实现onDraw()。每个view 实现onDraw()不一样,但是有一些大部分view通用的操作:

  1. Draw text using drawText(). Specify the typeface by calling setTypeface(), and the text color by calling setColor().
  2. Draw primitive shapes using drawRect(), drawOval(), and drawArc(). Change whether the shapes are filled, outlined, or both by calling setStyle().
  3. Draw more complex shapes using the Path class. Define a shape by adding lines and curves to a Path object, then draw the shape using drawPath(). Just as with primitive shapes, paths can be outlined, filled, or both, depending on the setStyle().
  4. Define gradient fills by creating LinearGradient objects. Call setShader() to use your LinearGradient on filled shapes.
  5. Draw bitmaps using drawBitmap().

  6. 画字用drawText(),用setTypeface()方法指定字体,setColor方法指定字体颜色。

  7. 画原始的形状用 drawRect(),drawOval(),drawArc()方法通过setStyle()来指定形状是否需要filled, outlined.
  8. 画一些负责的形状用Path类,通过对Path对象添加line和曲线定义shape,然后用drawPath()方法画形状,和原始形状一样,paths也可以通过setStyle()方法这是 是否 ontlined filled 或者全部。
  9. 通过创建LinearGradient对象来定义渐变。调用setShader()来使用LinearGradient。
  10. 通过drawBitmap() 方法画bitmap。
    For example, here’s the code that draws PieChart. It uses a mix of text, lines, and shapes.
    比如 ,这里的代码画PieChart。用到混合text line shape:
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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值