dynamically change the drawer layout - android

I have this situation: when launching the app, the first fragment (A) displayed has a list of some users. When clicking a user, another fragment (B)is displayed and, together, the drawer menu will be different for each user.
As you can see, I can NOT set up the DrawerLayout when launching the app (the main activity or Launcher) since I don't have the data for the listView yet, but have to set it up when clicking a user in the list in fragment A after the app finishes launching (That's when I can retrieve the data for the listView of the drawer menu, which is indicated by the id: fragment_drawer in below xml file).
Here's the xml file for "MainActivity":
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!-- The main content view -->
<FrameLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- The navigation drawer, listView is inside MyDrawerFragment's layout-->
<fragment android:id="#+id/fragment_drawer"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:name="mypackage.com.MyDrawerFragment"
tools:layout="#layout/drawer_layout" />
</android.support.v4.widget.DrawerLayout>
How can I achieve the goal in fragment B? Is this achievable at all in fragment B?
If not possible doing this in fragment B, I am thinking to launch a second activity, say Activity2, and the above xml layout file will be applied to this new activity, when clicking the user in fragment A, I just keeps doing this: closing the existing Acitvity2 by calling finish() and create a new Activity2 instance with new data for drawer menu? Is this approach feasible at all?
Any hints are highly appreciated!
Thanks,
Shawn

I had to do an app where I had to make an Activity with 4 fragments and a ViewPager, where only in 2 I would open a different Drawer.
The Activity's layout must contain the DrawerLayout*, but since the Drawer itself depends on which Fragment I'm on, I figured that it should be the Fragment the one in charge of rendering (or not) the Drawer.
*If the Activity doesn't contain the DrawerLayout, it wouldn't be displayed filling the whole screen!
I did something like this, though it does need some refactoring and clean up :)
Working on the idea that the Fragment is in charge of rendering the Drawer, but it is the Activity the one that has access to it, I made two interfaces to communicate Fragment and Activity:
/**
* should be implemented by any fragment interested in communicating
* with a {#link FragmentListener} Activity
*/
public interface ActivityListener {
/**
* called from {#link FragmentListener} requestOpenDrawer
* the fragment is in charge of rendering its drawer's layout
*/
public void renderDrawer(DrawerLayout mDrawerLayout,
NavigationView viewById, Activity activity);
}
-
/**
* used to communicate FROM fragments, to its parent Activity
* implemented by {#link MainTabbedActivity}
*/
public interface FragmentListener {
/**
* call from a fragment to request opening the drawer
* note that if the drawer isn't opened by the activity,
* it wouldn't cover the whole screen
*/
void requestOpenDrawer(Fragment requester);
void requestCloseDrawer();
DrawerLayout getDrawerLayout();
}
Implementation
#Override
public void requestOpenDrawer(Fragment requester) {
mDrawerLayout.openDrawer(Gravity.RIGHT); //Edit Gravity.End need API 14
if (requester instanceof ActivityListener) {
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
//let fragment render the drawer
((ActivityListener) requester).renderDrawer(mDrawerLayout, navigationView, this);
}
}
-
#Override
public void renderDrawer(DrawerLayout mDrawerLayout, NavigationView viewById, Activity activity) {
View child = activity.getLayoutInflater().inflate(R.layout.credit_fragment_drawer, null);
navigationView.addView(child);
}
Layouts
Both the Activity layout and the Fragments layout don't have anything more than the standard Drawer implementation:
Activity layout with Drawer:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout 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/main_tabbed_drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:openDrawer="right">
<include
layout="#layout/activity_main_tabbed_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.design.widget.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="right"
android:fitsSystemWindows="true" />
</android.support.v4.widget.DrawerLayout>
Where activity_main_tabbed_content is whatever the content you want to put in the Activity.
The fragments really don't have anything more than a common Fragment.
Example:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- whatever views you want -->
</RelativeLayout>

Related

How to show base activity's view in another activty

