Попытка понять иерархию представления - PullRequest
6 голосов
/ 15 сентября 2011

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

Настройка:

В настоящее время у меня есть три вида.2 - ViewGroups, а 1 - View.Допустим, они в следующем порядке:

    TestA extends ViewGroup
    TestB extends ViewGroup
    TestC extends View
    TestA->TestB->TestC
    Where TestC is in TestB and TestB is in TestA.

В моем Activity я просто отображаю представления следующим образом:

TestA myView = new TestA(context);
myView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));
setContentView(myView);

Проблемы:

  1. Метод TestA onDraw(Canvas canvas) никогда не вызывается.Я видел пару решений этого высказывания, что у моего вида нет никаких измерений (высота / ширина = 0), однако это не тот случай.Когда я переопределяю onLayout(), я получаю размеры моего макета, и они правильные.Кроме того, getHeight () / Width () точно такие, какими они должны быть.Я также могу переопределить dispatchDraw() и заставить свои базовые виды рисовать себя.

  2. Я хочу анимировать объект в TestB.Традиционно я переопределяю метод onDraw() при вызове invalidate() на себя, пока объект не завершит анимацию, которую он должен был сделать.Однако в TestB, когда я звоню invalidate(), представление никогда не перерисовывается.У меня сложилось впечатление, что работа моего родительского представления заключается в том, чтобы снова вызывать метод onDraw(), но мое родительское представление больше не вызывает dispatchDraw().

Я полагаюмои вопросы: почему мой onDraw() метод моего родительского представления никогда не будет вызван с самого начала?Какие методы в моем родительском представлении должны вызываться, когда один из его детей делает себя недействительным?Является ли родитель ответственным за то, чтобы его рисовали дети, или Android об этом позаботится?Если Android отвечает на invalidate(), почему мой TestB больше никогда не обращается?

Ответы [ 2 ]

5 голосов
/ 16 сентября 2011

Хорошо, после некоторого исследования и много попыток, я обнаружил, что я делал три вещи неправильно в отношении проблемы # 2. Во многом это были простые ответы, но не очень очевидные.

  • Вам необходимо переопределить onMeasure() для каждого просмотра. В пределах onMeasure() вам необходимо вызвать measure() для каждого дочернего элемента, содержащегося в передаче ViewGroup в MeasureSpec, в которой нуждается дочерний элемент.

  • Вам нужно позвонить addView() для каждого ребенка, которого вы хотите включить. Первоначально я просто создал объект просмотра и использовал его напрямую. Это позволило мне нарисовать его один раз, но представление не было включено в дерево представления, поэтому при вызове invalidate() оно не аннулировало дерево представления и не перерисовало. Например:

В тесте A:

 TestB childView;
 TestA(Context context){
   ****** setup code *******
   LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
   childView = new TestB(context);
   childView.setLayoutParams(params);
 }

 @Override
 protected void dispatchDraw(Canvas canvas){
   childView.draw(canvas);
 }

Это нарисует дочерний вид один раз. Однако, если это представление нуждается в обновлении для анимации или чего-то еще, это было так Я добавил addView(childView) в конструктор TestA, чтобы добавить его в дерево представлений. Окончательный код таков:

 TestB childView;
 TestA(Context context){
   ****** setup code *******
   LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
   childView = new TestB(context);
   childView.setLayoutParams(params);
   addView(childView);
 }

 @Override
 protected void dispatchDraw(Canvas canvas){
   childView.draw(canvas);
 }

В качестве альтернативы, я мог бы переопределить dispatchDraw(Canvas canvas) примерно так, если бы у меня было много детей для рисования, но мне нужен какой-то пользовательский элемент между каждым рисунком, например, линии сетки или что-то в этом роде.

@Override
protectd void dispatchDraw(Canvas canvas){
   int childCount = getChildCount();
   for(int i = 0; i < size; i++)
   {
       drawCustomElement();
       getChildAt(i).draw(canvas);
   }
}
  • Вы должны переопределить onLayout() (в любом случае оно абстрактно в ViewGroup, так что в любом случае требуется). В рамках этого метода вы должны вызывать layout для каждого ребенка. Даже после выполнения первых двух вещей мои взгляды не станут недействительными. Как только я это сделал, все заработало просто отлично.

UPDATE: Проблема № 1 была решена. Другое чрезвычайно простое, но не столь очевидное решение.

Когда я создаю экземпляр TestA, мне нужно позвонить setWillNotDraw(false), иначе Android не будет его рисовать по причинам оптимизации. Итак, полная настройка:

TestA myView = new TestA(context);
myView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));
myView.setWillNotDraw(false);
setContentView(myView);
2 голосов
/ 16 сентября 2011

Это не прямой ответ, но это фантастический учебник о том, как рисовать пользовательские виды, и дает отличные подсказки по анимации вида.Код очень прост и чист.

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

...