Когда я могу впервые измерить вид? - PullRequest
59 голосов
/ 09 декабря 2010

Так что я немного запутался, пытаясь установить фон для рисования в том виде, в котором он отображается. Код опирается на знание высоты представления, поэтому я не могу назвать его из 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);

Ответы [ 8 ]

69 голосов
/ 09 декабря 2010

Я не знал о 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

Вот что яв конечном итоге делать из этого кода.Ответ Готье привел меня к этому вопросу, поэтому я предпочел бы принять этот ответ с изменениями, чем отвечать на него сам.Я закончил тем, что вместо этого использовал метод addOnGlobalLayoutListener () ViewTreeObserver, вот так (это в 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 и не увидел никакой необычной активности.Надеюсь, это так!Спасибо!

64 голосов
/ 08 марта 2013

Относительно дерева просмотра наблюдателя:

Возвращенный наблюдатель 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                                        

            }
        });            
    }

Это кажется мне довольно хрупким. Многие вещи - хотя и редко - кажется, могут пойти не так.

Итак, я начал делать это: когда вы публикуете сообщение в View, сообщения будут доставляться только после полной инициализации View (включая измерение). Если вы хотите, чтобы ваш измерительный код запускался один раз, и вы на самом деле не хотите наблюдать изменения макета в течение всего срока службы представления (потому что он не изменяется), тогда я просто помещаю свой измерительный код в сообщение, подобное этому :

 viewToMeasure.post(new Runnable() {

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

    });

У меня не было проблем со стратегией публикации представления, и я не видел мерцания экрана или другие вещи, которые вы ожидали, могли бы пойти не так (пока ...)

У меня были сбои в рабочем коде, потому что мой глобальный наблюдатель макета не был удален или потому что наблюдатель макета не был "живым", когда я пытался получить к нему доступ

11 голосов
/ 19 декабря 2012

с помощью глобального прослушивателя вы можете столкнуться с бесконечным циклом.

я исправил это, позвонив

whateverlayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);

внутри метода onGlobalLayout в слушателе.

3 голосов
/ 03 июня 2014

В своих проектах я использую следующий фрагмент:

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 был измерен и выполнить соответствующий код.

2 голосов
/ 09 декабря 2010

Вы можете переопределить onMeasure для 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
   }
   ...
}

Я почти уверен, что вы можете сделать это без какой-либо строки кода. Я думаю, что есть шанс создать слой-список.

0 голосов
/ 20 июня 2017

Итак, всего три способа обработки размера представления .... хммм, может быть и так!

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 с той же целью, добавив небольшое время задержки.Давайте попробуем сами.Вам это понадобится когда-нибудь.Например, если вы хотите автоматически прокрутить представление прокрутки после добавления дополнительных элементов в представление прокрутки или развернуть или сделать еще один вид, видимый из пропавшего состояния, в представлении прокрутки с анимацией (это означает, что этот процесс займет немного времени, чтобы показать все).Это определенно изменит размер вида прокрутки, поэтому после отрисовки вида прокрутки нам также нужно немного подождать, чтобы получить точный размер, после того как он добавит в него больше вида.Настало время, чтобы метод postDelay пришел на помощь!

3) И если вы хотите настроить собственное представление, вы можете создать класс и расширить его из любого представления, которое вам нравится, у всех из которых есть onMeasure ()метод определения размера представления.

Надеюсь, это поможет,

Mttdat.

0 голосов
/ 22 марта 2017

Используйте OnPreDrawListener() вместо addOnGlobalLayoutListener(), потому что он вызывается раньше.

  tv.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener()
    {
        @Override
        public boolean onPreDraw()
        {
            tv.getViewTreeObserver().removeOnPreDrawListener(this);
            // put your measurement code here
            return false;
        }
    });
0 голосов
/ 31 января 2015

Вы можете получить все показатели зрения в методе основного действия класса ниже метода onCreate ():

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

Так что вы можете перерисовывать, сортировать ... ваши взгляды. :)

...