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