布局与界面设计
osu!lazer 的布局框架究竟是个什么样子,沐雨酱搞了快一年了,也并没有完全搞清楚。可能用 FillFlowContainer 与相对坐标的思路比较好,搞了个大舞台,可惜后来冒烟起火了...
...大型纪录片:《将设计进行到底》,正在播出...
概述
在与计算机相关的图形设计中,我们常常能接触到的无非两类:
- 直接设计(也就是使用绘图、布局可视化工具进行的设计,所见即所得)
- 间接设计(即使用代码、样式定义等进行的设计,可能没有所见,也没有所得)
这么概括似乎有些过度,但是一定程度上能够反映设计方面的大致情况。对于 osu!lazer 不同地方涉及到的设计方式,可以看一眼下图:
从系列标题中不难见得,这部分的内容大多与代码层面的抽象设计相关。
出于软件构建的性质,osu!lazer 的界面设计有这些特性:
- 精准直接:各个组件的属性都通过代码定义
- 易于复用:一个组件类可以在多个组件与界面使用
- 预览不易:每次更改之后需要将所在项目重新构建,出错的话需要通过日志/调试定位(有时甚至难以定位,谜语人了解一下)
我阐释你的码
这边是 osu!lazer 开发过程中,比较稀松平常的一个组件类。然而在运行的时候出现了一些问题,并导致了客户端的卡死与闪退。
尝试阅读下代码,然后找找问题吧。
using System.Drawing;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics;
using osu.Framework.Allocation;
using osuTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Game.Graphics;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osuTK;
namespace osu.Game.Tournament.Components
{
    // This class makes osu!lazer crash. Why?
    public partial class WindowSizeIndicator : CompositeDrawable
    {
        private BindableSize sizeBindable = new BindableSize();
        private TournamentSpriteText winWidthText = null!;
        private TournamentSpriteText winHeightText = null!;
        public WindowSizeIndicator(BindableSize bSize)
        {
            Anchor = Anchor.BottomRight;
            Origin = Anchor.BottomRight;
            sizeBindable = bSize;
            sizeBindable.BindValueChanged(bindSizeChanged);
        }
        [BackgroundDependencyLoader]
        private void load()
        {
            Width = 150;
            Height = 100;
            Alpha = 0;
            AlwaysPresent = true;
            InternalChildren = new Drawable[]
            {
                new EmptyBox(cornerRadius: 10)
                {
                    RelativeSizeAxes = Axes.Both,
                    Colour = Color4.Black.Opacity(0.6f),
                },
                new FillFlowContainer
                {
                    RelativeSizeAxes = Axes.Both,
                    Direction = FillDirection.Vertical,
                    Spacing = new Vector2(5),
                    Children = new Drawable[]
                    {
                        new FillFlowContainer
                        {
                            Height = 40,
                            Direction = FillDirection.Horizontal,
                            Spacing = new Vector2(5),
                            Children = new Drawable[]
                            {
                                new SpriteIcon
                                {
                                    Icon = FontAwesome.Solid.RulerHorizontal,
                                    Size = new Vector2(24),
                                },
                                winWidthText = new TournamentSpriteText
                                {
                                    Text = sizeBindable.Value.Width.ToString(),
                                    Colour = TournamentGame.TEXT_COLOUR,
                                    Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold),
                                },
                            }
                        },
                        new FillFlowContainer
                        {
                            Height = 40,
                            Direction = FillDirection.Horizontal,
                            Spacing = new Vector2(5),
                            Children = new Drawable[]
                            {
                                new SpriteIcon
                                {
                                    Icon = FontAwesome.Solid.RulerVertical,
                                    Size = new Vector2(24),
                                },
                                winWidthText = new TournamentSpriteText
                                {
                                    Text = sizeBindable.Value.Height.ToString(),
                                    Colour = TournamentGame.TEXT_COLOUR,
                                    Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold),
                                },
                            }
                        },
                    }
                },
            };
        }
        private void bindSizeChanged(ValueChangedEvent<Size> e)
        {
            winWidthText.Text = e.NewValue.Width.ToString();
            winHeightText.Text = e.NewValue.Height.ToString();
        }
    }
}
点击阐释你的码
这段代码量确实不小,不过错误也算相对简单...
在这个类中,winWidthText 在两个并列的 FillFlowContainer 中被赋值了两次,导致其错误地将应该显示高度的字段改成了宽度。同时,由于 winHeightText 始终为 null,在调用 bindSizeChanged 时会出现空引用而导致报错。
不过也正是因为代码量大,导致我一度怀疑是 FillFlowContainer 的特性,排错排了二十多分钟😠
各位在写这样的代码时,别忘了检查变量调用和空引用啊(震声