上一个主题中我们讨论学习了Qt布局的概念及Qt基类QLayout的认识。为了更好的进行布局控制,Qt实现了常见的布局有QFormLayout,QBoxLayout,QGridLayout,QStackLayout,每个布局都有着各自的特点,在此逐个理解学习

   1、QFormLayout:表单布局,顾名思义就是实现表单模式的布局。表单就是提示用户进行交互的一种模式,其主要有两个列组成,第一个列用于显示信息,给予用提提示,一般叫做label域,第二个是需要用户选择输入的,一般叫field域。表单就是很多由这两项/两列内容组成的行的布局。label与field关系就是label是关联field的。

   表单布局完全可以使用表格布局实现,是一种多行两列的列表,但表单布局提供一种比较完善的策略,其主要有以下优点

   1)可以适应不同平台外观和感觉的一致性

   2)支持一行的label和field域换行显示,有两种策略一种是如果输入域过长,field换行显示,还有一种就是不管怎么样都换行显示,当然默认是一行显示两个域就可以了

   3)创造label--field对很便捷的接口,因为使用一般的布局,想要关联label和field,创建好label和feild,并且调用label的setBuddy才能完成,但formlayout使用addRow就可以直接对应了。


   表单布局的样式可以通过几个方面体现

   1)label的样式,是左对齐还是右对齐,可以使用setLabelAlignment进行设置

   2)form的样式,其内容的显示方式,则可以通过setFormAlignment进行设置

   3)表单一行内容的显示,是否换行,则使用setRowWrapPolicy设置,主要值是DontWrap

   Rows,即Field域永远跟着其label;WrapLongRows,给予label足够的空间,剩余的空间给field域,如果field域的最小空间比剩余的控件大/宽,则field会换行到下一行显示;WrapAllRows,所有的field域不管后面剩余的空间是否够,都自动换行。

   4)filed域拉升生长策略growth policy,其主要是FieldGrowthPolicy控制,首先是FieldStayAtSizeHint 0 ,field域永远不会超过有效的sizehint尺寸;ExpandingFieldGrow 1,field域水平尺寸拉升或者最小值超出时会占用可用空间,其他field在sizehint尺寸下不会扩大grow;AllNonFixedFieldGrow 2所有的field允许他们长grow就长grow到填充可用的空间,如果是fixed固定尺寸策略的field则不会长


   表单由两列组成,所以一本都是分为label和field域,但是有些控件占用两行,所以对每个行其可以通过角色来访问,如LabelRole 0,一个label控件,FieldRole 1,一个field空间,SpanningRole就是占用了label和field两列的控件。当访问某一个行的某个特定的空间时,如果不知道具体名,则可以通过此角色访问。


   1.1 表单布局formLayout的属性

   1)fieldGrowthPolicy :FieldGrowthPolicy ,表示feild域如何扩张延伸的方式

   如果没有任何一个field域可以延伸并且表单form重置了大小,多余的空间会根据当前的表单form aligment对齐方式分部。

   fieldGrowthPolicy()获取此属性。setFieldGrowthPolicy(FieldGrowthPolicy)进行设置

   2)formAligment : Qt::Alignment ,此属性表示扁担布局formlayout内容的对齐方式。使用formAlignment()获取,setFormAlignment(Qt::Alignment)进行设置

   3)horizontalSpacing :int表示每行空间之间的空间间隔。通过horizontalSpacing()获取,setHorizontalSpacing(int)设置

   4)域horizontalSpacing对应的是verticalSpacing保存了垂直放置的控件之间的间隔。verticalSpacing获取,setVerticalSpacing设置。

   5)labelAlignment:Qt::Alignment 保存标签label水平方向的对齐alignment模式。

   通过labelAlignment()获取,setLabelAlignment进行设置。

   6)rowWrapPolicy : RowWrapPolicy保存表行每行换行的方式。其可见值概述已做详细描述

   1.2 QFormLayout的接口

   表单布局是以行作为基本单位的,所以需要为表单布局添加控件,一个是标签域label,一个是输入域field,通过addRow方法进行添加。

   1)添加一行表单

   添加第一个域可以是QWidget或者QString,如果是后者会自动创建一个QLabel,并且将field的QWidget设置为label的buddy。field可以是QLayout或者QWidget。

   如果是占用两行的空间,则只有一个变量,可以是QWidget或者QLayout。具体形式如下

   void addRow(QWidget *label,QWidget *field):添加一行到布局末尾,使用label和field填充表单布局的对应域。

   void addRow(QWidget *label,QLayout *layout):

   void addRow(const QString *labelText,QWidget *widget):会把widget设置为qlabel的buddy

   void addRow(const QString *labelText,QLayout *layout):

   void addRow(QWidget *widget):占用两列

   void addRow(QLayout *layout):占用两列

   int count(),返回有多少个元素,在后续查找、插入和删除的时候用。

   2)查找元素

   void getItemPosition(int index,int *rowPtr,ItemRole *rolePtr):找到指定位置index元素item的行数row值和角色值。如果index越界了,rowptr值被设置为-1,让否则将值存到rowPtr和rolePtr中。

   void getLayoutPosition(QLayout *layout,int *rowPtr,ItemRole *rolePtr)

   找到特定的子布局layout的行号row和role(colume)。如果layout不在formLayout,那么rowPtr设置为-1,否则正确设置。

   void getWidgetPosition(QWidget *w,int *rowPtr,ItemRole *rolePtr)追溯控件w在布局中的行号row和角色role。如果此布局没有w,则rowPtr返回 -1.

   QLayoutItem *itemAt(int index)返回索引为index的控件

   QLayoutItem *itemAt(int row,ItemRole role)返回布局中元素在row行角色是role的控件,如果没有则返回0

   QLayoutItem * QFormLayout::takeAt ( int index )获取索引为index的元素并删除原来的元素

   3)插入元素,与addRow对应,只是添加一个参数及int row,指定在某个行之后添加。如果row越界,则自动添加到最后一行。

   setItem(int row,ItemRole role,QLayoutItem *item)将item元素设置到指定的行号row的指定角色role的位置。如果此处的位置已经被占用了,则插入失败。

   void setLayout(int row,ItemRole role,QLayout *layout)将指定的layout设置到行row角色为role的位置,根据需要扩展没有行元素的布局。如果此单元被占用,则不会插入

   void setWidget(int row,ItemRole,QWidget *widget)将指定的widget设置到行row角色为role的位置,根据需要扩展没有行元素的布局。如果此单元被占用,则不会插入

   4)QWidget *labelForField(QWidget *field)返回与field关联的label控件

      QWidget *labelForLayout(QLayout *layout)返回与field关联的label控件

   5)QSize minimumSize()获取布局的最小尺寸,对应的有maxmumSize最大尺寸

   6)rowCount()返回此布局中有多少行

   7)setGeometry(QRect rect)设置布局的空间大小

   8)void setSpacing(int spacing)设置垂直和水平方向的空间间隔。spacing()返回这个值,前提是horizontal和vertical空间相等

   具体使用见一个简单的输入例子:用户登录信息窗口

