Android自绘制控件
開發過程中,我們免不了需要用到一些自定義的 View,自定義 View 一般可分為三類:
① 繼承類 View —— 一般繼承系統以后的基本 View,新增/重置一些自定義屬性 ,例如兩端對齊的TextView;
②?組合類 View —— 將系統某幾個基本View組合在一起形成一個新的View,例如末尾帶 ”ד(清空) 的EditText,就是將EditText和ImageView組合在一起來實現;?
③?自繪制 View —— 某些特殊的設計控件,無法通過上兩種方式實現時,我們就需要考慮通過自繪制來進行處理,本篇我們將著重介紹此類 View 的實現過程。
?
下面我們通過自定義一個圓形的Button(DCircleButton)來進行說明:
自定義View的步驟:
?① 自定義 View 的屬性;
?② 在自定義 View 的構造方法中獲取 View 的屬性值;
?③ 重寫測量尺寸的方法 onMeasure(int, int); (是否需要重寫根據具體根據需求);
?④ 重寫繪制方法 onDraw(Canvas c);?
?⑤ 在布局XML文件中,使用自定義 View 的屬性。
?
1. 自定義 View 的屬性:
在目錄 res/values 下新建 attrs.xml 屬性文件。
<?xml version="1.0" encoding="utf-8"?> <resources><!--name 是自定義控件的類名--><declare-styleable name="DCircleButton" parent="android.widget.Button"><attr name="txtSize" format="dimension"/><attr name="text" format="string"/><attr name="txtColor" format="color"/><attr name="txtBackgroundColor" format="color"/></declare-styleable> </resources>?
自定義屬性分兩步: ① 定義控件的主題樣式;②?定義屬性名稱及類型。
如上面的 xml 文件是自定義控件DCirclebutton的主題樣式,主題樣式里為屬性定義,有些人可能會糾結format字段后面都有哪些屬性單位?如果你是使用AS開發的話IDE會自動有提示,基本包括如下:
dimension(字體大小)string(字符串)color(顏色)boolean(布爾類型)float(浮點型)integer(整型)enmu(枚舉)fraction(百分比)等。
2. ?在構造方法中獲取屬性值,并繪制
第一步:繼承View,實現(AS會提示)以下四種,
public DCircleButton(Context context) {super(context); } public DCircleButton(Context context, @Nullable AttributeSet attrs) {super(context, attrs); } public DCircleButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public DCircleButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes); }第二步,改寫這四種構造,讓其逐級遞進:
public DCircleButton(Context context) {super(context, null); } public DCircleButton(Context context, AttributeSet attrs) {super(context, attrs, 0); } public DCircleButton(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr, 0); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public DCircleButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes); }第三步,我們在最后一個方法獲取屬性值:
private void initAttrs(Context context, AttributeSet attrs) {TypedArray tArr = context.obtainStyledAttributes(attrs, R.styleable.DCircleButton);if (null != tArr) {txtColor = tArr.getColor(R.styleable.DCircleButton_txtColor, Color.BLACK); // 獲取文字顏色txtSize = tArr.getDimensionPixelSize(R.styleable.DCircleButton_txtSize, 18); // 獲取文字大小txt = tArr.getString(R.styleable.DCircleButton_text); // 獲取文字內容backgroundColor = tArr.getColor(R.styleable.DCircleButton_txtBackgroundColor, Color.GRAY); // 獲取文字背景顏色 tArr.recycle();} }第四步,繪制
/** 字體顏色 **/ private int txtColor; /** 字體背景顏色 **/ private int backgroundColor; /** 字體大小 **/ private int txtSize; /** 按鈕文字內容 **/ private String txt; /** 圓半徑 **/ private float mDrawableRadius;/** 字體背景畫筆 **/ private Paint mBackgroundPaint; /** 字體畫筆 **/ private Paint mTxtPaint;public DCircleButton(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initAttrs(context, attrs);init(); } /** 初始化 **/ private void init() {mBackgroundPaint = new Paint();mBackgroundPaint.setColor(backgroundColor);mTxtPaint = new Paint();mTxtPaint.setTextAlign(Paint.Align.CENTER);mTxtPaint.setColor(txtColor);mTxtPaint.setTextSize(txtSize); }@Override protected void onDraw(Canvas canvas) {super.onDraw(canvas);mDrawableRadius = Math.min(getWidth() >> 1, getHeight() >> 1);canvas.drawCircle(getWidth() >> 1, getHeight() >> 1, mDrawableRadius, mBackgroundPaint);if (null != txt)canvas.drawText(txt, getWidth() >> 1, getHeight() >> 1, mTxtPaint); }?3. 布局中應用
<dinn.circle.button.DCircleButtonandroid:layout_width="wrap_content"android:layout_height="wrap_content"app:text="我是按鈕"app:txtBackgroundColor="@android:color/holo_orange_dark"app:txtColor="@android:color/white"app:txtSize="20sp" />?4. 運行結果
?
這個時候回發現按鈕是充滿屏幕的,但是布局中我們設置的尺寸屬性為“wrap_content”。其實是由于我們在自定義View的流程中還有一個onMeasure方法沒有重寫。
5. 重寫onMeasure控制View的大小
當你沒有重寫onMeasure方法時候,系統調用默認的onMeasure方法。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec); }這個方法的作用是:測量控件的大小。其實Android系統在加載布局的時候是由系統測量各子View的大小來告訴父View我需要占多大空間,然后父View會根據自己的大小來決定分配多大空間給子View。
那么從上面的效果來看,當你在布局中設置View的大小為”wrap_content”時,其實系統測量出來的大小是“match_parent”。為什么會是這樣子呢?
那得從MeasureSpec的specMode模式說起了。一共有三種模式:
MeasureSpec.EXACTLY:父視圖希望子視圖的大小是specSize中指定的大小;一般是設置了明確的值或者是MATCH_PARENT。
MeasureSpec.AT_MOST:子視圖的大小最多是specSize中的大小;表示子布局限制在一個最大值內,一般為WARP_CONTENT。
MeasureSpec.UNSPECIFIED:父視圖不對子視圖施加任何限制,子視圖可以得到任意想要的大小;表示子布局想要多大就多大,很少使用。
?
我們看看系統源碼 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 是如何實現的:
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; }從上面的代碼 getDefaultSize() 方法中看出,原來 MeasureSpec.AT_MOST 和 MeasureSpec.EXACTLY 走的是同一個分支,也就是父視圖希望子視圖的大小是specSize中指定的大小。
得出來的默認值就是填充整個父布局。因此,不管你布局大小是 ”wrap_content” 還是 “match_parent” 效果都是充滿整個父布局。那我想要 ”wrap_content” 的效果怎么辦?那么只有重寫onMeasure方法了。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// 測量模式int widthMode = MeasureSpec.getMode(widthMeasureSpec);// 父布局希望子布局的大小,如果布局里面設置的是固定值,這里取布局里面的固定值和父布局大小值中的最小值.// 如果設置的是match_parent,則取父布局的大小int widthSize = MeasureSpec.getSize(widthMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);int heightSize = MeasureSpec.getSize(heightMeasureSpec);int width;int height;Rect mBounds = new Rect();if (widthMode == MeasureSpec.EXACTLY) {width = widthSize;} else {mTxtPaint.setTextSize(txtSize);mTxtPaint.getTextBounds(txt, 0, txt.length(), mBounds);float textWidth = mBounds.width();int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());width = desired;}if (heightMode == MeasureSpec.EXACTLY) {height = heightSize;} else {height = width;}// 最后調用父類方法,把View的大小告訴父布局。 setMeasuredDimension(width, height); }這樣實現的最終效果如下:
?
轉載于:https://www.cnblogs.com/steffen/p/9941947.html
總結
以上是生活随笔為你收集整理的Android自绘制控件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 国产航母改称17号广东舰?
- 下一篇: 水印相机拍摄的会议记录照片会存放在哪里?