我什么时候可以先测量视图?

时间:2010-12-09 00:03:12

标签: android layout height android-lifecycle

因此,我尝试设置视图的背景可绘制时会有点混乱。代码依赖于知道视图的高度,因此我无法从onCreate()onResume()调用它,因为getHeight()返回0. onResume()似乎是最接近的我可以得到。我应该在哪里放置如下所示的代码,以便在显示给用户时背景发生变化?

    TextView tv = (TextView)findViewById(R.id.image_test);
    LayerDrawable ld = (LayerDrawable)tv.getBackground();
    int height = tv.getHeight(); //when to call this so as not to get 0?
    int topInset = height / 2;
    ld.setLayerInset(1, 0, topInset, 0, 0);
    tv.setBackgroundDrawable(ld);

8 个答案:

答案 0 :(得分:69)

我不知道ViewTreeObserver.addOnPreDrawListener(),我在测试项目中尝试过。

使用您的代码,它看起来像这样:

public void onCreate() {
setContentView(R.layout.main);

final TextView tv = (TextView)findViewById(R.id.image_test);
final LayerDrawable ld = (LayerDrawable)tv.getBackground();
final ViewTreeObserver obs = tv.getViewTreeObserver();
obs.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    @Override
    public boolean onPreDraw () {
        Log.d(TAG, "onPreDraw tv height is " + tv.getHeight()); // bad for performance, remove on production
        int height = tv.getHeight();
        int topInset = height / 2;
        ld.setLayerInset(1, 0, topInset, 0, 0);
        tv.setBackgroundDrawable(ld);

        return true;
   }
});
}

在我的测试项目中onPreDraw()被调用了两次,我认为在你的情况下它可能会导致无限循环。

只有当setBackgroundDrawable()的高度发生变化时,您才可以尝试拨打TextView

private int mLastTvHeight = 0;

public void onCreate() {
setContentView(R.layout.main);

final TextView tv = (TextView)findViewById(R.id.image_test);
final LayerDrawable ld = (LayerDrawable)tv.getBackground();
final ViewTreeObserver obs = mTv.getViewTreeObserver();
obs.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    @Override
    public boolean onPreDraw () {
        Log.d(TAG, "onPreDraw tv height is " + tv.getHeight()); // bad for performance, remove on production
        int height = tv.getHeight();
        if (height != mLastTvHeight) {
            mLastTvHeight = height;
            int topInset = height / 2;
            ld.setLayerInset(1, 0, topInset, 0, 0);
            tv.setBackgroundDrawable(ld);
        }

        return true;
   }
});
}

但是对于你想要达到的目标而言,这听起来有点复杂,而且对性能并不是很好。

由kcoppock编辑

这是我最终通过此代码完成的工作。戈蒂埃的回答让我达到了这一点,所以我宁愿接受这个答案而不是自己回答。我最终使用了ViewTreeObserver的addOnGlobalLayoutListener()方法,就像这样(这是在onCreate()中):

final TextView tv = (TextView)findViewById(R.id.image_test);
ViewTreeObserver vto = tv.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        LayerDrawable ld = (LayerDrawable)tv.getBackground();
        ld.setLayerInset(1, 0, tv.getHeight() / 2, 0, 0);
    }
});

似乎工作得很完美;我检查了LogCat并没有看到任何异常活动。希望就是这样!谢谢!

答案 1 :(得分:64)

关于视图树观察者:

  

不保证返回的ViewTreeObserver观察器在此View的生命周期内保持有效。如果此方法的调用者保持对ViewTreeObserver的长期引用,则应始终检查isAlive()的返回值。

即使它是一个短暂的参考(只用一次来测量视图并做同样的事情)我已经看到它不再是“活着”而且抛出异常。事实上,ViewTreeObserver上的每个函数都会抛出IllegalStateException,如果它不再是'alive',那么你总是要检查'isAlive'。我不得不这样做:

    if(viewToMeasure.getViewTreeObserver().isAlive()) {
        viewToMeasure.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if(viewToMeasure.getViewTreeObserver().isAlive()) {
                    // only need to calculate once, so remove listener
                    viewToMeasure.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                }

                // you can get the view's height and width here

                // the listener might not have been 'alive', and so might not have been removed....so you are only
                // 99% sure the code you put here will only run once.  So, you might also want to put some kind
                // of "only run the first time called" guard in here too                                        

            }
        });            
    }

