Using Toolbar with Fragments - java

I am trying to create a viewpager that swipes through 3 different fragments each with a different toolbar. I have implemented the new toolbar in an activity before and got it to work however I am trying to get it to work with fragments
Here is the fragment code
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout resource that'll be returned
View rootView = inflater.inflate(R.layout.fragment_home, container, false);
mToolbar = (Toolbar) rootView.findViewById(R.id.toolbar_home);
if (mToolbar != null) {
setSupportActionBar(mToolbar);
}
mToolbar.setTitle(null);
return rootView;
}
I am extending my fragment with Fragment, however I am getting the error
Cannot resolve method setSupportActionBar
I am not sure how to resolve this, if I remove the setSupportActionBar code will it stop working with certain devices?

Fragments don't have such method setSupportActionBar(). ActionBar is a property of Activity, so to set your toolbar as the actionBar, your activity should extend from ActionBarActivity and then you can call in your Fragment:
((ActionBarActivity)getActivity()).setSupportActionBar(mToolbar);
UPDATE
If you're using AppCompatActivity :
((AppCompatActivity)getActivity()).setSupportActionBar(mToolbar);

I have seen a lot of answers mentioning to setSupportActionBar for toolbar inside Fragment but this approach may go wrong if you are having a a toolbar in Activity and a separate Toolbar in Fragment.
As you shift setSupportActionBar from Activity's Toolbar to Fragment's toolbar, You may face duplication of MenuItem even you try to override using setHasOptionsMenu(true).
Secondly If you want to update Activity's Toolbar you see your changes are not reflected because of setSupportActionBar inside your Fragment.
So in order to avoid this I recommend to use toolbar methods like this inside fragment to inflate menu and use
toolbar = (Toolbar) view.findViewById(R.id.toolbar_frag);
toolbar.inflateMenu(R.menu.frag_menu_items);
Menu menu = toolbar.getMenu();
and use Toolbar.OnMenuItemClickListener interface to receive with menuItems click events.
Edit (Section Copied from MrEngineer13 answer)
and if you are worried about the back button you can set it like this
toolbar.setNavigationIcon(getResources().getDrawable(R.drawable.ic_action_back));
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//What to do on back clicked
}
});

Base on #Faisal Naseer answer. Here is the full example (with few notes) for using custom Toolbar
with navigation and menu in Fragment
fragment_home.xml
<androidx.constraintlayout.widget.ConstraintLayout 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="match_parent"">
...
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar_home"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="Home" />
</androidx.constraintlayout.widget.ConstraintLayout>
HomeFragment.kt
class HomeFragment : BaseFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
// setHasOptionsMenu(true): don't need this anymore
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_home, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
toolbar_home.setNavigationIcon(R.drawable.ic_back) // need to set the icon here to have a navigation icon. You can simple create an vector image by "Vector Asset" and using here
toolbar_home.setNavigationOnClickListener {
// do something when click navigation
}
toolbar_home.inflateMenu(R.menu.menu_home)
toolbar_home.setOnMenuItemClickListener {
when (it.itemId) {
R.id.action_add -> {
// do something
true
}
R.id.action_update -> {
// do something
true
}
else -> {
super.onOptionsItemSelected(it)
}
}
}
}
}
menu_home.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="#+id/action_add"
android:title="#string/add_device"
app:showAsAction="never" />
<item
android:id="#+id/action_update_room"
android:title="#string/update_room"
app:showAsAction="never" />
</menu>
Hope it help

With the new AppCompatActivity you should call it instead of ActionBarActivity:
((AppCompatActivity)getActivity()).setSupportActionBar(toolbar);

You can add toolbar in Fragments using this
((YOUR_ACTIVITY) getActivity()).getDelegate().setSupportActionBar(toolbar);

I use Kotlin. In my case Activity is a child class of AppCompatActivity and theme of activity is inherited from
Theme.MaterialComponents.DayNight.NoActionBar
So my Activity doesn't have action bar, but my Fragment do.
I will show you how to use toolbar with defined menu as a SupportActionBar in fragment
This is my Toolbar
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:fitsSystemWindows="true"
app:navigationContentDescription="Back to the previous question"
android:background="?attr/colorPrimary"
tools:title="#string/posts" />
This is my Fragment's methods:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(context as AppCompatActivity).setSupportActionBar(_bind?.toolbar)
setHasOptionsMenu(true)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.toolbar_menu_post_list, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when(item.itemId)
{
R.id.add -> {
val post = Post()
postListViewModel.addPost(post)
callbacks?.onItemSelected(post.id)
return true
}
else -> super.onOptionsItemSelected(item)
}
}

