Android自定義view學習筆記02

Android自定義view學習筆記02

本文代碼來自於張鴻洋老師的博客之Android 自定義View (二) 進階 學習筆記,對代碼進行些許修改,並補充一些在coding過程中遇到的問題、學習的新東西。

相關代碼

//CustomImageView.java
package mmrx.com.myuserdefinedview.textview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;

import mmrx.com.myuserdefinedview.R;

/**
 * Created by mmrx on 2015/4/6.
 */
public class CustomImageView extends View {

    //文本內容
    private String mTitleText;
    //文本的顏色
    private int mTitleColor;
    //文本大小
    private int mTitleTextSize;
    //圖片資源
    private Bitmap mImage;
    //圖片縮放
    private int mImageScale;
    //文本時候的文字顯示範圍
    private Rect mBound;
    private Paint mPaint;
    //圖片和字外面的矩形
    private Rect rect;
    //高度和寬度
    int mWidth,mHeight;

    //在代碼中調用
    public CustomImageView(Context context){
        this(context,null);
    }
    //在佈局文件中調用
    public CustomImageView(Context context,AttributeSet set){
        this(context,set,R.attr.CustomImageView02Style);
    }
    //顯示被調用
    public CustomImageView(Context context,AttributeSet set,int defStyle){
        super(context,set,defStyle);

        TypedArray ta = context.obtainStyledAttributes(set, R.styleable.CustomImageView,
                defStyle,R.style.CustomizeStyle02);
        int count = ta.getIndexCount();

        for(int i=0;i<count;i++){
            int index = ta.getIndex(i);

            switch (index){
                case R.styleable.CustomImageView_image:
                    /*
                    * decodeResource(Resources res, int id)
                    * res 是一個Resources類對象,用來讀取res文件夾下的資源
                    * 這個res是的來源是該view創建時傳入的Context對象, context.getResources()
                    * */
                    mImage = BitmapFactory.decodeResource(getResources(),ta.getResourceId(index,0));
//                    assert mImage!=null;
                    break;
                case R.styleable.CustomImageView_imageScaleType:
                    mImageScale = ta.getInt(index,0);
                    break;
                case R.styleable.CustomImageView_titleColor:
                    mTitleColor = ta.getColor(index, Color.BLACK);
                    break;
                case R.styleable.CustomImageView_titleSize:
                    /*
                    * TypedValue.applyDimension是轉變爲標準尺寸的函數
                    * 第一個參數是 單位,第二個參數是要數值,第三個是DisplayMetircs 對象,用於獲取屏幕分辨率
                    * */
                    mTitleTextSize = ta.getDimensionPixelSize(index,
                            (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                                    16,getResources().getDisplayMetrics()));
                    break;
                case R.styleable.CustomImageView_titleText:
                    mTitleText = ta.getString(index);
                    break;
                default:
                    break;
            }//end switch
        }//end for

        ta.recycle();

        rect = new Rect();
        mPaint = new Paint();
        mBound = new Rect();
        mPaint.setTextSize(mTitleTextSize);
        //計算字體所需範圍
        mPaint.getTextBounds(mTitleText,0,mTitleText.length(),mBound);
    }//end method

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int hightMod = MeasureSpec.getMode(heightMeasureSpec);
        int hightSize = MeasureSpec.getSize(heightMeasureSpec);
        int widthMod = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        //高度有具體數值
        if(hightMod == MeasureSpec.EXACTLY){
            mHeight = hightSize;
        }
        //wrap_content & others
        else{
            //獲得圖片的高度
            int imageHeight = mImage.getHeight();
            //獲得文字高度
            int textHeight = mBound.height();
            //總體高度
            int totalHeight = imageHeight + textHeight + getPaddingTop() + getPaddingBottom();
            if(hightMod == MeasureSpec.AT_MOST){
                //warp_content
                //獲得相對較小的高度
                mHeight = Math.min(hightSize,totalHeight);
            }
            else{
                //多個自定義CustomImageView放入ScrollView中,只會顯示出最後一個處理辦法
                mHeight = hightSize;
            }
        }

        //寬度有具體數值
        if(widthMod == MeasureSpec.EXACTLY){
            mWidth = widthSize;
        }
        //wrap_content & others
        else{
            // 由圖片決定的寬
            int desireByImg = getPaddingLeft() + getPaddingRight() + mImage.getWidth();
            // 由字體決定的寬
            int desireByTitle = getPaddingLeft() + getPaddingRight() + mBound.width();
            Log.w("CustomImageView","desireByTitle:"+desireByTitle+" desireByImg:"+desireByImg+" widthSize: "+widthSize);
            if(widthMod == MeasureSpec.AT_MOST){
                //warp_content
                Log.w("CustomImageView","MeasureSpec.AT_MOST");
                //獲得相對較大的寬度
                int desire = Math.max(desireByImg,desireByTitle);
                mWidth = Math.min(widthSize,desire);
            }
            else{
                Log.w("CustomImageView","NOT MeasureSpec.AT_MOST");
                //多個自定義CustomImageView放入ScrollView中,只會顯示出最後一個處理辦法
                mWidth = widthSize;
            }
        }

