Android自定義View注意事項

xxq2dream發表於2018-07-30

Android自定義View系列

自定義View的分類

  • 繼承View重寫onDraw方法

主要用於實線不規則的效果,即這種效果不方便通過佈局的組合方式來實現。相當於就是得自己“畫”了。採用這種方式需要自己支援wrap_content,padding也需要自己處理

  • 繼承ViewGroup派生特殊的Layout

主要用於實現自定義的佈局,看起來很像幾種View組合在一起的時候,可以使用這種方式。這種方式需要合適地處理ViewGroup的測量和佈局,並同時處理子元素的測量和佈局過程。比如自定義一個自動換行的LinerLayout等。

  • 繼承特定的View,比如TextView

這種方法主要是用於擴充套件某種已有的View,增加一些特定的功能。這種方法比較簡單,也不需要自己支援wrap_content和padding。

  • 繼承特定的ViewGroup,比如LinearLayout

這種方式也比較常見,和上面的第2種方法比較類似,第2種方法更佳接近View的底層。

自定義View有多種方式,需要根據實際需要選擇一種簡單低成本的方式來實現

自定義View需要注意的地方

  • 讓View支援wrap_content

直接繼承View和ViewGroup的控制元件需要在onMeasure方法中處理wrap_content的方法。處理方法是在wrap_content的情況下設定一個固定的尺寸

//處理wrap_content的套路
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    //處理WAP_CONTENT
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(200,200);
    }else if (widthSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(200, heightSize);
    }else if (heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(widthSize, 200);
    }
}
複製程式碼
  • 讓View支援padding

直接繼承View的控制元件需要在onDraw方法中處理padding,否則使用者設定padding屬性就不會起作用。直接繼承ViewGroup的控制元件需要在onMeasure和onLayout中考慮padding和子元素的margin對其造成的影響,不然將導致padding和子元素的margin失效。

@Override
public void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //獲取padding,然後根據實際情況處理就好
    mPaddingLeft = getPaddingLeft();
    mPaddingRight = getPaddingRight();
    mPaddingTop = getPaddingTop();
    mPaddingBottom = getPaddingBottom();
    mWidth = getWidth() - mPaddingLeft - mPaddingRight;
    mHeight = getHeight() - mPaddingTop - mPaddingBottom;
}
複製程式碼
  • 儘量不要在View中使用Handler

View中已經提供了post系列方法,完全可以替代Handler的作用。

@UiThread
public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
        
    ...
        
    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
    
        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }

    public boolean postDelayed(Runnable action, long delayMillis) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.postDelayed(action, delayMillis);
        }
    
        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().postDelayed(action, delayMillis);
        return true;
    } 
    
    ...
}

複製程式碼
  • View中如果有執行緒或者動畫,需要及時停止

在View的onDetachedFromWindow方法可以停止執行緒和動畫,因為當View被remove或是包含此View的Activity退出時,就會呼叫View的onDetachedFromWindow方法。如果不處理的話很可能會導致記憶體洩漏

  • View帶有滑動巢狀時,需要處理好滑動衝突問題

  • 在View的onDraw方法中不要建立太多的臨時物件,也就是new出來的物件。因為onDraw方法會被頻繁呼叫,如果有大量的臨時物件,就會引起記憶體抖動,影響View的效果


今天你進步了嘛?歡迎關注我的微信公眾號,和我一起每天進步一點點!

AntDream

相關文章