要求:Android Studio必须是3.6 Canary 11以及更高的版本才能使用
作用:View Binding是一项功能,使您可以更轻松地编写与视图交互的代码。在模块中启用视图绑定后,它将为该模块中存在的每个XML布局文件生成一个绑定类。绑定类的实例包含对在相应布局中具有ID的所有视图的直接引用。
像ButterKnife、Android Kotlin extensions一样都是为了简化findViewById
Jake Wharton 也在 Butter Knife开源库中添加了如下一句话:
Attention: Development on this tool is winding down. Please consider switching to view binding in the coming months.
想必 ViewBinding 在未来的地位和作用将不言而喻了吧。
// Android Studio 3.6.0
android {
...
viewBinding {
enabled = true
}
}
在 Android Studio 4.0 中,viewBinding 将被变成属性整合到了buildFeatures 选项中,配置要改成:
// Android Studio 4.0
android {
buildFeatures {
viewBinding = true
}
}
如果你的布局文件是activity_main.xml,则会生成一个ActivityMainBinding的类,如果你的布局文件是result_profile.xml,则会生成一个ResultProfileBinding的类,以此类推。
下面以activity_main.xml及其对应的MainActivity.java为例说明:
假设activity_main.xml放置了三个控件:TextView(Id为text)、Button(Id为button)、ImageView(没有设置Id),其中ImageView因为没有设置Id,因此绑定类中不存在对它的引用,所以在代码中无法被引用。
MainActivity.java的部分代码如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
LayoutInflater layoutInflater = LayoutInflater.from(this);
ActivityMainBinding binding = ActivityMainBinding.inflate(layoutInflater);
setContentView(binding.getRoot());
binding.text.setText("文字已变化");
binding.button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "Button被点击", Toast.LENGTH_SHORT).show();
}
});
}
在获取布局的时候,如下这样写,更是官方文档中推荐的
//使用ViewBinding后的方法
mBinding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(mBinding.getRoot());
注意事项
注意:原先的setContentView(R.layout.activity_main)需要注释掉,否则会重复设置ContentView。
布局的根视图(activity_main.xml)会自动生成一个名为 rootView 的成员变量。在 Activity 的 onCreate()方法中,要将 rootView传入 setContentView()方法,从而让 Activity 可以使用绑定对象中的布局,rootView是私有变量,需要使用getRoot()方法拿到。
public class BaseFragment extends Fragment {
private Activity mActivity;
private FrgamentBaseBinding mBind;
private TextView mTv;
@Override
public void onAttach(Context context) {
super.onAttach(context);
mActivity = (Activity) context;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mBind = FrgamentBaseBinding.inflate(inflater, container, false);
View root = mBind.getRoot();
//View root = inflater.inflate(R.layout.frgament_base, container, false);
return root;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mTv = mBind.frgamentTv;
mBind.frgamentBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mActivity, "hello i am fragment btn", Toast.LENGTH_SHORT).show();
mTv.setText("click fragment btn~~~");
}
});
mBind.frgamentTv.setText("hello i am fragment tv");
}
@Override
public void onDestroyView() {
super.onDestroyView();
mBind = null;
}
}
注意:Fragment 的存在时间比其视图长。请务必在 Fragment 的 onDestroyView()
方法中清除对绑定类实例的所有引用。
include填充的布局如下
activity_test_include.xml引用这个布局
Java代码
public class TestIncludeActivity extends AppCompatActivity {
private ActivityTestIncludeBinding mBind;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBind = ActivityTestIncludeBinding.inflate(getLayoutInflater());
setContentView(mBind.getRoot());
mBind.includeTv.setText("呵呵,为了测试include");
//1.不给这个布局中的include增加id,include的xml文件中增加id。答案是获取不到 填充部分的view
//2.给这个布局中的include增加id,include的xml文件中增加id。答案是没问题的
mBind.includeInclude.containerInclude.setBackgroundColor(Color.RED);
mBind.includeInclude.tvInclude.setText("hello");
//3.给这个布局中的include增加id,不给include的xml文件中的根布局增加id。答案是没问题的
}
}
include:如果填充的布局不是merge,那么include的id必须要加,否则就不能获取到填充布局中的控件id。
控件中的根布局id也是可以加的,能获取到。可以通过id设置背景颜色等
include填充的布局如下
activity_test_include.xml引用这个布局
Java代码
public class TestIncludeActivity extends AppCompatActivity {
private ActivityTestIncludeBinding mBind;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBind = ActivityTestIncludeBinding.inflate(getLayoutInflater());
setContentView(mBind.getRoot());
mBind.includeTv.setText("呵呵,为了测试include");
//4.将include的xml文件的根布局改成merge,测试下有没有问题。这里是RelativeLayout改成Merge
IncludeItemBinding binding = IncludeItemBinding.bind(mBind.getRoot());
binding.tvInclude.setText("这就不会出现问题了吧");
//无法获取到根布局。。。
}
}
merge:merge xml文件中的根布局id可以加,但是获取不到,没有什么意义!
如果include中引用merge,但是有id。会报如下异常
java.lang.NullPointerException: Missing required view with ID: com.example.viewbinding:id/i1
如果想要获取到Merge中的控件,只能通过IncludeItemBinding.bind(mBind.getRoot()); 然后在操作想要的控件
创建简单的自定义Dialog
public class TestCusDialogActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_cus_dialog);
}
public void onClick(View view) {
MyDialog dialog = new MyDialog(this, R.style.AppTheme);
dialog.show();
}
}
Java代码中使用,我在activity布局中忽略了view bind。如果不想用view bind的布局一定记得要忽略!!!
public class TestCusDialogActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_cus_dialog);
}
public void onClick(View view) {
MyDialog dialog = new MyDialog(this, R.style.AppTheme);
dialog.show();
}
}
cus_view_layout.xml
创建的自定义LinearLayout
/***
* 创建时间:2020/8/29 20:55
* 创建人:10850
* 功能描述:自定义view
* 1.使用的layout文件不包含merge
* init1、2、3、4是使用inflate来导入layout布局的写法,全部可以正常显示自定义的布局。
* init10、11、12是使用ViewBinding的写法,10无法正常显示视图,11和12是两种不同的写法,道理一样。
*/
public class MyLinearLayout extends LinearLayout {
public MyLinearLayout(Context context) {
this(context, null);
}
public MyLinearLayout(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MyLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// init1();
// init2();
// init3();
// init4();
//11、12都是可以的
//10的位置就偏离中心了
init12();
}
private void init1() {
inflate(getContext(), R.layout.cus_view_layout, this);
}
private void init2() {
View view = LayoutInflater.from(getContext()).inflate(R.layout.cus_view_layout, this);
}
//和init2()方法相等
private void init3() {
View view = LayoutInflater.from(getContext()).inflate(R.layout.cus_view_layout, this, true);
}
private void init4() {
View view = LayoutInflater.from(getContext()).inflate(R.layout.cus_view_layout, this, false);
addView(view);
}
//视图异常,布局无法填充满
private void init10() {
CusViewLayoutBinding binding = CusViewLayoutBinding.inflate(LayoutInflater.from(getContext()));
addView(binding.getRoot());
}
private void init11() {
CusViewLayoutBinding binding = CusViewLayoutBinding.inflate(LayoutInflater.from(getContext()), this, true);
}
private void init12() {
CusViewLayoutBinding binding = CusViewLayoutBinding.inflate(LayoutInflater.from(getContext()), this, false);
addView(binding.getRoot());
}
}
cus_view_merge.xml
创建的自定义LinearLayout
/***
* 创建时间:2020/8/29 20:55
* 创建人:10850
* 功能描述:自定义view
* 1.使用的layout文件不包含merge
* init1、2、3、4是使用inflate来导入layout布局的写法,全部可以正常显示自定义的布局。
* init10、11、12是使用ViewBinding的写法,10无法正常显示视图,11和12是两种不同的写法,道理一样。
* 2.使用的layout文件根标签为merge
*
*/
public class MegeLinearLayout extends LinearLayout {
public MegeLinearLayout(Context context) {
this(context, null);
}
public MegeLinearLayout(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MegeLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// init1();
// init2();
// init3();
// init4();
//21没效果 20是正常的
init20();
}
private void init1() {
inflate(getContext(), R.layout.cus_view_merge, this);
}
private void init2() {
View view = LayoutInflater.from(getContext()).inflate(R.layout.cus_view_merge, this);
}
//和init2()方法相等
private void init3() {
View view = LayoutInflater.from(getContext()).inflate(R.layout.cus_view_merge, this, true);
}
private void init4() {
View view = LayoutInflater.from(getContext()).inflate(R.layout.cus_view_merge, this, false);
addView(view);
}
private void init20() {
CusViewMergeBinding binding = CusViewMergeBinding.inflate(LayoutInflater.from(getContext()), this);
}
//没有效果,可以理解为还没有rootView
private void init21() {
CusViewMergeBinding binding = CusViewMergeBinding.bind(this);
}
}
activity_test_cus_view.xml
TestRvActivity.class
public class TestRvActivity extends AppCompatActivity {
private RecyclerView mRv;
private MyAdapter mAdapter;//适配器
private List list;//数据集合
private LinearLayoutManager mLayout;//布局管理器
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityTestRvBinding binding = ActivityTestRvBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
mRv = binding.rv;
initData();//初始化数据
mAdapter = new MyAdapter(this, list);
//设置适配器
mRv.setAdapter(mAdapter);
//布局有3种 LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager
mLayout = new LinearLayoutManager(this);
mRv.setLayoutManager(mLayout);
//设置Item增加、移除动画
mRv.setItemAnimator(new DefaultItemAnimator());
//添加默认分割线
mRv.addItemDecoration(new DividerItemDecoration(
this, DividerItemDecoration.VERTICAL));
//点击和长按事件
mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(TestRvActivity.this, ((TextView) view).getText() + " click", Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(TestRvActivity.this, position + " Long click", Toast.LENGTH_SHORT).show();
mAdapter.removeData(position);
mAdapter.notifyDataSetChanged();
}
});
}
private void initData() {
list = new ArrayList<>();
for (int i = 0; i <= 200; i++) {
list.add("Item " + i);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_item, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
//增加:在第二个位置增加一个item
//删除:在第二个位置删掉一个item
case R.id.add:
mAdapter.addData(1);
mAdapter.notifyDataSetChanged();
break;
case R.id.delete:
mAdapter.removeData(1);
mAdapter.notifyDataSetChanged();
break;
}
return true;
}
}
其布局
MyAdapter.class
public class MyAdapter extends RecyclerView.Adapter {
//数据源
private List mList;
private Context mContext;
private RvItemBinding mBinding;
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
private OnItemClickListener mOnItemClickListener;
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
mOnItemClickListener = onItemClickListener;
}
//构造方法
public MyAdapter(Context context, List list) {
this.mList = list;
this.mContext = context;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
//View inflate = LayoutInflater.from(mContext).inflate(R.layout.rv_item, viewGroup, false);
//仿照原来的方式,否则不充满布局
mBinding = RvItemBinding.inflate(LayoutInflater.from(viewGroup.getContext()), viewGroup, false);
//这样不行,不能充满布局。不推荐
//mBinding = RvItemBinding.inflate(LayoutInflater.from(mContext));
return new ViewHolder(mBinding);
}
@Override
public void onBindViewHolder(@NonNull final ViewHolder viewHolder, int i) {
viewHolder.mTextView.setText(mList.get(i));
// ViewGroup.LayoutParams layoutParams = viewHolder.mTextView.getLayoutParams();
// layoutParams.height = new Random().nextInt(200) + 200;
// viewHolder.mTextView.setLayoutParams(layoutParams);
//点击事件
if (mOnItemClickListener != null) {
viewHolder.mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int pos = viewHolder.getLayoutPosition();
mOnItemClickListener.onItemClick(viewHolder.mTextView, pos);
}
});
viewHolder.mTextView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
int pos = viewHolder.getLayoutPosition();
mOnItemClickListener.onItemLongClick(viewHolder.mTextView, pos);
return false;
}
});
}
}
@Override
public int getItemCount() {
return mList == null ? 0 : mList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView mTextView;
private FrameLayout mFrameLayout;
//1.这里如果使用之前的方式,onCreateViewHolder方法,返回的是new ViewHolder(mBinding.getRoot());即可
/*public ViewHolder(@NonNull View itemView) {
super(itemView);
mTextView = mBinding.rvTextView;
mFrameLayout = mBinding.rvContainer;
}*/
//2.使用如下方式,onCreateViewHolder方法,返回的是new ViewHolder(mBinding);即可
public ViewHolder(@NonNull RvItemBinding binding) {
super(binding.getRoot());
mTextView = binding.rvTextView;
mFrameLayout = binding.rvContainer;
}
}
//添加item
public void addData(int position) {
mList.add(position, "Insert One");
notifyItemInserted(position);
}
//删除item
public void removeData(int position) {
mList.remove(position);
notifyItemRemoved(position);
}
}
其item布局
public class TestRvActivity extends AppCompatActivity {
private RecyclerView mRv;
private MyAdapter mAdapter;//适配器
private List list;//数据集合
private LinearLayoutManager mLayout;//布局管理器
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityTestRvBinding binding = ActivityTestRvBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
mRv = binding.rv;
initData();//初始化数据
AAAdapter aaAdapter = new AAAdapter(R.layout.rv_item, list);
mRv.setAdapter(aaAdapter);
mLayout = new LinearLayoutManager(this);
mRv.setLayoutManager(mLayout);
}
private void initData() {
list = new ArrayList<>();
for (int i = 0; i <= 200; i++) {
list.add("Item " + i);
}
}
}
AAAdapter
public class AAAdapter extends BaseQuickAdapter {
public AAAdapter(int layoutResId, @Nullable List data) {
super(layoutResId, data);
}
public AAAdapter(@Nullable List data) {
super(data);
}
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
protected void convert(@NonNull AAA aaa, String s) {
aaa.mRvContainer.setBackgroundColor(mContext.getColor(R.color.colorPrimary));
aaa.mTextView.setText(s + "~");
}
}
AAA
public class AAA extends BaseViewHolder {
public FrameLayout mRvContainer;
public TextView mTextView;
public AAA(View view) {
super(view);
RvItemBinding rvItemBinding = RvItemBinding.bind(view);
mRvContainer = rvItemBinding.rvContainer;
mTextView = rvItemBinding.rvTextView;
}
}
这个看自己的喜好,愿意这么用就这么用,不愿意就算了
如果您希望在生成绑定类时忽略某个布局文件,请将 tools:viewBindingIgnore="true"
属性添加到相应布局文件的根视图中:
...
使用View bind的时候,需要注意布局的名称。因为生成的Bind类是根据布局来的,这里一定要注意!注意!注意!
上面这种情况,有两种解决方式
public class TestFragmentActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LayoutInflater layoutInflater = LayoutInflater.from(this);
ActivityTestFragmentBinding inflate = ActivityTestFragmentBinding.inflate(layoutInflater);
setContentView(inflate.getRoot());
}
}
与使用 findViewById
相比,视图绑定具有一些很显著的优点:
@Nullable
标记。这些差异意味着布局和代码之间的不兼容将会导致构建在编译时(而非运行时)失败。
视图绑定和数据绑定均会生成可用于直接引用视图的绑定类。但是,视图绑定旨在处理更简单的用例,与数据绑定相比,具有以下优势:
反过来,与数据绑定相比,视图绑定也具有以下限制:
考虑到这些因素,在某些情况下,最好在项目中同时使用视图绑定和数据绑定。您可以在需要高级功能的布局中使用数据绑定,而在不需要高级功能的布局中使用视图绑定。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SeNXGFT3-1598758462438)(E:\学习截图\新建文件夹\view_binds.png)]
layout
文件名去掉下划线,下划线首字母大写,最后加上Binding
。例如我有一个layout
文件叫activity_main.xml
,那对应生成的类文件为ActivityMainBinding.java
。build\generated\data_binding_base_class_source_out\debug\out\包名\databinding
下Android Studio3.6新特性之视图绑定ViewBinding使用指南
中文文档
简单封装
是时候拥抱ViewBinding了!!
秒懂Android开发之ViewBinding,一代神器ButterKnife的终结者
上面的这些链接可以看看。
如果有问题,欢迎指出!谢谢~~~