#include<QLabel>

#include<QLineEdit>
#include<QFormLayout>
#include<QPushButton>
voidinitLayout(QWidget&w)
{
QFormLayout*mainFormLayout=newQFormLayout();
mainFormLayout->setSizeConstraint(QLayout::SetFixedSize);
mainFormLayout->setVerticalSpacing(40);
mainFormLayout->setHorizontalSpacing(10);
mainFormLayout->setRowWrapPolicy(QFormLayout::DontWrapRows);
mainFormLayout->setFieldGrowthPolicy(QFormLayout::FieldsStayAtSizeHint);
mainFormLayout->setLabelAlignment(Qt::AlignRight|Qt::AlignVCenter);
mainFormLayout->setFormAlignment(Qt::AlignCenter);
 
  
QLineEdit*nameLineEdit=newQLineEdit("enteryourname");
//nameLineEdit->setInputMask("ANNNNNnnnnnnnnnnnnnn");
nameLineEdit->setDragEnabled(true);
nameLineEdit->setAlignment(Qt::AlignCenter);
nameLineEdit->setFrame(false);
nameLineEdit->show();
QLineEdit*pwdLineEdit=newQLineEdit();
pwdLineEdit->setEchoMode(QLineEdit::Password);
pwdLineEdit->setInputMask("XXXXXX");//6-20
pwdLineEdit->setAlignment(Qt::AlignLeft|Qt::AlignVCenter);
pwdLineEdit->setDragEnabled(false);
pwdLineEdit->setFrame(true);
pwdLineEdit->show();
 
  
QPushButton*buttonOk=newQPushButton("OK");
QObject::connect(buttonOk,SIGNAL(pressed()),nameLineEdit,SLOT(clear()));
QPushButton*buttonCancel=newQPushButton("Cancel");
QObject::connect(buttonCancel,SIGNAL(pressed()),nameLineEdit,SLOT(clear()));
 
  
QHBoxLayout*buttonLayout=newQHBoxLayout();
buttonLayout->setSizeConstraint(QLayout::SetFixedSize);
buttonLayout->addWidget(buttonOk);
buttonLayout->addWidget(buttonCancel);
 
  
mainFormLayout->addRow("&Name:",nameLineEdit);
mainFormLayout->addRow("&Password:",pwdLineEdit);
mainFormLayout->addRow(buttonLayout);
//mainFormLayout->addRow(buttonOk);
//mainFormLayout->addRow(buttonCancel);
 
  
w.setLayout(mainFormLayout);
}
intmain(intargc,char*argv[])
{
QApplicationa(argc,argv);
FormLayouTestw;
w.resize(1280,720);
initLayout(w);
w.show();
 
  
returna.exec();

}

