Когда я могу сначала измерить вид?

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

Ответ 1

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

Но это звучит немного сложно для того, чего вы пытаетесь достичь и не очень хорошего для производительности.

EDIT by 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 и не видел никаких необычных действий. Надеюсь, это все! Спасибо!

Ответ 2

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

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

    });

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

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

Ответ 3

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

i зафиксировал это, позвонив

whateverlayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

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

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

Ответ 7

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

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

Ответ 8

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

1) Как сказал @Gautier Hayoun, используя прослушиватель OnGlobalLayoutListener. Однако, пожалуйста, не забудьте позвонить по первому коду в прослушивателе: listViewProduct.getViewTreeObserver().removeOnGlobalLayoutListener(this);, чтобы убедиться, что этот прослушиватель не будет звонить бесконечно. И еще одна вещь, когда-нибудь этот слушатель вообще не будет ссылаться, потому что ваше представление было нарисовано где-то, чего вы не знаете. Итак, чтобы быть в безопасности, зарегистрируйте этот прослушиватель сразу после объявления вида в методе onCreate() (или onViewCreated() в фрагменте)

view = ([someView]) findViewById(R.id.[viewId]);

// Get width of view before it drawn.
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
      @Override
      public void onGlobalLayout() {

        // Make this listener call once.
        view.getViewTreeObserver().removeOnGlobalLayoutListener(this);

        int width = listViewProduct.getWidth();
      });