当 showing/hiding 个其他 UI 个元素时布局宽度不变

Layout width doesnt change when showing/hiding other UI elements

免责声明:这是一个非常大的项目的一部分,所以我尽我所能来简化它。

我正在 Xamarin.Android 中浏览向导。我试图让它尽可能灵活,我可以在其中启动向导 activity 并且它将 return 结果 (StartActivityWithResult)。向导的内容由不同的view pager2 adapter设置,是Intent的枚举参数。为了简化问题,我删除了这部分并将适配器类型硬编码到一个不工作的特定适配器。

向导activity非常简单。它有一个标题(工具栏)、一个视图分页器和一个底部按钮。底部按钮可以是图标或文本。这是 XML:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <include
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            layout="@layout/toolbar"/>
        <androidx.viewpager2.widget.ViewPager2
            android:id="@+id/stepsPager"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>
        <LinearLayout
            android:padding="8dp"
            android:background="@drawable/BorderTopOrange"
            android:id="@+id/BottomButton"
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:layout_gravity="bottom"
            android:layout_alignParentBottom="true"
            style="?android:borderlessButtonStyle">
            <ImageView
                android:background="@color/sickGreen"
                android:padding="12dp"
                android:id="@+id/imgBottomButtonDrawable"
                android:gravity="center|center_vertical"
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>
            <TextView
                android:background="@android:color/holo_purple"
                android:text="@string/ok"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:textColor="@color/standardWhite"
                android:textStyle="bold"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="center|center_vertical"
                android:id="@+id/tvBottomButtonText" />
        </LinearLayout>
    </LinearLayout>

随着视图 pager2 中的页面(步骤 1、步骤 2...等)发生变化,我想更新标题、底部按钮 Text/Icon 和底部按钮的 OnClickEventHandler。所以我创建了一个名为 IWizardStep.

的接口
    public interface IWizardStep
        {
            public string Title { get; }
            public string BottomButtonText { get; }
            public int BottomButtonDrawableResource { get; }
            public void OnButtonClick();
        }

ViewPager2 的每个 Fragment 都将实现此接口。

public class WizardStep1 : BaseFragment, IWizardStep
    {
        public string Title => "First Step";

        public string BottomButtonText => "Test";

        public int BottomButtonDrawableResource => 0;

        public void OnButtonClick()
        {
            Toast.MakeText(Context, "Success!", ToastLength.Short).Show();
        }

        public override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
        }

        public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
            return inflater.Inflate(Resource.Layout.wizard_step1, container, false);
        }

        public override void OnResume()
        {
            base.OnResume();
        }
    }

为了更新标题和按钮文本,向导Activity 注册了片段生命周期回调。在生命周期回调的 OnResume 覆盖中,那些 UI 元素被更新。这是回调实现:

class WizardStepLifecycleCallback : FragmentLifecycleCallbacks
    {
        ILogger Logger;
        Action<string> action_ChangeTitle;
        Action<string, int> action_ChangeBottomButton;

        public WizardStepLifecycleCallback(ILogger logger, Action<string> changeTitle, Action<string, int> changeBottomButton)
        {
            Logger = logger;
            action_ChangeTitle = changeTitle;
            action_ChangeBottomButton = changeBottomButton;
        }

        public override void OnFragmentResumed(AndroidX.Fragment.App.FragmentManager fm, AndroidX.Fragment.App.Fragment f)
        {
            base.OnFragmentResumed(fm, f);

            if (f is IWizardStep step)
            {
                Try(() => action_ChangeTitle.Invoke(step.Title));
                Try(() => action_ChangeBottomButton.Invoke(step.BottomButtonText, step.BottomButtonDrawableResource));
            }
        }

        private void Try(Action action)
        {
            try
            {
                action.Invoke();
            }
            catch (Exception e)
            {
                //Ignore exceptions
                Logger.Error(e);
            }
        }
    }

将它们结合在一起就是向导 Activity:

