疯狂Android讲义(二)——第二部分:第1组UI组件(布局管理器)

一、第1组UI组件:布局管理器

        Android 的界面组件比较多,不利于掌握它们内在的关系。为了帮助读者更好地掌握Android界面组件的关系,本书将会把这些界面组件按照它们的关联分析,分为几组进行介绍。本节介绍的是第1组UI组件:以ViewGroup为基类派生的布局管理器

        为了更好地管理Android应用的用户界面里的各组件,Android提供了布局管理器。通过使用布局管理器,Android应用的图形用户界面具有良好的平台无关性通常,推荐使用布局管理器来管理组件的分布、大小,而不是直接设置组件位置和大小。

        为了让组件在不同的手机屏幕上都能运行良好——不同手机屏幕的分辨率、尺寸并不完全相同,如果让程序手动控制每个组件的大小、位置,则将给编程带来巨大的困难。为了解决这个问题,Android提供了布局管理器。布局管理器可以根据运行平台来调整组件的大小,程序员要做的,只是为容器选择合适的布局管理器。

        Android的布局管理器本身就是一个UI组件,所有的布局管理器都是ViewGroup的子类。如下图显示了Android布局管理器的类图。

疯狂Android讲义(二)——第二部分:第1组UI组件(布局管理器)_第1张图片

        可以看出,所有布局都可作为容器类使用,因此可以调用多个重载的addView()向布局管理器中添加组件。实际上,我们完全可以用一个布局管理器嵌套到其他布局管理器中——因为布局管理器也继承了View,也可以作为普通UI组件使用。

        如图所示的GridLayoutRaletiveLayout已被Android 9 标注为不推荐使用,推荐使用ConstraintLayout来代替他们,因此后面对GridLayout和RaletiveLayout就点一下,不详解了。

  1.线性布局

        线性布局由LinearLayout类来代表,线性布局有点像Swing编程里的Box,它们都会将容器里的组件一个挨着一个地排列起来。LinearLayout可以控制各组件横向排列(通过设置android: orientation属性控制),也可控制各组件纵向排列。

        Android的线性布局不会换行,当组件一个挨着一个地排列到头之后,剩下的组件将不会被显示出来。

        下表显示了LinearLayout支持的常用XML属性及相关方法的说明。

XML属性 相关方法 说明
android:baselineAligned setBaselineAligned(boolean) 该属性设为false,将会阻止该布局管理器与它的子元素的基线对齐
android:divider setDividerDrawable(Drawable) 设置垂直布局时两个按钮之间的分隔条
android:gravity setGravity(int) 设置布局管理器内组件的对齐方式。该属性支持top、 bottom、left、right、center_vertical、fill_vertical、center_horizontal、fill_horizontal、center、fill、clip_vertical、clip_horizontal 几个属性值。也可以同时指定多种对齐方式的组合,例如 left|center_vertical 代表出现在屏幕左边,而且垂直居中
android:measureWithLargestChild setMeasureWithLargestChildEnabled(boolean) 当该属性设为true时,所有带权重的子元素都会具有最大子元素的最小尺寸。
android:orientation setOrientation(int) 设置布局管理器内组件的排列方式,可以设置为horizontal(水平排列)、vertical(垂直排列,默认值)两个值的其中之一
android:weightSum 设置该布局管理器的最大权衡和

        LinearLayout包含的所有子元素都受LinearLayout.LayoutParams控制,因此LinearLayout包含的子元素可以额外指定如下表所示的属性。

XML属性 相关方法 说明
android:layout_gravity 指定该子元素在LinearLayout中的对齐方式
android:layout_weight 指定该子元素在 LinearLayout中所占的权重

提示:

