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

kco*_*ock 59 height layout android 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);
Run Code Online (Sandbox Code Playgroud)

Gau*_*oun 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;
   }
});
}
Run Code Online (Sandbox Code Playgroud)

在我的测试项目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;
   }
});
}
Run Code Online (Sandbox Code Playgroud)

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

由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);
    }
});
Run Code Online (Sandbox Code Playgroud)

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

  • @ffleandro你需要将`tv`保存为类成员.那么`onGlobalLayout()`的最后一行应该是`tv.getViewTreeObserver().removeGlobalOnLayoutListener(this);`(或者v16 + API中的`removeOnGlobalLayoutListener`). (11认同)
  • 我会在你进行计算后立即取消注册监听器,因为你最终会不断调用该代码 (6认同)
  • +1这是所有平台上的常见问题,但答案很难找到.至少我现在已经将Android包裹起来了. (2认同)

Spl*_*ash 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                                        

            }
        });            
    }
Run Code Online (Sandbox Code Playgroud)

这对我来说似乎很脆弱.许多事情 - 虽然很少 - 似乎可能出错.

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

 viewToMeasure.post(new Runnable() {

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

    });
Run Code Online (Sandbox Code Playgroud)

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

有在生产代码中的崩溃,因为我的全球布局观察者未删除或因布局观察者不是"活",当我试图访问它

  • 我还注意到,在片段的onCreateView中向ViewTreeObserver全局布局添加一个侦听器会阻止该片段被收集,即使我再次删除它.使用post()为我修复了这个内存泄漏问题.+1 (4认同)

kev*_*inl 12

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

我通过电话来解决这个问题

whateverlayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
Run Code Online (Sandbox Code Playgroud)

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