天天看點

Android自定義View小結篇

近來忙着銀聯支付接口對接,部落格也沒有更新。在前幾篇Android自定義View基礎篇當中有幾處地方明顯不足。例如:view中使用handler 就不合理,view中自帶了post方法。在view小結篇當中主要講解自定義過程中的一些疑問及踩過的坑。

1、自定義View中的wrap_content

從ViewRoot的performTraversals開始,經過measure,layout,draw 三個流程。draw流程結束以後就可以在螢幕上看到view了。

經常我們會遇到自定義View在xml中設定wrap_content屬性效果跟match_parent一樣的。這個時候我們就需要在代碼中處理了:

int   widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
       int   widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
       int   heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
       int   heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(, );
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(, heightSpecSize);
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSpecSize, );
        }
           

一般的處理方式是固定寬高,MeasureSpec.AT_MOST模式對應的xml布局中的wrap_content

2、自定義View中的padding

如果你繼承View,一般情況下在onDraw()方法處理;如果你繼承viewGroup,那在onMeasure和onLayout裡面也要考慮。

final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop = getPaddingTop();
        final int paddingBottom = getPaddingBottom();
           

擷取目前控件的padding值。

3、線程、動畫

view提供了post方法處理線程,view的動畫或者線程需要停止,可以考慮在onDetachedFromWindow裡面來做。

4、在activity擷取View的寬高

measure的過程和activity的生命周期沒有任何關系。你無法确定在哪個生命周期執行完畢以後 view的measure過程一定完成。你可以使用如下幾種方法來擷取。

方法一:

@Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if (hasFocus) {
            int width = custom.getMeasuredWidth();
            int height = custom.getMeasuredHeight();
        }
    }
           

方法二:

@Override
    protected void onStart() {
        super.onStart();
        custom.post(new Runnable() {
            @Override
            public void run() {
                int width = custom.getMeasuredWidth();
                int height = custom.getMeasuredHeight();
            }
        });
    }
           

方法三:

@Override
    protected void onStart() {
        super.onStart();
        ViewTreeObserver observer = custom.getViewTreeObserver();
        observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
            @Override
            public void onGlobalLayout() {
                int width = custom.getMeasuredWidth();
                int height = custom.getMeasuredHeight();
                custom.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        });
    }
           

5、簡單案例

繼承View的案例:

Android自定義View小結篇
public class CircleView extends View {

    final Paint paint = new Paint();

    public CircleView(Context context) {
        this(context, null);
    }

    public CircleView(Context context, AttributeSet attrs) {
        this(context, attrs, );
    }

    public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.RED);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(, );
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(, heightSpecSize);
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSpecSize, );
        }
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //處理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() - paddingTop - paddingBottom;

        int radius = (int) (Math.min(width, height) /  * (f));

        canvas.drawCircle(getWidth() /  + paddingLeft, getHeight() /  + paddingTop, radius, paint);
    }

    @Override
    public boolean postDelayed(Runnable action, long delayMillis) {
        return super.postDelayed(action, delayMillis);
    }

    /**
     * 從窗體上分離
     */
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
    }
}
           

繼承ViewGroup:

Android自定義View小結篇
public class CustomHorizontalLayout extends ViewGroup {

    //設定預設的控件最小是多少 這裡不提供自定義屬性了 寫死在代碼裡 你們可以自行拓展
    final int minHeight = ;
    final int minWidth = ;

    public CustomHorizontalLayout(Context context) {
        this(context, null);
    }

    public CustomHorizontalLayout(Context context, AttributeSet attrs) {
        this(context, attrs, );
    }

    public CustomHorizontalLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int measureWidth = ;
        int measureHeight = ;

        final int childCount = getChildCount();
        measureChildren(widthMeasureSpec, heightMeasureSpec);

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

        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop = getPaddingTop();
        final int paddingBottom = getPaddingBottom();

        //沒有子控件
        if (childCount == ) {
            if (widthSpecMode == MeasureSpec.AT_MOST || heightSpecMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(minWidth, minHeight);
            } else {
                //否則根據我們的layout屬性來
                setMeasuredDimension(getLayoutParams().width, getLayoutParams().height);
            }
        } else if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
            for (int i = ; i < childCount; i++) {
                final View view = getChildAt(i);
                measureWidth += view.getMeasuredWidth();
                measureHeight = measureHeight >= view.getMeasuredHeight() ? measureHeight : view.getMeasuredHeight();
            }
            setMeasuredDimension(paddingLeft + measureWidth + paddingRight, paddingTop + measureHeight + paddingBottom);
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            for (int i = ; i < childCount; i++) {
                final View view = getChildAt(i);
                measureHeight = measureHeight >= view.getMeasuredHeight() ? measureHeight :view.getMeasuredHeight();
            }
            setMeasuredDimension(paddingLeft + paddingRight + widthSpecSize, paddingTop + paddingBottom + measureHeight);
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            for (int i = ; i < childCount; i++) {
                final View view = getChildAt(i);
                measureWidth += view.getMeasuredWidth();
            }
            setMeasuredDimension(paddingLeft + paddingRight + measureWidth, paddingTop + paddingBottom + heightSpecSize);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop = getPaddingTop();
        final int paddingBottom = getPaddingBottom();
        //左邊初始位置為0
        int childLeft =  + paddingLeft;
        final int childCount = getChildCount();
        for (int i = ; i < childCount; i++) {
            final View childView = getChildAt(i);
            if (childView.getVisibility() == View.GONE) {
                continue;
            }
            final int childWidth = childView.getMeasuredWidth();
            childView.layout(childLeft,  + paddingTop, childLeft + childWidth, paddingTop + childView.getMeasuredHeight());
            childLeft += childWidth;
        }
    }
}
           

如有什麼疑問,請給我留言。