前言
日常开发过程中,我们都会遇到这样一种场景:我们写出的UI效果在对接数据之前需要提前进行预览,而调整UI细节和布局问题。
如果存在像TextView或ImageView这种基础控件,你是不是还在通过诸如
android:text="xxx"
和的方式来测试和预览UI效果?当然你肯定也会遇到这些“脏数据”给你带来的存储:测试的时候某些地方出现了本不该出现的数据,事后可能一拍脑门才发现,原来是布局中面板预览数据没有清除导致的。android:src="@drawable/xxx"
如果是RecyclerView,在后台接口尚能测试的情况下,你是否又要自己生成“假数据”并手写Adapter呢?这时候你不禁会问:有没有一种方法,既能够做到布局时预览数据方便汇总,又能够在对接真实数据运行后动态替换和移除这些无关数据呢?
安卓的的Tools attributes
应运而生。
老规矩,我们先来看一个效果:
什么?你在耍我吗?这么简单的列表拿出来干嘛?哈哈,客观不要着急。这个不确定难实现,倘若我说这里并没有写一行Java或者Kotlin代码就实现了此效果,而只是在布局页面预览,你敢信吗?上图只是冰山一角,下面这张图才是全貌:
下面会带大家一步步实现上述功能,首先,让我们从头说起。
认识工具属性
工具属性即以工具开始的命名空间,举个我们最常见到的例子:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
</android.support.constraint.ConstraintLayout>
大家肯定平时都会见到tools:context =“。XXXActivity”这个系统替代为我们生成的配置。一般来说,只有根视图才能使用这个属性,它指定了当前布局替代与其中Activity相关联,布局能够获取到绑定活动的一些信息,一些主题等等,而且当你在布局中给子的视图中添加onClickEvent时,相应的方法代码会插入到这个活动中。Androidstudio支持很多在XML文件中以工具为命名空间的属性,当构建App时这些属性会被纠正,对APK的大小和运行时行为没有任何影响,这也就是我们文章最初想要的结果。
细说工具属性
在具体介绍工具属性之前,我们需要先了解如何约会工具的命名空间并使用,很简单,只需要在XML布局文件的根元素中添加即可:
<RootTag xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
这些工具属性大概可以分为以下三类:
1.错误处理属性
即错误和警告处理属性。
这类属性常被用作规避被lint检查出的一些错误提示以及警告。下面让我们看一些常见的例子:
1.1工具:忽略
主要用来忽略一些lint产生的警告信息,并支持一些属性,例如:
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name">ConstraintSample</string>
<string name="header_image_string" tools:ignore="MissingTranslation">header image</string>
</resources>
这个关于Android studio升级到3.0以上的小伙伴来说应该是很常见了,如果我们项目中涉及到国际化支持,那么编译器就会提示我们为每一种语言做适应,不能“厚此薄彼”,如果我们某些字符串只需要支持一种语言,只需要像上面那样添加工具:ignore =“ M is singTranslation就可以。
相似的例子还可以在使用ImageView的时候看到:
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_person_off"
tools:ignore="contentDescription" />
1.2工具:targetApi
这个属性的功能和Java代码中的注解@TargetApi是一样的:它指定了当前控件或元素支持的API等级,属性值可以是API代码名或者API常数值,它支持一切属性。 ,andro id:海拔属性是在API 21版本以上才支持的,所以我们可以通过以下代码规避lint的警告:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="4dp"
tools:targetApi="lollipop"/>
1.3工具:语言环境
这个属性主要用在<resource>标签内,指定为当前资源中替换为某种语言和区域,并且长规规避语言拼写的检测,从而您可以指定values / strings.xml文件的替代语言是而不是是英语:
<resources xmlns:tools="http://schemas.android.com/tools"
tools:locale="es">
2.资源缩减属性
即资源压缩属性。下面简单说明一下。
我们可以通过tools:shrinkMode和tools:keep属性来分别指定资源压缩的模式和需要保留的不被压缩的资源,还可以通过tools:discard属性来指定需要保留的资源,与keep功能类似:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:shrinkMode="strict"
tools:keep="@layout/activity_video*,@layout/dialog_update_v2"
tools:discard="@layout/unused_layout,@drawable/unused_selector" />
下面就到本文章文章的重头戏了,注意,前方高能来袭!
3.设计时视图属性
这就是我们先前效果图中的重要功臣了,即:布局设计时的控件属性。类别属性主要作用于查看控件,如上文所述的工具:context就是“成员”之一,下面我们来介绍其他重要成员。
在此之前,我们需要先揭开tools命名空间的另一层神秘面纱:tools:可以替换任何以andro id:为本身的属性,并因此设置样例数据(sample data)。所说的,工具属性只能在布局编辑期间有效,App真正运行后就毫无意义了,所以,我们就可以像下面这样来在运行前预览布局效果:
上图对应的布局文件为:
Card_item_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@android:color/white"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
tools:targetApi="m"
tools:ignore="UnusedAttribute">
<ImageView
android:id="@+id/card_item_avatar"
android:layout_width="38dp"
android:layout_height="38dp"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="16dp"
app:layout_constraintVertical_bias="0.0"
tools:ignore="ContentDescription"
tools:srcCompat="@drawable/user_other"/>
<TextView
android:id="@+id/card_item_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="@+id/card_item_title"
app:layout_constraintEnd_toEndOf="@+id/card_item_title"
app:layout_constraintHorizontal_bias="0.0"
android:textSize="12sp"
android:textColor="@color/username_text_color"
android:layout_marginEnd="16dp"
android:paddingEnd="16dp"
tools:text="水月沐风" />
<TextView
android:id="@+id/card_item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="@color/title_text_color"
app:layout_constraintStart_toEndOf="@+id/card_item_avatar"
android:layout_marginStart="12dp"
app:layout_constraintTop_toBottomOf="@+id/card_item_username"
android:layout_marginTop="8dp"
android:maxLines="1"
tools:text="今天上海的夜色真美!"/>
<TextView
android:id="@+id/card_item_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="24dp"
app:layout_constraintTop_toBottomOf="@+id/card_item_avatar"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="16dp"
app:layout_constraintVertical_bias="1.0"
android:maxLines="3"
android:ellipsize="end"
android:textColor="@color/content_text_color"
android:textStyle="normal"
app:layout_constraintEnd_toEndOf="@+id/card_item_bottom_border"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
app:layout_constraintHorizontal_bias="0.0"
tools:text="人生若只如初见,何事秋风悲画扇..."/>
<ImageView
android:id="@+id/card_item_poster"
android:layout_width="0dp"
android:layout_height="200dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/card_item_content"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="16dp"
android:scaleType="centerCrop"
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="16dp"
app:layout_constraintVertical_bias="0.0"
tools:ignore="ContentDescription"
android:visibility="visible"
tools:src="@drawable/shanghai_night"/>
<View
android:id="@+id/card_item_bottom_border"
android:layout_width="0dp"
android:layout_height="2dp"
app:layout_constraintTop_toBottomOf="@+id/card_item_poster"
android:background="#ffededfe"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/card_item_date"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="16dp"
android:textColor="@color/date_text_color"
android:textSize="12sp"
tools:text="2019-08-10"/>
</android.support.constraint.ConstraintLayout>
通过上面的代码我们可以发现:通过对TextView使用tools:text
属性代替即可可以实现文本具体效果的预览,而实际上设置并不正确地我们应用实际运行效果产生影响。android:text
同理,通过将tools:src
作用于ImageView也可以达到预览图片的效果。
此外,我们还可以对其他以雄ID:为前缀的属性进行预览而不影响实际运行的效果,例如:上面布局代码中的底部分割线<View>
,我们想将其在应用程序实际运行的时候隐藏掉,但我们还是需要知道它的预览效果和所占高度:
<View
android:id="@+id/card_item_bottom_border"
android:layout_width="0dp"
android:layout_height="2dp"
android:visibility="gone"
tools:visibility="visible"
tools:layout_height="8dp"
app:layout_constraintTop_toBottomOf="@+id/card_item_poster"
android:background="#ffededfe"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"/>
如上所示,通过tools:v is ibility 和tools:layout_height就可以仅在布局预览情况下改变查看的状态和高度。虽然上述情况比较少用,但是希望大家也能够知道,tools:可以替代所有andro id :修饰的属性。
下面再列举一些其他会常用到的属性。
-
工具:布局
这个属性只能用于fragmentWidget中,
如果我们的活动布局文件中声明了<fragment>控件,我们就可以通过tools:layout =” @ layout / fragment_main”来在当前活动布局中预览fragment中的布局效果。
-
工具:showIn
这个属性就比较好玩了,它可以指定其他布局文件像<include>组件相同在当前布局文件中使用和预览<include>控件的实际效果。
例如,我们card_item_layout.xml作为showIn的对象给show_in_layout.xml布局使用,然后我就可以看到show_in_layout.xml中如下效果:
-
工具:菜单
这个属性可以给当前布局预览器的工具栏添加多个菜单项,但仅限于布局文件的根矩阵元素。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:menu="menu1,menu2" />
-
工具:maxValue | 工具:minValue
这两个属性仅用于<NumberPicker>,可以在预览时指定其替代和替换:
<NumberPicker
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/numberPicker"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:minValue="0"
tools:maxValue="10" />
-
工具:我是山雀| 工具:我是专家| 工具:L 是 tfooter | 工具:l 是 tCount
下面就是一下列表相关组件的工具属性。
上面,四个属性仅用于<AdapterView>及其子类(如:L 是 tView和RecyclerView)。而,它们内部却有一些使用限制:tools:l 是 tCount仅用于RecyclerView; tools:l 是 theader和tools:l 是 tfooter唯一于L 是 tView;至于tools:l is titem属性都可以使用。之前的效果图就是替代此属性:
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:background="#ffEBEBEF">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:theme="?attr/actionBarTheme"
android:minHeight="?attr/actionBarSize"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:elevation="1dp"
app:title="@string/app_name"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHorizontal_bias="0.0"/>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:scrollbars="vertical"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintVertical_bias="0.0"
tools:listitem="@layout/card_item_layout"/>
</android.support.constraint.ConstraintLayout>
样本数据
即样本数据功能,可以通过@tools:sample来使用该属性,也属于设计时视图属性。但它并非只是一个属性那么简单,更应该算是一个“工具利器”,所以可以其单独拿出来详细介绍。这个工具是本年度Google大会上Android开发团队特别介绍的一个新推属性。它有什么用呢?用处大了!先前的布局预览使用的数据都是我们直接在布局控件中指定或者在字符串.xml文件中确认的,此就会产生一些脏数据,不利于我们后期的处理。而有了示例数据,我们就可以对布局预览器中的**“样本数据” **进行集中保存和管理了。
一,样本数据的使用
Android studio已为我们提供了以下样本数据,我可以直接拿来使用:
属性值 | 占位符数据的描述 |
---|---|
@tools:sample/full_names | 是随机产生的,从全名的组合@tools:sample/first_names 和@tools:sample/last_names 。 |
@tools:sample/first_names | 共同的名字。 |
@tools:sample/last_names | 常用姓氏。 |
@tools:sample/cities | 从整个城市的名称的世界。 |
@tools:sample/us_zipcodes | 随机生成的美国邮政编码。 |
@tools:sample/us_phones | 随机产生电话号码与该格式如下:(800) 555-xxxx 。 |
@tools:sample/lorem | 该占位符文本是起源于拉丁语。 |
@tools:sample/date/day_of_week | 随机的日期和时间的指定格式。 |
@tools:sample/date/ddmmyy | |
@tools:sample/date/mmddyy | |
@tools:sample/date/hhmm | |
@tools:sample/date/hhmmss | |
@tools:sample/avatars | 可用作轮廓头像的矢量可绘制对象。 |
@tools:sample/backgrounds/scenic | 可以用作背景的图像。 |
上述表格中既有常用文本数据和日期等数据,又提供了一些图片样本数据,那么该如何使用呢?很简单,只需要切换到布局预览界面,并可以将一个ImageView到面板上,然后Android studio就会弹出如下界面:
然后选择avatars或者background / scenic数据源就可以了。当然你也可以通过xml代码形式来设置:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@android:color/white"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="36dp"
android:layout_height="36dp"
android:id="@+id/imageView"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp"
tools:srcCompat="@tools:sample/avatars"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/textView" app:layout_constraintStart_toEndOf="@+id/imageView"
android:layout_marginStart="8dp" app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="16dp"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem/random"
tools:maxLines="8"
android:ellipsize="end"
android:textSize="14sp"
android:textColor="@color/title_color" android:layout_marginTop="16dp"
app:layout_constraintHorizontal_bias="0.0"/>
</android.support.constraint.ConstraintLayout>
同样地,TextView也可以通过@tools:sample / lorem / random来添加样本数据,如此一来,效果如下:
哈哈,还不错吧。那么问题来了,如果我们想要用自己的样本数据源呢?
二,自定义样本数据
如果我们想要是用自己定制化的样例数据,该如何做呢?其实很简单,只需要在app目录下创建示例数据目录就可以了:
接下来,我们就可以在里面定制我们自己的数据了,在刚建好的sampledata目录下新建一个txt格式的数据文件,如用户,然后在里面创建如下数据:
如此这般,同理创建我们的其他数据:标题,描述,然后在上述card_item_layout.xml布局文件中替换并使用自己的数据源:
<TextView
android:id="@+id/card_item_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="@+id/card_item_title"
app:layout_constraintEnd_toEndOf="@+id/card_item_title"
app:layout_constraintHorizontal_bias="0.0"
android:textSize="12sp"
android:textColor="#8989ae"
android:layout_marginEnd="16dp"
android:paddingEnd="16dp"
tools:text="@sample/users" />
这里仅以其中一个TextView模拟说明,其他同理。
什么?你以为到这里就讲完了?哈哈,少年,看你骨骼惊讶,再教你一招来上天入地:通过自定义Json格式的数据来为控件绑定数据:
打完收工,还是上面的例子,来看看如何通过json数据来绑定:
<TextView
android:id="@+id/card_item_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="@+id/card_item_title"
app:layout_constraintEnd_toEndOf="@+id/card_item_title"
app:layout_constraintHorizontal_bias="0.0"
android:textSize="12sp"
android:textColor="#8989ae"
android:layout_marginEnd="16dp"
android:paddingEnd="16dp"
tools:text="@sample/sample.json/data/username" />
Android Studio都会自动提示sampledatapath下一个数据文件,Json格式亦会提示到具体部分。
最后
从Google Android官方的进一步动向来看,后续Android更新过程中,布局编辑器将更加强大。
从工具属性到ConstraintLayout1.1再到即将到来的ConstraintLayout2.0中的MotionLayout,可以预见:Android将在UI渲染和动画实现方面进一步解放我们的双手。