何时可以首先测量视图?

所以我有一个混乱,试图设置一个视图的背景可绘制,因为它显示。 代码依赖于知道视图的高度,所以我不能从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); 

我不知道ViewTreeObserver.addOnPreDrawListener() ,我在一个testing项目中试过。

用你的代码看起来像这样:

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

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

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

 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编辑

这是我最终从这个代码做的。 Gautier的回答让我明白了这一点,所以我宁愿接受这个答案而不是修改,而不是自己回答。 我最终使用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并没有看到任何不寻常的活动。 希望是这样! 谢谢!

关于视图树观察者:

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

即使它是一个短暂的参考(只用一次来衡量视图,做同样的事情,你在做什么),我已经看到它不再是“活着”,抛出一个例外。 事实上,如果ViewTreeObserver中的每个函数不再处于“活动”状态,它将抛出一个IllegalStateExceptionexception,因此您必须检查“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 } }); } 

这对我来说似乎很脆弱。 很多事情 – 尽pipe很less – 似乎能够出错。

所以我开始这样做:当你发布消息到一个视图,消息只会在视图完全初始化(包括被测量)之后被传递。 如果你只想让你的测量代码运行一次,而且你实际上并不想观察视图生命周期的布局变化(因为它没有resize),那么我只是把我的测量代码放在这样的post中:

  viewToMeasure.post(new Runnable() { @Override public void run() { // safe to get height and width here } }); 

我没有任何问题的视图发布策略,我还没有看到任何屏幕闪烁或其他事情你可能会出错(但…)

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

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

我通过调用解决这个问题

 whateverlayout.getViewTreeObserver().removeGlobalOnLayoutListener(this); 

监听器中的onGlobalLayout方法的内部。

您可以覆盖TextView的onMeasure:

 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 } ... } 

我几乎可以肯定,你可以做任何代码也没有。 我认为有机会创build图层列表。

在我的项目中,我使用下面的代码片段:

 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被测量并执行相关的代码。

你可以在下面的onCreate()方法的类主要活动的方法中获得所有的观点措施:

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

所以你可以重新绘制,sorting…你的意见。 🙂

使用OnPreDrawListener()而不是addOnGlobalLayoutListener() ,因为它在早期调用。

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

所以概括地说,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(); });