通過翻譯開發(fā)指南學(xué)習(xí)畫布和圖片

1.前言


安卓框架提供了一組2D繪圖的APIs鬓照,允許在畫布上渲染自定義圖形或者修改已存在的View來定義外觀和感覺涮母。繪制2D圖形通常有兩種途徑:
  a.給布局中的View對象繪制圖形和動(dòng)畫槐秧。由系統(tǒng)的視圖層次結(jié)構(gòu)處理繪制過程光酣,你只需定義視圖內(nèi)的圖形。
  b.直接將圖形繪制到畫布上犀概。你自己調(diào)用適當(dāng)類的 onDraw() 方法(傳入畫布)或者Canvas對象的 draw...() 方法(就像 drawPicture() 方法)敢靡,這樣也可以控制任何動(dòng)畫挂滓。
  選項(xiàng)“a”,當(dāng)繪制的是不需要?jiǎng)討B(tài)變化的簡單圖形和非性能密集型游戲的一部分時(shí)啸胧,在視圖上繪制是最好的選擇赶站。例如,顯示靜態(tài)圖形或預(yù)定義的動(dòng)畫纺念。詳細(xì)信息看圖形這一章節(jié)贝椿。
  選項(xiàng)“b”,當(dāng)應(yīng)用需要經(jīng)常重繪時(shí)陷谱,在畫布上繪制是較好的選擇烙博。例如,電子游戲等烟逊。有兩種方式去實(shí)現(xiàn):

  • 與UI Activity同一線程時(shí)渣窜,給布局創(chuàng)建自定義視圖組件,需要調(diào)用 invalidate() 方法和處理 onDraw() 方法的回調(diào)焙格。
  • 一個(gè)單獨(dú)的線程時(shí)图毕,管理一個(gè)SurfaceView,在畫布上以線程支持的最快速度繪制(不需要請求 invalidate())眷唉。

2.使用畫布繪制


寫程序時(shí)予颤,若希望執(zhí)行專門的繪圖和/或控制圖形的動(dòng)畫,應(yīng)該在畫布上繪制冬阳。畫布只是表面的封裝蛤虐,負(fù)責(zé)所有 draw...() 方法的調(diào)用,圖形傳遞給實(shí)際的Surface上肝陪,擺放在窗口中的底層的位圖驳庭。
  如果在 onDraw() 回調(diào)方法內(nèi)繪制,調(diào)用提供的Canvas的 draw...() 方法即可氯窍。處理SurfaceView對象時(shí)饲常,通過 SurfaceHolder.lockCanvas() 方法獲取Canvas對象。(這兩種情況在下面的章節(jié)中都有討論)如果需要?jiǎng)?chuàng)建新的Canvas對象狼讨,必須定義實(shí)際執(zhí)行繪制的Bitmap對象贝淤。畫布總是需要位圖配合,創(chuàng)建新畫布如下:

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

現(xiàn)在畫布將在給定的位圖上繪制政供,繪制完后播聪,通過 drawBitmap(Bitmap,...) 方法可將位圖帶給另外畫布。建議在 View.onDraw() 或 SurfaceHolder.lockCanvas() 方法提供的畫布上繪制最終圖形(詳見下面的章節(jié))布隔。

2.1.繪制在View上

如果你的應(yīng)用不需要大量的處理或好高的幀速率(也許是象棋游戲离陶,貪吃蛇游戲或者緩慢的動(dòng)畫應(yīng)用),應(yīng)該創(chuàng)建自定義視圖組件和在 View.onDraw() 方法內(nèi)用畫布繪圖衅檀。這樣做方便的是招刨,安卓系統(tǒng)將提供預(yù)定義的Canvas對象來調(diào)用 draw...() 方法。
  首先哀军,繼承View類(或其子類)和定義 onDraw() 回調(diào)方法计济。當(dāng)視圖繪制自己時(shí),安卓系統(tǒng)將調(diào)用這個(gè)方法排苍,通過傳入的Canvas對象執(zhí)行所有的繪制操作沦寂。
  安卓系統(tǒng)只會(huì)在必要時(shí)調(diào)用 onDraw() 方法。每當(dāng)應(yīng)用準(zhǔn)備好繪制時(shí)淘衙,調(diào)用 invalidate() 方法廢除當(dāng)前的視圖传藏,同時(shí)告知安卓系統(tǒng)調(diào)用 onDraw() 方法(不能保證回調(diào)是瞬時(shí)的)。
  在視圖組件的 onDraw() 方法內(nèi)彤守,使用提供的Canvas對象的各種 draw...() 方法或其它類的 draw() 方法(以提供的Canvas對象為參數(shù))完成所有繪圖毯侦。一旦 onDraw() 方法完成,安卓系統(tǒng)將使用畫布繪制位圖具垫。