基本上很多布局管理器都提供了相应的LayoutParams内部类该内部类用于控制它们的子元素支持指定android: layout_gravity属性,该属性设置该子元素在父容器中的对齐方式。与android: layout_gravity相似的属性还有android:gravity属性(一般容器才支持指定该属性),android: gravity属性用于控制它所包含的子元素的对齐方式

  2.表格布局

        表格布局由TableLayout所代表,TableLayout继承了LinearLayout因此它的本质依然是线性布局管理器。表格布局采用行、列的形式来管理UI组件,TableLayout并不需要明确地声明包含多少行、多少列,而是通过添加TableRow其他组件来控制表格的行数和列数。

        每次向TableLayout中添加一个TableRow,该TableRow就是一个表格行,TableRow也是容器,因此它也可以不断地添加其他组件,每添加一个子组件该表格就增加一列

        如果直接向TableLayout中添加组件,那么这个组件将直接占用一行

        在表格布局中,列的宽度由该列中最宽的那个单元格决定,整个表格布局的宽度则取决于父容器的宽度(默认总是占满父容器本身)。

        在表格布局管理器中,可以为单元格设置如下3种行为方式。

  • Shrinkable:如果某个列被设为 Shrinkable,那么该列的所有单元格的宽度可以被收缩,以保证该表格能适应父容器的宽度。
  • Stretchable:如果某个列被设为Stretchable,那么该列的所有单元格的宽度可以被拉伸,以保证组件能完全填满表格空余空间。
  • Collapsed:如果某个列被设为Collapsed,那么该列的所有单元格会被隐藏

        TableLayout继承了LinearLayout,因此它完全可以支持LinearLayout所支持的全部XML属性。除此之外,TableLayout还支持如下表所示的XML属性。

XML属性 相关方法 说明
android:collapseColumns seiColumnCollapsed(int,boolean) 设置需要被隐藏的列的列序号。多个列序号之间用逗号隔开
android:shrinkColumns setShrinkAllColumns(boolean) 设置允许被收缩的列的列序号。多个列序号之间用逗号隔开
android:stretchColumns setStretchAllColumns(boolean) 设置允许被拉伸的列的列序号。多个列序号之间用逗号隔开

案例:丰富的表格布局

    XML代码




    
    

        

        上面页面中定义了3个TableLayout,3个TableLayout中粗体字代码指定了它们对各列的控制行为。

  • 第1个TableLayout,指定第2列允许收缩,第3列允许拉伸。
  • 第2个TableLayout,指定第2列被隐藏。
  • 第3个TableLayout,指定第2列和第3列允许拉伸。

        布局中第1个TableLayout添加两行,第1行不使用TableRow,直接添加一个Button,那么该Button自己将占用整行。第2行先添加一个TableRow,并为TableRow添加3个Button,那说明该表格将包含3列。接下来两个TableLayout看代码就行。
 

    效果图:

疯狂Android讲义(二)——第二部分:第1组UI组件(布局管理器)_第2张图片

  3.帧布局

        帧布局由FrameLayout所代表,FrameLayout直接继承了ViewGroup组件。

        帧布局容器为每个加入其中的组件创建一个空白的区域(称为一帧),每个子组件占据一帧,这些帧都会根据gravity属性执行自动对齐。帧布局的效果有点类似于AWT编程的CardLayout,都是把组件一个个地叠加在一起。与CardLayout的区别在于,CardLayout可以将下面的Card移上来,但FrameLayout则没有提供相应的方法。

        下表显示了FrameLayout常用的XML属性及相关方法说明。

XML属性 相关方法 说明
android:foregroundGravity setForegroundGravity(int) 定义绘制前景图像的gravity 属性
android:measureAllChildren setMeasureAllChildren(boolean) 设置该布局管理器计算大小时是否考虑所有子组件,默认为false,即只考虑可见状态的组件

        FrameLayout包含的子元素也受FrameLayout.LayoutParams控制因此它所包含的子元素也可指定android: layout_gravity属性,该属性控制该子元素在FrameLayout中的对齐方式。

        下面示范了帧布局的用法,可以看到6个TextView叠加在一起,上面的TextView遮住下面的TextView。下面是使用帧布局的页面定义代码。

    XML布局:




    
    
    
    
    
    
    

    接下来实现代码部分:java文件

public class test_4 extends AppCompatActivity {

    int[] names = new int[]{R.id.test_4_view1,R.id.test_4_view2,
            R.id.test_4_view3,R.id.test_4_view4,R.id.test_4_view5,R.id.test_4_view6
    };
    TextView[] views = new TextView[names.length];