I am developing an android application. What I am want to achieve in this app is, I want to make a custom progress bar which has to be available to all the activities. So I created BaseActivity and I added one custom gif in BaseActivity's layout then I created SecondActivity which extends BaseActivity.
BaseActivity(By default gif is invisible) has two methods called showProgressBar and hideProgressBar I am calling this method from SecondActivity but it is not showing a progress bar. Then I make a BaseActivity launcher activity and to check the gif it is showing.
How should I achieve this?
You can override the setContentView in your BaseActivity and add your base layout there, that way when the child activities call the setContentView, the base layout is also set. You'll need to set a FrameLayout that will 'hold' the child activity's layout. Something along the following lines should work.
//In BaseActivity
#Override
public void setContentView(int layoutResID)
{
DrawerLayout fullView = (DrawerLayout) getLayoutInflater().inflate(R.layout.activity_base, null);
FrameLayout activityContainer = (FrameLayout) fullView.findViewById(R.id.activity_content);
getLayoutInflater().inflate(layoutResID, activityContainer, true);
super.setContentView(fullView);
}
P.S - In my above example, I used a DrawerLayout, you should be able to use it with any ViewGroup. Just replace that line with the base ViewGroup in your base activity layout.
Edit - Added xml for clarity
//activity_base.xml
<android.support.v4.widget.DrawerLayout
android:id="#+id/activity_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:background="#color/background_material_dark"
/>
<FrameLayout
android:id="#+id/activity_content"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<android.support.design.widget.NavigationView
android:id="#+id/navigationView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:menu="#menu/menu_base"/>
</android.support.v4.widget.DrawerLayout>
You can create a custom dialog class so it will be available for every Activity in your project
Create dialogClass:
public class ProgressDialog extends Dialog {
public ProgressDialog(#NonNull Context context) {
super(context);
setContentView(R.layout.progress_dialog); //this is your layout for the dialog
}
}
And all you need to do is to create dialog instant and call it like this:
ProgressDialog progressDialog = new ProgressDialog(getContext());
progressDialog.show(); // this line shows your dialog
And in your dialog class, you can put your logic, change the way your layout will look and do anything that you need to.

Android JetPack navigation with multiple stack

