fragment_menu.xml 包含 <include layout="@layout/view_global_menu_header" />。view_global_menu_header.xml 內有 ImageView (id: ivMenuUserProfilePhoto)。onCreateView 中 view.findViewById(R.id.ivMenuUserProfilePhoto) 返回 **null**。<include> 會將子佈局**原地展開合併**到父佈局。若 Header 佈局根節點有 android:id,且未正確處理,會導致 ID 遮蔽或查找上下文錯誤。
<!-- fragment_menu.xml -->
<include
android:id="@+id/headerContainer" <!-- 給 include 一個新 ID -->
layout="@layout/view_global_menu_header" />
然後在 Fragment 中:
View headerView = view.findViewById(R.id.headerContainer);
ImageView ivAvatar = headerView.findViewById(R.id.ivMenuUserProfilePhoto); // ✅ 先找容器,再找子 View
<!-- view_global_menu_header.xml -->
<LinearLayout
xmlns:android="..."
android:layout_width="match_parent"
android:layout_height="wrap_content"
> <!-- 刪除 android:id="@+id/vGlobalMenuHeader" -->
<ImageView android:id="@+id/ivMenuUserProfilePhoto" ... />
</LinearLayout>
合併後 ID 直接屬於主佈局,view.findViewById(R.id.ivMenuUserProfilePhoto) 即可找到。
// build.gradle
android {
buildFeatures { viewBinding true }
}
// Fragment
private FragmentMenuBinding binding;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentMenuBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// 直接訪問,編譯期檢查,永不為 null(除非佈局真無此 ID)
binding.ivMenuUserProfilePhoto.setImageResource(R.drawable.avatar);
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null; // 防內存洩漏
}
| 痛點 | ViewBinding 解決 |
|---|---|
| findViewById 返回 null 運行時崩潰 | 編譯期綁定,**找不到 ID 直接編譯報錯** |
| include/merge 導致 ID 作用域混亂 | 自動處理巢狀佈局,生成對應的 Binding 類層級 |
| 類型轉換 (ImageView) view.findViewById() | **自動類型安全**,無需強轉 |
| Kotlin synthetic / ButterKnife 棄用 | 官方維護,無第三方依賴 |
R.id.ivMenuUserProfilePhoto 在 **同一模塊** 的 R.java 中存在。import com.example.R; 是否導入正確(非 android.R)。<include>,優先嘗試**方案一(賦予 include 新 ID)**。拋棄 findViewById,擁抱 **ViewBinding**,從源頭消滅「找不到 View」的煩惱。