如何复制 YouTube 的应用导航逻辑
How to copy YouTube's app navigation logic
我想在我的应用程序导航逻辑中实现,就像在 Youtube 应用程序中一样。 (BottomNavigationView + 片段管理)。我想要这个,因为这些片段很重,所以我希望它们被延迟初始化然后存储在backstack中,我感觉YouTube就是这样做的。我已经实现了 BottomNagivationView,但我在片段管理方面遇到了问题。
我的代码:
bottomNavigationView.setOnTabSelectedListener { position, _ ->
setFragment(OnlinePageFragment.Page.values()[position])
}
其中 Pages 是枚举
enum class Page(index: Int, val klass: Class<*>) {
ONE(0, OnePageFragment::class.java),
TWO(1, TwoPageFragment::class.java),
THREE(2, ThreePageFragment::class.java)
}
这是我的 setFragment 函数
fun setFragment(page: OnlinePageFragment.Page) {
var fragment: Fragment? = supportFragmentManager.findFragmentByTag(page.klass.name)
val tag = page.klass.name
if (fragment == null)
fragment = OnlinePageFragment.newInstance(page, null)
val ft = supportFragmentManager.beginTransaction()
with(ft) {
replace(R.id.fragmentContainer, fragment, tag)
addToBackStack(tag)
commit()
}
}
override fun onBackPressed() {
if (supportFragmentManager.backStackEntryCount == 1) finish()
else super.onBackPressed()
}
它可以运行,但不如 YouTube 应用程序好。 YouTube 应用程序有一些神奇的行为,即每个片段只保留一个事务,而我的应用程序允许创建 "infinite" 个事务的后台堆栈。您知道它在 YouTube 应用中的工作原理吗?
我想这里最好的方法是使用ViewPager
来存储根片段。导航栏将更改 ViewPager
个页面。
根片段将在其内部存储内容片段ChildFragmentManagers
并实现内容片段之间的导航逻辑。
此外,您还需要实现从 activity 到根片段的委派 backpress 事件的逻辑,以在其后台执行导航。
UPD #1
确保根片段没有在 ViewPager 中被破坏,使用 setOffscreenPageLimit()
UPD #2
如果您不想使用 ViewPager,可以使用此 中的代码来管理根片段。
无需使用查看寻呼机即可管理它。
我已经实施请检查这个。 https://github.com/sandeshsk/BackStackFragmentRedirectsToHome
有问题请及时更新
这是一个分配片段的方法
public void addFragment(FragmentManager fragmentManager,
Fragment fragment,
int containerId,boolean isFromHome){
fragmentManager.popBackStack(null,FragmentManager.POP_BACK_STACK_INCLUSIVE);
FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
if(isFromHome){
fragmentTransaction.replace(containerId,fragment);
}else{
fragmentTransaction.add(new HomeFragment(),"Home");
fragmentTransaction.addToBackStack("Home");
}
fragmentTransaction.replace(containerId,fragment).commit();
}
这是您的导航项侦听器
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.navigation_home:
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
addFragment(getSupportFragmentManager(), new HomeFragment(), R.id.frame, true);
}else{
getSupportFragmentManager().popBackStack();
}
return true;
case R.id.navigation_dashboard:
addFragment(getSupportFragmentManager(),new DashboardFragment(),R.id.frame,false);
return true;
case R.id.navigation_notifications:
addFragment(getSupportFragmentManager(),new NotificationFragment(),R.id.frame,false);
return true;
case R.id.navigation_setting:
addFragment(getSupportFragmentManager(),new SettingFragment(),R.id.frame,false);
return true;
}
return false;
}
};
onBackPressed 方法
@Override
public void onBackPressed() {
if(getSupportFragmentManager().getBackStackEntryCount()>0){
navigation.setSelectedItemId(R.id.navigation_home);
}else {
super.onBackPressed();
}
}
由于 按预期显示了片段,但存在一些错误,每次更改片段时都会调用 HomeFragment 上的 onCreateView 两次。此外,每次更改页面时,更改页面都会实例化新片段。
所以我采用了 并修改了我的 setPage
,它运行良好。
方法如下:
private void setPage(Page page) {
FragmentManager fragmentManager = getSupportFragmentManager();
String tag = page.name();
FragmentTransaction transaction = fragmentManager.beginTransaction();
// detach everything
for (Fragment fragment : fragmentManager.getFragments()) {
transaction.detach(fragment);
}
// Retrieve fragment instance, if it was already created
Fragment fragment = fragmentManager.findFragmentByTag(tag);
if (fragment == null) { // If not, crate new instance and add it
try {
fragment = (Fragment) page.clazz.newInstance();
transaction.add(R.id.frame, fragment, tag);
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
} else { // otherwise just attach it
transaction.attach(fragment);
}
transaction.commit();
}
onBackPressed 实现
@Override
public void onBackPressed() {
if (!getSupportFragmentManager().findFragmentByTag(Page.HOME.name()).isDetached()) {
super.onBackPressed();
} else {
navigation.setSelectedItemId(R.id.navigation_home);
}
}
我想在我的应用程序导航逻辑中实现,就像在 Youtube 应用程序中一样。 (BottomNavigationView + 片段管理)。我想要这个,因为这些片段很重,所以我希望它们被延迟初始化然后存储在backstack中,我感觉YouTube就是这样做的。我已经实现了 BottomNagivationView,但我在片段管理方面遇到了问题。
我的代码:
bottomNavigationView.setOnTabSelectedListener { position, _ ->
setFragment(OnlinePageFragment.Page.values()[position])
}
其中 Pages 是枚举
enum class Page(index: Int, val klass: Class<*>) {
ONE(0, OnePageFragment::class.java),
TWO(1, TwoPageFragment::class.java),
THREE(2, ThreePageFragment::class.java)
}
这是我的 setFragment 函数
fun setFragment(page: OnlinePageFragment.Page) {
var fragment: Fragment? = supportFragmentManager.findFragmentByTag(page.klass.name)
val tag = page.klass.name
if (fragment == null)
fragment = OnlinePageFragment.newInstance(page, null)
val ft = supportFragmentManager.beginTransaction()
with(ft) {
replace(R.id.fragmentContainer, fragment, tag)
addToBackStack(tag)
commit()
}
}
override fun onBackPressed() {
if (supportFragmentManager.backStackEntryCount == 1) finish()
else super.onBackPressed()
}
它可以运行,但不如 YouTube 应用程序好。 YouTube 应用程序有一些神奇的行为,即每个片段只保留一个事务,而我的应用程序允许创建 "infinite" 个事务的后台堆栈。您知道它在 YouTube 应用中的工作原理吗?
我想这里最好的方法是使用ViewPager
来存储根片段。导航栏将更改 ViewPager
个页面。
根片段将在其内部存储内容片段ChildFragmentManagers
并实现内容片段之间的导航逻辑。
此外,您还需要实现从 activity 到根片段的委派 backpress 事件的逻辑,以在其后台执行导航。
UPD #1
确保根片段没有在 ViewPager 中被破坏,使用 setOffscreenPageLimit()
UPD #2
如果您不想使用 ViewPager,可以使用此
无需使用查看寻呼机即可管理它。 我已经实施请检查这个。 https://github.com/sandeshsk/BackStackFragmentRedirectsToHome
有问题请及时更新
这是一个分配片段的方法
public void addFragment(FragmentManager fragmentManager,
Fragment fragment,
int containerId,boolean isFromHome){
fragmentManager.popBackStack(null,FragmentManager.POP_BACK_STACK_INCLUSIVE);
FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
if(isFromHome){
fragmentTransaction.replace(containerId,fragment);
}else{
fragmentTransaction.add(new HomeFragment(),"Home");
fragmentTransaction.addToBackStack("Home");
}
fragmentTransaction.replace(containerId,fragment).commit();
}
这是您的导航项侦听器
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.navigation_home:
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
addFragment(getSupportFragmentManager(), new HomeFragment(), R.id.frame, true);
}else{
getSupportFragmentManager().popBackStack();
}
return true;
case R.id.navigation_dashboard:
addFragment(getSupportFragmentManager(),new DashboardFragment(),R.id.frame,false);
return true;
case R.id.navigation_notifications:
addFragment(getSupportFragmentManager(),new NotificationFragment(),R.id.frame,false);
return true;
case R.id.navigation_setting:
addFragment(getSupportFragmentManager(),new SettingFragment(),R.id.frame,false);
return true;
}
return false;
}
};
onBackPressed 方法
@Override
public void onBackPressed() {
if(getSupportFragmentManager().getBackStackEntryCount()>0){
navigation.setSelectedItemId(R.id.navigation_home);
}else {
super.onBackPressed();
}
}
由于
所以我采用了 setPage
,它运行良好。
方法如下:
private void setPage(Page page) {
FragmentManager fragmentManager = getSupportFragmentManager();
String tag = page.name();
FragmentTransaction transaction = fragmentManager.beginTransaction();
// detach everything
for (Fragment fragment : fragmentManager.getFragments()) {
transaction.detach(fragment);
}
// Retrieve fragment instance, if it was already created
Fragment fragment = fragmentManager.findFragmentByTag(tag);
if (fragment == null) { // If not, crate new instance and add it
try {
fragment = (Fragment) page.clazz.newInstance();
transaction.add(R.id.frame, fragment, tag);
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
} else { // otherwise just attach it
transaction.attach(fragment);
}
transaction.commit();
}
onBackPressed 实现
@Override
public void onBackPressed() {
if (!getSupportFragmentManager().findFragmentByTag(Page.HOME.name()).isDetached()) {
super.onBackPressed();
} else {
navigation.setSelectedItemId(R.id.navigation_home);
}
}