Navigation的简单使用参参考:https://blog.csdn.net/hongxue8888/article/details/103672351
自定义注解处理器参考:https://blog.csdn.net/hongxue8888/article/details/97228827
AS新建项目,选择“Bottom Navigation Activity”。
新建两个Java Library:lib_nav_annotation
和 lib_nav_compiler
在 lib_nav_compiler的build.gradle中添加依赖:
api 'com.alibaba:fastjson:1.2.59'//在其他module中会使用,所以依赖类型为api
implementation 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
implementation project(path: ':lib_nav_annotation')
在app的build.gradle的build.gradle中添加:
android {
...
compileOptions {
sourceCompatibility "1.8"
targetCompatibility "1.8"
}
}
dependencies {
...
implementation project(path: ':lib_nav_annotation')
implementation project(path: ':lib_nav_compiler')//需要使用lib_nav_compiler中的fastjson
annotationProcessor project(path: ':lib_nav_compiler')
}
在lib_nav_annotation中定义两个注解:
@Target(ElementType.TYPE)
public @interface ActivityDestination {
String pageUrl();
boolean needLogin() default false;//有些页面需要登录才能进入,判断是否需要登录
boolean asStarter() default false;//是否是startDestination
}
@Target(ElementType.TYPE)
public @interface FragmentDestination {
String pageUrl();
boolean needLogin() default false;
boolean asStarter() default false;
}
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({"com.mooc.libnavannotation.FragmentDestination", "com.mooc.libnavannotation.ActivityDestination"})
public class NavProcessor extends AbstractProcessor {
private Messager messager;
private Filer filer;
private static final String OUTPUT_FILE_NAME = "destination.json";
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager();
filer = processingEnv.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> fragmentElements = roundEnv.getElementsAnnotatedWith(FragmentDestination.class);
Set<? extends Element> activityElements = roundEnv.getElementsAnnotatedWith(ActivityDestination.class);
if (!fragmentElements.isEmpty() || !activityElements.isEmpty()) {
HashMap<String, JSONObject> destMap = new HashMap<>();
handleDestination(fragmentElements, FragmentDestination.class, destMap);
handleDestination(activityElements, ActivityDestination.class, destMap);
//app/src/main/assets
FileOutputStream fos = null;
OutputStreamWriter writer = null;
try {
FileObject resource = filer.createResource(StandardLocation.CLASS_OUTPUT, "", OUTPUT_FILE_NAME);
String resourcePath = resource.toUri().getPath();
messager.printMessage(Diagnostic.Kind.NOTE, "resourcePath:" + resourcePath);
String appPath = resourcePath.substring(0, resourcePath.indexOf("app") + 4);
String assetsPath = appPath + "src/main/assets/";
File file = new File(assetsPath);
if (!file.exists()) {
file.mkdirs();
}
File outPutFile = new File(file, OUTPUT_FILE_NAME);
if (outPutFile.exists()) {
outPutFile.delete();
}
outPutFile.createNewFile();
String content = JSON.toJSONString(destMap);
fos = new FileOutputStream(outPutFile);
writer = new OutputStreamWriter(fos, "UTF-8");
writer.write(content);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return true;
}
private void handleDestination(Set<? extends Element> elements, Class<? extends Annotation> annotationClaz, HashMap<String, JSONObject> destMap) {
for (Element element : elements) {
TypeElement typeElement = (TypeElement) element;
String pageUrl = null;
String clazName = typeElement.getQualifiedName().toString();
int id = Math.abs(clazName.hashCode());
boolean needLogin = false;
boolean asStarter = false;
boolean isFragment = false;
Annotation annotation = typeElement.getAnnotation(annotationClaz);
if (annotation instanceof FragmentDestination) {
FragmentDestination dest = (FragmentDestination) annotation;
pageUrl = dest.pageUrl();
asStarter = dest.asStarter();
needLogin = dest.needLogin();
isFragment = true;
} else if (annotation instanceof ActivityDestination) {
ActivityDestination dest = (ActivityDestination) annotation;
pageUrl = dest.pageUrl();
asStarter = dest.asStarter();
needLogin = dest.needLogin();
isFragment = false;
}
if (destMap.containsKey(pageUrl)) {
messager.printMessage(Diagnostic.Kind.ERROR, "不同的页面不允许使用相同的pageUrl:" + clazName);
} else {
JSONObject object = new JSONObject();
object.put("id", id);
object.put("needLogin", needLogin);
object.put("asStarter", asStarter);
object.put("pageUrl", pageUrl);
object.put("className", clazName);
object.put("isFragment", isFragment);
destMap.put(pageUrl, object);
}
}
}
}
@FragmentDestination(pageUrl = "main/tabs/home", asStarter = true)
public class HomeFragment extends Fragment {
...
}
@FragmentDestination(pageUrl = "main/tabs/dash", asStarter = false)
public class DashboardFragment extends Fragment {
...
}
@FragmentDestination(pageUrl = "main/tabs/notification", asStarter = false)
public class NotificationsFragment extends Fragment {
...
}
Build -->Make Project后,在assets目录下生成了destination.json文件
{
"activeColor": "#333333",
"inActiveColor": "#666666",
"tabs": [
{
"size": 24,
"enable": true,
"index": 0,
"pageUrl": "main/tabs/home",
"title": "首页"
},
{
"size": 24,
"enable": true,
"index": 1,
"pageUrl": "main/tabs/sofa",
"title": "沙发"
},
{
"size": 40,
"enable": true,
"index": 2,
"tintColor": "#ff678f",
"pageUrl": "main/tabs/publish",
"title": ""
},
{
"size": 24,
"enable": true,
"index": 3,
"pageUrl": "main/tabs/find",
"title": "发现"
},
{
"size": 24,
"enable": true,
"index": 4,
"pageUrl": "main/tabs/my",
"title": "我的"
}
]
}
public class AppConfig {
private static HashMap<String, Destination> sDestConfig;
public static HashMap<String, Destination> getDestConfig() {
if (sDestConfig == null) {
String content = parseFile("destination.json");
sDestConfig = JSON.parseObject(content, new TypeReference<HashMap<String, Destination>>() {
});
}
return sDestConfig;
}
private static String parseFile(String fileName) {
AssetManager assets = AppGlobals.getApplication().getAssets();
InputStream is = null;
BufferedReader br = null;
StringBuilder builder = new StringBuilder();
try {
is = assets.open(fileName);
br = new BufferedReader(new InputStreamReader(is));
String line = null;
while ((line = br.readLine()) != null) {
builder.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
if (br != null) {
br.close();
}
} catch (Exception e) {
}
}
return builder.toString();
}
}
public class NavGraphBuilder {
public static void build(NavController controller) {
NavigatorProvider provider = controller.getNavigatorProvider();
NavGraph navGraph = new NavGraph(new NavGraphNavigator(provider));
FragmentNavigator fragmentNavigator = provider.getNavigator(FragmentNavigator.class);
ActivityNavigator activityNavigator = provider.getNavigator(ActivityNavigator.class);
HashMap<String, Destination> destConfig = AppConfig.getDestConfig();
for (Destination value : destConfig.values()) {
if (value.isFragment) {
FragmentNavigator.Destination destination = fragmentNavigator.createDestination();
destination.setId(value.id);
destination.setClassName(value.className);
destination.addDeepLink(value.pageUrl);
navGraph.addDestination(destination);
} else {
ActivityNavigator.Destination destination = activityNavigator.createDestination();
destination.setId(value.id);
destination.setComponentName(new ComponentName(AppGlobals.getApplication().getPackageName(), value.className));
destination.addDeepLink(value.pageUrl);
navGraph.addDestination(destination);
}
if (value.asStarter) {
navGraph.setStartDestination(value.id);
}
}
controller.setGraph(navGraph);
}
}
public class AppBottomBar extends BottomNavigationView {
private static int[] sIcons = new int[]{R.drawable.icon_tab_home, R.drawable.icon_tab_sofa, R.drawable.icon_tab_publish, R.drawable.icon_tab_find, R.drawable.icon_tab_mine};
private final BottomBar config;
public AppBottomBar(Context context) {
this(context, null);
}
public AppBottomBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@SuppressLint("RestrictedApi")
public AppBottomBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
config = AppConfig.getBottomBarConfig();
int[][] state = new int[2][];
state[0] = new int[]{android.R.attr.state_selected};
state[1] = new int[]{};
int[] colors = new int[]{Color.parseColor(config.activeColor), Color.parseColor(config.inActiveColor)};
ColorStateList stateList = new ColorStateList(state, colors);
setItemTextColor(stateList);
setItemIconTintList(stateList);
//LABEL_VISIBILITY_LABELED:设置按钮的文本为一直显示模式
//LABEL_VISIBILITY_AUTO:当按钮个数小于三个时一直显示,或者当按钮个数大于3个且小于5个时,被选中的那个按钮文本才会显示
//LABEL_VISIBILITY_SELECTED:只有被选中的那个按钮的文本才会显示
//LABEL_VISIBILITY_UNLABELED:所有的按钮文本都不显示
setLabelVisibilityMode(LabelVisibilityMode.LABEL_VISIBILITY_LABELED);
List<BottomBar.Tab> tabs = config.tabs;
for (BottomBar.Tab tab : tabs) {
if (!tab.enable) {
continue;
}
int itemId = getItemId(tab.pageUrl);
if (itemId < 0) {
continue;
}
MenuItem menuItem = getMenu().add(0, itemId, tab.index, tab.title);
menuItem.setIcon(sIcons[tab.index]);
}
//此处给按钮icon设置大小
for (BottomBar.Tab tab : config.tabs) {
int iconSize = dp2Px(tab.size);
BottomNavigationMenuView menuView = (BottomNavigationMenuView) getChildAt(0);
BottomNavigationItemView itemView = (BottomNavigationItemView) menuView.getChildAt(tab.index);
itemView.setIconSize(iconSize);
if (TextUtils.isEmpty(tab.title)) {
int tintColor = TextUtils.isEmpty(tab.tintColor) ? Color.parseColor("#ff678f") : Color.parseColor(tab.tintColor);
itemView.setIconTintList(ColorStateList.valueOf(tintColor));
//禁止掉点按时 上下浮动的效果
itemView.setShifting(false);
}
}
}
private int dp2Px(int dpValue) {
DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
return (int) (metrics.density * dpValue + 0.5f);
}
private int getItemId(String pageUrl) {
Destination destination = AppConfig.getDestConfig().get(pageUrl);
if (destination == null)
return -1;
return destination.id;
}
}
使用自定义的BottomNavigationView
<com.hongx.ppjoke.view.AppBottomBar
android:id="@+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
/**
* 定制的Fragment导航器,替换ft.replace(mContainerId, frag);为 hide()/show()
*/
@Navigator.Name("fixfragment")
public class FixFragmentNavigator extends FragmentNavigator {
private static final String TAG = "FixFragmentNavigator";
private Context mContext;
private FragmentManager mManager;
private int mContainerId;
public FixFragmentNavigator(@NonNull Context context, @NonNull FragmentManager manager, int containerId) {
super(context, manager, containerId);
mContext = context;
mManager = manager;
mContainerId = containerId;
}
@Nullable
@Override
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
if (mManager.isStateSaved()) {
Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"
+ " saved its state");
return null;
}
String className = destination.getClassName();
if (className.charAt(0) == '.') {
className = mContext.getPackageName() + className;
}
//final Fragment frag = instantiateFragment(mContext, mManager,
// className, args);
//frag.setArguments(args);
final FragmentTransaction ft = mManager.beginTransaction();
int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1;
int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1;
int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1;
int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1;
if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
enterAnim = enterAnim != -1 ? enterAnim : 0;
exitAnim = exitAnim != -1 ? exitAnim : 0;
popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
}
Fragment fragment = mManager.getPrimaryNavigationFragment();
if (fragment != null) {
ft.hide(fragment);
}
Fragment frag = null;
String tag = String.valueOf(destination.getId());
frag = mManager.findFragmentByTag(tag);
if (frag != null) {
ft.show(frag);
} else {
frag = instantiateFragment(mContext, mManager, className, args);
frag.setArguments(args);
ft.add(mContainerId, frag, tag);
}
//ft.replace(mContainerId, frag);
ft.setPrimaryNavigationFragment(frag);
final @IdRes int destId = destination.getId();
ArrayDeque<Integer> mBackStack = null;
try {
Field field = FragmentNavigator.class.getDeclaredField("mBackStack");
field.setAccessible(true);
mBackStack = (ArrayDeque<Integer>) field.get(this);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
final boolean initialNavigation = mBackStack.isEmpty();
// TODO Build first class singleTop behavior for fragments
final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
&& navOptions.shouldLaunchSingleTop()
&& mBackStack.peekLast() == destId;
boolean isAdded;
if (initialNavigation) {
isAdded = true;
} else if (isSingleTopReplacement) {
// Single Top means we only want one instance on the back stack
if (mBackStack.size() > 1) {
// If the Fragment to be replaced is on the FragmentManager's
// back stack, a simple replace() isn't enough so we
// remove it from the back stack and put our replacement
// on the back stack in its place
mManager.popBackStack(
generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
FragmentManager.POP_BACK_STACK_INCLUSIVE);
ft.addToBackStack(generateBackStackName(mBackStack.size(), destId));
}
isAdded = false;
} else {
ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
isAdded = true;
}
if (navigatorExtras instanceof Extras) {
Extras extras = (Extras) navigatorExtras;
for (Map.Entry<View, String> sharedElement : extras.getSharedElements().entrySet()) {
ft.addSharedElement(sharedElement.getKey(), sharedElement.getValue());
}
}
ft.setReorderingAllowed(true);
ft.commit();
// The commit succeeded, update our view of the world
if (isAdded) {
mBackStack.add(destId);
return destination;
} else {
return null;
}
}
private String generateBackStackName(int backStackindex, int destid) {
return backStackindex + "-" + destid;
}
}
修改NavGraphBuilder:
public class NavGraphBuilder {
public static void build(FragmentActivity activity, NavController controller, int containerId) {
NavigatorProvider provider = controller.getNavigatorProvider();
NavGraph navGraph = new NavGraph(new NavGraphNavigator(provider));
//FragmentNavigator fragmentNavigator = provider.getNavigator(FragmentNavigator.class);
FixFragmentNavigator fragmentNavigator = new FixFragmentNavigator(activity, activity.getSupportFragmentManager(), containerId);
provider.addNavigator(fragmentNavigator);
ActivityNavigator activityNavigator = provider.getNavigator(ActivityNavigator.class);
HashMap<String, Destination> destConfig = AppConfig.getDestConfig();
Iterator<Destination> iterator = destConfig.values().iterator();
while (iterator.hasNext()) {
Destination node = iterator.next();
if (node.isFragment) {
FragmentNavigator.Destination destination = fragmentNavigator.createDestination();
destination.setId(node.id);
destination.setClassName(node.className);
destination.addDeepLink(node.pageUrl);
navGraph.addDestination(destination);
} else {
ActivityNavigator.Destination destination = activityNavigator.createDestination();
destination.setId(node.id);
destination.setComponentName(new ComponentName(AppGlobals.getApplication().getPackageName(), node.className));
destination.addDeepLink(node.pageUrl);
navGraph.addDestination(destination);
}
if (node.asStarter) {
navGraph.setStartDestination(node.id);
}
}
controller.setGraph(navGraph);
}
}
public class MainActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener {
private NavController navController;
private AppBottomBar navView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
navView = findViewById(R.id.nav_view);
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
navController = NavHostFragment.findNavController(fragment);
NavGraphBuilder.build(this, navController, fragment.getId());
navView.setOnNavigationItemSelectedListener(this);
}
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
navController.navigate(menuItem.getItemId());
return !TextUtils.isEmpty(menuItem.getTitle());
}
}
Github:
https://github.com/345166018/HxJetpack/tree/master/hx_ppjoke_navigation