[Activity(ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
    public class WizardActivity : BaseActivity
    {
        private ViewPager2 _viewPager;
        private WizardAdapter _pagerAdapter;
        public TextView tvBottomButton;
        public ImageView imgBottomButton;
        private LinearLayout BottomButton;


        protected override int LayoutResource
        {
            get
            {
                return Resource.Layout.Wizard;
            }
        }

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            tvBottomButton = FindViewById<TextView>(Resource.Id.tvBottomButtonText);
            imgBottomButton = FindViewById<ImageView>(Resource.Id.imgBottomButtonDrawable);
            BottomButton = FindViewById<LinearLayout>(Resource.Id.BottomButton);
            BottomButton.AddClickHandler(OnButtonClick);

            // Instantiate a ViewPager2 and a PagerAdapter.
            _viewPager = FindViewById<ViewPager2>(Resource.Id.stepsPager);
            _viewPager.UserInputEnabled = false;
            SupportFragmentManager.RegisterFragmentLifecycleCallbacks(new WizardStepLifecycleCallback(Logger, UpdateTitle, UpdateBottomButton), false);

            _pagerAdapter = new WizardAdapter(this);
            _viewPager.Adapter = _pagerAdapter;


        }

        protected override void OnResume()
        {
            base.OnResume();
        }

        private void OnButtonClick()
        {
            if (_viewPager.CurrentItem < SupportFragmentManager.Fragments.Count)
            {
                var frag = SupportFragmentManager.Fragments[_viewPager.CurrentItem];

                if (frag != null && frag is IWizardStep wizardStep)
                {
                    try
                    {
                        wizardStep?.OnButtonClick();
                    }
                    catch (Exception e)
                    {
                        Logger.Error(e);
                    }

                }
            }
        }

        public void UpdateTitle(string title)
        {
            EnsureRunOnUiThread(() =>
            {
                Toolbar.Title = title ?? "";
            });
        }

        public void UpdateBottomButton(string buttonText, int drawableResource)
        {
            RunOnUiThread(() =>
            {
                if (drawableResource != 0)
                {
                    //Use Drawable
                    imgBottomButton.SetImageResource(drawableResource);
                    tvBottomButton.Visibility = ViewState.Gone;
                    imgBottomButton.Visibility = ViewState.Visible;
                }
                else
                {
                    //Use Text
                    tvBottomButton.Text = buttonText ?? "";
                    imgBottomButton.Visibility = ViewState.Gone;
                    tvBottomButton.Visibility = ViewState.Visible;
                }
            });
        }
    }

BaseActivity 中,布局是这样膨胀的:

protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            SetContentView(LayoutResource);
            ContentView = FindViewById(Android.Resource.Id.Content);

            Toolbar = FindViewById<AndroidX.AppCompat.Widget.Toolbar>(Resource.Id.toolbar);

            if (Toolbar != null)
            {
                Toolbar.Title = string.Empty;

                SetSupportActionBar(Toolbar);
                SupportActionBar.SetDisplayHomeAsUpEnabled(true);
                SupportActionBar.SetHomeButtonEnabled(true);
            }
        }

最后,这是向导的视图 Pager2 适配器:

public class WizardAdapter : FragmentStateAdapter
    {

        private enum Steps
        {
            Step1
        }

        public WizardAdapter(FragmentActivity fragmentActivity) : base(fragmentActivity) { }

        override public int ItemCount => Enum.GetNames(typeof(Steps)).Length;

        override public Fragment CreateFragment(int position)
        {
            switch ((Steps)position)
            {
                case Steps.Step1:
                default:
                    return new WizardStep1();
            }
        }

    }

问题:问题出在第一次加载屏幕时,底部按钮的文本视图不显示,即使它可见并且文本设置正确(我进行了调试,我可以看到值tvBottomButton.Text)。请注意标题是如何正确设置的,如果我单击底部按钮,Toast 会正确显示。这是第一次访问该屏幕的输出:See the image here.

在我暂停并恢复应用后,它显示正确。 See the image here.

我试过为 tvBottomButtonimgBottomButton 分配权重。我还尝试根据 showing/hiding 元素在运行时分配这些权重,但结果类似,我可以正确看到 TextView 文本,但它的权重是原始权重,而不是刷新后的权重。See Result

我还尝试在 BottomButtontvBottomButtonimgBottomButton 上添加 .RequestLayout()。以及 .Invalidate() 上的 root viewtvBottomButtonBottomButtonimgBottomButton。全部来自 UpdateBottomButton() 方法,但相同。

发现问题。我通过在根视图的 post 中包围底部按钮文本的更新来使其工作,而不是立即 运行 它:

public void UpdateBottomButton(string buttonText, int drawableResource)
    {
        EnsureRunOnUiThread(() =>
        {
            RootView.Post(() =>
            {
                if (drawableResource != 0)
                {
                    //Use Drawable
                    BottomButtonImg.SetImageResource(drawableResource);
                    BottomButtonTV.LayoutParameters = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MatchParent, 0f);
                    BottomButtonImg.LayoutParameters = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MatchParent, 2f);
                    BottomButtonTV.Hide();
                    BottomButtonImg.Show();
                }
                else
                {
                    //Use Text
                    BottomButtonTV.Text = buttonText ?? "";
                    BottomButtonImg.LayoutParameters = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MatchParent, 0f);
                    BottomButtonTV.LayoutParameters = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MatchParent, 2f);
                    BottomButtonImg.Hide();
                    BottomButtonTV.Show();
                }
            });

        });
    }