【问题标题】:Trying to understand view hierarchy试图理解视图层次结构
【发布时间】:2012-04-11 23:12:40
【问题描述】:

我正在编写一个应用程序,其唯一目的是试图了解 Android 中的视图层次结构是如何工作的。我现在遇到了一些非常严重的问题。我会尽量简明扼要地解释这里。

设置:

目前我有三个视图。 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 中,我只是像这样显示视图:

TestA myView = new TestA(context);
myView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));
setContentView(myView);

问题:

  1. 永远不会调用 TestA 的 onDraw(Canvas canvas) 方法。我已经看到了一些解决方案,即我的视图没有任何尺寸(高度/宽度 = 0),但是事实并非如此。当我覆盖onLayout() 时,我得到了布局的尺寸并且它们是正确的。此外,getHeight()/Width() 完全符合它们应有的状态。我还可以覆盖dispatchDraw() 并让我的基本视图自行绘制。

  2. 我想为TestB 中的对象设置动画。传统上,我会在自己的调用invalidate() 上覆盖onDraw() 方法,直到对象完成它应该执行的动画。但是,在TestB 中,当我调用invalidate() 时,视图永远不会被重绘。我的印象是再次调用onDraw() 方法是我的父视图的工作,但我的父视图不会再次调用dispatchDraw()

我想我的问题是,为什么我的父视图的 onDraw() 方法永远不会被调用?当其中一个孩子使自己失效时,应该调用我父母视图中的哪些方法?父母是负责确保孩子被吸引的人,还是Android会处理这个问题?如果Android回复invalidate(),为什么我的TestB再也不会被抽到了?

【问题讨论】:

    标签: android android-layout android-view


    【解决方案1】:

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

    • 您需要为每个视图覆盖 onMeasure()。在onMeasure() 中,您需要为ViewGroup 中包含的每个 孩子调用measure(),并传入孩子需要的MeasureSpec。

    • 您需要为您想要包含的每个孩子致电addView()。最初,我只是简单地创建了一个视图对象并直接使用它。这允许我绘制一次,但视图不包含在视图树中,因此当我调用invalidate() 时,它并没有使视图树无效,也没有重绘。例如:

    在测试A中:

     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);
     }
    

    这将绘制一次子视图。但是,如果该视图需要更新动画或其他内容,就是这样。我将addView(childView) 放入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);
       addView(childView);
     }
    
     @Override
     protected void dispatchDraw(Canvas canvas){
       childView.draw(canvas);
     }
    

    或者,如果我有更多的孩子要绘制,我可以像这样覆盖dispatchDraw(Canvas canvas),但我需要在每幅图之间添加一些自定义元素,例如网格线或其他东西。

    @Override
    protectd void dispatchDraw(Canvas canvas){
       int childCount = getChildCount();
       for(int i = 0; i < size; i++)
       {
           drawCustomElement();
           getChildAt(i).draw(canvas);
       }
    }
    
    • 您必须覆盖onLayout()(无论如何它在ViewGroup 中是抽象的,所以无论如何它都是必需的)。在此方法中,您必须为每个孩子调用layout。即使做了前两件事,我的观点也不会失效。一旦我这样做了,一切都完美无缺。

    更新: 问题 #1 已解决。另一个非常简单但不那么明显的解决方案。

    当我创建TestA 的实例时,我必须调用setWillNotDraw(false) 否则Android 出于优化原因不会绘制它。所以完整的设置是:

    TestA myView = new TestA(context);
    myView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));
    myView.setWillNotDraw(false);
    setContentView(myView);
    

    【讨论】:

    • 你能在你的博客或其他地方分享这个 Viewgroup 的演示项目吗?
    • 即使我们在 XML 中指定,我是否需要添加 childView ?
    • 对不起。我没有博客或任何要上传的内容。我会尽我所能提供帮助。要回答你的第一个问题,不。当您扩充 xml 时,子项包含在 ViewGroup 中。最早可以检索到的是onFinishInflate()。在那之后,你有一个选择。通过getChildAt(int index) 方法或使用findViewById(int id) 获取您的孩子。索引将是子项在 xml 中的位置顺序。不过,我不知道索引如何与子项中的子项一起使用。
    • @Deev:你能邮寄那个演示吗?我会更容易与你消除我的疑虑..我知道我要求大份额..但这将是 gr8 的帮助!谢谢队友
    • 抱歉,我的演示是在 9 月编写的,并且早已演变为完整的项目。将其降低到相关性需要相当长的时间。
    【解决方案2】:

    这不是一个直接的答案,而是here is a fantastic tutorial 关于如何绘制自定义视图并提供了如何为视图设置动画的精彩速成课程。代码也非常简洁干净。

    希望这会有所帮助!

    【讨论】:

    • 谢谢。这并没有解决我的问题,但确实有助于解决问题。
    猜你喜欢
    • 1970-01-01
    • 2017-07-11
    • 2018-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多