运行结果如下

150620540.png


   2、 QBoxLayout:框布局,这是最简单的一种布局,这种布局将需要管理的控件排成一列或者一行。其实讲QBoxLayout占用的空间分成一行或者一列框,然后把布局所管理的控件填进取。

   此布局的方向orientation有两种,水平的即horizontal和垂直的vertical。每一个被放置到框里面的布局获取的大小最小是其minimum大小,最大不超过maxmumSize。任何多余的空间将根据伸展因子共享处理。如果此布局不是最顶层布局,则一定需要将布局放置到父窗口或者布局里面

   为了更快捷的使用,可以使用QHBoxLayout和QVBoxLayout创建指定方向的boxlayout,当然可以根据排列空间的方式不同,使用QBoxLayout创建布局,其方向主要是水平方向:LeftToRight、RightToLeft;垂直方向:BottonToTop,TopToBottom.

   添加一个box控件主要方式如下:

   1)addWidget()添加一个窗口不见widget到QBoxLayout,并且设置此不见得伸缩因子。伸展因子是boxes的行使用。

   2)addSpacing()添加一个空的box。

   3)addStretch()创建一个可伸缩的box

   4)addLayout()将一个包含其他控件的布局添加到box上并设置layout的伸缩因子。同样有对应的insert方法。

   QBoxLayout有两个边界宽度,一个是内容边界,setContentsMargins,设置每个窗口部件的外部边距。这是每个QBoxLayout四边的保留空间

   setSpacing设置两个相邻的box之间的间距,可以使用addSpacing获取更多的空间。

   删除一个控件,使用removeWidget,或者调用QWidget::hide()一样可以从布局中删除,知道show被调用。

   简单的讲,这个布局是要注意以下几点:

   第一,布局控件的排列方式决定了布局的方向,其主要有两个布局,水平和垂直

   第二,布局各个空间的拉升,不是所有的控件平分布局的空间,而是根据伸缩因子和显示比例完成控件空间的分配。

   第三,各个控件之间的间隔及整个控件和布局的外部边距大小。这三点决定了此布局的使用

   2.1 常用的函数

   QBoxLayout::QBoxLayout ( Directiondir, QWidget * parent = 0 )

   创建一个框布局,dir有TopToBottom,BottomToLeft,LeftToRight及RightToLeft

   void QBoxLayout::addLayout ( QLayout * layout, int stretch = 0 )

   添加一个布局到框的末尾,设置伸缩因子stretch factor为stretch值,默认为0

   void QBoxLayout::addSpacing ( int size )

   添加一个不能伸缩的空间(一个QSpacerItem),其宽度设置为size到布局末尾。框布局提供了默认的边距margin和spacing,这是额外添加的空间。

   void addStretch(int stretch = 0)

   添加一个可伸缩的空间(一个QSpacerItem),设0为最小值并且伸缩因子为stretch值到布局末尾

   void addStrut(int size)

   限制垂直尺寸到最小值size,如一个LeftToRight的框布局的高度。

   void addWidget(QWidget *w,int stretch=0,Qt::Alignment alignment=0)

   添加一个窗口部件到此框布局的末尾,并设定其伸缩率为stretch和alignment。

   stretch设置为0,并且没有其他空间有stretch大于0的控件,则空间的分配按照默认的值进行分配。alignment被赋值为alignment,默认值为0,表示控件将填充整个单元。

   注意此分配因子的工作原理,stretch其实相当于一个占位符号,其可伸缩在0与stretch值之间,原则就是如果分配完空间有额外的空间,则根据stretch值大小比例去分,如果布局空间不够,则会压缩这个空间以被其他控件使用。

   比如有两个控件,在添加的时候第一个控件设置为200,第二个设置为0,那么最后结果是第一个控件比第二个控件多占用0-200的空间,如果第二个也设置为200,则两个伸缩系数一样的,所以两个控件占用空间基本一致,如果第二个设置为100,则其空间占有约以2:1的比例分多余的空间。

   本质上来讲spacing和stretch都是QSpaceItem控件,占用空间用的,只是一个固定的,一个可伸缩的。

   int count()返回此布局有多少个空间,此值在后续中inset相关的函数中进行使用。

   Direction direction()返回布局的方向,添加控件与伸缩与这个方向一致。

   void insertLayout ( int index, QLayout * layout, int stretch = 0 )

   插入布局到指定index位置,并设定伸缩因子stretch。如果index是负数,layout添加到末尾。

   void QBoxLayout::insertSpacing ( int index, int size )

   在指定位置插入大小为size空间的不可伸缩的box。次函数常用于在指定位置添加空间。

   void QBoxLayout::insertStretch ( int index, int stretch = 0 )

   在指定位置添加可伸缩的空间,从0到伸缩因子stretch伸缩。

   void QBoxLayout::insertWidget ( int index, QWidget * widget, int stretch = 0,Qt::Alignmentalignment = 0 )

   在指定位置index插入控件widget

   QSize minimumSize() QSize maxmumSize()获取布局的最大最小空间尺寸

   setDirection()设置方向

   setGeometry(QRect r)设置布局空间大小尺寸

   setSpacing(int spacing)设置相邻控件的间距。

   setStretch(int index,int stretch)

   设置指定位置的伸缩因子为stretch值

   bool setStretchFactor(QWidget *w,int stretch)设置窗口部件QWidget的伸缩因子为stretch,如果找到此部件返回true,否则返回false

   bool setStretchFactor(QLayout *layout,int stretch)设置子布局的伸缩因子为stretch,如果找到此布局返回true,否则返回false

   QSize sizeHint()返回合适的布局尺寸

   int spacing()如果空间属性可用,则直接返回,否则需要通过计算后返回。因为布局间距受窗口控件样式决定。


   int QBoxLayout::stretch ( int index )

   返回index位置的伸缩因子stretch

   注:stretch其实是规定了一个可以伸缩的范围,在这个范围内更好的合理的展现布局,如果空间过大,则此stretch会占用的多一点,如果空间太少,则会占用的少一点,addSpacing是添加一个固定大小空间的box,此相当于一个控件占用一个空间,只是不显示,其布局在处理的时候不可压缩这部分空间,举例如下:

