自定義view,你真的理解onMeasure了嗎?

IsabelleChr 8年前發布 | 15K 次閱讀 安卓開發 Android開發 移動開發

疑惑

當android內置view無法實現我們的需求,此刻我們需要自定義view來實現定制需求效果。自定義view主要是通過onMeasure、onLayout、onDraw等實現的。然而很多童鞋在onMeasure中存在很多疑惑,我們已經在xml布局中指明了寬高尺寸大小,為何還要在自定義view中獲取寬高并指定寬高?

實際

在我們寫布局xml文件的時候,layout_width、layout_height等不需要寫具體的尺寸大小,可以是wrap_content(包裹內容)和match_content(填充內容),它們并沒有指定具體的尺寸大小,但是我們繪制view的時候是需要具體的尺寸大小,所以我們需要去處理并設置尺寸大小。

onMeasure是什么?

onMeasure,根據谷歌翻譯字面意思是測量,繼承view寫這個方法的時候,我們可以發現此方法有兩個參數分別為widthMeasureSpec,heightMeasureSpec。這兩個參數是干嘛的呢?根據谷歌翻譯字面意思分別是寬度測量規格、高度測量規格。這兩個參數包含了寬度和高度的信息。具體信息是什么呢?勤快地童鞋會去百度查,主要是測量模式和尺寸大小。

測量模式有三種

  • UNSPECIFIED

    • 父容器沒有對當前View有任何限制,當前View可以任意取尺寸
    </li>
  • EXACTLY

    • 當前的尺寸就是當前View應該取的尺寸
    • </ul> </li>
    • AT_MOST

      • 前尺寸是當前View能取的最大尺寸
      • </ul> </li> </ul>

        那此測量模式跟我們寫的xml布局文件中wrap_content、match_parent、固定的尺寸有什么對應關系呢?

        • wrap_content

          • 對應AT_MOST,如何理解?就是我們想要將大小設置為包裹我們的view內容,那么尺寸大小就是父View給我們作為參考的尺寸,只要不超過這個尺寸就可以啦,具體尺寸就根據我們的需求去設定。
          </li>
        • match_parent

          • 對應EXACTLY,如何理解?就是要利用父View給我們提供的所有剩余空間,而父View剩余空間是確定的,也就是這個測量模式的整數里面存放的尺寸。
          • </ul> </li>
          • 固定尺寸(如50dp)

            • 對應EXACTLY,如何理解?用戶自己指定了尺寸大小,我們就不用再去干涉了,當然是以指定的大小為主啦。
            • </ul> </li> </ul>

              實踐

              理論終究是理論,實踐才能加深我們對此理解,接下來我們就自定義一個CustomView來驗證理論是否正確?

              此處我們需要自定義一個默認尺寸大小布局屬性,該怎么做呢?

              首先我們需要在res/values/styles.xml文件里面聲明一個我們自定義的屬性:

              <resources>
                  <!--name為聲明的"屬性集合"名,可以隨便取,但是最好是設置為跟我們的View一樣的名稱-->
                  <declare-styleable name="CustomView">
                      <!--聲明我們的屬性,名稱為default_size,取值類型為尺寸類型(dp,px等)-->
                      <attr name="default_size" format="dimension" />
                  </declare-styleable>
              </resources>

              然后在布局文件用我們的自定義的屬性,如下:

              <?xml version="1.0" encoding="utf-8"?>
              <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  xmlns:tools="http://schemas.android.com/tools"
                  xmlns:ctv = "http://schemas.android.com/apk/res-auto"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent"
                  android:paddingBottom="@dimen/activity_vertical_margin"
                  android:paddingLeft="@dimen/activity_horizontal_margin"
                  android:paddingRight="@dimen/activity_horizontal_margin"
                  android:paddingTop="@dimen/activity_vertical_margin"
                  tools:context="cn.jianke.customview.MainActivity">
                  <cn.jianke.customview.CustomView
                          android:layout_width="wrap_content"
                          android:layout_height="100dp"
                          android:background="@color/colorAccent"
                          ctv:default_size="50dp"/>
              </RelativeLayout>

              注意:需要在根標簽(RelativeLayout )里面設定命名空間,命名空間名稱可以隨便取,比如ctv,命名空間后面取得值是固定的:

              最后在我們自定義View里面將我們自定義的屬性值取出來,在構造函數中,有個AttributeSet屬性,通過它我們可以把布局里面屬性取出來,代碼如下:

              public CustomView(Context context, AttributeSet attrs) {
                      super(context, attrs);
                      //第二個參數就是我們在styles.xml文件中的<declare-styleable>標簽
                      //即屬性集合的標簽,在R文件中名稱為R.styleable+name
                      TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView);

                  //第一個參數為屬性集合里面的屬性,R文件名稱:R.styleable+屬性集合名稱+下劃線+屬性名稱
                  //第二個參數為,如果沒有設置這個屬性,則設置的默認的值
                  defaultSize = a.getDimensionPixelSize(R.styleable.CustomView_default_size, 100);
              
                  //最后記得將TypedArray對象回收
                  a.recycle();
              }</code></pre> 
              

              接下來我們就可以在onMeasure計算該view的大小,并在特定的測量模式下設置特定的尺寸大小值。

              onMeasure代碼如下:

              @Override
                  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
                      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
                      // 獲取合適的寬度值
                      int width = getProperSize(defaultSize, widthMeasureSpec);
                      // 獲取合適的高度值
                      int height = getProperSize(defaultSize, heightMeasureSpec);
                      // 設置寬高尺寸大小值,此方法決定view最終的尺寸大小
                      setMeasuredDimension(width, height);
                  }
              
                      /**
                   * 獲取合適的大小
                   * @author leibing
                   * @createTime 2016/09/19
                   * @lastModify 2016/09/19
                   * @param defaultSize 默認大小
                   * @param measureSpec 測量規格
                   * @return
                   */
                  private int getProperSize(int defaultSize, int measureSpec){
                      int properSize = defaultSize;
                      int mode = MeasureSpec.getMode(measureSpec);
                      int size = MeasureSpec.getSize(measureSpec);
                      switch (mode){
                          case MeasureSpec.UNSPECIFIED:
                              // 沒有指定大小,設置為默認大小
                              properSize = defaultSize;
                              break;
                          case MeasureSpec.EXACTLY:
                              // 固定大小,無需更改其大小
                              properSize = size;
                              break;
                          case MeasureSpec.AT_MOST:
                              // 此處該值可以取小于等于最大值的任意值,此處取最大值的1/4
                              properSize = size / 4;
                      }
              
                      return properSize;
                  }

              效果圖如下(寬為wrap_content(寬度最大值的1/4),高度100dp):

              CustomView.gif

              大家理解onMeasure了嗎?是不是很簡單?大家動動手,一切會變得簡單。

               

               

              來自:http://www.jianshu.com/p/df387b08c76f

               

 本文由用戶 IsabelleChr 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!