常用的主界面框架及问题

2016/06/29 Android

一、FragmentTabHost+Fragment

主界面布局

	<?xml version="1.0" encoding="utf-8"?>
	<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	              android:layout_width="match_parent"
	              android:layout_height="match_parent"
	              android:orientation="vertical">
	              
	<!--Fragment内容-->
	    <FrameLayout
	        android:id="@+id/fragment_container"
	        android:layout_width="match_parent"
	        android:layout_height="0dp"
	        android:layout_weight="1">
	    </FrameLayout>
	
	<!--分割线-->
	    <TextView
	        android:layout_width="match_parent"
	        android:layout_height="1dp"
	        android:background="#e3e3e3"/>
	
	<!--Tab标签栏-->
	    <android.support.v4.app.FragmentTabHost
	        android:id="@+id/tabhost"
	        android:layout_width="match_parent"
	        android:layout_height="wrap_content"
	        android:layout_gravity="bottom">
	    </android.support.v4.app.FragmentTabHost>
	</LinearLayout>

主界面Activity

    private static Class[] mHomeFragments = {ItemFragment2.class, ItemFragment.class, ItemFragment2.class, ItemFragment.class};

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home_pager);
        ButterKnife.bind(this);
        for (; i < 4; i++) {
            mFragmentTabHost.setup(this, getSupportFragmentManager(), R.id.tab_content);
            <!--setIndicator定制TabView,可以自己添加点击事件,覆盖默认的点击事件,注意newTabSpec("" + i)标签要设置的不一样,否则不转换-->
            TabHost.TabSpec tabSpec = mFragmentTabHost.newTabSpec("" + i).setIndicator(getCustomedTabView(i));
            mFragmentTabHost.addTab(tabSpec, mHomeFragments[i], null);
        }
    }

CustomedTabView 布局

Fragment的实现

需要缓存View,否则每次都会调用onCreateView

	public class ItemFragment2 extends Fragment {
	
	    @Nullable
	    @Override
	    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
	        return inflater.inflate(R.layout.fragment_2, container, false);
	//        每次都新建View但是不新建ItemFragment2比较烦人
	    }	

可能问题

使用FragmetnTabHost虽然不是每次都是新建Fragment,但是每次都会执行 onCreateView,如果想要不重复执行,需要自己添加逻辑,参考方法如下:

public class ItemFragment2 extends Fragment {

    private SoftReference<View> mRootView;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if (mRootView == null || mRootView.get() == null) {
            mRootView = new SoftReference<>(inflater.inflate(R.layout.fragment_2, container, false));
        } else {
            if (mRootView.get().getParent() != null) {
                ((ViewGroup) mRootView.get().getParent()).removeView(mRootView.get());
            }
        }
        return mRootView.get();
    }

不过这里要注意,一定要采取这种写法inflater.inflate(R.layout.fragment_2, container, false) ,不然有问题

注意事项 FragmentMangerImpl里mAdded跟mActive的区别

