2024年Android性能优化 _ 把构建布局用时缩短 20 倍(下),2024年最新2024金九银十Android大厂面试题来袭

面试宝典

面试必问知识点、BATJ历年历年面试真题+解析

学习经验总结

(一)调整好心态
心态是一个人能否成功的关键,如果不调整好自己的心态,是很难静下心来学习的,尤其是现在这么浮躁的社会,大部分的程序员的现状就是三点一线,感觉很累,一些大龄的程序员更多的会感到焦虑,而且随着年龄的增长,这种焦虑感会越来越强烈,那么唯一的解决办法就是调整好自己的心态,要做到自信、年轻、勤奋。这样的调整,一方面对自己学习有帮助,另一方面让自己应对面试更从容,更顺利。

(二)时间挤一挤,制定好计划
一旦下定决心要提升自己,那么再忙的情况下也要每天挤一挤时间,切记不可“两天打渔三天晒网”。另外,制定好学习计划也是很有必要的,有逻辑有条理的复习,先查漏补缺,然后再系统复习,这样才能够做到事半功倍,效果才会立竿见影。

(三)不断学习技术知识,更新自己的知识储备
对于一名程序员来说,技术知识方面是非常重要的,可以说是重中之重。**要面试大厂,自己的知识储备一定要非常丰富,若缺胳膊少腿,别说在实际工作当中,光是面试这一关就过不了。**对于技术方面,首先基础知识一定要扎实,包括自己方向的语言基础、计算机基础、算法以及编程等等。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

app:layout_constraintStart_toStartOf=“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 的内部,得益于带接收者的lambdaage > 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,它的接收者是ConstraintLayoutKotlin 独有的这个特性使得 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原有的paddingLeftpaddingRightpaddingBottom,以便使这三个属性保持原样,而只修改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
val constraint_parent = ConstraintProperties.PARENT_ID

新增属性:组合属性

利用扩展属性,还可以任意动态新增一些原先 xml 中没有的属性。

ConstraintLayout中如果想纵向对齐一个控件,需要将两个属性的值设置为目标控件ID,分别是top_toTopOfbottom_toBottomOf,若通过扩展属性就能简化这个步骤:

inline var View.align_vertical_to: String
get() {
return “”
}
set(value) {
top_toTopOf = value
bottom_toBottomOf = value
}

其中的top_toTopOfbottom_toBottomOf和上面列举的start_toStartOf类似,不再赘述。

同样的,还可以定义align_horizontal_to

新增属性:视图点击监听器

下面的代码通过扩展属性来设置点击事件:

var View.onClick: (View) -> Unit
get() {
return {}
}
set(value) {
setOnClickListener { v -> value(v) }
}

View扩展属性onClick,它是函数类型。 然后就可以像这样设置点击事件了:

private fun buildViewByClDsl(): View =
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
}
}

val onBackClick = { v : View ->
activity?.finish()
}

得益于函数类型,可以把点击逻辑封装在一个 lambda 中并赋值给变量onBackClick

新增属性: 列表表项点击事件

RecyclerView没有子控件点击事件监听器,同样可以通过扩展属性来解决这个问题:

//‘为 RecyclerView 扩展表项点击监听器属性’
var RecyclerView.onItemClick: (View, Int) -> Unit
get() {
return { _, _ -> }
}
set(value) {
setOnItemClickListener(value)
}

//‘为 RecyclerView 扩展表项点击监听器’
fun RecyclerView.setOnItemClickListener(listener: (View, Int) -> Unit) {
//‘为 RecyclerView 子控件设置触摸监听器’
addOnItemTouchListener(object : RecyclerView.OnItemTouchListener {
//‘构造手势探测器,用于解析单击事件’
val gestureDetector = GestureDetector(context, object : GestureDetector.OnGestureListener {
override fun onShowPress(e: MotionEvent?) {
}

override fun onSingleTapUp(e: MotionEvent?): Boolean {
//‘当单击事件发生时,寻找单击坐标下的子控件,并回调监听器’
e?.let {
findChildViewUnder(it.x, it.y)?.let { child ->
listener(child, getChildAdapterPosition(child))
}
}
return false
}

override fun onDown(e: MotionEvent?): Boolean {
return false
}

override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean {
return false
}

override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean {
return false
}

override fun onLongPress(e: MotionEvent?) {
}
})

override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {

}

//‘在拦截触摸事件时,解析触摸事件’
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
gestureDetector.onTouchEvent(e)
return false
}

override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
}
})
}

然后可以像这样为RecyclerView设置表项点击事件:

RecyclerView {
layout_id = “rvTest”
layout_width = match_parent
layout_height = 300
onItemClick = onListItemClick
}

val onListItemClick = { v: View, i: Int ->
Toast.makeText(context, “item $i is clicked”, Toast.LENGTH_SHORT).show()
}

新增属性:文字变化监听器

上面两个新增属性都可以用一个函数类型的变量表示,如果有多个回调,比如监听EditText中文字的变化,就可以这样写:

