要繪制一個view那么我們就需要告訴系統(tǒng)這個view的大小以及這個view繪制的位置。如何知道大泻衔洹次氨?必然是經(jīng)過了測量才能知道view的大小哮伟。而這個測量的過程就是在onMeasure( )
方法中完成的萄涯。
說view的測量,MeasureSpec
這個類是怎么也無法跳過的闻书。MeasureSpec
是一個32位的int值名斟,其中int值的高兩位為測量模式,低30位為測量的大小魄眉。
測量模式呢主要是下面的三種:
- EXACTLY模式
該模式為精確模式砰盐。什么時候用到的是這種模式呢?當我們將layout_width
orlayout_height
設(shè)置為具體數(shù)值的時候坑律,如100dp
或者是match_parent
的時候岩梳。這時系統(tǒng)就是調(diào)用的精確模式 - AT_MOST
該模式為最大值模式。當我們將layout_width
orlayout_height
設(shè)置為wrap_content
的時候晃择,系統(tǒng)調(diào)用的就是該模式冀值。此時控件大小是隨著子控件或者內(nèi)容的變化而變化的,只要不超出父控件允許的最大范圍即可 - UNSPECIFIED
這個模式一般只有在我們自定義控件的時候才會用到宫屠,它代表沒有具體的測量模式列疗,view自己想多大就多大。
三種模式中EXACTLY模式是onMeasure( )
方法中默認的測量模式浪蹂。所以抵栈,我們在自定義控件的時候如果沒有重寫onMeasure( )
方法,那么我們就只能使用EXACTLY模式坤次。那么我們在指定控件layout_width
or layout_height
的時候只能設(shè)置為具體的數(shù)值或者match_parent
古劲,這時候如果想讓我們的控件可以wrap_content
,那么我們就必須重寫onMeasure( )
來指定onMeasure( )
的大小缰猴。
由MeasureSpec
我們知道了測量模式绢慢,也知道了測量大小。
我們重寫onMeasure( )
(為啥重寫后面會說)可以得到
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
我們查看super.onMeasure
發(fā)現(xiàn)系統(tǒng)調(diào)用了setMeasuredDimension
方法將測量后的寬高值設(shè)置好。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)
{
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent))
{
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
有上面分析可知胰舆,如果我們要對寬高重新定義,那么我們要重寫onMeasure
如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
setMeasuredDimension(measuredWidth(widthMeasureSpec),measuredHeight(widthMeasureSpec));
}
//measureWidth 和measureHeight 是我們自己定義的方法
//參數(shù)則是對應的MeasureSpec對象蹬挤,包含測量模式和測量值
public int measuredWidth(int measureSpec){
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if(specMode == MeasureSpec.EXACYLY){
result = specSize ;
}else{
result = 200;//這里的200是自己設(shè)置的默認值缚窿,單位為px
if(specMode == MeasureSpec.AT_MOST){
result = Math.min(result,specSize);//如果我們的模式是AAT_MOST就取測量值和默認值中的較小一個。
}
}
return result;
}
//measureHeight的方式和measureWidth的差不多焰扳,這里就不寫出來了倦零,一會又完整代碼
*** ----------------分割線,下面是完整代碼加解釋----------------***
自定義的MyView.class
package com.unibox.dm_setxfermode;
import android.content.Context;
import android.content.pm.PackageItemInfo;
import android.content.res.Resources;import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.View;
/** * Created by Administrator on 2016/2/11 0011. */
public class MyView extends View {
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);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));
}
public int measureWidth(int measureSpec){
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY){
result = specSize;
}else{
result = 200;
if (specMode == MeasureSpec.AT_MOST){
result = Math.min(result,specSize);
}
}
return result;
}
public int measureHeight(int measureSpec){
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY){
result = specSize;
}else {
result =200;
if (specMode == MeasureSpec.AT_MOST){
result = Math.min(result,specSize);
}
}
return result;
}
}
對自定義view的使用activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.unibox.dm_setxfermode.MainActivity">
<com.unibox.dm_setxfermode.MyView
android:id="@+id/mv"
android:background="@color/colorAccent"
android:layout_width="400px"
android:layout_height="200px" />
</RelativeLayout>
下面是效果截圖:
當都是精確值的時候吨悍,我將寬度設(shè)置為400px扫茅,寬度還是默認的200px(這里為了對比,所以用px)育瓜。精確值的情況下葫隙,寬高值都是跟隨我們設(shè)置的值變化的。
當我們把寬高設(shè)置為match_parent
的時候:
當我們把寬高設(shè)置為wrap_content
.*** 其實我們這里已經(jīng)可以猜到寬高就是我們result的默認值****:
到這里簡單的把view的測量總結(jié)了一下躏仇,個人覺新手沒必要在源碼里面轉(zhuǎn)恋脚,轉(zhuǎn)也轉(zhuǎn)不明白。只要知道:
- 控件繪制前都是經(jīng)過系統(tǒng)測量的
- 測量的結(jié)果和模式都在MeasureSpec當中
- 系統(tǒng)默認是精確測量模式焰手,要修改就要重寫onMeasure方法糟描,不然你設(shè)置屬性為wrap_content是不起作用的.