View.java
/**
* <p>
* This is called to find out how big a view should be. The parent
* supplies constraint information in the width and height parameters.
* </p>
*
* <p>
* The actual mesurement work of a view is performed in
* {@link #onMeasure(int, int)}, called by this method. Therefore, only
* {@link #onMeasure(int, int)} can and must be overriden by subclasses.
* </p>
*
*
* @param widthMeasureSpec Horizontal space requirements as imposed by the
* parent
* @param heightMeasureSpec Vertical space requirements as imposed by the
* parent
*
* @see #onMeasure(int, int)
*/
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
// first clears the measured dimension flag
mPrivateFlags &= ~MEASURED_DIMENSION_SET;
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
}
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec); //重点是它
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
throw new IllegalStateException("onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
}
/**
* <p>
* Measure the view and its content to determine the measured width and the
* measured height. This method is invoked by {@link #measure(int, int)} and
* should be overriden by subclasses to provide accurate and efficient
* measurement of their contents.
* </p>
*
* <p>
* <strong>CONTRACT:</strong> When overriding this method, you
* <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
* measured width and height of this view. Failure to do so will trigger an
* <code>IllegalStateException</code>, thrown by
* {@link #measure(int, int)}. Calling the superclass'
* {@link #onMeasure(int, int)} is a valid use.
* </p>
*
* <p>
* The base class implementation of measure defaults to the background size,
* unless a larger size is allowed by the MeasureSpec. Subclasses should
* override {@link #onMeasure(int, int)} to provide better measurements of
* their content.
* </p>
*
* <p>
* If this method is overridden, it is the subclass's responsibility to make
* sure the measured height and width are at least the view's minimum height
* and width ({@link #getSuggestedMinimumHeight()} and
* {@link #getSuggestedMinimumWidth()}).
* </p>
*
* @param widthMeasureSpec horizontal space requirements as imposed by the parent.
* The requirements are encoded with
* {@link android.view.View.MeasureSpec}.
* @param heightMeasureSpec vertical space requirements as imposed by the parent.
* The requirements are encoded with
* {@link android.view.View.MeasureSpec}.
*
* @see #getMeasuredWidth()
* @see #getMeasuredHeight()
* @see #setMeasuredDimension(int, int)
* @see #getSuggestedMinimumHeight()
* @see #getSuggestedMinimumWidth()
* @see android.view.View.MeasureSpec#getMode(int)
* @see android.view.View.MeasureSpec#getSize(int)
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
每个view 中一定存在一个必调的方法, measure(int, int) ,它调用onMeasure(int, int)
onMeasure(widthMeasureSpec, heightMeasureSpec)的作用是什么?
测量view和它的content 得出 widthMeasureSpec heightMeasureSpec 并保存下来。
measure(widthMeasureSpec, heightMeasureSpec)的作用是什么?
通过 父布局提供的 widthMeasureSpec, widthMeasureSpec 来确定这个View 的 大小, 有多高,有多宽,
特别对于一些实现 ViewGroup 的 控件 在某些情况下,当 android:layout_height android:layout_width 不能达到目的时, 可以通过重写 onMeasure(int, int) 来达到调整view高宽的目的。
/**
* <p>This mehod 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) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= MEASURED_DIMENSION_SET;
}
setMeasuredDimension 在 onMeasure(int, int) 中调用,用来存储跟新测量的宽和高, 若不调,可能产生异常。
MeasureSpec介绍及使用详解
MeasureSpec 是 view 中的内部类
一个MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求。一个MeasureSpec由大小和模式组成。它有三种模式:UNSPECIFIED(未指定),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小;EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;AT_MOST(至多),子元素至多达到指定大小的值。
1 父布局传下来的
2 子元素布局文件或代码中设置的
它常用的三个函数:
1.staticint getMode(int measureSpec):根据提供的测量值(格式)提取模式(上述三个模式之一)
2.staticint getSize(int measureSpec):根据提供的测量值(格式)提取大小值(这个大小也就是我们通常所说的大小)
3.staticint makeMeasureSpec(int size,int mode):根据提供的大小值和模式创建一个测量值(格式)
这个类的使用呢,通常在view组件的onMeasure方法里面调用但也有少数例外,看看几个例子:
a.首先一个我们常用到的一个有用的函数,View.resolveSize(intsize,int measureSpec)
public static int resolveSize(int size, int measureSpec) {
return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK;
}
public static int resolveSizeAndState(int size, int measureSpec, int childMe asuredState) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result | (childMeasuredState&MEASURED_STATE_MASK);
}
简单说一下,这个方法的主要作用就是根据你提供的大小和MeasureSpec,返回你想要的大小值,这个里面根据传入模式的不同来做相应的处理。
再看看MeasureSpec.makeMeasureSpec方法,实际上这个方法很简单:
public static int makeMeasureSpec(int size, int mode) {
return size + mode;
}
这样大家不难理解size跟measureSpec区别了。
******************************************
布局步骤
1 measure onMeasure(measureWidth, measureHight) 计算大小
2 layout onLayout(chang, l, t, r, b) 给自己 及 子view分配size, position
3 draw ondraw(canvas) 画图
dispatchDraw 空方法体
getWidth() getMeasureWidth() 区别
getWidth() : view 的布局完成后,view的宽度
getMeasureWidth() : 这是一个过程量,得到的是在最近一次調用measure()方法測量後得到的view的寬度,它僅僅用在測量和layout的計算中。
举例: 有时会通过 child.getMeasureWidth() 的值 根据 Gravity 重心 得出 view 的 4个l t r b 再layout
如 Gridview 中 存在一个方法:
private void setupChild(View child, int position, int y, boolean flow, int childrenLeft,
boolean selected, boolean recycled, int where) {
boolean isSelected = selected && shouldShowSelector();
final boolean updateChildSelected = isSelected != child.isSelected();
final int mode = mTouchMode;
final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
mMotionPosition == position;
final boolean updateChildPressed = isPressed != child.isPressed();
boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
// Respect layout params that are already in the view. Otherwise make
// some up...
AbsListView.LayoutParams p = (AbsListView.LayoutParams)child.getLayoutParams();
if (p == null) {
p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
}
p.viewType = mAdapter.getItemViewType(position);
if (recycled && !p.forceAdd) {
attachViewToParent(child, where, p);
} else {
p.forceAdd = false;
addViewInLayout(child, where, p, true);
}
if (updateChildSelected) {
child.setSelected(isSelected);
if (isSelected) {
requestFocus();
}
}
if (updateChildPressed) {
child.setPressed(isPressed);
}
if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
if (child instanceof Checkable) {
((Checkable) child).setChecked(mCheckStates.get(position));
} else if (getContext().getApplicationInfo().targetSdkVersion
>= android.os.Build.VERSION_CODES.HONEYCOMB) {
child.setActivated(mCheckStates.get(position));
}
}
if (needToMeasure) {
int childHeightSpec = ViewGroup.getChildMeasureSpec(
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
int childWidthSpec = ViewGroup.getChildMeasureSpec(
MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width);
child.measure(childWidthSpec, childHeightSpec);
} else {
cleanupLayoutState(child);
}
final int w = child.getMeasuredWidth();
final int h = child.getMeasuredHeight();
int childLeft;
final int childTop = flow ? y : y - h;
final int layoutDirection = getResolvedLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.LEFT:
childLeft = childrenLeft;
break;
case Gravity.CENTER_HORIZONTAL:
childLeft = childrenLeft + ((mColumnWidth - w) / 2);
break;
case Gravity.RIGHT:
childLeft = childrenLeft + mColumnWidth - w;
break;
default:
childLeft = childrenLeft;
break;
}
if (needToMeasure) {
final int childRight = childLeft + w;
final int childBottom = childTop + h;
child.layout(childLeft, childTop, childRight, childBottom);
} else {
child.offsetLeftAndRight(childLeft - child.getLeft());
child.offsetTopAndBottom(childTop - child.getTop());
}
if (mCachingStarted) {
child.setDrawingCacheEnabled(true);
}
if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)
!= position) {
child.jumpDrawablesToCurrentState();
}
}
ViewGroup.java
1 measureChildren(measureWidth, measureHight) measureChild(child, widthMeasureSpec, heightMeasureSpec)
2 继承 layout(l, t, r, b) onLayout(chang, l, t, r, b)
3 继承 dispatchDraw drawChild
window view
Window 抽象类
|
PhoneWindow
|
DecorView(mDecor) PhoneWindow 内部类, 继承FrameLayout
| |
TextView(title) ViewGroup(mParentContent)
|
ViewGroup
| | |
View View ViewGroup
| | |
View View ViewGroup
Application Part main.xml
window 与 view
Android系 统中的所有UI类都是建立在View和ViewGroup这两个类的基础上的。所有View的子类成为”Widget”,所有ViewGroup的子类成 为”Layout”。View和ViewGroup之间采用了组合设计模式,可以使得“部分-整体”同等对待。ViewGroup作为布局容器类的最上 层,布局容器里面又可以有View和ViewGroup。
window 抽象类 代表 一个phone 界面。 在 里面有个 DecorView 类, DecorView 继承自
FrameLayout, DecorView 是 window 与 view 链接的开始。 DecorView显示界面组件。
除此之外 window 中还定义了窗口的 基本属性 和 基本功能。
Window 在继承关系上 与view 无关系。
当我们运行程序的时候,有一个setContentView()方法,Activity其实不是显示视图(直观上感觉是它),实际上Activity调用 了PhoneWindow的setContentView()方法,然后加载视图,将视图放到这个Window上,而Activity其实构造的时候初始 化的是Window(PhoneWindow),Activity其实是个控制单元,即可视的人机交互界面。
其他 kan app课件
webview
view 的 scrollbar 四种模式
SCROLLBARS_INSIDE_OVERLAY without increasing the padding
SCROLLBARS_INSIDE_INSET increasing the padding
SCROLLBARS_OUTSIDE_OVERLAY without increasing the padding
SCROLLBARS_OUTSIDE_INSET increasing the padding
boolean mOverlayHorizontalScrollbar 表示是否水平方向 scrollbar overlay 模式
boolean mOverlayVerticalScrollbar 表示是否垂直方向 scrollbar overlay 模式
isHorizontalScrollBarEnabled() 表示水平方向的 scrollbar 是否被画出。
GetHorizontalScrollbarHeight() 水平方向 scrollbar 高度。 View 里方法
int getViewHeightWithTitle() {
int height = getHeight();
if (isHorizontalScrollBarEnabled() && !mOverlayHorizontalScrollbar) {
height -= getHorizontalScrollbarHeight();
}
return height;
}
如果水平方向的 scrollbar 被画出且 属于 增加区域的 某种模式 计算viewHight时要减去这块区域的高。
private boolean handleTouchEventCommon(MotionEvent ev, int action, int x, int y) {
long eventTime = ev.getEventTime();
// Due to the touch screen edge effect, a touch closer to the edge
// always snapped to the edge. As getViewWidth() can be different from
// getWidth() due to the scrollbar, adjusting the point to match
// getViewWidth(). Same applied to the height.
x = Math.min(x, getViewWidth() - 1);
y = Math.min(y, getViewHeightWithTitle() - 1);
int deltaX = mLastTouchX – x;
int deltaY = mLastTouchY - y;
int contentX = viewToContentX(x + mScrollX);
int contentY = viewToContentY(y + mScrollY);
x x轴坐标 y y轴坐标 一台手机屏幕的宽高是固定的 (x,y) 手机屏幕
mLastTouchX 上一次触点的 x轴坐标
mLastTouchY 上一次触点的 y轴坐标
mScrollX scrollbar 滑动 x轴距离
mScrollY scrollbar 滑动 y轴距离
viewToContentX(.) webview content x轴坐标 网页+scrollbar
(contentx, contenty) webview content 坐标
OverScroller
介绍
IOS上的bounce功能给人的感觉很爽,当一个可以滚动的区域被拖到边界时,它允许用户将内容拖过界,放手后再弹回来,以一种非常棒的方式提示 了用户边界的存在,是IOS的一大特色。android2.3新增了overscroll功能,听名字就知道应该是bounce功能的翻版,但也许是出于 专利方面的考虑,google的默认实现跟IOS有所不同,它只是在list拖到边界处时做了一个发光的动画,个人觉得体验比IOS差远了。而且这个黄色 的发光在黑色背景下虽然效果不错,在其它背景下可就难说了,因此很多人想要关掉它。
日前google上搜索“android overscroll”,对此效果的介绍很多,但关于其具体使用方式和实现,则很少涉及,偶有提及,也经常答非所问或似是而非,反而误导了别人。于是我查阅了android相关源码,并做了一些测试,在此讲讲我的理解。
首先是overscroll功能本身,在最顶层的View类提供了支持,可通过setOverScrollMode函数控制其出现条件。但其实 View中并没有实现overscroll功能,它仅仅提供了一个辅助函数overScrollBy,该函数根据overScrollMode和内容是否 需要滚动控制最大滚动范围,最后将计算结果传给onOverScrolled实现具体的overscroll功能,但此函数在View类中是全空的。
overscroll功能真正的实现分别在ScrollView、AbsListView、HorizontalScrollView和 WebView中各有一份,代码基本一样。以ScrollView为例,它在处理笔点移动消息时调用overScrollBy来滚动视图,然后重载了 overScrollBy函数来实现具体功能,其位置计算通过OverScroller类实现。OverScroller作为一个计算引擎,应该是一个独 立的模块,具体滚动效果和范围都不可能通过它来设置,我觉得没有必要细看。但滚动位置最终是它给出的,那相关数据肯定要传递给它,回头看 overScrollBy函数,它有两个控制overScroll出界范围的参数,几个实现里面都是取自 ViewConfiguration.getScaledOverscrollDistance,而这个参数的值在我的源码中都是0,而且我没找到任何可 以影响其结果的设置。
真悲催,绕了半天,android的默认实现里面根本没有给出overscroll功能,它只是提供了实现机制,要想用起来还得应用程序自己显式重写相关控件,估计还有一层隐含的意思,法律风险自负。在我的系统中一试,果然一个像素都不能拉出界。但那个闪光是怎么回事呢?
在处理笔点消息处,overScrollBy后面不远处有一段mEdgeGlowTop的操作代码,看名字就像,用它一搜,相关机制就全明白了。 mEdgeGlowTop在setOverScrollMode函数时创建,它使用的图片都是系统中固有的,甚至不能通过theme改变。它的实现原理也 很简单,仅仅是两张png图片的合成,通过透明度的变化制造闪光的效果。更无语的是它既不能被应用程序访问,也不受任何控制,要关闭它的唯一办法是 setOverScrollMode(View.OVER_SCROLL_NEVER)。否则就重写onTouchEvent函数吧,想干啥都可以,只是 得自己做。
谈到overScroll,很多文章都提到了ListView的setOverscrollHeader和 setOverscrollFooter,很多人想通过这个来控制那个闪光效果。这两玩意不但可以通过函数设置,也可以在xml中指定,相当方便。但最后 很多人发现没有任何作用,百思不得其解。其实这两张图片是用来作为overScroll拖过界时的背景的,默认系统不能拖过界,自然永远都看不到,有些定 制的系统中能拖出界几个像素,但也很难看清。
Boolean AllowDoubleTap 允许双击
只有 onPageFinish() 后 AllowDoubleTap=true ,其他时候双击无效。
Int mTouchHighlightRequested set mTouchHighlightRequested to 0 to cause an
immediate drawing of the touch rings 设为0, 区域立即开始高亮。
Region MtouchHighlightRegion = new Region() 代表一块区域,点击网页,产生高亮的区域.
页面可见坐标计算流程
屏幕可见坐标 + view滚动条数据 + webview放缩数据
/**
* Given a distance in view space, convert it to content space. Note: this
* does not reflect translation, just scaling, so this should not be called
* with coordinates, but should be called for dimensions like width or
* height.
*/
private int viewToContentDimension(int d) {
return Math.round(d * mZoomManager.getInvScale());
}
/**
* Given an x coordinate in view space, convert it to content space. Also
* may be used for absolute heights (such as for the WebTextView's
* textSize, which is unaffected by the height of the title bar).
*/
/*package*/ int viewToContentX(int x) {
return viewToContendtDimension(x); 根据 webview 页面的放缩率 完善 Rect坐标
}
private Point mGlobalVisibleOffset = new Point();
// Sets r to be the visible rectangle of our webview in view coordinates
private void calcOurVisibleRect(Rect r) { 计算出 webview 可见区域坐标付给Rect
getGlobalVisibleRect(r, mGlobalVisibleOffset); 计算手机屏幕的可见区域坐标付给Rect
r.offset(-mGlobalVisibleOffset.x, -mGlobalVisibleOffset.y); 根据页面滚动条的数据如 scrollx scrolly 完善 Rect坐标。
}
private void calcOurContentVisibleRect(Rect r) { 传入Rect 对象
calcOurVisibleRect(r);
r.left = viewToContentX(r.left);
// viewToContentY will remove the total height of the title bar. Add
// the visible height back in to account for the fact that if the title
// bar is partially visible, the part of the visible rect which is
// displaying our content is displaced by that amount.
r.top = viewToContentY(r.top + getVisibleTitleHeightImpl());
r.right = viewToContentX(r.right);
r.bottom = viewToContentY(r.bottom);
}
webtextview 的尺寸 位置更新
// These values are possible options for didUpdateWebTextViewDimensions.
private static final int FULLY_ON_SCREEN = 0;
private static final int INTERSECTS_SCREEN = 1;
private static final int ANYWHERE = 2;
/**
* Check to see if the focused textfield/textarea is still on screen. If it
* is, update the the dimensions and location of WebTextView. Otherwise,
* remove the WebTextView. Should be called when the zoom level changes.
* @param intersection How to determine whether the textfield/textarea is
* still on screen.
* @return boolean True if the textfield/textarea is still on screen and the
* dimensions/location of WebTextView have been updated.
*/
private boolean didUpdateWebTextViewDimensions(int intersection) {
//计算出焦点坐标节点区域
Rect contentBounds = nativeFocusCandidateNodeBounds();
Rect vBox = contentToViewRect(contentBounds);
offsetByLayerScrollPosition(vBox);
//计算出可见区域
Rect visibleRect = new Rect();
calcOurVisibleRect(visibleRect);
// If the textfield is on screen, place the WebTextView in
// its new place, accounting for our new scroll/zoom values,
// and adjust its textsize.
boolean onScreen;
switch (intersection) {
case FULLY_ON_SCREEN: vBox 在 visibleRect 之内
onScreen = visibleRect.contains(vBox);
break;
case INTERSECTS_SCREEN: vBox 与 visibleRect 交互
onScreen = Rect.intersects(visibleRect, vBox);
break;
case ANYWHERE:
onScreen = true;
break;
default:
throw new AssertionError(
"invalid parameter passed to didUpdateWebTextViewDimensions");
}
if (onScreen) {
mWebTextView.setRect(vBox.left, vBox.top, vBox.width(),
vBox.height());
mWebTextView.updateTextSize();
updateWebTextViewPadding(); 设置输入框内的padding,
return true;
} else {
// The textfield is now off screen. The user probably
// was not zooming to see the textfield better. Remove
// the WebTextView. If the user types a key, and the
// textfield is still in focus, we will reconstruct
// the WebTextView and scroll it back on screen.
mWebTextView.remove();
return false;
}
}
android 焦点控制
* 父元素分配焦点
setFocusable() 设置view接受焦点的资格 isFocusable view是否具有接受焦点的资格
setFocusInTouchMode() 对应在触摸模式下,设置是否有焦点来响应点触的资格
isFocusableInTouchMode() 对应在触摸模式下,来获知是否有焦点来响应点触
焦点获取
requestFocus() ------ view
requestFocus(...) 当用户在某个界面聚集焦点
requestFocusFromTouch() 触摸模式下
......
requestChildFocus (View child, View focused) ------viewGroup
1 父元素 调用此方法
2 child 将要获取焦点的子元素
3 focused 现在拥有焦点的子元素
一般也可以通过 配置文件设置
View.FOCUS_LEFT Move focus to the left
View.FOCUS_UP Move focus up
View.FOCUS_RIGHT Move focus to the right
View.FOCUS_DOWN Move focus down 代码设置实现 其实都是通过这些设置的
isInTouchMode() 触摸模式
liulanqi 菜单栏
这是一个viewGroup (可以包含 子view 的) 形式的ViewPager, 通过改写 onMeasure()方法,重置viewpager 的 计算高度。
******************透过他的子View的 MeasuredHeight 计算得出**********************,
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = 0;
int size = getChildCount(); // 得到 子view s 的count
for (int i = 0; i < size; ++i) {
final View child = getChildAt(i);
expandSpec = child.getMeasuredHeight(); // 这里可以得到子View 的 MeasuredHeight,不知为什么 ,但的却可以拿到
}
setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
getDefaultSize(0, MeasureSpec.makeMeasureSpec(expandSpec, MeasureSpec.AT_MOST)));
}