The Problem
I have a navigation drawer with different fragments. There is a default toolbar every Fragment should use, except of one Fragment which needs a collapsing Toolbar.
My Question
How can I switch between the toolbars for the fragments ?
It seems you want to achieve something like this.
I have made an activity with common toolbar. when switching to the collapsing toolbar fragment I've made the toolbar transparent and fragment's toolbar takes over. The toolbar's color remains the same on switching to other fragments.
This allows you to manage complete collapsing toolbar's layout structure in xml and logic remains in Fragment.
Hope this will help. Refer the gif linked.
Gist for gif
The best solution that I found to easily collapse, lock it(keep it in collapsed mode) and unlock the collapsingToolbar.
private void collapseAppBar() {
// Collapse the AppBarLayout with animation
mAppBarLayout.setExpanded(false, true);
}
private void lockAppBar() {
/* Disable the nestedScrolling to disable expanding the
appBar with dragging the nestedScrollView below it */
ViewCompat.setNestedScrollingEnabled(nestedScrollView, false);
/* But still appBar is expandable with dragging the appBar itself
and below code disables that too
*/
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams();
AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();
behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
#Override
public boolean canDrag(AppBarLayout appBarLayout) {
return false;
}
});
}
private void unLockAppBar() {
ViewCompat.setNestedScrollingEnabled(nestedScrollView, true);
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams();
AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();
if (behavior != null) {
behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
#Override
public boolean canDrag(AppBarLayout appBarLayout) {
return true;
}
});
}
}
And I use these functions in this way:
Fragment fragment = null;
Class fragmentClass;
switch (menuItem.getItemId()) {
case R.id.fragment1:
unLockAppBar();
fragmentClass = first_Fragment.class;
break;
case R.id.fragment2:
collapseAppBar();
lockAppBar();
fragmentClass = second_Fragment.class;
break;
case R.id.fragment3:
collapseAppBar();
lockAppBar();
fragmentClass = third_Fragment.class;
break;
You can easily get the Toolbar from your Fragment and then modify or change some property of that Toolbar inside the Fragment.
To get the Toolbar from your Activity you might consider using this.
Toolbar toolbar = (Toolbar) getActivity().findViewById(R.id.toolbar);
Now you need to make the changes on the Toolbar in the onResume function and then undo the changes each time you return from the Fragment inside onStop function. Otherwise the changes made in the Fragment will be carried on to other fragments as well when switched to other Fragment from the navigation drawer.
But in your case, I would recommend each Fragment should have their Toolbar so that it doesn't conflict with each other and can be modified as you need. And yes, remove the Toolbar from your Activity.
So add the Toolbar in the layout of your Fragment like this.
<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimaryDark"/>
Then find it in the Fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment, container, false);
Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar);
// Modify your Toolbar here.
// ...
// For example.
// toolbar.setBackground(R.color.red);
// Create home button
AppCompatActivity activity = (AppCompatActivity) getActivity();
activity.setSupportActionBar(toolbar);
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
And Override the onOptionsItemSelected function.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()){
case android.R.id.home:
getActivity().onBackPressed();
}
return super.onOptionsItemSelected(item);
}
I'm using Jetpack's Navigation components with single Activity and different Fragments in my app.
Some Fragments are accessible from bottom navigation (and have Toolbar from Activity). Some others are "special" Fragments and have their own Collapsible Toolbar.
To achieve this, I'm hiding Toolbar from Activity in "special" Fragments with this code in Activity:
// Handle toolbar changes in different Fragments
val navController = findNavController(R.id.nav_host_fragment)
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.my_special_fragment_with_collapsible_toolbar -> {
binding.toolbarMain.visibility = View.GONE
}
else -> {
binding.toolbarMain.visibility = View.VISIBLE
}
}
}
The recommend practice is to use toolbars in fragments instead of a common toolbar in activity. That way you can control the looks and behaviour of toolbar in fragment. Refer https://developer.android.com/guide/navigation/navigation-ui#support_app_bar_variations
Related
I built a customizable navigation drawer from scratch(didn't make use of the default drawer provided by Android Studio). In my weather app's navigation bar menu https://i.stack.imgur.com/SIjdx.jpg, whenever I select an option on the menu(say settings), it displays the contents of the option along with the bottom navigation view and my Activity's Toolbar contents which comprises of the nav hamburger icon, the edittext and the search button(the activity hosting my 3 fragments) which spoils the app and makes it look very ugly i.e. https://i.stack.imgur.com/gxj5n.jpg (From that screenshot, the entire content should be empty if implemented well). The case is the same for the other bar menu options. All I want is an empty space to work on, I want the app to only display the navigation bar contents without the rest. Example; https://i.stack.imgur.com/3Jtga.png Please how should I do this?
The view of the Navigation Menu is controlled by this code(on line 185):
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.settings_id:
getSupportFragmentManager().beginTransaction().replace(R.id.fragment,
new Settings()).commit();
break;
case R.id.ads_upgrade_id:
getSupportFragmentManager().beginTransaction().replace(R.id.fragment,
new Upgrade()).commit();
break;
case R.id.privacy_policy_id:
getSupportFragmentManager().beginTransaction().replace(R.id.fragment,
new Privacy_Policy()).commit();
break;
}
drawer.closeDrawer(GravityCompat.START);
return true;
}
"fragment" there represents that I'm currently using my fragment's container view on my activity to display the Nav menu contents which I know is wrong for sure, so what should I use in replace? I lack strong experience as it's my first time building an app and I've tirelessly spent 3 hours on my own trying to figure out the issue which proved abortive.
Here is my Activity code:
public class HomeActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
private DrawerLayout drawer;
// Last update time, click sound, search button, search panel.
TextView timeField;
MediaPlayer player;
ImageView Search;
EditText textfield;
// For scheduling background image change(using constraint layout, start counting from dubai, down to statue of liberty.
ConstraintLayout constraintLayout;
public static int count = 0;
int[] drawable = new int[]{R.drawable.dubai, R.drawable.norway, R.drawable.eiffel_tower, R.drawable.hong_kong, R.drawable.statue_of_liberty,
R.drawable.beijing, R.drawable.chicago, R.drawable.colombia, R.drawable.vienna,R.drawable.tokyo};
Timer _t;
private WeatherDataViewModel viewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
// use home activity layout.
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// Allow activity to make use of the toolbar
drawer = findViewById(R.id.drawer_layout);
NavigationView navigationView = findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
viewModel = new ViewModelProvider(this).get(WeatherDataViewModel.class);
// Trigger action to open & close navigation drawer
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar
, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.addDrawerListener(toggle);
toggle.syncState();
timeField = findViewById(R.id.textView9);
Search = findViewById(R.id.imageView4);
textfield = findViewById(R.id.textfield);
// find the id's of specific variables.
BottomNavigationView bottomNavigationView = findViewById(R.id.bottomNavigationView);
// host 3 fragments along with bottom navigation.
final NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.fragment);
assert navHostFragment != null;
final NavController navController = navHostFragment.getNavController();
NavigationUI.setupWithNavController(bottomNavigationView, navController);
// Make hourly & daily tab unusable
bottomNavigationView.setOnNavigationItemSelectedListener(item -> {
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
getSupportFragmentManager().popBackStack();
}
return false;
});
navController.addOnDestinationChangedListener((controller, destination, arguments) -> navController.popBackStack(destination.getId(), false));
// For scheduling background image change
constraintLayout = findViewById(R.id.layout);
constraintLayout.setBackgroundResource(R.drawable.dubai);
_t = new Timer();
_t.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
// run on ui thread
runOnUiThread(() -> {
if (count < drawable.length) {
constraintLayout.setBackgroundResource(drawable[count]);
count = (count + 1) % drawable.length;
}
});
}
}, 5000, 5000);
Search.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// make click sound when search button is clicked.
player = MediaPlayer.create(HomeActivity.this, R.raw.click);
player.start();
getWeatherData(textfield.getText().toString().trim());
// make use of some fragment's data
Fragment currentFragment = navHostFragment.getChildFragmentManager().getFragments().get(0);
if (currentFragment instanceof FirstFragment) {
FirstFragment firstFragment = (FirstFragment) currentFragment;
firstFragment.getWeatherData(textfield.getText().toString().trim());
} else if (currentFragment instanceof SecondFragment) {
SecondFragment secondFragment = (SecondFragment) currentFragment;
secondFragment.getWeatherData(textfield.getText().toString().trim());
} else if (currentFragment instanceof ThirdFragment) {
ThirdFragment thirdFragment = (ThirdFragment) currentFragment;
thirdFragment.getWeatherData(textfield.getText().toString().trim());
}
}
private void getWeatherData(String name) {
ApiInterface apiInterface = ApiClient.getClient().create(ApiInterface.class);
Call<Example> call = apiInterface.getWeatherData(name);
call.enqueue(new Callback<Example>() {
#Override
public void onResponse(#NonNull Call<Example> call, #NonNull Response<Example> response) {
try {
assert response.body() != null;
timeField.setVisibility(View.VISIBLE);
timeField.setText("First Updated:" + " " + response.body().getDt());
} catch (Exception e) {
timeField.setVisibility(View.GONE);
timeField.setText("First Updated: Unknown");
Log.e("TAG", "No City found");
Toast.makeText(HomeActivity.this, "No City found", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onFailure(#NotNull Call<Example> call, #NotNull Throwable t) {
t.printStackTrace();
}
});
}
});
}
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.settings_id:
getSupportFragmentManager().beginTransaction().replace(R.id.fragment,
new Settings()).commit();
break;
case R.id.ads_upgrade_id:
getSupportFragmentManager().beginTransaction().replace(R.id.fragment,
new Upgrade()).commit();
break;
case R.id.privacy_policy_id:
getSupportFragmentManager().beginTransaction().replace(R.id.fragment,
new Privacy_Policy()).commit();
break;
}
drawer.closeDrawer(GravityCompat.START);
return true;
}
#Override
public void onBackPressed() {
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
// Open/close drawer animation
}
}
}
In case you require any other code to look into the issue, please let me know. I'm just trying to avoid posting too much
EDIT:
My old bottomtabs nav graph:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/my_nav"
app:startDestination="#id/firstFragment">
<fragment
android:id="#+id/firstFragment"
android:name="com.viz.lightweatherforecast.FirstFragment"
android:label="fragment_first"
tools:layout="#layout/fragment_first" />
<fragment
android:id="#+id/secondFragment"
android:name="com.viz.lightpreciseweatherforecast.SecondFragment"
android:label="fragment_second"
tools:layout="#layout/fragment_second" />
<fragment
android:id="#+id/thirdFragment"
android:name="com.viz.lightpreciseweatherforecast.ThirdFragment"
android:label="fragment_third"
tools:layout="#layout/fragment_third" />
</navigation>
My new nav bar graph:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/bar_nav"
app:startDestination="#id/firstFragment">
<fragment
android:id="#+id/firstFragment"
android:name="com.viz.lightweatherforecast.FirstFragment"
android:label="fragment_first"
tools:layout="#layout/fragment_first" />
<fragment
android:id="#+id/settings_id"
android:name="com.viz.lightweatherforecast.Settings"
android:label="#string/settings"
tools:layout="#layout/settings" />
<fragment
android:id="#+id/ads_upgrade_id"
android:name="com.viz.lightweatherforecast.Upgrade"
android:label="#string/upgrade_to_remove_ads"
tools:layout="#layout/upgrade" />
<fragment
android:id="#+id/privacy_policy_id"
android:name="com.viz.lightweatherforecast.Privacy_Policy"
android:label="#string/privacy_policy"
tools:layout="#layout/privacy_policy"/>
</navigation>
You are using navigation architecture components, so the navController is the one that should control fragment transactions, you are doing that right with BottomNavigationView.
But within the navDrawer you are doing the transaction through the supportFragmentManager which should be done through the navController instead as both handle the navigation differently.
whenever I select an option on the menu(say settings), it displays the contents of the option along with the bottom navigation view
That is because the BottomNavView is a part of the activity, and you need to move it to a fragment; this requires to change the navigation design of your app; to do that change your app navigation like the below:
Main navigation:
<navigation
..... >
<fragment
android:name="......HomeFragment"/>
<fragment
android:name="......SettingFragment"/>
<fragment
android:name="......AdsUpgradeFragment"/>
<fragment
android:name="......PrivacyPolicyFragment"/>
</navigation>
The HomeFragment is the fragment that should hold the BottomNaviagtionView instead of the activity; and when you navigate to the SettingFragment, the navConroller will replace the entire fragment in the navHostFragment, and therefore the BottomNaviagtionView won't be shown.
my Activity's Toolbar contents which comprises of the nav hamburger
icon, the edittext and the search button(the activity hosting my 3
fragments) which spoils the app and makes it look very ugly
Unlike the BottomNaviagtionView, you can't do that with your toolBar that is used as the supportActionBar, because setting supportActionBar more than once in order to change its look; will duplicates it; so you have to accept a single toolbar; but instead you can hide/show the layout that holds the search button & the EditText whenever the destination changes:
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
LinearLayout searchBar = findViewById(R.id.searchbar); // change searchbar according to the layout id that holds the search button and the EditText
if (destination.getId() == R.id.nav_home) {
searchBar.setVisibility(View.VISIBLE);
} else {
searchBar.setVisibility(View.GONE);
}
});
and yes they currently exit the app when clicking back
To exit the app whenever, the bottom back button is pressed in any fragment use OnBackPressedDispatcher() within onCreateView() of those fragment (in your case SettingFragment, PrivacyPolicyFragment, & AdsUpgradeFragment):
And make sure that appBarConfiguration doesn't reference those fragments so, that the UP button can be shown instead of the burger.
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
#Override
public void handleOnBackPressed() {
// Exit the app when back is pressed
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
requireActivity().finishAndRemoveTask();
else requireActivity().finish();
}
});
Also, make sure that your setup the DrawerLayout & its navView in the navController with:
NavigationView navView = findViewById(....);
appBarConfiguration = new AppBarConfiguration.Builder(
R.id.nav_home) // remove up button from all these fragments >> Keep the up/back button in R.id.settings_id, R.id.settings_id, ads_upgrade_id, privacy_policy_id
.setOpenableLayout(drawer)
.build();
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(navView, navController);
And to make the Home fragment hidden by default in the navDrawer:
navView.getMenu().findItem(R.id.nav_home).setVisible(false); // adjust R.id.nav_home to yours
UPDATE
I currently have a single nav graph. The firstFragment represents the
today, 2nd - hourly and 3rd
I'm using is for my other weather tabs but the one you're suggesting
is for the navbar menu, should I replace yours with mine or just
create a new one for your suggestion?
You should use two navGraphs, the first is for the main navigation which I'd suggested; and the second is for the BottomNavigationView navigation which you already use; that is because we transferred the BottomNavigationView from the activity layout to the main/home fragment layout; and it' recommended the BottomNavigationView should have a separate navGraph;
So, you now need two FragmentContainerView; the first is in the activity layout which reference the navGraph provided in this answer, and the second is in the home fragment layout that references your original navGraph of the BottomNavigationView.
Sample:
I think I understood your problem very well (if I am not wrong). Thing is that what behavior you are getting right now is the normal scenario. You are using the same host activity to host both Navigation Drawer and Bottom Navigation fragments thus when you tried to navigate to another fragment from the same host the presence of the host's direct child view Bottom NavBar is showing. I think you can solve this problem in a few different but pretty simple logical ways.
(Not recommended) Using a different Activity for the Settings page. Just like startActivity(this, <some intent>). But in this way, you will end up creating lots of individual activities.
(Recommended) Using a common Navigation Host activity for navigating to independent pages/fragments. Like: Settings, Contact Us, etc. You can add different fragments into the common_nav_graph and set some actions with or without arguments for navigation. Just add the common_nav_graph as a nested graph inside your current one and set a simple argumented action. The arguments will help you to navigate desired pages/fragments without showing the bottom navigation bar.
Simply hide Bottom navigation when navigating to some fragments from your drawer.
I just wanna know if I'm doing something wrong since I'm kinda new to all this.
If there is anything else that you'll like me to add just let me know.
This is the repo branch where I'm trying to implement the ViewPager if you wanna see all the code.
Context
So I have 4 Categories represented with Fragments, each of this categories holds an ArrayList of items that each has a onItemClickListener that should reproduce some audio.
I'm trying to display the Fragments with a ViewPager but the problem is that when I scroll from a Fragment to another, then come back to the already created Fragment, it doesnt register the touch event, nothing happens, not even an error nor exception.
If I go to a newly created Fragment the touch works just fine.
Also, after switching back to an already created Fragment if I scroll even just a little bit to another Fragment and comeback or through the ArrayList of that Fragment for some reason it starts to recognize the touch in the ArrayList items again.
Similar questions that didn't really help
Fragments in ViewPager2 does not respond to clicks if scroll position is 0
ViewPager2 conflicting with SwipeRefreshLayout
Android ViewPager2 with fragment containing a recyclerview not scrolling
What I've tried
I tried to use a coordinatorlayout wrapping the ViewPager2 but there is no difference
I've been reading some of the official viewPager2 examples that are written in Kotlin but none of them seem to have a similar situation (also it's hard for me to read Kotlin code)
Code Snippets
word_list.xml:
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/root_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/tan_background" />
activity_main.xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="MainActivity">
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"/>
</FrameLayout>
This is one of the Fragments, the other three are basically the same, just the items in the arrayList change and some other minor things:
// ...Skipped some irrelevant code...
public class NumbersFragment extends Fragment {
private ArrayList<Word> mWords;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.word_list, container, false);
mWords = new ArrayList<>();
// ...Add all the items to the list...
// Make the adapter for the word items
WordAdapter adapter = new WordAdapter(getActivity(), mWords, R.color.category_numbers);
// Find the root view of the list
ListView listView = rootView.findViewById(R.id.root_list_view);
// Add adapter to the root list view
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Log.d("NumbersFragment", "CLICKED");
}
}
});
return rootView;
}
#Override
public void onPause() {
super.onPause();
Log.d("NumbersFragment", "Fragment paused");
}
}
This is the Category adapter, it manages the fragments:
public class CategoryAdapter extends FragmentStateAdapter {
private static final int NUM_CATEGORIES = 4;
// Required public constructor
public CategoryAdapter(#NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
}
#NonNull
#Override
public Fragment createFragment(int position) {
// Depending on which page the user is in,
// create a fragment of the corresponding category
switch (position) {
case 0:
return new NumbersFragment();
case 1:
return new FamilyFragment();
case 2:
return new ColorsFragment();
default:
return new PhrasesFragment();
}
}
#Override
public int getItemCount() {
return NUM_CATEGORIES;
}
}
And this is my MainActivity:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set the content of the activity to use the activity_main.xml layout file
setContentView(R.layout.activity_main);
// Find the view pager that will allow the user to swipe between fragments
ViewPager2 viewPager = findViewById(R.id.viewpager);
// Create an adapter that knows which fragment should be shown on each page
CategoryAdapter adapter = new CategoryAdapter(this);
//or CategoryAdapter adapter = new CategoryAdapter(getSupportFragmentManager(), getLifecycle());
// Set the adapter into the view pager
viewPager.setAdapter(adapter);
}
}
add this in your MainActivity viewPager.setOffscreenPageLimit(3); after creating viewpager
It’s because the ViewPager has a default offscreen limit of 1 ,and ViewPager2 has a default offscreen limit of 0.
In ViewPager2 when you switch tabs the previous tab will be automatically refreshed.
in ViewPager if you have 3 tabs or more when you switch to 3rd tab automatically first one will be destroyed and when you goes to 1st tab it will be recreated.
viewPager.setOffscreenPageLimit(3); from this line when you switch to a tab,the previous 3 tabs will be preloaded and next 3 tabs will be preloaded so nothing will be refreshed.
This is more of a Java question (having some trouble understanding how should the inheritance be set). I'm trying to add a side menu to my application (which works ok). The class signature of my menu activity is:
public class MenuActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener
Once the user logged in, the first window he see is the main dashboard. The dashboard activity extends the menu activity in order to have the side menu:
public class DashboardActivity extends MenuActivity
For navigating to other activities, I implemented the onNavigationItemSelected method to select the intent to load (The method is located in the MenuActivity):
#Override
public boolean onOptionsItemSelected(#NonNull MenuItem item) {
if (toggle.onOptionsItemSelected(item)) {
return true;
}
return super.onOptionsItemSelected(item);
}
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Intent intent = null;
switch (item.getItemId()) {
case R.id.lay_dashboard:
intent = new Intent(this, DashboardActivity.class);
//intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
break;
case R.id.lay_settings:
intent = new Intent(this, SettingsActivity.class);
//intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
break;
case R.id.lay_contacts:
startActivity(new Intent(this, ContactsActivity.class));
break;
case R.id.lay_about:
startActivity(new Intent(this, AboutActivity.class));
break;
case R.id.lay_logout:
mAuth.signOut();
intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
break;
}
if (intent != null) {
startActivity(intent);
}
drawerLayout.closeDrawer(GravityCompat.START);
return true;
}
As you can see I commented the intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); as part of debugging the issue. I want the navigation process not to start a new screen, rather move to it.
Also the OnCreate method in the MenuActivity looks like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_menu);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
}
drawerLayout = findViewById(R.id.drawer_layout);
navigationView = findViewById(R.id.lay_nav_view);
toggle = new ActionBarDrawerToggle(this, drawerLayout,
R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawerLayout.addDrawerListener(toggle);
toggle.syncState();
navigationView.setNavigationItemSelectedListener(this);
mAuth = FirebaseAuth.getInstance();
logged_user = mAuth.getCurrentUser();
if (logged_user != null) {
// Get data from firebase in order to set the avatar
// and user name in the menu
}
}
protected void setLayoutView(int layout) {
LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (inflater != null) {
View contentView = inflater.inflate(layout, null, false);
drawerLayout.addView(contentView, 0);
}
}
Each one of the activitise extends the MenuActivity and does the following in their onCreate method:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setLayoutView(R.layout.activity_about); // My method which loads UI
// other code
}
So, as you can see, each one of the activities is extending the MenuActivity. As I understand, this means that everytime I navigate from one activity to another, the OnCreate of MenuActivity is being executed again. I fetch data from the Firebase in that method in order to set the username and the avatar in the top of the menu. So everytime I navigate from one activity to another, it will fetch again and again. How can I make the menu to load only once in a logged mode ? Also, thanks to all of you that have read this topic (I know it's long).
EDIT: To make it more clear, I'm adding the hierarchy of my code:
Maybe the solution for this issue is to make the MenuActivity a singleton class?
All I want to have a menu on each one of the activities and fetching the data only once.
Hello you could use NavigationDrawer with fragments. That will be the most optimal solution and easy, You could also use Navigation Component from Jetpack to change the destinations.
you could use something like this,the following layout uses a DrawerLayout with two child views: a NavHostFragment to contain the main content and a NavigationView for the contents of the navigation drawer.
<?xml version="1.0" encoding="utf-8"?>
<!-- Use DrawerLayout as root container for activity -->
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<!-- Layout to contain contents of main body of screen (drawer will slide over this) -->
<fragment
android:name="androidx.navigation.fragment.NavHostFragment"
android:id="#+id/nav_host_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph" />
<!-- Container for contents of drawer - use NavigationView to make configuration easier -->
<com.google.android.material.navigation.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true" />
Next, connect the DrawerLayout to your navigation graph by passing it to AppBarConfiguration, as shown in the following example:
val appBarConfiguration = AppBarConfiguration(navController.graph, drawerLayout)
Next, in your main activity class, call setupWithNavController() from your main activity's onCreate() method, as shown below:
override fun onCreate(savedInstanceState: Bundle?) {
setContentView(R.layout.activity_main)
...
val navController = findNavController(R.id.nav_host_fragment)
findViewById<NavigationView>(R.id.nav_view)
.setupWithNavController(navController)
}
Thats it , then you can use the navContoller to navigate to any fragment,
and rest of what you need is a navigation graph and actions set on destinations to navigate between fragments this way :
navController.navigate(R.id.actionSettingFragment_to_ContactsFrament)
Hope this is kinda helpful. I have a blog written on this so please check it if it is useful to you.
There's more than one way to resolve your issue. I'm going to provide different approaches towards a solution.
Repository
To reuse the data you fetch from Firebase, it needs to be persisted or cached within your application. In general you'd implement an repository abstraction around the Firebase. This repository could be a singleton and just cache the data in memory directly. You only have to make sure to cleanup the cache when necessary. The MenuActivity mustn't be a singlton.
Intent extras
Instead of storing the data in memory, you could provide it as Intent extras. Whenever the data is present, just skip the loading and reuse the data provided.
Fragments
Instead of a stack of activities, you could use fragments. So you'll have a single activity and just replace the fragments within it. With this approach you'll just define a single drawer layout you'll automatically use with every fragment. This approach enables you to use Navigation components as well.
I am writing an app using jetpack recommended architecture, NavigationUI, and the navigation graph. So I have one main activity with a Toolbar, a BottomNavigationView and the NavHostFragment.
Everything worked nicely until now: I need to change the Toolbar to use a CollapsingToolbarLayout and hide the BottomNavigationView in one of my fragment.
I tried to add a navigation listener (as described here) to hide my Toolbar and BottomNavigationView, and in my fragment, I inflate the new Toolbar and call setSupportActionBar() on the main activity.
// in MainActivity.java
#Override
protected void onCreate(Bundle savedInstanceState) {
// ...
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
if(destination.getId() == R.id.detailFragment){
bottomBar.setVisibility(View.GONE);
topBar.setVisibility(View.GONE);
}else{
bottomBar.setVisibility(View.VISIBLE);
topBar.setVisibility(View.VISIBLE);
}
});
// ...
}
public void changeToolbar(Toolbar toolbar){
getSupportActionBar().hide();
setSupportActionBar(toolbar);
}
// in DetailFragment.java
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// ...
navController = NavHostFragment.findNavController(this);
AppBarConfiguration.Builder builder = new Builder(
R.id.accuracyFragment,
R.id.dataFragment,
R.id.magnetFragment,
R.id.settingsFragment);
AppBarConfiguration config = builder.build();
NavigationUI.setupWithNavController(toolbarLayout, toolbar, navController);
((MainActivity)getActivity()).changeToolbar(toolbar);
// ...
}
It almost works correctly, but:
when I navigate up or go to another fragment, the BottomNavigationView is not correctly displayed. It seems to be pushed down by the Toolbar.
the transition is ugly: the toolbar is visibly changing, I can see it disappearing before being changed
So the question is: is there another way to change/hide the navigation elements from the fragment? If not, should I create a new activity?
It's been a wild ride, but I finally found a solution. For the issue number 1, this is due to the way Android manages the fitsSystemWindows property propagation. For this to work correctly, I made a few changes to my layouts. I created a custom FitSystemWindowLinearLayout, which is simply a class extending the standard LinearLayout and overriding onApplyWindowInsets like this:
#Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
int childCount = getChildCount();
for (int index = 0; index < childCount; ++index) {
getChildAt(index).dispatchApplyWindowInsets(insets);
}
return insets;
}
My main activity now looks like this:
+-- CoordinatorLayout, fitsSystemWindows=false
+-- FitSystemWindowLinearLayout, fitsSystemWindows="false"
+-- Toolbar
+-- NavHostFragment, fitsSystemWindows="false"
+-- BottomNavigationView, fitsSystemWindows="false"
For the second issue, namely the transition being ugly, I mitigated that by adding a shared element to the transition.
All in all, I think it's easier to use a new activity for this kind of things, the NavigationUI falls a bit short for now.
Here are some resources that helped me:
https://medium.com/androiddevelopers/why-would-i-want-to-fitssystemwindows-4e26d9ce1eec
Android fitsSystemWindows not working when replacing fragments
Collapsing Toolbar problems with Status Bar and Bottom Bar. Fitssystemwindows="true" not working
fitsSystemWindows effect gone for fragments added via FragmentTransaction
https://www.reddit.com/r/androiddev/comments/aryxvu/fitssystemwindows_misbehaves_with_navigation/
-https://medium.com/androiddevelopers/why-would-i-want-to-fitssystemwindows-4e26d9ce1eec
I would like to be able to hide the Toolbar on swipe to a specific tab and then lock it. Expanding it and then locking would also work for me although i prefer the first.
I have tried doing stuff like in the code below, but it gives rise to some snappy behavior because the page is scrolled of the screen originally. As soon as i set the scroll flags to 0, the whole page snaps back up and then locks the screen with the toolbar expanded which makes sense because with scroll flags set to zero, the page should not be able to scroll off the screen at all so it just snaps back up.
The page I am tending to is a chat page and in order to have a static text input bar at the bottom I really need to disable the scrolling for that page while making it possible for the others.
Can you guys think of some way to accomplish this?
Activity:
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_group);
appBarLayout = (AppBarLayout) findViewById(R.id.appbar);
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// add all the needed fragments for the tabs inside a Vector
viewPager = (ViewPager) findViewById(R.id.viewPager);
viewPager.setAdapter(pagerAdapter);
viewPager.setOffscreenPageLimit(4);
tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(viewPager);
viewPager.clearOnPageChangeListeners();
viewPager.addOnPageChangeListener(new TabLayoutOnPageChangeListenerGroups(tabLayout, this, fab, appBarLayout));
viewPager.setCurrentItem(CURRENT_TAB);
}
public void disableToolbarScrolling()
{
logger.d("disabling scrolling toolbar");
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
params.setScrollFlags(0); // clear all scroll flags
toolbar.setLayoutParams(params);
}
public void enableToolbarScrolling()
{
logger.d("enabling scrolling toolbar");
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS); // clear all scroll flags
toolbar.setLayoutParams(params);
}
And then using a PageChangeListener to handle the event:
public TabLayoutOnPageChangeListenerGroups(TabLayout tabLayout, Activity activity, FloatingActionsMenu fab, AppBarLayout appBarLayout)
{
...
this.appBarLayout = appBarLayout;
...
}
private void enableScrolling()
{
((GroupActivity) mActivity).enableToolbarScrolling();
toolbarState = toolbarState_enabled;
}
private void disableScrolling()
{
((GroupActivity) mActivity).disableToolbarScrolling();
toolbarState = toolbarState_disabled;
}
#Override
public void onPageSelected(int position)
{
if(position == 0)
{
logger.d("pos 0");
disableScrolling();
appBarLayout.setExpanded(false);
}
else
{
logger.d("pos != 0");
enableScrolling();
}
}
EDIT 1:
I have tried setting toolbar visibility to both GONE or INVISIBLE. But they only make the toolbar white, giving a white bar at the top of the screen. Still allowing for the scrolling behaviour.
add this line to your code in onCreate
setSupportActionBar(toolbar); and then add
toolbar.setVisibility(View.GONE);
For hiding the toolbar you can just try :
getSupportActionBar().hide();