Related

How to animate Android Navigation Architecture fragment as sliding over old fragment?

In example navigation action defined in navigation graph:
<action
android:id="#+id/action_fragment1_to_fragment2"
app:destination="#id/fragment2"
app:enterAnim="#anim/right_slide_in"
app:popExitAnim="#anim/left_slide_out"/>
When Fragment2 opens and starts sliding into view from the right, Fragment1 disappears instantly (sadly). When Fragment2 is closed and starts sliding to the right, Fragment1 is nicely visible under it, giving a nice stack pop effect (comparable to iOS).
How can I keep Fragment1 visible while Fragment2 slides into view?
EDIT:
This is not the most elegant solution, it is actually a trick but it seems to be the best way to solve this situation until the NavigationComponent will include a better approach.
So, we can increase translationZ (starting with API 21) in Fragement2's onViewCreated method to make it appear above Fragment1.
Example:
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ViewCompat.setTranslationZ(getView(), 100f);
}
As very nice #xinaiz suggested, instead of 100f or any other random value, we can use getBackstackSize() to assign to the fragment a higher elevation than the previous one.
The solution was proposed by #JFrite at this thread
FragmentTransaction animation to slide in over top
More details can be found there.
In order to prevent the old fragment from disappearing during the sliding animation of the new fragment, first make an empty animation consisting of only the sliding animation's duration. I'll call it #anim/stationary:
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="#slidingAnimationDuration" />
Then in the navigation graph, set the exit animation of the action to the newly created empty animation:
<fragment android:id="#+id/oldFragment"
android:name="OldFragment">
<action android:id="#+id/action_oldFragment_to_newFragment"
app:destination="#id/newFragment"
app:enterAnim="#anim/sliding"
app:exitAnim="#anim/stationary"
</fragment>
The exit animation is applied to the old fragment and so the old fragment will be visible for the entire duration of the animation.
My guess as to why the old fragment disappears is if you don't specify an exit animation, the old fragment will be removed immediately by default as the enter animation begins.
It seems that you mistakenly used popExitAnim instead of exitAnim.
General rule is:
when you open (push) new screen, enterAnim and exitAnim take place
when you pop screen, popEnterAnim and popExitAnim take place
So, you should specify all 4 animations for each of your transitions.
For example, I use these:
<action
android:id="#+id/mainToSearch"
app:destination="#id/searchFragment"
app:enterAnim="#anim/slide_in_right"
app:exitAnim="#anim/slide_out_left"
app:popEnterAnim="#anim/slide_in_left"
app:popExitAnim="#anim/slide_out_right" />
Suppose your back stack currently contains:
A -> B -> C
and now from Fragment C, you want to navigate to Fragment D.
So your animation:
enterAnim -> Applied for D Fragment,
exitAnim -> Applied for C Fragment
Updated stack would be:
A -> B -> C -> D
Now you press the back or up button
popEnterAnim -> Applied for C Fragment,
popExitAnim -> Applied for D Fragment
now your back stack would be again:
A -> B -> C
TL;DR: enterAnim, exitAnim are for push, and popEnterAnim, popExitAnim are for pop operation.
I think using the R.anim.hold animation will create the effect you want:
int holdingAnimation = R.anim.hold;
int inAnimation = R.anim.right_slide_in;
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setCustomAnimations(inAnimation, holdingAnimation, inAnimation, holdingAnimation);
/*
... Add in your fragments and other navigation calls
*/
transaction.commit();
getSupportFragmentManager().executePendingTransactions();
Or just label it as you have within the action.
Here is the R.anim.hold animation mentioned above:
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="#android:integer/config_longAnimTime"
android:fromYDelta="0.0%p"
android:toYDelta="0.0%p"/>
</set>
In my own case the simplest solution was to use DialogFragment with proper animation and style.
Style:
<style name="MyDialogAnimation" parent="Animation.AppCompat.Dialog">
<item name="android:windowEnterAnimation">#anim/slide_in</item>
<item name="android:windowExitAnimation">#anim/slide_out</item>
</style>
<style name="MyDialog" parent="ThemeOverlay.MaterialComponents.Light.BottomSheetDialog">
<item name="android:windowIsFloating">false</item>
<item name="android:statusBarColor">#color/transparent</item>
<item name="android:windowAnimationStyle">#style/MyDialogAnimation</item>
</style>
Layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView 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="match_parent"
android:animateLayoutChanges="true"
android:background="#color/colorWhite"
android:fillViewport="true"
android:fitsSystemWindows="true"
android:layout_gravity="bottom"
android:orientation="vertical"
android:scrollbars="none"
android:transitionGroup="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout 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/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
// Your Ui here
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
Java:
public class MyFragmentDialog extends DialogFragment {
#Nullable
#Override
public View onCreateView(
#NonNull LayoutInflater inflater,
#Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_dialog, container, false);
}
#Override
public void onStart() {
super.onStart();
Dialog dialog = getDialog();
if (dialog != null) {
int width = ViewGroup.LayoutParams.MATCH_PARENT;
int height = ViewGroup.LayoutParams.MATCH_PARENT;
Objects.requireNonNull(dialog.getWindow())
.setFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
Objects.requireNonNull(dialog.getWindow()).setLayout(width, height);
dialog.getWindow().setWindowAnimations(R.style.MyDialogAnimation);
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NORMAL, R.style.MyDialog);
}
}
Adding a slide animation is very easy using the new material motion library. Make sure to use the material theme version 1.2.0 or later.
For example, if you want to navigate from FragmentA to FragmentB with a slide animation, follow the steps mentioned below.
In the onCreate() of FragmentA, add an exitTransition as shown below.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
exitTransition = MaterialFadeThrough().apply {
secondaryAnimatorProvider = null
}
}
In the onCreate() of FragmentB, add an enterTransition as shown below.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialFadeThrough().apply {
secondaryAnimatorProvider = SlideDistanceProvider(Gravity.END)
}
}
The above code will create an animation fading out FragmentA and sliding in FragmentB.
Why not use ViewPager?
It will take care of the animations and maintain the correct lifecycle of your fragments. You will be able to update fragments as they change from within onResume().
Once you have your ViewPager set up, you can change fragments by swiping, or automatically jump to a desired fragment without worrying about hand-coding transformations, translations, etc.: viewPager.setCurrentItem(1);
Examples and more in-depth description:
https://developer.android.com/training/animation/screen-slide
In your activity layout XML:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:fillViewport="true">
<include
layout="#layout/toolbar"
android:id="#+id/main_toolbar"
android:layout_width="fill_parent"
android:layout_height="?android:attr/actionBarSize">
</include>
<com.google.android.material.tabs.TabLayout
android:id="#+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/white"
android:minHeight="?android:attr/actionBarSize"/>
<androidx.viewpager.widget.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="fill_parent"/>
</LinearLayout>
In onCreate() of your Activity class:
ViewPager viewPager = null;
TabLayout tabLayout = null;
#Override
public void onCreate() {
...
tabLayout = findViewById(R.id.tab_layout);
viewPager = findViewById(R.id.pager);
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
String[] tabs = new String[]{"Tab 1", "Tab 2"};
for (String tab : tabs) {
tabLayout.addTab(tabLayout.newTab().setText(tab));
}
PagerAdapter adapter = new PagerAdapter(getSupportFragmentManager(), tabLayout);
viewPager.setAdapter(adapter);
viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
viewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
...
}
Your PagerAdapter class, which can reside within your Activity class:
public class PagerAdapter extends FragmentStatePagerAdapter {
TabLayout tabLayout;
PagerAdapter(FragmentManager fm, TabLayout tabLayout) {
super(fm);
this.tabLayout = tabLayout;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new your_fragment1();
case 1:
return new your_fragment2();
default:
return null;
}
return null;
}
#Override
public int getCount() {
return tabLayout.getTabCount();
}
}
Make sure to use the appropriate imports:
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.fragment.app.FragmentTransaction;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.tabs.TabLayout;

