当 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.
我试过为 tvBottomButton
和 imgBottomButton
分配权重。我还尝试根据 showing/hiding 元素在运行时分配这些权重,但结果类似,我可以正确看到 TextView 文本,但它的权重是原始权重,而不是刷新后的权重。See Result
我还尝试在 BottomButton
、tvBottomButton
和 imgBottomButton
上添加 .RequestLayout()
。以及 .Invalidate()
上的 root view
、tvBottomButton
、BottomButton
、imgBottomButton
。全部来自 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();
}
});
});
}
免责声明:这是一个非常大的项目的一部分,所以我尽我所能来简化它。
我正在 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.
我试过为 tvBottomButton
和 imgBottomButton
分配权重。我还尝试根据 showing/hiding 元素在运行时分配这些权重,但结果类似,我可以正确看到 TextView 文本,但它的权重是原始权重,而不是刷新后的权重。See Result
我还尝试在 BottomButton
、tvBottomButton
和 imgBottomButton
上添加 .RequestLayout()
。以及 .Invalidate()
上的 root view
、tvBottomButton
、BottomButton
、imgBottomButton
。全部来自 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();
}
});
});
}