Android 實(shí)現(xiàn)自定義View的繪制

2018-08-02 18:13 更新

編寫:kesenhoo - 原文:http://developer.android.com/training/custom-view/custom-draw.html

自定義view的最重要的一個(gè)部分是自定義它的外觀。根據(jù)你的程序的需求,自定義繪制可能簡(jiǎn)單也可能很復(fù)雜。這節(jié)課會(huì)演示一些最常見(jiàn)的操作。

Override onDraw()

重繪一個(gè)自定義的view的最重要的步驟是重寫onDraw()方法。onDraw()的參數(shù)是一個(gè)Canvas對(duì)象。Canvas類定義了繪制文本,線條,圖像與許多其他圖形的方法。你可以在onDraw方法里面使用那些方法來(lái)創(chuàng)建你的UI。

在你調(diào)用任何繪制方法之前,你需要?jiǎng)?chuàng)建一個(gè)Paint對(duì)象。

創(chuàng)建繪圖對(duì)象

android.graphics framework把繪制定義為下面兩類:

  • 繪制什么,由Canvas處理
  • 如何繪制,由Paint處理

例如Canvas提供繪制一條直線的方法,Paint提供直線顏色。Canvas提供繪制矩形的方法,Paint定義是否使用顏色填充。簡(jiǎn)單來(lái)說(shuō):Canvas定義你在屏幕上畫的圖形,而Paint定義顏色,樣式,字體,

所以在繪制之前,你需要?jiǎng)?chuàng)建一個(gè)或者多個(gè)Paint對(duì)象。在這個(gè)PieChart 的例子,是在init()方法實(shí)現(xiàn)的,由constructor調(diào)用。

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

   ...

剛開(kāi)始就創(chuàng)建對(duì)象是一個(gè)重要的優(yōu)化技巧。Views會(huì)被頻繁的重新繪制,初始化許多繪制對(duì)象需要花費(fèi)昂貴的代價(jià)。在onDraw方法里面創(chuàng)建繪制對(duì)象會(huì)嚴(yán)重影響到性能并使得你的UI顯得卡頓。

處理布局事件

為了正確的繪制你的view,你需要知道view的大小。復(fù)雜的自定義view通常需要根據(jù)在屏幕上的大小與形狀執(zhí)行多次layout計(jì)算。而不是假設(shè)這個(gè)view在屏幕上的顯示大小。即使只有一個(gè)程序會(huì)使用你的view,仍然是需要處理屏幕大小不同,密度不同,方向不同所帶來(lái)的影響。

盡管view有許多方法是用來(lái)計(jì)算大小的,但是大多數(shù)是不需要重寫的。如果你的view不需要特別的控制它的大小,唯一需要重寫的方法是onSizeChanged()).

onSizeChanged(),當(dāng)你的view第一次被賦予一個(gè)大小時(shí),或者你的view大小被更改時(shí)會(huì)被執(zhí)行。在onSizeChanged方法里面計(jì)算位置,間距等其他與你的view大小值。

當(dāng)你的view被設(shè)置大小時(shí),layout manager(布局管理器)假定這個(gè)大小包括所有的view的內(nèi)邊距(padding)。當(dāng)你計(jì)算你的view大小時(shí),你必須處理內(nèi)邊距的值。這段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())方法。這個(gè)方法的參數(shù)是View.MeasureSpec,它會(huì)告訴你的view的父控件的大小。那些值被包裝成int類型,你可以使用靜態(tài)方法來(lái)獲取其中的信息。

這里是一個(gè)實(shí)現(xiàn)onMeasure()的例子。在這個(gè)例子中PieChart試著使它的區(qū)域足夠大,使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);
}

上面的代碼有三個(gè)重要的事情需要注意:

  • 計(jì)算的過(guò)程有把view的padding考慮進(jìn)去。這個(gè)在后面會(huì)提到,這部分是view所控制的。
  • 幫助方法resolveSizeAndState()是用來(lái)創(chuàng)建最終的寬高值的。這個(gè)方法比較 view 的期望值與傳遞給 onMeasure 方法的 spec 值,然后返回一個(gè)合適的View.MeasureSpec值。
  • onMeasure()沒(méi)有返回值。它通過(guò)調(diào)用setMeasuredDimension()來(lái)獲取結(jié)果。調(diào)用這個(gè)方法是強(qiáng)制執(zhí)行的,如果你遺漏了這個(gè)方法,會(huì)出現(xiàn)運(yùn)行時(shí)異常。

繪圖!

每個(gè)view的onDraw都是不同的,但是有下面一些常見(jiàn)的操作:

  • 繪制文字使用drawText()。指定字體通過(guò)調(diào)用setTypeface(), 通過(guò)setColor()來(lái)設(shè)置文字顏色.
  • 繪制基本圖形使用drawRect(), drawOval(), drawArc(). 通過(guò)setStyle()來(lái)指定形狀是否需要filled, outlined.
  • 繪制一些復(fù)雜的圖形,使用Path類. 通過(guò)給Path對(duì)象添加直線與曲線, 然后使用drawPath()來(lái)繪制圖形. 和基本圖形一樣,paths也可以通過(guò)setStyle來(lái)設(shè)置是outlined, filled, both.
  • 通過(guò)創(chuàng)建LinearGradient對(duì)象來(lái)定義漸變。調(diào)用setShader()來(lái)使用LinearGradient。
  • 通過(guò)使用drawBitmap來(lái)繪制圖片.
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);
}


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)