I'm using Jetpack Navigation version 1.0.0-alpha04 with bottom navigation. It works but the navigation doesn't happen correctly. For example, if I have tab A and tab B and from tab A I go to Page C and from there I go to tab B and come back to tab A again, I will see root fragment in the tab A and not page C which does not what I expect.
I'm looking for a solution to have a different stack for each tab, so the state of each tab is reserved when I come back to it, Also I don't like to keep all this fragment in the memory since it has a bad effect on performance, Before jetpack navigation, I used this library https://github.com/ncapdevi/FragNav, That does exactly what, Now I'm looking for the same thing with jetpack navigation.
EDIT 2: Though still no first class support (as of writing this), Google has now updated their samples with an example of how they think this should be solved for now: https://github.com/googlesamples/android-architecture-components/tree/master/NavigationAdvancedSample
The major reason is you only use one NavHostFragment to hold the whole back stack of the app.
The solution is that each tab should hold its own back stack.
In your main layout, wrap each tab fragment with a FrameLayout.
Each tab fragment is a NavHostFragment and contains its own navigation graph in order to make each tab fragment having its own back stack.
Add a BottomNavigationView.OnNavigationItemSelectedListener to BottomNavigtionView to handle the visibility of each FrameLayout.
This also takes care of your "...I don't like to keep all this fragment in memory...", because a Navigation with NavHostFragment by default uses fragmentTransaction.replace(), i.e. you will always only have as many fragments as you have NavHostFragments. The rest is just in the back stack of your navigation graph.
Edit: Google is working on a native implementation https://issuetracker.google.com/issues/80029773#comment25
More in detail
Let's say you have a BottomNavigationView with 2 menu choices, Dogs and Cats.
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+id/dogMenu"
.../>
<item android:id="#+id/catMenu"
.../>
</menu>
Then you need 2 navigation graphs, say dog_navigation_graph.xml and cat_navigation_graph.xml.
The dog_navigation_graph might look like
<navigation
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/dog_navigation_graph"
app:startDestination="#id/dogMenu">
</navigation>
and the corresponding for cat_navigation_graph.
In your activity_main.xml, add 2 NavHostFragments
<FrameLayout
android:id="#+id/frame_dog"
...>
<fragment
android:id="#+id/dog_navigation_host_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="#navigation/dog_navigation_graph"
app:defaultNavHost="true"/>
</FrameLayout>
and underneath add the corresponding for your cat NavHostFragment. On your cat frame layout, set android:visibility="invisible"
Now, in your MainActivity's onCreateView you can
bottom_navigation_view.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.dogMenu -> showHostView(host = 0)
R.id.catMenu -> showHostView(host = 1)
}
return#setOnNavigationItemSelectedListener true
}
All that showHostView() is doing is toggling the visibility of your FrameLayouts that are wrapping the NavHostFragments. So make sure to save them in some way, e.g. in onCreateView
val hostViews = arrayListOf<FrameLayout>() // Member variable of MainActivity
hostViews.apply {
add(findViewById(R.id.frame_dog))
add(findViewById(R.id.frame_cat))
}
Now it's easy to toggle which hostViews should be visible and invisible.
The issue has been resolved by the Android team in the latest version 2.4.0-alpha01 multiple backstacks along with bottom navigation support is now possible without any workaround.
https://developer.android.com/jetpack/androidx/releases/navigation
First, I want to make an edit to #Algar's answer. The frame that you want to hide should have android:visibility="gone" instead of invisible. The reason for that in your main layout you would have something like this:
<LinearLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.activity.MainActivity">
<include
android:id="#+id/toolbar"
layout="#layout/toolbar_base" />
<FrameLayout
android:id="#+id/frame_home"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
>
<fragment
android:id="#+id/home_navigation_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/home_nav" />
</FrameLayout>
<FrameLayout
android:id="#+id/frame_find"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:visibility="gone">
<fragment
android:id="#+id/find_navigation_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/find_nav" />
</FrameLayout>
...
</LinearLayout>
If you wrap your main in a LinearLayout, setting the frame to invisible still make that frame counts, so the BottomNavigation wont appear.
Second, you should create a NavHostFragment instance (ie: curNavHostFragment) to keep track of which NavHostFragment is being visible when a tab in BottomNavigation is clicked. Note: you may want to restore this curNavHostFragment when the activity is destroyed by configuration's changes. This is an example:
#Override
protected void onRestoreInstanceState(#NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
//if this activity is restored from previous state,
//we will have the ItemId of botnav the has been selected
//so that we can set up nav controller accordingly
switch (bottomNav.getSelectedItemId()) {
case R.id.home_fragment:
curNavHostFragment = homeNavHostFragment;
...
break;
case R.id.find_products_fragment:
curNavHostFragment = findNavHostFragment;
...
break;
}
curNavController = curNavHostFragment.getNavController();

How do I add NavigationDrawer from an activity template to other activities?

I've been playing around with the Drawer Menu activity template from Android Studio since I haven't used a drawer menu before. Here's the layout file.
<android.support.v4.widget.DrawerLayout 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/drawer_layout"
android:layout_width="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true" tools:openDrawer="start">
<!-- Includes a Toolbar and FloatingActionButton. Generated by Android Studio-->
<include layout="#layout/app_bar_drawer_menu" android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.design.widget.NavigationView android:id="#+id/nav_view"
android:layout_width="wrap_content" android:layout_height="match_parent"
android:layout_gravity="start" android:fitsSystemWindows="true"
app:headerLayout="#layout/nav_header_drawer_menu"
app:menu="#menu/activity_side_menu_drawer" />
</android.support.v4.widget.DrawerLayout>
Android Studio also generated an Activity class as part of the template. My app has a main activity as well that uses a LinearLayout. I want to use Fragments to add the drawer menu to the main activity (and other activities), but I'm unsure how to do this. Do I need to create a new subclass of fragment and adapt DrawerMenuActivity's callback methods for the fragment class? Then do I add a fragment tag to activity_main.xml?
This will likely be something you need to wrap your head around...
Ideally, you want to swap fragments in a container located in the the xml for the MainActivity by using fragment transaction and calling replace with the fragment to be placed in the container. This will allow you to re-use the container and offer the drawer to the entire app.
Otherwise, you would need to include the drawer in every activity which creates a lot of extra code or not offer the drawer which depending on the activity you may be able to get away with that.
So, the drawer works with fragments being swapped in via the transaction and not so well for additional activities. Its something you need to design around.
Please, create a new project with a generated drawer for an Activity and proceed from there. Also read the android developer tutorial for fragments part on communication to see how the drawer fragment and activity communicate.
There's actually quite a bit involved for the drawer but you should be able to get a good grasp in a day or two.

Pushing the mainActivity to the side When the Navigation Drawer Opens

Currently, when the navigation drawer in my app opens, it appears on top of the mainActivity view.
My question being, is it possible to animate the mainActivity view so that the left side of said view matches the right side of the navigation drawer when it opens?
It is (almost) always possible. But i think it is a bad idea, as it will not follow the ui guidelines from google for an android app.
However if you really want to do it, you have two options :
Easy. When the drawer is open start animation on the rest of the layout, and do the opposite animation when the drawer is closed. It is less effort, but probably poor result.
Implement your own drawer. You will be able to have the perfect animation and behavior you desire. In this case you probably want to use fragment. The main fragment will be the content and another fragment will be the drawer. When you need to do it, you can animate both fragment with a translation to "open" your drawer.
You can read more about animation here.
All you have to do is setX() of your main_content in the Activity
my_activity.xml
<android.support.v4.widget.DrawerLayout
android:id="#+id/drawer_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- The main content view -->
<RelativeLayout
android:id="#+id/main_content_rl"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/material_grey_100">
<include
layout="#layout/content"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
<!-- The navigation drawer -->
<android.support.design.widget.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:menu="#menu/menu_drawer"/>
</android.support.v4.widget.DrawerLayout>
MyActivity java file
mDrawerToggle =
new ActionBarDrawerToggle(this, mDrawerLayout, toolbar, R.string.drawer_open,
R.string.drawer_close) {
#Override
public void onDrawerSlide(View drawerView, float slideOffset) {
float width = drawerView.findViewById(R.id.main_header_rl).getWidth();
final int movement= (int) (width * slideOffset);
findViewById(R.id.main_content_rl).setX(movement);
super.onDrawerSlide(drawerView, slideOffset);
}
};
mDrawerLayout.setDrawerListener(mDrawerToggle);
Where main_header_rl is the id of my header_navigation_drawer
<RelativeLayout
android:id="#+id/main_header_rl"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
...
</RelativeLayout>

Sub menu in navigation drawer

I'm trying to find solution how to create sub menu in navigation drawer. I will be the best to show example from iOS app video
I dont see any methods in navigation drawer documentation to do such layout animations. I will appreciate any help
there is not such concept called sub menu in navigation drawer on the android SDK.
but there are also good news - since Navigation Drawer is eventually layout container - nothing stops you from host inside of it any view or nested layout container you desire instead of host inside of it only ListView , and therefore - you can use also Fragments
if you'll do that - you could perform fragment transactions between them to swipe to another fragment representing the sub menu... also you'll get "for free" the fragment transaction animation, if you'll set it to use one.
each fragment would show ListView (or whatever..) representing different menu screen
add to it you own UI back button header and implement the onClick callback to perform fragment transaction to the "main menu" fragment, and you've got exactly what you want.
this is how your main activity UI xml layout file should look like:
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- The main content view -->
<FrameLayout
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- The navigation drawer -->
<FrameLayout android:id="#+id/left_drawer_layout_fragment_conatiner"
android:layout_width="240dp"
android:layout_height="match_parent"
android:background="#111"/>
as you see, I replaces the traditional navigation drawer ListView with a FrameLayout. the left drawer can be replaced with any other view or custom component you want, and can act also as fragment container.
EDIT
#Meryl requested from me to show based on the documentation example how I use Fragment as drawer menu. so, assuming you want that your menu would look exactly like the documentation example (which is not must at all because you can make any UI you wish..) this would be the way to translate it into Fragment:
the fragment_menu.xml layout file:
<ListView android:id="#+id/list_view"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
android:choiceMode="singleChoice"
android:divider="#android:color/transparent"
android:dividerHeight="0dp"
android:background="#111"/>
the FragmentMenu java class:
public class FragmentMenu extends Fragment {
private ListView mListView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_menu, null);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mListView = (ListView)view.findViewById(R.id.list_view);
// Set the adapter for the list view
mListView.setAdapter(new ArrayAdapter<String>(this, R.layout.drawer_list_item, mPlanetTitles));
// Set the list's click listener
mListView.setOnItemClickListener(new DrawerItemClickListener());
}
}
attaching the FragmentMenu to the Activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView...
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.left_drawer_layout_fragment_conatiner, new FragmentMenu());
.commit();
}
all the rest should stat almost the same.. and of course - the code in the documentation that creates the menu list and setting the adapter in the activity is not necessary anymore, because it's implemented inside the fragment instead.

Categories

Resources