JetPack知识点实战系列一:初识ConstraintLayo
我将通过一系列教程讲解利用JetPack的各个知识点,熟悉和巩固各个知识点的同时,我们将实现网易云音乐的功能。
请注意:系列教程的假设前提是希望您有入门级别的Android开发技能,譬如Kotlin语法,基本UI的控件的使用,常见的Android API的使用等。
第一节实战我们首来了解ConstraintLayout的知识,并且用ConstraintLayout的知识点来实现登录页面的布局。最终的界面效果如下。
登录页面本节教程您主要将学习到如下内容:
- 如何给View添加Constraint,从而实现界面布局需求?
- 如何实现沉浸式布局来实现全屏显示?
- 如何通过富文本来优化UI性能?
- 如何通过属性动画实现抖动效果?
创建项目
首先打开Android Studio 4.0, 点击 Start a new Android Studio project 来创建一个 Android studio 项目。
创建项目步骤1接下来选择 Empty Activity 模板
创建项目步骤2然后填写项目的相关信息,Name为项目名称,Language选择Kotlin, Minimum SDK可以选择Android 5.0
创建项目步骤3最后点击Finish, 项目就创建好了。等Gradle构建完成就可以进行Android开发了。开发之前我们可以看一下我们的constraintlayout的依赖版本,目前是2.0.1.
创建项目步骤4项目基本配置
进入res
> values
> styles
, 修改AppTheme主题样式为Theme.AppCompat.Light.NoActionBar
进入res
> values
> colors
, 修改主题颜色
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#FFFFFF</color>
<color name="colorPrimaryDark">#FFFFFF</color>
<color name="colorAccent">#DB2D25</color>
</resources>
ConstraintLayout初识
打开activity_main.xml的布局文件,我们能看到如下的界面内容。界面的每部分的功能在图中有标识。
图形设计界面总览.jpg请注意:打开的时候界面可能不会完全和图示内容一致,因为界面显示的内容是可以调整的,如何调整可以参阅相关资料。
通过上图,我们可以看到默认根布局是ConstraintLayout
,一个TextView
居中显示。您脑海中可能会闪现如下一些问题:
-
TextView
能居中显示是如何设置的? - 如何改变它的位置?
这一系列问题我们就来进行一一介绍。
去掉默认的Constraint
去掉Constraints可以用下面两种方法来实现:
- 通过点击Constaint设置区域的Clear all Constraints按钮
- 选中TextView,右击选择Clear Constraints of Selection选项
去掉TextView
的Constrains后会报错,因为安卓系统无法准确定位TextView
的位置。
提示:这个错误不会造成程序无法运行。因为UI控件没有设置任何Constraints,系统会把没有设置Constraints的UI控件放置在左上角。
设置TextView
居中显示
设置居中显示有如下两种方法:
- 点击Constaint设置区域的Infer Constraints按钮
- 将
TextView
上下左右四个约束锚点(Constraint anchor)连接到Constraint Layout上。
点击TextView
时会上下左右会出现四个圆圈,每个圆圈就是约束锚点(Constraint anchor)。
点击连接锚点然后拖拽至Constraint Layout的每条边界上,就能居中布局了。
拖拽调整TextView
的位置
TextView
居中显示是因为左右上下两对约束的值是一样的,没有 垂直约束偏移(layout_constraintVertical_bias) 和 水平约束偏移(layout_constraintHorizontal_bias)。
所以如果需要修改TextView
的位置,只需要在Attribute -> Layout -> Constraint Widget 里面拖动或者拖动后在 Attribute -> Declared Attribute 手动改变这两个值就可以了。
具体操作如下:
修改位置ConstraintLayout实现登录页面
有了初步的ConstraintLayout知识后,我们来实现下网易云音乐的登录界面,来了解更多的ConstraintLayout布局技巧。
UI元素准备
先将UI元素大致放置在布局文件中,包括logo,手机号登录按钮,四个第三方登录按钮,一个同意协议的TextView。
13.jpg提示:UI拖入放置的位置可以比较随意,但是最好放置大致位置和最后的布局文件相似,方便进行约束设置。
Logo布局调整
我们将Logo的位置设置为水平居中,垂直中心位于20%的高度处。
水平居中比较好设置,前面有介绍,就是将左右约束锚点(Constraint anchor)连接到Constraint Layout边界上。
水平居中实现垂直中心位于20%的高度处,需要借助辅助标线中的水平辅助线(Horizontal Guideline).
点击Constaint设置区域 -> Guidelines -> Add Horizontal Guideline 设置水平辅助线。
添加水平辅助线水平辅助线默认是距离顶部固定的距(我选择的模拟器视图是20dp),可以点击辅助标线侧边的蓝色小圆圈,点击一下,辅助标线会变成固定距离底部的距离,再点击一下就变成了百分比。把百分比的值改为0.2,就实现了水平辅助线位于20%的高度的位置。
水平辅助线设置接下来,将logo的上下约束锚点链接到这条水平辅助线就完成了。
基线对齐同意协议的TextView的布局调整
TextView的左边距里为屏幕宽度的10%,右边距为屏幕宽度的90%,底部距离为32dp
添加两条垂直的辅助线(Vertical Guideline),第一条设置为10%,第二条设置为90%。
垂直辅助线将TextView
的左约束锚点连接到10%的垂直辅助线,将TextView
的右约束锚点连接到右边90%的垂直辅助线。将TextView
的下约束锚点连接到ConstraintLayout。然后设置MarginBottom
为32dp。
注意:如果仅仅这么设置会有一点小问题,如果屏幕的宽度较小,文字可能会超出左右辅助线。
譬如我们把文字改成24sp, 就会出现我们上面所说的问题。
超出边界- 为什么呢?
我们看一下Attributes -> Constraint Widget, 我们把鼠标悬停在正方形内的左右相对的">>"和"<<"符号上面,会提示约束为"Wrap Content"。这样表示目前TextView
约束是左右居中,且宽度为包含内容,所以会出现超出左右辅助线的现象。
- 解决办法:将
TextView
的左右约束改为"Match Constraints"。
点击一次左右相对的">>"和"<<"其中一个图标按钮,此时左右约束变为左右距离为固定值,再点击一次图标按钮约束则变成了"Match Constraints"。就实现了不会超出左右垂直辅助线,高度包裹内容的目的。
gif1.gif四个三方登录按钮布局调整
微信登录按钮左侧距离为10%,网易登录按钮右侧距离为10%,其余两个按钮和这两个按钮水平均匀分布,且垂直居中对齐。距离底部的TextView间距为32dp。
这个水平均匀分布需求用LinearLayout的weight能方便的实现,但是会增加布局的层级,不利于UI的优化。其实这个需求用ConstraintLayout也可以很方便的实现。
实现方法如下:
- 四个按钮水平均匀分布于两个辅助线之间
- 鼠标移动框住四个按钮,选中一个按钮右键在弹出列表中选择Chains,然后选择Create Horizontal Chain。这样会生成一个链接四个按钮的水平约束链条。
- 点击上步生成的约束链(Chain), 右键在弹出列表中选择Chains,然后选择Horizontal Chain Style,选择spread inside。这样修改后的样式就是中间的按钮会平均分配距离,而最边上的两个按钮会贴边。
- 微信易登录按钮的左侧约束锚点和右侧辅助线链接
- 网易登录按钮的右侧约束锚点和右侧辅助线链接
- 四个按钮垂直居中
- 鼠标移动框住四个按钮,选中一个按钮右键在弹出列表中选择Align,然后选择Vertical Centers。这样四个按钮就在垂直方向上是居中显示的了。
- 四个按钮和底部的文本距离32dp
- 连接任意一个按钮的底部约束锚点和文本的顶部锚点,并设置间距为32dp
手机登录按钮布局调整
经过前面的练习,手机登录按钮的调整就驾轻就熟了。这里不做过多解释了。
目前为止,界面布局没有写一行代码,这种感觉要不要太爽。
沉浸式的布局
我们运行下程序,布局和我们预期的一致。但是细心的你可能发现了一个问题,顶部状态栏是白色的,且状态栏上显示的信息是白色造成无法被看到,这个显示肯定无法接受!
效果我们期望是全屏显示最好了。这个效果就是沉浸式的效果,Android 从4.4开始支持沉浸式的效果,Android5.0后设置沉浸式需要代码设置下。
- 在MainActivity这个类里面添加一个immersive方法
/* 代码实现沉浸式 */
private fun immersive() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// 5.0 +
window.apply {
// 1.
clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
// 2.
addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
// 3.
addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
// 4.
statusBarColor = Color.TRANSPARENT
}
findViewById<ViewGroup>(android.R.id.content).apply {
// 5.
for (index in 0 until childCount) {
val child = getChildAt(index) as? ViewGroup
child?.let {
// 6.
it.fitsSystemWindows = true
it.clipToPadding = true
}
}
}
} else {
// 7
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
}
}
代码的具体作用为:
- 清除透明状态栏标识
- 添加绘制状态栏标识
- 添加透明导航标识
- 设置状态栏的颜色为透明
- 遍历根布局的子布局
- 让布局根据系统窗口来调整自己的布局
- Android4.4这个设置就能实现沉浸式效果
- 在MainActivity类的onCreate里面调用immersive方法
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 1
immersive()
}
沉浸式效果
富文本
我们来看一下同意协议的部分,其实这个段文本是分为四部分的,每个部分都能点击,且点击后的效果是不一样的,第一部分点击切换是否同意协议,后面三部分点击效果是跳转到展示对应的协议内容的页面。
21.jpeg初起来看着稍微有点复杂,其实用Android 提供的富文本就能很好的实现这个功能,并且具有较好的UI性能。
- 在MainActivity这个类里面添加一个createSpannableString方法 和 是否同意协议checked这个属性
private var checked = false
private fun createSpannableString() {
// 1
SpannableString(" 同意《用户协议》《隐私政策》《儿童隐私政策》").also {
// 2
it.setSpan(ForegroundColorSpan(Color.parseColor("#CDCDCD")), 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)
// 3
it.setSpan(object : ClickableSpan() {
override fun onClick(widget: View) {
// 4
Toast.makeText(this@MainActivity, "点击用户协议",Toast.LENGTH_LONG).show()
}
override fun updateDrawState(ds: TextPaint) {
// 5
ds.color = Color.WHITE
ds.isUnderlineText = false
}
}, 4, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
it.setSpan(object : ClickableSpan(){
override fun onClick(widget: View) {
Toast.makeText(this@MainActivity, "点击隐私政策", Toast.LENGTH_LONG).show()
}
override fun updateDrawState(ds: TextPaint) {
ds.color = Color.WHITE
ds.isUnderlineText = false
}
}, 10, 16, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
it.setSpan(object : ClickableSpan() {
override fun onClick(widget: View) {
Toast.makeText(this@MainActivity, "点击儿童用户协议", Toast.LENGTH_LONG).show()
}
override fun updateDrawState(ds: TextPaint) {
ds.color = Color.WHITE
ds.isUnderlineText = false
}
}, 16, 24, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
// 6
textView.apply {
text = it
// 7
movementMethod = LinkMovementMethod.getInstance()
// 8
highlightColor = Color.TRANSPARENT
// 9
setOnClickListener {
if (selectionStart == -1 && selectionEnd == -1) {
// 10
checked = !checked
if (checked) {
setCompoundDrawablesWithIntrinsicBounds(
resources.getDrawable(R.mipmap.login_checked),
null,
null,
null
)
} else {
setCompoundDrawablesWithIntrinsicBounds(
resources.getDrawable(R.mipmap.login_unchecked),
null,
null,
null
)
}
}
}
}
}
}
这段代码的含义是:
- 构造一个文本为" 同意《用户协议》《隐私政策》《儿童隐私政策》"的SpannableString
- 把" 同意"的文字颜色设置为"#CDCDCD"
- 把"《用户协议》"设置成可点击的ClickableSpan
- 点击"《用户协议》"这个ClickableSpan,会弹出一个toast,toast文本为"点击用户协议"
- 覆写"《用户协议》"这个ClickableSpan的默认样式,文字颜色为白色,去掉下划线
- 找到TextView,设置这个TextView的文本为刚才构造的SpannableString
- 设置成可点击,否则前面虽然设置了ClickableSpan,仍然无法响应点击效果
- 设置高亮为透明,意思就是不需要高亮显示
- TextView设置点击事件,这样" 同意"就能响应点击事件了
- 进入这个方法说明点击的是" 同意"这块区域,就记录下checked,并且修改drawableStart的图片
- 在MainActivity类的onCreate里面调用createSpannableString方法
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 2
createSpannableString()
}
点击效果
属性动画
登录页面有一个需求就是,如果没有勾选“同意协议”,则点击“手机号码登录”按钮时候,“同意协议”这段文字需要抖动一下,提示用户去勾选“用户协议”。
这个抖动效果用属性动画就能很好的实现。
- 在MainActivity类的onCreate里面加上如下代码
login_btn.setOnClickListener {
if (!checked) {
ObjectAnimator.ofFloat(textView, "translationX", 0f, 25f, -25f, 25f, -25f, 15f, -15f, 6f, -6f, 0f).also {
it.duration = 1000
}.start()
}
}
抖动效果