Android自定義視圖四:定制onMeasure強制顯示為方形

這個系列是老外寫的,干貨!翻譯出來一起學(xué)習(xí)。如有不妥,不吝賜教!

  1. Android自定義視圖一:擴展現(xiàn)有的視圖,添加新的XML屬性
  2. Android自定義視圖二:如何繪制內(nèi)容
  3. Android自定義視圖三:給自定義視圖添加“流暢”的動畫
  4. Android自定義視圖四:定制onMeasure強制顯示為方形

QQ圖片20160507214103.jpg

上一篇開發(fā)之后的效果如上圖。不過看著這張圖,需要注意的不是我們自定義視圖展示了什么,而是這個視圖的大小和位置。你會看到這個折線圖有一個特定的大小(size)。這個size是怎么定的呢?現(xiàn)在的代碼是使用了一個豎直方向的LinearLayout,折線圖和他下面的TextView使用layout_weight屬性平分了他們所在的LinearLayout的高度。那么如果我們刪掉了TextView和全部的layout_weight,并把折線圖的高度設(shè)定為wrap_content會發(fā)生什么呢?
QQ圖片20160507213949.jpg

是的,以上修改之后整個的圖就變成了這樣。雖然使用了wrap_content的高度,但是效果是填滿了整個LinearLayout。這就是View的默認布局行為,但是,如果我們要改變一下呢?

View的Layout

自定義視圖顯示在屏幕上一共分三步:measure(測量),layout(布局),draw(繪制)?;旧弦粋€自定義視圖在測量這一步計算大小,之后可以通過getMeasureWidthgetMeasureHeight得到View的寬度和高度。在布局計算這個自定義視圖的左上和右下坐標(biāo)以及實際的寬度和高度,最后根據(jù)以上layout步驟獲得的數(shù)據(jù)調(diào)用onDraw方法把View繪制在屏幕上。

所以要修改size,也就是自定義視圖中修改默認的行為,就需要override onMeasure()方法。一般的通用做法是:

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)

    var widthSpecMode = MeasureSpec.getMode(widthMeasureSpec)
    var widthSpecSize = MeasureSpec.getSize(widthMeasureSpec)
    var heightSpecMode = MeasureSpec.getMode(heightMeasureSpec)
    var heightSpecSize = MeasureSpec.getSize(heightMeasureSpec)

    if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
        // set default width and height values you defined
        setMeasuredDimension(mDefaultWidth, mDefaultHeight)
    } else if (widthSpecMode == MeasureSpec.AT_MOST) {
        // set default width value and calculated `heightSpecSize` as height
        setMeasuredDimension(mDefaultWidth, heightSpecSize)
    } else if (heightSpecMode == MeasureSpec.AT_MOST) {
        // set calculated `widthSpecSize` as width and default height
        setMeasuredDimension(widthSpecSize, mDefaultHeight)
    }
}

簡單理解MeasureSpec.AT_MOST就是你給折線圖的layout_width或者layout_height設(shè)置了wrap_content,系統(tǒng)不知道精確的寬度、高度是多少的時候的一個標(biāo)記。如果有具體的50dp, 100dp的時候,這個標(biāo)記的值為MeasureSpec.EXACTLY。一般,一個view的寬度、高度只有這兩種標(biāo)記。
View的寬、高度測量分別處理三種情況:

  1. 如果寬、高度都是AT_MOST的時候,寬度和高度設(shè)置為默認值。
  2. 寬度為AT_MOST高度不是的時候,寬度設(shè)置為默認值,高度設(shè)置為測量的值heightSpecSize
  3. 寬度為精確值,高度為AT_MOST的時候,寬度設(shè)置為widthSpecSize,高度設(shè)置為默認值。

而我們這里則是意外的簡單。因為要設(shè)置為正方形,所以使用寬度和高度中相對較小的那個值來作為寬、高度共同的值就可以了:

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)

    var size = 0

    var width = getMeasuredWidth()
    var height = getMeasuredHeight()

    if (width > height) {
        size = height
    } else {
        size = width
    }

    setMeasuredDimension(size, size)
}

如前文所說,我們要把折線圖這個自定義視圖設(shè)置為正方形。所以,不管測量的Mode是如何的,只要用寬、高中的最小值就可以了。

而上面說到的layout部分,對于有子view的`View`比較有用,
也就是說對于繼承自`ViewGroup`的`View`來說比較有用。
我們的折線圖知識一個單純的不能更單純的`View`。

我們前面在onDraw方法使用的getWidth()getHeight()onMeasure()方法中都是不可用的。因為這個時候正在計算寬、高度。在這個方法里只能取到getMeasuredWidth()getMeasuredHeight()。

override onMeasure方法就一定要在最后調(diào)用setMeasuredDimension方法。調(diào)用setMeasuredDimension方法是告訴父view當(dāng)前view的測量高度是多少。如果不調(diào)用這個方法的話會拋異常。

修改之后的布局,寬度match_parent,高度wrap_content。不必要的屬性都略掉了。

<RelativeLayout>

    <LinearLayout>
        <Button android:text="walking" />
        <Button android:text="Running" />
        <Button android:text="Cycling" />
    </LinearLayout>

    <demo.customview.customviewdemo.Views.SquareLineChartView
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</RelativeLayout>

效果:


支持任意寬高比例

一個正方形的View已經(jīng)非常實用了。比如繼承ImageView之后像上面一樣overrideonMeasure()方法就可以得到一個一直都是正方形顯示的View。那么,既然我們已經(jīng)支持了寬、高1:1了,為什么不支持任意的寬高比呢。

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)

    var width = getMeasuredWidth()
    var height = getMeasuredHeight()
    var widthWithoutPadding = width - paddingLeft - paddingRight
    var heightWithoutPadding = height - paddingTop - paddingBottom

    var maxWidth = (heightWithoutPadding * RATIO).toInt()
    var maxHeight = (widthWithoutPadding / RATIO).toInt()

    if (widthWithoutPadding > maxWidth) {
        width = maxWidth + paddingLeft + paddingRight
    } else {
        height = maxHeight + paddingTop + paddingBottom
    }

    setMeasuredDimension(width, height)
}

上面的代碼就可以支持任意的寬高比了。看看效果(比例7:3):


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容