    private Handler handler = new MyHandler(new WeakReference(this));

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_4);
        //绑定控件
        for (int i = 0; i  activity;
        public MyHandler(WeakReference activity){
            this.activity = activity;
        }
        private int currentColor = 0;
        //定义一个颜色数组
        int[] colors = new int[]{R.color.color1,R.color.color2,
                R.color.color3,R.color.color4,R.color.color5,R.color.color6
        };

        @Override
        public void handleMessage(@NonNull Message msg) {
            //表明消息来自本程序所发送的
            if(msg.what == 0x123){
                for (int i = 0,len = activity.get().names.length; i < len; i++) {
                    activity.get().views[i].setBackgroundResource(
                            colors[(i + currentColor)%colors.length]);
                }
                currentColor++;
            }
            super.handleMessage(msg);
        }
    }
}

     动图演示:

疯狂Android讲义(二)——第二部分:第1组UI组件(布局管理器)_第3张图片

        上面程序中的粗体字代码定义了一个每0.4秒执行一次的任务,该任务仅仅向Handler发送—条消息,通知它更新6个TextView的背景色。

        可能会有读者提出疑问:为何不直接在run()方法里直接更新6个TextView的背景色呢?这是因为Android的View和UI组件不是线程安全的,所以Android不允许开发者启动线程访问用户界面的UI组件。因此,程序中额外定义了一个Handler来处理TextView背景色的更新。

        如果直接使用匿名内部类来定义Handler 类的实例,该Handler 直接使用主线程的Looper或MessageQueue,就有可能导致内存泄漏。因此,上面的程序为Handler派生了一个子类,并让该子类实例持有它所在Activity的弱引用(WeakReference),这样可以更好的避免内存泄漏。

注意:

        上面的程序中直接使用了R.color.color1、R.color.color2、R.color.color3等整型常量来代表颜色,这也得益于Android的资源访问支持,本书后面会有关于颜色资源的详细介绍。

  4.相对布局

        相对布局由RelativeLayout所代表,相对布局容器内子组件的位置总是相对兄弟组件、父容器来决定的,因此这种布局方式被称为相对布局。

        如果A组件的位置是由B组件的位置来决定的,Android要求先定义B组件,再定义A组件。

        RelativeLayout可支持如下表所示的两个XML属性。

XML属性 相关方法 说明
android:gravity setGravity(int) 设置该布局容器内各子组件的对齐方式
android:ignoreGravity setIgnoreGravity(int) 设置哪个组件不受gravity属性的影响

        为了控制该布局容器中各子组件的布局分布,RelativeLayout提供了一个内部类 :RelativeLayout.LayoutParams,该类提供了大量的XML属性来控制RelativeLayout布局容器中子组件的布局分布。 

        RelativeLayout.LayoutParams里只能设为true、false的XML属性如下表所示。

XML属性 说明
android:layout_centerHorizontal 控制该子组件是否位于布局容器的水平居中
android:layout_centerVertical 控制该子组件是否位于布局容器的垂直居中
android:layout_centerInParent 控制该子组件是否位于布局容器的中央位置
android:layout_alignParentBottom 控制该子组件是否与布局容器底端对齐
android:layout_alignParentLeft 控制该子组件是否与布局容器左边对齐
android:layout_alignParentRight 控制该子组件是否与布局容器右边对齐
android:layout_alignParentTop 控制该子组件是否与布局容器顶端对齐

        RelativeLayout.LayoutParams里属性值为其他UI组件ID的XML属性如下表所示。

XML属性 说明
android:layout_toRightOf 控制该子组件位于给出ID组件的右侧
android:layout_toLeftOf 控制该子组件位于给出ID组件的左侧
android:layout_above 控制该子组件位于给出ID组件的上方
android:layout_below 控制该子组件位于给出ID组件的下方
android:layout_alignTop 控制该子组件与给出ID组件的上边界对齐
android:layout_alignBottom 控制该子组件与给出ID组件的下边界对齐
android:layout_alignLeft 控制该子组件与给出ID组件的左边界对齐
android:layout_alignRight 控制该子组件与给出ID组件的右边界对齐

        除此之外,RelativeLayout.LayoutParams还继承了android.view.ViewGroup.MarginLayout Params,因此RelativeLayout布局容器中每个子组件也可指定android.view.ViewGroup.MarginLayoutParams所支持的各XML属性。

  5.网格布局

        网格布局由GridLayout所代表,它是Android 4.0新增的布局管理器,因此需要在
