├── LICENSE.txt ├── README.md ├── globals.xml.ftl ├── recipe.xml.ftl ├── root ├── AndroidManifest.xml.ftl ├── build.gradle.ftl ├── res │ ├── drawable │ │ ├── view_badge_selected.xml.ftl │ │ └── view_badge_un_selected.xml.ftl │ ├── layout │ │ ├── activity_tabs.xml.ftl │ │ ├── fragment_first.xml.ftl │ │ ├── fragment_second.xml.ftl │ │ ├── fragment_tabs.xml.ftl │ │ ├── fragment_third.xml.ftl │ │ ├── include_tabs_bottom.xml.ftl │ │ ├── include_tabs_top.xml.ftl │ │ ├── include_view_tabs_badge_bottom.xml.ftl │ │ ├── include_view_tabs_badge_top.xml.ftl │ │ ├── include_view_tabs_bottom.xml.ftl │ │ ├── include_view_tabs_top.xml.ftl │ │ └── view_tabs.xml.ftl │ ├── menu │ │ └── menu_search.xml │ └── values │ │ ├── strings.xml.ftl │ │ ├── styles.xml.ftl │ │ └── templates_colors.xml.ftl └── src │ └── app_package │ ├── AbstractModel.java.ftl │ ├── BaseFragment.java.ftl │ ├── FirstFragment.java.ftl │ ├── FragNavController.java.ftl │ ├── FragNavTransactionOptions.java.ftl │ ├── FragmentHistory.java.ftl │ ├── SecondFragment.java.ftl │ ├── TabsActivity.java.ftl │ ├── TabsFragment.java.ftl │ ├── ThirdFragment.java.ftl │ ├── include_bottom_tab_activity.java.ftl │ ├── include_bottom_tab_stack_activity.java.ftl │ └── include_top_tab_activity.java.ftl ├── template.xml └── templates_adapter.png /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Chandrasekar K 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so without any conditions and terms. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MaterialTabsTemplate 2 | 3 | 4 | Why this Repo? 5 | -------------- 6 | 7 | MaterialTabsTemplate is solely created to reduce the burden of writing same boiler plate codes for Tab creation in Android. 8 | 9 | Following are the possible styles of Tabs that can be generated from MaterialTabsTemplate. 10 | 11 | #### Simple Tabs (Top & Bottom) 12 | 13 | 14 | 15 | 16 | #### Icon Tabs (Top & Bottom) 17 | 18 | 19 | 20 | 21 | #### Icon with Text Tabs (Top & Bottom) 22 | 23 | 24 | 25 | 26 | #### Icon with Badge Tabs (Top & Bottom) 27 | 28 | 29 | 30 | Implementation 31 | -------------- 32 | 33 | 1. Download or clone this repo 34 | 35 | 36 | 37 | 2. Rename the downloaded folder to MaterialTabsTemplate and copy the complete folder. 38 | 39 | 40 | 41 | Paste the selected files into 42 | 43 | **For Mac** 44 | 45 | Go to Applications, Choose Android Studio, Right click and select **Show Package Contents** options. 46 | 47 | 48 | 49 | > Navigate to > Contents - Plugins - android - lib - templates - other - paste the downloaded MaterialTabsTemplate folder 50 | 51 | **For Windows** 52 | 53 | Go to C - Program Files - Android - Android Studio 54 | 55 | ``` 56 | C:\Program Files\Android\Android Studio\ 57 | ``` 58 | 59 | > Navigate to Plugins - android - lib - templates - other - paste the downloaded MaterialTabsTemplate folder 60 | 61 | 62 | 63 | 64 | 65 | Usages 66 | ------ 67 | 68 | It is easy and pretty straight forward 69 | 70 | > - Open your project in Android Studio. 71 | > - Right click on your project root package. 72 | > - Navigate to New - Other - MaterialTabsTemplate 73 | 74 | 75 | 76 | And This is how Template Wizard looks like! 77 | 78 | 79 | 80 | 81 | Why TabLayout instead of Design support BottomNavigationView? 82 | ------------------------------------------------------------- 83 | I have plenty of reasons for the same as below: 84 | 85 | > - It’s not flexible. 86 | > - Random behaviour if there are less tab items. 87 | > - Customising text sizes and icon sizes are like a nightmare (Just try adding small sized icon for one tab and big sized icon for the other ;-) ). 88 | 89 | Further article on https://blog.f22labs.com/instagram-like-bottom-tab-fragment-transaction-android-389976fb8759 90 | 91 | Also refer RecyclerViewTemplate: 92 | 93 | https://github.com/TakeoffAndroid/RecyclerViewTemplate 94 | 95 | 96 | Contributions 97 | ------------- 98 | 99 | Pull requests and contributions are most welcome. 100 | 101 | > Any type of Tabs layout on mind? You can always drop a mail!. 102 | -------------------------------------------------------------------------------- /globals.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /recipe.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <#if !(hasDependency('com.android.support:appcompat-v7'))> 7 | 8 | 9 | 10 | 11 | <#if !(hasDependency('com.android.support:design'))> 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 29 | 30 | <#if isToolbar> 31 | 33 | 34 | 35 | 36 | 37 | 38 | 40 | 41 | 42 | 43 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 55 | 56 | 58 | 59 | 61 | 62 | 64 | 65 | 67 | 68 | 70 | 71 | 73 | 74 | 75 | <#if tabstyle == 'badgewithicons'> 76 | 77 | 79 | 80 | 82 | 83 | 84 | 86 | 87 | 88 | 89 | <#if tabposition == 'bottom' && isFragmentHistory> 90 | 91 | 93 | 94 | 96 | 97 | 99 | 100 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /root/AndroidManifest.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | android:label="@string/app_name" 7 | <#else> 8 | <#if isToolbar> 9 | android:theme="@style/AppTheme.Template.Base" 10 | <#else> 11 | android:theme="@style/AppTheme" 12 | 13 | > 14 | <#if isLauncher> 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /root/build.gradle.ftl: -------------------------------------------------------------------------------- 1 | dependencies { 2 | <#if dependencyList?? > 3 | <#list dependencyList as dependency> 4 | compile '${dependency}' 5 | 6 | 7 | } 8 | -------------------------------------------------------------------------------- /root/res/drawable/view_badge_selected.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /root/res/drawable/view_badge_un_selected.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /root/res/layout/activity_tabs.xml.ftl: -------------------------------------------------------------------------------- 1 | <#if tabposition == 'top'> 2 | <#include "include_tabs_top.xml.ftl"/> 3 | <#else> 4 | <#include "include_tabs_bottom.xml.ftl"/> 5 | 6 | -------------------------------------------------------------------------------- /root/res/layout/fragment_first.xml.ftl: -------------------------------------------------------------------------------- 1 | 5 | 6 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /root/res/layout/fragment_second.xml.ftl: -------------------------------------------------------------------------------- 1 | 5 | 6 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /root/res/layout/fragment_tabs.xml.ftl: -------------------------------------------------------------------------------- 1 | <#include "include_tabs_top.xml.ftl"/> 2 | -------------------------------------------------------------------------------- /root/res/layout/fragment_third.xml.ftl: -------------------------------------------------------------------------------- 1 | 5 | 6 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /root/res/layout/include_tabs_bottom.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | <#if isToolbar> 8 | 9 | 14 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 35 | 36 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /root/res/layout/include_tabs_top.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | <#if isToolbar> 13 | 14 | 21 | 22 | 23 | 27 | android:layout_height="?attr/actionBarSize" 28 | <#else> 29 | android:layout_height="wrap_content" 30 | 31 | app:tabSelectedTextColor="@android:color/white" 32 | app:tabTextColor="#80FFFFFF" 33 | app:tabGravity="fill" 34 | app:tabMode="fixed" /> 35 | 36 | 37 | 38 | 43 | 44 | -------------------------------------------------------------------------------- /root/res/layout/include_view_tabs_badge_bottom.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 15 | 16 | 21 | 22 | 23 | 30 | 31 | 32 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /root/res/layout/include_view_tabs_badge_top.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 15 | 16 | 21 | 22 | 23 | 30 | 31 | 32 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /root/res/layout/include_view_tabs_bottom.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | <#if tabstyle == 'icons'> 11 | 12 | 16 | 17 | 18 | 19 | <#if tabstyle == 'iconswithtext'> 20 | 21 | 25 | 26 | 27 | 28 | 29 | <#if tabstyle == 'simple'> 30 | 31 | 32 | 39 | 40 | 41 | 42 | <#if tabstyle == 'iconswithtext'> 43 | 44 | 45 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /root/res/layout/include_view_tabs_top.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | <#if tabstyle == 'icons'> 11 | 12 | 16 | 17 | 18 | 19 | <#if tabstyle == 'iconswithtext'> 20 | 21 | 25 | 26 | 27 | 28 | 29 | <#if tabstyle == 'simple'> 30 | 31 | 32 | 39 | 40 | 41 | 42 | <#if tabstyle == 'iconswithtext'> 43 | 44 | 45 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /root/res/layout/view_tabs.xml.ftl: -------------------------------------------------------------------------------- 1 | <#if tabposition == 'top' && tabstyle == 'badgewithicons'> 2 | <#include "include_view_tabs_badge_top.xml.ftl"/> 3 | <#elseif tabposition == 'top'> 4 | <#include "include_view_tabs_top.xml.ftl"/> 5 | 6 | <#elseif tabposition == 'bottom' && tabstyle == 'badgewithicons'> 7 | <#include "include_view_tabs_badge_bottom.xml.ftl"/> 8 | 9 | <#elseif tabposition == 'bottom'> 10 | <#include "include_view_tabs_bottom.xml.ftl"/> 11 | 12 | -------------------------------------------------------------------------------- /root/res/menu/menu_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /root/res/values/strings.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | <#if !isNewProject> 3 | ${escapeXmlString(activityTitle)} 4 | 5 | 6 | Hello world! 7 | 8 | 9 | -------------------------------------------------------------------------------- /root/res/values/styles.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 20 | 21 | 22 | 23 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /root/res/values/templates_colors.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | @color/blue_light 4 | @color/blue_light 5 | @color/green 6 | 7 | 8 | #212121 9 | #727272 10 | #009688 11 | 12 | #F2F2F2 13 | 14 | #FFFFFF 15 | #80FFFFFF 16 | 17 | #00000000 18 | #66000000 19 | #BF000000 20 | #80000000 21 | #40000000 22 | #5f8ee4 23 | 24 | #6CC887 25 | 26 | 27 | 28 | #16000000 29 | #24000000 30 | 31 | -------------------------------------------------------------------------------- /root/src/app_package/AbstractModel.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}; 2 | import java.util.ArrayList; 3 | 4 | public class ${adapterModelClass} { 5 | 6 | private String title; 7 | 8 | private String message; 9 | 10 | <#if features == 'googleplay'> 11 | private ArrayList<${adapterModelClass}> singleItemModelArrayList; 12 | 13 | 14 | public String getTitle() { 15 | return title; 16 | } 17 | 18 | public void setTitle(String title) { 19 | this.title = title; 20 | } 21 | 22 | 23 | public String getMessage() { 24 | return message; 25 | } 26 | 27 | public void setMessage(String message) { 28 | this.message = message; 29 | } 30 | 31 | <#if features == 'googleplay'> 32 | 33 | public ArrayList<${adapterModelClass}> getSingleItemArrayList() { 34 | return singleItemModelArrayList; 35 | } 36 | 37 | public void setSingleItemArrayList(ArrayList<${adapterModelClass}> singleItemModelArrayList) { 38 | this.singleItemModelArrayList = singleItemModelArrayList; 39 | } 40 | 41 | 42 | 43 | 44 | public ${adapterModelClass}(String title, String message) { 45 | this.title = title; 46 | this.message = message; 47 | } 48 | 49 | <#if features == 'googleplay'> 50 | 51 | public ${adapterModelClass}(String title, String message, ArrayList<${adapterModelClass}> singleItemModelArrayList) { 52 | this.title = title; 53 | this.message = message; 54 | this.singleItemModelArrayList = singleItemModelArrayList; 55 | } 56 | 57 | 58 | 59 | public ${adapterModelClass}() { 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /root/src/app_package/BaseFragment.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.app.Fragment; 7 | 8 | 9 | public class BaseFragment extends Fragment { 10 | 11 | 12 | FragmentNavigation mFragmentNavigation; 13 | 14 | @Override 15 | public void onCreate(@Nullable Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | 18 | } 19 | 20 | 21 | @Override 22 | public void onAttach(Context context) { 23 | super.onAttach(context); 24 | if (context instanceof FragmentNavigation) { 25 | mFragmentNavigation = (FragmentNavigation) context; 26 | } 27 | } 28 | 29 | public interface FragmentNavigation { 30 | void pushFragment(Fragment fragment); 31 | } 32 | 33 | 34 | 35 | 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /root/src/app_package/FirstFragment.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}; 2 | 3 | <#if applicationPackage??> 4 | import ${applicationPackage}.R; 5 | 6 | 7 | import android.os.Bundle; 8 | import android.support.v4.app.Fragment; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | 13 | public class ${tabName1} extends Fragment{ 14 | 15 | public ${tabName1}() { 16 | // Required empty public constructor 17 | } 18 | 19 | 20 | public static ${tabName1} newInstance() { 21 | 22 | Bundle args = new Bundle(); 23 | ${tabName1} fragment = new ${tabName1}(); 24 | fragment.setArguments(args); 25 | return fragment; 26 | } 27 | 28 | @Override 29 | public void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | } 32 | 33 | @Override 34 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 35 | Bundle savedInstanceState) { 36 | // Inflate the layout for this fragment 37 | return inflater.inflate(R.layout.${tabLayoutName1}, container, false); 38 | } 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /root/src/app_package/FragNavController.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.CheckResult; 5 | import android.support.annotation.IdRes; 6 | import android.support.annotation.IntDef; 7 | import android.support.annotation.NonNull; 8 | import android.support.annotation.Nullable; 9 | import android.support.v4.app.DialogFragment; 10 | import android.support.v4.app.Fragment; 11 | import android.support.v4.app.FragmentManager; 12 | import android.support.v4.app.FragmentTransaction; 13 | import android.support.v4.util.Pair; 14 | import android.view.View; 15 | 16 | import org.json.JSONArray; 17 | 18 | import java.lang.annotation.Retention; 19 | import java.lang.annotation.RetentionPolicy; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.Stack; 23 | 24 | /** 25 | * The class is used to manage navigation through multiple stacks of fragments, as well as coordinate 26 | * fragments that may appear on screen 27 | *