inline var TextView.onTextChange: TextWatcher
get() {
return TextWatcher()
}
set(value) {
// 为控件设置文字变化监听器
val textWatcher = object : android.text.TextWatcher {
override fun afterTextChanged(s: Editable?) {
// 将回调的实现委托给 TextWatcher.afterTextChanged
value.afterTextChanged.invoke(s)
}

override fun beforeTextChanged(text: CharSequence?,start: Int,count: Int,after:Int) {
// 将回调的实现委托给 TextWatcher.beforeTextChanged
value.beforeTextChanged.invoke(text, start, count, after)
}

override fun onTextChanged(text: CharSequence?, start: Int, before: Int, count: Int) {
// 将回调的实现委托给 TextWatcher.onTextChanged
value.onTextChanged.invoke(text, start, before, count)
}
}
addTextChangedListener(textWatcher)
}

先为控件设置监听器,然后将回调的实现委托给TextWatcher中的 lambda:

// 类TextWatcher包含三个函数类型的变量,它们分别对应android.text.TextWatcher接口中的三个回调
class TextWatcher(
var beforeTextChanged: (
text: CharSequence?,
start: Int,
count: Int,
after: Int
) -> Unit = { _, _, _, _ -> },
var onTextChanged: (
text: CharSequence?,
start: Int,
count: Int,
after: Int
) -> Unit = { _, _, _, _ -> },
var afterTextChanged: (text: Editable?) -> Unit = {}
)

然后就可以像这样使用:

EditText {
layout_width = match_parent
layout_height = 50
textSize = 20f
background_color = “#00ffff”
top_toBottomOf = “rvTest”
onTextChange = textWatcher {
onTextChanged = { text: CharSequence?, start: Int, count: Int, after: Int ->
Log.v(“test”,“onTextChanged, text=${text}”)
}
}
}

其中textWatcher是一个顶层函数,他用于构建TextWatcher实例:

fun textWatcher(init: TextWatcher.() -> Unit): TextWatcher = TextWatcher().apply(init)

findViewById

如何获取控件实例的引用?得益于 DSL 的语法糖,这套动态布局构建有一种新的方法:

class MainActivity : AppCompatActivity() {
private var ivBack:ImageView? = null
private var tvTitle:TextView? = null

private val rootView by lazy {
ConstraintLayout {
layout_width = match_parent
layout_height = match_parent

ivBack = 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
}

tvTitle = TextView {
layout_width = wrap_content
layout_height = wrap_content
text = “commit”
textSize = 30f
textStyle = bold
align_vertical_to = “ivBack”
center_horizontal = true
}
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(rootView)
}
}

除了这种方式,还有一种常规方式:

fun View.find(id: String): T = findViewById(id.toLayoutId())

fun AppCompatActivity.find(id: String): T = findViewById(id.toLayoutId())

用 DSL 布局的爽点

简化多状态界面的控制逻辑

真实项目中经常有这样的场景:“在不同状态下,界面的某个位置展示不同类型的控件”。通常的做法是将不同状态下的控件都声明在布局文件中,然后通过代码根据状态用setVisibility(View.VISIBLE) + setVisibility(View.GONE)控制。

因为 DSL 是 Kotlin 代码,所以条件判断逻辑可以无障碍的插入其中:

class AFragment : Fragment() {
// 界面状态
private val type by lazy { arguments?.getInt(“layout-type”) }

private val rootView: ConstraintLayout? by lazy {
ConstraintLayout {
layout_width = match_parent
layout_height = match_parent

// 根据界面状态添加不同的视图
if (type == 1) {
TextView {
layout_width = wrap_content
layout_height = wrap_content
textSize = 14f
bottom_toBottomOf = parent_id
center_horizontal = true
onClick = { _ -> startActivityA() }
}
} else {
ImageView {
layout_width = match_parent
layout_height = 40
bottom_toBottomOf = parent_id
onClick = { _ -> startActivityB() }
}
}
}
}

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return rooView
}
}

动态构建布局

该界面的内容由服务器返回,即事先不能实现确定控件的个数。除了使用RecyclerView之外,也可以用 DSL 根据数据动态地构建布局:

class GameDialogFragment : DialogFragment() {
// 构建纵向根布局
private val rootView: LinearLayout? by lazy {
LinearLayout {
layout_width = match_parent
layout_height = 0
height_percentage = 0.22f
orientation = vertical
top_toTopOf = parent_id
}
}
}

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return rooView
}

fun onGameReturn(gameBeans: GameBean){
buildGameLayout(gameBeans)
}

最后

这里我希望可以帮助到大家提升进阶。

内容包含:Android学习PDF+架构视频+面试文档+源码笔记高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 这几块的内容。非常适合近期有面试和想在技术道路上继续精进的朋友。

喜欢本文的话,不妨给我点个小赞、评论区留言或者转发支持一下呗~

img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

{
layout_width = match_parent
layout_height = 0
height_percentage = 0.22f
orientation = vertical
top_toTopOf = parent_id
}
}
}

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return rooView
}

fun onGameReturn(gameBeans: GameBean){
buildGameLayout(gameBeans)
}

最后

这里我希望可以帮助到大家提升进阶。

内容包含:Android学习PDF+架构视频+面试文档+源码笔记高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 这几块的内容。非常适合近期有面试和想在技术道路上继续精进的朋友。

喜欢本文的话,不妨给我点个小赞、评论区留言或者转发支持一下呗~

img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值