自定義View(二)、自定義View的分類及流程

自定義View(二)、自定義View的分類及流程

原文 GcsSloop大神的自定義View系列
在這個基礎上簡單整理學習。

一、前言,

上一篇對View的座標位置等一些基礎概念進行了介紹,這篇開始對自定義View的流程進行分析,後面再通過一個簡單的實戰來鞏固。

首先看這麼一個圖,基本概述了整個自定view的流程:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-dKPhXzf3-1585814037311)(https://evanzch.oss-cn-beijing.aliyuncs.com/img/20200318193000.png)]

二、View的分類

視圖View主要分爲兩類

類別 解釋 特點
單個View 即一個View,如TextView 不包含子View
多個View 即多個View組成的ViewGroup,如LinearLayout 包含子View

2.1、自定義ViewGroup

自定義ViewGroup一般是利用現有的組件根據特定的佈局方式來組成新的組件,大多繼承自ViewGroup或各種Layout,包含有子View。

例如:應用底部導航條中的條目,一般都是上面圖標(ImageView),下面文字(TextView),那麼這兩個就可以用自定義ViewGroup組合成爲一個Veiw,提供兩個屬性分別用來設置文字和圖片,使用起來會更加方便。

2.2、自定義View

在沒有現成的View,需要自己實現的時候,就使用自定義View,一般繼承自View,SurfaceView或其他的View,不包含子View。

例如:製作一個支持自動加載網絡圖片的ImageView,製作圖表等。

三、流程詳細介紹

上面給了總的自定義View流程圖,下面會對每個流程進行介紹,部分重要的流程也會在後面的文章中重點介紹。

3.1、構造函數

在講構造函數之前,我們先看View類

View類簡介

  • View類是Android中各種組件的基類,如View是ViewGroup基類
  • View表現爲顯示在屏幕上的各種視圖

我們在自定義View或者自定義ViewGroup的時候,需要繼承View或者ViewGroup,一般要重寫四個構造方法,比如我這裏定義了MyViewGroup繼承至ViewGroup,重寫的構造方法如下:

  • 當我們直接New一個對象的時候,就會調第一個構造方法A,如下:
MyViewGroup myViewGroup = new MyViewGroup(this);
  • 當我們在Xml文件裏面引用的時候,就會回調第二個構造方法B,如下:
<com.evan.androidnote.ui.MyViewGroup
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

3.2、AttributeSet與自定義屬性

在第二個構造函數裏面我們看到了AttributeSet這個參數,這裏簡單對這個參數介紹一下,後面會有文章具體講到。

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="EvanZch"
    android:textColor="@color/colorPrimary"
    android:textSize="20sp" />

我們定義一個最簡單的TextView,我們可以看到,裏面有設置了各種配置屬性,比如文本內容設置使用android:text,字體大小設置使用android:textSize 這個就是AttributeSet

那我們怎麼自定義屬性呢?一般要遵循下面四個步驟:

  • 1、在attar.xml中定義declare-styleable

    這裏簡單瞭解以下,具體format這些屬性的含義後面會有文章單獨介紹。

  • 2、在佈局文件中使用:

  • 3、定義好屬性後,我們就可以通過上面的構造函數拿到我們設置的值。
public MyViewGroup(Context context, AttributeSet attrs) {
    super(context, attrs);
    TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyViewGroupStyle);
    int color = array.getColor(R.styleable.MyViewGroupStyle_color, Color.RED);
    String text = array.getString(R.styleable.MyViewGroupStyle_text);
    LogUtil.d(TAG + "--MyViewGroup  color=" + color + ",text=" + text);
}

打印結果:

  • 4、獲取到具體的屬性值後,在後面的應用中,就可以直接用在View上了,根據具體的邏輯來實現。

四、onMeasure()

測量View大小

onMeasure()方法用來測量View的大小,我們爲什麼還要測量View的大小呢?
因爲View的大小不僅由自身確定,也會受到父控件的影響,爲了我們自定義的View能更好的展示,我們一般需要自己來進行測量,根據測量結果合理的定義View大小

測量View的大小需要重寫onMeasure()方法。

@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);
}

可以看到在onMeasure()方法中有widthMeasureSpecheightMeasureSpec兩個參數,看名字肯定跟寬高有關係,但是他們並不是表示寬高具體的值,要獲取到具體的值需要使用MeasureSpec這個類來獲取,我們看到對應寬高,MeasureSpec分別通過getModegetSize兩個方法來獲取具體的數值,getSize可以知道是獲取具體的值,那getMode又是幹啥的?

  • int getMode(int measureSpec)
    當我們想了解這個方法,最好的辦法是點進去,先看官方介紹。
/**
 * Extracts the mode from the supplied measure specification.
 *
 * @param measureSpec the measure specification to extract the mode from
 * @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
 *         {@link android.view.View.MeasureSpec#AT_MOST} or
 *         {@link android.view.View.MeasureSpec#EXACTLY}
 */
@MeasureSpecMode
public static int getMode(int measureSpec) {
    //noinspection ResourceType
    return (measureSpec & MODE_MASK);
}

通過傳入的widthMeasureSpecheightMeasureSpec獲取對應的模式,那模式又是啥?我們看到返回值有UNSPECIFIEDAT_MOSTEXACTLY三種,它們大致區別如下,可以先大概瞭解,後面後面也會具體介紹。

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

注意
如果在onMeasure()方法中我們對View的寬高進行了修改,就不需要在調用super.onMeasure(widthMeasureSpec, heightMeasureSpec)這個方法,直接通過setMeasuredDimension(widthSize, heightSize)來設置。

五、onSizeChanged()

確定View大小

這個方法只有在視圖發生改變的時候纔會回調,通過方法名字我們也能看出來。

我們在前面已經通過onMeasure()方法測量了View的大小,然後通過setMeasuredDimension(widthSize, heightSize)來進行設置, 爲什麼還要再次確認View的大小?

原因是View的大小一是要由自身控制,而且受父控件的影響,所以,我們最好在onSizeChanged() 方法中確定View的具體大小

  • onSizeChanged

/**
  *  w : View的寬
  *  h : View的高
  *  oldw : View上一次的寬
  *  oldh : View上一次的高
  */
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    LogUtil.d(TAG + "--onSizeChanged  w=" + w + ",h=" + h);
}

我們直接通過方法中的w和h就可以拿到對應的寬高。

六、onLayout()

  • 確定子View佈局

確定子View的佈局使用onLayout()方法,用來確定子View的View,通常在自定義ViewGroup中會用上,調用的也是各個子View的layout方法

在自定義ViewGroup中,onLayout一般是循環取出子View,然後經過計算得出各個子View位置的座標值,然後用以下函數設置子View位置。

view.layout(l, t, r, b);

四個參數分別爲:

名稱 說明 對應的函數
l View左側距父View左側的距離 getLeft();
t View頂部距父View頂部的距離 getTop();
r View右側距父View左側的距離 getRight();
b View底部距父View頂部的距離 getBottom();

圖示如下:

七、onDrow()

我們自定義View真正繪製的地方,所有的繪製都在這個方法下進行,後面會重點介紹。

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
}
  • 參數 Canvas : “畫布”
  • 後續還會提到 Path : “畫筆”

通過調用各種方法,能實現在畫布上畫出各種想要的View。

八、自定義ViewGroup實戰

自定義ViewGroup,實現效果圖如下:

源碼地址,註釋基本都寫了。

總結

本篇對自定義View的整體流程大概走了一遍,對幾個重要的方法簡單進行了分析,這些方法在後面的自定義View的都會用上,到時候會再詳細的介紹。

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