注意:為了從非主線程刷新界面侈离,必須調(diào)用 postInvalidate() 方法。

關(guān)于擴(kuò)展View類的更多信息筝蚕,閱讀 read Building Custom Components卦碾。

2.2.繪制在SurfaceView上

SurfaceView是View的一個(gè)專注于繪圖的子類铺坞,目的是在應(yīng)用的子線程中提供繪圖功能,那樣應(yīng)用不需要等待系統(tǒng)視圖結(jié)構(gòu)的繪制完成洲胖。反而济榨,與SurfaceView相關(guān)聯(lián)的子線程可以按照自己的頻率在畫布上繪制。
  首先绿映,需要?jiǎng)?chuàng)建一個(gè)類繼承SurfaceView擒滑,同時(shí)實(shí)現(xiàn)SurfaceHolder.Callback接口。它可以提供Surface的底層信息叉弦,例如丐一,什么時(shí)候被創(chuàng)建、被改變或者被銷毀淹冰。這些信息很重要库车,可以知道何時(shí)開始繪圖,是否需要根據(jù)新的Surface屬性進(jìn)行調(diào)整榄棵,何時(shí)停止繪圖凝颇,以及殺死某些任務(wù)。在SurfaceView類內(nèi)部定義子線程疹鳄,以便在你的畫布上執(zhí)行所有的繪圖過程拧略。
  對于Surface的操作應(yīng)該通過SurfaceHolder而不是直接處理。當(dāng)SurfaceView被初始化后瘪弓,調(diào)用 getHolder() 方法獲取SurfaceHolder垫蛆。通過 addCallback() 方法(參數(shù)為this)可以將回調(diào)對象傳入SurfaceHolder以獲取通知,而被回調(diào)的方法需在SurfaceView類中重寫腺怯。
  為了在子線程中通過畫布繪制袱饭,需要將SurfaceHolder對象傳進(jìn)線程,并調(diào)用它的 lockCanvas() 方法獲取畫布呛占,有了畫布就可以進(jìn)行必要的繪制操作了虑乖。畫完后,調(diào)用 unlockCanvasAndPost() 方法解鎖和傳遞畫布對象晾虑,這樣疹味,內(nèi)容才會(huì)顯示到畫布上。每當(dāng)要重繪時(shí)帜篇,先鎖定畫布再解鎖畫布糙捺,代碼看這篇文章

注意:每次從SurfaceHolder獲取畫布笙隙,畫布之前的狀態(tài)將會(huì)被保留洪灯。為了正確展示動(dòng)畫,你必須重繪整個(gè)Surface竟痰。比如签钩,調(diào)用 drawColor() 方法填充一種顏色或者調(diào)用 drawBitmap() 方法設(shè)置一個(gè)背景圖像來清除畫布之前的狀態(tài)掏呼。否則,會(huì)在畫布上看到之前繪制過的痕跡边臼。

3.圖形


安卓提供了一個(gè)自定義2D圖形庫哄尔,用于繪制形狀和圖像假消。這些在兩個(gè)維度上繪制的公共類放在 android.graphics.drawable 包中柠并。
  本文討論使用Drawable對象繪制圖形的基本知識(shí)和如何使用Drawable子類。關(guān)于使用圖形完成幀動(dòng)畫富拗,詳見 Drawable Animation臼予。
  圖形通常指可以繪制的東西,它的子類定義了各種具體的可繪圖形啃沪,包括BitmapDrawable粘拾,ShapeDrawable,PictureDrawable创千,LayerDrawable等缰雇。當(dāng)然,也可以繼承這些類追驴,來實(shí)現(xiàn)自己想要的械哟、具有獨(dú)特行為的Drawable對象。
  有三種方式定義和實(shí)例化Drawable對象:使用保存在項(xiàng)目資源中的圖像殿雪;使用XML文件定義Drawable屬性暇咆;使用正常的類構(gòu)造函數(shù)。下面丙曙,將分別討論前兩種技術(shù)(第三種就是代碼的調(diào)用爸业,功能最全也不難)。

3.1.使用圖像資源創(chuàng)建

向應(yīng)用程序添加圖形的簡單方法是引用項(xiàng)目資源中的圖像文件亏镰,支持的文件類型有:PNG(首選)扯旷,JPG(可接受),GIF(不建議)索抓。這種技術(shù)顯然更適合應(yīng)用程序圖標(biāo)钧忽、徽標(biāo)或其它情況(如在游戲中使用)。
  使用圖像資源纸兔,僅僅需要將文件添加到你項(xiàng)目的 res/drawable/ 目錄下惰瓜,再從代碼和XML布局中引用。無論哪種方式汉矿,都涉及到使用資源ID崎坊,一種沒有文件類型擴(kuò)展名的文件名(例如,my_image.png 用my_image引用)洲拇。

