作者:open-Xu
來(lái)源:CSDN
原文:https://blog.csdn.net/xmxkf/article/details/51454685
版權(quán)聲明:本文為博主原創(chuàng)文章杂靶,轉(zhuǎn)載請(qǐng)附上博文鏈接!
目錄:
繼承View重寫(xiě)onDraw方法
自定義屬性
onMeasure方法
MeasureSpec
分析為什么我們自定義的MyTextView設(shè)置了wrap_content卻填充屏幕
重寫(xiě)onMeasure方法
自動(dòng)換行
源碼下載
對(duì)于一個(gè)Android攻城獅來(lái)說(shuō),自定義控件是一項(xiàng)必須掌握的重要技能點(diǎn)辐益,然而對(duì)于大部分人而言祭示,感覺(jué)自定義控件并不是那么容易肄满。在工作過(guò)程中難免遇到一些特效需要自己定義控件實(shí)現(xiàn),如果你不會(huì),內(nèi)心會(huì)有強(qiáng)烈的挫敗感稠歉,這對(duì)一個(gè)程序員來(lái)說(shuō)是決不能容忍的掰担,接下來(lái)我將寫(xiě)一系列博客,和大家一起學(xué)習(xí)自定義控件怒炸,讓她赤裸裸的站在我們的面前带饱,讓我們?yōu)樗麨椤?:joy:
言歸正傳,接觸到一個(gè)類阅羹,你不太了解他纠炮,如果貿(mào)然翻閱源碼只會(huì)讓你失去方向,不知從哪里下手灯蝴;所以我們應(yīng)該從文檔著手恢口,看看它是個(gè)什么東西,里面有哪些屬性和方法穷躁,都是用來(lái)干嘛的耕肩。下面我們看看官方文檔對(duì)View的介紹:
View這個(gè)類代表用戶界面組件的基本構(gòu)建塊。View在屏幕上占據(jù)一個(gè)矩形區(qū)域问潭,并負(fù)責(zé)繪制和事件處理猿诸。View是用于創(chuàng)建交互式用戶界面組件(按鈕、文本等)的基礎(chǔ)類狡忙。它的子類ViewGroup是所有布局的父類梳虽,它是一個(gè)可以包含其他view或者viewGroup并定義它們的布局屬性的看不見(jiàn)的容器。
實(shí)現(xiàn)一個(gè)自定義View灾茁,你通常會(huì)覆蓋一些framework層在所有view上調(diào)用的標(biāo)準(zhǔn)方法窜觉。你不需要重寫(xiě)所有這些方法。事實(shí)上北专,你可以只是重寫(xiě)onDraw(android.graphics.Canvas)禀挫。
分類 方法 說(shuō)明
構(gòu)造 Constructors 有一種形式的構(gòu)造方法是使用代碼創(chuàng)建的時(shí)候調(diào)用的,另一種形式是View被布局文件填充時(shí)被調(diào)用的拓颓。第二種形式應(yīng)該解析和使用一些屬性定義在布局文件中
onFinishInflate() 當(dāng)View和他的所有子控件被XML布局文件填充完成時(shí)被調(diào)用语婴。(這個(gè)方法里面可以完成一些初始化,比如初始化子控件)
布局 onMeasure(int, int) 當(dāng)決定view和他的孩子的尺寸需求時(shí)被調(diào)用(也就是測(cè)量控件大小時(shí)調(diào)用)
onLayout(boolean, int, int, int, int) 當(dāng)View給他的孩子分配大小和位置的時(shí)候調(diào)用(擺放子控件)
onSizeChanged(int, int, int, int) 當(dāng)view大小發(fā)生變化時(shí)調(diào)用
繪制 onDraw(android.graphics.Canvas) 當(dāng)視圖應(yīng)該呈現(xiàn)其內(nèi)容時(shí)調(diào)用(繪制)
事件處理 onKeyDown(int, KeyEvent) 按鍵時(shí)被調(diào)用
onKeyUp(int, KeyEvent) 按鍵被抬起時(shí)調(diào)用
onTrackballEvent(MotionEvent) Called when a trackball motion event occurs.
onTouchEvent(MotionEvent) 觸摸屏幕時(shí)調(diào)用
焦點(diǎn) onFocusChanged(boolean, int, android.graphics.Rect) 獲取到或者失去焦點(diǎn)是調(diào)用
onWindowFocusChanged(boolean) 窗口獲取或者失去焦點(diǎn)是調(diào)用
Attaching onAttachedToWindow() 當(dāng)視圖被連接到一個(gè)窗口時(shí)調(diào)用
onDetachedFromWindow() 當(dāng)視圖從窗口分離時(shí)調(diào)用
onWindowVisibilityChanged(int) 當(dāng)View的窗口的可見(jiàn)性發(fā)生改變時(shí)調(diào)用
從上面官方文檔介紹我們可以知道驶睦,View是所有控件(包括ViewGroup)的父類砰左,它里面有一些常見(jiàn)的方法(上表),如果我們要自定義View场航,最簡(jiǎn)單的只需要重寫(xiě)onDraw(android.graphics.Canvas)即可缠导,聽(tīng)起來(lái)是不是很簡(jiǎn)單?那我們就動(dòng)手自定義一個(gè)屬于自己的TextView吧旗闽。
1. 繼承View酬核,重寫(xiě)onDraw方法
創(chuàng)建一個(gè)類MyTextView繼承View蜜另,發(fā)現(xiàn)報(bào)錯(cuò),因?yàn)橐采w他的構(gòu)造方法(因?yàn)閂iew中沒(méi)有參數(shù)為空的構(gòu)造方法)嫡意,View有四種形式的構(gòu)造方法举瑰,其中四個(gè)參數(shù)的構(gòu)造方法是API 21才出現(xiàn),所以一般我們只需要重寫(xiě)其他三個(gè)構(gòu)造方法即可蔬螟。它們的參數(shù)不一樣分別對(duì)應(yīng)不同的創(chuàng)建方式此迅,比如只有一個(gè)Context參數(shù)的構(gòu)造方法通常是通過(guò)代碼初始化控件時(shí)使用;而兩個(gè)參數(shù)的構(gòu)造方法通常對(duì)應(yīng)布局文件中控件被映射成對(duì)象時(shí)調(diào)用(需要解析屬性)旧巾;通常我們讓這兩個(gè)構(gòu)造方法最終調(diào)用三個(gè)參數(shù)的構(gòu)造方法耸序,然后在第三個(gè)構(gòu)造方法中進(jìn)行一些初始化操作。
public class MyView extends View {
? ? /**
? ? * 需要繪制的文字
? ? */
? ? private String mText;
? ? /**
? ? * 文本的顏色
? ? */
? ? private int mTextColor;
? ? /**
? ? * 文本的大小
? ? */
? ? private int mTextSize;
? ? /**
? ? * 繪制時(shí)控制文本繪制的范圍
? ? */
? ? private Rect mBound;
? ? private Paint mPaint;
? ? public MyTextView(Context context) {
? ? ? ? this(context, null);
? ? }
? ? public MyTextView(Context context, AttributeSet attrs) {
? ? ? ? this(context, attrs, 0);
? ? }
? ? public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
? ? ? ? super(context, attrs, defStyleAttr);
//初始化
? ? ? ? mText = "Udf32fA";
? ? ? ? mTextColor = Color.BLACK;
? ? ? ? mTextSize = 100;
? ? ? ? mPaint = new Paint();
? ? ? ? mPaint.setTextSize(mTextSize);
? ? ? ? mPaint.setColor(mTextColor);
? ? ? ? //獲得繪制文本的寬和高
? ? ? ? mBound = new Rect();
? ? ? ? mPaint.getTextBounds(mText, 0, mText.length(), mBound);
? ? }
? ? //API21
//? ? public MyTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
//? ? ? ? super(context, attrs, defStyleAttr, defStyleRes);
//? ? ? ? init();
//? ? }
? ? @Override
? ? protected void onDraw(Canvas canvas) {
? ? ? ? //繪制文字
? ? ? ? canvas.drawText(mText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);
? ? }
}
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
? ? xmlns:tools="http://schemas.android.com/tools"
? ? android:layout_width="match_parent"
? ? android:layout_height="match_parent">
? ? ? ? <view.openxu.com.mytextview.MyTextView
? ? ? ? ? ? android:layout_width="200dip"
? ? ? ? ? ? android:layout_height="100dip"
? ? ? ? ? ? android:textSize="20sp"
? ? ? ? ? ? android:text="按鈕"
? ? ? ? ? ? android:background="#ff0000"/>
</LinearLayout>
運(yùn)行結(jié)果:
上面我只是重寫(xiě)了一個(gè)onDraw方法鲁猩,文本已經(jīng)繪制出來(lái)坎怪,說(shuō)明到此為止這個(gè)自定義控件已經(jīng)算成功了±眨可是發(fā)現(xiàn)了一個(gè)問(wèn)題搅窿,如果我要繪制另外的文本呢?比如寫(xiě)i love you隙券,那是不是又得重新定義一個(gè)自定義控件男应?跟上面一樣,只是需要修改mText就可以了娱仔;行沐飘,再寫(xiě)一遍,那如果我現(xiàn)在又想改變文字顏色為藍(lán)色呢牲迫?在寫(xiě)一遍耐朴?這時(shí)候就用到了新的知識(shí)點(diǎn):自定義屬性
2. 自定義屬性
在res/values/下創(chuàng)建一個(gè)名為attrs.xml的文件,然后定義如下屬性:
format的意思是該屬性的取值是什么類型(支持的類型有string,color,demension,integer,enum,reference,float,boolean,fraction,flag)
<?xml version="1.0" encoding="utf-8"?>
<resources>
? ? <attr name="mText" format="string" />
? ? <attr name="mTextColor" format="color" />
? ? <attr name="mTextSize" format="dimension" />
? ? <declare-styleable name="MyTextView">
? ? ? ? <attr name="mText"/>
? ? ? ? <attr name="mTextColor"/>
? ? ? ? <attr name="mTextSize"/>
? ? </declare-styleable>
</resources>
然后在布局文件中使用自定義屬性恩溅,記住一定要引入我們的命名空間xmlns:openxu="http://schemas.android.com/apk/res-auto"
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
? ? xmlns:tools="http://schemas.android.com/tools"
? ? xmlns:openxu="http://schemas.android.com/apk/res-auto"
? ? android:layout_width="match_parent"
? ? android:layout_height="match_parent">
? ? ? ? <view.openxu.com.mytextview.MyTextView
? ? ? ? ? ? android:layout_width="200dip"
? ? ? ? ? ? android:layout_height="100dip"
? ? ? ? ? ? openxu:mTextSize="25sp"
? ? ? ? ? ? openxu:mText="i love you"
? ? ? ? ? ? openxu:mTextColor ="#0000ff"
? ? ? ? ? ? android:background="#ff0000"/>
</LinearLayout>
在構(gòu)造方法中獲取自定義屬性的值:
public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
? ? super(context, attrs, defStyleAttr);
? ? //獲取自定義屬性的值
? ? TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyTextView, defStyleAttr, 0);
? ? mText = a.getString(R.styleable.MyTextView_mText);
? ? mTextColor = a.getColor(R.styleable.MyTextView_mTextColor, Color.BLACK);
? ? mTextSize = a.getDimension(R.styleable.MyTextView_mTextSize, 100);
? ? a.recycle();? //注意回收
? ? mPaint = new Paint();
? ? mPaint.setTextSize(mTextSize);
? ? mPaint.setColor(mTextColor);
? ? //獲得繪制文本的寬和高
? ? mBound = new Rect();
? ? mPaint.getTextBounds(mText, 0, mText.length(), mBound);
}
運(yùn)行結(jié)果:
通過(guò)運(yùn)行結(jié)果隔箍,我們已經(jīng)成功為MyTextView定義了屬性,并獲取到值脚乡,至于自定義屬性的詳細(xì)知識(shí)點(diǎn)到后面會(huì)專門(mén)寫(xiě)一篇博客去介紹。
到此為止滨达,發(fā)現(xiàn)自定義控件還是比較簡(jiǎn)單的嘛奶稠。看看結(jié)果捡遍,跟原生的TextView還有什么差別锌订?接下來(lái)做一點(diǎn)小變化:
讓繪制的文本長(zhǎng)一點(diǎn)openxu:mText="i love you i love you i love you",運(yùn)行結(jié)果:
有沒(méi)有發(fā)現(xiàn)不和諧的現(xiàn)象画株?文本超度超出了控件邊界辆飘,控件太小啦辐,不足以顯示辣么長(zhǎng)的文本,我們將寬高改為wrap_content試試:
什么鬼蜈项?不是包裹內(nèi)容嗎芹关?怎么填充整個(gè)屏幕了?根據(jù)頂部官方文檔的說(shuō)明紧卒,我們猜想肯定是控件的測(cè)量onMeasure方法出了問(wèn)題侥衬,接下來(lái)我們學(xué)習(xí)onMeasure方法。
3. onMeasure方法
①. MeasureSpec
在學(xué)習(xí)onMasure方法之前跑芳,我們要先了解他的參數(shù)中的一個(gè)類MeasureSpec轴总,知己知彼才能百戰(zhàn)百勝 。
跟蹤一下源碼博个,發(fā)現(xiàn)它是View中的一個(gè)靜態(tài)內(nèi)部類怀樟,是由尺寸和模式組合而成的一個(gè)值,用來(lái)描述父控件對(duì)子控件尺寸的約束盆佣,看看他的部分源碼漂佩,一共有三種模式,然后提供了合成和分解的方法:
/**
* measurespec封裝了父控件對(duì)他的孩子的布局要求罪塔。
* 一個(gè)measurespec由大小和模式投蝉。有三種可能的模式:
*/
public static class MeasureSpec {
? ? private static final int MODE_SHIFT = 30;
? ? private static final int MODE_MASK? = 0x3 << MODE_SHIFT;
? ? //父控件不強(qiáng)加任何約束給子控件,它可以是它想要任何大小征堪。
? ? public static final int UNSPECIFIED = 0 << MODE_SHIFT;? //0
? ? //父控件決定給孩子一個(gè)精確的尺寸
? ? public static final int EXACTLY? ? = 1 << MODE_SHIFT;? //1073741824
? ? //父控件會(huì)給子控件盡可能大的尺寸
? ? public static final int AT_MOST? ? = 2 << MODE_SHIFT;? //-2147483648
? ? /**
? ? * 根據(jù)給定的尺寸和模式創(chuàng)建一個(gè)約束規(guī)范
? ? */
? ? public static int makeMeasureSpec(int size, int mode) {
? ? ? ? if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
? ? ? ? ? ? return 0;
? ? ? ? }
? ? ? ? return makeMeasureSpec(size, mode);
? ? }
? ? /**
? ? * 從約束規(guī)范中獲取模式
? ? */
? ? public static int getMode(int measureSpec) {
? ? ? ? return (measureSpec & MODE_MASK);
? ? }
? ? /**
? ? * 從約束規(guī)范中獲取尺寸
? ? */
? ? public static int getSize(int measureSpec) {
? ? ? ? return (measureSpec & ~MODE_MASK);
? ? }
}
這樣說(shuō)起來(lái)還是有點(diǎn)抽象瘩缆,舉一個(gè)小栗子大家就知道這三種約束到底是什么意思。我們自定義一個(gè)View佃蚜,為了方便起見(jiàn)庸娱,讓它繼承Button,布局文件中設(shè)置不同的寬高條件谐算,然后在onMeasure方法中打印一下他的參數(shù)(int widthMeasureSpec, int heightMeasureSpec)到底是個(gè)什么鬼
/**
* Created by openXu on 16/5/19.
*/
public class MyView extends Button {
? ? public MyView(Context context) {
? ? ? ? this(context, null);
? ? }
? ? public MyView(Context context, AttributeSet attrs) {
? ? ? ? this(context, attrs, 0);
? ? }
? ? public MyView(Context context, AttributeSet attrs, int defStyle) {
? ? ? ? super(context, attrs, defStyle);
? ? }
? ? @Override
? ? protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
? ? ? ? super.onMeasure(widthMeasureSpec, heightMeasureSpec);
? ? ? ? int widthMode = MeasureSpec.getMode(widthMeasureSpec);? //獲取寬的模式
? ? ? ? int heightMode = MeasureSpec.getMode(heightMeasureSpec); //獲取高的模式
? ? ? ? int widthSize = MeasureSpec.getSize(widthMeasureSpec);? //獲取寬的尺寸
? ? ? ? int heightSize = MeasureSpec.getSize(heightMeasureSpec); //獲取高的尺寸
? ? ? ? Log.v("openxu", "寬的模式:"+widthMode);
? ? ? ? Log.v("openxu", "高的模式:"+heightMode);
? ? ? ? Log.v("openxu", "寬的尺寸:"+widthSize);
? ? ? ? Log.v("openxu", "高的尺寸:"+heightSize);
? ? }
}
情形1熟尉,讓按鈕包裹內(nèi)容:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
? ? xmlns:tools="http://schemas.android.com/tools"
? ? android:layout_width="match_parent"
? ? android:layout_height="match_parent">
? ? ? ? <com.example.openxu.myview.MyView
? ? ? ? ? ? android:layout_width="wrap_content"
? ? ? ? ? ? android:layout_height="wrap_content"
? ? ? ? ? ? android:textSize="20sp"
? ? ? ? ? ? android:text="按鈕"
? ? ? ? ? ? android:background="#ff0000"/>
</LinearLayout>
log打印:
05-19 06:02:55.112: V/openxu(15599): 寬的模式:-2147483648
05-19 06:02:55.112: V/openxu(15599): 高的模式:-2147483648
05-19 06:02:55.112: V/openxu(15599): 寬的尺寸:1080
05-19 06:02:55.112: V/openxu(15599): 高的尺寸:1860
情形2洲脂,讓按鈕填充父窗體:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
? ? xmlns:tools="http://schemas.android.com/tools"
? ? android:layout_width="match_parent"
? ? android:layout_height="match_parent">
? ? ? ? <com.example.openxu.myview.MyView
? ? ? ? ? ? android:layout_width="match_parent"
? ? ? ? ? ? android:layout_height="match_parent"
? ? ? ? ? ? android:textSize="20sp"
? ? ? ? ? ? android:text="按鈕"
? ? ? ? ? ? android:background="#ff0000"/>
</LinearLayout>
log打咏锒:
05-19 06:05:37.302: V/openxu(15960): 寬的模式:1073741824
05-19 06:05:37.302: V/openxu(15960): 高的模式:1073741824
05-19 06:05:37.302: V/openxu(15960): 寬的尺寸:1080
05-19 06:05:37.302: V/openxu(15960): 高的尺寸:1860
情形3,給按鈕的寬設(shè)置為具體的值:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
? ? xmlns:tools="http://schemas.android.com/tools"
? ? android:layout_width="match_parent"
? ? android:layout_height="match_parent">
? ? ? ? <com.example.openxu.myview.MyView
? ? ? ? ? ? android:layout_width="100dip"
? ? ? ? ? ? android:layout_height="wrap_content"
? ? ? ? ? ? android:textSize="20sp"
? ? ? ? ? ? android:text="按鈕"
? ? ? ? ? ? android:background="#ff0000"/>
</LinearLayout>
log打涌纸酢:
05-19 06:07:48.932: V/openxu(16105): 寬的模式:1073741824
05-19 06:07:48.932: V/openxu(16105): 高的模式:-2147483648
05-19 06:07:48.932: V/openxu(16105): 寬的尺寸:300
05-19 06:07:48.932: V/openxu(16105): 高的尺寸:1860
根據(jù)上面的測(cè)試往果,我們發(fā)現(xiàn),約束中分離出來(lái)的尺寸就是父控件剩余的寬高大幸磺Α(除了設(shè)置具體的寬高值外)陕贮;而幾種約束中的模式不就是對(duì)應(yīng)我們?cè)诓季治募性O(shè)置給按鈕的幾種情況嗎?如下:
約束 布局參數(shù) 輸出值 說(shuō)明
UNSPECIFIED(未指定) 0 父控件沒(méi)有對(duì)子控件施加任何約束潘飘,子控件可以得到任意想要的大小肮之。但是布局文件好像必須設(shè)置寬高掉缺,目前還沒(méi)找到與之對(duì)應(yīng)的布局參數(shù),使用較少
EXACTLY(完全) match_parent/具體寬高值 1073741824 父控件給子控件決定了確切大小戈擒,子控件將被限定在給定的邊界里而忽略它本身大小眶明。特別說(shuō)明如果是填充父窗體,說(shuō)明父控件已經(jīng)明確知道子控件想要多大的尺寸了(就是剩余的空間都要了)
AT_MOST(至多) wrap_content -2147483648 子控件至多達(dá)到指定大小的值峦甩。包裹內(nèi)容就是父窗體并不知道子控件到底需要多大尺寸(具體值)赘来,需要子控件自己測(cè)量之后再讓父控件給他一個(gè)盡可能大的尺寸以便讓內(nèi)容全部顯示但不能超過(guò)包裹內(nèi)容的大小
②. 分析為什么我們自定義的MyTextView設(shè)置了wrap_content卻填充屏幕
通過(guò)上面對(duì)MeasureSpec的了解,我們現(xiàn)在就有能看懂View的onMeasure方法默認(rèn)是怎樣為控件測(cè)量大小的了
看View中onMeasure的源碼:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
? ? setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
? ? ? ? ? ? getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
? ? int result = size;
? ? int specMode = MeasureSpec.getMode(measureSpec);
? ? int specSize = MeasureSpec.getSize(measureSpec);
? ? switch (specMode) {
? ? case MeasureSpec.UNSPECIFIED:
? ? ? ? result = size;
? ? ? ? break;
? ? case MeasureSpec.AT_MOST:
? ? case MeasureSpec.EXACTLY:
? ? ? ? result = specSize;
? ? ? ? break;
? ? }
? ? return result;
}
onMeasure方法調(diào)用了setMeasuredDimension(int measuredWidth, int measuredHeight)方法凯傲,而傳入的參數(shù)已經(jīng)是測(cè)量過(guò)的默認(rèn)寬和高的值了犬辰;我們看看getDefaultSize 方法是怎么計(jì)算測(cè)量寬高的。根據(jù)父控件給予的約束冰单,發(fā)現(xiàn)AT_MOST (相當(dāng)于wrap_content )和EXACTLY (相當(dāng)于match_parent )兩種情況返回的測(cè)量寬高都是specSize幌缝,而這個(gè)specSize正是我們上面說(shuō)的父控件剩余的寬高,所以默認(rèn)onMeasure方法中wrap_content 和match_parent 的效果是一樣的诫欠,都是填充剩余的空間涵卵。
③. 重寫(xiě)onMeasure方法
我們先忽略掉UNSPECIFIED 的情況(使用極少),只考慮AT_MOST 和EXACTLY 荒叼,現(xiàn)在的問(wèn)題是設(shè)置wrap_content 時(shí)轿偎,控件卻使用了match_parent 的效果,看下面怎么重寫(xiě)onMeasure(注釋比較詳細(xì)被廓,不做過(guò)多講解):
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
? ? super.onMeasure(widthMeasureSpec, heightMeasureSpec);
? ? int widthMode = MeasureSpec.getMode(widthMeasureSpec);? //獲取寬的模式
? ? int heightMode = MeasureSpec.getMode(heightMeasureSpec); //獲取高的模式
? ? int widthSize = MeasureSpec.getSize(widthMeasureSpec);? //獲取寬的尺寸
? ? int heightSize = MeasureSpec.getSize(heightMeasureSpec); //獲取高的尺寸
? ? Log.v("openxu", "寬的模式:"+widthMode);
? ? Log.v("openxu", "高的模式:"+heightMode);
? ? Log.v("openxu", "寬的尺寸:"+widthSize);
? ? Log.v("openxu", "高的尺寸:"+heightSize);
? ? int width;
? ? int height ;
? ? if (widthMode == MeasureSpec.EXACTLY) {
? ? ? ? //如果match_parent或者具體的值坏晦,直接賦值
? ? ? ? width = widthSize;
? ? } else {
? ? ? ? //如果是wrap_content,我們要得到控件需要多大的尺寸
? ? ? ? float textWidth = mBound.width();? //文本的寬度
? ? ? ? //控件的寬度就是文本的寬度加上兩邊的內(nèi)邊距嫁乘。內(nèi)邊距就是padding值昆婿,在構(gòu)造方法執(zhí)行完就被賦值
? ? ? ? width = (int) (getPaddingLeft() + textWidth + getPaddingRight());
? ? ? ? Log.v("openxu", "文本的寬度:"+textWidth + "控件的寬度:"+width);
? ? }
? ? //高度跟寬度處理方式一樣
? ? if (heightMode == MeasureSpec.EXACTLY) {
? ? ? ? height = heightSize;
? ? } else {
? ? ? ? float textHeight = mBound.height();
? ? ? ? height = (int) (getPaddingTop() + textHeight + getPaddingBottom());
? ? ? ? Log.v("openxu", "文本的高度:"+textHeight + "控件的高度:"+height);
? ? }
? ? //保存測(cè)量寬度和測(cè)量高度
? ? setMeasuredDimension(width, height);
}
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
? ? xmlns:tools="http://schemas.android.com/tools"
? ? xmlns:openxu="http://schemas.android.com/apk/res-auto"
? ? android:layout_width="match_parent"
? ? android:layout_height="match_parent">
? ? ? ? <view.openxu.com.mytextview.MyTextView
? ? ? ? ? ? android:layout_width="wrap_content"
? ? ? ? ? ? android:layout_height="wrap_content"
? ? ? ? ? ? android:padding="20dip"
? ? ? ? ? ? openxu:mTextSize="25sp"
? ? ? ? ? ? openxu:mText="i love you i love you i love you"
? ? ? ? ? ? openxu:mTextColor ="#0000ff"
? ? ? ? ? ? android:background="#ff0000"/>
</LinearLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
下面是輸出的log:
05-19 01:29:12.662 27380-27380/view.openxu.com.mytextview V/openxu: 寬的模式:-2147483648
05-19 01:29:12.662 27380-27380/view.openxu.com.mytextview V/openxu: 高的模式:-2147483648
05-19 01:29:12.662 27380-27380/view.openxu.com.mytextview V/openxu: 寬的尺寸:720
05-19 01:29:12.666 27380-27380/view.openxu.com.mytextview V/openxu: 高的尺寸:1230
05-19 01:29:12.678 27380-27380/view.openxu.com.mytextview V/openxu: 文本的寬度:652.0控件的寬度:732
05-19 01:29:12.690 27380-27380/view.openxu.com.mytextview V/openxu: 文本的高度:49.0控件的高度:129
我的模擬器是720x1280的,根據(jù)log顯示蜓斧,文本的寬度是652仓蛆,加上兩邊的內(nèi)邊距,控件的寬度為732挎春,確實(shí)實(shí)現(xiàn)了包裹內(nèi)容的效果看疙,運(yùn)行程序結(jié)果如下:
但是發(fā)現(xiàn)寬度已經(jīng)超出了屏幕,還不能像TextView一樣換行搂蜓;下面我們簡(jiǎn)單的模擬一下?lián)Q行的功能狼荞,做的不夠好,但有這個(gè)效果帮碰,不是重點(diǎn),不需要重點(diǎn)掌握
4. 自動(dòng)換行
只需要在測(cè)量的時(shí)候拾积,根據(jù)文字的總長(zhǎng)度和控件的寬度殉挽,就可以知道需要繪制幾行丰涉,然后將文本分割成小段放入集合中,在onDraw方法中分別繪制斯碌;
需要注意的是一死,onMeasure方法不只調(diào)用一次,所以在分段文本是需要判斷傻唾,不要重復(fù)分段投慈,否則會(huì)報(bào)錯(cuò)。代碼如下(僅供參考):
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import java.util.ArrayList;
/**
* Created by openXu on 16/5/19.
*/
public class MyTextView extends View {
? ? /**
? ? * 需要繪制的文字
? ? */
? ? private String mText;
? ? private ArrayList<String> mTextList;
? ? /**
? ? * 文本的顏色
? ? */
? ? private int mTextColor;
? ? /**
? ? * 文本的大小
? ? */
? ? private float mTextSize;
? ? /**
? ? * 繪制時(shí)控制文本繪制的范圍
? ? */
? ? private Rect mBound;
? ? private Paint mPaint;
? ? public MyTextView(Context context) {
? ? ? ? this(context, null);
? ? }
? ? public MyTextView(Context context, AttributeSet attrs) {
? ? ? ? this(context, attrs, 0);
? ? }
? ? public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
? ? ? ? super(context, attrs, defStyleAttr);
? ? ? ? mTextList = new ArrayList<String>();
? ? ? ? //獲取自定義屬性的值
? ? ? ? TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyTextView, defStyleAttr, 0);
? ? ? ? mText = a.getString(R.styleable.MyTextView_mText);
? ? ? ? mTextColor = a.getColor(R.styleable.MyTextView_mTextColor, Color.BLACK);
? ? ? ? mTextSize = a.getDimension(R.styleable.MyTextView_mTextSize, 100);
? ? ? ? a.recycle();? //注意回收
? ? ? ? Log.v("openxu", "文本總長(zhǎng)度:"+mText);
? ? ? ? mPaint = new Paint();
? ? ? ? mPaint.setTextSize(mTextSize);
? ? ? ? mPaint.setColor(mTextColor);
? ? ? ? //獲得繪制文本的寬和高
? ? ? ? mBound = new Rect();
? ? ? ? mPaint.getTextBounds(mText, 0, mText.length(), mBound);
? ? }
? ? //API21
//? ? public MyTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
//? ? ? ? super(context, attrs, defStyleAttr, defStyleRes);
//? ? ? ? init();
//? ? }
? ? @Override
? ? protected void onDraw(Canvas canvas) {
? ? ? ? //繪制文字
? ? ? ? for(int i = 0; i<mTextList.size(); i++){
? ? ? ? ? ? mPaint.getTextBounds(mTextList.get(i), 0, mTextList.get(i).length(), mBound);
? ? ? ? ? ? Log.v("openxu", "mBound.h:"+mBound.height());
? ? ? ? ? ? Log.v("openxu", "在X:" + (getWidth() / 2 - mBound.width() / 2)+"? Y:"+(getPaddingTop() + (mBound.height() *i))+"? 繪制:"+mTextList.get(i));
? ? ? ? ? ? canvas.drawText(mTextList.get(i), (getWidth() / 2 - mBound.width() / 2), (getPaddingTop() + (mBound.height() *i)), mPaint);
? ? ? ? }
? ? }
? ? boolean isOneLines = true;
? ? float lineNum;
? ? float spLineNum;
? ? @Override
? ? protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
? ? ? ? super.onMeasure(widthMeasureSpec, heightMeasureSpec);
? ? ? ? int widthMode = MeasureSpec.getMode(widthMeasureSpec);? //獲取寬的模式
? ? ? ? int heightMode = MeasureSpec.getMode(heightMeasureSpec); //獲取高的模式
? ? ? ? int widthSize = MeasureSpec.getSize(widthMeasureSpec);? //獲取寬的尺寸
? ? ? ? int heightSize = MeasureSpec.getSize(heightMeasureSpec); //獲取高的尺寸
? ? ? ? Log.v("openxu", "寬的模式:"+widthMode);
? ? ? ? Log.v("openxu", "高的模式:"+heightMode);
? ? ? ? Log.v("openxu", "寬的尺寸:"+widthSize);
? ? ? ? Log.v("openxu", "高的尺寸:"+heightSize);
? ? ? ? float textWidth = mBound.width();? //文本的寬度
? ? ? ? if(mTextList.size()==0){
? ? ? ? ? ? //將文本分段
? ? ? ? ? ? int padding = getPaddingLeft() + getPaddingRight();
? ? ? ? ? ? int specWidth = widthSize - padding; //能夠顯示文本的最大寬度
? ? ? ? ? ? if(textWidth<specWidth){
? ? ? ? ? ? ? ? //說(shuō)明一行足矣顯示
? ? ? ? ? ? ? ? lineNum = 1;
? ? ? ? ? ? ? ? mTextList.add(mText);
? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? //超過(guò)一行
? ? ? ? ? ? ? ? isOneLines = false;
? ? ? ? ? ? ? ? spLineNum = textWidth/specWidth;
? ? ? ? ? ? ? ? if((spLineNum+"").contains(".")){
? ? ? ? ? ? ? ? ? ? lineNum = Integer.parseInt((spLineNum+"").substring(0,(spLineNum+"").indexOf(".") ))+1;
? ? ? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? ? ? lineNum = spLineNum;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? int lineLength = (int)(mText.length()/spLineNum);
? ? ? ? ? ? ? ? Log.v("openxu", "文本總長(zhǎng)度:"+mText);
? ? ? ? ? ? ? ? Log.v("openxu", "文本總長(zhǎng)度:"+mText.length());
? ? ? ? ? ? ? ? Log.v("openxu", "能繪制文本的寬度:"+lineLength);
? ? ? ? ? ? ? ? Log.v("openxu", "需要繪制:"+lineNum+"行");
? ? ? ? ? ? ? ? Log.v("openxu", "lineLength:"+lineLength);
? ? ? ? ? ? ? ? for(int i = 0; i<lineNum; i++){
? ? ? ? ? ? ? ? ? ? String lineStr;
? ? ? ? ? ? ? ? ? ? if(mText.length()<lineLength){
? ? ? ? ? ? ? ? ? ? ? ? lineStr = mText.substring(0, mText.length());
? ? ? ? ? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? ? ? ? ? lineStr = mText.substring(0, lineLength);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? Log.v("openxu", "lineStr:"+lineStr);
? ? ? ? ? ? ? ? ? ? mTextList.add(lineStr);
? ? ? ? ? ? ? ? ? ? if(!TextUtils.isEmpty(mText)) {
? ? ? ? ? ? ? ? ? ? ? ? if(mText.length()<lineLength){
? ? ? ? ? ? ? ? ? ? ? ? ? ? mText = mText.substring(0, mText.length());
? ? ? ? ? ? ? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? ? ? ? ? ? ? mText = mText.substring(lineLength, mText.length());
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? int width;
? ? ? ? int height ;
? ? ? ? if (widthMode == MeasureSpec.EXACTLY) {
? ? ? ? ? ? //如果match_parent或者具體的值冠骄,直接賦值
? ? ? ? ? ? width = widthSize;
? ? ? ? } else {
? ? ? ? ? ? //如果是wrap_content伪煤,我們要得到控件需要多大的尺寸
? ? ? ? ? ? if(isOneLines){
? ? ? ? ? ? ? ? //控件的寬度就是文本的寬度加上兩邊的內(nèi)邊距。內(nèi)邊距就是padding值凛辣,在構(gòu)造方法執(zhí)行完就被賦值
? ? ? ? ? ? ? ? width = (int) (getPaddingLeft() + textWidth + getPaddingRight());
? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? //如果是多行抱既,說(shuō)明控件寬度應(yīng)該填充父窗體
? ? ? ? ? ? ? ? width = widthSize;
? ? ? ? ? ? }
? ? ? ? ? ? Log.v("openxu", "文本的寬度:"+textWidth + "控件的寬度:"+width);
? ? ? ? }
? ? ? ? //高度跟寬度處理方式一樣
? ? ? ? if (heightMode == MeasureSpec.EXACTLY) {
? ? ? ? ? ? height = heightSize;
? ? ? ? } else {
? ? ? ? ? ? float textHeight = mBound.height();
? ? ? ? ? ? if(isOneLines){
? ? ? ? ? ? ? ? height = (int) (getPaddingTop() + textHeight + getPaddingBottom());
? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? //如果是多行
? ? ? ? ? ? ? ? height = (int) (getPaddingTop() + textHeight*lineNum + getPaddingBottom());;
? ? ? ? ? ? }
? ? ? ? ? ? Log.v("openxu", "文本的高度:"+textHeight + "控件的高度:"+height);
? ? ? ? }
? ? ? ? //保存測(cè)量寬度和測(cè)量高度
? ? ? ? setMeasuredDimension(width, height);
? ? }
}
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
? ? xmlns:tools="http://schemas.android.com/tools"
? ? xmlns:openxu="http://schemas.android.com/apk/res-auto"
? ? android:layout_width="match_parent"
? ? android:layout_height="match_parent">
? ? ? ? <view.openxu.com.mytextview.MyTextView
? ? ? ? ? ? android:layout_width="wrap_content"
? ? ? ? ? ? android:layout_height="wrap_content"
? ? ? ? ? ? android:padding="20dip"
? ? ? ? ? ? openxu:mTextSize="25sp"
? ? ? ? ? ? openxu:mText="門(mén)前大橋下,游過(guò)一群鴨扁誓,快來(lái)快來(lái)數(shù)一數(shù)防泵,二四六七八。帽兒破蝗敢,鞋兒破捷泞,身上滴袈裟破,你笑我"
? ? ? ? ? ? openxu:mTextColor ="#0000ff"
? ? ? ? ? ? android:background="#ff0000"/>
</LinearLayout>
運(yùn)行效果:
到此為止寿谴,我們已經(jīng)了解到自定義控件的基本步驟:
1. 繼承View锁右,重寫(xiě)構(gòu)造方法
2. 自定義屬性,在構(gòu)造方法中初始化屬性
3. 重寫(xiě)onMeasure方法測(cè)量寬高
4. 重寫(xiě)onDraw方法繪制控件
各位看官拭卿,看到這里辛苦了骡湖,順便留個(gè)言、點(diǎn)個(gè)贊唄~ ~謝過(guò)了
源碼下載:
https://github.com/openXu/MyTextView
---------------------
作者:open-Xu
來(lái)源:CSDN
原文:https://blog.csdn.net/xmxkf/article/details/51454685
版權(quán)聲明:本文為博主原創(chuàng)文章峻厚,轉(zhuǎn)載請(qǐng)附上博文鏈接响蕴!