一.使用viewBinding
1.想开启那个模块的viewBinding就进入对应模块的build.gradle
2.进入app的build.gradle
开启viewbinding
viewBinding {
enabled = true
}
3.当你开启viewbinding后,app目录下的所有布局文件都会生成一个绑定类。这个类的类名是以xml布局文件名去掉下换线后,单词首字母大写加上Binding命名的。如activity_main.xml生成的绑定类为ActivityMainBinding.
如果你不想生成绑定类,也很简单,将tools:viewBindingIgnore=“true” 属性添加到相应布局文件的根视图中。
使用就更简单了,如下
package com.example.animationtest
import android.os.Bundle
import android.os.PersistableBundle
import androidx.appcompat.app.AppCompatActivity
import com.example.animationtest.databinding.ConstrainBinding
class Constraint:AppCompatActivity() {
private val viewBinding=ConstrainBinding.inflate(layoutInflater)
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
setContentView(viewBinding.root)
}
}
viewBinding.root就是布局的根布局
二.开发中可能会遇到的场景
1.当A或者C宽度固定或者为wrap_content,你想要B的宽度自适应。
其实这个场景可以分解为:
B.width=屏幕宽度-A.width-C.width(视情况可能还会减去margin或padding)
如果用约束布局就要用android:layout_width="0dp"来解决这个问题,那就是把B的宽度设置为0dp。
如下
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="@+id/text_a"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:text="A"
android:background="@color/black"
android:textColor="@color/white"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/text_b"
tools:ignore="MissingConstraints" />
<TextView
android:id="@+id/text_b"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="B"
android:textColor="@color/white"
android:background="@color/design_default_color_primary_dark"
app:layout_constraintStart_toEndOf="@id/text_a"
app:layout_constraintEnd_toStartOf="@id/text_c"
tools:ignore="MissingConstraints" />
<TextView
android:id="@+id/text_c"
android:layout_width="60dp"
android:layout_height="wrap_content"
android:text="C"
android:textColor="@color/teal_200"
app:layout_constraintStart_toEndOf="@id/text_b"
app:layout_constraintEnd_toEndOf="parent"
tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>
布局很简单,就三个Textview,把三个控件相互约束,再把把B的宽度设置为0dp。
2.如果想要三个控件平分宽度
2.1 如果你没有背景,就把三个控件相互约束,但三个控件的宽度都必须是wrap_content或固定宽度(这个应该都能理解吧,为match_parent其中一个子布局就充满了父控件了,就别谈自适应了)。
2.2 你有背景就把三个控件的宽度都设置为0dp
其实这个场景可以分解为:
任意一个的宽度=(屏幕宽度-(视情况可能减去margin或padding))/3
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_a"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/black"
android:text="A"
android:textColor="@color/white"
app:layout_constraintEnd_toStartOf="@id/text_b"
app:layout_constraintStart_toStartOf="parent"
tools:ignore="MissingConstraints" />
<TextView
android:id="@+id/text_b"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/design_default_color_primary_dark"
android:text="B"
android:textColor="@color/white"
app:layout_constraintEnd_toStartOf="@id/text_c"
app:layout_constraintStart_toEndOf="@id/text_a"
tools:ignore="MissingConstraints" />
<TextView
android:id="@+id/text_c"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="C"
android:textColor="@color/teal_200"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/text_b"
tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>
现在基本上都是用约束布局,这就像属性动画一样,它可以适用所有场景。
三.动态改变布局
1.获取屏幕宽度
可以去下面的博客去看,写的很好
Android 获取屏幕宽度和高度的几种方法
当你获取了屏幕宽度或高度,你就可以写一写适配Android的布局了。
val outSize = DisplayMetrics()
windowManager.defaultDisplay.getRealMetrics(outSize)
val width = outSize.widthPixels
val height = outSize.widthPixels
viewBinding.textA.updateLayoutParams<ConstraintLayout.LayoutParams> {
topMargin=width
}
2.动态改变布局
2.1 改变布局属性
1.viewBinding.textA.updateLayoutParams<ConstraintLayout.LayoutParams> { topMargin=width }
ConstraintLayout.LayoutParams 这里是父布局的LayoutParams,
比如我的text A的父布局就是ConstraintLayout
2.2 改变布局约束
val constrainSet = ConstraintSet().apply { clone(viewBinding.root) }
constrainSet.connect(
viewBinding.textC.id,ConstraintSet.START,viewBinding.textA.id,ConstraintSet.END
)
constrainSet.applyTo(viewBinding.root)
val constrainSet = ConstraintSet().apply { clone(viewBinding.root) }
这段话就是复制一份父布局的约束,方法有三个如下:
set.clone(constraintLayout: ConstraintLayout);
set.clone(set: ConstraintSet);
set.clone(context: Context, constraintLayoutId: Int);
这里需要注意,在调用clone()方法的时候,必须保证这个父布局的所有子布局都设置了 id,不然会报错,这跟它的内部实现原理有关,有兴趣可以去看一下源码。
- 设置组件之间的约束,常用方法有:
set.connect(startID: Int, startSide: Int, endID: Int, endSide: Int, margin: Int)
set.connect(startID: Int, startSide: Int, endID: Int, endSide: Int)
constrainSet.connect(
viewBinding.textC.id,ConstraintSet.START,viewBinding.textA.id,ConstraintSet.END,20
)
这句话的意思就是: 修改一个id为textC的约束,依赖的约束组件为一个id为textA的控件,修改为textC的左边和textA的右边对其,边距margin为20px:
3.然后把新的布局约束设置到原来的布局
constrainSet.applyTo(viewBinding.root)
4.View.post()
这是为了解决布局还没执行View的meaure()而获取宽高为0的其中一个解决方案。
viewBinding.textB.post
{
val width=viewBinding.textB.width
val width=viewBinding.textB.measuredWidth
val height=viewBinding.textB.height
}
大家应该注意到我上面用了width和measuredWidth
这两者有何区别呢
很简单就是字母的区别(开个冷玩笑)
measuredWidth:它的值在measure方法运行后会确定
width:它的值在layout方法运行后会确定
这里引用下这个博主的一段话
Android是消息驱动的模式,View.post的Runnable任务会被加入任务队列,并且等待第一次TraversalRunnable执行结束后才执行,此时已经执行过一次measure,layout过程了,所以在后面执行post的Runnable时,已经有measure的结果,因此此时可以获取到View的宽高。