Embedded Fragment interaction

I have a fragment with 2 embedded fragments.
I would like to have embedded fragment1 interact with embedded fragment2.
For example I have a button inside embedded fragment1, when I press on said button I would like it to update a TextView inside embedded fragment2.
Here's my main "parent" fragment
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<fragment
android:id="#+id/fragment1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout="#layout/fragment1"/>
<fragment
android:id="#+id/fragment2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout="#layout/fragment2"/>
</LinearLayout>
Here is my fragment1 class onCreateView method:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
val mainView = inflater.inflate(R.layout.fragment1, container, false)
val button = mainView.findViewById<Button>(R.id.button)
button.setOnClickListener {
/* Update TextView inside Fragment2 */
}
return mainView
}
Here is my fragment2 class onCreateView method:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
val mainView = inflater.inflate(R.layout.fragment2, container, false)
val textView = mainView.findViewById<TextView>(R.id.textview)
return mainView
}
Read on this:
https://developer.android.com/training/basics/fragments/communicating
This is the standard way. Basically create the Fragment1 Interface for the Activity to implement; assign a reference to Activity in Fragment as that interface; then find the other fragment from the Activity to call the fragment method for updating the textView.
Edit:
In the case of Fragments within fragments, you can implement the interface in the parent fragment and attach like this:
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
callback = (SomeInterface) getParentFragment();
} catch (ClassCastException e) {
throw new ClassCastException("Parent fragment must implement SomeInterface");
}
}
However, as mentioned in the article, this is the old way. The modern way is to communicate between a shared ViewModel.

