1. 程式人生 > >Android之View(一)

Android之View(一)

  • 掌握
  • 掌握

    1. 什麼是View?
    2. View 座標的基本概念
    3. View的生命週期
    4. 如何自定義View

    什麼是View?

    android.app.View 就是手機的UI,View 負責繪製UI,處理事件(evnet),Android 利用 View 打造出所 Widgets,利用 Widget 可打造出互動式的使用者介面,每個View 負責一定區域的繪製。

    一張圖理解常用控制元件層級關係

    這裡寫圖片描述

    View 座標的基本概念

    View的寬高是有top、left、right、bottom引數決定的 而X,Y和translationX,和translationY則負責View位置的改變。

    從Android3.0開始,加入了translation的概念,即相對於父容器的偏移量以及X,Y座標的概念,X,Y代表左上頂點的橫縱座標。當View在發生平移時,getX,getY,setX,setY
    get/setTranslationX/Y來獲得當前左上點的座標。

    X=left+translationX Y同理。
    注意:在View發生改變的過程中,top,left等值代表原始位置,是不會改變的。改變的只有X,Y,translationX/Y。

    一張圖理解View的座標概念
    這裡寫圖片描述

    View的生命週期

    View 的幾個建構函式

    • public MyView(Context context)
      java程式碼直接new一個Custom View例項的時候,會呼叫第一個建構函式

    • public MyView(Context context, AttributeSet attrs)
      在xml建立但是沒有指定style的時候被呼叫.多了一個AttributeSet型別的引數,自定義屬性,在通過佈局檔案xml建立一個view時,會把XML內的引數通過AttributeSet帶入到View內。

    • public MyView(Context context, AttributeSet attrs, int defStyleAttr)
      建構函式中第三個引數是預設的Style,這裡的預設的Style是指它在當前Application或Activity所用的Theme中的預設Style,且只有在明確呼叫的時候才會呼叫

    • @TargetApi(Build.VERSION_CODES.LOLLIPOP)
      public MyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)

      該建構函式是在api21的時候才新增上的

    http://blog.csdn.net/vfush

    View 的幾個重要方法

    • requestLayout
      View重新呼叫一次layout過程

    • invalidate
      View重新呼叫一次draw過程

    • forceLayout
      標識View在下一次重繪,需要重新呼叫layout過程。

    • postInvalidate
      這個方法與invalidate方法的作用是一樣的,都是使View樹重繪,但兩者的使用條件不同,postInvalidate是在非UI執行緒中呼叫,invalidate則是在UI執行緒中呼叫。

    自定義View

    簡單理解View的繪製

    這裡我們先簡單理解View 的繪製,後續文章我們會深入理解。
    1.測量——onMeasure():決定View的大小

    2.佈局——onLayout():決定View在ViewGroup中的位置

    3.繪製——onDraw():如何繪製這個View。

    這裡寫圖片描述

    自定義View的分類

    • 繼承View
    • 繼承ViewGroup
    • 繼承系統控制元件(Button,LinearLayout…)

    自定義View的過程

    1. 自定義 View 首先要實現一個繼承自 View 的類

    2. 新增類的構造方法,通常是三個構造方法,不過從 Android5.0 開始構造方法已經新增到 4 個了

    3. override 父類的方法,如 onDraw,(onMeasure)

    4. 自定義屬性,需要在 values 下建立 attrs.xml 檔案,在其中定義屬性

    通過context.obtainStyledAttributes將建構函式中的attrs進行解析出來,就可以拿到相對應的屬性.
    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyView);
    mColor = typedArray.getColor(R.styleable.MyView_myColor, 0XFF00FF00);

    【注意】三個函式獲取尺寸的區別:
    getDimension()是基於當前DisplayMetrics進行轉換,獲取指定資源id對應的尺寸
    getDimensionPixelSize()getDimension()功能類似,不同的是將結果轉換為int,並且小數部分四捨五入
    getDimensionPixelOffset()getDimension()功能類似,不同的是將結果轉換為int,取整去除小數。舉個例子
    列如getDimension()返回結果是20.5f,那麼getDimensionPixelSize()返回結果就是 21,getDimensionPixelOffset()返回結果就是20。

  • 開啟佈局檔案我們可以看到有很多的以xmlns開頭的欄位。其實這個就是XML name space 的縮寫。我們可以使用res-atuo名稱空間,就不用在新增自定義View全類名。
    xmlns:app="http://schemas.android.com/apk/res-auto"

  • /**
     * Created by fuchenxuan on 16/6/4.
     */
    
    public class MyView extends View {
        private int mRadius=200;
        private int mColor;
    
        public MyView(Context context) {
            this(context,null);
        }
    
        public MyView(Context context, AttributeSet attrs) {
            this(context, attrs,0);
        }
    
        public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            //read custom attrs
            TypedArray t = context.obtainStyledAttributes(attrs,
                    R.styleable.rainbowbar, 0, 0);
           mRadius = t.getDimensionPixelSize(R.styleable.coutom_radius, (int) hSpace);
            t.getDimensionPixelOffset(R.styleable.coutom_at1, (int) vSpace);
            mColor=t.getColor(R.styleable.color, barColor);
            t.recycle();   // we should always recycle after used
    
    
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
             int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
            //set size
            setMeasuredDimension(widthMode == MeasureSpec.AT_MOST ? (int) mRadius * 3 : widthSize, heightMode == MeasureSpec.AT_MOST ? (int) mRadius * 3 : heightSize);
        }
    
    
        //draw be invoke clire.
        int index = 0;
        @Override
        protected void onDraw(Canvas canvas) {
            //super.onDraw(canvas);
             mPaint = new Paint();
            mPaint.setColor(mColor);
            mPaint.setAntiAlias(true);
             canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
         }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    這裡是一個普通的自定義View,裡面畫了圓,根據不同的模式設定了父View的大小。

    關於View重寫onMeasure()時機
    如果用了wrap_content。那麼在onMeasure()中就要呼叫setMeasuredDimension()
    來指定view的寬高。如果使用的是match_parent或者一個具體的dp值。那麼直接使用super.onMeasure()即可。

    自定義ViewGroup

    自定義ViewGroup的過程

    1. 自定義 ViewGroup 和自定義View 一樣,只是繼承自 ViewGroup 的類,和必須實現onLayout()函式
     /**
     * Created by fuchenxuan on 16-6-6.
     */
    public class CostumViewGroup extends ViewGroup {
    
    
        public CostumViewGroup(Context context) {
            super(context);
        }
    
        public CostumViewGroup(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                measureChild(childView, widthMeasureSpec, heightMeasureSpec);
            }
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            if (changed) {
                int childCount = getChildCount();
                for (int i = 0; i < childCount; i++) {
                    View childView = getChildAt(i);
                    childView.layout(i * childView.getMeasuredWidth(), 0, (i + 1) * childView.getMeasuredWidth(), childView.getMeasuredHeight());
                }
            }
        }
    
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    這裡是一個簡單的自定義ViewGroup,實現類似LinearLayout 橫向排放子View位置。這就是一個簡單的ViewGroup過程。

    徹底理解MeasureSpec三種模式

    View的大小不僅由自身所決定,同時也會受到父控制元件的影響,為了我們的控制元件能更好的適應各種情況,一般會自己進行測量。他們是由 mode+size兩部分組成的。widthMeasureSpec和heightMeasureSpec轉化成二進位制數字表示,他們都是30位的。前兩位代表mode(測量模 式),後面28位才是他們的實際數值(size);MeasureSpec.getMode()獲取模式,MeasureSpec.getSize()獲取尺寸
    測量View大小使用的是onMeasure函式,所以我們需要了解三種測量模式:

    • EXACTLY:一般是設定了明確的值(100dp)或者是MATCH_PARENT
    • AT_MOST:表示子佈局限制在一個最大值內,一般為WARP_CONTENT
    • UNSPECIFIED:表示子佈局想要多大就多大,很少使用

    關於ViewGroup重寫onMeasure()時機

    • 首先要先測量子View的寬高:
      getChildAt(int index)可以拿到index上的子view。
      getChildCount()得到子view的個數,再迴圈遍歷出子view。

    • 使用子view自身的測量方法
      childView.measure(int wSpec, int hSpec);

    使用viewGroup的測量子view的方法

    • measureChild(subView, int wSpec, int hSpec);
      測量某一個子view,多寬,多高, 內部加上了viewGroup的padding值

    • measureChildren(int wSpec, int hSpec);
      測量所有子view 都是 多寬,多高, 內部呼叫了measureChild方法

    • measureChildWithMargins(subView, intwSpec, int wUsed, int hSpec, int hUsed);
      測量某一個子view,多寬,多高, 內部加上了viewGroup的padding值、margin值和傳入的寬高wUsed、hUsed

    問題總結

    1. getWidth()和getMeasuredWidth()的區別?
      getMeasuredWidth():只要一執行完 setMeasuredDimension() 方法,就有值了,並且不再改變。
      getWidth():必須執行完 onMeasure() 才有值,可能發生改變。
      如果 onLayout 沒有對子 View 實際顯示的寬高進行修改,那麼 getWidth() 的值 == getMeasuredWidth() 的值。

    2. onLayout() 和Layout()的區別?
      onLayout() ViewGroup中子View的佈局方法,layout()是子View佈局的方法

    3. View 裡面的 onSavedInstanceState和onRestoreInstanceState的作用?
      View和Activity一樣的,每個View都有onSavedInstanceState和onRestoreInstanceState這兩個方法,可用於儲存和恢復view的狀態。

    在本章節中我們知道什麼是View?,View 座標的基本概念,理解了View的生命週期,學習瞭如何自定義View?雖然全是理論知識總結,在後續我們會一起來自定義View的實戰學習。不管有沒有任何疑問,歡迎在下方留言吧。

    更多Android 面試題總結,請點選下方圖片哦。