我正在编写一个应用程序,其唯一目的是试图了解Android中的 View 层次结构是如何工作的。我现在遇到一些非常棘手的问题。在这里,我将尽量简化我的解释。

设置:

目前我有三个观点。 2个是ViewGroups,而1个只是View。假设他们的顺序是:

    TestA extends ViewGroup
    TestB extends ViewGroup
    TestC extends View
    TestA->TestB->TestC
    Where TestC is in TestB and TestB is in TestA.

在我的Activity中,我只显示如下 View :
TestA myView = new TestA(context);
myView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));
setContentView(myView);

问题:
  • 永远不会调用TestA的onDraw(Canvas canvas)方法。我已经看到了几种解决方案,说我的 View 没有任何尺寸(height/width = 0),但是事实并非如此。当我覆盖onLayout()时,我得到了布局的尺寸,它们是正确的。另外,getHeight()/Width()完全符合其应有的状态。我还可以覆盖dispatchDraw()并获取基本 View 以进行绘制。
  • 我想为TestB中的对象设置动画。传统上,我会覆盖自身调用onDraw()上的invalidate()方法,直到对象完成它应该做的动画为止。但是,在TestB中,当我调用invalidate()时, View 永远不会重绘。我的印象是,再次调用onDraw()方法是我的父 View 的工作,但是我的父 View 不再再次调用dispatchDraw()

  • 我想我的问题是,为什么我的父 View 的onDraw()方法永远不会被调用?当其中一个 child 使自己无效时,应该调用父 View 中的哪些方法?家长是负责确保 child 被画成 child 的人吗?还是Android会照顾 child ?如果Android响应invalidate(),为什么我的TestB再也不会被绘制?

    最佳答案

    好的,经过一些研究和大量尝试,我发现我在问题2上做错了三件事。其中很多是简单的答案,但不是很明显。

  • 您需要为每个 View 覆盖onMeasure()。在onMeasure()中,您需要为中包含的每个子级调用子级,并传递该子级所需的MeasureSpec。
  • 您需要为每个想要包括的子代调用measure()。最初,我只是创建了一个 View 对象并直接使用它。这使我可以绘制一次,但是 View 不包含在 View 树中,因此当我调用ViewGroup时,它并没有使 View 树无效且没有重新绘制。例如:

  • 在testA中:
     TestB childView;
     TestA(Context context){
       ****** setup code *******
       LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
       childView = new TestB(context);
       childView.setLayoutParams(params);
     }
    
     @Override
     protected void dispatchDraw(Canvas canvas){
       childView.draw(canvas);
     }
    

    这将绘制一次 subview 。但是,如果该 View 需要更新动画或其他内容,就可以了。我将addView()放入TestA的构造函数中,以将其添加到 View 树中。最终代码如下:
     TestB childView;
     TestA(Context context){
       ****** setup code *******
       LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
       childView = new TestB(context);
       childView.setLayoutParams(params);
       addView(childView);
     }
    
     @Override
     protected void dispatchDraw(Canvas canvas){
       childView.draw(canvas);
     }
    

    另外,如果要绘制更多的子级,可以像这样重写invalidate(),但是我需要在每个图形之间添加一些自定义元素,例如网格线或其他东西。
    @Override
    protectd void dispatchDraw(Canvas canvas){
       int childCount = getChildCount();
       for(int i = 0; i < size; i++)
       {
           drawCustomElement();
           getChildAt(i).draw(canvas);
       }
    }
    
  • 您必须覆盖addView(childView)(无论如何,它都是dispatchDraw(Canvas canvas)中的抽象内容,因此还是必须的)。在此方法中,必须为每个 child 调用onLayout()。即使完成了前两件事,我的 View 也不会失效。我这样做后,一切都变得非常完美。

  • 更新:
    问题1已解决。另一个极其简单但不太明显的解决方案。

    当我创建ViewGroup实例时,我必须调用layout,否则出于优化原因,Android将不会绘制它。因此完整的设置是:
    TestA myView = new TestA(context);
    myView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));
    myView.setWillNotDraw(false);
    setContentView(myView);
    

    10-05 21:01
    查看更多