mAdded 是被添加到Container中去的,而mActive是全部参与的Fragment集合,mAdded是活着的,是被Add到container里面的,mActive却可能死掉,对于FragmentTabHost,只有一个Added,原因如下,change的时候,先detach再add或者attach,

    void makeInactive(Fragment f) {
        if (f.mIndex < 0) {
            return;
        }
        
        if (DEBUG) Log.v(TAG, "Freeing fragment index " + f);
        <!--这里会置空-->
        mActive.set(f.mIndex, null);
        if (mAvailIndices == null) {
            mAvailIndices = new ArrayList<Integer>();
        }

void makeActive(Fragment f) {
    if (f.mIndex >= 0) {
        return;
    }
    
    <!--如果有空位置,就用空位置,如果没有,就新建,一般都是用的,死一个,留一个坑-->
    if (mAvailIndices == null || mAvailIndices.size() <= 0) {
        if (mActive == null) {
            mActive = new ArrayList<Fragment>();
        }
        f.setIndex(mActive.size(), mParent);
        mActive.add(f);
        
    } else {
        f.setIndex(mAvailIndices.remove(mAvailIndices.size()-1), mParent);
        mActive.set(f.mIndex, f);
    }
    
    private FragmentTransaction doTabChanged(String tabId, FragmentTransaction ft) {
        TabInfo newTab = null;
        for (int i=0; i<mTabs.size(); i++) {
            TabInfo tab = mTabs.get(i);
            if (tab.tag.equals(tabId)) {
                newTab = tab;
            }
        }
            if (mLastTab != null) {
                if (mLastTab.fragment != null) {
                    ft.detach(mLastTab.fragment);
                }
            }
            if (newTab != null) {
                if (newTab.fragment == null) {
                    newTab.fragment = Fragment.instantiate(mContext,
                            newTab.clss.getName(), newTab.args);
                    ft.add(mContainerId, newTab.fragment, newTab.tag);
                } else {
                    ft.attach(newTab.fragment);
                }
            }


    public void detachFragment(Fragment fragment, int transition, int transitionStyle) {
    if (DEBUG) Log.v(TAG, "detach: " + fragment);
    if (!fragment.mDetached) {
        fragment.mDetached = true;
        if (fragment.mAdded) {
            // We are not already in back stack, so need to remove the fragment.
            if (mAdded != null) {
                if (DEBUG) Log.v(TAG, "remove from detach: " + fragment);
                mAdded.remove(fragment);
            }
            if (fragment.mHasMenu && fragment.mMenuVisible) {
                mNeedMenuInvalidate = true;
            }
            fragment.mAdded = false;
            moveToState(fragment, Fragment.CREATED, transition, transitionStyle, false);
        }
    }
}

滑动的时候,先remove再add,因为可以知道删除那个,但是,通过setCurrent那种,如果不是临近的,就先add,再remove,因为可能都要remove ,如果size足够大,就不要再新建可用槽了。remove才会

缺点

  • 不能滑动切换Page
  • 么有预加载,切换后才会加载新的页面
  • 自己处理onCreateView的重复创建逻辑

重建的问题 onRestoreInstanceState在onAttachedToWindow函数前执行,先restore设置好,再处理正确。

@Override
protected Parcelable onSaveInstanceState() {
    Parcelable superState = super.onSaveInstanceState();
    SavedState ss = new SavedState(superState);
    ss.curTab = getCurrentTabTag();
    return ss;
}

@Override
protected void onRestoreInstanceState(Parcelable state) {
    SavedState ss = (SavedState)state;
    super.onRestoreInstanceState(ss.getSuperState());
    setCurrentTabByTag(ss.curTab);
}

二、FragmentTabHost+ViewPager+Fragment

主界面布局

其实布局很简单,就是ViewPager+FragmentTabHost

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">
    </android.support.v4.view.ViewPager>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#e3e3e3"/>

    <android.support.v4.app.FragmentTabHost
        android:id="@+id/tabhost"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom">
    </android.support.v4.app.FragmentTabHost>
</LinearLayout>

Activity实现

 private static Class[] mHomeFragments = {ItemFragment2.class, ItemFragment.class, ItemFragment2.class, ItemFragment.class};

    private static ArrayList<String> mTablist = new ArrayList<>();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home_tabhosst_viewpager);
        ButterKnife.bind(this);
        for (; i < 4; i++) {
            mFragmentTabHost.setup(this, getSupportFragmentManager());
            TabHost.TabSpec tabSpec = mFragmentTabHost.newTabSpec("" + i).setIndicator(getCustomedTabView(i));
            mTablist.add("" + i);
			//   这里需要,因为会有重建问题,后台杀死后,FragmentTabHost在OnAttachToWindow的时候回调用,但是仍然会调用Adapter的getItem
            mFragmentTabHost.addTab(tabSpec, mHomeFragments[i], null);
            mFragmentTabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
                @Override
                public void onTabChanged(String s) {
                    mViewPager.setCurrentItem(mTablist.indexOf(s));
                }
            });

        }
        mViewPager.setAdapter(new HomeAdapter(getSupportFragmentManager()));
        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

            }

            @Override
            public void onPageSelected(int position) {
                mFragmentTabHost.setCurrentTab(position);
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });
		// 预加载开启
        mViewPager.setOffscreenPageLimit(4);

Fragment的实现

