android 自定義view起步之一

本文中用到的例子是來自於http://blog.csdn.net/lmj623565791/article/details/24300125,只是爲了方便更多的人瞭解自定義view的過程對其中的代碼進行詳細的解釋,如果給原作者帶來的不便還請諒解,如果本文中,有什麼說的不對的地方,還請指正,謝謝。
對於自定義view來說我們通常走的步驟大概分爲:

1、自定義View的屬性
2、在View的構造方法中獲得我們自定義的屬性
[ 3、重寫onMesure ]
4、重寫onDraw

我們以上面提到的例子源碼進行詳細的分析:

一、代碼解析

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- 自定義屬性 -->
    <attr name="titleText" format="string" />
    <!-- 前面是自定義屬性的名稱,後面是可使用的類型,注意大小寫 -->
    <attr name="titleSize" format="dimension" />
    <!-- 這個表示只要是顏色都可以 -->
    <attr name="titleColor" format="color" />
    <!-- 這個表示只要是資源文件即可 -->
    <attr name="image" format="reference" />
    <!-- 這種類似於填寫相對位置信息時的選項下面的枚舉類型就是可選項 -->
    <attr name="imageScaleType">
        <enum name="fillXY" value="0" />
        <enum name="center" value="1" />
    </attr>

    <!-- 此處用於說明CustomImageView 有哪些屬性 -->
    <declare-styleable name="customImageView">
        <attr name="titleText" />
        <attr name="titleSize" />
        <attr name="titleColor" />
        <attr name="image" />
        <attr name="imageScaleType" />
    </declare-styleable>

</resources>

這個是屬性文件的內容,在 res/valuse/attr.xml 設置此屬性,代碼中已經做出了詳細的解釋,在此先不贅訴。

下面的是整個view的編寫代碼,也有了詳細的註釋,稍後對其中的代碼片段進行解釋。

public class customImageView extends View {
    private Bitmap mImage;
    private int mImageScale;
    private String mTitle;
    private int mTextColor;
    private int mTextSize;
    private Rect rect;
    private Paint mPaint;
    private Rect mTextBound;
    private int mWidth;
    private int mHeight;
    private static int IMAGE_SCALE_FITXY = 0;