QPushButton*buttonOk=newQPushButton("OK");

QPushButton*buttonCancel=newQPushButton("Cancel");
 
  
QBoxLayout*buttonLayout=newQBoxLayout(QBoxLayout::LeftToRight);
buttonLayout->setSizeConstraint(QLayout::SetFixedSize);
buttonLayout->addStretch(10);
buttonLayout->addWidget(buttonOk);

buttonLayout->addStretch(200);

    //buttonLayout->addSpacing(200);


buttonLayout->addWidget(buttonCancel);

buttonLayout->addStretch(10);

   效果如下:

171308774.png171308407.png


buttonLayout->addStretch(200);       buttonLayout->addSpacing(200);


说明:QSpacerItem是为布局提供空白空间的类,在addStretch及addSpacing实际上就是处理这些。


   3、QStackedLayout:堆栈布局,是将一堆widget控件放置在一起,只有一个控件是能看见的。其不是管理控件的尺寸即位置,而是尺寸的显示,可以被一些字窗口控件(页)在同一空间填充。多用于页面切换等的操作。但是QStackedWidget并没有提供一种方法给用户区切换页。这种方式就需要借助QComboBox或者QListWidget去存储每个页面的标题,从而配合实现切换页面。

   当填充布局的时候,窗口部件widget会加载到内部的一个列表中,通过indexOf()方法返回一个窗口部件widge的索引在内部的列表中。当然widget可以被添加到列表的末尾,或者插入到指定的索引位置。removeWidget()方法删除在索引序号为index的窗口部件。通过count()方法可以获取到加载到布局layout的元素个数。

   widget()方法返回在指定位置上的部件widget,当前屏幕上显示的的窗口部件的索引序号值可以用使用currentIndex()获取,并且使用setCurrentIndex()设置。相似的情况,当前的显示可见的窗口部件可以通过currentWidget获取到,使用setCurrentWidget()更新。

   但是不管什么情况,只要当前widget的布局变了或者remove删除,则currentChanged()信号和widgetRemoved()信号会一次发送。

   3.1 重要的属性

   1)count:const int表示布局layout包含的窗口控件的数量,count()访问。

   2)currentIndex:int,此属性保存当前可见窗口部件的index值,如果没有当前窗口部件则返回-1,使用currentIndex()访问,setCurrentIndex(int index)进行设置。如果currentIndex改变了,则信号currentChanged(int index)信号发射。

   3)stackingMode:StackingMode,此属性决定子窗口部件可见方式的处理,默认是StackOne,即只有当前窗口是可见的,stackAll表示所有窗口都可见,但是只是提出了当前窗口。通过stackingMode()访问,setStackingMode(StackingMode)进行设置

   3.2 重要的函数:

   int addWidget(QWidget *widget)将指定的widget添加到布局的末尾,并返回对应部件的位置index。如果执行此函数之前QStackedLayout是空,则将此设置为当前窗口部件。

   void currentChanged(int intx)信号,当当前窗口部件改变后发射此信号。index是最新的部件索引,或则-1,如果没有一个新的当前激活的窗口,,如为空时

   QWidget *currentWidget()返回当前窗口部件,如果没有窗口部件则返回0

   int i当前indensertWidget(int index,QWidget *widget)在QStackedLayout的指定位置index插入widget。如果index越界,则追加到末尾。如果之前layout是空的,则此被设定为当前widget。这种插入的方式,index小于等于当前窗口的currentIndex,则会增加当前的index,但是保留当前活跃的用户。

   void setCurrentWidget(QWidget *widget)设置当前窗口为指定widget。

   widget(int intdex)返回index边上的widget或者0,如果没有这个值则。

   widgetRemoved(int index)窗口被删除时信号被发出。

