View Focus的处理过程及ViewGroup的mFocused字段分析
通过上篇的介绍,我们知道在对KeyEvent的处理中有非常重要的一环,那就是KeyEvent在focus view的path上自上而下的分发,
换句话说只有focus的view才有资格参与KeyEvent的处理,所以说focused view在KeyEvent的处理中很重要,我们需要弄清楚明白
focus view是如何设置以及改变的。
通过Android官方文档http://developer.android.com/reference/android/view/View.html中关于Focus Handling的介绍,
我们知道framework会根据用户的输入处理常规的focus移动,包括当删除、隐藏或添加新的view时改变focus。一个view有资格获得
focus的前提是isFocusable()方法返回true,你可以通过setFocusable(boolean)方法来设置它。另外当在touch mode下的时候,还
需要isFocusableInTouchMode()也返回true,你也可以通过setFocusableInTouchMode(boolean)来设置它。focus的移动是基于这
样的算法,它尝试在某个给定的方向上找最临近的view,设置它为新的focus。在极个别情况,如果默认的算法不符合你的需求,你也可以
在xml布局文件中通过显式指定nextFocusDown/Left/Right/Up这些属性来表明focus移动的顺序。在运行时刻,你也可以通过调用
View.requestFocus()方法来动态地让某个view获得focus。作为开始,我们先看看这几个具备获得焦点前提的方法,如下:
/**
* Returns whether this View is able to take focus.
*
* @return True if this view can take focus, or false otherwise.
* @attr ref android.R.styleable#View_focusable
*/
@ViewDebug.ExportedProperty(category = "focus")
public final boolean isFocusable() {
return FOCUSABLE == (mViewFlags & FOCUSABLE_MASK); // 各种位操作,不熟悉、习惯的同学可以翻本C语言的书看看,
} // 这里顺便推荐下《C Primer Plus》,一本足矣,而且里面
// 有一章是专门介绍bit操作的应用的,非常赞!!!
/**
* When a view is focusable, it may not want to take focus when in touch mode.
* For example, a button would like focus when the user is navigating via a D-pad
* so that the user can click on it, but once the user starts touching the screen,
* the button shouldn't take focus
* @return Whether the view is focusable in touch mode.
* @attr ref android.R.styleable#View_focusableInTouchMode
*/
@ViewDebug.ExportedProperty
public final boolean isFocusableInTouchMode() {
return FOCUSABLE_IN_TOUCH_MODE == (mViewFlags & FOCUSABLE_IN_TOUCH_MODE);
} /**
* Set whether this view can receive the focus.
*
* Setting this to false will also ensure that this view is not focusable
* in touch mode.
*
* @param focusable If true, this view can receive the focus.
*
* @see #setFocusableInTouchMode(boolean)
* @attr ref android.R.styleable#View_focusable
*/
public void setFocusable(boolean focusable) {
if (!focusable) { // 注意:是false的时候,会顺便保证在touch mode下也不能获得focus
setFlags(0, FOCUSABLE_IN_TOUCH_MODE);
}
setFlags(focusable ? FOCUSABLE : NOT_FOCUSABLE, FOCUSABLE_MASK); // 设置FOCUSABLEB位
} /**
* Set whether this view can receive focus while in touch mode.
*
* Setting this to true will also ensure that this view is focusable.
*
* @param focusableInTouchMode If true, this view can receive the focus while
* in touch mode.
*
* @see #setFocusable(boolean)
* @attr ref android.R.styleable#View_focusableInTouchMode
*/
public void setFocusableInTouchMode(boolean focusableInTouchMode) {
// Focusable in touch mode should always be set before the focusable flag
// otherwise, setting the focusable flag will trigger a focusableViewAvailable()
// which, in touch mode, will not successfully request focus on this view
// because the focusable in touch mode flag is not set
setFlags(focusableInTouchMode ? FOCUSABLE_IN_TOUCH_MODE : 0, FOCUSABLE_IN_TOUCH_MODE);
if (focusableInTouchMode) { // 如果是true顺便打开FOCUSABLE位
setFlags(FOCUSABLE, FOCUSABLE_MASK);
}
}
接下来我们就看看本文的重点View.requestFocus()等相关方法,代码如下:
/**
* Call this to try to give focus to a specific view or to one of its
* descendants.
*
* A view will not actually take focus if it is not focusable ({@link #isFocusable} returns
* false), or if it is focusable and it is not focusable in touch mode
* ({@link #isFocusableInTouchMode}) while the device is in touch mode.
*
* See also {@link #focusSearch(int)}, which is what you call to say that you
* have focus, and you want your parent to look for the next one.
*
* This is equivalent to calling {@link #requestFocus(int, Rect)} with arguments
* {@link #FOCUS_DOWN} and <code>null</code>.
*
* @return Whether this view or one of its descendants actually took focus.
*/
public final boolean requestFocus() {
return requestFocus(View.FOCUS_DOWN);
} /**
* Call this to try to give focus to a specific view or to one of its
* descendants and give it a hint about what direction focus is heading.
*
* A view will not actually take focus if it is not focusable ({@link #isFocusable} returns
* false), or if it is focusable and it is not focusable in touch mode
* ({@link #isFocusableInTouchMode}) while the device is in touch mode.
*
* See also {@link #focusSearch(int)}, which is what you call to say that you
* have focus, and you want your parent to look for the next one.
*
* This is equivalent to calling {@link #requestFocus(int, Rect)} with
* <code>null</code> set for the previously focused rectangle.
*
* @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
* @return Whether this view or one of its descendants actually took focus.
*/
public final boolean requestFocus(int direction) {
return requestFocus(direction, null);
} /**
* Call this to try to give focus to a specific view or to one of its descendants
* and give it hints about the direction and a specific rectangle that the focus
* is coming from. The rectangle can help give larger views a finer grained hint
* about where focus is coming from, and therefore, where to show selection, or
* forward focus change internally.
*
* A view will not actually take focus if it is not focusable ({@link #isFocusable} returns
* false), or if it is focusable and it is not focusable in touch mode
* ({@link #isFocusableInTouchMode}) while the device is in touch mode.
*
* A View will not take focus if it is not visible.
*
* A View will not take focus if one of its parents has
* {@link android.view.ViewGroup#getDescendantFocusability()} equal to
* {@link ViewGroup#FOCUS_BLOCK_DESCENDANTS}.
*
* See also {@link #focusSearch(int)}, which is what you call to say that you
* have focus, and you want your parent to look for the next one.
*
* You may wish to override this method if your custom {@link View} has an internal
* {@link View} that it wishes to forward the request to.
*
* @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
* @param previouslyFocusedRect The rectangle (in this View's coordinate system)
* to give a finer grained hint about where focus is coming from. May be null
* if there is no hint.
* @return Whether this view or one of its descendants actually took focus.
*/
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
return requestFocusNoSearch(direction, previouslyFocusedRect);
} private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) { // 此方法就是最终被调用的版本
// need to be focusable
if ((mViewFlags & FOCUSABLE_MASK) != FOCUSABLE || // 不是FOCUSABLE,即没资格获取焦点
(mViewFlags & VISIBILITY_MASK) != VISIBLE) { // 或者不是VISIBLE的,都直接返回false,表示请求获取焦点失败
return false; // 所以除了上文提到的2个获取focus的前提,其实这里的VISIBLE也应该算是第3个前提吧!
} // need to be focusable in touch mode if in touch mode
if (isInTouchMode() && // 同样在touch mode下,也要检测FOCUSABLE_IN_TOUCH_MODE标志
(FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
return false; // 不满足也直接返回false,表示失败
} // need to not have any parents blocking us
if (hasAncestorThatBlocksDescendantFocus()) { // parents阻止我们获得焦点的话,我们也只能以失败告终
return false;
}
// 以上重重关卡都通过了,才会走到这里,真正设置focus
handleFocusGainInternal(direction, previouslyFocusedRect);
return true;
} /**
* Give this view focus. This will cause
* {@link #onFocusChanged(boolean, int, android.graphics.Rect)} to be called.
*
* Note: this does not check whether this {@link View} should get focus, it just
* gives it focus no matter what. It should only be called internally by framework
* code that knows what it is doing, namely {@link #requestFocus(int, Rect)}.
*
* @param direction values are {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
* {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT}. This is the direction which
* focus moved when requestFocus() is called. It may not always
* apply, in which case use the default View.FOCUS_DOWN.
* @param previouslyFocusedRect The rectangle of the view that had focus
* prior in this View's coordinate system.
*/
void handleFocusGainInternal(int direction, Rect previouslyFocusedRect) {
if (DBG) {
System.out.println(this + " requestFocus()");
} if ((mPrivateFlags & PFLAG_FOCUSED) == 0) { // 只有当前View不是focused view时才会发生一系列操作,否则do nothing
mPrivateFlags |= PFLAG_FOCUSED; // 如果没focus的话,先设置此view的focused标志,isFocused,hasFocus等方法会检测此标志 View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null; // 找到之前的focus if (mParent != null) {
mParent.requestChildFocus(this, this); // 如果有mParent,则将此新focus请求向上传递
} if (mAttachInfo != null) { // callback接口,将focus change事件notify出去
mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
} onFocusChanged(true, direction, previouslyFocusedRect);
refreshDrawableState();
}
}
接着我们看下关于PFLAG_FOCUSED标志位相关的几个方法,如下:
/**
* Returns true if this view has focus
*
* @return True if this view has focus, false otherwise.
*/
@ViewDebug.ExportedProperty(category = "focus")
public boolean isFocused() {
return (mPrivateFlags & PFLAG_FOCUSED) != 0;
} /**
* Find the view in the hierarchy rooted at this view that currently has
* focus.
*
* @return The view that currently has focus, or null if no focused view can
* be found.
*/
public View findFocus() {
return (mPrivateFlags & PFLAG_FOCUSED) != 0 ? this : null;
} /**
* Returns true if this view has focus iteself, or is the ancestor of the
* view that has focus.
*
* @return True if this view has or contains focus, false otherwise.
*/
@ViewDebug.ExportedProperty(category = "focus")
public boolean hasFocus() { // 对View来说,hasFocus和isFocus是相同的,ViewGroup类重载了此方法
return (mPrivateFlags & PFLAG_FOCUSED) != 0;
}
最后,我们看看ViewParent接口(以及其实现ViewGroup)的requestChildFocus()实现,代码如下:
/**
* Called when a child of this parent wants focus
*
* @param child The child of this ViewParent that wants focus. This view
* will contain the focused view. It is not necessarily the view that
* actually has focus.
* @param focused The view that is a descendant of child that actually has
* focus
*/
public void requestChildFocus(View child, View focused); // parent中的某个child请求获得focus,child要么是focused,
// 要么是focused的parent
// 我们可以看到其实现类有ViewGroup、ScrollView等,这里我们看下ViewGroup类的,其他的有兴趣的同学可以自行研究 @Override
public void requestChildFocus(View child, View focused) {
if (DBG) {
System.out.println(this + " requestChildFocus()");
}
if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
return; // 如果此ViewGroup被设置为阻止任何children获得focus,则直接返回
} // Unfocus us, if necessary
super.unFocus(); // 先unFocus 此ViewGroup // We had a previous notion of who had focus. Clear it.
if (mFocused != child) { // 如果mFocused不同于传递进来的child,则更新mFocused
if (mFocused != null) {
mFocused.unFocus(); // 让旧的放弃focus
} mFocused = child; // 更新mFocused
}
if (mParent != null) { // 接着沿着focus path往上传递(递归调用)
mParent.requestChildFocus(this, focused); // 注意这里的第2个参数,一直是传递进来的focused不变
}
}
我们注意到只有ViewGroup才有mFocused字段,表示focus path上的一个节点。我们看看与之相关的代码:
// The view contained within this ViewGroup that has or contains focus.
private View mFocused; // 此ViewGroup中的child view,它要么是focused view本身要么包含focused view @Override
void handleFocusGainInternal(int direction, Rect previouslyFocusedRect) { // 此方法重载了View中的
if (mFocused != null) { // 添加了对mFocused的处理
mFocused.unFocus(); // 让mFocused unFocus在这种情况下
mFocused = null;
}
super.handleFocusGainInternal(direction, previouslyFocusedRect);
} /**
* {@inheritDoc}
*/
public void clearChildFocus(View child) {
if (DBG) {
System.out.println(this + " clearChildFocus()");
} mFocused = null; // 清空
if (mParent != null) { // 将事件告诉parent
mParent.clearChildFocus(this);
}
} /**
* {@inheritDoc}
*/
@Override
public void clearFocus() {
if (DBG) {
System.out.println(this + " clearFocus()");
}
if (mFocused == null) { // 如果没有mFocused,则ViewGroup自身clearFocus
super.clearFocus();
} else { // 否则,让mFocused clearFocus,并且重置为null
View focused = mFocused;
mFocused = null;
focused.clearFocus();
}
} /**
* {@inheritDoc}
*/
@Override
void unFocus() { // 大体同clearFocus,只是调的是unFocus方法
if (DBG) {
System.out.println(this + " unFocus()");
}
if (mFocused == null) {
super.unFocus();
} else {
mFocused.unFocus();
mFocused = null;
}
} /**
* Returns the focused child of this view, if any. The child may have focus
* or contain focus.
*
* @return the focused child or null.
*/
public View getFocusedChild() { // 返回这个字段,供客户端代码使用
return mFocused;
} /**
* Returns true if this view has or contains focus
*
* @return true if this view has or contains focus
*/
@Override
public boolean hasFocus() { // ViewGroup自己是focused或者其子、孙后代包含focused view
return (mPrivateFlags & PFLAG_FOCUSED) != 0 || mFocused != null;
} /*
* (non-Javadoc)
*
* @see android.view.View#findFocus()
*/
@Override
public View findFocus() {
if (DBG) {
System.out.println("Find focus in " + this + ": flags="
+ isFocused() + ", child=" + mFocused);
} if (isFocused()) { // 自己是focused,直接返回this
return this;
} if (mFocused != null) { // 否则,mFocused不为空,则沿着这条线往下继续找
return mFocused.findFocus();
}
return null;
} /**
* {@inheritDoc}
*/
@Override
public boolean hasFocusable() {
if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {
return false;
} if (isFocusable()) {
return true;
} final int descendantFocusability = getDescendantFocusability();
if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
final int count = mChildrenCount;
final View[] children = mChildren; for (int i = 0; i < count; i++) {
final View child = children[i];
if (child.hasFocusable()) {
return true;
}
}
} return false;
}
接着我们看下View自己的unFocus、clearFocus实现,代码如下:
/**
* Called internally by the view system when a new view is getting focus.
* This is what clears the old focus.
* <p>
* <b>NOTE:</b> The parent view's focused child must be updated manually
* after calling this method. Otherwise, the view hierarchy may be left in
* an inconstent state.
*/
void unFocus() {
if (DBG) {
System.out.println(this + " unFocus()");
} clearFocusInternal(false, false);
} /**
* Called when this view wants to give up focus. If focus is cleared
* {@link #onFocusChanged(boolean, int, android.graphics.Rect)} is called.
* <p>
* <strong>Note:</strong> When a View clears focus the framework is trying
* to give focus to the first focusable View from the top. Hence, if this
* View is the first from the top that can take focus, then all callbacks
* related to clearing focus will be invoked after wich the framework will
* give focus to this view.
* </p>
*/
public void clearFocus() {
if (DBG) {
System.out.println(this + " clearFocus()");
} clearFocusInternal(true, true);
} /**
* Clears focus from the view, optionally propagating the change up through
* the parent hierarchy and requesting that the root view place new focus.
*
* @param propagate whether to propagate the change up through the parent
* hierarchy
* @param refocus when propagate is true, specifies whether to request the
* root view place new focus
*/
void clearFocusInternal(boolean propagate, boolean refocus) {
if ((mPrivateFlags & PFLAG_FOCUSED) != 0) { // 如果当前是focused,先清掉PFLAG_FOCUSED位
mPrivateFlags &= ~PFLAG_FOCUSED; if (propagate && mParent != null) {
mParent.clearChildFocus(this); // 如果向上传播的话,调用parent.clearChildFocus方法
} onFocusChanged(false, 0, null); // 调用callback方法 refreshDrawableState(); // 刷新drawable状态 if (propagate && (!refocus || !rootViewRequestFocus())) {
notifyGlobalFocusCleared(this);
}
}
}
通过上一篇的介绍,我们知道KeyEvent的派发就是在view层次结构的focus path上自上而下发生的,具体参见View.dispatchKeyEvent
的方法doc。刚开始我一直不明白这里的focus path是怎么形成的,怎么按着这个链传递的。这里为了帮助大家理解,我举一个典型的例子,
通过例子可以很清楚的看到传递过程。比方说我们的view层次结构是这样的,C是个Button,B是C的parent,LinearLayout,A是B的parent,
FrameLayout。这里我们先假设C、B、A都是有资格且其parent都不阻止它获得焦点,当我们在代码里调用C.requestFocus()时发生的调用
序列如下:
1. --> B.requestChildFocus(C, C); 当此方法发生后产生的结果是:B.mFocused = C;接着产生2调用;
2. --> A.requestChildFocus(B, C); 同样的,当此方法发生后,A.mFocused = B; 接着往上传递直到parent为空时停止。
当C.requestFocus()调用结束时,如果没有各种失败的case发生,那么C就是当前view层次结构中的focus了,也就是C.isFocused()方法
此时会返回true。看到了吗?通过这个递归调用,focus path的链就形成了,从最顶层的A能通过其mFocused字段找到B,从找到的B能通过
其mFocused字段找到C,以此类推。为了加深这个印象,我们最后再看眼ViewGroup.dispatchKeyEvent()方法:
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
/// 2.2.1.1...
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 1);
} if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) { // ViewGroup是focused,则优先交给它自己处理
/// 2.2.1.2.
if (super.dispatchKeyEvent(event)) {
return true;
}
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) { // 否则就沿着mFocused形成的focus path向下传递
/// 2.2.1.3.
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
} if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
/// 2.2.1.4.
return false;
}
现在再回过头来看这里的逻辑,是不是感觉特别简单呢?那是因为你已经完全弄清楚了mFocused的由来以及各种变化过程。至此view层次
结构中关于focus的变化过程已经全部分析完毕了,enjoy。
View Focus的处理过程及ViewGroup的mFocused字段分析的更多相关文章
- Android View的加载过程
大家都知道Android中加载view是从Activity的onCreate方法调用setContentView开始的,那么View的具体加载过程又是怎么的呢?这一节我们做一下分析. 首先追踪一下代码 ...
- 源码解析Android中View的measure量算过程
Android中的Veiw从内存中到呈现在UI界面上需要依次经历三个阶段:量算 -> 布局 -> 绘图,关于View的量算.布局.绘图的总体机制可参见博文< Android中View ...
- Android View框架总结(八)ViewGroup事件分发机制
请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52298780 上篇分析了View的事件分发流程,留了一个问题:如果上 ...
- 源代码解析Android中View的layout布局过程
Android中的Veiw从内存中到呈如今UI界面上须要依次经历三个阶段:量算 -> 布局 -> 画图,关于View的量算.布局.画图的整体机制可參见博文 < Android中Vie ...
- 控制器View的加载过程
1.控制器内部的view是延迟加载 1> 用到时再加载2> 加载完毕后会调用控制器的viewDidLoad方法 2.创建控制器的方式 1> 直接通过代码创建OneViewContro ...
- Android中将xml布局文件转化为View树的过程分析(下)-- LayoutInflater源码分析
在Android开发中为了inflate一个布局文件,大体有2种方式,如下所示: // 1. get a instance of LayoutInflater, then do whatever yo ...
- thinkphp中view页面中的volist标签转化为原生php分析(多去看源代码,你会发现不仅简单,方便你理解,还节约时间)
thinkphp中view页面中的volist标签转化为原生php分析(多去看源代码,你会发现不仅简单,方便你理解,还节约时间) 一.总结 1.标签和原生php之间的关系:标签只是为了方便你使用,标签 ...
- 简单研究Android View绘制三 布局过程
2015-07-28 17:29:19 这一篇主要看看布局过程 一.布局过程肯定要不可避免的涉及到layout()和onLayout()方法,这两个方法都是定义在View.java中,源码如下: /* ...
- [Android FrameWork 6.0源码学习] View的重绘过程
View绘制的三部曲, 测量,布局,绘画今天我们分析测量过程 view的测量是从ViewRootImpl发起的,View需要重绘,都是发送请求给ViewRootImpl,然后他组织重绘在重绘的过程中 ...
随机推荐
- 30天C#基础巩固-----值类型/引用类型,泛型,空合并操作符(??),匿名方法
一:值类型/引用类型的区别 值类型主要包括简单类型,枚举类型,和结构体类型等,值类型的实例通常被分配在线程堆栈上面变量保存的内容是实例数据本身.引用类型被分配在托管堆上,变量保存的是地址.引 ...
- ASP.NET MVC使用jQuery实现Autocomplete
Insus.NET的以前的ASP.NET MVC的练习中,也有实现过Autocomplete的功能.依次是使用jQuery来实现. 首先在数据库准备一些数据: CREATE TABLE [dbo].[ ...
- VB.NET WinForm获取运行程序用户名
一个程序也许会被多个用户运行,如下: 那在VB.NET的WinForm环境下,怎样获取User Name呢?可从下面的方法: 代码: Public Shared Function GetProcess ...
- 关于EF的 序列化类型为“XXX”的对象时检测到循环引用。
在用Ef的时候,也许经常会遇到循环引用的错误. 下面提供解决办法.(不是Json.Net,如果是Json.Net可以给导航属性通过增加特性标签来解决该问题) ef大多数问题,可以通过ToList()来 ...
- 前端mvc框架backbone.js入门
关于backbone.js的优缺点,这里就不详谈了,网上关于这方面的讨论很多了,而且各种框架之所以长久生存,通常都是有其特定优势和擅长点的. 使用backbone.js作为前端框架的应用通常都是htm ...
- .Net加密保护工具分析介绍
本文主要介绍一些dotNet加密保护工具的原理以及就其脱壳进行简单探讨. remotesoft protector.maxtocode..Net Reactor.Cliprotector.themid ...
- Linux文件查看/编辑方法介绍
转载:https://www.centos.bz/2011/10/linux-file-view-edit/ cat 命令介绍 cat 命令的原含义为连接(concatenate), 用于连接多个文件 ...
- css知多少(10)——display
1. 引言 网页的所有元素,除了“块”就是“流”,而且“流”都是包含在“块”里面的(最外层的body就是一个“块”).在本系列一开始讲<浏览器默认样式>的时候,大家也都看到了浏览器默认样式 ...
- WPF实现强大的动态公式计算
数据库可以定义表不同列之间的计算公式,进行自动公式计算,但如何实现行上的动态公式计算呢?行由于可以动态扩展,在某些应用场景下将能很好的解决实际问题. 1.VS2012新建一个WPF应用程序WpfApp ...
- 站长必备:10个好用的 WordPress 备份插件
网站备份对于站长来说极其重要的.任何的事情都可能发生,这可能会导致你失去所有的辛勤工作:您的网站可能被黑客攻破,你可以安装一个了插件导致冲突,你的服务器可能被攻击,你可能在编辑文件时犯了一个错误等等, ...