android toolbar inside a tab

I want to make a toolbar inside one of my tabs but nothing works for me. Can this even be done? I currently have a toolbar above my tabs but i need it to appear inside the first tab.
Here's the code (with the toolbar above tabs):
import ...
public class MainActivity extends ActionBarActivity {
Toolbar toolbar;
ViewPager pager;
ViewPagerAdapter adapter;
SlidingTabLayout tabs;
CharSequence Titles[] = {"Projects", "People", "Files"};
int Numboftabs = 3;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toolbar = (Toolbar) findViewById(R.id.tool_bar);
setSupportActionBar(toolbar);
adapter = new ViewPagerAdapter(getSupportFragmentManager(), Titles, Numboftabs);
pager = (ViewPager) findViewById(R.id.pager);
pager.setAdapter(adapter);
tabs = (SlidingTabLayout) findViewById(R.id.tabs);
tabs.setDistributeEvenly(true);
tabs.setViewPager(pager);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_primary, menu);
return true;
}
final ArrayList<String> listItems = new ArrayList<String>();
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.addButton) {
final TextView noProject = (TextView) findViewById(R.id.NOPROJECT);
final ListAdapter addAdapter = new ArrayAdapter<String>(this,
R.layout.list_item, R.id.listFrame, listItems);
final ListView lv = (ListView) findViewById(R.id.lv);
lv.setAdapter(addAdapter);
noProject.setVisibility(View.GONE);
lv.setVisibility(View.VISIBLE);
listItems.add("New Project");
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent switchToEdit = new Intent(MainActivity.this,
TeamCreate.class);
startActivity(switchToEdit);
}
});
}
return super.onOptionsItemSelected(item);
}
}
First tab:
import ...
public class Tab1 extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View v =inflater.inflate(R.layout.tab_1,container,false);
return v;
}
}
I faced a similar challenge with a non-standard toolbar and tabs and got past it, though frankly the results were odd looking. You can probably get the toolbar to work under the tab using a similar technique, however it may leave your users scratching their heads. Your technique will have 2 toolbars, one holding the TabLayout widget and another one inside the fragment(s) in the ViewPager.
I was using Material Design tabs for the Detail view in my app. It looked fine until I did the 2-pane Master-Detail view for tablets. Then I had an issue with the tabs wanting to be directly under the toolbar, but no obvious way to have the Master & Detail share the toolbar with the tabs only showing up under the Detail portion of the screen.
I got it working by:
Create a "NoActionBar" style in styles.xml, then refer to it in your main layout using "android:theme="#style/AppTheme".
<!-- Turn off normal application bar so we can put our toolbars where we please.
Do this by replacing "Theme.AppCompat.Light.DarkActionBar" with NoActionBar
per http://www.truiton.com/2015/04/android-action-bar-dialog-using-toolbar/ -->
<style name="AppTheme" parent="MaterialTheme.Base"/>
<style name="MaterialTheme.Base" parent="Theme.AppCompat.Light.NoActionBar">
<item name="windowNoTitle">true</item>
<!--We will be using the toolbar so no need to show ActionBar-->
<item name="windowActionBar">false</item>
Toss android.support.design.widget.CoordinatorLayout and just use a LinearLayout.
Here is a snippet that has the toolbar containing the tabs only taking up part of the screen real estate rather than the entire width. Note that the toolbar has "app:layout_scrollFlags="scroll|enterAlways":
<FrameLayout android:id="#+id/detail_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_gravity="end"
android:layout_weight="2"
tools:name="com.android.bazemom.popularmovies.TabContainerFragment"
>
<!-- second toolbar that will be used for the tabs -->
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
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"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways"
/>
<android.support.design.widget.TabLayout
android:id="#+id/tabanim_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<!-- Helps handing the Fragments to load for each Tab -->
<android.support.v4.view.ViewPager
android:id="#+id/tabanim_viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
Here is what the Master-Detail view looked like on wide tablet. The tabs are circled in green. The toolbar with an action is above the tabs.

