面包屑導航的效果如下:
點擊面包屑上標藍的文字,可以直接返回到對應的級別。比如點擊“第2層”,就能直接返回到第2層下的目錄,而無須依次點返回。
面包屑導航的原理是通過FragmentTransaction去實現的,下面簡單介紹一下實現原理。
頁面分為兩部分:頂部的自定義導航欄view和下方的ListFragment,點擊fragment的item會進入下一層ListFragment。
自定義view我們暫且稱為CrumbView,它需要繼承HorizontalScrollView以擁有可以左右滾動的效果,同時要實現FragmentManager的OnBackStackChangedListener接口,這樣當fragment棧發生變化時,CrumbView就能收到通知並更新導航欄文字。
每次FragmentTransaction在執行replace/add fragment的操作時,需要調用setBreadCrumbTitle()方法設置面包屑的標題,同時需要將每次的操作都addToBackStack(),這樣在CrumbView中才能調用FragmentManager的getBackStackEntryAt()方法得到這個操作並取標題並顯示在標題欄上。
關鍵代碼如下:
CrumbView中收到OnBackStackChangedListener的回調時更新面包屑的代碼:
private void updateCrumbs() {
// 嵌套的fragment數量
int numFrags = mFragmentManager.getBackStackEntryCount();
// 面包屑的數量
int numCrumbs = mContainer.getChildCount();
for(int i = 0; i < numFrags; i++){
final FragmentManager.BackStackEntry backStackEntry = mFragmentManager.getBackStackEntryAt(i);
if(i < numCrumbs){
View view = mContainer.getChildAt(i);
Object tag = view.getTag();
if(tag != backStackEntry){
for(int j = i; j < numCrumbs; j++){
mContainer.removeViewAt(i);
}
numCrumbs = i;
}
}
if(i >= numCrumbs){
View itemView = LayoutInflater.from(getContext()).inflate(R.layout.crumb_item_layout, null);
TextView tv = (TextView) itemView.findViewById(R.id.crumb_name);
tv.setText(backStackEntry.getBreadCrumbTitle());
itemView.setTag(backStackEntry);
itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
FragmentManager.BackStackEntry bse;
if (v.getTag() instanceof FragmentManager.BackStackEntry) {
bse = (FragmentManager.BackStackEntry) v.getTag();
mFragmentManager.popBackStack(bse.getId(), 0);
} else {
//全部回退
int count = mFragmentManager.getBackStackEntryCount();
if (count > 0) {
bse = mFragmentManager.getBackStackEntryAt(0);
mFragmentManager.popBackStack(bse.getId(), 0);
}
}
}
});
mContainer.addView(itemView);
}
}
numCrumbs = mContainer.getChildCount();
while(numCrumbs > numFrags){
mContainer.removeViewAt(numCrumbs - 1);
numCrumbs--;
}
//調整可見性
for (int i = 0; i < numCrumbs; i++) {
final View child = mContainer.getChildAt(i);
// 高亮
highLightIndex(child, !(i < numCrumbs - 1));
}
// 滑動到最后一個
post(new Runnable() {
@Override
public void run() {
fullScroll(ScrollView.FOCUS_RIGHT);
}
});
}
點擊fragment的item進入下一層ListFragment時的代碼:
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
FragmentTransaction ft = mFragmentManager.beginTransaction();
ft.setBreadCrumbTitle(getString(R.string.crumb_title, mLevel + 1));
ft.replace(R.id.frag_container, MyFragment.getInstance(mLevel + 1));
ft.addToBackStack(null);
ft.commitAllowingStateLoss();
}
完整的demo已上傳至gitHub:
https://github.com/whsdu929/CrumbView.git
====================================================================================
有朋友評論說當fragment回退完后,會顯示空白的activity,解決方案是不要把第一個fragment addToBackStack:
activity里首次加載fragment時注釋這一行
//ft.addToBackStack(null);
然后在CrumbView里手動加上第一個fragment的標題
public void setActivity(final FragmentActivity activity, String rootTitle){
//手動把root fragment的標題加上
View itemView = LayoutInflater.from(getContext()).inflate(R.layout.crumb_item_layout, null);
final TextView tv = (TextView) itemView.findViewById(R.id.crumb_name);
tv.setText(rootTitle);
tv.setTextColor(LIGHT_COLOR);
itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//全部回退
FragmentManager.BackStackEntry bse = mFragmentManager.getBackStackEntryAt(0);
mFragmentManager.popBackStack(bse.getId(), 0);
tv.setTextColor(LIGHT_COLOR);
}
});
LinearLayout rootContainer = new LinearLayout(getContext());
......
rootContainer.addView(itemView);
rootContainer.addView(mContainer);
addView(rootContainer);
......
}