注意:放在 res/drawable/ 目錄下的圖像資源可能會(huì)被aapt工具在編譯過程中使用圖像無損壓縮進(jìn)行自動(dòng)優(yōu)化奈揍,比如曲尸,一個(gè)真的不需要超過256種顏色的PNG可能會(huì)被顏色調(diào)色板轉(zhuǎn)換成8位的PNG。在圖像質(zhì)量不變的情況下男翰,可以使用較少的內(nèi)存另患。因此,需要認(rèn)識(shí)到蛾绎,這個(gè)目錄下的圖像二進(jìn)制文件在編譯的過程中會(huì)改變昆箕。如果計(jì)劃用位流讀取圖像轉(zhuǎn)換成位圖,最好將圖像放到 res/raw/ 目錄下租冠,避免被優(yōu)化鹏倘。

示例代碼(使用圖形資源創(chuàng)建ImageView并添加到布局中)

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對象來處理圖像資源顽爹。那么纤泵,通過資源創(chuàng)建Drawable對象如下:

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

項(xiàng)目中每個(gè)獨(dú)特的資源,不管實(shí)例化多少個(gè)不同的對象镜粤,只能維持一種狀態(tài)捏题。例如,對同一個(gè)圖像資源實(shí)例化兩個(gè)Drawable對象肉渴,改變其中一個(gè)對象的一個(gè)屬性(拿透明度來說)公荧,將會(huì)影響另一個(gè)。所以黄虱,處理一個(gè)對象的多個(gè)資源時(shí)稚矿,使用補(bǔ)間動(dòng)畫來改變圖形比直接操作要好。

示例XML(在XML布局中給ImageView添加圖形資源)

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

關(guān)于使用項(xiàng)目資源的詳細(xì)信息捻浦,閱讀 Resources and Assets晤揣。

3.2.使用XML資源創(chuàng)建

若對安卓開發(fā)用戶界面的原則熟悉,應(yīng)該清楚在XML中定義對象所固有的能力和靈活性朱灿,這種理念從View貫穿到Drawable昧识。如果想創(chuàng)建的Drawable對象不依賴于應(yīng)用程序代碼或者用戶交互,那么在XML中定義是個(gè)好的選擇盗扒。即使期望圖形隨著用戶的使用而改變屬性跪楞,你也應(yīng)該認(rèn)識(shí)到在XML中定義對象可以隨時(shí)修改初始化時(shí)的屬性。
  一旦在XML中定義Drawable對象侣灶,文件需保存到項(xiàng)目的 res/drawable/ 目錄下甸祭。然后調(diào)用 Resources.getDrawable() 方法,根據(jù)XML文件的資源ID獲取和初始化對象褥影。
  任何支持 inflate() 方法的Drawable子類能夠被XML定義和被應(yīng)用初始化池户,它們特有的XML屬性能夠找到對應(yīng)的對象屬性(詳見類參考)。
  示例

// TransitionDrawable在XML中的使用
<transition xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/image_expand">
    <item android:drawable="@drawable/image_collapse">
</transition>

// 實(shí)例化TransitionDrawable并設(shè)置為ImageView的內(nèi)容
Resources res = mContext.getResources();
TransitionDrawable transition = (TransitionDrawable)res.getDrawable(R.drawable.expand_collapse);
ImageView image = (ImageView) findViewById(R.id.toggle_image);
image.setImageDrawable(transition);

// 運(yùn)行變換一秒鐘
transition.startTransition(1000);

4.形狀圖片


當(dāng)想要?jiǎng)討B(tài)地繪制兩個(gè)維度的圖形時(shí),ShapeDrawable對象將滿足需求校焦,可以通過編程的方式繪制原始形狀和定義任何樣式赊抖。
  ShapeDrawable繼承自Drawable類,可以被當(dāng)作Drawable對象使用寨典,比如氛雪,調(diào)用 setBackgroundDrawable() 方法給視圖設(shè)置背景。當(dāng)然也可以給自定義視圖繪制自己的形狀耸成,但記得添加到自己的布局中报亩。因?yàn)镾hapeDrawable對象有自己的 draw() 方法,所以在View子類的 View.onDraw() 方法中調(diào)用ShapeDrawable對象的 draw() 方法墓猎。下面是對View類最基本的擴(kuò)展捆昏,繪制一個(gè)ShapeDrawable:

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

  protected void onDraw(Canvas canvas) {
    mDrawable.draw(canvas);
  }
}