28 | * https://github.com/ncapdevi/FragNav 29 | * Nic Capdevila 30 | * Nic.Capdevila@gmail.com 31 | *

32 | * Originally Created March 2016 33 | */ 34 | @SuppressWarnings("RestrictedApi") 35 | public class FragNavController { 36 | //Declare the constants There is a maximum of 5 tabs, this is per Material Design's Bottom Navigation's design spec. 37 | public static final int NO_TAB = -1; 38 | public static final int TAB1 = 0; 39 | public static final int TAB2 = 1; 40 | public static final int TAB3 = 2; 41 | public static final int TAB4 = 3; 42 | public static final int TAB5 = 4; 43 | 44 | private static final int MAX_NUM_TABS = 5; 45 | 46 | // Extras used to store savedInstanceState 47 | private static final String EXTRA_TAG_COUNT = FragNavController.class.getName() + ":EXTRA_TAG_COUNT"; 48 | private static final String EXTRA_SELECTED_TAB_INDEX = FragNavController.class.getName() + ":EXTRA_SELECTED_TAB_INDEX"; 49 | private static final String EXTRA_CURRENT_FRAGMENT = FragNavController.class.getName() + ":EXTRA_CURRENT_FRAGMENT"; 50 | private static final String EXTRA_FRAGMENT_STACK = FragNavController.class.getName() + ":EXTRA_FRAGMENT_STACK"; 51 | 52 | @IdRes 53 | private final int mContainerId; 54 | @NonNull 55 | private final List> mFragmentStacks; 56 | @NonNull 57 | private final FragmentManager mFragmentManager; 58 | private final FragNavTransactionOptions mDefaultTransactionOptions; 59 | @TabIndex 60 | private int mSelectedTabIndex; 61 | private int mTagCount; 62 | @Nullable 63 | private Fragment mCurrentFrag; 64 | @Nullable 65 | private DialogFragment mCurrentDialogFrag; 66 | @Nullable 67 | private RootFragmentListener mRootFragmentListener; 68 | @Nullable 69 | private TransactionListener mTransactionListener; 70 | private boolean mExecutingTransaction; 71 | 72 | //region Construction and setup 73 | 74 | private FragNavController(Builder builder, @Nullable Bundle savedInstanceState) { 75 | mFragmentManager = builder.mFragmentManager; 76 | mContainerId = builder.mContainerId; 77 | mFragmentStacks = new ArrayList<>(builder.mNumberOfTabs); 78 | mRootFragmentListener = builder.mRootFragmentListener; 79 | mTransactionListener = builder.mTransactionListener; 80 | mDefaultTransactionOptions = builder.mDefaultTransactionOptions; 81 | mSelectedTabIndex = builder.mSelectedTabIndex; 82 | 83 | //Attempt to restore from bundle, if not, initialize 84 | if (!restoreFromBundle(savedInstanceState, builder.mRootFragments)) { 85 | 86 | for (int i = 0; i < builder.mNumberOfTabs; i++) { 87 | Stack stack = new Stack<>(); 88 | if (builder.mRootFragments != null) { 89 | stack.add(builder.mRootFragments.get(i)); 90 | } 91 | mFragmentStacks.add(stack); 92 | } 93 | 94 | initialize(builder.mSelectedTabIndex); 95 | } 96 | } 97 | 98 | public static Builder newBuilder(@Nullable Bundle savedInstanceState, FragmentManager fragmentManager, int containerId) { 99 | return new Builder(savedInstanceState, fragmentManager, containerId); 100 | } 101 | 102 | //endregion 103 | 104 | //region Transactions 105 | 106 | /** 107 | * Function used to switch to the specified fragment stack 108 | * 109 | * @param index The given index to switch to 110 | * @param transactionOptions Transaction options to be displayed 111 | * @throws IndexOutOfBoundsException Thrown if trying to switch to an index outside given range 112 | */ 113 | public void switchTab(@TabIndex int index, @Nullable FragNavTransactionOptions transactionOptions) throws IndexOutOfBoundsException { 114 | //Check to make sure the tab is within range 115 | if (index >= mFragmentStacks.size()) { 116 | throw new IndexOutOfBoundsException("Can't switch to a tab that hasn't been initialized, " + 117 | "Index : " + index + ", current stack size : " + mFragmentStacks.size() + 118 | ". Make sure to create all of the tabs you need in the Constructor or provide a way for them to be created via RootFragmentListener."); 119 | } 120 | if (mSelectedTabIndex != index) { 121 | mSelectedTabIndex = index; 122 | 123 | FragmentTransaction ft = createTransactionWithOptions(transactionOptions); 124 | 125 | detachCurrentFragment(ft); 126 | 127 | Fragment fragment = null; 128 | if (index == NO_TAB) { 129 | ft.commit(); 130 | } else { 131 | //Attempt to reattach previous fragment 132 | fragment = reattachPreviousFragment(ft); 133 | if (fragment != null) { 134 | ft.commit(); 135 | } else { 136 | fragment = getRootFragment(mSelectedTabIndex); 137 | ft.add(mContainerId, fragment, generateTag(fragment)); 138 | ft.commit(); 139 | } 140 | } 141 | 142 | executePendingTransactions(); 143 | 144 | mCurrentFrag = fragment; 145 | if (mTransactionListener != null) { 146 | mTransactionListener.onTabTransaction(mCurrentFrag, mSelectedTabIndex); 147 | } 148 | } 149 | } 150 | 151 | /** 152 | * Function used to switch to the specified fragment stack 153 | * 154 | * @param index The given index to switch to 155 | * @throws IndexOutOfBoundsException Thrown if trying to switch to an index outside given range 156 | */ 157 | public void switchTab(@TabIndex int index) throws IndexOutOfBoundsException { 158 | switchTab(index, null); 159 | } 160 | 161 | /** 162 | * Push a fragment onto the current stack 163 | * 164 | * @param fragment The fragment that is to be pushed 165 | * @param transactionOptions Transaction options to be displayed 166 | */ 167 | public void pushFragment(@Nullable Fragment fragment, @Nullable FragNavTransactionOptions transactionOptions) { 168 | if (fragment != null && mSelectedTabIndex != NO_TAB) { 169 | FragmentTransaction ft = createTransactionWithOptions(transactionOptions); 170 | 171 | detachCurrentFragment(ft); 172 | ft.add(mContainerId, fragment, generateTag(fragment)); 173 | ft.commit(); 174 | 175 | executePendingTransactions(); 176 | 177 | mFragmentStacks.get(mSelectedTabIndex).push(fragment); 178 | 179 | mCurrentFrag = fragment; 180 | if (mTransactionListener != null) { 181 | mTransactionListener.onFragmentTransaction(mCurrentFrag, TransactionType.PUSH); 182 | } 183 | 184 | } 185 | } 186 | 187 | /** 188 | * Push a fragment onto the current stack 189 | * 190 | * @param fragment The fragment that is to be pushed 191 | */ 192 | public void pushFragment(@Nullable Fragment fragment) { 193 | pushFragment(fragment, null); 194 | } 195 | 196 | /** 197 | * Pop the current fragment from the current tab 198 | * 199 | * @param transactionOptions Transaction options to be displayed 200 | */ 201 | public void popFragment(@Nullable FragNavTransactionOptions transactionOptions) throws UnsupportedOperationException { 202 | popFragments(1, transactionOptions); 203 | } 204 | 205 | /** 206 | * Pop the current fragment from the current tab 207 | */ 208 | public void popFragment() throws UnsupportedOperationException { 209 | popFragment(null); 210 | } 211 | 212 | /** 213 | * Pop the current stack until a given tag is found. If the tag is not found, the stack will popFragment until it is at 214 | * the root fragment 215 | * 216 | * @param transactionOptions Transaction options to be displayed 217 | */ 218 | public void popFragments(int popDepth, @Nullable FragNavTransactionOptions transactionOptions) throws UnsupportedOperationException { 219 | if (isRootFragment()) { 220 | throw new UnsupportedOperationException( 221 | "You can not popFragment the rootFragment. If you need to change this fragment, use replaceFragment(fragment)"); 222 | } else if (popDepth < 1) { 223 | throw new UnsupportedOperationException("popFragments parameter needs to be greater than 0"); 224 | } else if (mSelectedTabIndex == NO_TAB) { 225 | throw new UnsupportedOperationException("You can not pop fragments when no tab is selected"); 226 | } 227 | 228 | //If our popDepth is big enough that it would just clear the stack, then call that. 229 | if (popDepth >= mFragmentStacks.get(mSelectedTabIndex).size() - 1) { 230 | clearStack(transactionOptions); 231 | return; 232 | } 233 | 234 | Fragment fragment; 235 | FragmentTransaction ft = createTransactionWithOptions(transactionOptions); 236 | 237 | //Pop the number of the fragments on the stack and remove them from the FragmentManager 238 | for (int i = 0; i < popDepth; i++) { 239 | fragment = mFragmentManager.findFragmentByTag(mFragmentStacks.get(mSelectedTabIndex).pop().getTag()); 240 | if (fragment != null) { 241 | ft.remove(fragment); 242 | } 243 | } 244 | 245 | //Attempt to reattach previous fragment 246 | fragment = reattachPreviousFragment(ft); 247 | 248 | boolean bShouldPush = false; 249 | //If we can't reattach, either pull from the stack, or create a new root fragment 250 | if (fragment != null) { 251 | ft.commit(); 252 | } else { 253 | if (!mFragmentStacks.get(mSelectedTabIndex).isEmpty()) { 254 | fragment = mFragmentStacks.get(mSelectedTabIndex).peek(); 255 | ft.add(mContainerId, fragment, fragment.getTag()); 256 | ft.commit(); 257 | } else { 258 | fragment = getRootFragment(mSelectedTabIndex); 259 | ft.add(mContainerId, fragment, generateTag(fragment)); 260 | ft.commit(); 261 | 262 | bShouldPush = true; 263 | } 264 | } 265 | 266 | executePendingTransactions(); 267 | 268 | //Need to have this down here so that that tag has been 269 | // committed to the fragment before we add to the stack 270 | if (bShouldPush) { 271 | mFragmentStacks.get(mSelectedTabIndex).push(fragment); 272 | } 273 | 274 | mCurrentFrag = fragment; 275 | if (mTransactionListener != null) { 276 | mTransactionListener.onFragmentTransaction(mCurrentFrag, TransactionType.POP); 277 | } 278 | } 279 | 280 | /** 281 | * Pop the current fragment from the current tab 282 | */ 283 | public void popFragments(int popDepth) throws UnsupportedOperationException { 284 | popFragments(popDepth, null); 285 | } 286 | 287 | /** 288 | * Clears the current tab's stack to get to just the bottom Fragment. This will reveal the root fragment 289 | * 290 | * @param transactionOptions Transaction options to be displayed 291 | */ 292 | public void clearStack(@Nullable FragNavTransactionOptions transactionOptions) { 293 | if (mSelectedTabIndex == NO_TAB) { 294 | return; 295 | } 296 | 297 | //Grab Current stack 298 | Stack fragmentStack = mFragmentStacks.get(mSelectedTabIndex); 299 | 300 | // Only need to start popping and reattach if the stack is greater than 1 301 | if (fragmentStack.size() > 1) { 302 | Fragment fragment; 303 | FragmentTransaction ft = createTransactionWithOptions(transactionOptions); 304 | 305 | //Pop all of the fragments on the stack and remove them from the FragmentManager 306 | while (fragmentStack.size() > 1) { 307 | fragment = mFragmentManager.findFragmentByTag(fragmentStack.pop().getTag()); 308 | if (fragment != null) { 309 | ft.remove(fragment); 310 | } 311 | } 312 | 313 | //Attempt to reattach previous fragment 314 | fragment = reattachPreviousFragment(ft); 315 | 316 | boolean bShouldPush = false; 317 | //If we can't reattach, either pull from the stack, or create a new root fragment 318 | if (fragment != null) { 319 | ft.commit(); 320 | } else { 321 | if (!fragmentStack.isEmpty()) { 322 | fragment = fragmentStack.peek(); 323 | ft.add(mContainerId, fragment, fragment.getTag()); 324 | ft.commit(); 325 | } else { 326 | fragment = getRootFragment(mSelectedTabIndex); 327 | ft.add(mContainerId, fragment, generateTag(fragment)); 328 | ft.commit(); 329 | 330 | bShouldPush = true; 331 | } 332 | } 333 | 334 | executePendingTransactions(); 335 | 336 | if (bShouldPush) { 337 | mFragmentStacks.get(mSelectedTabIndex).push(fragment); 338 | } 339 | 340 | //Update the stored version we have in the list 341 | mFragmentStacks.set(mSelectedTabIndex, fragmentStack); 342 | 343 | mCurrentFrag = fragment; 344 | if (mTransactionListener != null) { 345 | mTransactionListener.onFragmentTransaction(mCurrentFrag, TransactionType.POP); 346 | } 347 | } 348 | } 349 | 350 | /** 351 | * Clears the current tab's stack to get to just the bottom Fragment. This will reveal the root fragment. 352 | */ 353 | public void clearStack() { 354 | clearStack(null); 355 | } 356 | 357 | /** 358 | * Replace the current fragment 359 | * 360 | * @param fragment the fragment to be shown instead 361 | * @param transactionOptions Transaction options to be displayed 362 | */ 363 | public void replaceFragment(@NonNull Fragment fragment, @Nullable FragNavTransactionOptions transactionOptions) { 364 | Fragment poppingFrag = getCurrentFrag(); 365 | 366 | if (poppingFrag != null) { 367 | FragmentTransaction ft = createTransactionWithOptions(transactionOptions); 368 | 369 | //overly cautious fragment popFragment 370 | Stack fragmentStack = mFragmentStacks.get(mSelectedTabIndex); 371 | if (!fragmentStack.isEmpty()) { 372 | fragmentStack.pop(); 373 | } 374 | 375 | String tag = generateTag(fragment); 376 | ft.replace(mContainerId, fragment, tag); 377 | 378 | //Commit our transactions 379 | ft.commit(); 380 | 381 | executePendingTransactions(); 382 | 383 | fragmentStack.push(fragment); 384 | mCurrentFrag = fragment; 385 | 386 | if (mTransactionListener != null) { 387 | mTransactionListener.onFragmentTransaction(mCurrentFrag, TransactionType.REPLACE); 388 | 389 | } 390 | } 391 | } 392 | 393 | /** 394 | * Replace the current fragment 395 | * 396 | * @param fragment the fragment to be shown instead 397 | */ 398 | public void replaceFragment(@NonNull Fragment fragment) { 399 | replaceFragment(fragment, null); 400 | } 401 | 402 | /** 403 | * @return Current DialogFragment being displayed. Null if none 404 | */ 405 | @Nullable 406 | @CheckResult 407 | public DialogFragment getCurrentDialogFrag() { 408 | if (mCurrentDialogFrag != null) { 409 | return mCurrentDialogFrag; 410 | } 411 | //Else try to find one in the FragmentManager 412 | else { 413 | FragmentManager fragmentManager; 414 | if (mCurrentFrag != null) { 415 | fragmentManager = mCurrentFrag.getChildFragmentManager(); 416 | } else { 417 | fragmentManager = mFragmentManager; 418 | } 419 | if (fragmentManager.getFragments() != null) { 420 | for (Fragment fragment : fragmentManager.getFragments()) { 421 | if (fragment instanceof DialogFragment) { 422 | mCurrentDialogFrag = (DialogFragment) fragment; 423 | break; 424 | } 425 | } 426 | } 427 | } 428 | return mCurrentDialogFrag; 429 | } 430 | 431 | /** 432 | * Clear any DialogFragments that may be shown 433 | */ 434 | public void clearDialogFragment() { 435 | if (mCurrentDialogFrag != null) { 436 | mCurrentDialogFrag.dismiss(); 437 | mCurrentDialogFrag = null; 438 | } 439 | // If we don't have the current dialog, try to find and dismiss it 440 | else { 441 | FragmentManager fragmentManager; 442 | if (mCurrentFrag != null) { 443 | fragmentManager = mCurrentFrag.getChildFragmentManager(); 444 | } else { 445 | fragmentManager = mFragmentManager; 446 | } 447 | 448 | if (fragmentManager.getFragments() != null) { 449 | for (Fragment fragment : fragmentManager.getFragments()) { 450 | if (fragment instanceof DialogFragment) { 451 | ((DialogFragment) fragment).dismiss(); 452 | } 453 | } 454 | } 455 | } 456 | } 457 | 458 | /** 459 | * Display a DialogFragment on the screen 460 | * 461 | * @param dialogFragment The Fragment to be Displayed 462 | */ 463 | public void showDialogFragment(@Nullable DialogFragment dialogFragment) { 464 | if (dialogFragment != null) { 465 | FragmentManager fragmentManager; 466 | if (mCurrentFrag != null) { 467 | fragmentManager = mCurrentFrag.getChildFragmentManager(); 468 | } else { 469 | fragmentManager = mFragmentManager; 470 | } 471 | 472 | //Clear any current dialog fragments 473 | if (fragmentManager.getFragments() != null) { 474 | for (Fragment fragment : fragmentManager.getFragments()) { 475 | if (fragment instanceof DialogFragment) { 476 | ((DialogFragment) fragment).dismiss(); 477 | mCurrentDialogFrag = null; 478 | } 479 | } 480 | } 481 | 482 | mCurrentDialogFrag = dialogFragment; 483 | try { 484 | dialogFragment.show(fragmentManager, dialogFragment.getClass().getName()); 485 | } catch (IllegalStateException e) { 486 | // Activity was likely destroyed before we had a chance to show, nothing can be done here. 487 | } 488 | } 489 | } 490 | 491 | //endregion 492 | 493 | //region Private helper functions 494 | 495 | /** 496 | * Helper function to make sure that we are starting with a clean slate and to perform our first fragment interaction. 497 | * 498 | * @param index the tab index to initialize to 499 | */ 500 | private void initialize(@TabIndex int index) { 501 | mSelectedTabIndex = index; 502 | if (mSelectedTabIndex > mFragmentStacks.size()) { 503 | throw new IndexOutOfBoundsException("Starting index cannot be larger than the number of stacks"); 504 | } 505 | 506 | mSelectedTabIndex = index; 507 | clearFragmentManager(); 508 | clearDialogFragment(); 509 | 510 | if (index == NO_TAB) { 511 | return; 512 | } 513 | 514 | FragmentTransaction ft = createTransactionWithOptions(null); 515 | 516 | Fragment fragment = getRootFragment(index); 517 | ft.add(mContainerId, fragment, generateTag(fragment)); 518 | ft.commit(); 519 | 520 | executePendingTransactions(); 521 | 522 | mCurrentFrag = fragment; 523 | if (mTransactionListener != null) { 524 | mTransactionListener.onTabTransaction(mCurrentFrag, mSelectedTabIndex); 525 | } 526 | } 527 | 528 | /** 529 | * Helper function to get the root fragment for a given index. This is done by either passing them in the constructor, or dynamically via NavListener. 530 | * 531 | * @param index The tab index to get this fragment from 532 | * @return The root fragment at this index 533 | * @throws IllegalStateException This will be thrown if we can't find a rootFragment for this index. Either because you didn't provide it in the 534 | * constructor, or because your RootFragmentListener.getRootFragment(index) isn't returning a fragment for this index. 535 | */ 536 | @NonNull 537 | @CheckResult 538 | private Fragment getRootFragment(int index) throws IllegalStateException { 539 | Fragment fragment = null; 540 | if (!mFragmentStacks.get(index).isEmpty()) { 541 | fragment = mFragmentStacks.get(index).peek(); 542 | } else if (mRootFragmentListener != null) { 543 | fragment = mRootFragmentListener.getRootFragment(index); 544 | 545 | if (mSelectedTabIndex != NO_TAB) { 546 | mFragmentStacks.get(mSelectedTabIndex).push(fragment); 547 | } 548 | 549 | } 550 | if (fragment == null) { 551 | throw new IllegalStateException("Either you haven't past in a fragment at this index in your constructor, or you haven't " + 552 | "provided a way to create it while via your RootFragmentListener.getRootFragment(index)"); 553 | } 554 | 555 | return fragment; 556 | } 557 | 558 | /** 559 | * Will attempt to reattach a previous fragment in the FragmentManager, or return null if not able to. 560 | * 561 | * @param ft current fragment transaction 562 | * @return Fragment if we were able to find and reattach it 563 | */ 564 | @Nullable 565 | private Fragment reattachPreviousFragment(@NonNull FragmentTransaction ft) { 566 | Stack fragmentStack = mFragmentStacks.get(mSelectedTabIndex); 567 | Fragment fragment = null; 568 | if (!fragmentStack.isEmpty()) { 569 | fragment = mFragmentManager.findFragmentByTag(fragmentStack.peek().getTag()); 570 | if (fragment != null) { 571 | ft.attach(fragment); 572 | } 573 | } 574 | return fragment; 575 | } 576 | 577 | /** 578 | * Attempts to detach any current fragment if it exists, and if none is found, returns. 579 | * 580 | * @param ft the current transaction being performed 581 | */ 582 | private void detachCurrentFragment(@NonNull FragmentTransaction ft) { 583 | Fragment oldFrag = getCurrentFrag(); 584 | if (oldFrag != null) { 585 | ft.detach(oldFrag); 586 | } 587 | } 588 | 589 | /** 590 | * Helper function to attempt to get current fragment 591 | * 592 | * @return Fragment the current frag to be returned 593 | */ 594 | @Nullable 595 | @CheckResult 596 | public Fragment getCurrentFrag() { 597 | //Attempt to used stored current fragment 598 | if (mCurrentFrag != null) { 599 | return mCurrentFrag; 600 | } else if (mSelectedTabIndex == NO_TAB) { 601 | return null; 602 | } 603 | //if not, try to pull it from the stack 604 | else { 605 | Stack fragmentStack = mFragmentStacks.get(mSelectedTabIndex); 606 | if (!fragmentStack.isEmpty()) { 607 | mCurrentFrag = mFragmentManager.findFragmentByTag(mFragmentStacks.get(mSelectedTabIndex).peek().getTag()); 608 | } 609 | } 610 | return mCurrentFrag; 611 | } 612 | 613 | /** 614 | * Create a unique fragment tag so that we can grab the fragment later from the FragmentManger 615 | * 616 | * @param fragment The fragment that we're creating a unique tag for 617 | * @return a unique tag using the fragment's class name 618 | */ 619 | @NonNull 620 | @CheckResult 621 | private String generateTag(@NonNull Fragment fragment) { 622 | return fragment.getClass().getName() + ++mTagCount; 623 | } 624 | 625 | /** 626 | * This check is here to prevent recursive entries into executePendingTransactions 627 | */ 628 | private void executePendingTransactions() { 629 | if (!mExecutingTransaction) { 630 | mExecutingTransaction = true; 631 | mFragmentManager.executePendingTransactions(); 632 | mExecutingTransaction = false; 633 | } 634 | } 635 | 636 | /** 637 | * Private helper function to clear out the fragment manager on initialization. All fragment management should be done via FragNav. 638 | */ 639 | private void clearFragmentManager() { 640 | if (mFragmentManager.getFragments() != null) { 641 | FragmentTransaction ft = createTransactionWithOptions(null); 642 | for (Fragment fragment : mFragmentManager.getFragments()) { 643 | if (fragment != null) { 644 | ft.remove(fragment); 645 | } 646 | } 647 | ft.commit(); 648 | executePendingTransactions(); 649 | } 650 | } 651 | 652 | /** 653 | * Setup a fragment transaction with the given option 654 | * 655 | * @param transactionOptions The options that will be set for this transaction 656 | */ 657 | @CheckResult 658 | private FragmentTransaction createTransactionWithOptions(@Nullable FragNavTransactionOptions transactionOptions) { 659 | FragmentTransaction ft = mFragmentManager.beginTransaction(); 660 | if (transactionOptions == null) { 661 | transactionOptions = mDefaultTransactionOptions; 662 | } 663 | if (transactionOptions != null) { 664 | 665 | ft.setCustomAnimations(transactionOptions.enterAnimation, transactionOptions.exitAnimation, transactionOptions.popEnterAnimation, transactionOptions.popExitAnimation); 666 | ft.setTransitionStyle(transactionOptions.transitionStyle); 667 | 668 | ft.setTransition(transactionOptions.transition); 669 | 670 | 671 | if (transactionOptions.sharedElements != null) { 672 | for (Pair sharedElement : transactionOptions.sharedElements) { 673 | ft.addSharedElement(sharedElement.first, sharedElement.second); 674 | } 675 | } 676 | 677 | 678 | if (transactionOptions.breadCrumbTitle != null) { 679 | ft.setBreadCrumbTitle(transactionOptions.breadCrumbTitle); 680 | } 681 | 682 | if (transactionOptions.breadCrumbShortTitle != null) { 683 | ft.setBreadCrumbShortTitle(transactionOptions.breadCrumbShortTitle); 684 | 685 | } 686 | } 687 | return ft; 688 | } 689 | 690 | //endregion 691 | 692 | //region Public helper functions 693 | 694 | /** 695 | * Get the number of fragment stacks 696 | * 697 | * @return the number of fragment stacks 698 | */ 699 | @CheckResult 700 | public int getSize() { 701 | return mFragmentStacks.size(); 702 | } 703 | 704 | 705 | /** 706 | * Get a copy of the stack at a given index 707 | * 708 | * @return requested stack 709 | */ 710 | @SuppressWarnings("unchecked") 711 | @CheckResult 712 | @Nullable 713 | public Stack getStack(@TabIndex int index) { 714 | if (index == NO_TAB) return null; 715 | if (index >= mFragmentStacks.size()) { 716 | throw new IndexOutOfBoundsException("Can't get an index that's larger than we've setup"); 717 | } 718 | return (Stack) mFragmentStacks.get(index).clone(); 719 | } 720 | 721 | 722 | /** 723 | * Get a copy of the current stack that is being displayed 724 | * 725 | * @return Current stack 726 | */ 727 | @SuppressWarnings("unchecked") 728 | @CheckResult 729 | @Nullable 730 | public Stack getCurrentStack() { 731 | return getStack(mSelectedTabIndex); 732 | } 733 | 734 | /** 735 | * Get the index of the current stack that is being displayed 736 | * 737 | * @return Current stack index 738 | */ 739 | @CheckResult 740 | @TabIndex 741 | public int getCurrentStackIndex() { 742 | return mSelectedTabIndex; 743 | } 744 | 745 | 746 | /** 747 | * @return If true, you are at the bottom of the stack 748 | * (Consider using replaceFragment if you need to change the root fragment for some reason) 749 | * else you can popFragment as needed as your are not at the root 750 | */ 751 | @CheckResult 752 | public boolean isRootFragment() { 753 | Stack stack = getCurrentStack(); 754 | 755 | return stack == null || stack.size() == 1; 756 | } 757 | 758 | //endregion 759 | 760 | //region SavedInstanceState 761 | 762 | /** 763 | * Call this in your Activity's onSaveInstanceState(Bundle outState) method to save the instance's state. 764 | * 765 | * @param outState The Bundle to save state information to 766 | */ 767 | public void onSaveInstanceState(@NonNull Bundle outState) { 768 | 769 | // Write tag count 770 | outState.putInt(EXTRA_TAG_COUNT, mTagCount); 771 | 772 | // Write select tab 773 | outState.putInt(EXTRA_SELECTED_TAB_INDEX, mSelectedTabIndex); 774 | 775 | // Write current fragment 776 | if (mCurrentFrag != null) { 777 | outState.putString(EXTRA_CURRENT_FRAGMENT, mCurrentFrag.getTag()); 778 | } 779 | 780 | // Write stacks 781 | try { 782 | final JSONArray stackArrays = new JSONArray(); 783 | 784 | for (Stack stack : mFragmentStacks) { 785 | final JSONArray stackArray = new JSONArray(); 786 | 787 | for (Fragment fragment : stack) { 788 | stackArray.put(fragment.getTag()); 789 | } 790 | 791 | stackArrays.put(stackArray); 792 | } 793 | 794 | outState.putString(EXTRA_FRAGMENT_STACK, stackArrays.toString()); 795 | } catch (Throwable t) { 796 | // Nothing we can do 797 | } 798 | } 799 | 800 | /** 801 | * Restores this instance to the state specified by the contents of savedInstanceState 802 | * 803 | * @param savedInstanceState The bundle to restore from 804 | * @param rootFragments List of root fragments from which to initialize empty stacks. If null, pull fragments from RootFragmentListener. 805 | * @return true if successful, false if not 806 | */ 807 | private boolean restoreFromBundle(@Nullable Bundle savedInstanceState, @Nullable List rootFragments) { 808 | if (savedInstanceState == null) { 809 | return false; 810 | } 811 | 812 | // Restore tag count 813 | mTagCount = savedInstanceState.getInt(EXTRA_TAG_COUNT, 0); 814 | 815 | // Restore current fragment 816 | mCurrentFrag = mFragmentManager.findFragmentByTag(savedInstanceState.getString(EXTRA_CURRENT_FRAGMENT)); 817 | 818 | // Restore fragment stacks 819 | try { 820 | final JSONArray stackArrays = new JSONArray(savedInstanceState.getString(EXTRA_FRAGMENT_STACK)); 821 | 822 | for (int x = 0; x < stackArrays.length(); x++) { 823 | final JSONArray stackArray = stackArrays.getJSONArray(x); 824 | final Stack stack = new Stack<>(); 825 | 826 | if (stackArray.length() == 1) { 827 | final String tag = stackArray.getString(0); 828 | final Fragment fragment; 829 | 830 | if (tag == null || "null".equalsIgnoreCase(tag)) { 831 | if (rootFragments != null) { 832 | fragment = rootFragments.get(x); 833 | } else { 834 | fragment = getRootFragment(x); 835 | } 836 | 837 | } else { 838 | fragment = mFragmentManager.findFragmentByTag(tag); 839 | } 840 | 841 | if (fragment != null) { 842 | stack.add(fragment); 843 | } 844 | } else { 845 | for (int y = 0; y < stackArray.length(); y++) { 846 | final String tag = stackArray.getString(y); 847 | 848 | if (tag != null && !"null".equalsIgnoreCase(tag)) { 849 | final Fragment fragment = mFragmentManager.findFragmentByTag(tag); 850 | 851 | if (fragment != null) { 852 | stack.add(fragment); 853 | } 854 | } 855 | } 856 | } 857 | 858 | mFragmentStacks.add(stack); 859 | } 860 | // Restore selected tab if we have one 861 | switch (savedInstanceState.getInt(EXTRA_SELECTED_TAB_INDEX)) { 862 | case TAB1: 863 | switchTab(TAB1); 864 | break; 865 | case TAB2: 866 | switchTab(TAB2); 867 | break; 868 | case TAB3: 869 | switchTab(TAB3); 870 | break; 871 | case TAB4: 872 | switchTab(TAB4); 873 | break; 874 | case TAB5: 875 | switchTab(TAB5); 876 | break; 877 | } 878 | 879 | //Successfully restored state 880 | return true; 881 | } catch (Throwable t) { 882 | return false; 883 | } 884 | } 885 | //endregion 886 | 887 | public enum TransactionType { 888 | PUSH, 889 | POP, 890 | REPLACE 891 | } 892 | 893 | //Declare the TabIndex annotation 894 | @IntDef({NO_TAB, TAB1, TAB2, TAB3, TAB4, TAB5}) 895 | @Retention(RetentionPolicy.SOURCE) 896 | public @interface TabIndex { 897 | } 898 | 899 | // Declare Transit Styles 900 | @IntDef({FragmentTransaction.TRANSIT_NONE, FragmentTransaction.TRANSIT_FRAGMENT_OPEN, FragmentTransaction.TRANSIT_FRAGMENT_CLOSE, FragmentTransaction.TRANSIT_FRAGMENT_FADE}) 901 | @Retention(RetentionPolicy.SOURCE) 902 | @interface Transit { 903 | } 904 | 905 | public interface RootFragmentListener { 906 | /** 907 | * Dynamically create the Fragment that will go on the bottom of the stack 908 | * 909 | * @param index the index that the root of the stack Fragment needs to go 910 | * @return the new Fragment 911 | */ 912 | Fragment getRootFragment(int index); 913 | } 914 | 915 | public interface TransactionListener { 916 | 917 | void onTabTransaction(Fragment fragment, int index); 918 | 919 | void onFragmentTransaction(Fragment fragment, TransactionType transactionType); 920 | } 921 | 922 | public static final class Builder { 923 | private final int mContainerId; 924 | private FragmentManager mFragmentManager; 925 | private RootFragmentListener mRootFragmentListener; 926 | @TabIndex 927 | private int mSelectedTabIndex = TAB1; 928 | private TransactionListener mTransactionListener; 929 | private FragNavTransactionOptions mDefaultTransactionOptions; 930 | private int mNumberOfTabs = 0; 931 | private List mRootFragments; 932 | private Bundle mSavedInstanceState; 933 | 934 | public Builder(@Nullable Bundle savedInstanceState, FragmentManager mFragmentManager, int mContainerId) { 935 | this.mSavedInstanceState = savedInstanceState; 936 | this.mFragmentManager = mFragmentManager; 937 | this.mContainerId = mContainerId; 938 | } 939 | 940 | /** 941 | * @param selectedTabIndex The initial tab index to be used must be in range of rootFragments size 942 | */ 943 | public Builder selectedTabIndex(@TabIndex int selectedTabIndex) { 944 | mSelectedTabIndex = selectedTabIndex; 945 | if (mRootFragments != null && mSelectedTabIndex > mNumberOfTabs) { 946 | throw new IndexOutOfBoundsException("Starting index cannot be larger than the number of stacks"); 947 | } 948 | return this; 949 | } 950 | 951 | /** 952 | * @param rootFragment A single root fragment. This library can still be helpful when managing a single stack of fragments 953 | */ 954 | public Builder rootFragment(Fragment rootFragment) { 955 | mRootFragments = new ArrayList<>(1); 956 | mRootFragments.add(rootFragment); 957 | mNumberOfTabs = 1; 958 | return rootFragments(mRootFragments); 959 | } 960 | 961 | /** 962 | * @param rootFragments a list of root fragments. root Fragments are the root fragments that exist on any tab structure. If only one fragment is sent in, fragnav will still manage 963 | * transactions 964 | */ 965 | public Builder rootFragments(@NonNull List rootFragments) { 966 | mRootFragments = rootFragments; 967 | mNumberOfTabs = rootFragments.size(); 968 | if (mNumberOfTabs > MAX_NUM_TABS) { 969 | throw new IllegalArgumentException("Number of root fragments cannot be greater than " + MAX_NUM_TABS); 970 | } 971 | return this; 972 | } 973 | 974 | /** 975 | * @param transactionOptions The default transaction options to be used unless otherwise defined. 976 | */ 977 | public Builder defaultTransactionOptions(@NonNull FragNavTransactionOptions transactionOptions) { 978 | mDefaultTransactionOptions = transactionOptions; 979 | return this; 980 | } 981 | 982 | 983 | /** 984 | * @param rootFragmentListener a listener that allows for dynamically creating root fragments 985 | * @param numberOfTabs the number of tabs that will be switched between 986 | */ 987 | public Builder rootFragmentListener(RootFragmentListener rootFragmentListener, int numberOfTabs) { 988 | mRootFragmentListener = rootFragmentListener; 989 | mNumberOfTabs = numberOfTabs; 990 | if (mNumberOfTabs > MAX_NUM_TABS) { 991 | throw new IllegalArgumentException("Number of tabs cannot be greater than " + MAX_NUM_TABS); 992 | } 993 | return this; 994 | } 995 | 996 | /** 997 | * @param val A listener to be implemented (typically within the main activity) to fragment transactions (including tab switches) 998 | */ 999 | public Builder transactionListener(TransactionListener val) { 1000 | mTransactionListener = val; 1001 | return this; 1002 | } 1003 | 1004 | 1005 | public FragNavController build() { 1006 | if (mRootFragmentListener == null && mRootFragments == null) { 1007 | throw new IndexOutOfBoundsException("Either a root fragment(s) needs to be set, or a fragment listener"); 1008 | } 1009 | return new FragNavController(this, mSavedInstanceState); 1010 | } 1011 | 1012 | 1013 | } 1014 | } 1015 | -------------------------------------------------------------------------------- /root/src/app_package/FragNavTransactionOptions.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}; 2 | 3 | import android.support.annotation.AnimRes; 4 | import android.support.annotation.StyleRes; 5 | import android.support.v4.app.FragmentTransaction; 6 | import android.support.v4.util.Pair; 7 | import android.view.View; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | /** 13 | * 14 | */ 15 | 16 | 17 | public class FragNavTransactionOptions { 18 | List> sharedElements; 19 | @FragNavController.Transit 20 | int transition = FragmentTransaction.TRANSIT_NONE; 21 | @AnimRes 22 | int enterAnimation = 0; 23 | @AnimRes 24 | int exitAnimation = 0; 25 | @AnimRes 26 | int popEnterAnimation = 0; 27 | @AnimRes 28 | int popExitAnimation = 0; 29 | @StyleRes 30 | int transitionStyle = 0; 31 | String breadCrumbTitle; 32 | String breadCrumbShortTitle; 33 | 34 | private FragNavTransactionOptions(Builder builder) { 35 | sharedElements = builder.sharedElements; 36 | transition = builder.transition; 37 | enterAnimation = builder.enterAnimation; 38 | exitAnimation = builder.exitAnimation; 39 | transitionStyle = builder.transitionStyle; 40 | popEnterAnimation = builder.popEnterAnimation; 41 | popExitAnimation = builder.popExitAnimation; 42 | breadCrumbTitle = builder.breadCrumbTitle; 43 | breadCrumbShortTitle = builder.breadCrumbShortTitle; 44 | } 45 | 46 | public static Builder newBuilder() { 47 | return new Builder(); 48 | } 49 | 50 | public static final class Builder { 51 | private List> sharedElements; 52 | private int transition; 53 | private int enterAnimation; 54 | private int exitAnimation; 55 | private int transitionStyle; 56 | private int popEnterAnimation; 57 | private int popExitAnimation; 58 | private String breadCrumbTitle; 59 | private String breadCrumbShortTitle; 60 | 61 | private Builder() { 62 | } 63 | 64 | public Builder addSharedElement(Pair val) { 65 | if (sharedElements == null) { 66 | sharedElements = new ArrayList<>(3); 67 | } 68 | sharedElements.add(val); 69 | return this; 70 | } 71 | 72 | public Builder sharedElements(List> val) { 73 | sharedElements = val; 74 | return this; 75 | } 76 | 77 | public Builder transition(@FragNavController.Transit int val) { 78 | transition = val; 79 | return this; 80 | } 81 | 82 | public Builder customAnimations(@AnimRes int enterAnimation, @AnimRes int exitAnimation) { 83 | this.enterAnimation = enterAnimation; 84 | this.exitAnimation = exitAnimation; 85 | return this; 86 | } 87 | 88 | public Builder customAnimations(@AnimRes int enterAnimation, @AnimRes int exitAnimation, @AnimRes int popEnterAnimation, @AnimRes int popExitAnimation) { 89 | this.popEnterAnimation = popEnterAnimation; 90 | this.popExitAnimation = popExitAnimation; 91 | return customAnimations(enterAnimation, exitAnimation); 92 | } 93 | 94 | 95 | public Builder transitionStyle(@StyleRes int val) { 96 | transitionStyle = val; 97 | return this; 98 | } 99 | 100 | public Builder breadCrumbTitle(String val) { 101 | breadCrumbTitle = val; 102 | return this; 103 | } 104 | 105 | public Builder breadCrumbShortTitle(String val) { 106 | breadCrumbShortTitle = val; 107 | return this; 108 | } 109 | 110 | public FragNavTransactionOptions build() { 111 | return new FragNavTransactionOptions(this); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /root/src/app_package/FragmentHistory.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | 6 | 7 | public class FragmentHistory { 8 | 9 | 10 | private ArrayList stackArr; 11 | 12 | /** 13 | * constructor to create stack with size 14 | * 15 | * @param 16 | */ 17 | public FragmentHistory() { 18 | stackArr = new ArrayList<>(); 19 | 20 | 21 | } 22 | 23 | /** 24 | * This method adds new entry to the top 25 | * of the stack 26 | * 27 | * @param entry 28 | * @throws Exception 29 | */ 30 | public void push(int entry) { 31 | 32 | if (isAlreadyExists(entry)) { 33 | return; 34 | } 35 | stackArr.add(entry); 36 | 37 | } 38 | 39 | private boolean isAlreadyExists(int entry) { 40 | return (stackArr.contains(entry)); 41 | } 42 | 43 | /** 44 | * This method removes an entry from the 45 | * top of the stack. 46 | * 47 | * @return 48 | * @throws Exception 49 | */ 50 | public int pop() { 51 | 52 | int entry = -1; 53 | if(!isEmpty()){ 54 | 55 | entry = stackArr.get(stackArr.size() - 1); 56 | 57 | stackArr.remove(stackArr.size() - 1); 58 | } 59 | return entry; 60 | } 61 | 62 | 63 | /** 64 | * This method removes an entry from the 65 | * top of the stack. 66 | * 67 | * @return 68 | * @throws Exception 69 | */ 70 | public int popPrevious() { 71 | 72 | int entry = -1; 73 | 74 | if(!isEmpty()){ 75 | entry = stackArr.get(stackArr.size() - 2); 76 | stackArr.remove(stackArr.size() - 2); 77 | } 78 | return entry; 79 | } 80 | 81 | 82 | 83 | /** 84 | * This method returns top of the stack 85 | * without removing it. 86 | * 87 | * @return 88 | */ 89 | public int peek() { 90 | if(!isEmpty()){ 91 | return stackArr.get(stackArr.size() - 1); 92 | } 93 | 94 | return -1; 95 | } 96 | 97 | 98 | 99 | public boolean isEmpty(){ 100 | return (stackArr.size() == 0); 101 | } 102 | 103 | 104 | public int getStackSize(){ 105 | return stackArr.size(); 106 | } 107 | 108 | public void emptyStack() { 109 | 110 | stackArr.clear(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /root/src/app_package/SecondFragment.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}; 2 | 3 | <#if applicationPackage??> 4 | import ${applicationPackage}.R; 5 | 6 | 7 | import android.os.Bundle; 8 | import android.support.v4.app.Fragment; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | 13 | public class ${tabName2} extends Fragment{ 14 | 15 | public ${tabName2}() { 16 | // Required empty public constructor 17 | } 18 | 19 | public static ${tabName2} newInstance() { 20 | 21 | Bundle args = new Bundle(); 22 | ${tabName2} fragment = new ${tabName2}(); 23 | fragment.setArguments(args); 24 | return fragment; 25 | } 26 | 27 | 28 | @Override 29 | public void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | } 32 | 33 | @Override 34 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 35 | Bundle savedInstanceState) { 36 | // Inflate the layout for this fragment 37 | return inflater.inflate(R.layout.${tabLayoutName2}, container, false); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /root/src/app_package/TabsActivity.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}; 2 | 3 | <#if applicationPackage??> 4 | import ${applicationPackage}.R; 5 | 6 | 7 | <#if tabposition == 'bottom' && isFragmentHistory> 8 | <#include "include_bottom_tab_stack_activity.java.ftl"/> 9 | <#elseif tabposition == 'bottom'> 10 | <#include "include_bottom_tab_activity.java.ftl"/> 11 | <#else> 12 | <#include "include_top_tab_activity.java.ftl"/> 13 | 14 | -------------------------------------------------------------------------------- /root/src/app_package/TabsFragment.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}; 2 | 3 | <#if applicationPackage??> 4 | import ${applicationPackage}.R; 5 | 6 | import android.os.Bundle; 7 | import android.support.design.widget.TabLayout; 8 | import android.support.v4.app.Fragment; 9 | import android.support.v4.app.FragmentManager; 10 | import android.support.v4.app.FragmentPagerAdapter; 11 | import android.support.v4.view.ViewPager; 12 | import android.support.v7.app.AppCompatActivity; 13 | import android.support.v7.widget.Toolbar; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | 19 | public class ${activityClass} extends AppCompatActivity { 20 | 21 | private Toolbar toolbar; 22 | private TabLayout tabLayout; 23 | private ViewPager viewPager; 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.${layoutActivityName}); 29 | 30 | toolbar = (Toolbar) findViewById(R.id.toolbar); 31 | setSupportActionBar(toolbar); 32 | 33 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 34 | 35 | viewPager = (ViewPager) findViewById(R.id.viewpager); 36 | setupViewPager(viewPager); 37 | 38 | tabLayout = (TabLayout) findViewById(R.id.tabs); 39 | tabLayout.setupWithViewPager(viewPager); 40 | } 41 | 42 | private void setupViewPager(ViewPager viewPager) { 43 | ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager()); 44 | adapter.addFragment(new OneFragment(), "ONE"); 45 | adapter.addFragment(new TwoFragment(), "TWO"); 46 | adapter.addFragment(new ThreeFragment(), "THREE"); 47 | viewPager.setAdapter(adapter); 48 | } 49 | 50 | class ViewPagerAdapter extends FragmentPagerAdapter { 51 | private final List mFragmentList = new ArrayList<>(); 52 | private final List mFragmentTitleList = new ArrayList<>(); 53 | 54 | public ViewPagerAdapter(FragmentManager manager) { 55 | super(manager); 56 | } 57 | 58 | @Override 59 | public Fragment getItem(int position) { 60 | return mFragmentList.get(position); 61 | } 62 | 63 | @Override 64 | public int getCount() { 65 | return mFragmentList.size(); 66 | } 67 | 68 | public void addFragment(Fragment fragment, String title) { 69 | mFragmentList.add(fragment); 70 | mFragmentTitleList.add(title); 71 | } 72 | 73 | @Override 74 | public CharSequence getPageTitle(int position) { 75 | return mFragmentTitleList.get(position); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /root/src/app_package/ThirdFragment.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}; 2 | 3 | <#if applicationPackage??> 4 | import ${applicationPackage}.R; 5 | 6 | 7 | import android.os.Bundle; 8 | import android.support.v4.app.Fragment; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | 13 | public class ${tabName3} extends Fragment{ 14 | 15 | public ${tabName3}() { 16 | // Required empty public constructor 17 | } 18 | 19 | public static ${tabName3} newInstance() { 20 | 21 | Bundle args = new Bundle(); 22 | ${tabName3} fragment = new ${tabName3}(); 23 | fragment.setArguments(args); 24 | return fragment; 25 | } 26 | 27 | @Override 28 | public void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | } 31 | 32 | @Override 33 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 34 | Bundle savedInstanceState) { 35 | // Inflate the layout for this fragment 36 | return inflater.inflate(R.layout.${tabLayoutName3}, container, false); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /root/src/app_package/include_bottom_tab_activity.java.ftl: -------------------------------------------------------------------------------- 1 | 2 | import android.os.Bundle; 3 | import android.support.design.widget.TabLayout; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v4.app.FragmentManager; 6 | import android.support.v4.app.FragmentPagerAdapter; 7 | import android.support.v4.app.FragmentTransaction; 8 | import android.support.v4.view.ViewPager; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.support.v7.widget.Toolbar; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import android.view.MenuItem; 15 | import android.view.View; 16 | import android.view.LayoutInflater; 17 | import android.widget.TextView; 18 | import android.view.ViewGroup; 19 | import android.widget.ImageView; 20 | import android.content.res.ColorStateList; 21 | import android.graphics.drawable.Drawable; 22 | import android.graphics.drawable.StateListDrawable; 23 | import android.graphics.Color; 24 | import android.support.v4.content.ContextCompat; 25 | import android.widget.RelativeLayout; 26 | import android.content.Context; 27 | 28 | 29 | 30 | public class ${activityClass} extends AppCompatActivity { 31 | 32 | <#if isToolbar> 33 | private Toolbar toolbar; 34 | 35 | 36 | private TabLayout tablayout; 37 | 38 | private String tabNames[] = {"${tab1}","${tab2}","${tab3}"}; 39 | 40 | <#if tabstyle == 'icons' || tabstyle == 'iconswithtext' || tabstyle == 'badgewithicons'> 41 | 42 | private int[] tabIconsUnSelected = { 43 | R.drawable.YOUR_DRAWABLE, 44 | R.drawable.YOUR_DRAWABLE, 45 | R.drawable.YOUR_DRAWABLE }; 46 | 47 | private int[] tabIconsSelected = { 48 | R.drawable.YOUR_DRAWABLE, 49 | R.drawable.YOUR_DRAWABLE, 50 | R.drawable.YOUR_DRAWABLE}; 51 | 52 | 53 | 54 | 55 | @Override 56 | protected void onCreate(Bundle savedInstanceState) { 57 | super.onCreate(savedInstanceState); 58 | setContentView(R.layout.${layoutActivityName}); 59 | 60 | initView(); 61 | 62 | <#if isToolbar> 63 | initToolbar(); 64 | 65 | 66 | setupTabLayout(); 67 | 68 | initTab(); 69 | 70 | } 71 | 72 | private void setupTabLayout() { 73 | 74 | tablayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { 75 | @Override 76 | public void onTabSelected(TabLayout.Tab tab) { 77 | 78 | 79 | switchTab(tab.getPosition()); 80 | } 81 | 82 | @Override 83 | public void onTabUnselected(TabLayout.Tab tab) { 84 | 85 | } 86 | 87 | @Override 88 | public void onTabReselected(TabLayout.Tab tab) { 89 | 90 | } 91 | }); 92 | 93 | 94 | } 95 | 96 | <#if isToolbar> 97 | @Override 98 | public boolean onOptionsItemSelected(MenuItem item) { 99 | 100 | switch (item.getItemId()){ 101 | case android.R.id.home: 102 | finish(); 103 | return true; 104 | } 105 | 106 | return super.onOptionsItemSelected(item); 107 | } 108 | 109 | 110 | 111 | private void initView() { 112 | <#if isToolbar> 113 | toolbar = (Toolbar) findViewById(R.id.toolbar); 114 | 115 | tablayout = (TabLayout) findViewById(R.id.tablayout); 116 | } 117 | 118 | <#if isToolbar> 119 | 120 | private void initToolbar() { 121 | setSupportActionBar(toolbar); 122 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 123 | } 124 | 125 | 126 | private void initTab() { 127 | if (tablayout != null) { 128 | for (int i = 0; i < tabNames.length; i++) { 129 | tablayout.addTab(tablayout.newTab()); 130 | TabLayout.Tab tab = tablayout.getTabAt(i); 131 | if (tab != null) 132 | tab.setCustomView(getTabView(i)); 133 | } 134 | } 135 | 136 | } 137 | 138 | 139 | private View getTabView(int position) { 140 | View view = LayoutInflater.from(${activityClass}.this).inflate(R.layout.view_tabs, null); 141 | 142 | <#if tabstyle == 'icons' || tabstyle == 'iconswithtext' || tabstyle == 'badgewithicons'> 143 | ImageView icon = (ImageView) view.findViewById(R.id.tab_icon); 144 | icon.setImageDrawable(setDrawableSelector(${activityClass}.this, tabIconsUnSelected[position], tabIconsSelected[position])); 145 | 146 | 147 | 148 | <#if tabstyle == 'simple'|| tabstyle == 'iconswithtext'> 149 | TextView text = (TextView) view.findViewById(R.id.tab_text); 150 | text.setText(tabNames[position]); 151 | text.setTextColor(setTextselector(Color.parseColor("#F2F2F2"), Color.parseColor("#FFFFFF"))); 152 | 153 | 154 | 155 | <#if tabstyle == 'badgewithicons'> 156 | 157 | // TODO: 24/08/17 Disabling badge for second tab. Play with your logics according to your use case. 158 | if(position != 1) { 159 | 160 | RelativeLayout rlBadge = (RelativeLayout) view.findViewById(R.id.rl_badge); 161 | rlBadge.setVisibility(View.VISIBLE); 162 | 163 | TextView txtBadge = (TextView) view.findViewById(R.id.txt_badge); 164 | txtBadge.setBackground(setDrawableSelector(${activityClass}.this, R.drawable.view_badge_un_selected, R.drawable.view_badge_selected)); 165 | 166 | // TODO: 24/08/17 Hard coded color just for demo. Pass your own color from colors.xml 167 | txtBadge.setTextColor(setTextselector(Color.parseColor("#FFFFFF"), Color.parseColor("#5f8ee4"))); 168 | txtBadge.setText("10"); 169 | 170 | } 171 | 172 | 173 | 174 | view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 175 | return view; 176 | } 177 | 178 | 179 | private void switchTab(int position){ 180 | 181 | FragmentManager fragmentManager = getSupportFragmentManager(); 182 | FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); 183 | 184 | switch (position){ 185 | 186 | case 0: 187 | 188 | fragmentTransaction.replace(R.id.content_frame, ${tabName1}.newInstance()); 189 | break; 190 | 191 | case 1: 192 | 193 | fragmentTransaction.replace(R.id.content_frame, ${tabName2}.newInstance()); 194 | break; 195 | 196 | case 2: 197 | fragmentTransaction.replace(R.id.content_frame, ${tabName3}.newInstance()); 198 | break; 199 | } 200 | fragmentTransaction.commit(); 201 | 202 | } 203 | 204 | public static Drawable setDrawableSelector(Context context, int normal, int selected) { 205 | 206 | Drawable state_normal = ContextCompat.getDrawable(context, normal); 207 | 208 | Drawable state_pressed = ContextCompat.getDrawable(context, selected); 209 | 210 | StateListDrawable drawable = new StateListDrawable(); 211 | 212 | drawable.addState(new int[]{android.R.attr.state_selected}, 213 | state_pressed); 214 | drawable.addState(new int[]{android.R.attr.state_enabled}, 215 | state_normal); 216 | 217 | return drawable; 218 | } 219 | 220 | 221 | 222 | public static ColorStateList setTextselector(int normal, int pressed) { 223 | ColorStateList colorStates = new ColorStateList( 224 | new int[][]{ 225 | new int[]{android.R.attr.state_selected}, 226 | new int[]{} 227 | }, 228 | new int[]{ 229 | pressed, 230 | normal}); 231 | return colorStates; 232 | } 233 | 234 | } 235 | -------------------------------------------------------------------------------- /root/src/app_package/include_bottom_tab_stack_activity.java.ftl: -------------------------------------------------------------------------------- 1 | 2 | import android.os.Bundle; 3 | import android.support.design.widget.TabLayout; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v4.app.FragmentManager; 6 | import android.support.v4.app.FragmentPagerAdapter; 7 | import android.support.v4.app.FragmentTransaction; 8 | import android.support.v4.view.ViewPager; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.support.v7.widget.Toolbar; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import android.view.MenuItem; 15 | import android.view.View; 16 | import android.view.LayoutInflater; 17 | import android.widget.TextView; 18 | import android.view.ViewGroup; 19 | import android.widget.ImageView; 20 | import android.content.res.ColorStateList; 21 | import android.graphics.drawable.Drawable; 22 | import android.graphics.drawable.StateListDrawable; 23 | import android.graphics.Color; 24 | import android.support.v4.content.ContextCompat; 25 | import android.widget.RelativeLayout; 26 | import android.content.Context; 27 | 28 | 29 | 30 | public class ${activityClass} extends AppCompatActivity implements BaseFragment.FragmentNavigation, FragNavController.TransactionListener, FragNavController.RootFragmentListener { 31 | 32 | <#if isToolbar> 33 | private Toolbar toolbar; 34 | 35 | 36 | private TabLayout tablayout; 37 | 38 | private String tabNames[] = {"${tab1}","${tab2}","${tab3}"}; 39 | 40 | <#if tabstyle == 'icons' || tabstyle == 'iconswithtext' || tabstyle == 'badgewithicons'> 41 | 42 | private int[] tabIconsUnSelected = { 43 | R.drawable.YOUR_DRAWABLE, 44 | R.drawable.YOUR_DRAWABLE, 45 | R.drawable.YOUR_DRAWABLE }; 46 | 47 | private int[] tabIconsSelected = { 48 | R.drawable.YOUR_DRAWABLE, 49 | R.drawable.YOUR_DRAWABLE, 50 | R.drawable.YOUR_DRAWABLE}; 51 | 52 | 53 | 54 | private FragNavController mNavController; 55 | 56 | private FragmentHistory fragmentHistory; 57 | 58 | @Override 59 | protected void onCreate(Bundle savedInstanceState) { 60 | super.onCreate(savedInstanceState); 61 | setContentView(R.layout.${layoutActivityName}); 62 | 63 | initView(); 64 | 65 | <#if isToolbar> 66 | initToolbar(); 67 | 68 | initTab(); 69 | 70 | setupFragmentHistory(savedInstanceState); 71 | 72 | switchTab(0); 73 | 74 | setupTabLayout(); 75 | 76 | 77 | 78 | } 79 | 80 | 81 | private void initView() { 82 | <#if isToolbar> 83 | toolbar = (Toolbar) findViewById(R.id.toolbar); 84 | 85 | tablayout = (TabLayout) findViewById(R.id.tablayout); 86 | } 87 | 88 | <#if isToolbar> 89 | 90 | private void initToolbar() { 91 | setSupportActionBar(toolbar); 92 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 93 | } 94 | 95 | 96 | private void initTab() { 97 | if (tablayout != null) { 98 | for (int i = 0; i < tabNames.length; i++) { 99 | tablayout.addTab(tablayout.newTab()); 100 | TabLayout.Tab tab = tablayout.getTabAt(i); 101 | if (tab != null) 102 | tab.setCustomView(getTabView(i)); 103 | } 104 | } 105 | 106 | } 107 | 108 | private void setupTabLayout() { 109 | tablayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { 110 | @Override 111 | public void onTabSelected(TabLayout.Tab tab) { 112 | fragmentHistory.push(tab.getPosition()); 113 | switchTab(tab.getPosition()); 114 | } 115 | 116 | @Override 117 | public void onTabUnselected(TabLayout.Tab tab) { 118 | 119 | } 120 | 121 | @Override 122 | public void onTabReselected(TabLayout.Tab tab) { 123 | 124 | mNavController.clearStack(); 125 | switchTab(tab.getPosition()); 126 | 127 | } 128 | }); 129 | } 130 | 131 | private void setupFragmentHistory(Bundle savedInstanceState) { 132 | fragmentHistory = new FragmentHistory(); 133 | 134 | 135 | mNavController = FragNavController.newBuilder(savedInstanceState, getSupportFragmentManager(), R.id.content_frame) 136 | .transactionListener(this) 137 | .rootFragmentListener(this, tabNames.length) 138 | .build(); 139 | } 140 | 141 | 142 | private void switchTab(int position) { 143 | mNavController.switchTab(position); 144 | } 145 | 146 | 147 | 148 | 149 | <#if isToolbar> 150 | @Override 151 | public boolean onOptionsItemSelected(MenuItem item) { 152 | 153 | switch (item.getItemId()){ 154 | case android.R.id.home: 155 | finish(); 156 | return true; 157 | } 158 | 159 | return super.onOptionsItemSelected(item); 160 | } 161 | 162 | 163 | @Override 164 | public void onBackPressed() { 165 | 166 | if (!mNavController.isRootFragment()) { 167 | mNavController.popFragment(); 168 | } else { 169 | 170 | if (fragmentHistory.isEmpty()) { 171 | super.onBackPressed(); 172 | } else { 173 | if (fragmentHistory.getStackSize() > 1) { 174 | 175 | int position = fragmentHistory.popPrevious(); 176 | 177 | switchTab(position); 178 | 179 | updateTabSelection(position); 180 | 181 | } else { 182 | 183 | switchTab(0); 184 | 185 | updateTabSelection(0); 186 | 187 | fragmentHistory.emptyStack(); 188 | } 189 | } 190 | 191 | } 192 | } 193 | 194 | 195 | private void updateTabSelection(int currentTab) { 196 | 197 | for (int i = 0; i < tabNames.length; i++) { 198 | TabLayout.Tab selectedTab = tablayout.getTabAt(i); 199 | if (currentTab != i) { 200 | selectedTab.getCustomView().setSelected(false); 201 | } else { 202 | selectedTab.getCustomView().setSelected(true); 203 | } 204 | } 205 | } 206 | 207 | @Override 208 | protected void onSaveInstanceState(Bundle outState) { 209 | super.onSaveInstanceState(outState); 210 | if (mNavController != null) { 211 | mNavController.onSaveInstanceState(outState); 212 | } 213 | } 214 | 215 | @Override 216 | public void pushFragment(Fragment fragment) { 217 | if (mNavController != null) { 218 | mNavController.pushFragment(fragment); 219 | } 220 | } 221 | 222 | 223 | private View getTabView(int position) { 224 | View view = LayoutInflater.from(${activityClass}.this).inflate(R.layout.view_tabs, null); 225 | 226 | <#if tabstyle == 'icons' || tabstyle == 'iconswithtext' || tabstyle == 'badgewithicons'> 227 | ImageView icon = (ImageView) view.findViewById(R.id.tab_icon); 228 | icon.setImageDrawable(setDrawableSelector(${activityClass}.this, tabIconsUnSelected[position], tabIconsSelected[position])); 229 | 230 | 231 | 232 | <#if tabstyle == 'simple'|| tabstyle == 'iconswithtext'> 233 | TextView text = (TextView) view.findViewById(R.id.tab_text); 234 | text.setText(tabNames[position]); 235 | text.setTextColor(setTextselector(Color.parseColor("#F2F2F2"), Color.parseColor("#FFFFFF"))); 236 | 237 | 238 | 239 | <#if tabstyle == 'badgewithicons'> 240 | 241 | // TODO: 24/08/17 Disabling badge for second tab. Play with your logics according to your use case. 242 | if(position != 1) { 243 | 244 | RelativeLayout rlBadge = (RelativeLayout) view.findViewById(R.id.rl_badge); 245 | rlBadge.setVisibility(View.VISIBLE); 246 | 247 | TextView txtBadge = (TextView) view.findViewById(R.id.txt_badge); 248 | txtBadge.setBackground(setDrawableSelector(${activityClass}.this, R.drawable.view_badge_un_selected, R.drawable.view_badge_selected)); 249 | 250 | // TODO: 24/08/17 Hard coded color just for demo. Pass your own color from colors.xml 251 | txtBadge.setTextColor(setTextselector(Color.parseColor("#FFFFFF"), Color.parseColor("#5f8ee4"))); 252 | txtBadge.setText("10"); 253 | 254 | } 255 | 256 | 257 | 258 | view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 259 | return view; 260 | } 261 | 262 | 263 | @Override 264 | public Fragment getRootFragment(int index) { 265 | switch (index) { 266 | 267 | case FragNavController.TAB1: 268 | return ${tabName1}.newInstance(); 269 | case FragNavController.TAB2: 270 | return ${tabName2}.newInstance(); 271 | case FragNavController.TAB3: 272 | return ${tabName3}.newInstance(); 273 | 274 | 275 | 276 | } 277 | throw new IllegalStateException("Need to send an index that we know"); 278 | } 279 | 280 | @Override 281 | public void onTabTransaction(Fragment fragment, int index) { 282 | // If we have a backstack, show the back button 283 | } 284 | 285 | 286 | 287 | @Override 288 | public void onFragmentTransaction(Fragment fragment, FragNavController.TransactionType transactionType) { 289 | //do fragmentty stuff. Maybe change title, I'm not going to tell you how to live your life 290 | // If we have a backstack, show the back button 291 | } 292 | 293 | 294 | 295 | public static Drawable setDrawableSelector(Context context, int normal, int selected) { 296 | 297 | Drawable state_normal = ContextCompat.getDrawable(context, normal); 298 | 299 | Drawable state_pressed = ContextCompat.getDrawable(context, selected); 300 | 301 | StateListDrawable drawable = new StateListDrawable(); 302 | 303 | drawable.addState(new int[]{android.R.attr.state_selected}, 304 | state_pressed); 305 | drawable.addState(new int[]{android.R.attr.state_enabled}, 306 | state_normal); 307 | 308 | return drawable; 309 | } 310 | 311 | 312 | 313 | public static ColorStateList setTextselector(int normal, int pressed) { 314 | ColorStateList colorStates = new ColorStateList( 315 | new int[][]{ 316 | new int[]{android.R.attr.state_selected}, 317 | new int[]{} 318 | }, 319 | new int[]{ 320 | pressed, 321 | normal}); 322 | return colorStates; 323 | } 324 | 325 | } 326 | -------------------------------------------------------------------------------- /root/src/app_package/include_top_tab_activity.java.ftl: -------------------------------------------------------------------------------- 1 | 2 | import android.os.Bundle; 3 | import android.support.design.widget.TabLayout; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v4.app.FragmentManager; 6 | import android.support.v4.app.FragmentPagerAdapter; 7 | import android.support.v4.view.ViewPager; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.support.v7.widget.Toolbar; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import android.view.MenuItem; 14 | import android.view.View; 15 | import android.widget.TextView; 16 | import android.widget.RelativeLayout; 17 | 18 | import android.view.LayoutInflater; 19 | import android.widget.ImageView; 20 | import android.content.res.ColorStateList; 21 | import android.graphics.drawable.Drawable; 22 | import android.graphics.drawable.StateListDrawable; 23 | import android.graphics.Color; 24 | import android.support.v4.content.ContextCompat; 25 | import android.content.Context; 26 | 27 | 28 | public class ${activityClass} extends AppCompatActivity { 29 | 30 | <#if isToolbar> 31 | private Toolbar toolbar; 32 | 33 | 34 | private TabLayout tablayout; 35 | private ViewPager viewpager; 36 | 37 | private String tabNames[] = {"${tab1}","${tab2}","${tab3}"}; 38 | 39 | <#if tabstyle == 'icons' || tabstyle == 'iconswithtext' || tabstyle == 'badgewithicons'> 40 | 41 | private int[] tabIconsUnSelected = { 42 | R.drawable.YOUR_DRAWABLE, 43 | R.drawable.YOUR_DRAWABLE, 44 | R.drawable.YOUR_DRAWABLE }; 45 | 46 | private int[] tabIconsSelected = { 47 | R.drawable.YOUR_DRAWABLE, 48 | R.drawable.YOUR_DRAWABLE, 49 | R.drawable.YOUR_DRAWABLE}; 50 | 51 | 52 | @Override 53 | protected void onCreate(Bundle savedInstanceState) { 54 | super.onCreate(savedInstanceState); 55 | setContentView(R.layout.${layoutActivityName}); 56 | 57 | initView(); 58 | 59 | <#if isToolbar> 60 | initToolbar(); 61 | 62 | 63 | setupViewPager(viewpager); 64 | 65 | setupTabLayout(); 66 | 67 | initTab(); 68 | 69 | 70 | } 71 | 72 | private void setupTabLayout() { 73 | tablayout.setupWithViewPager(viewpager); 74 | } 75 | 76 | <#if isToolbar> 77 | @Override 78 | public boolean onOptionsItemSelected(MenuItem item) { 79 | 80 | switch (item.getItemId()){ 81 | case android.R.id.home: 82 | finish(); 83 | return true; 84 | } 85 | 86 | return super.onOptionsItemSelected(item); 87 | } 88 | 89 | 90 | private void setupViewPager(ViewPager viewPager) { 91 | 92 | viewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) { 93 | @Override 94 | public Fragment getItem(int position) { 95 | 96 | switch (position) { 97 | 98 | case 0: 99 | return ${tabName1}.newInstance(); 100 | 101 | case 1: 102 | return ${tabName2}.newInstance(); 103 | 104 | case 2: 105 | return ${tabName3}.newInstance(); 106 | 107 | } 108 | return null; 109 | } 110 | 111 | @Override 112 | public CharSequence getPageTitle(int position) { 113 | <#if tabstyle == 'icons'> 114 | return null; 115 | <#else> 116 | return tabNames[position]; 117 | 118 | 119 | } 120 | 121 | @Override 122 | public int getCount() { 123 | return tabNames.length; 124 | } 125 | }); 126 | } 127 | 128 | private void initView() { 129 | <#if isToolbar> 130 | toolbar = (Toolbar) findViewById(R.id.toolbar); 131 | 132 | tablayout = (TabLayout) findViewById(R.id.tablayout); 133 | viewpager = (ViewPager) findViewById(R.id.viewpager); 134 | } 135 | 136 | <#if isToolbar> 137 | 138 | private void initToolbar() { 139 | setSupportActionBar(toolbar); 140 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 141 | } 142 | 143 | 144 | 145 | private void initTab() { 146 | tablayout.getTabAt(0).setCustomView(getTabView(0)); 147 | tablayout.getTabAt(1).setCustomView(getTabView(1)); 148 | tablayout.getTabAt(2).setCustomView(getTabView(2)); 149 | 150 | 151 | } 152 | 153 | 154 | private View getTabView(int position) { 155 | View view = LayoutInflater.from(${activityClass}.this).inflate(R.layout.view_tabs, null); 156 | 157 | <#if tabstyle == 'icons' || tabstyle == 'iconswithtext' || tabstyle == 'badgewithicons'> 158 | ImageView icon = (ImageView) view.findViewById(R.id.tab_icon); 159 | icon.setImageDrawable(setDrawableSelector(${activityClass}.this, tabIconsUnSelected[position], tabIconsSelected[position])); 160 | 161 | 162 | <#if tabstyle == 'simple'|| tabstyle == 'iconswithtext'> 163 | TextView text = (TextView) view.findViewById(R.id.tab_text); 164 | text.setText(tabNames[position]); 165 | text.setTextColor(setTextselector(Color.parseColor("#F2F2F2"), Color.parseColor("#FFFFFF"))); 166 | 167 | 168 | 169 | <#if tabstyle == 'badgewithicons'> 170 | 171 | // TODO: 24/08/17 Disabling badge for second tab. Play with your logics according to your use case. 172 | if(position != 1) { 173 | 174 | RelativeLayout rlBadge = (RelativeLayout) view.findViewById(R.id.rl_badge); 175 | rlBadge.setVisibility(View.VISIBLE); 176 | 177 | TextView txtBadge = (TextView) view.findViewById(R.id.txt_badge); 178 | txtBadge.setBackground(setDrawableSelector(${activityClass}.this, R.drawable.view_badge_un_selected, R.drawable.view_badge_selected)); 179 | 180 | // TODO: 24/08/17 Hard coded color just for demo. Pass your own color from colors.xml 181 | txtBadge.setTextColor(setTextselector(Color.parseColor("#FFFFFF"), Color.parseColor("#5f8ee4"))); 182 | txtBadge.setText("10"); 183 | 184 | } 185 | 186 | 187 | return view; 188 | } 189 | 190 | 191 | public static Drawable setDrawableSelector(Context context, int normal, int selected) { 192 | 193 | Drawable state_normal = ContextCompat.getDrawable(context, normal); 194 | 195 | Drawable state_pressed = ContextCompat.getDrawable(context, selected); 196 | 197 | StateListDrawable drawable = new StateListDrawable(); 198 | 199 | drawable.addState(new int[]{android.R.attr.state_selected}, 200 | state_pressed); 201 | drawable.addState(new int[]{android.R.attr.state_enabled}, 202 | state_normal); 203 | 204 | return drawable; 205 | } 206 | 207 | 208 | 209 | public static ColorStateList setTextselector(int normal, int pressed) { 210 | ColorStateList colorStates = new ColorStateList( 211 | new int[][]{ 212 | new int[]{android.R.attr.state_selected}, 213 | new int[]{} 214 | }, 215 | new int[]{ 216 | pressed, 217 | normal}); 218 | return colorStates; 219 | } 220 | 221 | 222 | } 223 | -------------------------------------------------------------------------------- /template.xml: -------------------------------------------------------------------------------- 1 | 2 | 87 | -------------------------------------------------------------------------------- /templates_adapter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckdevrel/MaterialTabsTemplate/baa697a543cf341e4777250daab0e532409d6d73/templates_adapter.png --------------------------------------------------------------------------------