自定义View之onLayout方法学习

关于onLayout的学习,也是在基于View视图树的递归调用实现。

本篇想说明的是,不去深究View源码关于onLayout,以及layout方法的实现原理。知道大概,目的是在会用。但是需要了解并掌握View内部关于onLayout方法的相关API使用。

在ViewGroup中,onLayout是一个抽象方法,所以如果继承了ViewGroup类,除了添加构造方法外,还必须要重写onLayout方法。

/** 
 * 当这个view和其子view被分配一个大小和位置时,被layout调用。 
 * @param changed 当前View的大小和位置改变了 
 * @param left 左部位置(相对于父视图) 
 * @param top 顶部位置(相对于父视图) 
 * @param right 右部位置(相对于父视图) 
 * @param bottom 底部位置(相对于父视图) 
 */  
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
此方法是在ViewGroup中的layout方法所调用:

@Override
    public final void layout(int l, int t, int r, int b) {
        if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
            if (mTransition != null) {
                mTransition.layoutChange(this);
            }
            super.layout(l, t, r, b);
        } else {
            // record the fact that we noop'd it; request layout when transition finishes
            mLayoutCalledWhileSuppressed = true;
        }
    }
而ViewGroup中的layout方法,是继承自View类当中的layout方法,然后在看一下

View类中layout的方法实现。

/** 
 * 给View和其所有子View分配大小和位置 
 * 
 * 这是布局的第二个阶段(第一个阶段是测量)。在这个阶段中,每个父视图需要去调用layout去为他所有的子视图确定位置 
 * 派生的子类不应该重写layout方法,应该重写onLayout方法,在onlayout方法中应该去调用每一个view的layout 
 */  
public void layout(int l, int t, int r, int b) {  
    // 将当前视图的左上右下记录为old值(参数中传入的为新的l,t,r,b值)  
    int oldL = mLeft;  
    int oldT = mTop;  
    int oldB = mBottom;  
    int oldR = mRight;  
      
    // setFrame方法的作用就是将新传入的ltrb属性赋值给View,然后判断当前View大小和位置是否发生了变化并返回  
    boolean changed = setFrame(l, t, r, b);  
      
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {  
        // 调用onLayout回调方法,具体实现由重写了onLayout方法的ViewGroup的子类去实现(后面详细说明)  
        onLayout(changed, l, t, r, b);  
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;  
  
        // 调用所有重写了onLayoutChange监听的方法,通知View大小和位置发生了改变  
        ListenerInfo li = mListenerInfo;  
        if (li != null && li.mOnLayoutChangeListeners != null) {  
            ArrayList<OnLayoutChangeListener> listenersCopy =  
                    (ArrayList<OnLayoutChangeListener>)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);  
            }  
        }  
    }  
    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;  
} 
在这里边也有onLayout()方法,在View中的onLayout();中是一个实现了个空方法。

 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

关于layout,onLayout在View与ViewGroup中就这四个。

我们在ViewGroup中重写onLayout的目的

就是设置当前View与其所有的子View,在ViewGroup(或其继承ViewGroup的Layout)父布局当中的位置。

在onLayout方法中,我们可以获取当前布局,在父布局当中的位置:

getLeft();//子View左边界到父view左边界的距离
getTop();//子View上边界到父view上边界的距离
getRight();//子View右边界到父View左边界距离
getBottom();//子View下边界到父View上边界距离。

还可以在onMeasure()方法执行后在onLayout方法中,测量出当前布局View,在父布局中的
宽度:getWidth();//在onLayout()方法中取得相对在父布局中的宽、高
高度:getHeight();//取值在getMeasuredHeight()方法之后。
此处要注意区分子View的

childView.getMeasuredWidth();//在onMeasure()方法之后取得View的实际宽、高

childView.getMeasuredHeight();

两个方法取值的区别就在如果View的大小没有超过父布局View的大小,二者是相等的。getWidth() == childView.getMeasuredWidth();

如果childView宽、高大于了父布局的宽、高 则:getWidth() + 超出区域 = childView.getMeasuredWidth();

来源:官方文档。详见jafsldkfj博客。

而计算viewChild在父布局中的位置,可以通过:

viewChild.layout();方法实现View的布局定位。

在ViewGroup中重写onLayout方法,在onLayout方法中对子View,viewChild.layout();布局定位,而layout中又调用了onLayout()方法

所以实现了View视图树递归调用的原理。跟onMeasure类似。

(在ViewGroup中,layout()方法是个final类型,所以不能被子类调用。在ViewGroup中,没有measure,onMeasure方法。)

(在View中,measure()方法是个final类型,也不能被子类调用。但是layout是一个普通方法,onLayout是一个实现了空方法。)

添加点一Tip:
① 在代码注释文档中/**{@hide}*/ 表示该方法不向子类开发,不提供外部使用。顺便可以学习下Java doc注释。


最终的学习,还是为了在项目中灵活运用,实践。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章