title: Android Layout won't update/calculate in bottomsheet before touching
body: <p>I have a layout that is in a android-design(23.4.0) bottomsheet. The content view i have set for the bottomsheet content has a 'place bet' button and an 'accept changes' view. It should look like this:</p>
  
  <p><a href="https://i.stack.imgur.com/KKtG6.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/KKtG6.png" alt="Place bet button view (is ~ 80dp high)"></a></p>
  
  <p>When the price changes the user has to accepts the changes - clicking the accept button shows the placebet button again.</p>
  
  <p><a href="https://i.stack.imgur.com/a9ec2.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/a9ec2.png" alt="Accept changes view (is ~ 160dp high)"></a></p>
  
  <p>The problem is that the BottomSheet dialog isn't initialising properly for some reason - the accept changes view (which is in state <code>visibility==gone</code>) at the start doesn't show and i get the result below. if I click on one of the edittexts to show the keyboard then that seems to initialise it properly and the layout update correctly when the visibility of the placeBet and AcceptChangesView are set. But if the keyboard hasn't shown yet - no layout changes occur and the layout data for the view is unchanged. Automatically showing and hiding the keyboard doesnt seem work either i have to touch the edittext manually. I am even calling forceLayout on all the view that wont update - but even this doesnt call layout (see the log below)</p>
  
  <p><a href="https://i.stack.imgur.com/z2Euo.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/z2Euo.png" alt="enter image description here"></a></p>
  
  <ul>
  <li>In hierarchy viewer <code>AcceptChangesView</code> layout properties (mTop,mLeft,mBottom,mRight) all show 0. The measured properties (getMeasuredWidth(), getMeasuredHeight()) are correct.</li>
  <li>The dialog height won't change even though the <code>ViewTreeObserver().addOnGlobalLayoutListener</code> is called and the StraightBetDialogView.getMeasuredHeight() height is correct. </li>
  </ul>
  
  <p>How do I mimic whatever the Android touch system/keyboard does to make this dialog initialise properly and correctly handle the view layout?</p>
  
  <p><strong>CODE:</strong></p>
  
  <pre><code>package com.gtech.liquidsportsbook.ui.dialog;
  
  
  import android.content.Context;
  import android.content.DialogInterface;
  import android.support.design.widget.BottomSheetBehavior;
  import android.support.design.widget.BottomSheetDialog;
  import android.util.Log;
  import android.view.ViewTreeObserver;
  import android.widget.FrameLayout;
  
  import StraightBetDialogView; 
  
  /**
   * Created by robert on 25/04/16.
   */
  public class StraightBetDialog implements ViewTreeObserver.OnGlobalLayoutListener, DialogInterface.OnShowListener, StraightBetView.LayoutListener {
  
  private final BottomSheetDialog dialog;
  private final StraightBetView dialogView;
  
  private final DialogInterface.OnDismissListener onDismissListener = new DialogInterface.OnDismissListener() {
      @Override
      public void onDismiss(final DialogInterface dialog) {
          dialogView.getViewTreeObserver().removeOnGlobalLayoutListener(StraightBetDialog.this);
          ViewServer.get(getDialogView().getContext()).removeWindow(dialogView);
          if (dialogView.getListener() != null) {
              dialogView.getListener().onCancel();
          }
      }
  };
  
  public StraightBetDialog(final Context c) {
      dialogView = new StraightBetView(c);
      dialog = new BottomSheetDialog(c);
      dialog.setContentView(dialogView);
      dialog.setOnDismissListener(onDismissListener);
      dialog.setCancelable(true);
      dialog.setOnShowListener(this);
      dialogView.getViewTreeObserver().addOnGlobalLayoutListener(this);
      dialogView.setLayoutListener(this);
      ViewServer.get(c).addWindow(dialogView, StraightBetView.class.getSimpleName());
  }
  
  public StraightBetView getDialogView() {
      return dialogView;
  }
  
  public BottomSheetDialog getDialog() {
      return dialog;
  }
  
  @Override
  public void onGlobalLayout() {
      adjustHeight();
  }
  
  public void adjustHeight() {
      final FrameLayout bottomSheetFrameLayout = (FrameLayout) dialog.getWindow().findViewById(R.id.design_bottom_sheet);
  //        bottomSheetFrameLayout.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
  //        bottomSheetFrameLayout.forceLayout();
          dialogView.invalidate();
          final BottomSheetBehavior&lt;FrameLayout&gt; dialogBehaviour = BottomSheetBehavior.from(bottomSheetFrameLayout);
          dialogBehaviour.setPeekHeight(dialogView.getMeasuredHeight());
          dialogBehaviour.setState(BottomSheetBehavior.STATE_EXPANDED);
          Log.d(getClass().getSimpleName(), "adjustHeight(): " + dialogView.getMeasuredHeight(), new Exception());
  }
  
  @Override
  public void onShow(final DialogInterface dialog) {
      if (dialogView.getParameters() != null) {
          dialogView.getParameters().setHasChanges(false);
          adjustHeight();
      }
  }
  
  @Override
  public void onLayout() {
      adjustHeight();
  }
  
  }
  </code></pre>
  
  <p>The relevant layout xml is below: the bottom container contains both the acceptChangesView and the placeBetButton.</p>
  
  <pre><code>&lt;FrameLayout
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:id="@+id/botttomContainer"
      &gt;
  
          &lt;com.gtech.liquidsportsbook.ui.views.AcceptChangesView
              android:id="@+id/straightBetAcceptChanges"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:visibility="gone"
              app:listener="@{listener}"
          /&gt;
  
          &lt;Button
              android:id="@+id/placeBetButton"
              style="@style/style_mgm_button_betting"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_margin="10dp"
              android:enabled="@{viewModel.placeBetEnabled}"
              android:text="@string/placeBet"
              android:onClick="@{listener.onPlaceBetClick}"
          /&gt;
  
      &lt;/FrameLayout&gt;
  </code></pre>
  
  <p>The cumstom view code is here (there are a few different attempts of what i have tried to forceLayout - none of which actually work). Initially the viewModel just set the visibility according to the hasChanges attribute via databinding.</p>
  
  <pre><code>public class StraightBetView extends FrameLayout {
  
  private StraightBetViewModel viewModel;
  
  private ViewStraightBetBinding binding;
  
  private AmountListener amountListener;
  
  private Listener listener;
  private LayoutListener layoutListener;
  
  @Inject
  protected BettingUtils bettingUtils;
  
  public interface AmountListener {
      void onRiskChanged(final BigDecimal bigDecimal);
  
      void onToWinChanged(final BigDecimal bigDecimal);
  
      void onUnfocus(AmountInputEditText v);
  }
  
  
  public interface Listener extends AcceptChangesView.Listener {
      void onPlaceBetClick(View v);
  
      void onCancel();
  }
  
  public interface LayoutListener {
      void onLayout();
  }
  
  private final AmountInputEditText.OnUnfocusListener editTextUnfocusListener = new AmountInputEditText.OnUnfocusListener() {
      @Override
      public void onUnfocus(final AmountInputEditText view) {
          if (!binding.riskEditText.hasFocus() &amp;&amp; !binding.toWinEditText.hasFocus()) {
              UIUtils.closeKeyBoard(view);
          } else {
              UIUtils.showKeyBoard(view);
          }
          if (amountListener != null) {
              amountListener.onUnfocus(view);
          }
      }
  };
  
  private final android.databinding.Observable.OnPropertyChangedCallback propertyChangedCallback = new android.databinding.Observable.OnPropertyChangedCallback() {
      @Override
      public void onPropertyChanged(final android.databinding.Observable observable, final int i) {
          if (i == BR.hasChanges) {
              setAcceptChangesState();
          }
      }
  };
  
  public StraightBetView(final Context context) {
      super(context);
      init(context);
  }
  
  public StraightBetView(final Context context, final AttributeSet attrs) {
      super(context, attrs);
      init(context);
  }
  
  public StraightBetView(final Context context, final AttributeSet attrs, final int defStyleAttr) {
      super(context, attrs, defStyleAttr);
      init(context);
  }
  
  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  public StraightBetView(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) {
      super(context, attrs, defStyleAttr, defStyleRes);
      init(context);
  }
  
  private void init(final Context context) {
      MGMApplicationComponent.Injector.getComponent(context).inject(this);
      final LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
      binding = ViewStraightBetBinding.inflate(layoutInflater, this, true);
      setId(R.id.straightBetContainer);
      //setOrientation(VERTICAL);
      binding.toWinEditText.setOnUnfocusListener(editTextUnfocusListener);
      binding.riskEditText.setOnUnfocusListener(editTextUnfocusListener);
      setAcceptChangesState();
  
  }
  
  private void updateView() {
      binding.setViewModel(viewModel);
  }
  
  public StraightBetViewModel getParameters() {
      return viewModel;
  }
  
  public void setParameters(final StraightBetViewModel parameters) {
      if (viewModel != null) {
          viewModel.removeOnPropertyChangedCallback(propertyChangedCallback);
      }
      this.viewModel = parameters;
      viewModel.addOnPropertyChangedCallback(propertyChangedCallback);
      //setAcceptChangesState();
      updateView();
  }
  
  public AmountListener getAmountListener() {
      return amountListener;
  }
  
  public void setAmountListener(final AmountListener amountListener) {
      this.amountListener = amountListener;
      binding.setAmountListener(amountListener);
  }
  
  @BindingAdapter("bind:amountListener")
  public static void setAmountListener(final StraightBetView view, final AmountListener amountListener) {
      view.setAmountListener(amountListener);
  }
  
  public Listener getListener() {
      return listener;
  }
  
  public void setListener(final Listener listener) {
      this.listener = listener;
      binding.setListener(listener);
  }
  
  @BindingAdapter("bind:listener")
  public static void setListener(final StraightBetView view, final Listener listener) {
      view.setListener(listener);
  }
  
  public void localeChanged() {
      LocaleUiUtil.initEditTextLocale(binding.riskEditText);
      LocaleUiUtil.initEditTextLocale(binding.toWinEditText);
  }
  
  public void setAcceptChangesState() {
      if (viewModel != null) {
          binding.placeBetButton.setVisibility(!viewModel.isHasChanges() ? VISIBLE : GONE);
          binding.straightBetAcceptChanges.setVisibility(viewModel.isHasChanges() ? VISIBLE : GONE);
      }
      binding.straightBetAcceptChanges.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
      binding.straightBetAcceptChanges.forceLayout();
      binding.straightBetAcceptChanges.layout(0, binding.botttomContainer.getTop(), getMeasuredWidth(), binding.botttomContainer.getTop() + binding.straightBetAcceptChanges.getMeasuredHeight());
      binding.botttomContainer.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
      binding.botttomContainer.forceLayout();
      measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
      forceLayout();
      if (layoutListener != null) {
          layoutListener.onLayout();
      }
  }
  
  public void setLayoutListener(final LayoutListener layoutListener) {
      this.layoutListener = layoutListener;
  }
  }
  </code></pre>
  
  <p>The code for showing the dialog is fairly simple:</p>
  
  <pre><code>public void showStraightBetDialog(final StraightBetDialogParameters straightBetParams, final Event event) {
  
  
      straightBetDialog = new StraightBetDialog(getActivity());
      getPresenter().createStraightBetViewModel(getActivity(), straightBetParams, event);
      straightBetDialog.getDialogView().setParameters(getPresenter().getStraightBetViewModel());
      straightBetDialog.getDialogView().setListener(this);
      straightBetDialog.getDialogView().setAmountListener(getPresenter());
  
      straightBetDialog.getDialog().show();
  }
  </code></pre>
  
  <p>Any help appreciated - if i can add any more information please let me know in comments.</p>
  
  <p>UPDATE: this log shows that onLayout is not called before the view are touched.</p>
  
  <pre><code>06-01 12:08:28.221 28768-28768/com.app.dev D/StraightBetView: onMeasure : mw:0 mh:1593
  06-01 12:08:28.271 28768-28768/com.app.dev D/StraightBetView: onAttachedToWindow:
  06-01 12:08:28.271 28768-28768/com.app.dev D/StraightBetView.AcceptChangesView: onAttachedToWindow:
  06-01 12:08:28.281 28768-28768/com.app.dev D/StraightBetView: onMeasure : mw:1440 mh:1008
  06-01 12:08:28.331 28768-28768/com.app.dev D/StraightBetView: onMeasure : mw:1440 mh:1008
  06-01 12:08:28.331 28768-28768/com.app.dev D/StraightBetView: onLayout : true left:0 top:0 right:1440 bottom:1008
  06-01 12:08:28.331 28768-28768/com.app.dev D/StraightBetDialog: adjustHeight(): 1008
  06-01 12:08:28.361 28768-28768/com.app.dev D/StraightBetDialog: adjustHeight(): 1008
  06-01 12:08:28.361 28768-28768/com.app.dev D/StraightBetView: onMeasure : mw:1440 mh:1008
  06-01 12:08:28.361 28768-28768/com.app.dev D/StraightBetDialog: adjustHeight(): 1008
  06-01 12:08:28.391 28768-28768/com.app.dev D/StraightBetView: onMeasure : mw:1440 mh:1008
  06-01 12:08:28.391 28768-28768/com.app.dev D/StraightBetDialog: adjustHeight(): 1008
  &lt;!—- price changed should show accept changes but onLayout is NOT called (onMeasuere is) -—&gt;
  06-01 12:08:55.201 28768-28768/com.app.dev D/StraightBetView.AcceptChangesView: onMeasure : mw:1440 mh:636
  06-01 12:08:55.201 28768-28768/com.app.dev D/StraightBetView.AcceptChangesView: onMeasure : mw:1440 mh:636
  06-01 12:08:55.221 28768-28768/com.app.dev D/StraightBetView.AcceptChangesView: onMeasure : mw:1440 mh:636
  06-01 12:08:55.231 28768-28768/com.app.dev D/StraightBetView.AcceptChangesView: onMeasure : mw:1440 mh:636
  06-01 12:08:55.231 28768-28768/com.app.dev D/StraightBetView: onMeasure : mw:1440 mh:1372
  06-01 12:08:55.231 28768-28768/com.app.dev D/StraightBetDialog: adjustHeight(): 1372
  &lt;!—- ANOTHER CHNAGE (again onLayout is not called)-—&gt;
  06-01 12:10:57.141 28768-28768/com.app.dev D/StraightBetView.AcceptChangesView: onMeasure : mw:1440 mh:636
  06-01 12:10:57.151 28768-28768/com.app.dev D/StraightBetView.AcceptChangesView: onMeasure : mw:1440 mh:636
  06-01 12:10:57.161 28768-28768/com.app.dev D/StraightBetView.AcceptChangesView: onMeasure : mw:1440 mh:636
  06-01 12:10:57.171 28768-28768/com.app.dev D/StraightBetView.AcceptChangesView: onMeasure : mw:1440 mh:636
  06-01 12:10:57.171 28768-28768/com.app.dev D/StraightBetView: onMeasure : mw:1440 mh:1372
  06-01 12:10:57.171 28768-28768/com.app.dev D/StraightBetDialog: adjustHeight(): 1372
  &lt;!—- CLOSED -—&gt;
  06-01 12:11:52.091 28768-28768/com.app.dev D/StraightBetView.AcceptChangesView: onDetachedFromWindow:
  06-01 12:11:52.091 28768-28768/com.app.dev D/StraightBetView: onDetachedFromWindow:
  &lt;!—- SHOW AGAIN -—&gt;
  06-01 12:12:18.091 28768-28768/com.app.dev D/StraightBetView: onMeasure : mw:0 mh:1593
  06-01 12:12:18.161 28768-28768/com.app.dev D/StraightBetView: onAttachedToWindow:
  06-01 12:12:18.161 28768-28768/com.app.dev D/StraightBetView.AcceptChangesView: onAttachedToWindow:
  06-01 12:12:18.171 28768-28768/com.app.dev D/StraightBetView: onMeasure : mw:1440 mh:1008
  06-01 12:12:18.241 28768-28768/com.app.dev D/StraightBetView: onMeasure : mw:1440 mh:1008
  06-01 12:12:18.241 28768-28768/com.app.dev D/StraightBetView: onLayout : true left:0 top:0 right:1440 bottom:1008
  06-01 12:12:18.241 28768-28768/com.app.dev D/StraightBetDialog: adjustHeight(): 1008
  06-01 12:12:18.261 28768-28768/com.app.dev D/StraightBetDialog: adjustHeight(): 1008
  06-01 12:12:18.261 28768-28768/com.app.dev D/StraightBetView: onMeasure : mw:1440 mh:1008
  06-01 12:12:18.261 28768-28768/com.app.dev D/StraightBetDialog: adjustHeight(): 1008
  06-01 12:12:18.291 28768-28768/com.app.dev D/StraightBetView: onMeasure : mw:1440 mh:1008
  06-01 12:12:18.291 28768-28768/com.app.dev D/StraightBetDialog: adjustHeight(): 1008
  &lt;!—- TOUCH -—&gt;
  06-01 12:12:46.391 28768-28768/com.app.dev D/StraightBetView: onMeasure : mw:1440 mh:1008
  06-01 12:12:46.401 28768-28768/com.app.dev D/StraightBetDialog: adjustHeight(): 1008
  &lt;!—- CLOSE KEYBOARD -—&gt;
  06-01 12:13:49.711 28768-28768/com.app.dev D/StraightBetView: onMeasure : mw:1440 mh:1008
  06-01 12:13:49.721 28768-28768/com.app.dev D/StraightBetView: onLayout : false left:0 top:0 right:1440 bottom:1008
  06-01 12:13:49.721 28768-28768/com.app.dev D/StraightBetDialog: adjustHeight(): 1008
  &lt;!—- price changed should show accept changes (since the view was touched onLayout IS called and it displays correctly) -—&gt;
  06-01 12:14:48.461 28768-28768/com.app.dev D/StraightBetView.AcceptChangesView: onMeasure : mw:1440 mh:636
  06-01 12:14:48.461 28768-28768/com.app.dev D/StraightBetView.AcceptChangesView: onMeasure : mw:1440 mh:636
  06-01 12:14:48.471 28768-28768/com.app.dev D/StraightBetView.AcceptChangesView: onMeasure : mw:1440 mh:636
  06-01 12:14:48.481 28768-28768/com.app.dev D/StraightBetView.AcceptChangesView: onMeasure : mw:1440 mh:636
  06-01 12:14:48.481 28768-28768/com.app.dev D/StraightBetView: onMeasure : mw:1440 mh:1372
  06-01 12:14:48.481 28768-28768/com.app.dev D/StraightBetDialog: adjustHeight(): 1372
  06-01 12:14:48.481 28768-28768/com.app.dev D/StraightBetView.AcceptChangesView: onMeasure : mw:1440 mh:636
  06-01 12:14:48.481 28768-28768/com.app.dev D/StraightBetView: onMeasure : mw:1440 mh:1372
  06-01 12:14:48.491 28768-28768/com.app.dev D/StraightBetView.AcceptChangesView: onLayout : true left:0 top:0 right:1440 bottom:636
  06-01 12:14:48.491 28768-28768/com.app.dev D/StraightBetView: onLayout : true left:0 top:0 right:1440 bottom:1372
  06-01 12:14:48.491 28768-28768/com.app.dev D/StraightBetDialog: adjustHeight(): 1372
  </code></pre>