我什么时候可以第一次测量视图?

发布于 2024-10-06 09:51:44 字数 547 浏览 5 评论 0原文

因此,我对尝试设置视图显示时的背景可绘制对象感到有点困惑。该代码依赖于了解视图的高度,因此我无法从 onCreate()onResume() 调用它,因为 getHeight() code> 返回 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);

So I have a bit of confusion with trying to set the background drawable of a view as it is displayed. The code relies upon knowing the height of the view, so I can't call it from onCreate() or onResume(), because getHeight() returns 0. onResume() seems to be the closest I can get though. Where should I put code such as the below so that the background changes upon display to the user?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

謌踐踏愛綪 2024-10-13 09:51:44

我不知道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() 已被调用两次,我认为在您的情况下它可能会导致无限循环。

您可以尝试仅在 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 编辑

这是我最终根据这段代码所做的事情。戈蒂埃的回答让我明白了这一点,所以我宁愿接受这个答案并进行修改,也不愿自己回答。我最终使用 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,没有发现任何异常活动。希望就是这样!谢谢!

I didn't know about ViewTreeObserver.addOnPreDrawListener(), and I tried it in a test project.

With your code it would look like this:

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

In my test project onPreDraw() has been called twice, and I think in your case it may cause an infinite loop.

You could try to call the setBackgroundDrawable() only when the height of the TextView changes :

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

But that sounds a bit complicated for what you are trying to achieve and not really good for performance.

EDIT by kcoppock

Here's what I ended up doing from this code. Gautier's answer got me to this point, so I'd rather accept this answer with modification than answer it myself. I ended up using the ViewTreeObserver's addOnGlobalLayoutListener() method instead, like so (this is in 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);
    }
});

Seems to work perfectly; I checked LogCat and didn't see any unusual activity. Hopefully this is it! Thanks!

旧伤慢歌 2024-10-13 09:51:44

关于视图树观察者:

不保证返回的 ViewTreeObserver 观察者在此 View 的生命周期内保持有效。如果此方法的调用者保留对 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               
        }

    });

我的视图发布策略没有遇到任何问题,也没有看到任何屏幕闪烁或其他您预计可能会出现问题的情况(但是......)

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

Concerning the view tree observer:

The returned ViewTreeObserver observer is not guaranteed to remain valid for the lifetime of this View. If the caller of this method keeps a long-lived reference to ViewTreeObserver, it should always check for the return value of isAlive().

Even if its a short lived reference (only used once to measure the view and do the same sort of thing you're doing) I've seen it not be "alive" anymore and throw an exception. In fact, every function on ViewTreeObserver will throw an IllegalStateException if its no longer 'alive', so you always have to check 'isAlive'. I had to do this:

    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                                        

            }
        });            
    }

This seems pretty brittle to me. Lots of things - albeit rarely - seem to be able to go wrong.

So I started doing this: when you post a message to a View, the messages will only be delivered after the View has been fully initialized (including being measured). If you only want your measuring code to run once, and you don't actually want to observe layout changes for the life of the view (cause it isn't being resized), then I just put my measuring code in a post like this:

 viewToMeasure.post(new Runnable() {

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

    });

I haven't had any problems with the view posting strategy, and I haven't seen any screen flickers or other things you'd anticipate might go wrong (yet...)

I have had crashes in production code because my global layout observer wasn't removed or because the layout observer wasn't 'alive' when I tried to access it

网白 2024-10-13 09:51:44

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

我通过

whateverlayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);

在侦听器内调用 onGlobalLayout 方法内部来修复此问题。

by using the global listener, you may run into an infinite loop.

i fixed this by calling

whateverlayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);

inside of the onGlobalLayout method within the listener.

假装不在乎 2024-10-13 09:51:44

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

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

In my projects I'm using following snippet:

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

So, inside provided Runnable#run you can be sure that View was measured and execute related code.

过期以后 2024-10-13 09:51:44

您可以重写 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
   }
   ...
}

我几乎确定您也可以在没有任何代码行的情况下做到这一点。我认为有机会创建图层列表。

You can override 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
   }
   ...
}

I am almost sure that you can do it without any line of code also. I think there is a chance to create layer-list.

单身情人 2024-10-13 09:51:44

综上所述,有 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() 达到相同的目的,增加一点延迟时间。我们自己尝试一下吧。有时你会需要它。例如,当您想在将更多项目添加到滚动视图中后自动滚动滚动视图,或者通过动画展开或使更多视图从滚动视图中的消失状态可见时(这意味着这个过程需要一点时间才能显示所有内容)。它肯定会改变scroll view的大小,所以在绘制scroll view之后,我们还需要等待一段时间才能在添加更多内容后获得准确的大小查看它。现在是 postDelay() 方法发挥作用的时候了!

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

So summarily, 3 ways to handle the size of view.... hmmm, maybe so!

1) As @Gautier Hayoun mentioned, using OnGlobalLayoutListener listener. However, please remember to call at the first code in listener: listViewProduct.getViewTreeObserver().removeOnGlobalLayoutListener(this); to make sure this listener won't call infinitely. And one more thing, sometime this listener won't invoke at all, it because your view has been drawn somewhere you don't know. So, to be safe, let's register this listener right after you declare a view in onCreate() method (or onViewCreated() in fragment)

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) If you want to do something right after the view has been drawn and shown up, just use post() method (of course you can get the size here since your view was drawn completely already). For example,

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

Moreover, you can use postDelay() with the same purpose, adding a little delay time. Let's try it yourself. You will need it sometime. For prompt example, when you want to scroll the scroll view automatically after adding more items into scroll view or expand or make some more view visible from gone status in scroll view with animation (it means this process will take a little time to show up everything). It will definitely change the scroll view's size, so after the scroll view is drawn, we also need to wait a little time to get an exact size after it adds more view into it. This's a high time for postDelay() method to come to the rescue!

3) And if you want to set up your own view, you can create a class and extend from any View yo like which all have onMeasure() method to define size of the view.

苍暮颜 2024-10-13 09:51:44
textView.doOnPreDraw {
   //call textView.height
}

您还可以使用此视图扩展:

androidx.core.view (ViewKt.class)

public inline fun View.doOnPreDraw(
    crossinline action: (View) → Unit
): OneShotPreDrawListener

当即将绘制视图树时执行给定的操作。这
动作只会在下一次抽奖之前调用一次,然后
已删除。

textView.doOnPreDraw {
   //call textView.height
}

You can also use this view extension:

androidx.core.view (ViewKt.class)

public inline fun View.doOnPreDraw(
    crossinline action: (View) → Unit
): OneShotPreDrawListener

Performs the given action when the view tree is about to be drawn. The
action will only be invoked once prior to the next draw and then
removed.

美煞众生 2024-10-13 09:51:44

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

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

这样您就可以重绘、排序...您的视图。 :)

You can get all of view measures in method of class main activity below onCreate() method:

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

So you can redrawing, sorting... your views. :)

握住你手 2024-10-13 09:51:44

使用 OnPreDrawListener() 而不是 addOnGlobalLayoutListener(),因为它被调用得更早。

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

Use OnPreDrawListener() instead of addOnGlobalLayoutListener(), because it is called earlier.

  tv.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener()
    {
        @Override
        public boolean onPreDraw()
        {
            tv.getViewTreeObserver().removeOnPreDrawListener(this);
            // put your measurement code here
            return false;
        }
    });
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文