自定義View之總結

自定義View之總結

文章來自:Android程序員日記

作者:賢榆的魚

參考閱讀時間:5 min 15s

導讀語:自定義控件只看這一篇,是不夠的!

前言

在之前我先后寫了"自定義View之擴展式"、"自定義View之復合式"、"自定義View之完全自定義",在這三篇文章中我都分別給出了一個例子。當然了,例子其實是相對比較簡單的,主要原因可能是個人水平有限吧!但盡管如此,我也盡可能把我要講的內容表述的更清晰一些!那么為了更好的在我們的腦海里構建這個知識框架(這句話太專業,讓我寫起來都感到了滿滿的壓力!其實就是讓某些重要的或是會用到的內容,能夠在我們大腦里留下個更深刻的印象,并讓他們之間有更多的聯系。)以便于在我們需要的時候,更容易提取出來用!為此,寫個總結也是一個不錯的方法!這個觀點是來自《暗時間》!

正文

我們暫且將自定義View分為了擴展式、復合式和完全自定義三種類型(當然我們也可以按別的分)!他們看起來確實有一定的從易到難的梯度關系!不管怎樣,我們將這前面三遍中的知識點和自定義View的一些知識點做一個總結!好處就是——可能會有跟多人關注我的“Android程序員日記”的公眾號吧!

都說程序員都是從零開始計數的,那我們開始吧!

[ 0 ]關于自定義View的三個構造方法

  • 一個參數的構造方法:在用代碼動態的添加我們的自定義view時調用。
  • 兩個參數的構造方法:在使用xml +inflate的方法添加控件時會調用,里面多了一個AttributeSet類型的值
  • 三個參數的構造方法:這個構造方法系統是不調用的,需要我們顯示調用并給defStyleAttr傳值,多了一個defStyleAttr參數,這是這個view引用style資源的屬性參數,也就是我們可以在style中為自定義View定義一個默認的屬性樣式然后添加進來!

[ 1 ]關于三種自定義View

  • 擴展式:

    擴展式自定義View繼承自Android原生特定的View如:TextView,ImageView等等。我們通過重寫onDrow()等回調方法對其進行擴展!使其實現我們想要的更能或樣式!

    注:該方法實現的自定義View控件不需要自己支持wrap_content和padding。

  • 組合式:

    組合式自定義View繼承自ViewGrop的子View如:LinearLayout、RelativieLayout等。當某種效果看起來像幾種View組合在一起的時候,都可以使用這種方式實現。

    注:該方式實現自定義View不需要自己處理ViewGroup的測量和布局這兩個過程。

  • 完全自定義:
    完全自定義View繼承自View(android中所有控件的基類),通常實現一些不方便布局的組合方式來達到的,需要靜態或動態地顯示一些不規則的控件或圖形!

    注:該方法實現的自定義View控件需要自己支持wrap_content和padding。

[ 2 ]常用的回調方法

  • onFinishInflate():加載完XML組件后回調
  • onSizeChanged():組件大小改變時回調
  • onMeasure():回調該方法來進行測量(在該方法中實現對wrap_content支持的代碼)
  • onLayout():回調該方法來顯示位置
  • onTouchEvent():監聽到觸摸事件回調,也是實現交互非常重要的回調方法
  • onDraw():回調該方法對我們的控件進行繪制

[ 3 ]為自定義View添加并使用自定義屬性的過程

Step 1 : 在Values下創建attrs.xml(當然也可以以別的名字命名,無限制),然后在該文件中添加自定義View的自定義屬性!

Step 2 : 在自定義View的構造方法中獲取自定義屬性值,并將值配予相應的位置!

Step 3 :在xml中使用自定義控件及配置其自定義屬性

注:在xml使用自定義控件時一定要加 :

xmlns:custom="http://schemas.android.com/apk/res-auto"

當然你也可以寫成

xmlns:custom="http://schemas.android.com/apk/res/com.timen4.t3"(com.timen4.t3是應用的報名)

這兩種方式沒有本質上的區別。至于custom隨便你喜歡命名什么都可以。

