Canvas和Drawable

文章并未完全按照Android文档进行翻译增加了一些实例及效果,减少了一下赘述

Android提供了一套绘图API来定制你的界面,有两种方式来自定义2D界面:
1,在Layout里绘制view,只需要修改自己的图像或者动画。
2,在Canvas里直接绘图

Canvas就是一个画布,可以自定义画笔,在画布上进行绘图。

对于方法2:
a,如果是在UI线程,你需要创建一个自定义的View,然后只需要调用invalidate() 来重绘。并在onDraw()回调中自定义你的绘图。
b,在单独的进程中,你需要管理一个SurfaceView 来绘制Canvas(不需要调用invalidate())

使用Canvas绘图

通过Canvas来绘图,Canvas就如同一个操作真正的surface的接口。Canvas提供了各种draw的方法,帮助我们绘图,Canvas是绘制在它下面的Bitmap上的。

从哪可以拿到Canvas?

  • 可以通过onDraw()方法拿到,该方法参数就是一个Canvas,你可以在Canvas上进行绘制。
  • 或者在操作SurfaceView对象时通过SurfaceHolder.lockCanvas()来获得一个Canvas。

当然你也可以重新创建一个新的Canvas。方法如下:

Bitmap b = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);Canvas c = new Canvas(b);

Bitmap是必须的!!因为Canvas所有的draw绘制动作,需要绘制到具体的Bitmap图像上才能够反应出实际的效果。

这时我们就可以调用Canvas的各种绘制方法在Bitmap上绘制了。
(不建议新建Canvas,而是通过以上2中方法获取Canvas)

Canvas类有一系列绘图方法比如drawBitmap(…), drawRect(…), drawText(…)。其他的类也可能有draw()方法,比如以下Drawable对象有draw方法,不过要把Canvas作为参数传进draw方法。

在View上绘制

如果应用不需要高速处理,或者不要求较高帧率,比如象棋游戏。你可以考虑创建一个自定义的View。并在View的onDraw方法中在提供的Canvas上直接绘制。

Android框架只有在有必要的时候才会调用onDraw方法,如果你想让系统调用onDraw方法,可以通过invalidate()方法。

在onDraw方法中,Canvas调用各种drawXXX方法后,Android系统会使用这个Canvas在对应的Bitmap上绘制。

invalidate()方法是针对UI线程的。 在非UI线程中,调用postInvalidate(). 方法

示例代码:

    @Override    public void onDraw(Canvas canvas) {        super.onDraw(canvas);        for (int x = 0; x < mXTileCount; x += 1) {            for (int y = 0; y < mYTileCount; y += 1) {                if (mTileGrid[x][y] > 0) {                    canvas.drawBitmap(mTileArray[mTileGrid[x][y]], mXOffset + x * mTileSize,                            mYOffset + y * mTileSize, mPaint);                }            }        }    }

该示例代码来自/samples/Snake/.其自定义的TileView。

在SurfaceView上绘制

SurfaceView是View的子类,它在View层级中提供一个专用的绘制界面,好处就是可以在非UI线程中进行绘制。非UI线程可以持有SurfaceView对象,在它上面进行绘制,而不用听从Android固有的绘制调度。

首先需要创建一个SurfaceView的子类,并实现SurfaceHolder.Callback接口。该接口有如下方法:

返回类型 方法
void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
void surfaceCreated(SurfaceHolder holder)
void surfaceDestroyed(SurfaceHolder holder)

这些回调会在SurfaceView对应的Surface创建,改变,销毁的时候被调用。

在SurfaceView类中可以定义另一个线程类,在其中进行绘制。

应该通过 SurfaceHolder 来操作Surface,当SurfaceView初始化结束,通过

getHolder()

得到SurfaceHolder对象,然后通过调用SurfaceHolder的addCallback(this)添加这个SurfaceView为SurfaceHolder的回调(因为urfaceView实现了SurfaceHolder.Callback接口)。

为了让你的线程能在Surface上绘图,你需要把SurfaceHolder传递给线程。然后可以通过SurfaceHolder的lockCanvas()获取Canvas对象,然后进行绘制,绘制结束后调用unlockCanvasAndPost(Canvas canvas) 来把Canvas的绘制作用到Surface上。

示例代码:

class LunarView extends SurfaceView implements SurfaceHolder.Callback {    class LunarThread extends Thread {            @Override        public void run() {        /* 通过SurfaceHolder的lockCanvas()获取Canvas对象,然后进行绘制,绘制结束后调用unlockCanvasAndPost(Canvas canvas) 来把Canvas的绘制作用到Surface上 */            while (mRun) {                Canvas c = null;                try {                    c = mSurfaceHolder.lockCanvas(null);                    synchronized (mSurfaceHolder) {                        if (mMode == STATE_RUNNING) updatePhysics();                        doDraw(c);                    }                } finally {                    // do this in a finally so that if an exception is thrown                    // during the above, we don't leave the Surface in an                    // inconsistent state                    if (c != null) {                        mSurfaceHolder.unlockCanvasAndPost(c);                    }                }            }        }    }........    public LunarView(Context context, AttributeSet attrs) {        super(context, attrs);        // 把SurfaceHolder传递给线程        SurfaceHolder holder = getHolder();        holder.addCallback(this);        // create thread only; it's started in surfaceCreated()        thread = new LunarThread(holder, context, new Handler() {            @Override            public void handleMessage(Message m) {                mStatusText.setVisibility(m.getData().getInt("viz"));                mStatusText.setText(m.getData().getString("text"));            }        });        setFocusable(true); // make sure we get key events    }........

该示例代码来自 /samples/LunarLander/.其自定义的LunarView。

注意:每次你从SurfaceHolder得到Canvas,Canvas之前的状态都会被保持,为了更好的显示你的绘制,你应该重画整个Surface.比如,你可以通过drawColor() 来填充一个颜色或者drawBitmap()来设置一个背景,从而清除Canvas之前的状态。否则,你会看到之前Canvas的内容(叠加)。

Drawables

Drawable是一种我们常用到的资源,它可以是图片,也可以是xml定义的形状。Drawable是基类,其子类包括BitmapDrawable, ShapeDrawable, PictureDrawable, LayerDrawable等。你也可以继承这些类来定义自己的Drawable类,
有三种方法来定义并初始化一个Drawable对象,
- 使用在工程资源中的图片文件
- 使用在drawable目录下的xml,定义的Drawable对象。
- 使用Drawable类创建。

从资源图片中创建

图片格式限定为PNG,JPG,GIF,较多用于应用图标,Logo,游戏资源等。
图片文件放置于res/drawable/ 目录,然后可以在代码或者layout文件中使用。
注意:res/drawable/ 目录下的图片文件,可能在打包的时候被aapt工具自动优化,例如,一个用不到256色的真彩色的PNG图片可能被转换成8-bit的PNG图片,从而节省内存(但是图片质量差不多)。所以如果你想要在代码里得到图像的数据流然后得到一个Bitmap,你可以把图片放在res/raw目录下。res/raw目录是不会进行优化的。

示例代码:
下面的代码演示了如何使用图片资源创建一个ImageView,并把它加入Layout中。

  LinearLayout mLinearLayout;  protected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  // Create a LinearLayout in which to add the ImageView  mLinearLayout = new LinearLayout(this);  // Instantiate an ImageView and define its properties  ImageView i = new ImageView(this);  i.setImageResource(R.drawable.my_image);  i.setAdjustViewBounds(true); // set the ImageView bounds to match the Drawable's dimensions  i.setLayoutParams(new Gallery.LayoutParams(LayoutParams.WRAP_CONTENT,  LayoutParams.WRAP_CONTENT));  // Add the ImageView to the layout and set the layout as the content view  mLinearLayout.addView(i);  setContentView(mLinearLayout);  }

如果你想得到Drawable对象,(而不是一个Int的ID)可以如下操作:

Resources res = mContext.getResources();Drawable myImage = res.getDrawable(R.drawable.my_image);

xml文件:

<ImageView  android:layout_width="wrap_content" android:layout_height="wrap_content" android:tint="#55ff0000" android:src="@drawable/my_image"/>

注意:
你的工程中的每个资源只能保持一种状态,不论你用这个资源实例化了多少对象。比如你使用1个图片,实例化2个Drawable对象,然后改变了其中一个的属性如alpha。那么这个修改也会影响到另一个Drawable。所以如果你想处理这种使用同一资源的多个实例的时候,可以考虑使用View动画

使用XML资源文件自定义Drawable

当Drawable不是一个图片的时候,相对使用代码初始化Drawable资源,使用XML的好处显而易见,在此就不赘述了。
Drawable的XML文件放在 res/drawable/ 目录下,然后你同样可以通过Resources.getDrawable(resourceID)来得到Drawable对象。
所有实现了inflate()方法的Drawable子类都可以通过XML来定义并初始化,Drawable子类通过XML中定义的属性来创建Drawable对象。

Drawable子类:

AnimatedRotateDrawableBitmapDrawableClipDrawableColorDrawableDrawableContainerGradientDrawableInsetDrawableLayerDrawableNinePatchDrawablePictureDrawableRotateDrawableScaleDrawableScrollBarDrawableShapeDrawable 

TransitionDrawable示例:
TransitionDrawable是一种带过渡效果的drawable。

继承关系:
android.graphics.drawable.Drawable
android.graphics.drawable.LayerDrawable
android.graphics.drawable.TransitionDrawable
通过xml文件定义一个TransitionDrawable,其中包含2个图片

XML定义(expand_collapse.xml):

<transition xmlns:android="http://schemas.android.com/apk/res/android" >    <item android:drawable="@drawable/penguins"/>    <item android:drawable="@drawable/tulips"/></transition>

在java中使用:

      Resources res = mContext.getResources();      TransitionDrawable transition = (TransitionDrawable)res.getDrawable(R.drawable.expand_collapse);      ImageView image = (ImageView) findViewById(R.id.toggle_image);      image.setImageDrawable(transition);

然后可以调用

transition.startTransition(1000);

来进行1s中的过渡效果。
示例效果:

Shape Drawable

Drawable顾名思义是可以绘制的,图像当然是可以绘制的,但除了图像外,形状也可以绘制出来—-虽然效果比较粗糙。
ShapeDrawable是Drawable的扩展,但它毕竟也是Drawable,所以你可以用它来做View的背景( setBackgroundDrawable())。也可以用来自定义自己的View。因为ShapeDrawable有自己的draw()方法,自定义View的时候,在View.onDraw()方法里调用来绘制这个ShapeDrawable。
下面代码是一个自定义View的例子:

public class CustomDrawableView extends View {      private ShapeDrawable mDrawable;      public CustomDrawableView(Context context) {      super(context);      int x = 10;      int y = 10;      int width = 300;      int height = 50;      mDrawable = new ShapeDrawable(new OvalShape());      mDrawable.getPaint().setColor(0xff74AC23);//不设定颜色的话,默认黑色      mDrawable.setBounds(x, y, x + width, y + height);//不设定bound是绘制不出来的      }      protected void onDraw(Canvas canvas) {      mDrawable.draw(canvas);      }      }

代码绘制了一个椭圆形来作为自定义的View,在onDraw方法中,把这个Drawable绘制出来。。

一旦自定义View定义好了,就可以在代码中直接使用了,上面的自定义View的使用如下:

    CustomDrawableView mCustomDrawableView;      protected void onCreate(Bundle savedInstanceState) {      super.onCreate(savedInstanceState);      mCustomDrawableView = new CustomDrawableView(this);      setContentView(mCustomDrawableView);      }

同样也可以在XML中定义:

<com.example.shapedrawable.CustomDrawableView  android:layout_width="fill_parent" android:layout_height="wrap_content" />

对于自定义View,还可以自定义自己的属性,然后在XML中同样可以使用这些属性来定义自定义View。这里就不赘述了,回头单写一篇。

Nine-patch 图片

Nine-patch 图片是以9.png结尾的一种图片,一般放在 res/drawable/ 目录中,在代码中是NinePatchDrawable对象,在Android中你可以用Nine-patch 图片来作为View的背景,好处就是不会让图片因为拉伸而变成一坨马赛克。一个典型应用比如Android自带的Button按钮,不论按钮多大,看起来都是很顺滑。Nine-patch 图片在标准的PNG图片的基础上在外围多了一圈像素。

外圈的多出来的像素是用来定义当图片缩放时,哪些区域可以拉伸,哪些区域是不动的。

可拉伸区域:由图片左边和上边1像素宽区域的两条线构成的一个矩形。可绘制区域:由图片右边和下边1像素宽区域的两条线构成的一个矩形。(可选的)(如果一个View设定一个9.png图片为背景,然后在View中绘制文字,会以可绘制区域作为边界,如果不设定可绘制区域,则会从图片的左上角绘制)

下图是一个典型的9.PNG的按钮图片

此处应该有图片

其中:
1,上图中的灰色点构成的区域会在图像拉伸的时候填充复制的图像。
2,下图中粉色的区域会在内部的Text装不下的时候,图像就会被拉伸,。

9.PNG的编辑

9.PNG的使用

假设9.PNG保存为res/drawable/my_button_background.9.png 文件
使用该图片的Xml文件定义如下:

<Button id="@+id/tiny" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerInParent="true" android:text="Tiny" android:textSize="8sp" android:background="@drawable/my_button_background"/><Button id="@+id/big" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerInParent="true" android:text="Biiiiiiig text!" android:textSize="30sp" android:background="@drawable/my_button_background"/>

其中layout_width和layout_height都设为wrap_content,以便随着内容的增加,图片自动的

此处应该有图片

更多相关文章

  1. Android(安卓)中不同项目共用通用库Module方法
  2. Android中BroadcastReceiver的运用
  3. ImageSwitcher的应用
  4. 浅谈Android中的MVP模式
  5. Android中GridView的使用——使用自带的SimpleAdapter(简单适配
  6. Android操作JNI函数以及复杂对象传递
  7. Android中Handler Runnable与Thread的区别
  8. Android(安卓)绘制圆形图片
  9. Activity 生命周期(一)

随机推荐

  1. Android中的界面组成
  2. 《Android(安卓)Dev Guide》系列教程1:什
  3. Android(安卓)线性布局详解
  4. Android(安卓)要想美化就用Shape
  5. Android(安卓)NDK 入门
  6. android的Instrumentation详解
  7. Android(安卓)核心分析 之八------Androi
  8. Android中LCD背光驱动
  9. 《Android/OPhone开发完全讲义》连载(4):And
  10. android单元测试