    public customImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);// 此處一定要寫成this
                                // ,因爲你除非設置樣式否則都會執行第一或者第二個構造函數,不會經過第三個,所以第一第二個要調用第三個
        // TODO Auto-generated constructor stub
    }

    public customImageView(Context context) {
        this(context, null);
        // TODO Auto-generated constructor stub
    }

    public customImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 獲取我們自定義的屬性
        // 得到屬性數組 //attrs 屬性集合(哪一個對應那些值) 屬性集(前面的那個的哪一個) 默認樣式
        TypedArray array = context.getTheme().obtainStyledAttributes(attrs,
                R.styleable.customImageView, defStyleAttr, 0);// 這個地方傳入的R.styleable.customImageView
                                                                // 就是需要篩選的集合
        int n = array.getIndexCount();
        for (int i = 0; i < n; i++) {
            int attr = array.getIndex(i); // 獲取其中的一個屬性
            switch (attr) {
            case R.styleable.customImageView_image:
                // array 通過get 方法,通過傳入的標識(attr),獲取對應的屬性的內容,此處的就是圖片資源
                mImage = BitmapFactory.decodeResource(getResources(),
                        array.getResourceId(attr, 0));
                // bitmap工廠類生成bitmap

                break;
            case R.styleable.customImageView_imageScaleType:
                mImageScale = array.getInt(attr, 0);// 此處是整數 //放大倍數
                break;
            case R.styleable.customImageView_titleText:
                mTitle = array.getString(attr);
                break;
            case R.styleable.customImageView_titleColor:
                mTextColor = array.getColor(attr, 0);
                break;
            case R.styleable.customImageView_titleSize:
                mTextSize = array.getDimensionPixelSize(attr, (int) TypedValue
                        .applyDimension(TypedValue.COMPLEX_UNIT_SP, 16,
                                getResources().getDisplayMetrics()));
                break;
            default:
                break;
            }
        }
        array.recycle(); // 釋放內存資源
        rect = new Rect(); // 矩形畫布
        mPaint = new Paint();// 新建畫筆
        mTextBound = new Rect();// 文字的矩形畫布
        // 畫筆根據文字內容測算文字矩形畫布的寬高並賦值給mTextBound(此時是包裹文字的最小矩形)
        mPaint.getTextBounds(mTitle, 0, mTitle.length(), mTextBound);

    }

    // 測量函數
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// 此處傳入的值由父容器決定
        // ---設置控件寬度---
        int specMode = MeasureSpec.getMode(widthMeasureSpec); // 獲取寬度的設置類型
        int specSize = MeasureSpec.getSize(widthMeasureSpec);// 獲取寬度大小

        if (specMode == MeasureSpec.EXACTLY) {// fill_parent,或者具體值 --type : 精確模式
            mWidth = specSize;
        } else {//非精確模式
            // 由圖片決定的寬
            int desireByImg = getPaddingLeft() + getPaddingRight()
                    + mImage.getWidth();
            // 由文字決定的寬
            int desireByTitle = getPaddingLeft() + getPaddingRight()
                    + mTextBound.width();
            if (specMode == MeasureSpec.AT_MOST) {// wrap_content
                int desire = Math.max(desireByImg, desireByTitle); // 獲取文字和圖片之間的最大值,保證能正常顯示
                mWidth = Math.min(specSize, desire); // 此處的specSize 是父佈局剩餘的大小
                                                        // 選擇能正常顯示和剩餘大小的最小值//也就是說如果剩餘值大於最小值此時圖片會顯示不完整,下面的代碼會詳細標明
            }

        }
        // ---設置高度---
        specMode = MeasureSpec.getMode(heightMeasureSpec);
        specSize = MeasureSpec.getSize(heightMeasureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            mHeight = specSize;
        } else {
            int desire = getPaddingTop() + getPaddingBottom()
                    + mImage.getHeight() + mTextBound.height();
            if (specMode == MeasureSpec.AT_MOST) { // 同上理解
                mHeight = Math.min(desire, specSize);
            }
        }
        setMeasuredDimension(mWidth, mHeight);// 設置當前控件的寬高
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // ---------繪製一個寬度爲4的矩形邊框----------------
        mPaint.setStrokeWidth(4);// 設置畫筆寬度
        mPaint.setStyle(Paint.Style.STROKE); // 設置畫筆填充樣式
        mPaint.setColor(Color.CYAN);// 設置畫筆顏色
        // 繪製一個矩形,左上座標爲0,0 右下爲getMeasuredWidth(), getMeasuredHeight()
        canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
        // --------設置這個控件的4個位置信息--------------------------------------------
        rect.left = getPaddingLeft(); // 左上的x 座標
        rect.right = mWidth - getPaddingRight();// 右下的x 座標 ,看mWidth 的組成
                                                // 減去這個getPaddingRight等同於getPaddingLeft()+控件自身所用到的寬度
        rect.top = getPaddingTop();// 左上的y
        rect.bottom = mHeight - getPaddingBottom(); // 右下的y ,解釋同上

        // ---------開始繪製文字----------------------
        mPaint.setColor(mTextColor);
        mPaint.setStyle(Style.FILL);
        // 當設置的寬度小於字體需要的寬度,將內容更改爲xxx... 後面爲省略號的形式
        if (mTextBound.width() > mWidth) {
            TextPaint paint = new TextPaint(mPaint);
            String msg = TextUtils.ellipsize(mTitle,
                    paint, // 參數含義 原始內容,畫筆,真實寬度,出現“...”的方式(中間或者結尾)
                    (float) mWidth - getPaddingLeft() - getPaddingRight(),
                    TextUtils.TruncateAt.END).toString();
            canvas.drawText(msg, getPaddingLeft(),
                    mHeight - getPaddingBottom(), mPaint); // 特別指出

        } else {
            // 正常情況,將字體居中
            canvas.drawText(mTitle, mWidth / 2 - mTextBound.width() * 1.0f / 2,
                    mHeight - getPaddingBottom(), mPaint);
        }

        // 去掉使用掉的高度
        rect.bottom -= mTextBound.height();

        if (mImageScale == IMAGE_SCALE_FITXY) {
            canvas.drawBitmap(mImage, null, rect, mPaint);
        } else {
            rect.left = mWidth / 2 - mImage.getWidth() / 2; // 圖片大於控件寬度的時候會是負值,圖片會被裁剪、、也就是說會有一部分顯示不出來
            rect.right = mWidth / 2 + mImage.getWidth() / 2;
            rect.top = (mHeight - mTextBound.height()) / 2 - mImage.getHeight()
                    / 2;
            rect.bottom = (mHeight - mTextBound.height()) / 2
                    + mImage.getHeight() / 2;
            canvas.drawBitmap(mImage, null, rect, mPaint);

        }

    }
}

二、疑問解答

下面對其中出現的不太容易理解的地方進行分析:

1. 下面這行代碼的含義是什麼呢?

    mTextSize = array.getDimensionPixelSize(attr, (int) TypedValue
                        .applyDimension(TypedValue.COMPLEX_UNIT_SP, 16,
                                getResources().getDisplayMetrics()));

getDimensionPixelSize 這個方法的功能是獲取像素值,TypedValue .applyDimension(TypedValue.COMPLEX_UNIT_SP, 16,
getResources().getDisplayMetrics()) 這個代碼的含義是將16轉化爲sp 格式時int 的值。也就是16sp 的int 值。

2. 這個將文字居中怎麼理解?

    // 正常情況,將字體居中
            canvas.drawText(mTitle, mWidth / 2 - mTextBound.width() * 1.0f / 2,
                    mHeight - getPaddingBottom(), mPaint);

首先我們要理解一下canvas.drawText(text, x, y, paint) 這個函數的含義:首先第一個和第四個參數的含義很容易理解,第一個是需要繪製的文本,最後一個是所使用的畫筆,x,y 的話按照一般的規律來說應該是文本的位置信息,但是他的x,y 並不是一般的理解思路, 首先y 的確定上跟baseline 有關,不清楚的可以百度一下,這個就不解釋了,x 和設置的文字的居中方式有關,如果設置了paint.setTextAlign(Paint.Align.CENTER),則x 的起點默認是字符的中心位置,默認是最左側。
所以上面的y 的計算其實是計算baseline ,x的位置參考下圖:
x的位置理解

未完待續…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章