34 Commits

Author SHA1 Message Date
terrakok ced094ea55 Merge branch 'develop' 2017-08-21 23:02:21 +03:00
terrakok ecd214fa36 update readme.md for new version 2017-08-21 22:58:35 +03:00
Konstantin dcfe1a32b8 Merge pull request #41 from Jeevuz/small_javadocs_fix
Changed the javadocs of the animation allowing methods to look similar.
2017-08-02 11:02:52 +03:00
Vasili Chyrvon 4120ce878d Changed the javadocs of the animation allowing methods to look similar. 2017-08-02 01:28:34 +03:00
Konstantin c9ca3f76da Merge pull request #37 from Jeevuz/patch-2
Update FragmentNavigator.java
2017-08-01 23:10:45 +03:00
Konstantin 9a7b4421c2 Merge pull request #38 from Jeevuz/patch-3
Update SupportFragmentNavigator.java
2017-08-01 23:09:47 +03:00
Konstantin 81774d2d12 Merge pull request #39 from Popalay/feature/activity-animations
Feature/activity animations
2017-08-01 23:09:04 +03:00
Denis Nikiforov 30f2f736c0 Update docs 2017-07-29 22:12:40 +03:00
Denis Nikiforov 0f59f588b8 Rename setupActivityTransactionAnimation to createStartActivityOptions 2017-07-29 22:09:50 +03:00
Denis Nikiforov 68bc8c809c Create activity transition only if activity intent is not null 2017-07-28 12:34:26 +03:00
Denis Nikiforov 37d5818413 Add support of activity transitions 2017-07-27 16:04:40 +03:00
Vasili Chyrvon 7344926349 Update SupportFragmentNavigator.java
Small change in docs
2017-07-18 01:03:58 +03:00
Vasili Chyrvon 357f8d9670 Update FragmentNavigator.java
Small change in docs
2017-07-18 01:01:59 +03:00
terrakok d8266b02b8 Merge branch 'develop' 2017-07-18 00:51:17 +03:00
terrakok 7337901f19 updated version to 2.0.0 AND fixed java docs 2017-07-18 00:50:45 +03:00
terrakok 2e647aec0e updated README 2017-07-18 00:37:38 +03:00
terrakok e10725955b update README file for new version 2017-07-18 00:35:09 +03:00
terrakok f357e9db3a Merge branch 'feature/fragment_animations' into develop 2017-07-17 23:26:57 +03:00
terrakok 5ed9df5409 added javaDocs for new setupFragmentTransactionAnimation navigator method AND update sample 2017-07-17 23:20:13 +03:00
Konstantin Tskhovrebov b493cf5a7b fix after merge 2017-07-15 13:47:29 +03:00
Konstantin Tskhovrebov 2be8ac0db2 Merge branch 'develop' into feature/fragment_animations 2017-07-15 13:45:15 +03:00
Konstantin Tskhovrebov b317498d95 changed weak references on manual set/remove result listener for Router 2017-07-15 13:44:53 +03:00
Konstantin Tskhovrebov 230d7ba064 added sample for present fragment transaction animation and returning result from screen 2017-07-15 01:37:59 +03:00
Konstantin Tskhovrebov b5c2d20ea3 added next fragment instance to applyFragmentAnimations method 2017-07-15 01:35:19 +03:00
Konstantin Tskhovrebov 1647377fb1 Merge branch 'develop' into feature/fragment_animations 2017-07-14 22:43:43 +03:00
Konstantin f6b8c0bfe1 Merge pull request #36 from terrakok/feature/result_bus
Feature/result_bus
2017-07-12 12:55:18 +03:00
terrakok 5a4eeef49f added javaDocs 2017-07-12 01:16:55 +03:00
terrakok 340e659502 removed ResultData marker 2017-07-12 01:05:10 +03:00
terrakok f88ab56ee9 changed call ordering for exitWithResult method 2017-07-12 01:03:16 +03:00
terrakok 3e26610157 renamed requestCode -> resultCode 2017-07-12 01:01:45 +03:00
terrakok 906baccdfe Added the ability to apply animation to a fragment transaction 2017-07-05 00:36:34 +03:00
terrakok 72d100197a added simple "bus" to Router for listen result data 2017-07-04 23:57:27 +03:00
terrakok 10918fd759 added warning for bad sample code 2017-05-31 01:10:44 +03:00
terrakok c02d5a1ff8 optimized navigator backToRoot implementation 2017-05-31 01:04:04 +03:00
36 changed files with 819 additions and 47 deletions
+32 -18
View File
@@ -1,18 +1,18 @@
# Cicerone
[![jCenter](https://api.bintray.com/packages/terrakok/terramaven/cicerone/images/download.svg)](https://bintray.com/terrakok/terramaven/cicerone/_latestVersion)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Join the chat at https://gitter.im/terrakok/Cicerone](https://img.shields.io/badge/Gitter-Join%20Chat-brightred.svg?style=flat)](https://gitter.im/terrakok/Cicerone)
[![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Cicerone-green.svg?style=true)](https://android-arsenal.com/details/1/4700)
[![Android Weekly](https://img.shields.io/badge/Android%20Weekly-250-lightgrey.svg)](http://androidweekly.net/issues/issue-250)
[![Telegram](https://img.shields.io/badge/Telegram-ENG-blue.svg)](https://t.me/Cicerone_ENG)
[![Telegram](https://img.shields.io/badge/Telegram-RUS-blue.svg)](https://t.me/Cicerone_RUS)
[![Android Weekly](https://img.shields.io/badge/Android%20Weekly-250-green.svg)](http://androidweekly.net/issues/issue-250)
[![Android Weekly](https://img.shields.io/badge/Android%20Weekly-271-green.svg)](http://androidweekly.net/issues/issue-271)
![](https://habrastorage.org/files/644/32e/9eb/64432e9eb3664723b3ee438449dab3b0.png)
Cicerone (a guide, one who conducts sightseers) is a lightweight library that makes the navigation in an Android app easy.
It was designed to be used with the MVP pattern (try [Moxy](https://github.com/Arello-Mobile/Moxy)), but will work great with any architecture.
[Russian version readme](https://github.com/terrakok/Cicerone/blob/develop/README_RUS.md)
## Main advantages
+ is not tied to Fragments
+ not a framework
@@ -21,6 +21,11 @@ It was designed to be used with the MVP pattern (try [Moxy](https://github.com/A
+ functionality is simple to extend
+ suitable for Unit Testing
## Version 2.+
+ easy screen result subscription
+ predefined navigator ready for setup transition animation
**See the sample application**
## How to add
Add the dependency in your build.gradle:
```groovy
@@ -140,30 +145,39 @@ This commands set will fulfill the needs of the most applications. But if you ne
The library provides predefined navigators for _Fragments_ to use inside _Activity_.
To use, just provide it with the container and _FragmentManager_ and override few simple methods.
```java
private Navigator navigator = new SupportFragmentNavigator(
getSupportFragmentManager(), R.id.main_container) {
private Navigator navigator = new SupportAppNavigator(this, R.id.container) {
@Override
protected Intent createActivityIntent(String screenKey, Object data) {
return null;
}
@Override
protected Fragment createFragment(String screenKey, Object data) {
return SampleFragment.getNewInstance((int) data);
switch (screenKey) {
case Screens.PROFILE_SCREEN:
return new ProfileFragment();
case Screens.SELECT_PHOTO_SCREEN:
return SelectPhotoFragment.getNewInstance((int) data);
}
return null;
}
@Override
protected void showSystemMessage(String message) {
Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
}
@Override
protected void exit() {
finish();
protected void setupFragmentTransactionAnimation(
Command command,
Fragment currentFragment,
Fragment nextFragment,
FragmentTransaction fragmentTransaction) {
//setup animation
}
};
```
## Sample
To see how to add, initialize and use the library and predefined navigators check out the sample.
![](https://habrastorage.org/files/16d/2ee/6e3/16d2ee6e33a0428eb4f0dcab8ce6b294.gif)
![](https://hsto.org/files/867/638/c33/867638c338704489b3107a6d7cb28c2d.gif)
![](https://habrastorage.org/web/a94/d73/653/a94d736534694d9daa994e0c260fca28.gif)
![](https://habrastorage.org/web/6dd/a19/15c/6dda1915cdcf4f14bed16fcffb3fd938.gif)
![](https://habrastorage.org/web/a63/881/7f8/a638817f8bba49daacc4fa427987fabb.gif)
## Participants
+ idea and code - Konstantin Tskhovrebov (@terrakok)
+4
View File
@@ -1,5 +1,9 @@
# Cicerone
[![Telegram](https://img.shields.io/badge/Telegram-RUS-blue.svg)](https://t.me/Cicerone_RUS)
![](https://habrastorage.org/files/644/32e/9eb/64432e9eb3664723b3ee438449dab3b0.png)
Cicerone (_"чи-че-ро́-не"_ - устар. гид) - легкая библиотека для простой реализации навигации в андроид приложении.
Разработана для использования в MVP архитектуре (попробуйте [Moxy](https://github.com/Arello-Mobile/Moxy)), но легко встраивается в любые решения.
+1 -1
View File
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip
+1 -1
View File
@@ -15,7 +15,7 @@ ext {
bintrayName = 'cicerone'
publishedGroupId = 'ru.terrakok.cicerone'
artifact = 'cicerone'
libraryVersion = '1.2.1'
libraryVersion = '2.1.0'
gitUrl = 'https://github.com/terrakok/Cicerone'
allLicenses = ['MIT']
}
@@ -1,10 +1,13 @@
package ru.terrakok.cicerone;
import java.util.HashMap;
import ru.terrakok.cicerone.commands.Back;
import ru.terrakok.cicerone.commands.BackTo;
import ru.terrakok.cicerone.commands.Forward;
import ru.terrakok.cicerone.commands.Replace;
import ru.terrakok.cicerone.commands.SystemMessage;
import ru.terrakok.cicerone.result.ResultListener;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
@@ -19,10 +22,49 @@ import ru.terrakok.cicerone.commands.SystemMessage;
*/
public class Router extends BaseRouter {
private HashMap<Integer, ResultListener> resultListeners = new HashMap<>();
public Router() {
super();
}
/**
* Subscribe to the screen result.<br>
* <b>Note:</b> only one listener can subscribe to a unique resultCode!<br>
* You must call a <b>removeResultListener()</b> to avoid a memory leak.
*
* @param resultCode key for filter results
* @param listener result listener
*/
public void setResultListener(Integer resultCode, ResultListener listener) {
resultListeners.put(resultCode, listener);
}
/**
* Unsubscribe from the screen result.
*
* @param resultCode key for filter results
*/
public void removeResultListener(Integer resultCode) {
resultListeners.remove(resultCode);
}
/**
* Send result data to subscriber.
*
* @param resultCode result data key
* @param result result data
* @return TRUE if listener was notified and FALSE otherwise
*/
protected boolean sendResult(Integer resultCode, Object result) {
ResultListener resultListener = resultListeners.get(resultCode);
if (resultListener != null) {
resultListener.onResult(result);
return true;
}
return false;
}
/**
* Open new screen and add it to the screens chain.
*
@@ -138,6 +180,17 @@ public class Router extends BaseRouter {
executeCommand(new Back());
}
/**
* Return to the previous screen in the chain and send result data.
*
* @param resultCode result data key
* @param result result data
*/
public void exitWithResult(Integer resultCode, Object result) {
exit();
sendResult(resultCode, result);
}
/**
* Return to the previous screen in the chain and show system message.
*
@@ -3,6 +3,7 @@ package ru.terrakok.cicerone.android;
import android.app.Activity;
import android.app.FragmentManager;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
import ru.terrakok.cicerone.commands.BackTo;
@@ -34,6 +35,17 @@ public abstract class AppNavigator extends FragmentNavigator {
this.activity = activity;
}
/**
* Override this method to create option for start activity
*
* @param command current navigation command. Will be only {@link Forward} or {@link Replace}
* @param activityIntent activity intent
* @return transition options
*/
protected Bundle createStartActivityOptions(Command command, Intent activityIntent) {
return null;
}
@Override
public void applyCommand(Command command) {
if (command instanceof Forward) {
@@ -42,7 +54,8 @@ public abstract class AppNavigator extends FragmentNavigator {
// Start activity
if (activityIntent != null) {
activity.startActivity(activityIntent);
Bundle options = createStartActivityOptions(command, activityIntent);
activity.startActivity(activityIntent, options);
return;
}
@@ -52,7 +65,8 @@ public abstract class AppNavigator extends FragmentNavigator {
// Replace activity
if (activityIntent != null) {
activity.startActivity(activityIntent);
Bundle options = createStartActivityOptions(command, activityIntent);
activity.startActivity(activityIntent, options);
activity.finish();
return;
}
@@ -2,6 +2,7 @@ package ru.terrakok.cicerone.android;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import ru.terrakok.cicerone.Navigator;
import ru.terrakok.cicerone.commands.Back;
@@ -42,6 +43,21 @@ public abstract class FragmentNavigator implements Navigator {
this.containerId = containerId;
}
/**
* Override this method to setup custom fragment transaction animation.
*
* @param command current navigation command. Will be only {@link Forward} or {@link Replace}
* @param currentFragment current fragment in container
* (for {@link Replace} command it will be screen previous in new chain, NOT replaced screen)
* @param nextFragment next screen fragment
* @param fragmentTransaction fragment transaction
*/
protected void setupFragmentTransactionAnimation(Command command,
Fragment currentFragment,
Fragment nextFragment,
FragmentTransaction fragmentTransaction) {
}
@Override
public void applyCommand(Command command) {
if (command instanceof Forward) {
@@ -51,8 +67,16 @@ public abstract class FragmentNavigator implements Navigator {
unknownScreen(command);
return;
}
fragmentManager
.beginTransaction()
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
setupFragmentTransactionAnimation(
command,
fragmentManager.findFragmentById(containerId),
fragment,
fragmentTransaction
);
fragmentTransaction
.replace(containerId, fragment)
.addToBackStack(forward.getScreenKey())
.commit();
@@ -71,14 +95,29 @@ public abstract class FragmentNavigator implements Navigator {
}
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStackImmediate();
fragmentManager
.beginTransaction()
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
setupFragmentTransactionAnimation(
command,
fragmentManager.findFragmentById(containerId),
fragment,
fragmentTransaction
);
fragmentTransaction
.replace(containerId, fragment)
.addToBackStack(replace.getScreenKey())
.commit();
} else {
fragmentManager
.beginTransaction()
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
setupFragmentTransactionAnimation(
command,
fragmentManager.findFragmentById(containerId),
fragment,
fragmentTransaction
);
fragmentTransaction
.replace(containerId, fragment)
.commit();
}
@@ -106,10 +145,7 @@ public abstract class FragmentNavigator implements Navigator {
}
private void backToRoot() {
for (int i = 0; i < fragmentManager.getBackStackEntryCount(); i++) {
fragmentManager.popBackStack();
}
fragmentManager.executePendingTransactions();
fragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
/**
@@ -147,4 +183,4 @@ public abstract class FragmentNavigator implements Navigator {
protected void unknownScreen(Command command) {
throw new RuntimeException("Can't create a screen for passed screenKey.");
}
}
}
@@ -2,6 +2,7 @@ package ru.terrakok.cicerone.android;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.widget.Toast;
@@ -35,6 +36,17 @@ public abstract class SupportAppNavigator extends SupportFragmentNavigator {
this.activity = activity;
}
/**
* Override this method to create option for start activity
*
* @param command current navigation command. Will be only {@link Forward} or {@link Replace}
* @param activityIntent activity intent
* @return transition options
*/
protected Bundle createStartActivityOptions(Command command, Intent activityIntent) {
return null;
}
@Override
public void applyCommand(Command command) {
if (command instanceof Forward) {
@@ -43,7 +55,8 @@ public abstract class SupportAppNavigator extends SupportFragmentNavigator {
// Start activity
if (activityIntent != null) {
activity.startActivity(activityIntent);
Bundle options = createStartActivityOptions(command, activityIntent);
activity.startActivity(activityIntent, options);
return;
}
@@ -53,7 +66,8 @@ public abstract class SupportAppNavigator extends SupportFragmentNavigator {
// Replace activity
if (activityIntent != null) {
activity.startActivity(activityIntent);
Bundle options = createStartActivityOptions(command, activityIntent);
activity.startActivity(activityIntent, options);
activity.finish();
return;
}
@@ -2,6 +2,7 @@ package ru.terrakok.cicerone.android;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import ru.terrakok.cicerone.Navigator;
import ru.terrakok.cicerone.commands.Back;
@@ -42,6 +43,21 @@ public abstract class SupportFragmentNavigator implements Navigator {
this.containerId = containerId;
}
/**
* Override this method to setup custom fragment transaction animation.
*
* @param command current navigation command. Will be only {@link Forward} or {@link Replace}
* @param currentFragment current fragment in container
* (for {@link Replace} command it will be screen previous in new chain, NOT replaced screen)
* @param nextFragment next screen fragment
* @param fragmentTransaction fragment transaction
*/
protected void setupFragmentTransactionAnimation(Command command,
Fragment currentFragment,
Fragment nextFragment,
FragmentTransaction fragmentTransaction) {
}
@Override
public void applyCommand(Command command) {
if (command instanceof Forward) {
@@ -51,8 +67,16 @@ public abstract class SupportFragmentNavigator implements Navigator {
unknownScreen(command);
return;
}
fragmentManager
.beginTransaction()
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
setupFragmentTransactionAnimation(
command,
fragmentManager.findFragmentById(containerId),
fragment,
fragmentTransaction
);
fragmentTransaction
.replace(containerId, fragment)
.addToBackStack(forward.getScreenKey())
.commit();
@@ -71,14 +95,29 @@ public abstract class SupportFragmentNavigator implements Navigator {
}
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStackImmediate();
fragmentManager
.beginTransaction()
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
setupFragmentTransactionAnimation(
command,
fragmentManager.findFragmentById(containerId),
fragment,
fragmentTransaction
);
fragmentTransaction
.replace(containerId, fragment)
.addToBackStack(replace.getScreenKey())
.commit();
} else {
fragmentManager
.beginTransaction()
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
setupFragmentTransactionAnimation(
command,
fragmentManager.findFragmentById(containerId),
fragment,
fragmentTransaction
);
fragmentTransaction
.replace(containerId, fragment)
.commit();
}
@@ -106,10 +145,7 @@ public abstract class SupportFragmentNavigator implements Navigator {
}
private void backToRoot() {
for (int i = 0; i < fragmentManager.getBackStackEntryCount(); i++) {
fragmentManager.popBackStack();
}
fragmentManager.executePendingTransactions();
fragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
/**
@@ -146,4 +182,4 @@ public abstract class SupportFragmentNavigator implements Navigator {
protected void unknownScreen(Command command) {
throw new RuntimeException("Can't create a screen for passed screenKey.");
}
}
}
@@ -0,0 +1,15 @@
package ru.terrakok.cicerone.result;
/**
* @author Konstantin Tskhovrebov (aka terrakok) on 04.07.17.
*/
public interface ResultListener {
/**
* Received result from screen.
*
* @param resultData
*/
void onResult(Object resultData);
}
@@ -2,6 +2,7 @@ package android.app;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
/***
* Created by laomo on 2016-11-21.
@@ -11,6 +12,10 @@ public class Activity extends Context {
throw new RuntimeException("Stub!");
}
public void startActivity(Intent intent, Bundle options) {
throw new RuntimeException("Stub!");
}
public FragmentManager getFragmentManager() {
throw new RuntimeException("Stub!");
}
@@ -4,6 +4,7 @@ package android.app;
* Created by laomo on 2016-11-21.
*/
public class FragmentManager {
public static final int POP_BACK_STACK_INCLUSIVE = 1<<0;
public FragmentTransaction beginTransaction() {
throw new RuntimeException("Stub!");
@@ -33,6 +34,10 @@ public class FragmentManager {
throw new RuntimeException("Stub!");
}
public Fragment findFragmentById(int id) {
throw new RuntimeException("Stub!");
}
public interface BackStackEntry {
int getId();
@@ -6,6 +6,7 @@ package android.support.v4.app;
*/
public class FragmentManager {
public static final int POP_BACK_STACK_INCLUSIVE = 1<<0;
public FragmentTransaction beginTransaction() {
throw new RuntimeException("Stub!");
@@ -35,6 +36,9 @@ public class FragmentManager {
throw new RuntimeException("Stub!");
}
public Fragment findFragmentById(int id) {
throw new RuntimeException("Stub!");
}
public interface BackStackEntry {
int getId();
+1 -1
View File
@@ -9,7 +9,7 @@ android {
buildToolsVersion '25.0.2'
defaultConfig {
minSdkVersion 16
minSdkVersion 19
targetSdkVersion 25
versionCode 1
versionName "1.0.0"
+1
View File
@@ -20,5 +20,6 @@
<activity android:name=".ui.main.MainActivity"/>
<activity android:name=".ui.bottom.BottomNavigationActivity"/>
<activity android:name=".ui.animations.ProfileActivity"/>
</application>
</manifest>
@@ -18,4 +18,7 @@ public class Screens {
public static final String FORWARD_SCREEN = "forward screen";
public static final String GITHUB_SCREEN = "github screen";
public static final String PROFILE_SCREEN = "profile screen";
public static final String SELECT_PHOTO_SCREEN = "select photo screen";
}
@@ -5,6 +5,9 @@ import javax.inject.Singleton;
import dagger.Component;
import ru.terrakok.cicerone.sample.dagger.module.LocalNavigationModule;
import ru.terrakok.cicerone.sample.dagger.module.NavigationModule;
import ru.terrakok.cicerone.sample.ui.animations.ProfileActivity;
import ru.terrakok.cicerone.sample.ui.animations.photos.SelectPhotoFragment;
import ru.terrakok.cicerone.sample.ui.animations.profile.ProfileFragment;
import ru.terrakok.cicerone.sample.ui.bottom.BottomNavigationActivity;
import ru.terrakok.cicerone.sample.ui.bottom.TabContainerFragment;
import ru.terrakok.cicerone.sample.ui.main.MainActivity;
@@ -31,4 +34,10 @@ public interface AppComponent {
void inject(BottomNavigationActivity activity);
void inject(TabContainerFragment fragment);
void inject(ProfileFragment fragment);
void inject(SelectPhotoFragment fragment);
void inject(ProfileActivity activity);
}
@@ -0,0 +1,42 @@
package ru.terrakok.cicerone.sample.mvp.animation.photos;
import com.arellomobile.mvp.InjectViewState;
import com.arellomobile.mvp.MvpPresenter;
import ru.terrakok.cicerone.Router;
import ru.terrakok.cicerone.sample.R;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@InjectViewState
public class SelectPhotoPresenter extends MvpPresenter<SelectPhotoView> {
private Router router;
private final int RESULT_CODE;
public SelectPhotoPresenter(Router router, int resultCode) {
this.router = router;
RESULT_CODE = resultCode;
}
@Override
protected void onFirstViewAttach() {
super.onFirstViewAttach();
getViewState().showPhotos(new int[] {
R.drawable.ava_1,
R.drawable.ava_2,
R.drawable.ava_3,
R.drawable.ava_4
});
}
public void onPhotoClick(int photoRes) {
router.exitWithResult(RESULT_CODE, photoRes);
}
public void onBackPressed() {
router.exit();
}
}
@@ -0,0 +1,14 @@
package ru.terrakok.cicerone.sample.mvp.animation.photos;
import com.arellomobile.mvp.MvpView;
import com.arellomobile.mvp.viewstate.strategy.AddToEndSingleStrategy;
import com.arellomobile.mvp.viewstate.strategy.StateStrategyType;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@StateStrategyType(AddToEndSingleStrategy.class)
public interface SelectPhotoView extends MvpView {
void showPhotos(int[] resurceIds);
}
@@ -0,0 +1,57 @@
package ru.terrakok.cicerone.sample.mvp.animation.profile;
import com.arellomobile.mvp.InjectViewState;
import com.arellomobile.mvp.MvpPresenter;
import ru.terrakok.cicerone.Router;
import ru.terrakok.cicerone.result.ResultListener;
import ru.terrakok.cicerone.sample.Screens;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@InjectViewState
public class ProfilePresenter extends MvpPresenter<ProfileView> {
private static final int PHOTO_RESULT_CODE = 42;
private Router router;
private int currentPhoto;
public ProfilePresenter(int defaultPhoto, Router router) {
currentPhoto = defaultPhoto;
this.router = router;
router.setResultListener(PHOTO_RESULT_CODE, new ResultListener() {
@Override
public void onResult(Object resultData) {
currentPhoto = (int) resultData;
updatePhoto();
}
});
}
@Override
protected void onFirstViewAttach() {
super.onFirstViewAttach();
updatePhoto();
}
@Override
public void onDestroy() {
router.removeResultListener(PHOTO_RESULT_CODE);
super.onDestroy();
}
private void updatePhoto() {
getViewState().showPhoto(currentPhoto);
}
public void onPhotoClicked() {
router.navigateTo(Screens.SELECT_PHOTO_SCREEN, PHOTO_RESULT_CODE);
}
public void onBackPressed() {
router.exit();
}
}
@@ -0,0 +1,14 @@
package ru.terrakok.cicerone.sample.mvp.animation.profile;
import com.arellomobile.mvp.MvpView;
import com.arellomobile.mvp.viewstate.strategy.AddToEndSingleStrategy;
import com.arellomobile.mvp.viewstate.strategy.StateStrategyType;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@StateStrategyType(AddToEndSingleStrategy.class)
public interface ProfileView extends MvpView {
void showPhoto(int resId);
}
@@ -60,6 +60,7 @@ public class SamplePresenter extends MvpPresenter<SampleView> {
future = executorService.schedule(new Runnable() {
@Override
public void run() {
//WARNING! Navigation must be only in UI thread. this method works only for sample :)
router.navigateTo(Screens.SAMPLE_SCREEN + (screenNumber + 1), screenNumber + 1);
}
}, 5, TimeUnit.SECONDS);
@@ -23,6 +23,10 @@ public class StartActivityPresenter extends MvpPresenter<StartActivityView> {
router.navigateTo(Screens.BOTTOM_NAVIGATION_ACTIVITY_SCREEN);
}
public void onResultWithAnimationPressed() {
router.navigateTo(Screens.PROFILE_SCREEN);
}
public void onBackPressed() {
router.exit();
}
@@ -0,0 +1,117 @@
package ru.terrakok.cicerone.sample.ui.animations;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.transition.ChangeBounds;
import android.view.View;
import javax.inject.Inject;
import ru.terrakok.cicerone.Navigator;
import ru.terrakok.cicerone.NavigatorHolder;
import ru.terrakok.cicerone.android.SupportAppNavigator;
import ru.terrakok.cicerone.commands.Command;
import ru.terrakok.cicerone.commands.Forward;
import ru.terrakok.cicerone.commands.Replace;
import ru.terrakok.cicerone.sample.R;
import ru.terrakok.cicerone.sample.SampleApplication;
import ru.terrakok.cicerone.sample.Screens;
import ru.terrakok.cicerone.sample.ui.animations.photos.SelectPhotoFragment;
import ru.terrakok.cicerone.sample.ui.animations.profile.ProfileFragment;
import ru.terrakok.cicerone.sample.ui.common.BackButtonListener;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
public class ProfileActivity extends AppCompatActivity {
public static final String PHOTO_TRANSITION = "photo_trasition";
@Inject
NavigatorHolder navigatorHolder;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
SampleApplication.INSTANCE.getAppComponent().inject(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_container);
if (savedInstanceState == null) {
navigator.applyCommand(new Replace(Screens.PROFILE_SCREEN, null));
}
}
@Override
protected void onResumeFragments() {
super.onResumeFragments();
navigatorHolder.setNavigator(navigator);
}
@Override
protected void onPause() {
navigatorHolder.removeNavigator();
super.onPause();
}
private Navigator navigator = new SupportAppNavigator(this, R.id.container) {
@Override
protected Intent createActivityIntent(String screenKey, Object data) {
return null;
}
@Override
protected Fragment createFragment(String screenKey, Object data) {
switch (screenKey) {
case Screens.PROFILE_SCREEN:
return new ProfileFragment();
case Screens.SELECT_PHOTO_SCREEN:
return SelectPhotoFragment.getNewInstance((int) data);
}
return null;
}
@Override
protected void setupFragmentTransactionAnimation(Command command, Fragment currentFragment, Fragment nextFragment, FragmentTransaction fragmentTransaction) {
if (command instanceof Forward
&& currentFragment instanceof ProfileFragment
&& nextFragment instanceof SelectPhotoFragment) {
setupSharedElementForProfileToSelectPhoto(
(ProfileFragment) currentFragment,
(SelectPhotoFragment) nextFragment,
fragmentTransaction
);
}
}
};
private void setupSharedElementForProfileToSelectPhoto(ProfileFragment profileFragment,
SelectPhotoFragment selectPhotoFragment,
FragmentTransaction fragmentTransaction) {
ChangeBounds changeBounds = new ChangeBounds();
selectPhotoFragment.setSharedElementEnterTransition(changeBounds);
selectPhotoFragment.setSharedElementReturnTransition(changeBounds);
profileFragment.setSharedElementEnterTransition(changeBounds);
profileFragment.setSharedElementReturnTransition(changeBounds);
View view = profileFragment.getAvatarViewForAnimation();
fragmentTransaction.addSharedElement(view, PHOTO_TRANSITION);
selectPhotoFragment.setAnimationDestinationId((Integer) view.getTag());
}
@Override
public void onBackPressed() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.container);
if (fragment != null
&& fragment instanceof BackButtonListener
&& ((BackButtonListener) fragment).onBackPressed()) {
return;
} else {
super.onBackPressed();
}
}
}
@@ -0,0 +1,135 @@
package ru.terrakok.cicerone.sample.ui.animations.photos;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.arellomobile.mvp.MvpAppCompatFragment;
import com.arellomobile.mvp.presenter.InjectPresenter;
import com.arellomobile.mvp.presenter.ProvidePresenter;
import javax.inject.Inject;
import ru.terrakok.cicerone.Router;
import ru.terrakok.cicerone.sample.R;
import ru.terrakok.cicerone.sample.SampleApplication;
import ru.terrakok.cicerone.sample.mvp.animation.photos.SelectPhotoPresenter;
import ru.terrakok.cicerone.sample.mvp.animation.photos.SelectPhotoView;
import ru.terrakok.cicerone.sample.ui.animations.ProfileActivity;
import ru.terrakok.cicerone.sample.ui.common.BackButtonListener;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
public class SelectPhotoFragment extends MvpAppCompatFragment implements SelectPhotoView, BackButtonListener {
private static final String ARG_RESULT_CODE = "arg_result_code";
private static final String ARG_ANIM_DESTINATION = "arg_anim_dest";
private ImageView photo1;
private ImageView photo2;
private ImageView photo3;
private ImageView photo4;
@Inject
Router router;
@InjectPresenter
SelectPhotoPresenter presenter;
public static SelectPhotoFragment getNewInstance(int resultCode) {
SelectPhotoFragment fragment = new SelectPhotoFragment();
Bundle args = new Bundle();
args.putInt(ARG_RESULT_CODE, resultCode);
fragment.setArguments(args);
return fragment;
}
public void setAnimationDestinationId(int resId) {
Bundle arguments = getArguments();
arguments.putInt(ARG_ANIM_DESTINATION, resId);
setArguments(arguments);
}
private int getAnimationDestionationId() {
return getArguments().getInt(ARG_ANIM_DESTINATION);
}
@ProvidePresenter
SelectPhotoPresenter providePresenter() {
return new SelectPhotoPresenter(router, getArguments().getInt(ARG_RESULT_CODE));
}
@Override
public void onCreate(Bundle savedInstanceState) {
SampleApplication.INSTANCE.getAppComponent().inject(this);
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_select_photo, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
photo1 = (ImageView) view.findViewById(R.id.select_image_1);
photo2 = (ImageView) view.findViewById(R.id.select_image_2);
photo3 = (ImageView) view.findViewById(R.id.select_image_3);
photo4 = (ImageView) view.findViewById(R.id.select_image_4);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
photo1.setOnClickListener(clickListener);
photo2.setOnClickListener(clickListener);
photo3.setOnClickListener(clickListener);
photo4.setOnClickListener(clickListener);
}
private View.OnClickListener clickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
photo1.setTransitionName(null);
photo2.setTransitionName(null);
photo3.setTransitionName(null);
photo4.setTransitionName(null);
v.setTransitionName(ProfileActivity.PHOTO_TRANSITION);
presenter.onPhotoClick((Integer) v.getTag());
}
};
@Override
public void showPhotos(int[] resurceIds) {
if (resurceIds.length >= 4) {
photo1.setImageResource(resurceIds[0]);
photo2.setImageResource(resurceIds[1]);
photo3.setImageResource(resurceIds[2]);
photo4.setImageResource(resurceIds[3]);
photo1.setTag(resurceIds[0]);
photo2.setTag(resurceIds[1]);
photo3.setTag(resurceIds[2]);
photo4.setTag(resurceIds[3]);
//for shared element animation
int animRes = getAnimationDestionationId();
photo1.setTransitionName(animRes == resurceIds[0] ? ProfileActivity.PHOTO_TRANSITION : null);
photo2.setTransitionName(animRes == resurceIds[1] ? ProfileActivity.PHOTO_TRANSITION : null);
photo3.setTransitionName(animRes == resurceIds[2] ? ProfileActivity.PHOTO_TRANSITION : null);
photo4.setTransitionName(animRes == resurceIds[3] ? ProfileActivity.PHOTO_TRANSITION : null);
}
}
@Override
public boolean onBackPressed() {
presenter.onBackPressed();
return true;
}
}
@@ -0,0 +1,89 @@
package ru.terrakok.cicerone.sample.ui.animations.profile;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.arellomobile.mvp.MvpAppCompatFragment;
import com.arellomobile.mvp.presenter.InjectPresenter;
import com.arellomobile.mvp.presenter.ProvidePresenter;
import javax.inject.Inject;
import ru.terrakok.cicerone.Router;
import ru.terrakok.cicerone.sample.R;
import ru.terrakok.cicerone.sample.SampleApplication;
import ru.terrakok.cicerone.sample.mvp.animation.profile.ProfilePresenter;
import ru.terrakok.cicerone.sample.mvp.animation.profile.ProfileView;
import ru.terrakok.cicerone.sample.ui.animations.ProfileActivity;
import ru.terrakok.cicerone.sample.ui.common.BackButtonListener;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
public class ProfileFragment extends MvpAppCompatFragment implements ProfileView, BackButtonListener {
private ImageView avatar;
@Inject
Router router;
@InjectPresenter
ProfilePresenter presenter;
@ProvidePresenter
ProfilePresenter providePresenter() {
return new ProfilePresenter(R.drawable.ava_1, router);
}
@Override
public void onCreate(Bundle savedInstanceState) {
SampleApplication.INSTANCE.getAppComponent().inject(this);
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_profile, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
avatar = (ImageView) view.findViewById(R.id.avatar_imageView);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
avatar.setTransitionName(ProfileActivity.PHOTO_TRANSITION);
avatar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
presenter.onPhotoClicked();
}
});
}
@Override
public void showPhoto(int resId) {
avatar.setImageResource(resId);
//for shared element animation
avatar.setTag(resId);
}
public View getAvatarViewForAnimation() {
return avatar;
}
@Override
public boolean onBackPressed() {
presenter.onBackPressed();
return true;
}
}
@@ -25,6 +25,7 @@ import ru.terrakok.cicerone.sample.SampleApplication;
import ru.terrakok.cicerone.sample.Screens;
import ru.terrakok.cicerone.sample.mvp.start.StartActivityPresenter;
import ru.terrakok.cicerone.sample.mvp.start.StartActivityView;
import ru.terrakok.cicerone.sample.ui.animations.ProfileActivity;
import ru.terrakok.cicerone.sample.ui.bottom.BottomNavigationActivity;
import ru.terrakok.cicerone.sample.ui.main.MainActivity;
@@ -68,6 +69,12 @@ public class StartActivity extends MvpAppCompatActivity implements StartActivity
presenter.onMultiPressed();
}
});
findViewById(R.id.result_and_anim_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
presenter.onResultWithAnimationPressed();
}
});
}
@Override
@@ -87,6 +94,7 @@ public class StartActivity extends MvpAppCompatActivity implements StartActivity
presenter.onBackPressed();
}
//Sample fully custom navigator:
private Navigator navigator = new Navigator() {
@Override
public void applyCommand(Command command) {
@@ -114,6 +122,9 @@ public class StartActivity extends MvpAppCompatActivity implements StartActivity
case Screens.BOTTOM_NAVIGATION_ACTIVITY_SCREEN:
startActivity(new Intent(StartActivity.this, BottomNavigationActivity.class));
break;
case Screens.PROFILE_SCREEN:
startActivity(new Intent(StartActivity.this, ProfileActivity.class));
break;
default:
Log.e("Cicerone", "Unknown screen: " + command.getScreenKey());
break;
@@ -125,6 +136,7 @@ public class StartActivity extends MvpAppCompatActivity implements StartActivity
case Screens.START_ACTIVITY_SCREEN:
case Screens.MAIN_ACTIVITY_SCREEN:
case Screens.BOTTOM_NAVIGATION_ACTIVITY_SCREEN:
case Screens.PROFILE_SCREEN:
forward(new Forward(command.getScreenKey(), command.getTransitionData()));
finish();
break;
Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
@@ -36,4 +36,11 @@
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/multi_nav"/>
<Button
android:id="@+id/result_and_anim_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/result_and_anim_nav"/>
</LinearLayout>
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/avatar_imageView"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center_horizontal"
android:layout_margin="40dp"
android:scaleType="centerCrop"
android:src="@drawable/ava_1"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:layout_marginStart="24dp"
android:gravity="center"
android:text="Click on image for select other photo"
android:textSize="16sp"/>
</LinearLayout>
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/select_image_1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="centerCrop"/>
<ImageView
android:id="@+id/select_image_2"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="centerCrop"/>
<ImageView
android:id="@+id/select_image_3"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="centerCrop"/>
<ImageView
android:id="@+id/select_image_4"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="centerCrop"/>
</LinearLayout>
+1
View File
@@ -3,6 +3,7 @@
<string name="cicerone_description">Cicerone is a lightweight library that makes the navigation in an Android app easy.</string>
<string name="ordinary_nav">Ordinary navigation</string>
<string name="multi_nav">Multi navigation</string>
<string name="result_and_anim_nav">Resulting and animation sample</string>
<string name="tab_android">Android</string>
<string name="tab_bug">Bug</string>