我什么时候可以首先测量视图?
-
10-10-2019 - |
题
因此,我有点混乱,试图设置显示视图的背景。该代码依赖于知道视图的高度,因此我无法从中调用它 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()
, ,我在一个测试项目中尝试了它。
使用您的代码,它看起来像这样:
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
这是我最终从此代码中做的事情。 Gautier的回答使我达到了这一点,因此我宁愿接受修改的答案,也不愿自己回答。我最终使用了viewTreeObserver的addongloballayoutlistener()方法,就像这样(这是在ongreate()中):
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上的每个函数都将不再“活着”,则将抛出一个iLlegalStateException,因此您始终必须检查“ 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
}
});
}
这对我来说似乎很脆弱。很多事情 - 尽管很少 - 似乎能够出错。
因此,我开始这样做:当您向视图发布消息时,仅在视图完全初始化(包括测量)之后才会传递消息。如果您只希望测量代码运行一次,并且实际上不想观察到视图寿命的布局更改(因为它没有调整大小),那么我只是将测量代码放在这样的帖子中:
viewToMeasure.post(new Runnable() {
@Override
public void run() {
// safe to get height and width here
}
});
我对视图发布策略没有任何问题,而且我还没有看到任何屏幕闪烁或您期望可能会出错的其他事情(...)
我 有 由于未删除我的全局布局观察者,因此在生产代码中发生了崩溃
通过使用全局侦听器,您可以遇到无限循环。
我通过打电话解决了这个问题
whateverlayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
侦听器内的ongloballayout方法的内部。
在我的项目中,我正在使用以下摘要:
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
测量并执行相关代码。
您可以覆盖文本视图:
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
}
...
}
我几乎可以确定您也可以无需任何代码。我认为有机会创建图层列表。
您可以在onCreate()方法下的类主要活动方法中获取所有查看措施:
@Override
protected void onCreate(Bundle savedInstanceState){}
@Override
public void onWindowFocusChanged(boolean hasFocus){
int w = yourTextView.getWidth();
}
因此,您可以重新绘制,分类...您的观点。 :)
利用 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()方法中声明视图(或fragment in viewCreated()中的视图之后,让我们立即注册此侦听器。
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,从而增加延迟时间。让我们自己尝试一下。您将需要它。例如,当您想在滚动视图中添加更多项目或扩展或使一些视图从滚动视图中使用动画可见时,请自动滚动视图滚动视图(这意味着此过程将需要一点时间才能显示所有内容)。它肯定会更改滚动视图的尺寸,因此绘制滚动视图后,我们还需要等待一点时间才能在添加更多视图后获得确切的大小。这是邮政方法来营救的时候!
3)如果要设置自己的视图,则可以创建一个类,并从所喜欢的任何视图中延伸,它们都有onMeasure()方法来定义视图的大小。
希望这可以帮助,
mttdat。