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课件
<style type="text/css"> <!-- pre {font-family:"DejaVu Sans"} p {margin-bottom:0.21cm} --> </style>
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() 触摸模式