Android关于View的知识
2017-12-16 本文已影响25人
zivxia
1、View的getWidth()和getMeasuredWidth()有什么区别吗?
首先看下getWidth()和getMeasuredWidth()的源码
getWidth()
/**
* Return the width of the your view.
*
* @return The width of your view, in pixels.
*/
@ViewDebug.ExportedProperty(category = "layout")
public final int getWidth() {
return mRight - mLeft;
}
返回View的宽度,再来看看mRight和mLeft
/**
* The distance in pixels from the left edge of this view's parent
* to the left edge of this view.
* {@hide}
*/
@ViewDebug.ExportedProperty(category = "layout")
protected int mLeft;
/**
* The distance in pixels from the left edge of this view's parent
* to the right edge of this view.
* {@hide}
*/
@ViewDebug.ExportedProperty(category = "layout")
protected int mRight;
/**
* Right position of this view relative to its parent.
*
* @return The right edge of this view, in pixels.
*/
@ViewDebug.CapturedViewProperty
public final int getRight() {
return mRight;
}
mLeft与mRight类似,所以getWidth()等于View右边相对于父View的距离减去View左边相对于父View的距离,也就是View显示时的真实宽度。那么mLeft和mRight又是素描时候确定的呢?继续来看源码,发现在View的layout方法中调用了setFrame方法来初始化mLeft、mRight的值。
/**
* Assign a size and position to this view.
*
* This is called from layout.
*
* @param left Left position, relative to parent
* @param top Top position, relative to parent
* @param right Right position, relative to parent
* @param bottom Bottom position, relative to parent
* @return true if the new size and position are different than the
* previous ones
* {@hide}
*/
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (DBG) {
Log.d("View", this + " View.setFrame(" + left + "," + top + ","
+ right + "," + bottom + ")");
}
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
// Remember our drawn bit
int drawn = mPrivateFlags & PFLAG_DRAWN;
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position
invalidate(sizeChanged);
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
mPrivateFlags |= PFLAG_HAS_BOUNDS;
if (sizeChanged) {
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
// If we are visible, force the DRAWN bit to on so that
// this invalidate will go through (at least to our parent).
// This is because someone may have invalidated this view
// before this call to setFrame came in, thereby clearing
// the DRAWN bit.
mPrivateFlags |= PFLAG_DRAWN;
invalidate(sizeChanged);
// parent display list may need to be recreated based on a change in the bounds
// of any child
invalidateParentCaches();
}
// Reset drawn bit to original value (invalidate turns it off)
mPrivateFlags |= drawn;
mBackgroundSizeChanged = true;
mDefaultFocusHighlightSizeChanged = true;
if (mForegroundInfo != null) {
mForegroundInfo.mBoundsChanged = true;
}
notifySubtreeAccessibilityStateChangedIfNeeded();
}
return changed;
}
而layout方法是在View进行布局确定自己以及子View位置的时候调用。
在来看看getMeasuredWidth()。
getMeasuredWidth()
/**
* Like {@link #getMeasuredWidthAndState()}, but only returns the
* raw width component (that is the result is masked by
* {@link #MEASURED_SIZE_MASK}).
*
* @return The raw measured width of this view.
*/
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
/**
* Width as measured during measure pass.
* {@hide}
*/
@ViewDebug.ExportedProperty(category = "measurement")
int mMeasuredWidth;
返回的是View的测量宽,再来看看这个mMeasuredWidth是在哪里进行赋值的,
/**
* Sets the measured dimension without extra processing for things like optical bounds.
* Useful for reapplying consistent values that have already been cooked with adjustments
* for optical bounds, etc. such as those from the measurement cache.
*
* @param measuredWidth The measured width of this view. May be a complex
* bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
* @param measuredHeight The measured height of this view. May be a complex
* bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
*/
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
/**
* <p>This method must be called by {@link #onMeasure(int, int)} to store the
* measured width and measured height. Failing to do so will trigger an
* exception at measurement time.</p>
*
* @param measuredWidth The measured width of this view. May be a complex
* bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
* @param measuredHeight The measured height of this view. May be a complex
* bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
*/
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
继续追踪发现是在View的measure中,所以mMeasuredWidth是在Measure阶段结束时获取的测量值。因为View的绘制流程是Measure、Layout、Draw。一般情况下getMeasureWidth()与getWidth()是一致的,但是也有一些特俗情况,比如在onLayout方法中改变了View的宽高,这时这两个值就不一样的了。但是getWidth()是获取layout结束时View的宽值,是View显示在界面上的真实宽高,所以一般在onLayout结束之后获取view的宽高是比较靠谱的。
2、如何在onCreate中拿到View的宽度和高度
- 第一种就是利用上文的getWidth获取View的宽高,在View onLayout方法之后进行调用这个方法,这时候我们可以通过post将一个runnable添加到消息队列的尾部,然后等待主线程Looper调用此runnable的时候,View的初始化也就好了。
view.post(new Runnable() {
@Override
public void run() {
int width = view.getWidth();
int height = view.getHeight();
}
})
- 使用ViewTreeObserver的回调来完成,
final ViewTreeObserver viewTreeObserver = btn4.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
@Override
public void onGlobalLayout() {
Log.e(TAG, "btn4的宽为: " + btn4.getWidth());
Log.e(TAG, "btn4的高为: " + btn4.getHeight());
viewTreeObserver.removeOnGlobalLayoutListener(this);
}
});
onGlobalLayout会被回调多次,当获取到宽高后要将viewTreeObserver注销掉。
- 利用onWindowFocusChanged
onWindowFocusChanged是Activity的回调方法,会被回调多次,当Activity的窗口获取到焦点和失去焦点时都会被调用一次。
/**
* Called when the current {@link Window} of the activity gains or loses
* focus. This is the best indicator of whether this activity is visible
* to the user. The default implementation clears the key tracking
* state, so should always be called.
*
* <p>Note that this provides information about global focus state, which
* is managed independently of activity lifecycles. As such, while focus
* changes will generally have some relation to lifecycle changes (an
* activity that is stopped will not generally get window focus), you
* should not rely on any particular order between the callbacks here and
* those in the other lifecycle methods such as {@link #onResume}.
*
* <p>As a general rule, however, a resumed activity will have window
* focus... unless it has displayed other dialogs or popups that take
* input focus, in which case the activity itself will not have focus
* when the other windows have it. Likewise, the system may display
* system-level windows (such as the status bar notification panel or
* a system alert) which will temporarily take window input focus without
* pausing the foreground activity.
*
* @param hasFocus Whether the window of this activity has focus.
*
* @see #hasWindowFocus()
* @see #onResume
* @see View#onWindowFocusChanged(boolean)
*/
public void onWindowFocusChanged(boolean hasFocus) {
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus){
Log.e(TAG, "btn4的宽为: " + btn4.getWidth());
Log.e(TAG, "btn4的高为: " + btn4.getHeight());
}
}
当然还可以通过view的measure来获得view的宽高,但是这种方法限制较多也比较麻烦。个人还是觉得通过view.post这种方法比较好,写法简单也不要考虑其他两种会被调用多次的问题。