面包屑導航的效果如下:

ZmlsZTovLy9DOlxVc2Vyc1x4dWRkXEFwcERhdGFcUm9hbWluZ1xUZW5jZW50XFVzZXJzXDEyMDgwMzY2M1xRUVxXaW5UZW1wXFJpY2hPbGVcSSR+RTVQOE0wX18oW1pIRUtPSVtCMUUucG5n

cabc0412da49cb3e6a334169a1af7e09.png

點擊面包屑上標藍的文字,可以直接返回到對應的級別。比如點擊“第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);

......

}

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