How to Display Navigation Drawer in all activities?

I have a Navigation Drawer which should appear in all my activities.
I saw many questions similar to this & found a solution like Extending the MainActivity with the Other Activities .
So i extended My Main Activity to my Second Activity.But the Drawer is not being showed in the Second Activity
MainActivity
public class MainActivity extends ActionBarActivity
{
private ListView mDrawerList;
private DrawerLayout mDrawer;
private CustomActionBarDrawerToggle mDrawerToggle;
private String[] menuItems;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
supportRequestWindowFeature(WindowCompat.FEATURE_ACTION_BAR);
// getSupportActionBar().hide();
setContentView(R.layout.activity_main_drawer);
// enable ActionBar app icon to behave as action to toggle nav drawer
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
mDrawer = (DrawerLayout) findViewById(R.id.drawer_layout);
// set a custom shadow that overlays the main content when the drawer
// opens
mDrawer.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
_initMenu();
mDrawerToggle = new CustomActionBarDrawerToggle(this, mDrawer);
mDrawer.setDrawerListener(mDrawerToggle);
}
private void _initMenu()
{
NsMenuAdapter mAdapter = new NsMenuAdapter(this);
// Add Header
mAdapter.addHeader(R.string.ns_menu_main_header);
// Add first block
menuItems = getResources().getStringArray(R.array.ns_menu_items);
String[] menuItemsIcon = getResources().getStringArray(R.array.ns_menu_items_icon);
int res = 0;
for (String item : menuItems)
{
int id_title = getResources().getIdentifier(item, "string", this.getPackageName());
int id_icon = getResources().getIdentifier(menuItemsIcon[res], "drawable", this.getPackageName());
NsMenuItemModel mItem = new NsMenuItemModel(id_title, id_icon);
// if (res==1) mItem.counter=12; //it is just an example...
// if (res==3) mItem.counter=3; //it is just an example...
mAdapter.addItem(mItem);
res++;
}
mAdapter.addHeader(R.string.ns_menu_main_header2);
mDrawerList = (ListView) findViewById(R.id.drawer);
if (mDrawerList != null)
mDrawerList.setAdapter(mAdapter);
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
}
#Override
protected void onPostCreate(Bundle savedInstanceState)
{
super.onPostCreate(savedInstanceState);
// Sync the toggle state after onRestoreInstanceState has occurred.
mDrawerToggle.syncState();
}
#Override
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
mDrawerToggle.onConfigurationChanged(newConfig);
}
#Override
public boolean onCreateOptionsMenu(Menu menu)
{
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.control_menu, menu);
return super.onCreateOptionsMenu(menu);
}
/* Called whenever we call invalidateOptionsMenu() */
#Override
public boolean onPrepareOptionsMenu(Menu menu)
{
// If the nav drawer is open, hide action items related to the content
// view
boolean drawerOpen = mDrawer.isDrawerOpen(mDrawerList);
menu.findItem(R.id.action_keyboard).setVisible(!drawerOpen);
return super.onPrepareOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
/*
* The action bar home/up should open or close the drawer.
* ActionBarDrawerToggle will take care of this.
*/
if (mDrawerToggle.onOptionsItemSelected(item))
{
return true;
}
// Handle your other action bar items...
return super.onOptionsItemSelected(item);
}
private class CustomActionBarDrawerToggle extends ActionBarDrawerToggle
{
public CustomActionBarDrawerToggle(Activity mActivity, DrawerLayout mDrawerLayout)
{
super(mActivity, mDrawerLayout, R.drawable.ic_drawer, R.string.ns_menu_open, R.string.ns_menu_close);
}
#Override
public void onDrawerClosed(View view)
{
getSupportActionBar().setTitle(getString(R.string.ns_menu_close));
supportInvalidateOptionsMenu(); // creates call to
// onPrepareOptionsMenu()
}
#Override
public void onDrawerOpened(View drawerView)
{
getSupportActionBar().setTitle(getString(R.string.ns_menu_open));
supportInvalidateOptionsMenu(); // creates call to
// onPrepareOptionsMenu()
}
}
private class DrawerItemClickListener implements ListView.OnItemClickListener
{
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
Intent intent = new Intent(MainActivity.this, Tutorial.class);
startActivity(intent);
}
}
}
SecondActivity
public class Tutorial extends MainActivity
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.help);
}
}
Here is my implementation.. hope it help
FIRST, this POST is concept.
SECOND, this is also the KEY one.
FINALLY, Here is combination of all answer in one place
BASE ACTIVITY
This is a base activity for all other activity
You can extends Activity or FragmentActivity or etc. base on your requirement.
Navigation Drawer setup here for one time.
public class BaseActivity extends FragmentActivity {
protected DrawerLayout mDrawer;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.base_layout);
mDrawer = (DrawerLayout) findViewById(R.id.drawer_layout);
//This is about creating custom listview for navigate drawer
//Implementation for NavigateDrawer HERE !
ArrayList<DrawerListItem> drawerListItems = new ArrayList<DrawerListItem>();
drawerListItems.add(new DrawerListItem(0,"AIRĀ° DEVICES"));
drawerListItems.add(new DrawerListItem(1,"A/C Device [1]"));
drawerListItems.add(new DrawerListItem(1,"A/C Device [2]"));
drawerListItems.add(new DrawerListItem(1,"A/C Device [3]"));
drawerListItems.add(new DrawerListItem(0,"AIRĀ° FEATURES"));
drawerListItems.add(new DrawerListItem(2,"SLEEP MODE"));
drawerListItems.add(new DrawerListItem(2,"TRACKING MODE"));
drawerListItems.add(new DrawerListItem(2,"SETTINGS"));
DrawerAdapter mDrawerAdapter = new DrawerAdapter(this, R.layout.drawer_list_header, drawerListItems);
ListView mDrawerList = (ListView) findViewById(R.id.left_drawer);
mDrawerList.setAdapter(mDrawerAdapter);
}
}
BASE ACTIVITY XML
This xml layout is for Navigation Drawer
<?xml version="1.0" encoding="utf-8"?>
<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">
<FrameLayout
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
<!-- The navigation drawer -->
<ListView
android:id="#+id/left_drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="left"
android:scrollingCache="false"
android:background="#drawable/drawer_bg"
android:divider="#null"
android:choiceMode="singleChoice"/>
</android.support.v4.widget.DrawerLayout>
ALL OTHERS ACTIVITY
Other Activity just extends BaseActivity and define code as below.
The Navigation Drawer will appear for particular activity.
mDrawer is form BaseActivity. it's a protected variable.
public class Screen1 extends BaseActivity
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//inflate your activity layout here!
View contentView = inflater.inflate(R.layout.screen1, null, false);
mDrawer.addView(contentView, 0);
//Do the rest as you want for each activity
}
SCREEN 1 XML SAMPLE
Design as you wish it each activity. no more Navigation Drawer Layout !
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>
NOTE
In this implementation, The Navigation Drawer doesn't bind with Action Bar. If you wish to do that do it in BaseActivity.Also, This guide is not cover all requirement. It's just a sample.
in onCreate of TutorialActivity don't call setContentView instead do this:
#Override
protected void onCreate(Bundle savedInstanceState)
{
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
LayoutInflater inflater = (LayoutInflater) this
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View contentView = inflater.inflate(R.layout.help, null, false);
mDrawer.addView(contentView, 0);
}
make mDrawer in MainActivity protected. and in R.layout.activity_main_drawer just keep drawer tag and the element with gravity left(or right).
I made a BaseActivity activity which extends SherlockActivity (or ActionBarActivity if is your case)
public class BaseActivity extends SherlockActivity
Then, make all your activities extends BaseActivity, like:
public class GlossaryActivity extends BaseActivity
Later, you must replace the activity layout with the one that correspond to your activity, I made a method in BaseActivity like that:
protected void replaceContentLayout(int sourceId, int destinationId) {
View contentLayout = findViewById(destinationId);
ViewGroup parent = (ViewGroup) contentLayout.getParent();
int index = parent.indexOfChild(contentLayout);
parent.removeView(contentLayout);
contentLayout = getLayoutInflater().inflate(sourceId, parent, false);
parent.addView(contentLayout, index);
}
I called this method on the onCreate method in each activity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.replaceContentLayout(R.layout.activity_glossary, super.CONTENT_LAYOUT_ID);
}
super.CONTENT_LAYOUT_ID is the FrameLayout of the BaseActivity, and other param is the layout you want replace with
You omitted the #Override from your derived class onCreate.
UPDATE: I'm not sure what the effects are of calling setContentView twice but that could be the problem. Separate out the code that sets up the drawer, and call that from both of the onCreate methods.
I had this problem too. This is my implementation:
activity_main.xml - the child at index 1 in the CoordinatorLayout is the content_main.xml, this I can change in code.
<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">
<android.support.design.widget.CoordinatorLayout
android:id="#+id/coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
**<include layout="#layout/content_main" />**
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="#dimen/fab_margin"
android:src="#android:drawable/ic_dialog_email" />
</android.support.design.widget.CoordinatorLayout>
<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_main"
app:menu="#menu/activity_main_drawer" />
</android.support.v4.widget.DrawerLayout>
I've created a class that uses inflates the others activities UI:
public class MyLayoutInflater {
public void inflate(Activity activity, int LayoutResource, android.app.ActionBar getSupportActionBar, Intent getIntent){
CoordinatorLayout coordinatorLayout = (CoordinatorLayout) activity.findViewById(R.id.coordinator);
android.view.LayoutInflater inflater = (android.view.LayoutInflater) activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View contentView = inflater.inflate(LayoutResource, null, false);
//change i so that it suits the child number in you coordinator layout
int i = 1;
coordinatorLayout.removeViewAt(i);
coordinatorLayout.addView(contentView, i);
getSupportActionBar.setTitle(actionBarTitle);
}
public void inflate(Activity activity, int LayoutResource, android.support.v7.app.ActionBar getActionBar, String actionBarTitle){
CoordinatorLayout coordinatorLayout = (CoordinatorLayout) activity.findViewById(R.id.coordinator);
android.view.LayoutInflater inflater = (android.view.LayoutInflater) activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View contentView = inflater.inflate(LayoutResource, null, false);
//change i so that it suits the child number in you coordinator layout
int i = 1;
coordinatorLayout.removeViewAt(i);
coordinatorLayout.addView(contentView, i);
getActionBar.setTitle(actionBarTitle);
}
}
Now on the other activities all you have to do is extend the MainActivity and call this method and give it the necessary parameters:
public class AnotherActivity extends MainActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new MyLayoutInflater().inflate(this,R.layout.content_activity_another, getSupportActionBar(), getIntent());
}
}
Ok here is hacky way to do this, I use it only for special kind of debug build to set properties of views in realtime (design tool).
It has advantage that you can use your child activities as usual without, special behavior that is required in different answers.
so in BaseActvity you can add:
#Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// WARNING: Hacky, use carefully!!!
ViewGroup androidBaseView = (ViewGroup) findViewById(android.R.id.content);
//this one in what child activity has just set in setContentView()
ViewGroup childContent = (ViewGroup) androidBaseView.getChildAt(0);
View drawerView = LayoutInflater.from(this)
.inflate(R.layout.base_activity_drawer, androidBaseView, false);
FrameLayout frameLayout = (FrameLayout) drawerView.findViewById(R.id.content);
androidBaseView.removeView(childContent);
frameLayout.addView(childContent);
androidBaseView.addView(drawerView);
}
and xml for drawer is just:
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/nav_drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<FrameLayout
android:id="#+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<LinearLayout
android:id="#+id/drawer_for_components"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="end"
android:orientation="vertical"
android:fitsSystemWindows="true"
/>
</android.support.v4.widget.DrawerLayout>
Here is a simple and fast way to do it in android studio:
Create a new activity (Navigation drawer activity) from the gallery, and name it whatever you want, android studio will create everything for you (the class and the xml files that you can customize it later)
In other activities you should extend your Navigation drawer activity, and make sure these other activities has "no action bar" in the manifests file (android:theme="#style/AppTheme.NoActionBar")
You should modify your other activities as follows:
public class Mainactivity extends NavActivity
{
super.onCreate(savedInstanceState);
LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//inflate your activity layout here!
View contentView = inflater.inflate(R.layout.activity_main, null, false);
drawer.addView(contentView, 0);
}
Note: the mainactivity will extend the action bar of the NavActivity, the NavActivity has a full functional action bar that will call the navigation drawer
I hope it will work with you
Nowadays you should use Single-Activity App Architecture (source).
Then simple add Navigation Drawer to Main Activity
you can simply use <include/>
By creating a nav drawer
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.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">
and then include the layout in it
<include
layout="#layout/activity_accounts"
android:layout_width="match_parent"
android:layout_height="match_parent" />
in your main activity make setContentView(R.layout.your_drawer_activity)
take note that if you use this method you have to create a nav drawer layout for every activity you have, unless you found a way to do includes programmatically.