        //將計算好的高度寬度放入,一定要記得這句話不要丟了...
        setMeasuredDimension(mWidth, mHeight);
    }

    @Override
    protected void onDraw(Canvas canvas) {
//        super.onDraw(canvas);
        //邊框
        //設置線寬
        mPaint.setStrokeWidth(4);
        //設置樣式爲空心
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(Color.RED);
        /*開畫,前四個參數是 左 上 右 下 邊的位置(注意是邊,不是點) 畫筆
         * left 矩形左邊的x座標 right 矩形右邊的x座標
         * top 矩形上邊的y座標 bottom 矩形下邊的y座標
         * */
        canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),mPaint);
        //獲得繪製圖片的矩形框
        rect.left = getPaddingLeft();
        rect.right = mWidth - getPaddingRight();
        rect.top = getPaddingTop();
        rect.bottom = mHeight - getPaddingBottom();

        //設置畫筆風格爲 實心
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mTitleColor);

        //噹噹前字體的寬度大於邊框的寬度,截取字符串的,改爲xxxx...
        if(mBound.width()>mWidth){
            //TextPaint 是 Paint 的一個子類
            TextPaint paint = new TextPaint(mPaint);
            /*TextUtils是處理字符串的工具類,ellipsize是用來截斷給定長度的字符串的方法
            * 參數 需要截斷的字符串 顯示的字符串的寬度 省略號的位置
            * */
            String msg = TextUtils.ellipsize(mTitleText,paint,
                    (float)mWidth-getPaddingLeft()-getPaddingRight(),TextUtils.TruncateAt.END).toString();
            /*
            * 座標參數 x,y x默認是字符串左邊在屏幕的位置,如果設置了paint.setTextAlign(Paint.Align.CENTER);
            * 那就是字符的中心,y是指定這個字符baseline在屏幕上的位置
            * */
            canvas.drawText(msg, getPaddingLeft(), mHeight - getPaddingBottom(), mPaint);
        }
        //正常情況,字體居中
        else{
            canvas.drawText(mTitleText,mWidth/2-mBound.width()*1.0f/2,
                    mHeight-getPaddingBottom(),mPaint);
        }
        //這句話是什麼意思?如果圖片縮放選擇的是FITXY,則需要將rect的bottom邊向上提一下,防止把
        //字覆蓋掉,如果選擇的是CENTER,rect的位置會重新定義就不用這句話了。我感覺這句話放到
        //if(mImageScale == 0)語句塊裏比較好懂一些。
        rect.bottom -= mBound.height();
        //如果縮放爲 FITXY
        if(mImageScale == 0){
            canvas.drawBitmap(mImage,null,rect,mPaint);
        }
        //如果爲居中 CENTER
        else{
            //計算居中的矩形範圍
            rect.left = mWidth/2 - mImage.getWidth()/2;
            rect.right = mWidth/2 + mImage.getWidth()/2;
            rect.bottom = (mHeight-mBound.height())/2 + mImage.getHeight()/2;
            rect.top = (mHeight-mBound.height())/2 - mImage.getHeight()/2;
            canvas.drawBitmap(mImage,null,rect,mPaint);
        }
    }
}

xml文件的相關內容

<!--attrs.xml-->
    <attr name="titleText" format="string"/>
    <attr name="titleColor" format="color"/>
    <attr name="titleSize" format="dimension"/>
    <attr name="CustomImageView02Style" format="reference"/>
    <attr name="image" format="reference"/>
    <attr name="imageScaleType">
        <enum name="fillXY" value="0"/>
        <enum name="center" value="1"/>
    </attr>    

    <declare-styleable name="CustomImageView">
        <attr name="titleText" />
        <attr name="titleColor" />
        <attr name="titleSize" />
        <attr name="imageScaleType"/>
        <attr name="image"/>
    </declare-styleable>
