編輯:關於Android編程
對於很多Android入門程序猿來說自定義View,都是比較恐懼的,但是這又是高手進階的必經之路。先總結下自定義View的步驟:
1、自定義View的屬性
2、在View的構造方法中獲得我們自定義的屬性
[ 3、重寫onMesure ]
4、重寫onDraw
我把3用[]標出了,所以說3不一定是必須的,當然了大部分情況下還是需要重寫的。
其中第1,第2點在前面的文章已經有詳細的介紹Android自定義屬性,不了解的童鞋可以去看看參考下,本文著重介紹第3和第4點。
onMeasure()
onMeasure()方法顧名思義就是用於測量視圖的大小的。它接收兩個參數,widthMeasureSpec和heightMeasureSpec,這兩個值分別用於確定視圖的寬度和高度的規格和大小。
MeasureSpec的值由specSize和specMode共同組成的,其中specSize記錄的是大小,specMode記錄的是規格。specMode一共有三種類型,如下所示:
表示父視圖希望子視圖的大小應該是由specSize的值來決定的,系統默認會按照這個規則來設置子視圖的大小,開發人員當然也可以按照自己的意願設置成任意的大小。(一般是在布局中設置了明確的值或者是MATCH_PARENT)
AT_MOST表示子視圖最多只能是specSize中指定的大小,開發人員應該盡可能小得去設置這個視圖,並且保證不會超過specSize。系統默認會按照這個規則來設置子視圖的大小,開發人員當然也可以按照自己的意願設置成任意的大小。(一般為WARP_CONTENT)
UNSPECIFIED表示開發人員可以將視圖按照自己的意願設置成任意的大小,沒有任何限制。這種情況比較少見,不太會用到。
那麼你可能會有疑問了,widthMeasureSpec和heightMeasureSpec這兩個值又是從哪裡得到的呢?通常情況下,這兩個值都是由父視圖經過計算後傳遞給子視圖的,說明父視圖會在一定程度上決定子視圖的大小。
當然,onMeasure()方法是可以重寫的,也就是說,如果你不想使用系統默認的測量方式,可以按照自己的意願進行定制,比如:
public class MyView extends View { ...... @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(200, 200); } }
這樣的話就把View默認的測量流程覆蓋掉了,不管在布局文件中定義MyView這個視圖的大小是多少,最終在界面上顯示的大小都將會是200*200。
需要注意的是,在setMeasuredDimension()方法調用之後,我們才能使用getMeasuredWidth()和getMeasuredHeight()來獲取視圖測量出的寬高,以此之前調用這兩個方法得到的值都會是0(注意區別於而getWidth()和getHeight(),這兩個方法要在ViewGroup的onLayout()過程結束後才能獲取到)。
由此可見,視圖大小的控制是由父視圖、布局文件、以及視圖本身共同完成的,父視圖會提供給子視圖參考的大小,而開發人員可以在XML文件中指定視圖的大小,然後視圖本身會對最終的大小進行拍板。
onDraw()
measure和layout的過程都結束後,接下來就進入到draw的過程了。同樣,根據名字你就能夠判斷出,在這裡才真正地開始對視圖進行繪制。
好了,原理性東西我們講過了,下面通過幾個簡單的例子來驗證一下,同樣按上面四步寫法:
1自定義View的屬性,首先在res/values/ 下建立一個attrs.xml , 在裡面定義我們的屬性和聲明我們的整個樣式。
然後在布局中聲明我們的自定義View,一定要引入我們的命名空間xmlns:custom=”http://schemas.android.com/apk/res/com.hx.test”
2
在View的構造方法中,獲得我們的自定義的屬性,我們重寫了3個構造方法,默認的布局文件調用的是兩個參數的構造方法,所以記得讓所有的構造調用我們的三個參數的構造,我們在三個參數的構造中獲得自定義屬性。
public CustomTextView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CustomTextView(Context context) { this(context, null); } /** * 獲得我自定義的樣式屬性 * * @param context * @param attrs * @param defStyle */ public CustomTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); /** * 獲得我們所定義的自定義樣式屬性 */ TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTextView, defStyle, 0); int n = a.getIndexCount(); for (int i = 0; i < n; i++) { int attr = a.getIndex(i); switch (attr) { case R.styleable.CustomTextView_titleText: mTitleText = a.getString(attr); break; case R.styleable.CustomTextView_titleTextColor: mTitleTextColor = a.getColor(attr, Color.BLACK); break; case R.styleable.CustomTextView_titleTextSize: // 默認設置為16sp,TypeValue也可以把sp轉化為px mTitleTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics())); break; } } a.recycle(); /** * 獲得繪制文本的寬和高 */ mPaint = new Paint(); mPaint.setTextSize(mTitleTextSize); mPaint.setColor(mTitleTextColor); mBound = new Rect(); mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound); }3
我們重寫onDraw,onMesure調用系統提供的:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onDraw(Canvas canvas) { mPaint.setColor(Color.GREEN); canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint); mPaint.setColor(mTitleTextColor); canvas.drawText(mTitleText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint); }
運行效果圖:
這已經基本已經實現了自定義View。但是此時如果我們把布局文件的寬和高寫成wrap_content,會發現效果並不是我們的預期:
這是因為MATCH_PARENT 時系統返回的specMode的值為AT_MOST,而specSize為全屏,子視圖默認占據了specSize中指定大小的范圍。這時需要我們重寫onDraw來實現自己的邏輯。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int width; int height ; if (widthMode == MeasureSpec.EXACTLY) { width = widthSize; } else { mPaint.setTextSize(mTitleTextSize); mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound); float textWidth = mBound.width(); int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight()); width = desired; } if (heightMode == MeasureSpec.EXACTLY) { height = heightSize; } else { mPaint.setTextSize(mTitleTextSize); mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound); float textHeight = mBound.height(); int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom()); height = desired; } setMeasuredDimension(width, height); }
我們再來修改下布局文件
效果如下:
完全復合我們的預期,現在我們可以對高度、寬度進行隨便的設置了,基本可以滿足我們的需求。我們還可以在構造函數中對它的點擊事件做一些處理:
this.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mTitleText = randomText(); postInvalidate(); //invalidate(); } });
private String randomText() { Random random = new Random(); int randomInt = random.nextInt(5); return Str[randomInt]; }
其中
private String[] Str = {"Text0", "Text1", "Text2", "Text3", "Text4"};
可以看到點擊事件已經生效了,每次當我們點擊自定義View時顯示的文字內容都發生變化。調用postInvalidate()就是讓View重新執行onDraw,其實這裡也可以使用invalidate(),都能生效。他們區別在於:
invalidate()得在UI線程中被調動,在工作者線程中可以通過Handler來通知UI線程進行界面更新。而postInvalidate()可以在工作者線程中被調用。
但是還有一個問題,我們看到點擊後自定義View的文字變短了,但自定義View的寬卻沒有相應變窄,這不符合我們寫的WRAP_CONTENT的思想。其實這是由於postInvalidate()只是強制View重新走onDraw,而View的寬高是在onMesure中定義的,所以文字改變後需要強制自定義View也要走onMesure,重新測量,繼續修改代碼:
this.setOnClickListener(new OnClickListener() { @Override public void onClick(View v){ mTitleText = randomText(); requestLayout(); } });
完美實現我們的構想。總結一下RequestLayout用法:
RequestLayout:
當view確定自身已經不再適合現有的區域時,該view本身調用這個方法要求parent view重新調用他的onMeasure onLayout來對重新設置自己位置。
特別的當view的layoutparameter發生改變,並且它的值還沒能應用到view上,這時候適合調用這個方法。也就是當通過getLayoutParrms().width = XXX的時候,我們需要重新調用RequestLayout。
invalidate:
View類調用迫使view重畫(onDraw)。
很多的Android入門程序猿來說對於Android自定義View,可能都是比較恐懼的,但是這又是高手進階的必經之路,所有准備在自定義View上面花一些功夫,多寫一些文章
自繪制Android界面核心類圖。入門Android時,會看到過一張系統架構圖,從那張圖可以知道Android系統自上到下被劃分了幾個層次(具體每個層次的職責定義不再概述
本文實例介紹了Android實現粒子雨效果的實現過程,分享給大家供大家參考,具體內容如下先看看效果圖:具體實現方法:1.baseview主要是設定雨滴要實現的動作,只是先
前言支持多媒體是Android設備的一個重要功能,可以想象一台不支持多媒體的設備是何等枯燥。通常意義上的多媒體(Multimedia)通常是指圖片、視頻、音頻、文本等等。