2017/5/3

Xamarin.Forms 教學系列文(十四.壹)Absolute layout & Attached bindable properties



學習目標
  • AbsoluteLayout 用法
  • 如何設定已存在元素之 Attached bindable properties

在前面的章節已經學過 ContentView, Frame, ScrollView 和 StackLayout 這幾個掌控畫面元件位置的 "畫板"。

AbsoluteLayout 顧名思義就是一個提供 "絕對位置" 的畫板。

不像 StackLayout 可以利用 HorizontalOptions 或 VierticalOptions 自動堆疊元件的位置。

在 AbsoluteLayout,你只能 自己指定 元件的位置。

而這章節有個前面沒看過的特殊屬性 -- Attached bindable property
用來 "再次設定" 已存在 AbsoluteLayout 內的元件。

AbsoluteLayout 

唯一和 StackLaout 一樣的是可以增加子元素:
absoluteLayout.Children.Add(child);

但在 AbsoluteLayout 增加子元素時,能夠同時帶入 Rectangle 這結構參數:

什麼是 Rectangle?
是一種包含 x, y 座標寬高  4 個屬性的結構:
        Point point = new Point(x, y);
        Size size = new Size(width, height);
        Rectangle rect = new Rectangle(point, size);

        //帶入 rect 結構
        absoluteLayout.Children.Add(child, rect);

這樣就能決定物件在 AbsoluteLayout 內的位置!

另外,如果要讓系統自己計算 child 的大小(寬高),也可以只帶入 Point 就好:
absoluteLayout.Children.Add(child, point);

假設今天要做緞帶的效果如下圖,AbsoluteLayout 就能派上用場:



其原理是將四個 BoxView 相疊:
    public class AbsoluteDemoPage : ContentPage
    {
        public AbsoluteDemoPage()
        {
            AbsoluteLayout absoluteLayout = new AbsoluteLayout
            {
                Padding = new Thickness(50)
            };

            //在 absoluteLayout 加入四個 BoxView
            absoluteLayout.Children.Add(new BoxView
            {
                Color = Color.Accent
            }, 
            new Rectangle(0, 10, 200, 5));

            absoluteLayout.Children.Add(new BoxView
            {
                Color = Color.Accent
            },
            new Rectangle(0, 20, 200, 5));

            absoluteLayout.Children.Add(new BoxView
            {
                Color = Color.Accent
            }, 
            new Rectangle(10, 0, 5, 65));

            absoluteLayout.Children.Add(new BoxView
            {
                Color = Color.Accent
            }, 
            new Rectangle(20, 0, 5, 65));
            
            //加入 Label
            absoluteLayout.Children.Add(new Label
            {
                Text = "Stylish Header", FontSize = 24
            }, 
            new Point(30, 25));

            absoluteLayout.Children.Add(new Label
            {
                FormattedText = new FormattedString
                {
                    Spans = {
                        new Span
                        {
                            Text = "Although the "
                        },

                        //...以下省略

                    }
                }
            },
             new Point(0, 80));

            this.Content = absoluteLayout;
        }
    }


AbsoluteLayout 最大的 特色(缺點),就是元件位置要自己計算,如上面的範例內加了兩個 Label ,如果你要加入第三個 Label 時,座標應該為何??

但事實上,你可以把 Label 放在 StackLayout 內自己堆疊位置,再將 StackLayout 放到 AbsoluteLayout 內 (Layout 允許巢狀) ~

相比之下,StackLayout 使用上還是比 AbsoluteLayout 方便...

但許多特殊情況我們還是需要 AbsoluteLayout。




AbsoluteLayout 的大小

上面小節都在討論 AbsoluteLayout 內 子元素 的大小和位置,那 AbsoluteLayout 本身的大小呢~?

就像所有的 Visual elements, AbsoluteLayout 也有自己的 HorizontalOptions 和 VerticalOptions 可以設定,初始值為 fill。

若是將 HorizontalOptions 和 VerticalOptions 設定為 center,可以發現 AbsoluteLayout 會依照 內容 自動計算其大小。

