本篇讲焦点移动不了的问题,先下下图效果。
进入“添加网络摄像机”页面后,遥控器按下往右的按键,焦点只落在第一个框上面,再也移动不了,页面拍的不是很清楚,需要仔细看下。
正常焦点移动,是系统根据某个具体的方向去查找,然后判断每个可以获取焦点的view的坐标是否是最合适获取焦点的
经过定位:发现是三个框子的view是代码动态生成的,坐落于屏幕中的位置是通过setTranslationX确定的。
下面是出问题的代码:
private void initSize() {
LinearLayout.LayoutParams imageParams = new LinearLayout.LayoutParams(mItemWidth, mItemHeight);
mImageViews.get(0).setLayoutParams(imageParams);
mImageViews.get(1).setLayoutParams(imageParams);
mImageViews.get(2).setLayoutParams(imageParams);
FrameLayout.LayoutParams text1Params = new FrameLayout.LayoutParams(mItemWidth, ViewGroup.LayoutParams.WRAP_CONTENT);
// text1Params.setMargins(mMarginLeft1, mMarginTop, 0, 0);
mCameraViews.get(0).setLayoutParams(text1Params);
FrameLayout.LayoutParams text2Params = new FrameLayout.LayoutParams(mItemWidth, ViewGroup.LayoutParams.WRAP_CONTENT);
//text2Params.setMargins(mMarginLeft2, mMarginTop, 0, 0);
mCameraViews.get(1).setLayoutParams(text2Params);
FrameLayout.LayoutParams text3Params = new FrameLayout.LayoutParams(mItemWidth, ViewGroup.LayoutParams.WRAP_CONTENT);
// text3Params.setMargins(mMarginLeft3, mMarginTop, 0, 0);
mCameraViews.get(2).setLayoutParams(text3Params);
initData();
initListen();
initTransmator();
}
private void initTransmator() {
mCameraViewLayout.setTranslationY(mMarginTop);
//确定第一个框的位置
mCameraViews.get(0).setTranslationX(mMarginLeft1);
//确定第二个框的位置
mCameraViews.get(1).setTranslationX(mMarginLeft2);
//确定第三个框的位置
mCameraViews.get(2).setTranslationX(mMarginLeft3);
}
思路:
setTranslationX是不是影响了坐标?或者view的坐标不对了?
下面进入源码分析:
FocusFinder 这个类专门负责查找焦点,里面包含着不同方向的查找条件,下面看下它的这个方法
View findNextFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused,
Rect focusedRect, int direction) {
// initialize the best candidate to something impossible
// (so the first plausible view will become the best choice)
mBestCandidateRect.set(focusedRect);
switch(direction) {
case View.FOCUS_LEFT:
mBestCandidateRect.offset(focusedRect.width() + 1, 0);
break;
case View.FOCUS_RIGHT:
mBestCandidateRect.offset(-(focusedRect.width() + 1), 0);
break;
case View.FOCUS_UP:
mBestCandidateRect.offset(0, focusedRect.height() + 1);
break;
case View.FOCUS_DOWN:
mBestCandidateRect.offset(0, -(focusedRect.height() + 1));
}
View closest = null;
int numFocusables = focusables.size();
//遍历当前页面上所有的可获取焦点的view
for (int i = 0; i < numFocusables; i++) {
View focusable = focusables.get(i);
// only interested in other non-root views
//如果是已经获取焦点的view,跳过
if (focusable == focused || focusable == root) continue;
// get focus bounds of other view in same coordinate system
//得到候选者view的Rect
focusable.getFocusedRect(mOtherRect);
//重点看下这个方法,该方法是将view的Rect转换成坐标系
root.offsetDescendantRectToMyCoords(focusable, mOtherRect);
//找出最合适可以获取焦点的方法
if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) {
mBestCandidateRect.set(mOtherRect);
closest = focusable;
}
}
//返回焦点view
return closest;
}
经过定位,是offsetDescendantRectToMyCoords“出了问题”,该方法是将view的Rect转换成响应的坐标系,包含x,y坐标。
看下实现:
public final void offsetDescendantRectToMyCoords(View descendant, Rect rect) {
offsetRectBetweenParentAndChild(descendant, rect, true, false);
}
void offsetRectBetweenParentAndChild(View descendant, Rect rect,
boolean offsetFromChildToParent, boolean clipToBounds) {
// already in the same coord system :)
if (descendant == this) {
return;
}
ViewParent theParent = descendant.mParent;
//从当前view遍历,一直到根view的直接下一个view
// search and offset up to the parent
while ((theParent != null)
&& (theParent instanceof View)
&& (theParent != this)) {
if (offsetFromChildToParent) {
//依次增加左边距-scrollx
rect.offset(descendant.mLeft - descendant.mScrollX,
descendant.mTop - descendant.mScrollY);
if (clipToBounds) {
View p = (View) theParent;
boolean intersected = rect.intersect(0, 0, p.mRight - p.mLeft,
p.mBottom - p.mTop);
if (!intersected) {
rect.setEmpty();
}
}
} else {
if (clipToBounds) {
View p = (View) theParent;
boolean intersected = rect.intersect(0, 0, p.mRight - p.mLeft,
p.mBottom - p.mTop);
if (!intersected) {
rect.setEmpty();
}
}
rect.offset(descendant.mScrollX - descendant.mLeft,
descendant.mScrollY - descendant.mTop);
}
descendant = (View) theParent;
theParent = descendant.mParent;
}
//到达根view
if (theParent == this) {
if (offsetFromChildToParent) {
//算上根view的left,top值。
rect.offset(descendant.mLeft - descendant.mScrollX,
descendant.mTop - descendant.mScrollY);
} else {
rect.offset(descendant.mScrollX - descendant.mLeft,
descendant.mScrollY - descendant.mTop);
}
} else {
throw new IllegalArgumentException("parameter must be a descendant of this view");
}
}
这段代码,是如何计算出view具体的坐标的呢?
下面我附上我画的一张图,没有考虑滑动(scroll)情况,看了这张图你就能知道了,画的不好,请忽略啊。
在该bug中,由于view调用了setTranstionX这个属性动画的方式设置了坐落在屏幕中的位置,但是在计算坐标的时候,依次在view层级树上计算的时候,这个view相关的mLeft,与mTop是缺失的,以mLeft举例,mLeft指的是view相对于父View的左边距,我们正常在xml中设置marginLeft或者代码中动态设置setMarginLeft,最后setLayoutParams都可以生效,mLeft是有值的。
setTranstionx本质上是通过动画的方式,没有调用onLayout,mLeft没有值。