<!-- style.xml-->
<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="CustomView01Style">@style/CustomizeStyle01</item>
        <item name="CustomImageView02Style">@style/CustomizeStyle02</item>
    </style>

    <style name="CustomizeStyle01">
        <item name="titleText">@string/hello_world</item>
        <item name="titleColor">#ff0000</item>
        <item name="titleSize">14dp</item>
    </style>

    <style name="CustomizeStyle02">
        <item name="titleText">@string/hello_world</item>
        <item name="titleColor">#ff0000</item>
        <item name="titleSize">14dp</item>
        <item name="imageScaleType">center</item>
        <item name="image">@drawable/ic_launcher</item>
    </style>
</resources>
<!--activity_main.xml-->
    <mmrx.com.myuserdefinedview.textview.CustomImageView
        android:layout_marginTop="20dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        customview:imageScaleType="center"
        customview:titleText="androidandroidandroidandroid"
        customview:image="@drawable/ic_launcher"
        android:padding="20dp"/>

    <mmrx.com.myuserdefinedview.textview.CustomImageView
        android:layout_marginTop="20dp"
        android:layout_width="150dp"
        android:layout_height="200dp"
        customview:imageScaleType="fillXY"
        customview:titleText="板娘"
        customview:image="@drawable/hello"
        android:padding="5dp"/>

遇到的問題

  • 啓動過程中logcat打印以下錯誤後程序崩潰
02-06 14:25:08.383  30804-30804/mmrx.com.myuserdefinedview E/AndroidRuntime﹕ FATAL EXCEPTION: main
    java.lang.IllegalStateException: onMeasure() did not set the measured dimension by calling setMeasuredDimension()
            at android.view.View.measure(View.java:15561)
            at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5109)
            at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1396)
            at android.widget.LinearLayout.measureVertical(LinearLayout.java:681)
            at android.widget.LinearLayout.onMeasure(LinearLayout.java:574)
            at android.view.View.measure(View.java:15556)
            at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5109)
            at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
            at android.view.View.measure(View.java:15556)
            at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5109)
            at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1396)
            at android.widget.LinearLayout.measureVertical(LinearLayout.java:681)
            at android.widget.LinearLayout.onMeasure(LinearLayout.java:574)
            at android.view.View.measure(View.java:15556)
            at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5109)
            at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
            at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2397)
            at android.view.View.measure(View.java:15556)
            at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:1987)
            at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1228)
            at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1401)
            at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1121)
            at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4598)
            at android.view.Choreographer$CallbackRecord.run(Choreographer.java:725)
            at android.view.Choreographer.doCallbacks(Choreographer.java:555)
            at android.view.Choreographer.doFrame(Choreographer.java:525)
            at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:711)
            at android.os.Handler.handleCallback(Handler.java:615)
            at android.os.Handler.dispatchMessage(Handler.java:92)
            at android.os.Looper.loop(Looper.java:137)
            at android.app.ActivityThread.main(ActivityThread.java:4921)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:511)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1038)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:805)
            at dalvik.system.NativeStart.main(Native Method)

一長串的錯誤信息。。。看了下代碼,是onMeasure方法中沒有調用setMeasuredDimension(int, int)方法。

  • 類似的還有一些比如說變量名用錯…case後的標籤用錯…導致上次coding的時候心煩意亂的,事隔半周後重新拿起來再看,錯誤都是一些及其低級的錯。

補充的知識點

  • Bitmap decodeResource(Resources res, int id)這個方法屬於類BitmapFactory。在示例代碼中的調用語句爲mImage = BitmapFactory.decodeResource(getResources(),ta.getResourceId(index,0));這第一個參數,當時有點不理解…查詢到的資料和通過看源碼的收穫如下:

    res 是一個Resources類對象,用來讀取res文件夾下的資源。
    每一個View類型的對象都擁有一個Resources實例mResources,而這個實例又是通過實例化View類型對象時傳入的Context對象獲取的。
    說明一個Activity的View共有一個Resources對象的引用。

  • TypedValue.applyDimension是轉變爲標準尺寸的函數,在示例代碼中有相關的註釋。

  • 關於canves.drawRect()方法的座標參數問題,我找到了一篇寫的很不錯的博客android Draw Rect 座標圖示,裏面有一張很生動的圖來表示座標的位置,清晰易懂。需要注意的是座標原點在左上方,x軸(橫軸)向右遞增,y軸(縱軸)向下遞增。於是也解決了代碼中這些運算的具體含義了。

rect.left = mWidth/2 - mImage.getWidth()/2;
rect.right = mWidth/2 + mImage.getWidth()/2;
rect.bottom = (mHeight-mBound.height())/2 +      mImage.getHeight()/2;
rect.top = (mHeight-mBound.height())/2 - mImage.getHeight()/2;

Android自定義view學習筆記01
Android自定義view學習筆記03
Android自定義view學習筆記04
源碼同步到gitHub

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