这对我来说似乎很脆弱。许多事情 - 尽管很少 - 似乎都可能出错。

所以我开始这样做:当您向View发布消息时,消息将仅在View完全初始化(包括被测量)之后传递。如果您只希望测量代码运行一次,并且您实际上并不想观察视图生命周期中的布局更改(因为它没有调整大小),那么我只是将测量代码放在这样的帖子中:

 viewToMeasure.post(new Runnable() {

        @Override
        public void run() {
            // safe to get height and width here               
        }

    });

我对查看发布策略没有任何问题,我没有看到任何屏幕闪烁或其他你预期可能出错的事情(但......)

已经在生产代码中崩溃了,因为我的全局布局观察者没有被移除,或者因为我试图访问它时布局观察者没有“活着”

答案 2 :(得分:11)

通过使用全局侦听器,您可能会遇到无限循环。

我通过调用

来解决这个问题
whateverlayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);

在侦听器中的onGlobalLayout方法内部。

答案 3 :(得分:3)

在我的项目中,我正在使用以下代码段:

public static void postOnPreDraw(Runnable runnable, View view) {
    view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            try {
                runnable.run();
                return true;
            } finally {
                view.getViewTreeObserver().removeOnPreDrawListener(this);
            }
        }
    });
}

因此,在提供Runnable#run内部,您可以确定View已被衡量并执行相关代码。

答案 4 :(得分:2)

您可以覆盖onMeasure for TextView:

public MyTextView extends TextView {
   ...
   public void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
      super.onMeasure(widthMeasureSpec,heightMeasureSpec); // Important !!!
      final int width = getMeasuredHeight();
      final int height = getMeasuredHeight();
      // do you background stuff
   }
   ...
}

我几乎可以肯定你可以在没有任何代码的情况下完成它。我认为有机会创建图层列表。

答案 5 :(得分:0)

您可以在onCreate()方法下面的类主要活动方法中获取所有视图度量:

@Override
protected void onCreate(Bundle savedInstanceState){}
@Override
public void onWindowFocusChanged(boolean hasFocus){
    int w = yourTextView.getWidth();
}

所以你可以重新绘制,排序......你的观点。 :)

答案 6 :(得分:0)

使用OnPreDrawListener()代替addOnGlobalLayoutListener(),因为它之前已被调用。

  tv.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener()
    {
        @Override
        public boolean onPreDraw()
        {
            tv.getViewTreeObserver().removeOnPreDrawListener(this);
            // put your measurement code here
            return false;
        }
    });

答案 7 :(得分:0)

所以简单地说,有3种处理视图大小的方法....嗯,也许是这样!

1)正如@Gautier Hayoun所提到的,使用OnGlobalLayoutListener监听器。但是,请记得在监听器的第一个代码中调用:listViewProduct.getViewTreeObserver().removeOnGlobalLayoutListener(this);以确保此监听器无法无限呼叫。还有一件事,有时候这个听众根本不会调用它,因为你的观点被吸引到了你不知道的地方。因此,为了安全起见,让我们在onCreate()方法(或片段中的onViewCreated())中声明视图后立即注册此侦听器



view = ([someView]) findViewById(R.id.[viewId]);

// Get width of view before it's drawn.
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
      @Override
      public void onGlobalLayout() {

        // Make this listener call once.
        view.getViewTreeObserver().removeOnGlobalLayoutListener(this);

        int width = listViewProduct.getWidth();
      });




2)如果你想在绘制和显示视图后立即执行某些操作,只需使用post方法(当然,您可以在此处获取大小,因为您的视图已经完全绘制)。例如,



listViewProduct.post(new Runnable() {
  @Override
  public void run() {
    // Do your stuff here.
  }
});




此外,您可以使用具有相同目的的postDelay,添加一点延迟时间。让我们自己尝试一下吧。你有时会需要它。对于提示示例,当您想要在滚动视图中添加更多项目后自动滚动滚动视图,或者在滚动视图中展开或通过动画显示更多视图,这意味着此过程将需要一些时间来显示所有内容)。它肯定会改变滚动视图的大小,因此在绘制滚动视图后,我们还需要等待一段时间才能获得更多视图。这是postDelay方法来抢救的最佳时机!

3)如果你想设置自己的视图,你可以创建一个类,并从你喜欢的任何视图扩展,它们都有onMeasure()方法来定义视图的大小。

希望这有帮助,

Mttdat。