Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android自定義View實戰

Android自定義View實戰

編輯:關於Android編程

在Android的實際開發中,我們Android系統本身已經給我們提供了很豐富的UI以及各種實用的控件,例如TextView,Button,ImageView等。用這些基礎控件已經能夠實現非常優美的界面以及功能。然而在實際的開發中,我們由於客戶的各種需求,App開發的各種標新立異,追求個性化,所以,導致我們用這些最基礎的控件已經不能夠滿足我們的各種個性化需求。那麼,我們就要考慮去自定義控件來完成我們的要求,要真正去完成一個自定義控件,我們先來看看這個控件的繼承體系:

1.View體系

Android 應用中的所有用戶界面元素都是使用 View 和 ViewGroup 對象構建而成。View 對象用於在屏幕上繪制可供用戶交互的內容。ViewGroup 對象用於儲存其他 View(和 ViewGroup)對象,以便定義界面的局部。

Android 提供了一系列 View 和 ViewGroup 子類,可為您提供常用輸入控件(如按鈕和文本字段)和各種布局模式(如線性布局或相對布局)。

這裡寫圖片描述

2.View的關鍵繪制流程和關鍵的生命周期

Created with Rapha?l 2.1.0Constructor------>構造函數onMeasure------>測量View大小onSizeChanged------>確定View大小onLayout------>確定View布局的位置onDraw------>繪制View中的內容視圖狀態是否改變顯示invalidateyesno

綜上所述:View的關鍵生命周期為:
構造View() –> onMeasure() –> onSizeChanged() –> onLayout() –> onDraw()

3.構造函數的自定義屬性

構造函數

public CustomView(Context context) {
    super(context);
}

public CustomView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}
首先在res的values文件夾下面建一個attrs.xml文件,如下圖所示:
這裡寫圖片描述 然後在裡面定義如下內容



    
    

首先應該在布局文件的跟布局當中定義命名空間
xmlns:app="http://schemas.android.com/apk/res-auto"
在xml中的用法:如下所示
 
                app:border_width="2dp" />

最後在構造函數中這樣獲取:

public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    super.setScaleType(SCALE_TYPE);

    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);

    int mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH);
    int  mBorderColor = a.getColor(R.styleable.CircleImageView_border_color, DEFAULT_BORDER_COLOR);

    a.recycle();
    }

在xml中可以定義的屬性如下

編號 類型 format的值 在xml中定義值示例 1 reference參考指定Theme中資源ID reference app:name=”@string/label” 2 color:顏色 color app:Textcolor=”#ff0000” 3 boolean:布爾值 boolean app:isVisible=”false” 4 dimension:尺寸值 dimension app:myWidth=”100dp” 5 float:浮點型 float app:fromAlpha=”0.3” 6 integer:整型 integer app:framesCount=”22” 7 string:字符串 string app:name=”My name is yuan dong liang” 8 fraction:百分數 fraction app:pivotX=”200%” 9 enum:枚舉 enum app:borderRadius=”circle” 10 flag:位或運算 fraction android:windowSoftInputMode=”stateUnspecified 11 多種符合類型 color app:background = “@drawable/圖片ID|#00FF00”|

4..測量View大小(onMeasure)

代碼
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     // TODO Auto-generated method stub  
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
     System.out.println(widthMeasureSpec+":"+heightMeasureSpec);  
     // 如果是自定ViewGroup,計算自定義的ViewGroup中所有子控件的大小  
      //measureChildren(widthMeasureSpec, heightMeasureSpec);  
    int w = getMeasureWidth(widthMeasureSpec);    
    int h = getMeasureHeight(heightMeasureSpec); 
    setMeasuredDimension(w, h);  \\必須調用此方法,否則會拋出異常  

    }
    private int getMeasureHeight(int heightMeasureSpec) {  
    int result = 0;  
    int size = MeasureSpec.getSize(heightMeasureSpec);  \\每次調用此方法,測量用到的size會發生變化  
    int mode = MeasureSpec.getMode(heightMeasureSpec);  \\根據定義的Layout_width,Layout_height,會對此值產生影響  
    if (mode == MeasureSpec.EXACTLY) {  
        result = size;  
    } else if (mode == MeasureSpec.UNSPECIFIED) {  
        result = (int) paint.measureText("") + getPaddingLeft()  
                + getPaddingRight();  
    } else {  
        result = Math.min(result, size);  
    }  
    System.out.println("Height size:" + size);    
    System.out.println("Height mode:" + mode);  
    return result;  
}  

private int getMeasureWidth(int widthMeasureSpec) {  
    int result = 0;  
    int size = MeasureSpec.getSize(widthMeasureSpec);  
    int mode = MeasureSpec.getMode(widthMeasureSpec);  
    if (mode == MeasureSpec.EXACTLY) {  
        result = size;  
    } else if (mode == MeasureSpec.UNSPECIFIED) {  
        result = (int) paint.measureText("") + getPaddingTop()  
                + getPaddingBottom();  
    } else {  
        result = Math.min(result, size);  
    }  
    System.out.println("Width size:" + size);  
    System.out.println("Width mode:" + mode);  
    return result;  
}  
MeasureSpec

