面包屑导航的效果如下:
点击面包屑上标蓝的文字,可以直接返回到对应的级别。比如点击“第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);
......
}