JetPack知识点实战系列一:初识ConstraintLayout之实现登录页面

我将通过一系列教程讲解利用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可以用下面两种方法来实现:

  1. 通过点击Constaint设置区域的Clear all Constraints按钮

Clear all Constraints

  1. 选中TextView,右击选择Clear Constraints of Selection选项

Clear Constraint of Selection

去掉TextViewConstrains后会报错,因为安卓系统无法准确定位TextView的位置。

Constraint Error

提示:这个错误不会造成程序无法运行。因为UI控件没有设置任何Constraints,系统会把没有设置Constraints的UI控件放置在左上角。

设置TextView居中显示

设置居中显示有如下两种方法:

  1. 点击Constaint设置区域的Infer Constraints按钮

Infer Constraints

  1. TextView上下左右四个约束锚点(Constraint anchor)连接到Constraint Layout上。

点击TextView时会上下左右会出现四个圆圈,每个圆圈就是约束锚点(Constraint anchor)

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。

提示:UI拖入放置的位置可以比较随意,但是最好放置大致位置和最后的布局文件相似,方便进行约束设置。

13.jpg

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也可以很方便的实现。

实现方法如下:

  1. 四个按钮水平均匀分布于两个辅助线之间
  • 鼠标移动框住四个按钮,选中一个按钮右键在弹出列表中选择Chains,然后选择Create Horizontal Chain。这样会生成一个链接四个按钮的水平约束链条。
  • 点击上步生成的约束链(Chain), 右键在弹出列表中选择Chains,然后选择Horizontal Chain Style,选择spread inside。这样修改后的样式就是中间的按钮会平均分配距离,而最边上的两个按钮会贴边。
  • 微信易登录按钮的左侧约束锚点和右侧辅助线链接
  • 网易登录按钮的右侧约束锚点和右侧辅助线链接

水平均匀分布

  1. 四个按钮垂直居中
  • 鼠标移动框住四个按钮,选中一个按钮右键在弹出列表中选择Align,然后选择Vertical Centers。这样四个按钮就在垂直方向上是居中显示的了。

垂直居中

  1. 四个按钮和底部的文本距离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)
    }
}

代码的具体作用为:

  1. 清除透明状态栏标识
  2. 添加绘制状态栏标识
  3. 添加透明导航标识
  4. 设置状态栏的颜色为透明
  5. 遍历根布局的子布局
  6. 让布局根据系统窗口来调整自己的布局
  7. 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
        findViewById<TextView>(R.id.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
                        )
                    }
                }
            }
        }

    }

}

这段代码的含义是:

  1. 构造一个文本为" 同意《用户协议》《隐私政策》《儿童隐私政策》"的SpannableString
  2. 把" 同意"的文字颜色设置为"#CDCDCD"
  3. 把"《用户协议》"设置成可点击的ClickableSpan
  4. 点击"《用户协议》“这个ClickableSpan,会弹出一个toast,toast文本为"点击用户协议”
  5. 覆写"《用户协议》"这个ClickableSpan的默认样式,文字颜色为白色,去掉下划线
  6. 找到TextView,设置这个TextView的文本为刚才构造的SpannableString
  7. 设置成可点击,否则前面虽然设置了ClickableSpan,仍然无法响应点击效果
  8. 设置高亮为透明,意思就是不需要高亮显示
  9. TextView设置点击事件,这样" 同意"就能响应点击事件了
  10. 进入这个方法说明点击的是" 同意"这块区域,就记录下checked,并且修改drawableStart的图片
  • MainActivity类的onCreate里面调用createSpannableString方法
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    
    // 2
    createSpannableString()
}

点击效果

属性动画

登录页面有一个需求就是,如果没有勾选“同意协议”,则点击“手机号码登录”按钮时候,“同意协议”这段文字需要抖动一下,提示用户去勾选“用户协议”。

这个抖动效果用属性动画就能很好的实现。

  • MainActivity类的onCreate里面加上如下代码
findViewById<Button>(R.id.login_btn).setOnClickListener {
    if (!checked) {
        ObjectAnimator.ofFloat(findViewById(R.id.textView), "translationX", 0f, 25f, -25f, 25f, -25f, 15f, -15f, 6f, -6f, 0f).also {
            it.duration = 1000
        }.start()
    }
}

抖动效果

更多精彩内容,敬请期待

猜你喜欢

转载自blog.csdn.net/lcl130/article/details/108332751