#include <QApplication>

#include <QLabel>


#include <QStackedLayout>

#include <QDebug>

void initStackedLayout(QWidget &w)

{

QStackedLayout *stackedLayout = new QStackedLayout();

stackedLayout->setStackingMode(QStackedLayout::StackAll);

stackedLayout->setSizeConstraint(QLayout::SetFixedSize);

QLabel *firstPage = new QLabel();

firstPage->setAlignment(Qt::AlignLeft);

firstPage->setWordWrap(true);

firstPage->setText("1111");

QLabel *first1Page = new QLabel();

first1Page->setAlignment(Qt::AlignCenter);

first1Page->setWordWrap(true);

first1Page->setFrame(tue);

first1Page->setText("2222");

QLabel *first2Page = new QLabel();

first2Page->setAlignment(Qt::AlignRight);

first2Page->setWordWrap(true);

first2Page->setText("3333");

qDebug()<<stackedLayout->addWidget(firstPage)<<endl;

qDebug()<<stackedLayout->addWidget(first1Page)<<endl;

qDebug()<<stackedLayout->addWidget(first2Page)<<endl;

stackedLayout->setCurrentIndex(1);

w.setLayout(stackedLayout);

}

int main(int argc,char **argv)

{

QApplication app(argc,argv);

QWidget w;

w.setFixedSize(1280,720);

initStackedLayout(w);

w.show();

return app.exec();

}

013636344.png013636557.png