在構(gòu)造方法中赚楚,ShapeDrawable對象被定義為橢圓形毙沾,然后給了顏色和大小。如果不設(shè)置形狀宠页,將不會(huì)繪制左胞;如果不設(shè)置顏色,將默認(rèn)黑色举户。
  在自定義視圖中可以繪制任何想要的樣子烤宙。我們可以將上面示例的形狀用代碼繪制到Activity中:

CustomDrawableView mCustomDrawableView;

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

  setContentView(mCustomDrawableView);
}

若要通過XML布局自定義圖形而不是在Activity中設(shè)置,CustomDrawableView類必須重寫 View(Context, AttributeSet) 構(gòu)造函數(shù)俭嘁,因?yàn)閺腦ML實(shí)例化View對象時(shí)將會(huì)調(diào)用躺枕。接著將CustomDrawableView元素添加到Activity的XML布局中,如下:

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

ShapeDrawable類(像 android.graphics.drawable 包中其它Drawable類型一樣)允許通過公開的方法定義各種屬性供填,包括alpha拐云、濾色器、震動(dòng)近她、不透明度和顏色叉瘩。
  使用XML也可以定義原始形狀,詳細(xì)信息參考 Drawable Resources文檔中ShapeDrawable章節(jié)粘捎。

5.可伸縮圖片


NinePatchDrawable是一個(gè)可伸縮的位圖圖像薇缅,在標(biāo)準(zhǔn)PNG圖像的基礎(chǔ)上加了1像素寬的邊框。當(dāng)它作為背景時(shí)攒磨,安卓會(huì)自動(dòng)調(diào)整大小以適應(yīng)視圖中的內(nèi)容泳桦。它必須以.9.png為擴(kuò)展名保存在項(xiàng)目的 res/drawable/ 目錄下。
  NinePatch圖分為兩個(gè)部分娩缰,左灸撰、上為定義拉伸區(qū),右、下為定義內(nèi)邊距(里面是內(nèi)容區(qū))梧奢,如下圖:

NinePatch.png

將它作為Button的背景圖時(shí)狱掂,實(shí)際效果如下:

Button.png

6.矢量圖片


VectorDrawable對象由定義在XML文件中的一系列點(diǎn)、線亲轨、曲線和相關(guān)的顏色信息組成趋惨。安卓5.0(API 21)開始,VectorDrawable和AnimatedVectorDrawable這兩個(gè)類支持矢量圖形作為圖形資源惦蚊。之前若想使用器虾,支持庫23.2或更高也提供矢量圖形和動(dòng)態(tài)矢量圖形的支持。
  關(guān)于使用矢量圖形系統(tǒng)APIs或矢量圖形支持庫的更多信息蹦锋,前往 Vector Drawable兆沙。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市莉掂,隨后出現(xiàn)的幾起案子葛圃,更是在濱河造成了極大的恐慌,老刑警劉巖憎妙,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件库正,死亡現(xiàn)場離奇詭異,居然都是意外死亡厘唾,警方通過查閱死者的電腦和手機(jī)褥符,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抚垃,“玉大人喷楣,你說我怎么就攤上這事『资鳎” “怎么了铣焊?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長魂迄。 經(jīng)常有香客問我粗截,道長,這世上最難降的妖魔是什么捣炬? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任熊昌,我火速辦了婚禮,結(jié)果婚禮上湿酸,老公的妹妹穿的比我還像新娘婿屹。我一直安慰自己,他們只是感情好推溃,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布昂利。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蜂奸。 梳的紋絲不亂的頭發(fā)上犁苏,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機(jī)與錄音扩所,去河邊找鬼围详。 笑死,一個(gè)胖子當(dāng)著我的面吹牛祖屏,可吹牛的內(nèi)容都是我干的助赞。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼袁勺,長吁一口氣:“原來是場噩夢啊……” “哼雹食!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起期丰,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤群叶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后咐汞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盖呼,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年化撕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片约炎。...
    茶點(diǎn)故事閱讀 40,117評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡植阴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出圾浅,到底是詐尸還是另有隱情掠手,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布狸捕,位于F島的核電站喷鸽,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏灸拍。R本人自食惡果不足惜做祝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鸡岗。 院中可真熱鬧混槐,春花似錦、人聲如沸轩性。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至悯嗓,卻和暖如春件舵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背脯厨。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工芦圾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人俄认。 一個(gè)月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓个少,卻偏偏與公主長得像,于是被迫代替她去往敵國和親眯杏。 傳聞我的和親對象是個(gè)殘疾皇子夜焦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容