[ 4 ]完全自定義控件中我們自己支持wrap_content和padding的代碼

  • MeasureSpec
    • 簡介:MeasureSpec代表一個32位int值,高2位代表SpecMode(測量模式),低30位代表SpecSize(指定模式下的規格大小)。

    • 三種SpecMode與SpecSize

      1.UNSPECIFIED:父容器自容器無限制,要多大給多大,這種情況一般用于系統內部。一般我們不關注蓋模式。

      2.EXACTLY:父容器已檢測出了View所需要的精確大小,這時V接我的最終大小就是SpecSize所給定的值。它對應于LayoutParams中的match_parent和具體的數值兩種模式

      3.AT_MOST:父容器制定了一個可用大小即SpecSize,View的大小不能大于這個值,具體值要看不同View的具體實現。它對應于LayoutParams中的wrap_content。

    • MeasureSpec和LayoutParams的對應關系
      普通的View(即非頂層View)的MeasureSpec由父容器的MeasureSpec和自身的LyoutParams來共同決定的,MeasureSpec一旦確定后,onMeasure中就可以確定View的測量寬高。但對于頂層View(即)

  • 對wrap_content的支持

    在onMeasure()方法中實現對wrap_content的支持

      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          super.onMeasure(widthMeasureSpec, heightMeasureSpec);
          int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
          int widthSpecSize= MeasureSpec.getSize(widthMeasureSpec);
          int heightSpectMode = MeasureSpec.getMode(heightMeasureSpec);
          int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec);
          //這里就是對wrap_content的支持
          if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpectMode==MeasureSpec.AT_MOST){
              //這里設定的根據你自己自定義View的情況而定
              setMeasuredDimension(200,200);
          }else if(widthSpecMode==MeasureSpec.AT_MOST){
              setMeasuredDimension(200,heightSpecSize);
          }else if (heightSpectMode==MeasureSpec.AT_MOST){
              setMeasuredDimension(widthSpecSize,200);
          }
      }
    
  • 對padding的支持

    在onDraw()方法中實現對padding的支持,其實就是在或控件時考慮到就padding就好了。如果不自己實現那么你對該自定義View設置padding將是無效的!
    
      @Override
         protected void onDraw(Canvas canvas) {
          super.onDraw(canvas);
          //這里是對畫一個圓形的View的padding支持
          final int paddingLeft = getPaddingLeft();
          final int paddingRight = getPaddingRight();
          final int paddingTop = getPaddingTop();
          final int paddingBottom=getPaddingBottom();
          int width = getWidth()-paddingLeft-paddingRight;
          int height = getHeight()-paddingBottom-paddingTop;
          int radius = Math.min(width,height)/2;
          canvas.drawCircle(paddingLeft+width/2,paddingTop+height/2,radius,mPaint_while);
      }
    

[ 5 ]為自定義View定義并實現接口回調的過程

本段示例代碼來自"自定義View之復合式",為了總結的需要,做了一些注釋上的修改,并簡化為實現一個回調方法!

Step 1: 定義回調接口(自定義View類中操作)

public interface TopBarClickListener {
    void leftClick();
}

Step 2: 暴露回調接口(自定義View類中操作)

public void setOnTopbarClickListener(TopBarClickListener mListener){
    this.mListener=mListener;
}

Step 3: 調用回調接口(自定義View類中操作)

private void bindEvents() {
    mLeftButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            if(mListener!=null){
                mListener.leftClick();
            }
        }
    });
}

Step 4: 實現回調回調接口及方法(在實例化自定義View對象的類中)

mtopBar.setOnTopbarClickListener(new TopBar.TopBarClickListener() {
    @Override
    public void leftClick() {
        Toast.makeText(MainActivity.this,"上一張",Toast.LENGTH_SHORT).show();
        if (index<=0){
            index=3;
        }else{
            index--;
        }
        iv_image.setImageResource(images.get(index));
    }

后記

好了,這篇總結就這么多了!我必須再次強調一次,本文最最重要的一句話,就是放在最前面的導讀語——自定義控件只看這一篇,是不夠的!這句話我沒開玩笑,你不信也沒關系,反正我是不會在哪我的美貌做賭注了!

其實,不只是我對自己寫的東西不自信,而是無論是我這一篇,還是某個大牛寫的某一篇。我都覺得自定義View看某一篇是不夠的!對于初學者或新手而言更是如此。因為正如我在我在前言中寫道的一樣,我們都在構造自己的知識架構。我們需要從更多的角度,去理解我們學到的或是要學習的內容!這樣才更有助于我們去理解和記憶!也才更有助于我們去實際應用!

最后總結一句話吧——“我們學習的任何事物”看“某一篇”都是不夠的!


喜歡可以關注我微信公眾號
分享絕不止于Android!


喜歡請關注公眾Android程序員日記
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容