android:layout_width=“100dp”
android:layout_height=“100dp”
android:layout_weight=“3”
android:scaleType=“fitXY”
android:src=“@drawable/user_portrait_gender_female” />
为了验证“嵌套布局是否会延长解析时间?”,特意用RelativeLayout
+LinearLayout
写了上面最深 5 层嵌套的布局。
把它设置为 Activity 的 ContentView,经多次测量构建平均耗时为 24.2 ms 。(布局略简单,复杂度远低于真实项目中的界面,遂真实项目中的优化空间更大)
动态构建布局
如果把 xml 中的布局称为静态布局的话,那用 Kotlin 代码构建布局就可以称为动态布局。
正如上一篇分析的那样,静态布局避免不了两个耗时的步骤:
- 通过 IO 操作将布局文件读至内存。
- 遍历布局文件中每一个标签,通过反射构建控件实例并填入 View 树。
那弃用静态布局,直接使用 Kotlin 代码构建布局,能节约多少时间?
于是我用纯 Kotlin 代码重写了一遍布局,写完。。。差点吐了,代码如下:
private fun buildLayout(): View {
return LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
RelativeLayout(this@Factory2Activity2).apply {
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 80f.dp())
setPadding(20f.dp(), 10f.dp(), 20.0f.dp(), 10f.dp())
ImageView(this@Factory2Activity2).apply {
layoutParams = RelativeLayout.LayoutParams(40f.dp(), 40f.dp()).apply {
addRule(RelativeLayout.ALIGN_PARENT_START, RelativeLayout.TRUE)
addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE)
}
setImageResource(R.drawable.ic_back_black)
}.also { addView(it) }
TextView(this@Factory2Activity2).apply {
layoutParams =
RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT).apply {
addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE)
}
text = “commit”
setTextSize(TypedValue.COMPLEX_UNIT_SP, 30f)
setTypeface(null, Typeface.BOLD)
}.also { addView(it) }
ImageView(this@Factory2Activity2).apply {
layoutParams =
RelativeLayout.LayoutParams(40f.dp(), 40f.dp()).apply {
addRule(RelativeLayout.ALIGN_PARENT_END, RelativeLayout.TRUE)
addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE)
}
setImageResource(R.drawable.ic_member_more)
}.also { addView(it) }
}.also { addView(it) }
View(this@Factory2Activity2).apply {
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1f.dp())
setBackgroundColor(Color.parseColor(“#eeeeee”))
}.also { addView(it) }
NestedScrollView(this@Factory2Activity2).apply {
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 500f.dp()).apply {
topMargin = 20f.dp()
}
isScrollbarFadingEnabled = true
LinearLayout(this@Factory2Activity2).apply {
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
orientation = LinearLayout.VERTICAL
setPadding(5f.dp(), 5f.dp(), 30f.dp(), 30f.dp())
LinearLayout(this@Factory2Activity2).apply {
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
marginStart = 10f.dp()
marginEnd = 10f.dp()
}
orientation = LinearLayout.VERTICAL
setBackgroundResource(R.drawable.tag_checked_shape)
LinearLayout(this@Factory2Activity2).apply {
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
orientation = LinearLayout.HORIZONTAL
ImageView(this@Factory2Activity2).apply {
layoutParams = LinearLayout.LayoutParams(40f.dp(), 40f.dp())
setImageResource(R.drawable.diamond_tag)
}.also { addView(it) }
TextView(this@Factory2Activity2).apply {
layoutParams =
LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
marginStart = 10f.dp()
}
gravity = Gravity.CENTER
setPadding(10f.dp(), 10f.dp(), 10f.dp(), 10f.dp())
text = “gole”
setTextColor(Color.parseColor(“#389793”))
setTextSize(TypedValue.COMPLEX_UNIT_SP, 20F)
this.setTypeface(null, Typeface.BOLD)
}.also { addView(it) }
}.also { addView(it) }
LinearLayout(this@Factory2Activity2).apply {
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
orientation = LinearLayout.HORIZONTAL
weightSum = 8f
LinearLayout(this@Factory2Activity2).apply {
layoutParams =
LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
weight = 5f
}
orientation = LinearLayout.VERTICAL
TextView(this@Factory2Activity2).apply {
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
text = “The changes were merged into release with so many bugs”
setTextSize(TypedValue.COMPLEX_UNIT_SP, 23f)
}.also { addView(it) }
TextView(this@Factory2Activity2).apply {
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
text = “merge it with mercy”
setTextColor(Color.parseColor(“#c4747E8B”))
setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f)
}.also { addView(it) }
}.also { addView(it) }
ImageView(this@Factory2Activity2).apply {
layoutParams = LinearLayout.LayoutParams(100f.dp(), 100f.dp()).apply {
weight = 3f
}
scaleType = ImageView.ScaleType.FIT_XY
setImageResource(R.drawable.user_portrait_gender_female)
}.also { addView(it) }
}.also { addView(it) }
RelativeLayout(this@Factory2Activity2).apply {
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
topMargin = 10f.dp()
}
setPadding(0, 0, 10f.dp(), 10f.dp())
TextView(this@Factory2Activity2).apply {
layoutParams =
RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT)
.apply {
addRule(RelativeLayout.ALIGN_PARENT_END, RelativeLayout.TRUE)
}
text = “2020.04.30”
}.also { addView(it) }
}.also { addView(it) }
}.also { addView(it) }
}.also { addView(it) }
}.also { addView(it) }
View(this@Factory2Activity2).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 1f.dp())
setBackgroundColor(Color.parseColor(“#eeeeee”))
}.also { addView(it) }
RelativeLayout(this@Factory2Activity2).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
topMargin = 40f.dp()
}
LinearLayout(this@Factory2Activity2).apply {
layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE)
}
orientation = LinearLayout.HORIZONTAL
Button(this@Factory2Activity2).apply {
layoutParams =
LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
rightMargin = 20f.dp()
gravity = Gravity.LEFT
}
setBackgroundResource(R.drawable.bg_orange_btn)
text = “cancel”
}.also {
addView(it)
}
Button(this@Factory2Activity2).apply {
layoutParams =
LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
leftMargin = 20f.dp()
gravity = Gravity.RIGHT
}
setBackgroundResource(R.drawable.bg_orange_btn)
text = “OK”
}.also { addView(it) }
}.also { addView(it) }
}.also { addView(it) }
}
}
用伪代码描述上述代码,结构就是这样的:
容器控件.apply {
子控件.apply {
//设置控件属性
}.also { addView(it) }
}
代码又臭又长又冗余,完全没有可读性。若要微调其中显示宝石的控件,你可以试下,反正我是找不到那个控件了。
但跑了一下测试代码,惊喜地发现构建布局的平均耗时只有 1.32 ms,时间是静态布局的 1/20 。
一开始我以为是嵌套布局导致特别耗时,于是用ConstraintLayout
将嵌套扁平化,代码如下:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:app=“http://schemas.android.com/apk/res-auto”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
</androidx.constraintlayout.widget.ConstraintLayout>
这次做到了零嵌套,带着期望重新运行了一遍代码。但解析布局耗时丝毫没有变化。。。好吧
既然静态布局和动态布局有这么大的性能差距,那就改善一下动态布局代码的可读性!!
DSL
DSL 是改善构建代码可读性的利器!
DSL = domain specific language,即“特定领域语言”,与它对应的一个概念叫“通用编程语言”,通用编程语言有一系列完善的能力来解决几乎所有能被计算机解决的问题,像 Java 就属于这种类型。而特定领域语言只专注于特定的任务,比如 SQL 只专注于操纵数据库,HTML 只专注于表述超文本。
既然通用编程语言能够解决所有的问题,那为啥还需要特定领域语言?因为它可以使用比通用编程语言中等价代码更紧凑的语法来表达特定领域的操作。比如当执行一条 SQL 语句时,不需要从声明一个类及其方法开始。
更紧凑的语法意味着更简洁的 API。应用程序中每个类都提供了其他类与之交互的可能性,确保这些交互易于理解并可以简洁地表达,对于软件的可维护性至关重要。
DSL 有一个普通API不具备特征:DSL 具有结构。而带接收者的lambda
使得构建结构化的 API 变得容易。
带接收者的 lambda
它是一种特殊的 lambda,是 kotlin 中特有的。可以把它理解成“为接收者声明的一个匿名扩展函数”。(扩展函数是一种在类体外为类添加功能的特性)
带接收者的lambda的函数体除了能访问其所在类的成员外,还能访问接收者的所有非私有成员,这个特性是它能够轻松地构建结构。
当带接收者的 lambda 配合高阶函数时,构建结构化的 API 就变得易如反掌。
高阶函数
它是一种特殊的函数,它的参数或者返回值是另一个函数。
比如集合的扩展函数filter()
就是一个高阶函数:
//filter的参数是一个带接收的lambda
public inline fun Iterable.filter(predicate: (T) -> Boolean): List {
return filterTo(ArrayList(), predicate)
}
可以使用它来过滤集合中的元素:
students.filter { age > 18 }
这样就是一种结构化 API 的调用(在 java 中看不到),虽然这种结构得益于 kotlin 的一个约定(如果函数只有一个参数且它是 lambda,则可以省略函数参数列表的括号)。但更关键的是 lambda 的内部,得益于带接收者的lambda
,age > 18
运行在一个和其调用方不同的上下文
中,在这个上下文中,可以轻松的访问到Student
的成员Student.age
( 指向 age 时可以省略 this )
让我们使用这样的技巧来改善“动态构建布局”代码的可读性。
动态布局DSL
用 DSL 重新构建上面的布局的效果如下:
private val rootView by lazy {
ConstraintLayout {
layout_width = match_parent
layout_height = match_parent
ImageView {
layout_id = “ivBack”
layout_width = 40
layout_height = 40
margin_start = 20
margin_top = 20
src = R.drawable.ic_back_black
start_toStartOf = parent_id
top_toTopOf = parent_id
onClick = { onBackClick() }
}
TextView {
layout_width = wrap_content
layout_height = wrap_content
text = “commit”
textSize = 30f
textStyle = bold
align_vertical_to = “ivBack”
center_horizontal = true
}
ImageView {
layout_width = 40
layout_height = 40
src = R.drawable.ic_member_more
align_vertical_to = “ivBack”
end_toEndOf = parent_id
margin_end = 20
}
View {
layout_id = “vDivider”
layout_width = match_parent
layout_height = 1
margin_top = 10
background_color = “#eeeeee”
top_toBottomOf = “ivBack”
}
Layer {
layout_id = “layer”
layout_width = wrap_content
layout_height = wrap_content
referenceIds = “ivDiamond,tvTitle,tvContent,ivAvatar,tvTime,tvSub”
background_res = R.drawable.tag_checked_shape
start_toStartOf = “ivDiamond”
top_toTopOf = “ivDiamond”
bottom_toBottomOf = “tvTime”
end_toEndOf = “tvTime”
}
ImageView {
layout_id = “ivDiamond”
layout_width = 40
layout_height = 40
margin_start = 20
margin_top = 40
src = R.drawable.diamond_tag
start_toStartOf = “ivBack”
top_toBottomOf = “vDivider”
}
TextView {
layout_id = “tvTitle”
layout_width = wrap_content
layout_height = wrap_content
margin_start = 5
gravity = gravity_center
text = “gole”
padding = 10
textColor = “#389793”
textSize = 20f
textStyle = bold
align_vertical_to = “ivDiamond”
start_toEndOf = “ivDiamond”
}
TextView {
layout_id = “tvContent”
layout_width = 0
layout_height = wrap_content
margin_top = 5
text = “The changes were merged into release with so many bugs”
textSize = 23f
start_toStartOf = “ivDiamond”
top_toBottomOf = “ivDiamond”
end_toStartOf = “ivAvatar”
}
ImageView {
layout_id = “ivAvatar”
layout_width = 100
layout_height = 100
margin_end = 20
src = R.drawable.user_portrait_gender_female
end_toEndOf = parent_id
start_toEndOf = “tvContent”
top_toTopOf = “tvContent”
}
TextView {
layout_id = “tvSub”
layout_width = wrap_content
layout_height = wrap_content
text = “merge it with mercy”
textColor = “#c4747E8B”
textSize = 18f
start_toStartOf = “ivDiamond”
top_toBottomOf = “tvContent”
}
TextView {
layout_id = “tvTime”
layout_width = wrap_content
layout_height = wrap_content
margin_top = 20
text = “2020.04.30”
end_toEndOf = “ivAvatar”
top_toBottomOf = “ivAvatar”
}
TextView {
layout_id = “tvCancel”
layout_width = wrap_content
layout_height = wrap_content
margin_end = 30
background_res = R.drawable.bg_orange_btn
padding_start = 30
padding_top = 10
padding_end = 30
padding_bottom = 10
text = “cancel”
margin_bottom = 20
textSize = 20f
textStyle = bold
bottom_toBottomOf = parent_id
end_toStartOf = “tvOk”
start_toStartOf = parent_id
horizontal_chain_style = packed
}
TextView {
layout_id = “tvOk”
layout_width = wrap_content
layout_height = wrap_content
background_res = R.drawable.bg_orange_btn
padding_start = 30
padding_top = 10
margin_bottom = 20
padding_end = 30
padding_bottom = 10
text = “Ok”
textSize = 20f
textStyle = bold
bottom_toBottomOf = parent_id
end_toEndOf = parent_id
horizontal_chain_style = packed
start_toEndOf = “tvCancel”
}
}
}
重构之后的动态布局代码,有了和静态布局一样的可读性,甚至比静态布局更简洁了。
构建控件
代码中每一个控件的类名都是一个扩展方法,构建容器控件的方法如下:
inline fun Context.ConstraintLayout(init: ConstraintLayout.() -> Unit): ConstraintLayout =
ConstraintLayout(this).apply(init)
容器控件的构造都通过Context
的扩展方法实现,只要有Context
的地方就能构建布局。
扩展方法会直接调用构造函数并应用为其初始化属性的 lambda。该 lambda 是一个带接收者的labmda
,它的接收者是ConstraintLayout
,Kotlin 独有的这个特性使得 lambda 函数体中可以额外地多访问一个对象的非私有成员。本例中 lambda 表达式init
的函数体中可以访问ConstraintLayout
的所有非私有成员,这样就能轻松地在函数体中设置控件属性。
有了这个扩展函数,就可以这样构建容器控件(可先忽略属性赋值逻辑,下一节再介绍):
ConstraintLayout {
layout_width = match_parent
layout_height = match_parent
}
上述这段等价于下面的 xml:
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
相较于 xml,省略了一些重复信息,显得更简洁。
构建子控件通过ViewGroup
的扩展方法实现:
inline fun ViewGroup.TextView(init: TextView.() -> Unit) =
TextView(context).apply(init).also { addView(it) }
子控件构建完毕后需要填入容器控件,定义成ViewGroup
的扩展方法就能方便的调用addView()
。
控件的构建方法都通过关键词inline
进行了内联,编译器会将带有inline
函数体中的代码平铺到调用处,这样就避免了一次函数调用,函数调用也有时间和空间上的开销(在栈中创建栈帧)。默认情况下、每个 Kotlin 中的 lambda 都会被编译成一个匿名类,除非 lambda 被内联。被内联的构建方法使得构建布局时不会发生函数调用,并且也不会创建匿名内部类。
现在就可以像这样为容器控件添加子控件了:
ConstraintLayout {
layout_width = match_parent
layout_height = match_parent
TextView {
layout_width = wrap_content
layout_height = wrap_content
}
}
这样定义的缺点是:只能在ViewGroup
中构建TextView
,若有单独构建的需求,可以模仿容器控件的构建方法:
inline fun Context.TextView(init: TextView.() -> Unit) =
TextView(this).apply(init)
设置控件属性
xml 中每一个属性都有对应的 Java 方法,直接调用方法使得动态构建代码可读性很差。
有什么办法可以把方法调用转化成属性赋值语句?—— 扩展属性:
inline var View.background_color: String
get() {
return “”
}
set(value) {
setBackgroundColor(Color.parseColor(value))
}
为View
增加了名为background_color
的扩展属性,它是String
类型的变量,需为其定义取值和设置方法。当该属性被赋值时,set()
方法会被调用,在其中调用了View.setBackgroundColor()
来设置背景色。
现在就可以像这样设置控件背景色了:
ConstraintLayout {
layout_width = match_parent
layout_height = match_parent
background_color = “#ffff00”
}
特别地,对于下面这种“可或”的属性:
改为+
:
TextView {
layout_width = wrap_content
layout_height = wrap_content
gravity = gravity_center_horizontal + gravity_top
}
增量修改布局属性
上面的例子中,背景色是一个独立的属性,即修改它不会影响到其他属性。但修改布局属性都是批量的。当只想修改其中一个属性值时,就必须增量修改:
inline var View.padding_top: Int
get() {
return 0
}
set(value) {
setPadding(paddingLeft, value.dp(), paddingRight, paddingBottom)
}
padding_top
被定义为View
的扩展属性,所以在set()
方法中能轻松访问到View
原有的paddingLeft
,paddingRight
,paddingBottom
,以便使这三个属性保持原样,而只修改paddingTop
。
dp()
是一个扩展方法,用来将 Int 值根据当前屏幕密度转换成 dp 值:
fun Int.dp(): Int =
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
this.toFloat(),
Resources.getSystem().displayMetrics
).toInt()
为控件设置宽高也需要增量修改:
inline var View.layout_width: Int
get() {
return 0
}
set(value) {
val w = if (value > 0) value.dp() else value
val h = layoutParams?.height ?: 0
layoutParams = ViewGroup.MarginLayoutParams(w, h)
}
在设置宽时,读取原有高,并新建ViewGroup.MarginLayoutParams
,重新为layoutParams
赋值。为了通用性,选择了ViewGroup.MarginLayoutParams
,它是所有其他LayoutParams
的父类。
一个更复杂的例子是ContraintLayout
中的相对布局属性:
inline var View.start_toStartOf: String
get() {
return “”
}
set(value) {
layoutParams = layoutParams.append {
//‘toLayoutId()是生成控件id的方法,下一节会介绍’
startToStart = value.toLayoutId()
startToEnd = -1
}
}
在 xml 中每一个相对布局属性都对应于ContraintLayout.LayoutParams
实例中的一个 Int 值(控件 ID 是 Int 类型)。所以必须获取原LayoutParams
实例并为对应的新增属性赋值,就像这样:
inline var View.start_toStartOf: String
get() {
return “”
}
set(value) {
layoutParams = layoutParams.apply {
startToStart = 控件ID
//‘-1表示没有相对约束’
startToEnd = -1
}
}
但设置宽高时,构造的是ViewGroup.MarginLayoutParams
实例,它并没有相对布局的属性。所以需要将原ViewGroup.MarginLayoutParams
中的宽高和边距值复制出来,重新构建一个ContraintLayout.LayoutParams
:
fun ViewGroup.LayoutParams.append(set: ConstraintLayout.LayoutParams.() -> Unit) =
//‘如果是限制布局则直接增量赋值’
(this as? ConstraintLayout.LayoutParams)?.apply(set) ?:
//‘否则将边距布局参数值拷贝到限制布局参数中,再增量赋值’
(this as? ViewGroup.MarginLayoutParams)?.toConstraintLayoutParam()?.apply(set)
//‘将边距布局参数转换成限制布局参数’
fun ViewGroup.MarginLayoutParams.toConstraintLayoutParam() =
ConstraintLayout.LayoutParams(width, height).also { it ->
it.topMargin = this.topMargin
it.bottomMargin = this.bottomMargin
it.marginStart = this.marginStart
it.marginEnd = this.marginEnd
}
这个方案有一个缺点:必须先为控件设置宽高,再设置相对布局属性。
生成控件ID
View.setId(int id)
接收 int 类型的值,但 int 值没有语义,起不到标记控件的作用,所以扩展属性layout_id
是 String 类型的:
inline var View.layout_id: String
get() {
return “”
}
set(value) {
id = value.toLayoutId()
}
//‘将String转化成对应的Int值’
fun String.toLayoutId():Int{
var id = java.lang.String(this).bytes.sum()
if (id == 48) id = 0
return id
}
String 必须转化成 Int 才能调用View.setId()
,采用的方法是:先将 String 转化成 byte 数组,然后对数组累加。但 Kotlin 中的 String 没有getBytes()
,所以只能显示地构造java.lang.String
。
之所以要硬编码48
是因为:
public class ConstraintLayout extends ViewGroup {
public static class LayoutParams extends MarginLayoutParams {
public static final int PARENT_ID = 0;
}
}
而我把该常量重新定义成 String 类型:
val parent_id = “0”
通过toLayoutId()
算法,"0"
对应值为 48。
更好的办法是找出toLayoutId()
算法的逆算法,即当该函数输出为 0 时,输入应该是多少?可惜并想不出如何实现。望知道的小伙伴点拨~
现在就可以像这样设置控件 ID 了:
ConstraintLayout {
layout_id = “cl”
layout_width = match_parent
layout_height = match_parent
background_color = “#ffff00”
ImageView {
layout_id = “ivBack”
layout_width = 40
layout_height = 40
src = R.drawable.ic_back_black
start_toStartOf = parent_id
top_toTopOf = parent_id
}
}
重命名控件属性
为了让构建语法尽可能的精简,原先带有类名的常量都被重新定义了,比如:
val match_parent = ViewGroup.LayoutParams.MATCH_PARENT
val wrap_content = ViewGroup.LayoutParams.WRAP_CONTENT
val constraint_start = ConstraintProperties.START
val constraint_end = ConstraintProperties.END
val constraint_top = ConstraintProperties.TOP
val constraint_bottom = ConstraintProperties.BOTTOM
val constraint_baseline = ConstraintProperties.BASELINE
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

