自定义View(三)

onLayout

从源码看起:在performTraversals方法中首先调用了performMeasure,接下来便调用了performLayout。

performLayout(lp, mWidth, mHeight);```

在performLayout方法中调用了layout方法:

host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());```

host就是一个View对象,进入View源码,找到layout方法:

    public void layout(int l, int t, int r, int b) {
    boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList listenersCopy =
                        (ArrayList)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }```

setFrame方法确定了子view在父view中的上下左右四个位置。在View里onLayou方法是一个空的实现。因为View的位置是由其父View决定的,所以onLayout方法应该在ViewGroup里实现。

@Override
protected abstract void onLayout(boolean changed,int l, int t, int r, int b);

可以看到,在ViewGroup方法里onLayout方法是一个抽象类,这是因为有各种不同的ViewGroup,比如LinearLayout,RelativeLayout等,layout规则不一样,所以需要具体不同的布局自己实现。这是LinearLayout的实现。

//伪代码
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
void layoutVertical(int left, int top, int right, int bottom) {

    for (int i = 0; i < count; i++) {
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
        } else if (child.getVisibility() != GONE) {
            final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();
            
            final LinearLayout.LayoutParams lp =
                    (LinearLayout.LayoutParams) child.getLayoutParams();
            
            childTop += lp.topMargin;
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
            i += getChildrenSkipCount(child, i);
        }
    }
}

private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}```
最后setChildFrame又调用view的layout方法。

实战

自定义一个ViewGroup

public class NewViewGroup extends ViewGroup {
    public NewViewGroup(Context context) {
        super(context);
    }

    public NewViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
        int childCount = getChildCount();
        int mTop = 0;
        for (int a=0;a

重写onMeasure,测量子View大小,否则不显示,在onLayout方法里遍历子view并为每一个view设置布局。布局里有三个button。显示结果如下:


自定义View(三)_第1张图片
截图

getMeasureWidth,getMeasureHeight与getWidth,getHeight的区别。

public final int getWidth() {  
    return mRight - mLeft;  
}
public final int getHeight() {  
    return mBottom - mTop;  
}
public final int getMeasuredWidth() {  
    return mMeasuredWidth;  
}
public final int getMeasuredHeight() {  
    return mMeasuredHeight;  
}```

getMeasureXXX和getXXX的区别在于,getMeasureXXX是在onMeasure之后确定的,而getXXX是在onLayout之后确定的。一般情况下的到的值是一样样的,但重写了layout方法,并修改了layout里的值的情况除外,比如:

//这里没有调用onLayout是因为在View中onLayout是一个空实现,关键代码在layout实现
@Override
public void layout(int l, int t, int r, int b) {
super.layout(l, t+10, r, b);
}```
这个时候getMeasureHeight和getHeight方法得到的数值不一样。

在Activity中获取view宽高时得到的数据为0

因为Activity的生命周期和view的创建周期不一致,在onCreate方法中调用getMeasureXXX或getXXX可能view还没有绘制完成。

解决方案:

  • onWindowFocusChanged
    
@Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if (hasFocus){
            Log.d("-----------",mNewView.getHeight()+"");
            Log.d("***********",mNewView.getMeasuredHeight()+"");
        }
    }```
* view.post()
    mNewView.post(new Runnable() {
        @Override
        public void run() {
            Log.d("-----------",mNewView.getHeight()+"");
            Log.d("***********",mNewView.getMeasuredHeight()+"");
        }
    });
* ViewTreeObserver
    ViewTreeObserver observer = mNewView.getViewTreeObserver();
    observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
        @Override
        public void onGlobalLayout() {

//只兼容api16以上的 mNewView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
Log.d("-----------", mNewView.getHeight() + "");
Log.d("***********", mNewView.getMeasuredHeight() + "");
}
});

* view.measure

int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec,heightMeasureSpec);

int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();

不能用于match_parent。

你可能感兴趣的:(自定义View(三))