我們來製作一個簡單的棋盤,同時觀察設定 center 後 AbsoluteLayout 的大小:
    public class ChessboardFixedPage : ContentPage
    {
        public ChessboardFixedPage()
        {
            const double squareSize = 35;
            AbsoluteLayout absoluteLayout = new AbsoluteLayout
            {
                //先做一個黃色的 AbsoluteLayout
                BackgroundColor = Color.FromRgb(240, 220, 130),
                HorizontalOptions = LayoutOptions.Center,
                VerticalOptions = LayoutOptions.Center
            };

            for (int row = 0; row < 8; row++)
            {
                for (int col = 0; col < 8; col++)
                {
                    // 利用 XOR 二元邏輯運算跳格 
                    if (((row ^ col) & 1) == 0)
                        continue;

                    BoxView boxView = new BoxView
                    {
                        //加入綠色的方塊
                        Color = Color.FromRgb(0, 64, 0)
                    };
                    Rectangle rect = new Rectangle(
                        col * squareSize, 
                        row * squareSize, 
                        squareSize, 
                        squareSize
                        );

                    absoluteLayout.Children.Add(boxView, rect);
                }
            }

            this.Content = absoluteLayout;
        }
    }

執行結果 (黃色部分為 AbsoluteLayout 範圍):


Attached bindable properties

承上一小節,如果我們想把棋盤放大跟螢幕大小一樣,有兩種方法可用:
  • SizeChanged 時,將適合大小的 BoxView 加入
  • 先加入所有 BoxView,在 SizeChanged 時才去改變 BoxView 的大小

兩個方法都可行,但第二種方法我們只需做一次加入 BoxView 的動作。

這種情況下,AbsoluteLayout 提供 SetLayoutBounds() 方法,讓你將 Rectangle 屬性附加上 (Attached) 已存在 AbsoluteLayout 內的物件:
AbsoluteLayout.SetLayoutBounds(view, rect);
*OnPlatform 寫法已更改,請參考此篇文章
來看範例:
    public class ChessboardDynamicPage : ContentPage
    {
        AbsoluteLayout absoluteLayout;

        public ChessboardDynamicPage()
        {
            //先做一個黃色的 AbsoluteLayout
            absoluteLayout = new AbsoluteLayout
            {
                BackgroundColor = Color.FromRgb(240, 220, 130),
                HorizontalOptions = LayoutOptions.Center,
                VerticalOptions = LayoutOptions.Center
            };

            //一口氣加入 32 個 BoxView,但先不帶入 rect 參數
            for (int i = 0; i < 32; i++)
            {
                BoxView boxView = new BoxView
                {
                    Color = Color.FromRgb(0, 64, 0)
                };

                absoluteLayout.Children.Add(boxView);
            }

            ContentView contentView = new ContentView
            {
                Content = absoluteLayout
            };

            //加入 SizeChanged 事件
            contentView.SizeChanged += OnContentViewSizeChanged;

            this.Padding = new Thickness(5, Device.OnPlatform(25, 5, 5), 5, 5);

            this.Content = contentView;
        }

        void OnContentViewSizeChanged(object sender, EventArgs args)
        {
            ContentView contentView = (ContentView)sender;

            //為了讓方塊適應手機 portrait 或 landscape 方向,大小用算的
            double squareSize = Math.Min(contentView.Width, contentView.Height) / 8;
            int index = 0;

            //取出所有 BoxView 並利用 SetLayoutBounds 附加(Attatched) rect 參數
            for (int row = 0; row < 8; row++)
            {
                for (int col = 0; col < 8; col++)
                {
                    // Skip every other square. 
                    if (((row ^ col) & 1) == 0)
                        continue;

                    View view = absoluteLayout.Children[index];

                    Rectangle rect = new Rectangle(
                        col * squareSize, //x
                        row * squareSize, //y
                        squareSize, //width
                        squareSize //height
                    );

                    //附加 rect !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                    AbsoluteLayout.SetLayoutBounds(view, rect);
                    index++;
                }
            }
        }
    }


原文書 P.346 有解釋其運作原理,有興趣可以翻閱 (都是英文喔~)…


書上這裡有介紹一個 寬高的常數變數 AbsoluteLayout.AutoSize 可以在 Rectangle 內使用,

可設定 width 和 height 為自動尺寸:

var rect = new Rectangle (0, 0, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));





沒有留言:

張貼留言