從上面可以看出 onMeasure 函數中有 widthMeasureSpec 和 heightMeasureSpec 這兩個 int 類型的參數, 毫無疑問他們是和寬高相關的, 但它們其實不是寬和高, 而是由寬、高和各自方向上對應的測量模式來合成的一個值:對於詳細測量值( measureSpec )需要兩樣東西來確定它,那就是大小(size)和模式(mode)。 而 measureSpec,size,mode他們三個的關系,都封裝在View類中的一個內部類裡,名叫 MeasureSpec 。
測量模式一共有三種, 被定義在 Android 中的 View 類的一個內部類View.MeasureSpec中:

模式 二進制數值 描述 UNSPECIFIED 00 默認值,父控件沒有給子view任何限制,子View可以設置為任意大小。 EXACTLY 01 表示父控件已經確切的指定了子View的大小。 AT_MOST 10 表示子View具體大小沒有尺寸限制,但是存在上限,上限一般為父View大小。

我們重寫onMeasure()所要實現的最終目的。它的作用就是存儲我們測量好的寬高值。

5.確定View大小(onSizeChanged)

因為View的大小不僅由View本身控制,而且受父控件的影響,所以我們在確定View大小的時候最好使用系統提供的onSizeChanged回調函數。它又四個參數,分別為 寬度,高度,上一次寬度,上一次高度。這個函數比較簡單,我們只需關注 寬度(w), 高度(h) 即可,這兩個參數就是View最終的大小。

 @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
    }

6.確定子View布局位置(onLayout)

確定布局的函數是onLayout,它用於確定子View的位置,在自定義ViewGroup中會用到,他調用的是子View的layout函數。在自定義ViewGroup中,onLayout一般是循環取出子View,然後經過計算得出各個子View位置的坐標值,然後用以下函數設置子View位置。

 @Override  
    protected void onLayout(boolean changed, int l, int t, int r, int b) {  
        // 記錄總高度  
        int mTotalHeight = 0;  
        // 遍歷所有子視圖  
        int childCount = getChildCount();  
        for (int i = 0; i < childCount; i++) {  
            View childView = getChildAt(i);  

            // 獲取在onMeasure中計算的視圖尺寸  
            int measureHeight = childView.getMeasuredHeight();  
            int measuredWidth = childView.getMeasuredWidth();  

            childView.layout(l, mTotalHeight, measuredWidth, mTotalHeight  
                    + measureHeight);  
              mTotalHeight += measureHeight;  

        }  
    }  
四個參數分別為: 名稱 說明 對應的函數 l View左側距父View左側的距離 getLeft(); t View頂部距父View頂部的距離 getTop(); r View右側距父View左側的距離 getRight(); b View底部距父View頂部的距離 getBottom();

7.繪制內容(onDraw)

  @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //利用Canvas 畫圖
    }

8.繪圖知識

Canvas(畫布)類

畫筆屬性設置好之後,還需要將圖像繪制到畫布上。Canvas類可以用來實現各種圖形的繪制工作,如繪制直線、矩形、圓等等。Canvas繪制常用圖形的方法如下:
繪制直線:canvas.drawLine(float startX, float startY, float stopX, float stopY, Paint paint);

  繪制矩形:canvas.drawRect(float left, float top, float right, float bottom, Paint paint);

  繪制圓形:canvas.drawCircle(float cx, float cy, float radius, Paint paint);

  繪制字符:canvas.drawText(String text, float x, float y, Paint paint);

  繪制圖形:canvas.drawBirmap(Bitmap bitmap, float left, float top, Paint paint);

Paint(畫筆)類
要繪制圖形,首先得調整畫筆,按照自己的開發需要設置畫筆的相關屬性。Pain類的常用屬性設置方法如下:
 setAntiAlias();             //設置畫筆的鋸齒效果

 setColor();                 //設置畫筆的顏色

 setARGB();                  //設置畫筆的A、R、G、B值

 setAlpha();                 //設置畫筆的Alpha值

 setTextSize();              //設置字體的尺寸

 setStyle();                  //設置畫筆的風格(空心或實心)

 setStrokeWidth();            //設置空心邊框的寬度

 getColor();                  //獲取畫筆的顏色

9.對外提供操作方法和監聽回調

自定義完View之後,一般會對外暴露一些接口,用於控制View的狀態等,或者監聽View的變化.也就是定義接口。我們的像Button的點擊事件就是這樣的原理。

10.自定義View的分類

在原有的控件基礎上擴展,比如繼承ImageView去實現圓形頭像等。 通過組合控件來自定義View,比如標題欄就可以通過組合控件來實現,左邊一個返回按鈕,中間一個顯示文本,右邊可有可無的提交按鈕。 完全重寫onDraw()自定義View。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved