Android中的繪圖

      網友投稿 682 2025-03-31

      視頻課:https://edu.csdn.net/course/play/7621

      學習內容

      ??Android中基本圖形的繪制

      ??Android文本的繪制

      ??雙緩沖技術

      ??圖像的繪制及效果處理

      能力目標

      ??能使用View類搭建繪圖框架

      ??能在Android中繪制基本圖形

      ??掌握雙緩沖技術在Android中的實現

      ??能在Canvas上繪制圖片并實現各種效果

      本章簡介

      界面是軟件與用戶交互的最直接的層,界面的好壞決定用戶對軟件的第一印象。Android系統能夠在諸多的移動平臺中脫穎而出,漂亮的界面帶來的良好的用戶體驗無疑是其中一個很重要的因素。在我們平時的軟件開發中,僅靠系統提供的那些組件來實現界面是遠遠不夠的,在很多情況下我們都需要自己來繪制軟件界面。在本章中我們就將學習Android中和繪制圖形及位圖顯示和效果有關的知識。

      核心技能部分

      1.1?簡單繪圖

      1.1.1?繪制基本圖形

      玩過憤怒的小鳥的同學一定會為它里面漂亮的界面所吸引,如下圖1.1.1所示。這些漂亮的界面是如何顯示出來呢,這些界面可以通過繪圖的形式實現。

      本節中所謂的繪圖指的就是在屏幕上繪制一系列基本的圖形,比如直線、圓、弧等。這些基本的圖形雖然簡單,但通過組合以及色彩渲染,它們就可以構成我們所看到的漂亮的程序界面。Android SDK提供了對基本圖形以及位圖的繪制,所有的繪圖操作通常都是在View類的onDraw()方法中進行的。

      在Android中繪圖只需要繼承View類,并重寫它的onDraw()方法就可以了。在具體的繪圖過程中可能會涉及Paint類、Color類、Canvas類等。其中,Paint類表示畫筆,通過它可以設置畫筆的精細、樣式等,只有先得到畫筆才能進行圖形的繪制;Color類主要定義了一些顏色常量,利用它可以畫出各種彩色的圖形; Canvas類相當于畫布,除了可以在它上繪制之外,還可以設置它的屬性,比如,畫布的顏色、尺寸等。

      一般情況下,應用程序的組件都是在相同的GUI線程中繪制的,這個主應用程序線程同時也用來處理所有的用戶交互(例如,按鈕單擊或者文本輸入)操作。在Android中,任何一個View的子類只需要重寫onDraw()方法,就可以實現界面的定制顯示。對于一個應用來說除了圖形的顯示之外還需要有交互功能,比如圖形的移動、變形等,但由于Android UI不是線程安全的,而界面刷新操作又必須得在UI線程中執行。為了解決這個問題,我們一般是利用Handler來實現UI線程的更新(通過調用View對象的invalidate()方法)。

      Android中的View類提供了onKeyDown、onKeyUP、onTouchEvent、onTrackballEvent等方法來處理用戶界面和用戶交互所發生的事件。故我們的View類只要重寫了這些的方法,當有按鍵按下或彈起等事件發生時,與之對應的事件處理方法就會被調用。

      下面我們通過一個示例程序給大家演示Android中基本圖形的繪制。在繪制基本圖形之前,我們先搭建一個在Android中編寫繪圖程序的框架,以后我們的程序都在這個框架的基礎之上進行編寫。

      示例1.1

      使用View類搭建繪圖框架。

      (1)定制一個用來繪制界面的View類

      public class?GameView extends?View {

      public?GameView(Context context) {

      super(context);

      }

      protected?void?onDraw(Canvas canvas) {

      // 用戶自己的繪圖代碼

      }

      }

      (2)編寫一個用來控制整個應用(界面元素動作)的類:

      public?class?ViewFrameActivity extends?Activity {

      private?static?final?int?REFRESH?= 0x000001;

      private?GameView mGameView?= null;

      @Override

      public?void?onCreate(Bundle savedInstanceState) {

      super.onCreate(savedInstanceState);

      mGameView?= new?GameView(this);

      setContentView(mGameView);??// 設置顯示為我們自定義的視圖GameView

      new?Thread(new?GameThread()).start();// 開啟線程

      }

      class?GameThread implements?Runnable {

      public?void?run() {

      while?(!Thread.currentThread().isInterrupted()) {

      Message message = Message.obtain();?//返回一個Message實例

      message.what?= ViewFrameActivity.REFRESH;

      // 發送消息

      myHandler.sendMessage(message);

      try?{

      Thread.sleep(100);

      } catch?(InterruptedException e) {

      Thread.currentThread().interrupt();

      }

      }

      }

      }

      Handler myHandler?= new?Handler() {

      // 接收到消息后處理

      public?void?handleMessage(Message msg) {

      if(msg.what?== ViewFrameActivity.REFRESH){

      mGameView.invalidate();?//更新整個屏幕區域

      }

      super.handleMessage(msg);

      }

      };

      // 這些事件也可以寫在GameView類中,當不需要事件處理時,這些方法可以不寫。

      public?boolean?onTouchEvent(MotionEvent event) {// 觸筆事件

      return?true;

      }

      public?boolean?onKeyDown(int?keyCode, KeyEvent event) {// 按鍵按下事件

      return?true;

      }

      public?boolean?onKeyUp(int?keyCode, KeyEvent event) {// 按鍵彈起事件

      return?false;

      }

      public?boolean?onKeyMultiple(int?keyCode, int?repeatCount, KeyEvent event) {

      return?true;

      }

      }

      本示例采用在onDraw方法之后調用invalidate()方法實現屏幕的刷新。這種方法會通知UI線程重繪,使View重新調用onDraw()方法,實現刷新屏幕。這樣寫看起來代碼非常簡潔漂亮,但是同時也存在一個很大的問題。invalidate()線程和程序主線程是分開的,它違背了單線程模式,這樣繪制的話是很不安全的。舉個例子,比如程序先進入Activity1中,使用invalidate()方法來重繪;然后我跳到了Activity2,這時候Activity1已經finash()掉,可是Activity1中的invalidate()線程還在程序中,Android的虛擬機不可能主動殺死正在運行中的線程,所以這樣操作是非常危險的。總結起來說即,重繪操作在UI線程中是被動調用的,所以不安全。

      解決方案,在調用postInvalidate()方法后通知UI線程重繪屏幕。以new ?Thread(this).start()開啟一個繪圖主線程,,然后在主線程中通過調用postInvalidate()方法來刷新屏幕。postInvalidate()方法調用后,系統會幫我們調用onDraw方法,它是在我們自己的線程中調用,通過調用它可以通知UI線程刷新屏幕。由此可見它是主動調用UI線程的。所以建議按這種方式使用postInvalidate()方法通知UI線程來刷新整個屏幕。

      修改后的代碼如下:

      public?class?ViewFrameActivity extends?Activity {

      private?GameView2 mGameView?= null;

      @Override

      public?void?onCreate(Bundle savedInstanceState) {

      super.onCreate(savedInstanceState);

      mGameView?= new?GameView2(this);

      setContentView(mGameView);// 設置顯示為我們自定義的視圖GameView

      new?Thread(new?GameThread()).start();//開啟一個游戲的主線程

      }

      class?GameThread implements?Runnable {

      public?void?run() {

      while?(!Thread.currentThread().isInterrupted()) {

      try?{

      Thread.sleep(100);

      } catch?(InterruptedException e) {

      Thread.currentThread().interrupt();

      }

      mGameView.postInvalidate();//使用這個方法可以直接在線程中更新界面。

      Android中的繪圖

      }

      }

      }

      // 這些事件也可以寫在GameView中

      public?boolean?onTouchEvent(MotionEvent event) {// 觸筆事件

      return?true;

      }

      public?boolean?onKeyDown(int?keyCode, KeyEvent event) {// 按鍵按下事件

      return?true;

      }

      public?boolean?onKeyUp(int?keyCode, KeyEvent event) {// 按鍵彈起事件

      return?true;

      }

      public?boolean?onKeyMultiple(int?keyCode, int?repeatCount, KeyEvent event) {

      return?true;

      }

      }

      示例1.2

      繪制基本圖形,包括像素點、直線、圓、弧、多邊形、矩形等,最終顯示效果如下圖1.1.2所示。

      有了示例1.1繪圖框架的搭建,本示例的實現就變得非常簡單了,我們只需要修改GameView類的代碼在其中加入相應的代碼即可,修改后的代碼如下:

      public?class?GameView extends?View {

      private?float[] pts;

      public?GameView(Context context) {

      super(context);

      pts?= new?float[20];

      for?(int?i = 0; i < 20; i++)

      pts[i] = (float) (i + 10 + Math.random() * 40);// 產生10到40之間的隨機數

      }

      protected?void?onDraw(Canvas canvas) {

      canvas.drawColor(Color.GRAY);

      Paint mPaint = new?Paint();

      mPaint.setAntiAlias(true);// 設置取消鋸齒

      mPaint.setStyle(Paint.Style.STROKE);

      // 把mPaint.setStyle();的參數改為Paint.Style.FILL,會畫出實心圖形

      mPaint.setStrokeWidth(2);

      mPaint.setColor(Color.BLUE);

      // 繪制像素

      canvas.drawPoint(5, 7, mPaint);

      canvas.drawPoints(pts, mPaint);// 繪制坐標在(10,40)之間的像素20個。

      canvas.drawPoints(pts, 2, 14, mPaint);

      mPaint.setColor(Color.RED);

      // 畫直線

      canvas.drawLine(27, 43, 99, 43, mPaint);

      canvas.drawLines(pts, mPaint);

      canvas.drawLines(pts, 3, 8, mPaint);

      // 畫圓

      canvas.drawCircle(50, 80, 20, mPaint);

      // 畫弧

      canvas.drawArc(new?RectF(20, 100, 70, 150), 34.0f, 99.0f, false, mPaint);

      canvas.drawArc(new?RectF(20, 100, 70, 150), 34.0f, 99.0f, true, mPaint);

      canvas.drawArc(new?RectF(20, 100, 70, 150), 34.0f, 400.0f, false,

      mPaint);

      // 畫多邊形

      Path path1 = new?Path();

      path1.moveTo(150 + 5, 130 + 80 - 50); // 設置多邊形的點

      path1.lineTo(150 + 45, 130 + 80 - 50);

      path1.lineTo(150 + 30, 130 + 120 - 50);

      path1.lineTo(150 + 20, 130 + 120 - 50);

      path1.close();// 使這些點構成封閉的多邊形

      canvas.drawPath(path1, mPaint); // 繪制這個多邊形

      // 繪制矩形

      mPaint.setColor(Color.YELLOW);

      canvas.drawRect(130, 30, 170, 70, mPaint);

      }

      }

      本示例中用到了產生隨機數的知識,其中產生M到N之間的隨機數(M

      M+Math.random()*(N-M)。

      在上述代碼中主要用到了Paint類和Canvas類的一些常用方法,下面分別加以說明。Paint類的常用方法有:

      ??public void setAntiAlias(boolean aa)

      設置畫筆的鋸齒效果,true表示無鋸齒。

      ??public void setColor(int color)

      設置畫筆的顏色。

      ??public void setStyle(Paint.Style?style)

      設置畫筆風格,取值有:Paint.Style.FILL、Paint.Style.FILL_AND_STROKE及Paint.Style.STROKE。

      ??public void setStrokeWidth(float width)

      設置空心的邊框寬度。

      使用Canvas類的方法可以繪制基本的圖形,它的常用方法包含了繪制像素點、直線、圓和弧線的功能。

      1、繪制像素點:

      ??public void drawPoint(float x, float y, Paint?paint)

      ??public void drawPoints(float[] pts, int offset, int count, Paint?paint)

      ??public void drawPoints(float[] pts, Paint?paint)

      x,y:指像素點的坐標。

      float[] pts:一次繪制的多個像素點的坐標,該數組必須得是偶數個,兩個一組為一個像素點的坐標。

      offset:偏移量,用來指定取得數組中連續元素的第一個元素的位置。因為drawPoints()方法可以取pts數組中的一部分連續元素作為像素點的坐標。

      count:要獲得的數組元素的個數,必須為偶數。

      2、畫直線

      ??public void drawLine(float startX, float startY, float stopX, float stopY, Paint?paint)

      ??public void drawLines(float[] pts, Paint?paint)

      ??public void drawLines(float[] pts, int offset, int count, Paint?paint)

      startX、startY:直線開始點的坐標。

      stopX、stopY:直線結束點的坐標。

      pts:畫多條直線時端點坐標的集合,其中每四個數組元素為一組,表示一條直線

      count:要獲得的數組元素的個數,這個數必須得是4的整數倍。

      3、畫圓

      ??public void drawCircle(float cx, float cy, float radius, Paint?paint)

      cx、cy:圓心坐標。

      radius:半徑。

      4、繪制弧

      ??public void drawArc(RectF?oval, float startAngle, float sweepAngle, boolean useCenter, Paint?paint)

      oval:弧的外切矩形坐標。

      startAngle:弧的起始角度。

      sweepAngle:弧的結束角度,如果sweepAngle - startAngle >360,則畫出來的圓或橢圓。

      useCenter:指定是否把起始的半徑給畫上,若為false則只畫弧而不畫起始的半徑。

      示例1.3

      在屏幕上顯示一個矩形,當我們按鍵盤方向鍵時控制其中的正方形進行移動。

      (1)以示例1.1為基礎,修改GameView的代碼如下:

      class?GameView extends?View {

      int?y?= 10;

      int?x?= 10;

      public?GameView2(Context context) {

      super(context);

      }

      public?void?onDraw(Canvas canvas) {

      // 繪圖

      Paint mPaint = new?Paint();

      mPaint.setStyle(Paint.Style.STROKE);

      mPaint.setColor(Color.YELLOW);

      // 畫圓,用來作為參照

      canvas.drawCircle(50, 80, 20, mPaint);

      // 繪制矩形--后面我們將詳細講解

      canvas.drawRect(x, y, x?+ 80, y?+ 40, mPaint);

      }

      }

      (2)修改DrawActivity類的onKeyDown方法,代碼如下:

      public?boolean?onKeyDown(int?keyCode, KeyEvent event) {// 按鍵按下事件

      switch?(keyCode) {

      case?KeyEvent.KEYCODE_DPAD_UP:// 上方向鍵

      mGameView.y?-= 3;

      break;

      case?KeyEvent.KEYCODE_DPAD_DOWN:// 下方向鍵

      mGameView.y?+= 3;

      break;

      case?KeyEvent.KEYCODE_DPAD_LEFT:

      mGameView.x?-= 3;

      break;

      case?KeyEvent.KEYCODE_DPAD_RIGHT:

      mGameView.x?+= 3;

      break;

      }

      return?true;

      }

      運行程序,當我們按方向鍵時,會發現矩形發生移動,而圓形位置保持不變,如圖1.1.3所示。

      1.1.2?繪制文本

      除了可以在屏幕上繪制簡單的形狀圖形外,我們還可以在圖形中繪制文本。在Android中我們不僅可以中規中矩地繪制文本,還可以按照指定的路徑繪制文本。這在開發中是很有用的,比如游戲中人物上面的提示文字等。

      示例1.4

      演示Android中文本的繪制,包括文本的簡單繪制及沿著指定路徑繪制。程序最終運行效果如下圖1.1.4所示:

      本案例需要繪制的圖形可以分為兩種:在某個位置繪制文本以及沿某個路徑繪制文本。

      繪制文本的基本方法是drawText(),語法

      canvas.drawText(String s,Point);

      沿路徑繪制需要兩個步驟,首先要繪制路徑,其次繪制文本,并在文本和路徑之間建立關系。

      canvas.drawTextOnPath(String ,Path );

      Path參數

      Path對象及其構建。

      修改GameView類的代碼如下:

      class?GameView extends?View {

      private?final?String showString?= "好好學習,天天向上!";

      private?Path[] paths?= new?Path[3];

      private?Paint paint;

      public?GameView3(Context context) {

      super(context);

      paths[0] = new?Path();

      paths[0].moveTo(0, 0);

      for?(int?i = 1; i <= 7; i++) {

      // 生成7個點,隨機生成它們的Y座標。并將它們連成一條Path

      paths[0].lineTo(i * 33, (float) Math.random() * 33);

      }

      paths[1] = new?Path();

      RectF rectF = new?RectF(0, 0, 200, 120);

      paths[1].addOval(rectF, Path.Direction.CCW);

      paths[2] = new?Path();

      paths[2].addArc(rectF, 60, 180);

      // 初始化畫筆

      paint?= new?Paint();

      paint.setAntiAlias(true);

      paint.setColor(Color.RED);

      paint.setStrokeWidth(2);

      }

      @Override

      protected?void?onDraw(Canvas canvas) {

      canvas.drawColor(Color.WHITE);

      canvas.translate(40, 40);

      // 設置從右邊開始繪制(右對齊)

      paint.setTextAlign(Paint.Align.RIGHT);

      paint.setTextSize(20);

      // 繪制路徑

      paint.setStyle(Paint.Style.STROKE);

      canvas.drawPath(paths[0], paint);

      // 沿著路徑繪制一段文本。

      paint.setStyle(Paint.Style.FILL);

      canvas.drawTextOnPath(showString, paths[0], -8, 20, paint);

      // 畫布下移120

      canvas.translate(0, 60);

      // 繪制路徑

      paint.setStyle(Paint.Style.STROKE);

      canvas.drawPath(paths[1], paint);

      // 沿著路徑繪制一段文本。

      paint.setStyle(Paint.Style.FILL);

      canvas.drawTextOnPath(showString, paths[1], -20, 20, paint);

      // 畫布下移120

      canvas.translate(0, 120);

      // 繪制路徑

      paint.setStyle(Paint.Style.STROKE);

      canvas.drawPath(paths[2], paint);

      // 沿著路徑繪制一段文本。

      paint.setStyle(Paint.Style.FILL);

      canvas.drawTextOnPath(showString, paths[2], -10, 30, paint);

      canvas.drawText(showString, 174, 169, paint);

      }

      }

      上面程序中用到了名為Path的類,這個類可以預先在View上將N個點連成一條路徑,然后我們就可以調用Canvas類的drawPath()方法沿著指定的路徑繪制圖形了。上面代碼中繪制文本應用到了下面方法:

      public void drawTextOnPath (String text, Path path, float hOffset, float vOffset, Paint paint)

      text:要繪制的文本。

      path:繪制文本時要使用的路徑對象。

      hOffset:繪制文本時相對于路徑水平方向的偏移量。

      vOffset:繪制文本時相對于路徑垂直方向的偏移量。

      paint:繪制文本的畫筆。

      1.1.3?雙緩沖技術

      本節要模擬實現一個畫圖程序,即當用戶在觸摸屏上移動時,在屏幕上繪制任意的圖形。具體的實現思路是:借助Android的Path類,使用Canvas的drawLine()方法畫直線。其中每條直線都是從上一次拖動事件發生點畫到本次拖動事件發生點。當用戶在屏幕上移動時,兩次拖動事件發后點的距離很小,多條極短的直線連接起來,肉眼看起來就是整條直線了。但如果程序每次都只是從上次拖動事件的發生點繪一條直線到本次拖動事件的發生點,那么用戶前面繪制的就會丟失。為了保留用戶之前繪制的內容,程序需要借助于下面講到的“雙緩沖”技術。

      所謂的雙緩沖技術其實很簡單,就是當程序需要在指定的View上進行繪圖時,程序并不直接繪制到該View組件上,而是先繪制到一個內存中的Bitmap上,等到內存中的Bitmap繪制好后,再一次性地將Bitmap繪制到View組件上。

      示例1.5

      采用雙緩沖技術模擬實現畫圖程序,要求能夠在屏幕上繪制任意圖形。程序運行結果如下圖1.1.5所示:

      (1)以示例1.1為基礎,修改GameView類的代碼如下:

      public?class?GameView4 extends?View {

      private?float?preX;

      private?float?preY;

      private?Path path;

      public?Paint paint?= null;

      // 定義一個內存中的圖片,該圖片將作為緩沖區

      Bitmap bitmap?= null;

      // 定義cacheBitmap上的Canvas對象

      Canvas canvas?= null;

      public?GameView4(Context context) {

      super(context);

      // 創建一個與該View相同大小的緩存區

      bitmap?= Bitmap.createBitmap(320, 480, Config.ARGB_8888);

      canvas?= new?Canvas();

      path?= new?Path();

      // 設置cacheCanvas將會繪制到內存中的cacheBitmap上

      canvas.setBitmap(bitmap);

      // 設置畫筆的顏色

      paint?= new?Paint();

      paint.setColor(Color.RED);

      // 設置畫筆風格

      paint.setStyle(Paint.Style.STROKE);

      paint.setStrokeWidth(1);

      // 反鋸齒

      paint.setAntiAlias(true);

      paint.setDither(true);

      }

      @Override

      public?boolean?onTouchEvent(MotionEvent event) {

      // 獲取拖動事件的發生位置

      float?x = event.getX();

      float?y = event.getY();

      switch?(event.getAction()) {

      case?MotionEvent.ACTION_DOWN:

      path.moveTo(x, y);

      preX?= x;

      preY?= y;

      break;

      case?MotionEvent.ACTION_MOVE:

      path.quadTo(preX, preY, x, y);

      preX?= x;

      preY?= y;

      break;

      case?MotionEvent.ACTION_UP:

      canvas.drawPath(path, paint); ??-------------①

      path.reset();

      break;

      }

      invalidate();

      return?true;// 返回true表明處理方法已經處理該事件

      }

      @Override

      public?void?onDraw(Canvas canvas) {

      Paint bmpPaint = new?Paint();

      // 將cacheBitmap繪制到該View組件上

      canvas.drawBitmap(bitmap, 0, 0, bmpPaint); ???------------②

      // 沿著path繪制

      canvas.drawPath(path, paint);

      }

      }

      在觸摸事件中我們只是簡單地修改了currentX、currentY兩個屬性的值 ,并通知組件重繪。注意①處的代碼并不是調用該View的Canvas進行繪制,而是調用了緩存Bitmap的Canvas進行繪制,這是向緩沖繪圖。②才是將緩沖中的Bitmap對象繪制到View組件上。

      再來看一下,主布局文件的代碼:

      public?class?HandDraw extends?Activity {

      @Override

      public?void?onCreate(Bundle savedInstanceState) {

      super.onCreate(savedInstanceState);

      setContentView(new?GameView4(this));

      }

      }

      代碼比較簡單,僅僅是把我們自定義的View設置為Activity顯示的內容。

      1.2?圖像的繪制及簡單處理

      Canvas不僅可以繪制簡單圖形,還可以將復雜的圖像繪制到View上,比如圖1.1.1中憤怒的小鳥中的小鳥、小草等都可以利用本節中講解的繪制圖片的方法進行實現。而且利用這種方法可以更容易地創造出來讓人賞心悅目的軟件界面,給用戶帶來愉悅的體驗。

      1.2.1?繪制圖像

      可以通過Canvas類的drawBirmap()來顯示位圖,也可以借助于BitmapDrawable將Bitmap繪制到Canvas中或者顯示到View中。其中使用Bitmap方式繪制圖像需要裝載圖像資源,并獲得圖像資源的InputStream對象,然后使用BitmapFactory.decodeStream()方法將InputStream解碼成Bitmap對象,最后使用Canvas.drawBitmap()方法在View上繪制位圖。下面我們通過一個綜合性的示例進行演示。

      示例1.6

      演示在屏幕上繪制圖像的方法及技巧。本示例程序中用于顯示繪制圖像的類的代碼如下:

      public?class?DrawImg extends?View {

      private?Bitmap bitmap1?= null;

      private?Bitmap bitmap2?= null;

      private?Bitmap bitmap3?= null;

      private?Bitmap bitmap4?= null;

      private?Drawable drawable?= null;

      public?DrawImg(Context context) {

      super(context);

      setBackgroundColor(Color.GRAY);

      //裝載圖像資源,獲得圖像資源的InputStream形式的對象

      InputStream is = getResources().openRawResource(R.drawable.wx);

      Options options = new?Options();

      bitmap1?= BitmapFactory.decodeStream(is, null, options);//將InputStream解碼為Bitmap對象

      is = getResources().openRawResource(R.drawable.psu);

      bitmap2?= BitmapFactory.decodeStream(is);

      int?w = bitmap2.getWidth();

      int?h = bitmap2.getHeight();

      int[] pixels = new?int[w * h];

      bitmap2.getPixels(pixels, 0, w, 0, 0, w, h);// 復制bitmap2所有像素的顏色值

      bitmap3?= Bitmap.createBitmap(pixels, 0, w, w, h, Bitmap.Config.ARGB_8888);

      bitmap4?= Bitmap.createBitmap(pixels, 0, w, w, h, Bitmap.Config.ARGB_4444);

      }

      @Override

      protected?void?onDraw(Canvas canvas) {

      canvas.drawBitmap(bitmap1, 120, 10, null); ? //將圖像繪制到屏幕上

      canvas.drawBitmap(bitmap2, 18, 150, null);

      canvas.drawBitmap(bitmap3, 118, 150, null);

      canvas.drawBitmap(bitmap4, 218, 150, null);

      //裝載圖像資源,并獲得圖像資源的Drawable對象

      drawable?= getResources().getDrawable(R.drawable.zwx);

      drawable.setBounds(40, 273, 142,347);

      drawable.draw(canvas); //使用Drawable的draw()方法繪制位圖

      Bitmap bitmap = ((BitmapDrawable) getResources().getDrawable(R.drawable.mm)).getBitmap();

      canvas.drawBitmap(bitmap, 224,283, null);

      }

      }

      代碼中用到的方法如下:

      ??Resources?View.getResources()

      返回和當前視圖所關聯的資源。

      ??InputStream?Resources.openRawResource(int id)

      通過讀取指定的未加工的資源打開一個數據流。

      ??Drawable?Resources.getDrawable(int id)

      返回一個和指定id相關聯的Drawable對象。

      ??void Drawable.setBounds(int left, int top, int right, int bottom)

      為Drawable對象設定邊界(它會對圖像進行縮放變形,但不會裁剪圖像)。

      ??void ?Drawable.draw(Canvas?canvas)

      將Drawable對象畫在canvas/屏幕上。

      程序運行效果如下圖1.1.6所示。

      1.2.2?改變圖像透明度

      Android系統支持的顏色由RGB三原色(紅、綠、藍)再加上一個Alpha四個值組成。這四個值都在0~255之間,顏色值越小,表示該顏色越淡,顏色值越大,表示顏色越深。如果RGB都為0,就是黑色,如果RGB都為255,就是白色。Alpha值也在0~255之間變化,值越小,顏色越透明,Alpha值越大,顏色越不透明,當Alpha值為0時,顏色將因完全透明而從屏幕上消失。設置顏色的透明度可以通過Paint類的setAlpha()方法完成。

      示例1.7

      實現改變指定圖片透明度的功能。本示例某一時刻程序最終運行效果如下圖1.1.7所示。

      本示例中用于顯示繪制圖像的類的代碼如下:

      public?class?DrawImg extends?View {

      private?Bitmap bitmap?= null;

      private?int?alpha?= 12;

      public?DrawImg(Context context ) {

      super(context);

      setBackgroundColor(Color.GRAY);

      InputStream is = getResources().openRawResource(R.drawable.wx);

      bitmap?= BitmapFactory.decodeStream(is);

      }

      public?void?setAlpha(int?alpha) {

      this.alpha?=alpha;

      }

      @Override

      protected?void?onDraw(Canvas canvas) {

      Paint paint = new?Paint();

      paint.setAlpha(alpha);

      canvas.drawBitmap(bitmap, 120,10, paint);

      }

      }

      Activity類的代碼如下:

      public?class?DrawActivity extends?Activity {

      private?static?final?int?REFRESH?= 0x000001;

      private?DrawImg drawImg?= null;

      private?int?alpha?= 15;

      private?SeekBar

      seekBar

      = null;

      @Override

      public?void?onCreate(Bundle savedInstanceState) {

      super.onCreate(savedInstanceState);

      LinearLayout?layout = new?LinearLayout(this);

      layout.setOrientation(LinearLayout.VERTICAL);

      drawImg?= new?DrawImg(this);

      drawImg.setLayoutParams(new?LayoutParams(LayoutParams.WRAP_CONTENT, 120));

      layout.addView(drawImg);

      SeekBar seekBar = new?SeekBar(this);

      seekBar.setMax(255);

      seekBar.setProgress(alpha);

      layout.addView(seekBar);

      setContentView(layout);

      seekBar.setOnSeekBarChangeListener(new?OnSeekBarChangeListener() {

      @Override

      public?void?onStopTrackingTouch(SeekBar seekBar) {

      }

      @Override

      public?void?onStartTrackingTouch(SeekBar seekBar) {

      }

      @Override

      public?void?onProgressChanged(SeekBar seekBar, int?progress, boolean?fromUser) {

      alpha?= progress;

      drawImg.setAlpha(alpha);

      drawImg.invalidate();

      }

      });

      // new Thread(new GameThread()).start();// 開啟線程

      }

      }

      1.2.3?旋轉圖像

      在憤怒的小鳥中,當小鳥和別的物體發生碰撞的時候,會出現圖像旋轉的動畫效果。在實際的游戲開發中,圖像旋轉是一個經常用到的功能,本小節中我們就學習和圖像旋轉有關的操作。要實現圖像的旋轉需要用到矩陣Matrix類的相關知識,要實現圖像的不斷旋轉,需要在onDraw()方法中調用invalidate()方法。每調用一次invalidate方法,onDraw()方法就會調用一次,當在onDraw()方法中調用invalidate()方法時,就意味著onDraw()方法會不斷地被調用。

      示例1.8:實現旋轉指定圖片的功能(讓圖片繞著某一方向不停地旋轉),某一時刻程序的運行效果如圖1.1.8所示。

      本示例中用于顯示繪制圖像的類的代碼如下:

      public?class?DrawImg extends?View {

      private?Bitmap bitmap?= null;

      private?int?degree?= 0 ;

      public?DrawImg(Context context) {

      super(context);

      setBackgroundColor(Color.GRAY);

      InputStream is = getResources().openRawResource(R.drawable.wx);

      bitmap?= BitmapFactory.decodeStream(is);

      }

      @Override

      protected?void?onDraw(Canvas canvas) {

      if(degree?<0)

      degree?= 360;

      if(degree?> 360 )

      degree?= 0;

      Matrix matrix = new?Matrix();

      matrix.setRotate(degree++ , 160, 144);

      canvas.setMatrix(matrix);

      canvas.drawBitmap(bitmap, 120,88, null);

      invalidate();

      }

      }

      另外對于圖像的變形還有扭曲和拉伸等。

      任務實訓部分

      1:在Android中繪制基本圖形

      訓練技能點

      ??Android中基本圖形的繪制

      ??熟悉Paint類、Canvas類的常用方法

      需求說明

      練習Android中如何繪制基本圖形,要求最終繪制結果如下圖1.2.1所示

      實現步驟

      (1)?搭建程序框架

      (2)?自定義View類,并在其中實現所要求圖形的繪制

      具體實現請參看1.1節的內容。

      2:完善1.1.3節的畫圖程序

      訓練技能點

      雙緩沖技術

      需求說明

      為1.1.3節中我們模擬實現的畫圖程序添加菜單選擇功能,要求至少添加兩個菜單項,一個用來設置畫筆的顏色,一個用來設置畫筆的寬度,并實現這些功能。

      鞏固練習

      一、選擇題

      1.?我們一般是在( ?)實現屏幕的刷新。

      A.?在onDraw方法之后調用invalidate()方法

      B.?在onDraw方法之前調用invalidate()方法

      C.?調用postInvalidate()方法之后

      D.?調用postInvalidate()方法之前

      2.?下列有關雙緩沖技術的說法正確的是( ?)

      A.?雙緩沖技術只能應用在Android中

      B.?雙緩沖技術的效率比較高

      C.?雙緩沖技術能夠避免閃屏

      D.?雙緩沖技術其實就是把要顯示的東西緩存起來繪制好之后才進行顯示

      二、上機練習

      1、在手機屏幕上利用基本圖形繪制出一個機器人。

      2、為第一題中的機器人添加走動的功能(選做)。

      擴展進階

      使用SurfaceView類搭建框架

      對于View的onDraw()方法,不能把容易阻塞的處理移動到后臺線程中,因為從后臺線程修改一個GUI元素是被顯式地禁止的。

      當需要快速地更新View的UI,或者當渲染代碼阻塞GUI線程的時間過長的時候,SurfaceView就派上用場了。SurfaceView封裝了一個Surface對象(View及其子類都是畫在Surface上的)。這一點很重要,因為Surface可以使用后臺線程繪制圖形。對于那些資源敏感的操作,或者那些要求快速更新或者高速幀率的地方,例如,使用3D圖形、創建游戲或者實時預覽攝像頭,這一點特別有用。

      獨立于GUI線程進行繪圖的代價是額外的內存消耗,所以,雖然它是創建定制的View的有效方式,有時甚至是必須的,但是使用SurfaceView的時候仍然要保持謹慎。SurfaceView的使用方式與任何View所派生的類是完全相同的。可以像其他View那樣應用動畫,并把它們放到布局中。SurfaceView類的事件處理和View一樣。

      SurfaceView封裝的Surface支持使用所有標準Canvas方法進行繪圖,同時也完全支持的OpenGL ES庫。SurfaceView和View的明顯不同在于Surface不需要通過線程來更新視圖,但在繪制之前必須使用lockCanvas方法鎖定畫布,并得到畫布,然后繪制,完成后用unlockCanvasAndPost方法解鎖畫布。

      ??Canvas?android.view.SurfaceHolder.lockCanvas()

      鎖定畫布,得到canvas。

      ??void android.view.SurfaceHolder.unlockCanvasAndPost(Canvas?canvas)

      繪制后解鎖,繪制后必須解鎖才能顯示。

      另外,值得強調的是對被繪制的畫面進行裁剪、控制大小等需要使用SurfaceHolder來完成。

      使用SurfaceView時,可以通過SurfaceHolder.Callback接口來對其進行創建、銷毀,還有當情況改變時進行監視。SurfaceHolder.Callback接口提供了以下三個方法:

      ??public void surfaceCreated(SurfaceHolder holder)

      在surface創建時觸發

      ??public void surfaceChanged(SurfaceHolder holder, int format, int width,int height)

      在surface的大小發生改變時觸發

      ??public void surfaceDestroyed(SurfaceHolder holder)

      在surface銷毀時觸發

      下面示例使用SurfaceView搭建繪圖框架。在畫布上繪制一個小球,然后為應用添加事件控制功能,要求可以通過方向鍵控制小球的移動。

      首先,編寫一個用來繪制界面的View類,代碼如下:

      class?GameSurfaceView extends?SurfaceView implements?SurfaceHolder.Callback, Runnable {

      boolean?mbLoop?= false;// 控制循環

      SurfaceHolder mSurfaceHolder?= null;// 定義SurfaceHolder對象

      int?miCount?= 0;

      int?y?= 50;

      public?GameSurfaceView(Context context) {

      super(context);

      mSurfaceHolder?= this.getHolder();// 實例化SurfaceHolder

      mSurfaceHolder.addCallback(this);// 添加回調

      this.setFocusable(true);

      mbLoop?= true;

      }

      // 在surface的大小發生改變時觸發

      @Override

      public?void?surfaceChanged(SurfaceHolder holder, int?format, int?width, int?height)

      { }

      // 在surface創建時觸發

      @Override

      public?void?surfaceCreated(SurfaceHolder holder) {

      new?Thread(this).start();// 開啟繪圖線程

      }

      // 在surface銷毀時觸發

      @Override

      public?void?surfaceDestroyed(SurfaceHolder holder) {

      mbLoop?= false;?// 停止循環

      }

      // 繪圖循環

      public?void?run() {

      while?(mbLoop) {

      try?{

      Thread.sleep(200);

      } catch?(Exception e) {

      }

      synchronized?(mSurfaceHolder) {

      Draw();

      }

      }

      }

      // 繪圖方法

      public?void?Draw() {

      Canvas canvas = mSurfaceHolder.lockCanvas();// 鎖定畫布,得到canvas

      if?(mSurfaceHolder?== null?|| canvas == null) {

      return;

      }

      if?(miCount?< 100) {

      miCount++;

      } else?{

      miCount?= 0;

      }

      Paint mPaint = new?Paint();// 繪圖

      mPaint.setAntiAlias(true);

      mPaint.setColor(Color.BLACK);

      canvas.drawRect(0, 0, 320, 480, mPaint); // 繪制矩形--清屏作用

      switch?(miCount?% 4) {

      case?0:

      mPaint.setColor(Color.BLUE);

      break;

      case?1:

      mPaint.setColor(Color.GREEN);

      break;

      case?2:

      mPaint.setColor(Color.RED);

      break;

      case?3:

      mPaint.setColor(Color.YELLOW);

      break;

      default:

      mPaint.setColor(Color.WHITE);

      break;

      }

      canvas.drawCircle((320 - 25) / 2, y, 50, mPaint);// 繪制矩形

      mSurfaceHolder.unlockCanvasAndPost(canvas);// 繪制后解鎖,繪制后必須解鎖才能顯示

      }

      }

      然后編寫一個用來控制整個應用(界面元素動作)的類:

      public?class?SurfaceViewFrameActivity extends?Activity {

      GameSurfaceView mGameSurfaceView;

      @Override

      public?void?onCreate(Bundle savedInstanceState)

      {

      super.onCreate(savedInstanceState);

      mGameSurfaceView?= new?GameSurfaceView(this);//創建GameSurfaceView對象

      setContentView(mGameSurfaceView);//設置顯示GameSurfaceView視圖

      }

      //觸筆事件

      public?boolean?onTouchEvent(MotionEvent event)

      {

      return?true;

      }

      //按鍵按下事件

      public?boolean?onKeyDown(int?keyCode, KeyEvent event)

      {

      return?true;

      }

      //按鍵彈起事件

      public?boolean?onKeyUp(int?keyCode, KeyEvent event)

      {

      switch?(keyCode)

      {

      case?KeyEvent.KEYCODE_DPAD_UP://上方向鍵

      mGameSurfaceView.y-=3;

      break;

      case?KeyEvent.KEYCODE_DPAD_DOWN://下方向鍵

      mGameSurfaceView.y+=3;

      break;

      }

      return?false;

      }

      public?boolean?onKeyMultiple(int?keyCode, int?repeatCount, KeyEvent event)

      {

      return?true;

      }

      }

      程序運行效果如下圖1.3.1所示:

      Android 任務調度

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:WPS表格技巧如何制作雙行工資條(wps工資單表格怎么做)
      下一篇:python--pickle持久化(十九)
      相關文章
      久久久国产亚洲精品| 亚洲国产精品成人久久蜜臀| 亚洲国产一区视频| 男人天堂2018亚洲男人天堂| 亚洲国产综合第一精品小说| 亚洲电影在线播放| 久久久久久久亚洲Av无码| 亚洲视频在线观看免费| 国产V亚洲V天堂无码| 亚洲乱码日产一区三区| 国产亚洲综合久久系列| 国产亚洲成av片在线观看| 亚洲精品国偷自产在线| 国产精品国产亚洲精品看不卡| 亚洲精品~无码抽插| 亚洲av无码国产精品色午夜字幕| 国产亚洲精品a在线无码| 亚洲国产精品成人久久| 无码欧精品亚洲日韩一区| 亚洲人成电影在线天堂| 亚洲美女aⅴ久久久91| 亚洲另类古典武侠| 亚洲高清视频在线| 亚洲色大成WWW亚洲女子| 亚洲国产精品无码久久久秋霞1 | 亚洲日本va在线视频观看| 亚洲欭美日韩颜射在线二| 亚洲成AV人片在线观看| 亚洲国产精品一区| 亚洲日韩国产精品无码av| 亚洲欧洲另类春色校园网站| 亚洲天然素人无码专区| 18禁亚洲深夜福利人口| 亚洲精品岛国片在线观看| 伊人久久精品亚洲午夜| 久久精品国产亚洲AV麻豆不卡| 久久精品国产亚洲AV香蕉| 亚洲Av高清一区二区三区| 亚洲另类无码专区首页| va亚洲va日韩不卡在线观看| 自拍偷自拍亚洲精品情侣|