Android 4.0之后的版本中才能使用该布局管理器。如果希望在更早的Android平台上使用该布局管理器,则需要导入相应的支撑库。

        GridLayout的作用类似于HTML中的table标签,它把整个容器划分成rows×columns个网格,每个网格可以放置一个组件。除此之外,也可以设置一个组件横跨多少列、一个组件纵跨多少行。

        GridLayout提供了setRowCount (int)和setColumnCount (int)方法来控制该网格的行数量和列数量。

        下表显示了GridLayout常用的XML属性及相关方法。

XML属性 相关方法 说明
android:alignmentMode setAlignmentMode(int) 设置该布局管理器采用的对齐模式
android:columnCount setColumnCount(int) 设置该网格的列数量
android:columnOrderPreserved setColumnOrderPreserved(boolean) 设置该网格容器是否保留列序号
android:rowCount setRowCount(int) 设置该网格的行数量
android:rowOrderPreserved setRowOrderPreserved(boolean) 设置该网格容器是否保留行序号
android:useDefaultMargins setUseDefaultMargins(boolean) 设置该布局管理器是否使用默认的页边距

        为了控制GridLayout布局容器中各子组件的布局分布,GridLayout提供了一个内部类 :GridLayout.LayoutParams,该类提供了大量的XML属性来控制GridLayout布局容器中子组件的布局分布。

        下表显示了GridLayout.LayoutParams常用的XML属性及相关方法。

XML属性 相关方法 说明
android:layout_column 设置该子组件在 GridLayout的第几列
android:layout_columnSpan 设置该子组件在 GridLayout横向上跨几列
android:layout_gravity setGravity(int) 设置该子组件采用何种方式占据该网格的空间
android:layout _row 设置该子组件在GridLayout的第几行
android:layout_rowSpan 设置该子组件在 GridLayout纵向上跨几行

  6.绝对布局(过时了,被淘汰了)

        绝对布局由AbsoluteLayout所代表。绝对布局就像Java AWT编程中的空布局,就是Android不提供任何布局控制,而是由开发人员自己通过X坐标、Y坐标来控制组件的位置。当使用AbsoluteLayout作为布局容器时,布局容器不再管理子组件的位置、大小——这些都需要开发人员自己控制。

        使用绝对布局时,每个子组件都可指定如下两个XML属性。

  • layout_x:指定该子组件的X坐标。
  • layout_y:指定该子组件的Y坐标。

        绝对布局中的每个子组件都要指定layout_x、layout_y两个定位属性,这样才能控制每个子组件在容器中出现的位子。因此,当使用绝对布局来控制子组件布局时,编程要烦琐得多,而且在不同屏幕上的显示效果差异也很大。

        在绝对布局中指定各组件的android: layout_x、android: layout_y属性时指定了形如20dp这样的属性值,这是一个距离值。Android中一般支持如下常用的距离单位。

  • px(像素)∶每个px对应屏幕上的一个点。
  • dip或dp (device independent pixels,设备独立像素):一种基于屏幕密度的抽象单位。在每英寸160点的显示器上,1dip=1 px。但随着屏幕密度的改变,dip与px的换算会发生改变。
  • sp (scaled pixels,比例像素)︰主要处理字体的大小,可以根据用户的字体大小首选项进行缩放。
  • in(英寸):标准长度单位。
  • mm(毫米)︰标准长度单位。
  • pt(磅):标准长度单位,1/72英寸。

  7.约束布局

        约束布局是 Android 8新增的布局方式,如果读者有 iOS开发经验,则应该知道 iOS布局所支持的约束,而约束布局完全就是借鉴iOS所提供的约束的。

        从功能上讲,约束布局相当于相对布局的改进:相对布局只能控制组件在父容器中居中,或者与父容器(或另一个组件)左对齐、右对齐、顶端对齐、底端对齐,或者控制组件在另一个组件的