Android crashing at getSupportFragmentManager()

I decided to switch from multiple activities to one activity which switches between fragments however the application now crashes.
Here is the activity I am adding the fragment to
public class MainActivity extends SherlockFragmentActivity{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyFragment fragment = new MyFragment();
fragment.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, fragment).commit();
}
Here is the fragment its an observer and has functionality but to save space ill just show the creation
public class MyFragment extends SherlockFragment implements Observer{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.my_fragment, container, false);
}
Heres my_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/my_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" >
... HAS SOME TEXT VIEWS HERE!
</RelativeLayout>
The crash I get is
02-15 16:17:41.079: E/AndroidRuntime(18668): FATAL EXCEPTION: main
02-15 16:17:41.079: E/AndroidRuntime(18668): java.lang.RuntimeException:
Unable to start activity ComponentInfo{com.example.myapp/com.example.myapp.MainActivity}:
java.lang.IllegalArgumentException:
No view found for id 0x7f040036 for fragment MyFragmentt{41a05910 #0 id=0x7f040036}
Can anyone help me out here? I can't really figure out what is causing this. I know if I comment out getSupportFragmentManager() in main activity (the top code block in this post) it will run just not draw anything in my fragment.
UPDATE
The frame_container which I'm not sure where to place
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
So the way you can use Fragments in your applications are two.
First way is if you declare the Fragment in your xml file like this :
<fragment android:name="com.example.news.ArticleReaderFragment"
android:id="#+id/viewer"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent" />
The second way is if you add / replace your Fragments dynamically to your container which in the most examples is FrameLayout. Here is how you can do that :
In your main FragmentActivity :
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.myFragmentContainer);
}
and in your xml myFragmentContainer.xml is where you place your fragment_container and it looks like :
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
and you are adding and replacing your Fragments like this :
if (findViewById(R.id.fragment_container) != null) {
if (savedInstanceState != null) {
return;
}
// Create an instance of ExampleFragment
HeadlinesFragment firstFragment = new HeadlinesFragment();
// if there are any extras
firstFragment.setArguments(getIntent().getExtras());
// Add the fragment to the 'fragment_container' FrameLayout
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, firstFragment).commit();
}
and for the next Fragment which you want to show just do :
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, secondFragment).commit();

Categories