前言
在Android開發,代碼里獲取View一般是使用findViewById()獲取目標布局文件里的指定View。但是這樣使用會有大量代碼重復工作並且有空指針危險。為了減少重復工作有很多大神都八仙過海各顯神通,但是這些神通多多少少都有缺點。
- 大名鼎鼎的黃油刀bufferknife,缺點增加了編譯速度(因為原理是它需要生成一份對應查找View的代碼),並且需要時刻更新最新版本否則AndroidStudio更新后可能會出現無法編譯的問題。(另外bufferknife與ViewBinding是沖突的)
- DataBinding,缺點更明顯,需要更多的xml編寫工作量,並且一不小心會延伸到一些邪惡的用法,那就是在xml寫邏輯判斷,甚至在xml增加一些業務功能。這對代碼維護是恐怖的,因為xml邏輯的可讀性可比純Java代碼差多了。並且如果混亂到2頭都寫邏輯判斷,維護起來十分痛苦。
- AndroidStudio的插件功能自動生成代碼,比如LayoutCreator,減少了工作量但是並沒有減少代碼的冗余,代碼看起來一樣是不簡潔的。
google在AndroidStudio 3.6 版本后推出了ViewBinding,一方面可以讓代碼更加簡潔並且提高編譯速度防止空指針。另一方面AndroidStudio是支持ViewBinding進行關聯互動的,所以讓你在Java代碼與xml之間的跳轉更方便。
前提條件
AndroidStudio 需要更新到3.6版本以上。
在build.gradle文件里增加下面的代碼,開啟viewBinding
android { //略... buildFeatures{ viewBinding = true } }
各處簡單的使用Demo
首先你需要知道一個關鍵點,在啟用ViewBinding后。每一個layout文件都會自動生成一份Java類。它會自動根據下划線進行駝峰命名。比如一個叫 activity_mian_demo.xml 的布局文件,它對應自動生成的類叫ActivityMianDemoBinding。這就意味着我們可以在任何需要導入布局的地方都使用ViewBinding。
Activity里:
public class MainActivity extends AppCompatActivity { private ActivityMianDemoBinding mBinding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBinding = ActivityMianDemoBinding.inflate(getLayoutInflater()); setContentView(mBinding.getRoot()); mBinding.btn1.setText("這是按鍵1"); mBinding.btn1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { } }); } }
Fragment里:
public class FragmentDemo extends Fragment { private FragmentDemoBinding mBinding; @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { mBinding = FragmentDemoBinding.inflate(getLayoutInflater()); return mBinding.getRoot(); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mBinding.textView.setText("textView"); } }
在Dialog里:
public class DemoDialog extends Dialog { private DialogDemoBinding mBinding; public DemoDialog(@NonNull Context context) { super(context); mBinding = DialogDemoBinding.inflate(getLayoutInflater()); setContentView(mBinding.getRoot()); mBinding.textView.setText("hello"); } }
Adapter里:
public class DemoAdapter extends RecyclerView.Adapter<DemoAdapter.ViewHolder> { @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new ViewHolder(ItemDemoBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { holder.binding.textView2.setText("demo"); } @Override public int getItemCount() { return 0; } public static class ViewHolder extends RecyclerView.ViewHolder { ItemDemoBinding binding; public ViewHolder(@NonNull ItemDemoBinding itemDemoBinding) { super(itemDemoBinding.getRoot()); this.binding = itemDemoBinding; } } }
xml屬性include
在之前其他人的博客里有說ViewBinding還不支持include屬性。但是在這篇博客發布的時間,最新版本AndroidStudio4.0的已經支持使用了include屬性。
使用include的布局:
<?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" tools:context=".MainActivity"> <include android:id="@+id/title" layout="@layout/title_layout" app:layout_constraintTop_toTopOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent"/> <Button android:id="@+id/btn_1" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="A1" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/title" /> </androidx.constraintlayout.widget.ConstraintLayout>
activity里:
public class MainActivity extends AppCompatActivity { private ActivityMianDemoBinding mBinding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBinding = ActivityMianDemoBinding.inflate(getLayoutInflater()); setContentView(mBinding.getRoot()); mBinding.title.titleContent.setText("內容"); mBinding.title.titleContent.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(MainActivity.this, "點擊", Toast.LENGTH_SHORT).show(); } }); } }
假如你有需求要分離include的布局,單獨使用這個include布局的類,可以參考如下實現:
public class MainActivity extends AppCompatActivity { private ActivityMianDemoBinding mBinding; private TitleLayoutBinding mTitleLayoutBinding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBinding = ActivityMianDemoBinding.inflate(getLayoutInflater()); setContentView(mBinding.getRoot()); mTitleLayoutBinding = TitleLayoutBinding.bind(mBinding.getRoot()); mTitleLayoutBinding.titleContent.setText("內容11"); } }
指定布局不被ViewBinding識別
有些時候,我們希望某些布局不需要被ViewBinding識別,我只需要在這個布局的根布局上添加tools:viewBindingIgnore="true"
<?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" tools:viewBindingIgnore="true">
ViewBInding泛型封裝
public class BaseActivity<T extends ViewBinding> extends AppCompatActivity { protected T binding; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Type superclass = getClass().getGenericSuperclass(); Class<?> aClass = (Class<?>) ((ParameterizedType) superclass).getActualTypeArguments()[0]; try { Method method = aClass.getDeclaredMethod("inflate", LayoutInflater.class); binding = (T) method.invoke(null, getLayoutInflater()); setContentView(binding.getRoot()); } catch (NoSuchMethodException | IllegalAccessException| InvocationTargetException e) { e.printStackTrace(); } }
End