Android 如何在Dialog中使用ViewPager
遇到的問題
最近有這樣一個普通的需求,viewpager(Fragment) + 一個tab,但是與往常不一樣的是,以前是在 Activity 中建立很正常,這次是在一個 Dialog,寫完一執行,出現了 :
java.lang.IllegalArgumentException:No view found for id 0x7f10013 for fragment
提示中不到 ViewPager 的 id,而且位置是在 Fragment 中,很奇怪
原因
我們建立 PagerAdapter 的時候,傳入了一個 FragmentManager,我們一般是傳入getSupportFragmentManager()
,是 Activity 的 FragmentManger。
如果傳入的是 Activity 的 FragmentManger,預設在 Activity 的佈局 xml 中尋找ViewPager,但是實際上它是在彈出的View裡定義的,並不是在 activity 的佈局裡,所以出現找不到資源id的情況
同理,如果我們是在 Fragment 裡使用 viewpager 巢狀 Fragment,建立 PagerAdapter 時需要使用getChildFragmentManager()
解決辦法
使用 DialogFragment 來代替 Dialog,同時建立 PagerAdapter 時需要使用 `getChildFragmentManager(),完美解決, 這裡貼一個 demo 程式碼
public class BuyerLiveGoodsDialog extends DialogFragment { private XTabLayout tabLayout; private ViewPager viewPager; private ImageView ivClose; private int height; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { height = (int) (Tools.getScreenHeight(getContext()) * 0.8); getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE); View view = inflater.inflate(R.layout.dialog_bottom_buyer_live_goods, container, false); tabLayout = view.findViewById(R.id.tab_layout); viewPager = view.findViewById(R.id.view_pager); ivClose = view.findViewById(R.id.iv_cancel); ivClose.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { dismiss(); } }); BuyerLiveGoodsPageAdapter pageAdapter = new BuyerLiveGoodsPageAdapter(getChildFragmentManager()); viewPager.setAdapter(pageAdapter); tabLayout.setupWithViewPager(viewPager); return view; } @Override public void onStart() { super.onStart(); Window window = getDialog().getWindow(); if (window != null) { // 一定要設定Background,如果不設定,window屬性設定無效 window.setBackgroundDrawable(new ColorDrawable(getResources().getColor(R.color.text_color))); DisplayMetrics dm = new DisplayMetrics(); if (getActivity() != null) { WindowManager windowManager = getActivity().getWindowManager(); if (windowManager != null) { windowManager.getDefaultDisplay().getMetrics(dm); WindowManager.LayoutParams params = window.getAttributes(); params.gravity = Gravity.BOTTOM; // 使用ViewGroup.LayoutParams,以便Dialog 寬度充滿整個螢幕 params.width = ViewGroup.LayoutParams.MATCH_PARENT; params.height = height; window.setAttributes(params); } } } } static class BuyerLiveGoodsPageAdapter extends FragmentPagerAdapter { List<Fragment> fragments = new ArrayList<>(); List<String> titles = new ArrayList<>(); public BuyerLiveGoodsPageAdapter(FragmentManager fm) { super(fm); // 一口價 BuyerLiveOnePriceFragment onePriceFragment = new BuyerLiveOnePriceFragment(); fragments.add(onePriceFragment); titles.add("一口價"); // 拍賣 BuyerLiveAuctionFragment auctionFragment = new BuyerLiveAuctionFragment(); fragments.add(auctionFragment); titles.add("拍賣"); notifyDataSetChanged(); } @Override public Fragment getItem(int position) { return fragments.get(position); } @Nullable @Override public CharSequence getPageTitle(int position) { return titles.get(position); } @Override public int getCount() { return fragments.size(); } } public static void showDialog(FragmentManager fragmentManager) { BuyerLiveGoodsDialog dialog = new BuyerLiveGoodsDialog(); dialog.show(fragmentManager, "tag"); } }
原始碼閱讀
首先看 FragmentPagerAdapter 的 instantiateItem 方法,把 ViewGroup 的 id 傳入:
@SuppressWarnings("ReferenceEquality") @Override public Object instantiateItem(ViewGroup container, int position) { // 省略部分程式碼 if (fragment != null) { if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment); mCurTransaction.attach(fragment); } else { fragment = getItem(position); if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment); // 這裡會往 FragmentManager 裡 add 一個 fragment,傳入了 container 的id,也就是 ViewGroup 的id mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId)); } return fragment; }
Fragment 是有狀態的,定義在 Fragment 裡:
static final int INITIALIZING = 0;// Not yet created. static final int CREATED = 1;// Created. static final int ACTIVITY_CREATED = 2; // The activity has finished its creation. static final int STOPPED = 3;// Fully created, not started. static final int STARTED = 4;// Created and started, not resumed. static final int RESUMED = 5;// Created started and resumed. // 儲存當前的狀態 int mState = INITIALIZING;
這些狀態是由 FragmentManager 來管理的,通過方法 moveToState:
void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive) { if (f.mState <= newState) { switch (f.mState) { // 如果是已經建立的狀態 case Fragment.CREATED: // 重點!!,這裡通過 mContainer 的 onFindViewById 去找 ViewGroup,這個 mContainer 是 FragmentManager 所有者的佈局!! container = (ViewGroup) mContainer.onFindViewById(f.mContainerId); if (container == null && !f.mRestored) { String resName; try { resName = f.getResources().getResourceName(f.mContainerId); } catch (NotFoundException e) { resName = "unknown"; } throwException(new IllegalArgumentException( "No view found for id 0x" + Integer.toHexString(f.mContainerId) + " (" + resName + ") for fragment " + f)); } } } } }
我們可以看到,container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);
這裡執行了一次onFindViewById
的操作,也就是在這裡報的IllegalArgumentException