如何做好面试突击,规划学习方向?
面试题集可以帮助你查漏补缺,有方向有针对性的学习,为之后进大厂做准备。但是如果你仅仅是看一遍,而不去学习和深究。那么这份面试题对你的帮助会很有限。最终还是要靠资深技术水平说话。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。建议先制定学习计划,根据学习计划把知识点关联起来,形成一个系统化的知识体系。
学习方向很容易规划,但是如果只通过碎片化的学习,对自己的提升是很慢的。
同时我还搜集整理2020年字节跳动,以及腾讯,阿里,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 Xmind(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。
在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

如何做好面试突击,规划学习方向?
面试题集可以帮助你查漏补缺,有方向有针对性的学习,为之后进大厂做准备。但是如果你仅仅是看一遍,而不去学习和深究。那么这份面试题对你的帮助会很有限。最终还是要靠资深技术水平说话。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。建议先制定学习计划,根据学习计划把知识点关联起来,形成一个系统化的知识体系。
学习方向很容易规划,但是如果只通过碎片化的学习,对自己的提升是很慢的。
同时我还搜集整理2020年字节跳动,以及腾讯,阿里,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 Xmind(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。
[外链图片转存中…(img-ObsPvGr4-1712563468553)]
在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多。
[外链图片转存中…(img-sf0DYFU3-1712563468553)]
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!