这里不会重新调用onCreateView,所以可以不缓存View

		public class ItemFragment2 extends Fragment {
	
	    @Nullable
	    @Override
	    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
	        return inflater.inflate(R.layout.fragment_2, container, false);
	    }	

缺点

  • 对于多层内嵌支持不好,需要自定义ViewPager处理嵌套滑动
  • 可能会多生成额外外的Fragment ,因为默认attachtoWindow的时候,会生成相应的class 解决方式是自己定义,不做dotabchange类无意义的处理。

自定义FragmentTabHost解决生成额外的Fragment问题

删除doTabChange 删除onAttachToWindow

public class CustomedFragmentTabHost extends TabHost
        implements TabHost.OnTabChangeListener {
        
        。。。。
            @Override
    public void onTabChanged(String tabId) {

        if (mOnTabChangeListener != null) {
            mOnTabChangeListener.onTabChanged(tabId);
        }
    }

其他ViewPager问题

	 void dataSetChanged() {
	        // This method only gets called if our observer is attached, so mAdapter is non-null.
	
	        final int adapterCount = mAdapter.getCount();
	        mExpectedAdapterCount = adapterCount;
	        boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&
	                mItems.size() < adapterCount;
	        int newCurrItem = mCurItem;
	
	        boolean isUpdating = false;
	        for (int i = 0; i < mItems.size(); i++) {
	            final ItemInfo ii = mItems.get(i);
	            final int newPos = mAdapter.getItemPosition(ii.object);
	
	            if (newPos == PagerAdapter.POSITION_UNCHANGED) {
	                continue;
	            }
	
	            if (newPos == PagerAdapter.POSITION_NONE) {
	                mItems.remove(i);
	                i--;
	
	                if (!isUpdating) {
	                    mAdapter.startUpdate(this);
	                    isUpdating = true;
	                }
	
	                mAdapter.destroyItem(this, ii.position, ii.object);
	                needPopulate = true;
	
	                if (mCurItem == ii.position) {
	                    // Keep the current item in the valid range
	                    newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
	                    needPopulate = true;
	                }
	                continue;
	            }
	
	            if (ii.position != newPos) {
	                if (ii.position == mCurItem) {
	                    // Our current item changed position. Follow it.
	                    newCurrItem = newPos;
	                }
	
	                ii.position = newPos;
	                needPopulate = true;
	            }
	        }
	
	        if (isUpdating) {
	            mAdapter.finishUpdate(this);
	        }
	
	        Collections.sort(mItems, COMPARATOR);
	
	        if (needPopulate) {
	            // Reset our known page widths; populate will recompute them.
	            final int childCount = getChildCount();
	            for (int i = 0; i < childCount; i++) {
	                final View child = getChildAt(i);
	                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
	                if (!lp.isDecor) {
	                    lp.widthFactor = 0.f;
	                }
	            }
	
	            setCurrentItemInternal(newCurrItem, false, true);
	            requestLayout();
	        }
	    }

三、ViewPager+SlidingTabLayout

FragmentTabHost必须在底部一排显示完全,否则会挤在一起,不支持滑动,此时可以用SlidingTabLayout代替

四、FragmentTabHost+Fragment+ViewPager+TabLayout双重嵌套

FragmentPagerAdapter与FragmentStatePagerAdapter区别

FragmentPagerAdapter的destroyItem采用detach而后者采用remove

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
            + " v=" + ((Fragment)object).getView());
    mCurTransaction.detach((Fragment)object);
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    Fragment fragment = (Fragment) object;

    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
            + " v=" + ((Fragment)object).getView());
    while (mSavedState.size() <= position) {
        mSavedState.add(null);
    }
    mSavedState.set(position, fragment.isAdded()
            ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
    mFragments.set(position, null);

    mCurTransaction.remove(fragment);
}

所以Fragmentpageradapter中viewpager id有用的

        mCurTransaction.add(container.getId(), fragment,
                makeFragmentName(container.getId(), itemId));
                
                
                    @Override
public Object instantiateItem(ViewGroup container, int position) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }

    final long itemId = getItemId(position);

    // Do we already have this fragment?
    String name = makeFragmentName(container.getId(), itemId);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    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);
        mCurTransaction.add(container.getId(), fragment,
                makeFragmentName(container.getId(), itemId));

Search

    Table of Contents