这是stackedMode,前者是StackedAll ,后者是StackedOne


   4、QGridLayout:网格布局,顾名思义就是将所要管理的窗口控件放置到一个网格中,也就是一个二维表。所以其有行与列的概念。QGridLayout从其父窗口或者布局获取到可用的空间,并分为行和列rows and columns,然后将每个窗口widget放置到对应的单元cell中并管理起来。

   有列,又有行,其两者的行为是一样的。所以以列为点先讨论。每个列都有一个最小的宽度,还有一个伸缩因子,用于在吗某个范围内占用空用的空闲空间。每个列的最小宽度是由设置setColumnMinimumWidth()和控件widget的最小尺寸的相互结果。使用setColumnStretch()设置此列的伸缩参数,其决定了一列多少空间是可以使用的并且在其之上的最小空间是可用的。

   一般来讲,每列所管理的控件或者布局可以通过使用addWidget()添加,但是也有可能性就是一个控件需要占用多行,这样就需要使用行或者列的spanning参数,表示需要占用的行数或者列数。

   和其他布局一样,如果从布局拿掉一个管理的控件,调用removeWidget()参数,或者调用QWidget的hide函数也可以释放其占用的空间。

   4.1 属性

   1)horizontalSpacing:int 用于保留两个控件之间水平方向的空间,horizontalSpacing获取,setHorizontalSpacing(int spacing)进行设置。

   2)verticalSpacing : int在垂直方向上两个控件之间的间距,通过verticalSpacing获取,setVerticalSpacing进行设置

   上两个属性如果没有设置,则继承子其父类布局或者控件。其和layout本身的spacing关系是如果两者相等,则使用spacing,否则spacing为-1值

   4.2 主要的函数

   1)addLayout(Qlayout *layout,int row,int column,Qt::Alignment =0)

   layout:需要添加的布局对象;row column表示要进价进入网格的行列号,top-left位置是(0,0),alignment表示控件放置在cell但愿的位置,默认为0表示填充整个单元cell。

   2)addLayout(QLayout layout,int row,int column,int rowSpan,int columnSpan,Qt::Alignment = 0)同上面实现一样的功能,这里需要注意的就是添加的控件占用多少个单元,跨越多少个单元及rowSpan与columnSpan

   3)addWidget(QWidget *widget,int row,int column,Qt::Alignment =0)

   widget:需要添加的窗口不见;row column表示要进价进入网格的行列号,top-left位置是(0,0),alignment表示控件放置在cell但愿的位置,默认为0表示填充整个单元cell。

   如果rowSpan与/或者columnspan为-1,则布局会扩展到bottom与/或者right边界位置。

   4)addWidget(QWidget *widget,int fromrow,int fromcolumn,int rowSpan,int columnSpan,Qt::Alignment = 0)同上面实现一样的功能,这里需要注意的就是添加的控件占用多少个单元,跨越多少个单元及rowSpan与columnSpan。如果rowSpan与/或者columnspan为-1,则布局会扩展到bottom与/或者right边界位置。

   5)QRect cellRect(int row,int column)获取网格单元在row与column决定的单元的空间尺寸。如果row和column越界,则返回无效的QRect

   6)int columnCount() 获取columns列的个数。

   同样有rowCount返回row数

   7)int columnMinimumWidth(int column)获取指定列的最小宽度,通过setColumnMinimuWidth(int column,int width)设置

   同样有rowMinimumHeight(int row)获取某一行的最小高度setRowMinimunHeight(int row,int height)进行设置

   8)int columnStretch(int column)获取指定列column的拉升因子,可以用setColumnStretch(int column,int stretch)进行设置

   同样 int rowStretch(int row)获取行row的伸缩值,通过setRowStretch(int row,int stretch)设置。

   9)count()返回网格有多少个单元

   10)void getItemPosition(int index,int *row,int *column,int *rowSpan,int *columnspan)

   用于获取index所制定的元素所在的行列号及水平垂直所占用的跨越单元个数。

   11)setSpacing()设置网格垂直及水平方向控件之间的间隔尺寸。spacing()获取,如果horizontalSpacing与verticalSpacing不同,返回-1




   综上所述布局管理所管理的都是各个控件存放的位置及尺寸,以及控件拉升的策略。对控件的操作,主要获取其相关的信息,如spacing及strech,对于多行这种有行列不同的spacing,则根据具体情况获取设置。

   根据不同的布局特点,都添加了添加控件及子布局的接口,也提供了通过index获取布局的相关信息。

   布局的使用方法介绍就暂时告一段落,下一讲将会针对具体的应用使用布局。后续也会介绍如何实现自己的布局。