OnGlobalLayoutListener в ListView

Я хочу иметь возможность расширять строки в моем ListView с помощью анимации. Поэтому мне нужно знать высоту расширяемого представления. Я использую это в методе getView() моего ArrayAdapter:

mDetailsView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

    @Override
    public void onGlobalLayout() {
        mDetailsView.getViewTreeObserver().removeGlobalOnLayoutListener(this);

        onClickListener.setHeight(mDetailsView.getHeight());
        mDetailsView.setVisibility(View.GONE);
    }
});

Это отлично работает для строк, которые отображаются на экране при запуске, но когда я прокручиваю вниз, метод onGlobalLayout не вызывается для строк, которые сначала не были видны.

Как получить высоту для этих строк?

Ответ 1

Возможно, что реализации ListView не выполняют весь макет, поэтому ViewTreeObserver никогда не видит макет в процессе.

Если у меня нет определенного телефона, я не знаю, вы можете использовать метод post в представлениях для выполнения runnable, когда они находятся в поле зрения.

mDetailsView.post(new Runnable() {
  @Override
  public void run() {
    onClickListener.setHeight(mDetailsView.getHeight());
    mDetailsView.setVisibility(View.GONE);
  }
});

EDIT:

Я не знаю, как выкладывается весь метод getView(). Проблема, если я должен был догадаться, это ListView просто не запрашивает макет. Вместо этого он выполняет самую работу для каждого вида, чтобы ускорить процесс. Вы можете попробовать следующее:

public void getView(int position, View convertView, ViewGroup parent) {

  /* 
   * set your view up
   */

  mDetailsView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

    @Override
    public void onGlobalLayout() {
        mDetailsView.getViewTreeObserver().removeGlobalOnLayoutListener(this);

        onClickListener.setHeight(mDetailsView.getHeight());
        mDetailsView.setVisibility(View.GONE);
       }
   });

   notifyDataSetInvalidated(); // Notify the ListView() and any other listeners that your views are invalid.
   return view;
}

Забастовкa >

Новое редактирование: использование notifyDataSetInvalidated() обычно является плохой идеей и особенно если используется в getView().

Чтобы предварительно измерить макет, вы должны взять его параметры макета или дать ему новые, если они не существуют.

    LayoutParams params = newView.getLayoutParams();
    if (params == null) {
        params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }
    final int widthSpec = MeasureSpec.makeMeasureSpec(parent.getWidth(), MeasureSpec.UNSPECIFIED);
    final int heightSpec = MeasureSpec.makeMeasureSpec(parent.getHeight(), MeasureSpec.UNSPECIFIED);
    newView.measure(widthSpec, heightSpec);

Ответ 2

В ListView или RecyclerView вместо использования OnGlobalLayoutListener я всегда использую OnPreDrawListener. Этот обратный вызов запускается также для невидимых строк при запуске. Из официальной документации:

В этот момент все представления в дереве были измерены и заданы рамкой.

Ответ 3

Хорошо, похоже, что у меня была аналогичная проблема.

По какой-то причине, если ListView потерял convertView в середине, то при раздувании представления заново он не вызывал его Global Layout Listener, если он был установлен.

В этом обходном пути я придумал:

public class MyViewThatIsInList extends View {
  public MyViewThatIsInList (Context context) {
      super(context);
      ensureInited();
  }

  public MyViewThatIsInList (Context context, AttributeSet attrs) {
      this(context, attrs, 0);
  }

  public MyViewThatIsInList (Context context, AttributeSet attrs, int defStyle) {
      super(context, attrs, defStyle);

      TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyViewThatIsInList , 0, 0);
      //...
      ensureInited();
  }

  boolean isViewInited = false;
  private void init() {
      getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
          @Override
          public void onGlobalLayout() {
              //my custom helper method
              Utils.safeRemoveLayoutListener(getViewTreeObserver(), this);
              initLogic();
          }
      });
  }

  private void initLogic() {
    //...
    isViewInited = true;
  }

   private void ensureInited() {
      init();
      postDelayed(new Runnable() {
          @Override
          public void run() {
              if (!isViewInited) {
                  initLogic();
              }
          }
      }, 500);
  }

}

Ответ 4

В ListView или RecyclerView вместо использования OnGlobalLayoutListener мы всегда используем OnPreDrawListener. Этот обратный вызов запускается также для невидимых строк при запуске. Из официальной документации:

private void makeTextViewResizable (окончательный текст TextView, окончательный int maxLine, final String expandText, окончательный логический видMore) {

    try {
        if (tv.getTag() == null) {
            tv.setTag(tv.getText());
        }
        //OnGlobalLayoutListener
        ViewTreeObserver vto = tv.getViewTreeObserver();
        vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {

            @Override
            public boolean onPreDraw() {

                    ViewTreeObserver obs = tv.getViewTreeObserver();
                   // obs.removeGlobalOnLayoutListener((ViewTreeObserver.OnGlobalLayoutListener) mActivity);
                    obs.removeOnPreDrawListener(this);
                    if (maxLine == 0) {
                        int lineEndIndex = tv.getLayout().getLineEnd(0);
                        String text = tv.getText().subSequence(0, lineEndIndex - expandText.length() + 1) + " " + expandText;
                        tv.setText(text);
                        tv.setMovementMethod(LinkMovementMethod.getInstance());
                        tv.setText(
                                addClickablePartTextViewResizable(Html.fromHtml(tv.getText().toString()), tv, expandText,
                                        viewMore), TextView.BufferType.SPANNABLE);
                    } else if (maxLine > 0 && tv.getLineCount() >= maxLine) {
                        int lineEndIndex = tv.getLayout().getLineEnd(maxLine - 1);
                        String text = tv.getText().subSequence(0, lineEndIndex - expandText.length() + 1) + " " + expandText;
                        tv.setText(text);
                        tv.setMovementMethod(LinkMovementMethod.getInstance());
                        tv.setText(
                                addClickablePartTextViewResizable(Html.fromHtml(tv.getText().toString()), tv, expandText,
                                        viewMore), TextView.BufferType.SPANNABLE);
                    } else {
                        int lineEndIndex = tv.getLayout().getLineEnd(tv.getLayout().getLineCount() - 1);
                        String text = tv.getText().subSequence(0, lineEndIndex) + " " + expandText;
                        tv.setText(text);
                        tv.setMovementMethod(LinkMovementMethod.getInstance());
                        tv.setText(
                                addClickablePartTextViewResizable(Html.fromHtml(tv.getText().toString()), tv, expandText,
                                        viewMore), TextView.BufferType.SPANNABLE);
                    }


                return true;
            }


        });
    } catch (Exception e) {
        e.printStackTrace();
    }

}