├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── kotlinc.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE.txt ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── f22labs │ │ └── instalikefragmenttransaction │ │ ├── activities │ │ ├── BaseActivity.java │ │ └── MainActivity.java │ │ ├── fragments │ │ ├── BaseFragment.java │ │ ├── HomeFragment.java │ │ ├── NewsFragment.java │ │ ├── ProfileFragment.java │ │ ├── SearchFragment.java │ │ └── ShareFragment.java │ │ ├── utils │ │ ├── FragmentHistory.java │ │ └── Utils.java │ │ └── views │ │ ├── FragNavController.java │ │ └── FragNavTransactionOptions.java │ └── res │ ├── drawable │ ├── bottom_shadow.xml │ ├── ic_arrow_back.xml │ ├── tab_home.png │ ├── tab_news.png │ ├── tab_profile.png │ ├── tab_search.png │ └── tab_share.png │ ├── layout │ ├── activity_main.xml │ ├── fragment_home.xml │ ├── fragment_news.xml │ ├── fragment_profile.xml │ ├── fragment_search.xml │ ├── fragment_share.xml │ └── tab_item_bottom.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/dictionaries 41 | .idea/libraries 42 | 43 | # Keystore files 44 | *.jks 45 | 46 | # External native build folder generated in Android Studio 2.2 and later 47 | .externalNativeBuild 48 | 49 | # Google Services (e.g. APIs or Firebase) 50 | google-services.json 51 | 52 | # Freeline 53 | freeline.py 54 | freeline/ 55 | freeline_project_description.json -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.8 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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 | # InstaLikeFragmentTransaction 2 | Easy Bottom Tab Navigation with back stack history 3 | 4 | 5 | InstaLikeFragmentTransaction is an open source repository with custom Bottom tab Fragment backstack transaction and stack history for Tabs. 6 | 7 | What inspired me to write this post ? 8 | ------------------------------------- 9 | 10 | The reason is “Instagram”. I love to play with Insta app since it is easy, robust and less screen navigation, which in turn collectively bottom tab navigation took the peek place in the Android community. 11 | 12 | Fragment Child nested fragments push (CLICK ME) and pop (Back arrow pressed) behaviour 13 | -------------------------------------------------------------------------------------- 14 | 15 | 16 | 17 | 18 | 19 | Fragment History Behaviour on back hardware soft key pressed 20 | ------------------------------------------------------------ 21 | 22 | 23 | 24 | 25 | Why TabLayout instead of Design support BottomNavigationView? 26 | ------------------------------------------------------------- 27 | 28 | 1. It’s not flexible. 29 | 2. Random behaviour if there are less tab items. 30 | 3. 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 ;-) ). 31 | 32 | 33 | For a tutorial, refer my blog post on https://blog.f22labs.com/instagram-like-bottom-tab-fragment-transaction-android-389976fb8759 34 | 35 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | defaultConfig { 7 | applicationId "com.f22labs.instalikefragmenttransaction" 8 | minSdkVersion 15 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | 23 | ext { 24 | supportLibraryVersion = '25.2.0' 25 | butterknifeVersion = "8.5.1" 26 | 27 | } 28 | 29 | 30 | 31 | dependencies { 32 | 33 | 34 | compile "com.jakewharton:butterknife:$butterknifeVersion" 35 | annotationProcessor "com.jakewharton:butterknife-compiler:$butterknifeVersion" 36 | 37 | compile "com.android.support:appcompat-v7:$supportLibraryVersion" 38 | compile "com.android.support:design:$supportLibraryVersion" 39 | compile "com.android.support:support-v4:$supportLibraryVersion" 40 | } 41 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/f22labs/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/f22labs/instalikefragmenttransaction/activities/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.f22labs.instalikefragmenttransaction.activities; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.Toolbar; 8 | 9 | import com.f22labs.instalikefragmenttransaction.R; 10 | 11 | 12 | /** 13 | * Created by f22labs on 07/03/17. 14 | */ 15 | 16 | public class BaseActivity extends AppCompatActivity { 17 | 18 | @Override 19 | public void onCreate(@Nullable Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | 22 | } 23 | 24 | public void initToolbar(Toolbar toolbar, boolean isBackEnabled) { 25 | setSupportActionBar(toolbar); 26 | 27 | if(isBackEnabled) { 28 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 29 | getSupportActionBar().setDisplayShowHomeEnabled(true); 30 | getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_arrow_back); 31 | 32 | } 33 | } 34 | 35 | public void initToolbar(Toolbar toolbar, String title, boolean isBackEnabled) { 36 | 37 | setSupportActionBar(toolbar); 38 | 39 | if(isBackEnabled) { 40 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 41 | getSupportActionBar().setDisplayShowHomeEnabled(true); 42 | getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_arrow_back); 43 | 44 | } 45 | 46 | getSupportActionBar().setTitle(title); 47 | 48 | 49 | 50 | } 51 | 52 | 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/f22labs/instalikefragmenttransaction/activities/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.f22labs.instalikefragmenttransaction.activities; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.TabLayout; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v7.widget.Toolbar; 7 | import android.view.LayoutInflater; 8 | import android.view.MenuItem; 9 | import android.view.View; 10 | import android.widget.FrameLayout; 11 | import android.widget.ImageView; 12 | 13 | import com.f22labs.instalikefragmenttransaction.R; 14 | import com.f22labs.instalikefragmenttransaction.fragments.BaseFragment; 15 | import com.f22labs.instalikefragmenttransaction.fragments.NewsFragment; 16 | import com.f22labs.instalikefragmenttransaction.fragments.HomeFragment; 17 | import com.f22labs.instalikefragmenttransaction.fragments.ShareFragment; 18 | import com.f22labs.instalikefragmenttransaction.fragments.ProfileFragment; 19 | import com.f22labs.instalikefragmenttransaction.fragments.SearchFragment; 20 | import com.f22labs.instalikefragmenttransaction.utils.FragmentHistory; 21 | import com.f22labs.instalikefragmenttransaction.utils.Utils; 22 | import com.f22labs.instalikefragmenttransaction.views.FragNavController; 23 | 24 | import butterknife.BindArray; 25 | import butterknife.BindView; 26 | import butterknife.ButterKnife; 27 | 28 | public class MainActivity extends BaseActivity implements BaseFragment.FragmentNavigation, FragNavController.TransactionListener, FragNavController.RootFragmentListener { 29 | 30 | 31 | @BindView(R.id.content_frame) 32 | FrameLayout contentFrame; 33 | 34 | @BindView(R.id.toolbar) 35 | Toolbar toolbar; 36 | 37 | private int[] mTabIconsSelected = { 38 | R.drawable.tab_home, 39 | R.drawable.tab_search, 40 | R.drawable.tab_share, 41 | R.drawable.tab_news, 42 | R.drawable.tab_profile}; 43 | 44 | 45 | @BindArray(R.array.tab_name) 46 | String[] TABS; 47 | 48 | @BindView(R.id.bottom_tab_layout) 49 | TabLayout bottomTabLayout; 50 | 51 | private FragNavController mNavController; 52 | 53 | private FragmentHistory fragmentHistory; 54 | 55 | @Override 56 | public void onCreate(Bundle savedInstanceState) { 57 | super.onCreate(savedInstanceState); 58 | setContentView(R.layout.activity_main); 59 | 60 | 61 | ButterKnife.bind(this); 62 | 63 | 64 | initToolbar(); 65 | 66 | initTab(); 67 | 68 | fragmentHistory = new FragmentHistory(); 69 | 70 | 71 | mNavController = FragNavController.newBuilder(savedInstanceState, getSupportFragmentManager(), R.id.content_frame) 72 | .transactionListener(this) 73 | .rootFragmentListener(this, TABS.length) 74 | .build(); 75 | 76 | 77 | switchTab(0); 78 | 79 | bottomTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { 80 | @Override 81 | public void onTabSelected(TabLayout.Tab tab) { 82 | 83 | fragmentHistory.push(tab.getPosition()); 84 | 85 | switchTab(tab.getPosition()); 86 | 87 | 88 | } 89 | 90 | @Override 91 | public void onTabUnselected(TabLayout.Tab tab) { 92 | 93 | } 94 | 95 | @Override 96 | public void onTabReselected(TabLayout.Tab tab) { 97 | 98 | mNavController.clearStack(); 99 | 100 | switchTab(tab.getPosition()); 101 | 102 | 103 | } 104 | }); 105 | 106 | } 107 | 108 | private void initToolbar() { 109 | setSupportActionBar(toolbar); 110 | 111 | 112 | } 113 | 114 | private void initTab() { 115 | if (bottomTabLayout != null) { 116 | for (int i = 0; i < TABS.length; i++) { 117 | bottomTabLayout.addTab(bottomTabLayout.newTab()); 118 | TabLayout.Tab tab = bottomTabLayout.getTabAt(i); 119 | if (tab != null) 120 | tab.setCustomView(getTabView(i)); 121 | } 122 | } 123 | } 124 | 125 | 126 | private View getTabView(int position) { 127 | View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.tab_item_bottom, null); 128 | ImageView icon = (ImageView) view.findViewById(R.id.tab_icon); 129 | icon.setImageDrawable(Utils.setDrawableSelector(MainActivity.this, mTabIconsSelected[position], mTabIconsSelected[position])); 130 | return view; 131 | } 132 | 133 | 134 | @Override 135 | public void onStart() { 136 | super.onStart(); 137 | } 138 | 139 | @Override 140 | public void onStop() { 141 | 142 | super.onStop(); 143 | } 144 | 145 | 146 | private void switchTab(int position) { 147 | mNavController.switchTab(position); 148 | 149 | 150 | // updateToolbarTitle(position); 151 | } 152 | 153 | 154 | @Override 155 | protected void onResume() { 156 | super.onResume(); 157 | } 158 | 159 | 160 | @Override 161 | protected void onPause() { 162 | super.onPause(); 163 | } 164 | 165 | 166 | @Override 167 | public boolean onOptionsItemSelected(MenuItem item) { 168 | 169 | switch (item.getItemId()) { 170 | 171 | 172 | case android.R.id.home: 173 | 174 | 175 | onBackPressed(); 176 | return true; 177 | } 178 | 179 | 180 | return super.onOptionsItemSelected(item); 181 | 182 | } 183 | 184 | @Override 185 | public void onBackPressed() { 186 | 187 | if (!mNavController.isRootFragment()) { 188 | mNavController.popFragment(); 189 | } else { 190 | 191 | if (fragmentHistory.isEmpty()) { 192 | super.onBackPressed(); 193 | } else { 194 | 195 | 196 | if (fragmentHistory.getStackSize() > 1) { 197 | 198 | int position = fragmentHistory.popPrevious(); 199 | 200 | switchTab(position); 201 | 202 | updateTabSelection(position); 203 | 204 | } else { 205 | 206 | switchTab(0); 207 | 208 | updateTabSelection(0); 209 | 210 | fragmentHistory.emptyStack(); 211 | } 212 | } 213 | 214 | } 215 | } 216 | 217 | 218 | private void updateTabSelection(int currentTab){ 219 | 220 | for (int i = 0; i < TABS.length; i++) { 221 | TabLayout.Tab selectedTab = bottomTabLayout.getTabAt(i); 222 | if(currentTab != i) { 223 | selectedTab.getCustomView().setSelected(false); 224 | }else{ 225 | selectedTab.getCustomView().setSelected(true); 226 | } 227 | } 228 | } 229 | 230 | @Override 231 | protected void onSaveInstanceState(Bundle outState) { 232 | super.onSaveInstanceState(outState); 233 | if (mNavController != null) { 234 | mNavController.onSaveInstanceState(outState); 235 | } 236 | } 237 | 238 | @Override 239 | public void pushFragment(Fragment fragment) { 240 | if (mNavController != null) { 241 | mNavController.pushFragment(fragment); 242 | } 243 | } 244 | 245 | 246 | @Override 247 | public void onTabTransaction(Fragment fragment, int index) { 248 | // If we have a backstack, show the back button 249 | if (getSupportActionBar() != null && mNavController != null) { 250 | 251 | 252 | updateToolbar(); 253 | 254 | } 255 | } 256 | 257 | private void updateToolbar() { 258 | getSupportActionBar().setDisplayHomeAsUpEnabled(!mNavController.isRootFragment()); 259 | getSupportActionBar().setDisplayShowHomeEnabled(!mNavController.isRootFragment()); 260 | getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_arrow_back); 261 | } 262 | 263 | 264 | @Override 265 | public void onFragmentTransaction(Fragment fragment, FragNavController.TransactionType transactionType) { 266 | //do fragmentty stuff. Maybe change title, I'm not going to tell you how to live your life 267 | // If we have a backstack, show the back button 268 | if (getSupportActionBar() != null && mNavController != null) { 269 | 270 | updateToolbar(); 271 | 272 | } 273 | } 274 | 275 | @Override 276 | public Fragment getRootFragment(int index) { 277 | switch (index) { 278 | 279 | case FragNavController.TAB1: 280 | return new HomeFragment(); 281 | case FragNavController.TAB2: 282 | return new SearchFragment(); 283 | case FragNavController.TAB3: 284 | return new ShareFragment(); 285 | case FragNavController.TAB4: 286 | return new NewsFragment(); 287 | case FragNavController.TAB5: 288 | return new ProfileFragment(); 289 | 290 | 291 | } 292 | throw new IllegalStateException("Need to send an index that we know"); 293 | } 294 | 295 | 296 | // private void updateToolbarTitle(int position){ 297 | // 298 | // 299 | // getSupportActionBar().setTitle(TABS[position]); 300 | // 301 | // } 302 | 303 | 304 | public void updateToolbarTitle(String title) { 305 | 306 | 307 | getSupportActionBar().setTitle(title); 308 | 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /app/src/main/java/com/f22labs/instalikefragmenttransaction/fragments/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package com.f22labs.instalikefragmenttransaction.fragments; 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 | * Created by f22labs on 07/03/17. 10 | */ 11 | 12 | public class BaseFragment extends Fragment { 13 | 14 | public static final String ARGS_INSTANCE = "com.f22labs.instalikefragmenttransaction"; 15 | 16 | 17 | FragmentNavigation mFragmentNavigation; 18 | 19 | @Override 20 | public void onCreate(@Nullable Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | 23 | } 24 | 25 | 26 | @Override 27 | public void onAttach(Context context) { 28 | super.onAttach(context); 29 | if (context instanceof FragmentNavigation) { 30 | mFragmentNavigation = (FragmentNavigation) context; 31 | } 32 | } 33 | 34 | public interface FragmentNavigation { 35 | void pushFragment(Fragment fragment); 36 | } 37 | 38 | 39 | 40 | 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/f22labs/instalikefragmenttransaction/fragments/HomeFragment.java: -------------------------------------------------------------------------------- 1 | package com.f22labs.instalikefragmenttransaction.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.Button; 9 | import android.widget.TextView; 10 | import android.widget.Toast; 11 | 12 | import com.f22labs.instalikefragmenttransaction.R; 13 | import com.f22labs.instalikefragmenttransaction.activities.MainActivity; 14 | import com.f22labs.instalikefragmenttransaction.utils.Utils; 15 | 16 | import butterknife.BindView; 17 | import butterknife.ButterKnife; 18 | 19 | 20 | public class HomeFragment extends BaseFragment { 21 | 22 | 23 | @BindView(R.id.btn_click_me) 24 | Button btnClickMe; 25 | 26 | int fragCount; 27 | 28 | 29 | public static HomeFragment newInstance(int instance) { 30 | Bundle args = new Bundle(); 31 | args.putInt(ARGS_INSTANCE, instance); 32 | HomeFragment fragment = new HomeFragment(); 33 | fragment.setArguments(args); 34 | return fragment; 35 | } 36 | 37 | 38 | public HomeFragment() { 39 | // Required empty public constructor 40 | } 41 | 42 | @Override 43 | public void onCreate(Bundle savedInstanceState) { 44 | super.onCreate(savedInstanceState); 45 | 46 | setHasOptionsMenu(true); 47 | } 48 | 49 | @Override 50 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 51 | Bundle savedInstanceState) { 52 | // Inflate the layout for this fragment 53 | 54 | View view = inflater.inflate(R.layout.fragment_home, container, false); 55 | 56 | 57 | ButterKnife.bind(this, view); 58 | 59 | Bundle args = getArguments(); 60 | if (args != null) { 61 | fragCount = args.getInt(ARGS_INSTANCE); 62 | } 63 | 64 | 65 | return view; 66 | } 67 | 68 | @Override 69 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 70 | super.onViewCreated(view, savedInstanceState); 71 | 72 | 73 | btnClickMe.setOnClickListener(new View.OnClickListener() { 74 | @Override 75 | public void onClick(View v) { 76 | 77 | if (mFragmentNavigation != null) { 78 | mFragmentNavigation.pushFragment(HomeFragment.newInstance(fragCount + 1)); 79 | 80 | } 81 | } 82 | }); 83 | 84 | 85 | ( (MainActivity)getActivity()).updateToolbarTitle((fragCount == 0) ? "Home" : "Sub Home "+fragCount); 86 | 87 | } 88 | 89 | @Override 90 | public void onDestroyView() { 91 | super.onDestroyView(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/f22labs/instalikefragmenttransaction/fragments/NewsFragment.java: -------------------------------------------------------------------------------- 1 | package com.f22labs.instalikefragmenttransaction.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.widget.Toolbar; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.Button; 10 | 11 | import com.f22labs.instalikefragmenttransaction.R; 12 | import com.f22labs.instalikefragmenttransaction.activities.MainActivity; 13 | import com.f22labs.instalikefragmenttransaction.utils.Utils; 14 | 15 | import butterknife.BindView; 16 | import butterknife.ButterKnife; 17 | 18 | 19 | public class NewsFragment extends BaseFragment{ 20 | 21 | 22 | 23 | @BindView(R.id.btn_click_me) 24 | Button btnClickMe; 25 | 26 | int fragCount; 27 | 28 | 29 | public static NewsFragment newInstance(int instance) { 30 | Bundle args = new Bundle(); 31 | args.putInt(ARGS_INSTANCE, instance); 32 | NewsFragment fragment = new NewsFragment(); 33 | fragment.setArguments(args); 34 | return fragment; 35 | } 36 | 37 | 38 | 39 | @Override 40 | public void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | } 43 | 44 | @Override 45 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 46 | Bundle savedInstanceState) { 47 | // Inflate the layout for this fragment 48 | 49 | View view = inflater.inflate(R.layout.fragment_news, container, false); 50 | 51 | ButterKnife.bind(this, view); 52 | 53 | Bundle args = getArguments(); 54 | if (args != null) { 55 | fragCount = args.getInt(ARGS_INSTANCE); 56 | } 57 | 58 | 59 | 60 | return view; 61 | } 62 | 63 | 64 | @Override 65 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 66 | super.onViewCreated(view, savedInstanceState); 67 | 68 | 69 | btnClickMe.setOnClickListener(new View.OnClickListener() { 70 | @Override 71 | public void onClick(View v) { 72 | if (mFragmentNavigation != null) { 73 | mFragmentNavigation.pushFragment(NewsFragment.newInstance(fragCount + 1)); 74 | 75 | 76 | } 77 | } 78 | }); 79 | 80 | 81 | ( (MainActivity)getActivity()).updateToolbarTitle((fragCount == 0) ? "News" : "Sub News "+fragCount); 82 | 83 | 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/com/f22labs/instalikefragmenttransaction/fragments/ProfileFragment.java: -------------------------------------------------------------------------------- 1 | package com.f22labs.instalikefragmenttransaction.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.widget.Toolbar; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import com.f22labs.instalikefragmenttransaction.R; 10 | import com.f22labs.instalikefragmenttransaction.activities.MainActivity; 11 | 12 | import butterknife.BindView; 13 | import butterknife.ButterKnife; 14 | 15 | 16 | public class ProfileFragment extends BaseFragment{ 17 | 18 | 19 | 20 | 21 | @Override 22 | public void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | } 25 | 26 | @Override 27 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 28 | Bundle savedInstanceState) { 29 | // Inflate the layout for this fragment 30 | 31 | View view = inflater.inflate(R.layout.fragment_profile, container, false); 32 | 33 | ButterKnife.bind(this, view); 34 | 35 | ( (MainActivity)getActivity()).updateToolbarTitle("Profile"); 36 | 37 | 38 | return view; 39 | } 40 | 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/f22labs/instalikefragmenttransaction/fragments/SearchFragment.java: -------------------------------------------------------------------------------- 1 | package com.f22labs.instalikefragmenttransaction.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.widget.Toolbar; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import com.f22labs.instalikefragmenttransaction.R; 10 | import com.f22labs.instalikefragmenttransaction.activities.MainActivity; 11 | 12 | import butterknife.BindView; 13 | import butterknife.ButterKnife; 14 | 15 | 16 | public class SearchFragment extends BaseFragment{ 17 | 18 | 19 | 20 | @Override 21 | public void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | } 24 | 25 | @Override 26 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 27 | Bundle savedInstanceState) { 28 | // Inflate the layout for this fragment 29 | 30 | View view = inflater.inflate(R.layout.fragment_search, container, false); 31 | 32 | ButterKnife.bind(this, view); 33 | 34 | ( (MainActivity)getActivity()).updateToolbarTitle("Search"); 35 | 36 | 37 | return view; 38 | } 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/f22labs/instalikefragmenttransaction/fragments/ShareFragment.java: -------------------------------------------------------------------------------- 1 | package com.f22labs.instalikefragmenttransaction.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.widget.Toolbar; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import com.f22labs.instalikefragmenttransaction.R; 10 | import com.f22labs.instalikefragmenttransaction.activities.MainActivity; 11 | 12 | import butterknife.BindView; 13 | import butterknife.ButterKnife; 14 | 15 | 16 | public class ShareFragment extends BaseFragment{ 17 | 18 | 19 | 20 | @Override 21 | public void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | } 24 | 25 | @Override 26 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 27 | Bundle savedInstanceState) { 28 | // Inflate the layout for this fragment 29 | 30 | View view = inflater.inflate(R.layout.fragment_share, container, false); 31 | 32 | ButterKnife.bind(this, view); 33 | 34 | ( (MainActivity)getActivity()).updateToolbarTitle("Share"); 35 | 36 | 37 | return view; 38 | } 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/f22labs/instalikefragmenttransaction/utils/FragmentHistory.java: -------------------------------------------------------------------------------- 1 | package com.f22labs.instalikefragmenttransaction.utils; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | 6 | /** 7 | * Created by f22labs on 11/05/17. 8 | */ 9 | 10 | public class FragmentHistory { 11 | 12 | 13 | private ArrayList stackArr; 14 | 15 | /** 16 | * constructor to create stack with size 17 | * 18 | * @param 19 | */ 20 | public FragmentHistory() { 21 | stackArr = new ArrayList<>(); 22 | 23 | 24 | } 25 | 26 | /** 27 | * This method adds new entry to the top 28 | * of the stack 29 | * 30 | * @param entry 31 | * @throws Exception 32 | */ 33 | public void push(int entry) { 34 | 35 | if (isAlreadyExists(entry)) { 36 | return; 37 | } 38 | stackArr.add(entry); 39 | 40 | } 41 | 42 | private boolean isAlreadyExists(int entry) { 43 | return (stackArr.contains(entry)); 44 | } 45 | 46 | /** 47 | * This method removes an entry from the 48 | * top of the stack. 49 | * 50 | * @return 51 | * @throws Exception 52 | */ 53 | public int pop() { 54 | 55 | int entry = -1; 56 | if(!isEmpty()){ 57 | 58 | entry = stackArr.get(stackArr.size() - 1); 59 | 60 | stackArr.remove(stackArr.size() - 1); 61 | } 62 | return entry; 63 | } 64 | 65 | 66 | /** 67 | * This method removes an entry from the 68 | * top of the stack. 69 | * 70 | * @return 71 | * @throws Exception 72 | */ 73 | public int popPrevious() { 74 | 75 | int entry = -1; 76 | 77 | if(!isEmpty()){ 78 | entry = stackArr.get(stackArr.size() - 2); 79 | stackArr.remove(stackArr.size() - 2); 80 | } 81 | return entry; 82 | } 83 | 84 | 85 | 86 | /** 87 | * This method returns top of the stack 88 | * without removing it. 89 | * 90 | * @return 91 | */ 92 | public int peek() { 93 | if(!isEmpty()){ 94 | return stackArr.get(stackArr.size() - 1); 95 | } 96 | 97 | return -1; 98 | } 99 | 100 | 101 | 102 | public boolean isEmpty(){ 103 | return (stackArr.size() == 0); 104 | } 105 | 106 | 107 | public int getStackSize(){ 108 | return stackArr.size(); 109 | } 110 | 111 | public void emptyStack() { 112 | 113 | stackArr.clear(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/src/main/java/com/f22labs/instalikefragmenttransaction/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package com.f22labs.instalikefragmenttransaction.utils; 2 | 3 | import android.app.ProgressDialog; 4 | import android.content.Context; 5 | import android.content.pm.PackageInfo; 6 | import android.content.pm.PackageManager; 7 | import android.content.res.ColorStateList; 8 | import android.database.Cursor; 9 | import android.graphics.Bitmap; 10 | import android.graphics.Canvas; 11 | import android.graphics.Color; 12 | import android.graphics.Paint; 13 | import android.graphics.drawable.BitmapDrawable; 14 | import android.graphics.drawable.ColorDrawable; 15 | import android.graphics.drawable.Drawable; 16 | import android.graphics.drawable.StateListDrawable; 17 | import android.net.Uri; 18 | import android.os.Build; 19 | import android.provider.Settings; 20 | import android.support.design.widget.BottomSheetDialog; 21 | import android.support.v4.content.ContextCompat; 22 | import android.support.v4.widget.SwipeRefreshLayout; 23 | import android.text.TextUtils; 24 | import android.view.View; 25 | import android.widget.Button; 26 | import android.widget.EditText; 27 | import android.widget.ImageView; 28 | import android.widget.ProgressBar; 29 | import android.widget.TextView; 30 | import android.widget.Toast; 31 | 32 | 33 | import java.util.ArrayList; 34 | import java.util.HashMap; 35 | import java.util.TreeSet; 36 | 37 | /** 38 | * Created by f22labs on 07/03/17. 39 | */ 40 | 41 | public class Utils { 42 | 43 | 44 | public static final void showToast(Context context, String message) { 45 | 46 | Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 47 | } 48 | 49 | 50 | 51 | public static final String getDeviceID(Context context) { 52 | return Settings.Secure.getString(context.getContentResolver(), 53 | Settings.Secure.ANDROID_ID); 54 | } 55 | 56 | 57 | public static final String getVersionName(Context context) { 58 | 59 | PackageInfo pInfo = null; 60 | try { 61 | pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); 62 | } catch (PackageManager.NameNotFoundException e) { 63 | e.printStackTrace(); 64 | } 65 | return pInfo.versionName; 66 | 67 | } 68 | 69 | 70 | 71 | public static void setButtonBackgroundColor(Context context, Button button, int color) { 72 | 73 | if (Build.VERSION.SDK_INT >= 23) { 74 | button.setBackgroundColor(context.getResources().getColor(color, null)); 75 | } else { 76 | button.setBackgroundColor(context.getResources().getColor(color)); 77 | } 78 | } 79 | 80 | 81 | public static void setButtonBackgroundColor(Context context, TextView textView, int color) { 82 | 83 | if (Build.VERSION.SDK_INT >= 23) { 84 | textView.setBackgroundColor(context.getResources().getColor(color, null)); 85 | } else { 86 | textView.setBackgroundColor(context.getResources().getColor(color)); 87 | } 88 | } 89 | 90 | 91 | 92 | 93 | public static Drawable setDrawableSelector(Context context, int normal, int selected) { 94 | 95 | 96 | Drawable state_normal = ContextCompat.getDrawable(context, normal); 97 | 98 | Drawable state_pressed = ContextCompat.getDrawable(context, selected); 99 | 100 | 101 | Bitmap state_normal_bitmap = ((BitmapDrawable)state_normal).getBitmap(); 102 | 103 | // Setting alpha directly just didn't work, so we draw a new bitmap! 104 | Bitmap disabledBitmap = Bitmap.createBitmap( 105 | state_normal.getIntrinsicWidth(), 106 | state_normal.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 107 | Canvas canvas = new Canvas(disabledBitmap); 108 | 109 | Paint paint = new Paint(); 110 | paint.setAlpha(126); 111 | canvas.drawBitmap(state_normal_bitmap, 0, 0, paint); 112 | 113 | BitmapDrawable state_normal_drawable = new BitmapDrawable(context.getResources(), disabledBitmap); 114 | 115 | 116 | 117 | 118 | StateListDrawable drawable = new StateListDrawable(); 119 | 120 | drawable.addState(new int[]{android.R.attr.state_selected}, 121 | state_pressed); 122 | drawable.addState(new int[]{android.R.attr.state_enabled}, 123 | state_normal_drawable); 124 | 125 | return drawable; 126 | } 127 | 128 | 129 | public static StateListDrawable selectorRadioImage(Context context, Drawable normal, Drawable pressed) { 130 | StateListDrawable states = new StateListDrawable(); 131 | states.addState(new int[]{android.R.attr.state_checked}, pressed); 132 | states.addState(new int[]{}, normal); 133 | // imageView.setImageDrawable(states); 134 | return states; 135 | } 136 | 137 | public static StateListDrawable selectorRadioButton(Context context, int normal, int pressed) { 138 | StateListDrawable states = new StateListDrawable(); 139 | states.addState(new int[]{android.R.attr.state_checked}, new ColorDrawable(pressed)); 140 | states.addState(new int[]{}, new ColorDrawable(normal)); 141 | return states; 142 | } 143 | 144 | public static ColorStateList selectorRadioText(Context context, int normal, int pressed) { 145 | ColorStateList colorStates = new ColorStateList(new int[][]{new int[]{android.R.attr.state_checked}, new int[]{}}, new int[]{pressed, normal}); 146 | return colorStates; 147 | } 148 | 149 | 150 | public static StateListDrawable selectorRadioDrawable(Drawable normal, Drawable pressed) { 151 | StateListDrawable states = new StateListDrawable(); 152 | states.addState(new int[]{android.R.attr.state_checked}, pressed); 153 | states.addState(new int[]{}, normal); 154 | return states; 155 | } 156 | 157 | public static StateListDrawable selectorBackgroundColor(Context context, int normal, int pressed) { 158 | StateListDrawable states = new StateListDrawable(); 159 | states.addState(new int[]{android.R.attr.state_pressed}, new ColorDrawable(pressed)); 160 | states.addState(new int[]{}, new ColorDrawable(normal)); 161 | return states; 162 | } 163 | 164 | public static StateListDrawable selectorBackgroundDrawable(Drawable normal, Drawable pressed) { 165 | StateListDrawable states = new StateListDrawable(); 166 | states.addState(new int[]{android.R.attr.state_pressed}, pressed); 167 | states.addState(new int[]{}, normal); 168 | return states; 169 | } 170 | 171 | public static ColorStateList selectorText(Context context, int normal, int pressed) { 172 | ColorStateList colorStates = new ColorStateList(new int[][]{new int[]{android.R.attr.state_pressed}, new int[]{}}, new int[]{pressed, normal}); 173 | return colorStates; 174 | } 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | } 184 | -------------------------------------------------------------------------------- /app/src/main/java/com/f22labs/instalikefragmenttransaction/views/FragNavController.java: -------------------------------------------------------------------------------- 1 | package com.f22labs.instalikefragmenttransaction.views; 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 | -------------------------------------------------------------------------------- /app/src/main/java/com/f22labs/instalikefragmenttransaction/views/FragNavTransactionOptions.java: -------------------------------------------------------------------------------- 1 | package com.f22labs.instalikefragmenttransaction.views; 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 | 116 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bottom_shadow.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_back.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tab_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f22labs/InstaLikeFragmentTransaction/2fecf5064c507d1a3dc1a896e975ebdc9537b93e/app/src/main/res/drawable/tab_home.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/tab_news.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f22labs/InstaLikeFragmentTransaction/2fecf5064c507d1a3dc1a896e975ebdc9537b93e/app/src/main/res/drawable/tab_news.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/tab_profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f22labs/InstaLikeFragmentTransaction/2fecf5064c507d1a3dc1a896e975ebdc9537b93e/app/src/main/res/drawable/tab_profile.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/tab_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f22labs/InstaLikeFragmentTransaction/2fecf5064c507d1a3dc1a896e975ebdc9537b93e/app/src/main/res/drawable/tab_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/tab_share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f22labs/InstaLikeFragmentTransaction/2fecf5064c507d1a3dc1a896e975ebdc9537b93e/app/src/main/res/drawable/tab_share.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 33 | 34 | 35 | 36 | 37 | 38 | 42 | 43 | 44 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_home.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 |