左边、右边、上方或下方。但如果要控制组件位于父容器的某个百分比处,或者控制组件位于另个组件的左边25dp处,相对布局就无能为力了,此时就可借助于约束布局。

        使用Android Studio新建项目时添加的Actvitiy 的默认布局文件自动使用约束布局。

        可以看出,每个组件都可以在上、下、左、右4个方向添加约束,图中“Hello World!"文本框四周的4个带圆圈的点就是添加约束的点,开发者按住它们就可以向父容器或其他组件添加约束了。

        约束既可基于父容器,也可基于其他组件。比如在上面界面中添加一个新按钮,该按钮的定位需求如下:

  • 新按钮位于文本框下边(与文本框相差20dp)。
  • 新按钮左边与文本框对齐。
  • 新按钮右边比文本框右边少10dp。

首先向界面中拖入一个按钮,然后执行如下操作。

  1. 从按钮左边的带圆圈的点拖向文本框的左边——建立按钮左边与文本框左边对齐的约束。
  2. 从按钮右边的带圆圈的点拖向文本框的右边——建立按钮右边与文本框右边对齐的约束。
  3. 从按钮上边的带圆圈的点拖向文本框的下边——建立按钮上边与文本框下边对齐的约束。

        上面只是控制按钮与文本框对齐,如果要精确控制按钮与文本框的相对距离,则可通过Android Studio提供的 Inspector 设置界面进行控制(反正我是没找到,只看到Layout下面类似的。选中按钮后,可以在 Android Studio右上角看到如图2.14所示的Inspector 设置界面。

疯狂Android讲义(二)——第二部分:第1组UI组件(布局管理器)_第4张图片

        Inspector 设置界面中间的矩形代表选中的组件,该组件内部的“>>>”图标用于设置该组件的layout_width、layout_height。约束布局中的组件的layout_width、layout_height支持如下3个值。

  • wrap_content:该组件的宽度或高度刚够显示组件内容(空间弹簧拉不动)
  • 固定长度值:设置该组件的宽度或高度为固定值。
  • match_constraint(0dp):该组件的宽度或高度完全按约束计算(空间弹簧拉得动)

        疯狂Android讲义(二)——第二部分:第1组UI组件(布局管理器)_第5张图片

         实现上述的要求,这个使用Design模式非常快。

        如果希望文本框的垂直位置不是位于父容器的中间(由于按钮是根据文本框定位的,因此,如果文本框上移,按钮也会随之上移),而是位于父容器的上方5%处,约束布局也可处理。选中文本框,拖动Android Srudio右上角的Inspector设置界面中的“垂直”滑动条,将其值设置为5%。对应的代码为:

app:layout_constraintVertical_bias="0.05"
//该属性用于控制该组件在父容器中垂直方向的百分比
//只有属性设置为warp_content,才可以生效。

        至此,就讲完了在约束布局中定位约束的方法。实际上,它们对应于布局文件中的如下几个属性。

  • app:layout constraintXxx_toXxxOf:设置该组件与父容器(或其他组件)对齐。其中Xxx可以是 Start、End、Top、Bottom 等值,具体可根据实际情况设置。该属性的属性值可以是parent(代表父容器)或其他组件的ID。
  • android:layout_marginXxx:设置该组件与参照组件的间距。其中Xxx可以是Start、End、Top、Bottom 等值,具体可根据实际情况设置。
  • app:layout_constraintXxx_bias:设置该组件在参照组件中的百分比。其中 Xxx 可以是Horizontal或Vertical。

        在实际开发中,虽然会通过界面进行拖曳操作,但最后精确控制时肯定还是直接修改源代码的。因此,读者必须牢记上面3个属性对应于 Inspector 设置界面中的哪个控制部分。

        Android Studio设计界面也允许删除约束,删除约束很简单。

  • 删除单个约束:将鼠标指针悬浮在某个约束的圆圈上,然后该圆圈会恋成红色,单击该圈圈就能删除它对应的约束。
  • 删除某个组件的所有约束:选中该组件,它的左下伯会出现一个删除约束的图标()单击该图标就能删除当前组件的所有约束。
  • 如果要删除整个界面上的所有约束,则可通过单机布局设计器左上角的删除图标()来删除所有约束。

此外,约束布局还提供了两种自动创建约束的方式。

  • 自动连接(Autoconnect) ;自动连接默认是关闭的,可以通过单击约束布局设计器上方的Autoconnect图标()来打开自动连接。
  • 推断(Infer) :可以通过单击约束布局设计器上方的Infer图标()执行推断。

         如果打开了约束布局的自动连接,那么每次向约束布局中拖入新组件时,约束布局设计器都会自动为该组件添加上、下、左、右4个约束——并不保证所添加的约束符合开发者的期望,因此通过自动连接添加的约束通常都需要手动修改。

        “推断”功能则是另一种用法:用户先向界面中添加所有的UI组件,再通过拖曳摆放这些组件的位置,然后单击约束布局设计器上方的Infer 图标()执行推断,约束布局设计器会自动为所有组件添加上、下、左、右4个约束——依然不保证所添加的约束符 合开发者的期望。

        总体来说,约束布局是对iOS布局的一次完美模仿,这种布局方式功能比较强大,但在实际使用时也比较烦琐。本节介绍了约束布局的全部内容,但如何好好利用这种布局方式则需要读者多加练习。

  补充新概念:引导线和分界线

        有时候,一批组件都需要放在容器的某个位置,或着某个组件需要参照的组件并不存在,为此约束布局为之提供了引导线(GuideLine)
        引导线是约束布局中不可见的、却实实在在存在的组件,引导线唯一的作用就是被其他组件作为定位的参照
        引导线分为两种:水平引导线和垂直引导线。引导线对应于 Guideline系,该元素可支持如下属性。

  • android:orientation:该属性指定引导线的方向。该属性支持vertical和horizontal两个值,代表垂直引导线和水平引导线。
  • app:layout_constraintGuide_begin:该属性指定引守线到父容器起始处的距离。该属性值以是一个距离值。
  • app:layout_constraintGuide_end:该属性指定引守线到父容器结束处的距离。该属性值可是一个距离值。
  • app:layout_constraintGuide_percent:该属性指定引守线到父容器的指定百分比处

        上面的最后三个属性它们都用于控制引导线的位置,因此只要指定其中之一即可。

 添加引导线的方式:

  • 第一种:直接添加疯狂Android讲义(二)——第二部分:第1组UI组件(布局管理器)_第6张图片
  • 第二种:代码:疯狂Android讲义(二)——第二部分:第1组UI组件(布局管理器)_第7张图片

        除使用引导线之外,约束布局还提供了分界线(Barrier) 来定位其他组件,分界线同样是不可见的、但实实在在存在的组件。通过引导线图标右边的下拉菜单中的分界线图标可添加分界线。

        分界线同样可分为水平分界线和垂直分界线两种分界线与引导线的区别在于:引导线以父容器为基础进行定位比如设置与父容器的起始处、结束处的距离来控制引导线的定位,也可通过设置位于父容器的百分比来控制引导线的定位;而分界线根据容器中的组件进行定位,分界线可位于多个组件的上方、下方、左边或右边
 

  散称知识点:

 在上面的界面布局文件中我们直接把按钮上的文本写在布局文件中,这不是一种好的做法,因为Android推荐将这些字符串集中放到XML文件中管理。但此处为了编程简单,所以直接在XML布局文件中给出了按钮文本的字符串。

② 大部分时候,使用绝对布局都不是一个好思路因为运行Android应用的手机往往千差万别,因此屏幕大小、分辨率都可能存在较大差异,使用绝对布局会很难兼顾不同屏幕大小、分辨率的问题。因此,AbsoluteLayout布局管理器已经过时

你可能感兴趣的:(疯狂Android讲义,android,ui,java,疯狂Android讲义)