Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b32a30c1a | |||
| 893ffc0461 | |||
| 0df11e3224 | |||
| 75ad389424 | |||
| 00577823dc | |||
| 671a117b96 | |||
| 3d1c2d392c | |||
| b7611e1a1b | |||
| d13af316d3 | |||
| 7d5cc26ea4 | |||
| c4d881ac47 | |||
| 0a53b9f07a | |||
| c2ad655af2 | |||
| a888073e1b | |||
| df68655b13 | |||
| 86227ae3b3 | |||
| 97878b1ad6 | |||
| 314ee2b456 | |||
| f4ef47c2d2 | |||
| 769d552e88 | |||
| afa4b69d7a | |||
| a9bdf0dd06 | |||
| 6ffa94ed3a | |||
| 690001ed2a | |||
| 90e015b6b3 | |||
| 44bcd0f977 | |||
| 60d0fabcf4 | |||
| 10a1c8af3e | |||
| 6834df73e6 | |||
| 04d40a5b90 | |||
| be40900e1e | |||
| f16f7b6d2c | |||
| f74f8391b6 | |||
| 2ce8c0a45d | |||
| 77ad6b4512 | |||
| efbdf913bd | |||
| dfb01389f7 | |||
| e390261b53 | |||
| 27f5275172 | |||
| d15f2b68ab | |||
| 44ed19858b | |||
| e7c195d910 | |||
| 23a4dbbb60 | |||
| 0ac81767dd | |||
| 54cdc51557 | |||
| 09ce640d9c | |||
| 01df673a34 | |||
| 553bae0be5 |
@@ -3,9 +3,11 @@ language: android
|
||||
android:
|
||||
components:
|
||||
- tools
|
||||
- build-tools-23.0.2
|
||||
- android-23
|
||||
- build-tools-25.0.0
|
||||
- android-25
|
||||
- extra-android-m2repository
|
||||
licenses:
|
||||
- '.+'
|
||||
|
||||
script:
|
||||
- ./gradlew test
|
||||
|
||||
@@ -20,22 +20,26 @@ Conductor is architecture-agnostic and does not try to force any design decision
|
||||
## Installation
|
||||
|
||||
```gradle
|
||||
compile 'com.bluelinelabs:conductor:2.0.5'
|
||||
compile 'com.bluelinelabs:conductor:2.1.1'
|
||||
|
||||
// If you want the components that go along with
|
||||
// Android's support libraries (currently just a PagerAdapter):
|
||||
compile 'com.bluelinelabs:conductor-support:2.0.5'
|
||||
compile 'com.bluelinelabs:conductor-support:2.1.1'
|
||||
|
||||
// If you want RxJava/RxAndroid lifecycle support:
|
||||
compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.5'
|
||||
// If you want RxJava lifecycle support:
|
||||
compile 'com.bluelinelabs:conductor-rxlifecycle:2.1.1'
|
||||
|
||||
// If you want RxJava2 lifecycle support:
|
||||
compile 'com.bluelinelabs:conductor-rxlifecycle2:2.1.1'
|
||||
```
|
||||
|
||||
SNAPSHOT:
|
||||
|
||||
```gradle
|
||||
compile 'com.bluelinelabs:conductor:2.0.6-SNAPSHOT'
|
||||
compile 'com.bluelinelabs:conductor-support:2.0.6-SNAPSHOT'
|
||||
compile 'com.bluelinelabs:conductor-rxlifecycle:2.0.6-SNAPSHOT'
|
||||
compile 'com.bluelinelabs:conductor:2.1.2-SNAPSHOT'
|
||||
compile 'com.bluelinelabs:conductor-support:2.1.2-SNAPSHOT'
|
||||
compile 'com.bluelinelabs:conductor-rxlifecycle:2.1.2-SNAPSHOT'
|
||||
compile 'com.bluelinelabs:conductor-rxlifecycle2:2.1.2-SNAPSHOT'
|
||||
```
|
||||
|
||||
You also have to add the url to the snapshot repository:
|
||||
|
||||
@@ -3,19 +3,15 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:2.2.3'
|
||||
classpath 'com.android.tools.build:gradle:2.3.0'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '2.10'
|
||||
}
|
||||
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
apply plugin: 'java'
|
||||
|
||||
targetCompatibility = JavaVersion.VERSION_1_7
|
||||
sourceCompatibility = JavaVersion.VERSION_1_7
|
||||
|
||||
configurations {
|
||||
lintChecks
|
||||
}
|
||||
@@ -8,6 +11,9 @@ dependencies {
|
||||
compile rootProject.ext.lintapi
|
||||
compile rootProject.ext.lintchecks
|
||||
|
||||
testCompile rootProject.ext.lint
|
||||
testCompile rootProject.ext.lintTests
|
||||
|
||||
lintChecks files(jar)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.bluelinelabs.conductor.lint;
|
||||
|
||||
import com.android.annotations.NonNull;
|
||||
import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
|
||||
import com.android.tools.lint.client.api.JavaEvaluator;
|
||||
import com.android.tools.lint.detector.api.Category;
|
||||
import com.android.tools.lint.detector.api.Detector;
|
||||
import com.android.tools.lint.detector.api.Implementation;
|
||||
@@ -9,21 +8,13 @@ import com.android.tools.lint.detector.api.Issue;
|
||||
import com.android.tools.lint.detector.api.JavaContext;
|
||||
import com.android.tools.lint.detector.api.Scope;
|
||||
import com.android.tools.lint.detector.api.Severity;
|
||||
import com.android.tools.lint.detector.api.Speed;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.ast.ClassDeclaration;
|
||||
import lombok.ast.ConstructorDeclaration;
|
||||
import lombok.ast.Node;
|
||||
import lombok.ast.NormalTypeBody;
|
||||
import lombok.ast.StrictListAccessor;
|
||||
import lombok.ast.TypeMember;
|
||||
import lombok.ast.VariableDefinition;
|
||||
|
||||
public final class ControllerChangeHandlerIssueDetector extends Detector implements Detector.JavaScanner, Detector.ClassScanner {
|
||||
public final class ControllerChangeHandlerIssueDetector extends Detector implements Detector.JavaPsiScanner {
|
||||
|
||||
public static final Issue ISSUE =
|
||||
Issue.create("ValidControllerChangeHandler", "ControllerChangeHandler not instantiatable",
|
||||
@@ -34,67 +25,45 @@ public final class ControllerChangeHandlerIssueDetector extends Detector impleme
|
||||
|
||||
public ControllerChangeHandlerIssueDetector() { }
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Speed getSpeed() {
|
||||
return Speed.FAST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> applicableSuperClasses() {
|
||||
return Collections.singletonList("com.bluelinelabs.conductor.ControllerChangeHandler");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClass(@NonNull JavaContext context, ClassDeclaration node,
|
||||
@NonNull Node declarationOrAnonymous, @NonNull ResolvedClass cls) {
|
||||
|
||||
if (node == null) {
|
||||
public void checkClass(JavaContext context, PsiClass declaration) {
|
||||
final JavaEvaluator evaluator = context.getEvaluator();
|
||||
if (evaluator.isAbstract(declaration)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int flags = node.astModifiers().getEffectiveModifierFlags();
|
||||
if ((flags & Modifier.ABSTRACT) != 0) {
|
||||
if (!evaluator.isPublic(declaration)) {
|
||||
String message = String.format("This ControllerChangeHandler class should be public (%1$s)", declaration.getQualifiedName());
|
||||
context.report(ISSUE, declaration, context.getLocation(declaration), message);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((flags & Modifier.PUBLIC) == 0) {
|
||||
String message = String.format("This ControllerChangeHandler class should be public (%1$s)", cls.getName());
|
||||
context.report(ISSUE, node, context.getLocation(node.astName()), message);
|
||||
if (declaration.getContainingClass() != null && !evaluator.isStatic(declaration)) {
|
||||
String message = String.format("This ControllerChangeHandler inner class should be static (%1$s)", declaration.getQualifiedName());
|
||||
context.report(ISSUE, declaration, context.getLocation(declaration), message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cls.getContainingClass() != null && (flags & Modifier.STATIC) == 0) {
|
||||
String message = String.format("This ControllerChangeHandler inner class should be static (%1$s)", cls.getName());
|
||||
context.report(ISSUE, node, context.getLocation(node.astName()), message);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean hasConstructor = false;
|
||||
boolean hasDefaultConstructor = false;
|
||||
NormalTypeBody body = node.astBody();
|
||||
if (body != null) {
|
||||
for (TypeMember member : body.astMembers()) {
|
||||
if (member instanceof ConstructorDeclaration) {
|
||||
hasConstructor = true;
|
||||
ConstructorDeclaration constructor = (ConstructorDeclaration)member;
|
||||
|
||||
if (constructor.astModifiers().isPublic()) {
|
||||
StrictListAccessor<VariableDefinition, ConstructorDeclaration> params = constructor.astParameters();
|
||||
if (params.isEmpty()) {
|
||||
hasDefaultConstructor = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
PsiMethod[] constructors = declaration.getConstructors();
|
||||
for (PsiMethod constructor : constructors) {
|
||||
if (evaluator.isPublic(constructor)) {
|
||||
if (constructor.getParameterList().getParametersCount() == 0) {
|
||||
hasDefaultConstructor = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasConstructor && !hasDefaultConstructor) {
|
||||
if (constructors.length > 0 && !hasDefaultConstructor) {
|
||||
String message = String.format(
|
||||
"This ControllerChangeHandler needs to have a public default constructor (`%1$s`)",
|
||||
cls.getName());
|
||||
context.report(ISSUE, node, context.getLocation(node.astName()), message);
|
||||
"This ControllerChangeHandler needs to have a public default constructor (`%1$s`)", declaration.getQualifiedName());
|
||||
context.report(ISSUE, declaration, context.getLocation(declaration), message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
package com.bluelinelabs.conductor.lint;
|
||||
|
||||
import com.android.SdkConstants;
|
||||
import com.android.annotations.NonNull;
|
||||
import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
|
||||
import com.android.tools.lint.client.api.JavaEvaluator;
|
||||
import com.android.tools.lint.detector.api.Category;
|
||||
import com.android.tools.lint.detector.api.Detector;
|
||||
import com.android.tools.lint.detector.api.Implementation;
|
||||
@@ -10,21 +9,14 @@ import com.android.tools.lint.detector.api.Issue;
|
||||
import com.android.tools.lint.detector.api.JavaContext;
|
||||
import com.android.tools.lint.detector.api.Scope;
|
||||
import com.android.tools.lint.detector.api.Severity;
|
||||
import com.android.tools.lint.detector.api.Speed;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiParameter;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.ast.ClassDeclaration;
|
||||
import lombok.ast.ConstructorDeclaration;
|
||||
import lombok.ast.Node;
|
||||
import lombok.ast.NormalTypeBody;
|
||||
import lombok.ast.StrictListAccessor;
|
||||
import lombok.ast.TypeMember;
|
||||
import lombok.ast.VariableDefinition;
|
||||
|
||||
public final class ControllerIssueDetector extends Detector implements Detector.JavaScanner, Detector.ClassScanner {
|
||||
public final class ControllerIssueDetector extends Detector implements Detector.JavaPsiScanner {
|
||||
|
||||
public static final Issue ISSUE =
|
||||
Issue.create("ValidController", "Controller not instantiatable",
|
||||
@@ -35,74 +27,56 @@ public final class ControllerIssueDetector extends Detector implements Detector.
|
||||
|
||||
public ControllerIssueDetector() { }
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Speed getSpeed() {
|
||||
return Speed.FAST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> applicableSuperClasses() {
|
||||
return Collections.singletonList("com.bluelinelabs.conductor.Controller");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClass(@NonNull JavaContext context, ClassDeclaration node,
|
||||
@NonNull Node declarationOrAnonymous, @NonNull ResolvedClass cls) {
|
||||
|
||||
if (node == null) {
|
||||
public void checkClass(JavaContext context, PsiClass declaration) {
|
||||
final JavaEvaluator evaluator = context.getEvaluator();
|
||||
if (evaluator.isAbstract(declaration)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int flags = node.astModifiers().getEffectiveModifierFlags();
|
||||
if ((flags & Modifier.ABSTRACT) != 0) {
|
||||
if (!evaluator.isPublic(declaration)) {
|
||||
String message = String.format("This Controller class should be public (%1$s)", declaration.getQualifiedName());
|
||||
context.report(ISSUE, declaration, context.getLocation(declaration), message);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((flags & Modifier.PUBLIC) == 0) {
|
||||
String message = String.format("This Controller class should be public (%1$s)", cls.getName());
|
||||
context.report(ISSUE, node, context.getLocation(node.astName()), message);
|
||||
if (declaration.getContainingClass() != null && !evaluator.isStatic(declaration)) {
|
||||
String message = String.format("This Controller inner class should be static (%1$s)", declaration.getQualifiedName());
|
||||
context.report(ISSUE, declaration, context.getLocation(declaration), message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cls.getContainingClass() != null && (flags & Modifier.STATIC) == 0) {
|
||||
String message = String.format("This Controller inner class should be static (%1$s)", cls.getName());
|
||||
context.report(ISSUE, node, context.getLocation(node.astName()), message);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean hasConstructor = false;
|
||||
boolean hasDefaultConstructor = false;
|
||||
boolean hasBundleConstructor = false;
|
||||
NormalTypeBody body = node.astBody();
|
||||
if (body != null) {
|
||||
for (TypeMember member : body.astMembers()) {
|
||||
if (member instanceof ConstructorDeclaration) {
|
||||
hasConstructor = true;
|
||||
ConstructorDeclaration constructor = (ConstructorDeclaration)member;
|
||||
PsiMethod[] constructors = declaration.getConstructors();
|
||||
for (PsiMethod constructor : constructors) {
|
||||
if (evaluator.isPublic(constructor)) {
|
||||
PsiParameter[] parameters = constructor.getParameterList().getParameters();
|
||||
|
||||
if (constructor.astModifiers().isPublic()) {
|
||||
StrictListAccessor<VariableDefinition, ConstructorDeclaration> params = constructor.astParameters();
|
||||
if (params.isEmpty()) {
|
||||
hasDefaultConstructor = true;
|
||||
break;
|
||||
} else if (params.size() == 1 &&
|
||||
(params.first().astTypeReference().getTypeName().equals(SdkConstants.CLASS_BUNDLE)) ||
|
||||
params.first().astTypeReference().getTypeName().equals("Bundle")) {
|
||||
hasBundleConstructor = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (parameters.length == 0) {
|
||||
hasDefaultConstructor = true;
|
||||
break;
|
||||
} else if (parameters.length == 1 &&
|
||||
parameters[0].getType().equalsToText(SdkConstants.CLASS_BUNDLE) ||
|
||||
parameters[0].getType().equalsToText("Bundle")) {
|
||||
hasBundleConstructor = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasConstructor && !hasDefaultConstructor && !hasBundleConstructor) {
|
||||
if (constructors.length > 0 && !hasDefaultConstructor && !hasBundleConstructor) {
|
||||
String message = String.format(
|
||||
"This Controller needs to have either a public default constructor or a" +
|
||||
" public single-argument constructor that takes a Bundle. (`%1$s`)",
|
||||
cls.getName());
|
||||
context.report(ISSUE, node, context.getLocation(node.astName()), message);
|
||||
declaration.getQualifiedName());
|
||||
context.report(ISSUE, declaration, context.getLocation(declaration), message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.bluelinelabs.conductor.lint;
|
||||
|
||||
import com.android.tools.lint.checks.infrastructure.LintDetectorTest;
|
||||
import com.android.tools.lint.detector.api.Detector;
|
||||
import com.android.tools.lint.detector.api.Issue;
|
||||
|
||||
import org.intellij.lang.annotations.Language;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
public class ControllerChangeHandlerDetectorTest extends LintDetectorTest {
|
||||
|
||||
private static final String NO_WARNINGS = "No warnings.";
|
||||
private static final String CONSTRUCTOR =
|
||||
"src/test/SampleHandler.java:2: Error: This ControllerChangeHandler needs to have a public default constructor (test.SampleHandler) [ValidControllerChangeHandler]\n"
|
||||
+ "public class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n"
|
||||
+ "^\n"
|
||||
+ "1 errors, 0 warnings\n";
|
||||
private static final String PRIVATE_CLASS_ERROR =
|
||||
"src/test/SampleHandler.java:2: Error: This ControllerChangeHandler class should be public (test.SampleHandler) [ValidControllerChangeHandler]\n"
|
||||
+ "private class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n"
|
||||
+ "^\n"
|
||||
+ "1 errors, 0 warnings\n";
|
||||
|
||||
public void testWithNoConstructor() throws Exception {
|
||||
@Language("JAVA") String source = ""
|
||||
+ "package test;\n"
|
||||
+ "public class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n"
|
||||
+ "}";
|
||||
assertThat(lintProject(java(source))).isEqualTo(NO_WARNINGS);
|
||||
}
|
||||
|
||||
public void testWithEmptyConstructor() throws Exception {
|
||||
@Language("JAVA") String source = ""
|
||||
+ "package test;\n"
|
||||
+ "public class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n"
|
||||
+ " public SampleHandler() { }\n"
|
||||
+ "}";
|
||||
assertThat(lintProject(java(source))).isEqualTo(NO_WARNINGS);
|
||||
}
|
||||
|
||||
public void testWithInvalidConstructor() throws Exception {
|
||||
@Language("JAVA") String source = ""
|
||||
+ "package test;\n"
|
||||
+ "public class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n"
|
||||
+ " public SampleHandler(int number) { }\n"
|
||||
+ "}";
|
||||
assertThat(lintProject(java(source))).isEqualTo(CONSTRUCTOR);
|
||||
}
|
||||
|
||||
public void testWithEmptyAndInvalidConstructor() throws Exception {
|
||||
@Language("JAVA") String source = ""
|
||||
+ "package test;\n"
|
||||
+ "public class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n"
|
||||
+ " public SampleHandler() { }\n"
|
||||
+ " public SampleHandler(int number) { }\n"
|
||||
+ "}";
|
||||
assertThat(lintProject(java(source))).isEqualTo(NO_WARNINGS);
|
||||
}
|
||||
|
||||
public void testWithPrivateConstructor() throws Exception {
|
||||
@Language("JAVA") String source = ""
|
||||
+ "package test;\n"
|
||||
+ "public class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n"
|
||||
+ " private SampleHandler() { }\n"
|
||||
+ "}";
|
||||
assertThat(lintProject(java(source))).isEqualTo(CONSTRUCTOR);
|
||||
}
|
||||
|
||||
public void testWithPrivateClass() throws Exception {
|
||||
@Language("JAVA") String source = ""
|
||||
+ "package test;\n"
|
||||
+ "private class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n"
|
||||
+ " public SampleHandler() { }\n"
|
||||
+ "}";
|
||||
assertThat(lintProject(java(source))).isEqualTo(PRIVATE_CLASS_ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Detector getDetector() {
|
||||
return new ControllerChangeHandlerIssueDetector();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Issue> getIssues() {
|
||||
return Collections.singletonList(ControllerChangeHandlerIssueDetector.ISSUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean allowCompilationErrors() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.bluelinelabs.conductor.lint;
|
||||
|
||||
import com.android.tools.lint.checks.infrastructure.LintDetectorTest;
|
||||
import com.android.tools.lint.detector.api.Detector;
|
||||
import com.android.tools.lint.detector.api.Issue;
|
||||
|
||||
import org.intellij.lang.annotations.Language;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
public class ControllerDetectorTest extends LintDetectorTest {
|
||||
|
||||
private static final String NO_WARNINGS = "No warnings.";
|
||||
private static final String CONSTRUCTOR_ERROR =
|
||||
"src/test/SampleController.java:2: Error: This Controller needs to have either a public default constructor or a public single-argument constructor that takes a Bundle. (test.SampleController) [ValidController]\n"
|
||||
+ "public class SampleController extends com.bluelinelabs.conductor.Controller {\n"
|
||||
+ "^\n"
|
||||
+ "1 errors, 0 warnings\n";
|
||||
private static final String CLASS_ERROR =
|
||||
"src/test/SampleController.java:2: Error: This Controller class should be public (test.SampleController) [ValidController]\n"
|
||||
+ "private class SampleController extends com.bluelinelabs.conductor.Controller {\n"
|
||||
+ "^\n"
|
||||
+ "1 errors, 0 warnings\n";
|
||||
|
||||
public void testWithNoConstructor() throws Exception {
|
||||
@Language("JAVA") String source = ""
|
||||
+ "package test;\n"
|
||||
+ "public class SampleController extends com.bluelinelabs.conductor.Controller {\n"
|
||||
+ "}";
|
||||
assertThat(lintProject(java(source))).isEqualTo(NO_WARNINGS);
|
||||
}
|
||||
|
||||
public void testWithEmptyConstructor() throws Exception {
|
||||
@Language("JAVA") String source = ""
|
||||
+ "package test;\n"
|
||||
+ "public class SampleController extends com.bluelinelabs.conductor.Controller {\n"
|
||||
+ " public SampleController() { }\n"
|
||||
+ "}";
|
||||
assertThat(lintProject(java(source))).isEqualTo(NO_WARNINGS);
|
||||
}
|
||||
|
||||
public void testWithInvalidConstructor() throws Exception {
|
||||
@Language("JAVA") String source = ""
|
||||
+ "package test;\n"
|
||||
+ "public class SampleController extends com.bluelinelabs.conductor.Controller {\n"
|
||||
+ " public SampleController(int number) { }\n"
|
||||
+ "}";
|
||||
assertThat(lintProject(java(source))).isEqualTo(CONSTRUCTOR_ERROR);
|
||||
}
|
||||
|
||||
public void testWithEmptyAndInvalidConstructor() throws Exception {
|
||||
@Language("JAVA") String source = ""
|
||||
+ "package test;\n"
|
||||
+ "public class SampleController extends com.bluelinelabs.conductor.Controller {\n"
|
||||
+ " public SampleController() { }\n"
|
||||
+ " public SampleController(int number) { }\n"
|
||||
+ "}";
|
||||
assertThat(lintProject(java(source))).isEqualTo(NO_WARNINGS);
|
||||
}
|
||||
|
||||
public void testWithPrivateConstructor() throws Exception {
|
||||
@Language("JAVA") String source = ""
|
||||
+ "package test;\n"
|
||||
+ "public class SampleController extends com.bluelinelabs.conductor.Controller {\n"
|
||||
+ " private SampleController() { }\n"
|
||||
+ "}";
|
||||
assertThat(lintProject(java(source))).isEqualTo(CONSTRUCTOR_ERROR);
|
||||
}
|
||||
|
||||
public void testWithPrivateClass() throws Exception {
|
||||
@Language("JAVA") String source = ""
|
||||
+ "package test;\n"
|
||||
+ "private class SampleController extends com.bluelinelabs.conductor.Controller {\n"
|
||||
+ " public SampleController() { }\n"
|
||||
+ "}";
|
||||
assertThat(lintProject(java(source))).isEqualTo(CLASS_ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Detector getDetector() {
|
||||
return new ControllerIssueDetector();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Issue> getIssues() {
|
||||
return Collections.singletonList(ControllerIssueDetector.ISSUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean allowCompilationErrors() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,6 @@ android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
@@ -26,7 +22,6 @@ android {
|
||||
|
||||
dependencies {
|
||||
compile rootProject.ext.rxJava
|
||||
compile rootProject.ext.rxAndroid
|
||||
compile rootProject.ext.rxLifecycle
|
||||
compile rootProject.ext.rxLifecycleAndroid
|
||||
|
||||
|
||||
@@ -7,10 +7,6 @@ android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
apply from: rootProject.file('gradle-mvn-push.gradle')
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'de.mobilej.unmock:UnMockPlugin:0.3.6'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'de.mobilej.unmock'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
@@ -25,9 +28,22 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testCompile rootProject.ext.junit
|
||||
testCompile rootProject.ext.roboelectric
|
||||
|
||||
compile rootProject.ext.supportAppCompat
|
||||
compile project(':conductor')
|
||||
}
|
||||
|
||||
unMock {
|
||||
downloadFrom 'https://oss.sonatype.org/content/groups/public/org/robolectric/android-all/4.3_r2-robolectric-0/android-all-4.3_r2-robolectric-0.jar'
|
||||
|
||||
keep "android.os.Bundle"
|
||||
keep "android.os.BaseBundle"
|
||||
}
|
||||
|
||||
ext.artifactId = 'conductor-support'
|
||||
|
||||
apply from: rootProject.file('dependencies.gradle')
|
||||
apply from: rootProject.file('gradle-mvn-push.gradle')
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.bluelinelabs.conductor.support;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.view.PagerAdapter;
|
||||
import android.util.SparseArray;
|
||||
@@ -13,8 +14,12 @@ import com.bluelinelabs.conductor.Router;
|
||||
import com.bluelinelabs.conductor.RouterTransaction;
|
||||
|
||||
/**
|
||||
* @deprecated Use RouterPagerAdapter instead! This implementation was too limited and had too many
|
||||
* gotchas associated with it.
|
||||
*
|
||||
* An adapter for ViewPagers that will handle adding and removing Controllers
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class ControllerPagerAdapter extends PagerAdapter {
|
||||
|
||||
private static final String KEY_SAVED_PAGES = "ControllerPagerAdapter.savedStates";
|
||||
@@ -30,7 +35,7 @@ public abstract class ControllerPagerAdapter extends PagerAdapter {
|
||||
/**
|
||||
* Creates a new ControllerPagerAdapter using the passed host.
|
||||
*/
|
||||
public ControllerPagerAdapter(Controller host, boolean saveControllerState) {
|
||||
public ControllerPagerAdapter(@NonNull Controller host, boolean saveControllerState) {
|
||||
this.host = host;
|
||||
savesState = saveControllerState;
|
||||
}
|
||||
@@ -38,6 +43,7 @@ public abstract class ControllerPagerAdapter extends PagerAdapter {
|
||||
/**
|
||||
* Return the Controller associated with a specified position.
|
||||
*/
|
||||
@NonNull
|
||||
public abstract Controller getItem(int position);
|
||||
|
||||
@Override
|
||||
@@ -53,12 +59,17 @@ public abstract class ControllerPagerAdapter extends PagerAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
final Controller controller;
|
||||
if (!router.hasRootController()) {
|
||||
Controller controller = getItem(position);
|
||||
controller = getItem(position);
|
||||
router.setRoot(RouterTransaction.with(controller).tag(name));
|
||||
visiblePageIds.put(position, controller.getInstanceId());
|
||||
} else {
|
||||
router.rebindIfNeeded();
|
||||
controller = router.getControllerWithTag(name);
|
||||
}
|
||||
|
||||
if (controller != null) {
|
||||
visiblePageIds.put(position, controller.getInstanceId());
|
||||
}
|
||||
|
||||
return router.getControllerWithTag(name);
|
||||
@@ -119,7 +130,8 @@ public abstract class ControllerPagerAdapter extends PagerAdapter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the already instantiated Controller in the specified position, if available.
|
||||
* Returns the already instantiated Controller in the specified position or {@code null} if
|
||||
* this position does not yet have a controller.
|
||||
*/
|
||||
@Nullable
|
||||
public Controller getController(int position) {
|
||||
@@ -139,4 +151,4 @@ public abstract class ControllerPagerAdapter extends PagerAdapter {
|
||||
return viewId + ":" + id;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.bluelinelabs.conductor.support;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.view.PagerAdapter;
|
||||
import android.util.SparseArray;
|
||||
@@ -12,6 +13,7 @@ import com.bluelinelabs.conductor.Controller;
|
||||
import com.bluelinelabs.conductor.Router;
|
||||
import com.bluelinelabs.conductor.RouterTransaction;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -20,15 +22,19 @@ import java.util.List;
|
||||
public abstract class RouterPagerAdapter extends PagerAdapter {
|
||||
|
||||
private static final String KEY_SAVED_PAGES = "RouterPagerAdapter.savedStates";
|
||||
private static final String KEY_MAX_PAGES_TO_STATE_SAVE = "RouterPagerAdapter.maxPagesToStateSave";
|
||||
private static final String KEY_SAVE_PAGE_HISTORY = "RouterPagerAdapter.savedPageHistory";
|
||||
|
||||
private final Controller host;
|
||||
private int maxPagesToStateSave = Integer.MAX_VALUE;
|
||||
private SparseArray<Bundle> savedPages = new SparseArray<>();
|
||||
private SparseArray<Router> visibleRouters = new SparseArray<>();
|
||||
private ArrayList<Integer> savedPageHistory = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Creates a new RouterPagerAdapter using the passed host.
|
||||
*/
|
||||
public RouterPagerAdapter(Controller host) {
|
||||
public RouterPagerAdapter(@NonNull Controller host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
@@ -38,7 +44,21 @@ public abstract class RouterPagerAdapter extends PagerAdapter {
|
||||
* @param router The router used for the page
|
||||
* @param position The page position to be instantiated.
|
||||
*/
|
||||
public abstract void configureRouter(Router router, int position);
|
||||
public abstract void configureRouter(@NonNull Router router, int position);
|
||||
|
||||
/**
|
||||
* Sets the maximum number of pages that will have their states saved. When this number is exceeded,
|
||||
* the page that was state saved least recently will have its state removed from the save data.
|
||||
*/
|
||||
public void setMaxPagesToStateSave(int maxPagesToStateSave) {
|
||||
if (maxPagesToStateSave < 0) {
|
||||
throw new IllegalArgumentException("Only positive integers may be passed for maxPagesToStateSave.");
|
||||
}
|
||||
|
||||
this.maxPagesToStateSave = maxPagesToStateSave;
|
||||
|
||||
ensurePagesSaved();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object instantiateItem(ViewGroup container, int position) {
|
||||
@@ -50,6 +70,7 @@ public abstract class RouterPagerAdapter extends PagerAdapter {
|
||||
|
||||
if (routerSavedState != null) {
|
||||
router.restoreInstanceState(routerSavedState);
|
||||
savedPages.remove(position);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +89,11 @@ public abstract class RouterPagerAdapter extends PagerAdapter {
|
||||
router.saveInstanceState(savedState);
|
||||
savedPages.put(position, savedState);
|
||||
|
||||
savedPageHistory.remove((Integer)position);
|
||||
savedPageHistory.add(position);
|
||||
|
||||
ensurePagesSaved();
|
||||
|
||||
host.removeChildRouter(router);
|
||||
|
||||
visibleRouters.remove(position);
|
||||
@@ -89,6 +115,8 @@ public abstract class RouterPagerAdapter extends PagerAdapter {
|
||||
public Parcelable saveState() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putSparseParcelableArray(KEY_SAVED_PAGES, savedPages);
|
||||
bundle.putInt(KEY_MAX_PAGES_TO_STATE_SAVE, maxPagesToStateSave);
|
||||
bundle.putIntegerArrayList(KEY_SAVE_PAGE_HISTORY, savedPageHistory);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@@ -97,11 +125,14 @@ public abstract class RouterPagerAdapter extends PagerAdapter {
|
||||
Bundle bundle = (Bundle)state;
|
||||
if (state != null) {
|
||||
savedPages = bundle.getSparseParcelableArray(KEY_SAVED_PAGES);
|
||||
maxPagesToStateSave = bundle.getInt(KEY_MAX_PAGES_TO_STATE_SAVE);
|
||||
savedPageHistory = bundle.getIntegerArrayList(KEY_SAVE_PAGE_HISTORY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the already instantiated Router in the specified position, if available.
|
||||
* Returns the already instantiated Router in the specified position or {@code null} if there
|
||||
* is no router associated with this position.
|
||||
*/
|
||||
@Nullable
|
||||
public Router getRouter(int position) {
|
||||
@@ -112,6 +143,17 @@ public abstract class RouterPagerAdapter extends PagerAdapter {
|
||||
return position;
|
||||
}
|
||||
|
||||
SparseArray<Bundle> getSavedPages() {
|
||||
return savedPages;
|
||||
}
|
||||
|
||||
private void ensurePagesSaved() {
|
||||
while (savedPages.size() > maxPagesToStateSave) {
|
||||
int positionToRemove = savedPageHistory.remove(0);
|
||||
savedPages.remove(positionToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
private static String makeRouterName(int viewId, long id) {
|
||||
return viewId + ":" + id;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.bluelinelabs.conductor.support;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.SparseArray;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.bluelinelabs.conductor.Conductor;
|
||||
import com.bluelinelabs.conductor.Router;
|
||||
import com.bluelinelabs.conductor.RouterTransaction;
|
||||
import com.bluelinelabs.conductor.support.util.FakePager;
|
||||
import com.bluelinelabs.conductor.support.util.TestController;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.util.ActivityController;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class StateSaveTests {
|
||||
|
||||
private FakePager pager;
|
||||
private RouterPagerAdapter pagerAdapter;
|
||||
|
||||
public void createActivityController(Bundle savedInstanceState) {
|
||||
ActivityController<Activity> activityController = Robolectric.buildActivity(Activity.class).create().start().resume();
|
||||
Router router = Conductor.attachRouter(activityController.get(), new FrameLayout(activityController.get()), savedInstanceState);
|
||||
TestController controller = new TestController();
|
||||
router.setRoot(RouterTransaction.with(controller));
|
||||
|
||||
pager = new FakePager(new FrameLayout(activityController.get()));
|
||||
pager.setOffscreenPageLimit(1);
|
||||
|
||||
pagerAdapter = new RouterPagerAdapter(controller) {
|
||||
@Override
|
||||
public void configureRouter(@NonNull Router router, int position) {
|
||||
if (!router.hasRootController()) {
|
||||
router.setRoot(RouterTransaction.with(new TestController()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return 20;
|
||||
}
|
||||
};
|
||||
|
||||
pager.setAdapter(pagerAdapter);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
createActivityController(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoMaxSaves() {
|
||||
// Load all pages
|
||||
for (int i = 0; i < pagerAdapter.getCount(); i++) {
|
||||
pager.pageTo(i);
|
||||
}
|
||||
|
||||
pager.pageTo(pagerAdapter.getCount() / 2);
|
||||
|
||||
// Ensure all non-visible pages are saved
|
||||
assertEquals(pagerAdapter.getCount() - 1 - pager.getOffscreenPageLimit() * 2, pagerAdapter.getSavedPages().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaxSavedSet() {
|
||||
final int maxPages = 3;
|
||||
pagerAdapter.setMaxPagesToStateSave(maxPages);
|
||||
|
||||
// Load all pages
|
||||
for (int i = 0; i < pagerAdapter.getCount(); i++) {
|
||||
pager.pageTo(i);
|
||||
}
|
||||
|
||||
final int firstSelectedItem = pagerAdapter.getCount() / 2;
|
||||
pager.pageTo(firstSelectedItem);
|
||||
|
||||
SparseArray<Bundle> savedPages = pagerAdapter.getSavedPages();
|
||||
|
||||
// Ensure correct number of pages are saved
|
||||
assertEquals(maxPages, savedPages.size());
|
||||
|
||||
// Ensure correct pages are saved
|
||||
assertEquals(pagerAdapter.getCount() - 3, savedPages.keyAt(0));
|
||||
assertEquals(pagerAdapter.getCount() - 2, savedPages.keyAt(1));
|
||||
assertEquals(pagerAdapter.getCount() - 1, savedPages.keyAt(2));
|
||||
|
||||
final int secondSelectedItem = 1;
|
||||
pager.pageTo(secondSelectedItem);
|
||||
|
||||
savedPages = pagerAdapter.getSavedPages();
|
||||
|
||||
// Ensure correct number of pages are saved
|
||||
assertEquals(maxPages, savedPages.size());
|
||||
|
||||
// Ensure correct pages are saved
|
||||
assertEquals(firstSelectedItem - 1, savedPages.keyAt(0));
|
||||
assertEquals(firstSelectedItem, savedPages.keyAt(1));
|
||||
assertEquals(firstSelectedItem + 1, savedPages.keyAt(2));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.bluelinelabs.conductor.support.util;
|
||||
|
||||
import android.util.SparseArray;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.support.RouterPagerAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class FakePager {
|
||||
|
||||
private ViewGroup container;
|
||||
private int offscreenPageLimit;
|
||||
private final SparseArray<Object> pages = new SparseArray<>();
|
||||
|
||||
private RouterPagerAdapter adapter;
|
||||
|
||||
public FakePager(ViewGroup container) {
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
public void setAdapter(RouterPagerAdapter adapter) {
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
public void pageTo(int page) {
|
||||
int firstPage = Math.max(0, page - offscreenPageLimit);
|
||||
int lastPage = Math.min(adapter.getCount() - 1, page + offscreenPageLimit);
|
||||
|
||||
List<Integer> pagesI = new ArrayList<>();
|
||||
for (int i = 0; i < pages.size(); i++) {
|
||||
pagesI.add(pages.keyAt(i));
|
||||
}
|
||||
|
||||
for (int i = pages.size() - 1; i >= 0; i--) {
|
||||
int key = pages.keyAt(i);
|
||||
|
||||
if (key < firstPage || key > lastPage) {
|
||||
adapter.destroyItem(container, key, pages.get(key));
|
||||
pages.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
for (int key = firstPage; key <= lastPage; key++) {
|
||||
if (pages.get(key) == null) {
|
||||
pages.put(key, adapter.instantiateItem(container, key));
|
||||
}
|
||||
}
|
||||
|
||||
adapter.setPrimaryItem(container, page, pages.get(page));
|
||||
}
|
||||
|
||||
public int getOffscreenPageLimit() {
|
||||
return offscreenPageLimit;
|
||||
}
|
||||
|
||||
public void setOffscreenPageLimit(int offscreenPageLimit) {
|
||||
this.offscreenPageLimit = offscreenPageLimit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.bluelinelabs.conductor.support.util;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
|
||||
public class TestController extends Controller {
|
||||
|
||||
@NonNull @Override
|
||||
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
|
||||
return new FrameLayout(inflater.getContext());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,10 +14,6 @@ android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.bluelinelabs.conductor;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.IntentSender.SendIntentException;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
@@ -9,12 +11,14 @@ import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeListener;
|
||||
import com.bluelinelabs.conductor.internal.LifecycleHandler;
|
||||
import com.bluelinelabs.conductor.internal.TransactionIndexer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ActivityHostedRouter extends Router {
|
||||
|
||||
private LifecycleHandler lifecycleHandler;
|
||||
private final TransactionIndexer transactionIndexer = new TransactionIndexer();
|
||||
|
||||
public final void setHost(@NonNull LifecycleHandler lifecycleHandler, @NonNull ViewGroup container) {
|
||||
if (this.lifecycleHandler != lifecycleHandler || this.container != container) {
|
||||
@@ -31,6 +35,20 @@ public class ActivityHostedRouter extends Router {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveInstanceState(@NonNull Bundle outState) {
|
||||
super.saveInstanceState(outState);
|
||||
|
||||
transactionIndexer.saveInstanceState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||
super.restoreInstanceState(savedInstanceState);
|
||||
|
||||
transactionIndexer.restoreInstanceState(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override @Nullable
|
||||
public Activity getActivity() {
|
||||
return lifecycleHandler != null ? lifecycleHandler.getLifecycleActivity() : null;
|
||||
@@ -69,6 +87,12 @@ public class ActivityHostedRouter extends Router {
|
||||
lifecycleHandler.startActivityForResult(instanceId, intent, requestCode, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
void startIntentSenderForResult(@NonNull String instanceId, @NonNull IntentSender intent, int requestCode, @Nullable Intent fillInIntent,
|
||||
int flagsMask, int flagsValues, int extraFlags, @Nullable Bundle options) throws SendIntentException {
|
||||
lifecycleHandler.startIntentSenderForResult(instanceId, intent, requestCode, fillInIntent, flagsMask, flagsValues, extraFlags, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerForActivityResult(@NonNull String instanceId, int requestCode) {
|
||||
lifecycleHandler.registerForActivityResult(instanceId, requestCode);
|
||||
@@ -98,4 +122,9 @@ public class ActivityHostedRouter extends Router {
|
||||
Router getRootRouter() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override @Nullable
|
||||
TransactionIndexer getTransactionIndexer() {
|
||||
return transactionIndexer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,16 +18,16 @@ class Backstack implements Iterable<RouterTransaction> {
|
||||
private final Deque<RouterTransaction> backstack = new ArrayDeque<>();
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public boolean isEmpty() {
|
||||
boolean isEmpty() {
|
||||
return backstack.isEmpty();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
int size() {
|
||||
return backstack.size();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public RouterTransaction root() {
|
||||
RouterTransaction root() {
|
||||
return backstack.size() > 0 ? backstack.getLast() : null;
|
||||
}
|
||||
|
||||
@@ -37,12 +37,12 @@ class Backstack implements Iterable<RouterTransaction> {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Iterator<RouterTransaction> reverseIterator() {
|
||||
Iterator<RouterTransaction> reverseIterator() {
|
||||
return backstack.descendingIterator();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<RouterTransaction> popTo(@NonNull RouterTransaction transaction) {
|
||||
List<RouterTransaction> popTo(@NonNull RouterTransaction transaction) {
|
||||
List<RouterTransaction> popped = new ArrayList<>();
|
||||
if (backstack.contains(transaction)) {
|
||||
while (backstack.peek() != transaction) {
|
||||
@@ -56,27 +56,27 @@ class Backstack implements Iterable<RouterTransaction> {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public RouterTransaction pop() {
|
||||
RouterTransaction pop() {
|
||||
RouterTransaction popped = backstack.pop();
|
||||
popped.controller.destroy();
|
||||
return popped;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public RouterTransaction peek() {
|
||||
RouterTransaction peek() {
|
||||
return backstack.peek();
|
||||
}
|
||||
|
||||
public void remove(@NonNull RouterTransaction transaction) {
|
||||
void remove(@NonNull RouterTransaction transaction) {
|
||||
backstack.removeFirstOccurrence(transaction);
|
||||
}
|
||||
|
||||
public void push(@NonNull RouterTransaction transaction) {
|
||||
void push(@NonNull RouterTransaction transaction) {
|
||||
backstack.push(transaction);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<RouterTransaction> popAll() {
|
||||
List<RouterTransaction> popAll() {
|
||||
List<RouterTransaction> list = new ArrayList<>();
|
||||
while (!isEmpty()) {
|
||||
list.add(pop());
|
||||
@@ -84,7 +84,7 @@ class Backstack implements Iterable<RouterTransaction> {
|
||||
return list;
|
||||
}
|
||||
|
||||
public void setBackstack(@NonNull List<RouterTransaction> backstack) {
|
||||
void setBackstack(@NonNull List<RouterTransaction> backstack) {
|
||||
for (RouterTransaction existingTransaction : this.backstack) {
|
||||
boolean contains = false;
|
||||
for (RouterTransaction newTransaction : backstack) {
|
||||
@@ -105,7 +105,11 @@ class Backstack implements Iterable<RouterTransaction> {
|
||||
}
|
||||
}
|
||||
|
||||
public void saveInstanceState(@NonNull Bundle outState) {
|
||||
boolean contains(@NonNull RouterTransaction transaction) {
|
||||
return backstack.contains(transaction);
|
||||
}
|
||||
|
||||
void saveInstanceState(@NonNull Bundle outState) {
|
||||
ArrayList<Bundle> entryBundles = new ArrayList<>(backstack.size());
|
||||
for (RouterTransaction entry : backstack) {
|
||||
entryBundles.add(entry.saveInstanceState());
|
||||
@@ -114,7 +118,7 @@ class Backstack implements Iterable<RouterTransaction> {
|
||||
outState.putParcelableArrayList(KEY_ENTRIES, entryBundles);
|
||||
}
|
||||
|
||||
public void restoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||
void restoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||
ArrayList<Bundle> entryBundles = savedInstanceState.getParcelableArrayList(KEY_ENTRIES);
|
||||
if (entryBundles != null) {
|
||||
Collections.reverse(entryBundles);
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@@ -18,18 +19,19 @@ import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnAttachStateChangeListener;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.Router.OnControllerPushedListener;
|
||||
import com.bluelinelabs.conductor.internal.ClassUtils;
|
||||
import com.bluelinelabs.conductor.internal.RouterRequiringFunc;
|
||||
import com.bluelinelabs.conductor.internal.ViewAttachHandler;
|
||||
import com.bluelinelabs.conductor.internal.ViewAttachHandler.ViewAttachListener;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -44,7 +46,6 @@ public abstract class Controller {
|
||||
private static final String KEY_CLASS_NAME = "Controller.className";
|
||||
private static final String KEY_VIEW_STATE = "Controller.viewState";
|
||||
private static final String KEY_CHILD_ROUTERS = "Controller.childRouters";
|
||||
private static final String KEY_CHILD_BACKSTACK = "Controller.childBackstack";
|
||||
private static final String KEY_SAVED_STATE = "Controller.savedState";
|
||||
private static final String KEY_INSTANCE_ID = "Controller.instanceId";
|
||||
private static final String KEY_TARGET_INSTANCE_ID = "Controller.target.instanceId";
|
||||
@@ -80,21 +81,13 @@ public abstract class Controller {
|
||||
private ControllerChangeHandler overriddenPushHandler;
|
||||
private ControllerChangeHandler overriddenPopHandler;
|
||||
private RetainViewMode retainViewMode = RetainViewMode.RELEASE_DETACH;
|
||||
private OnAttachStateChangeListener onAttachStateChangeListener;
|
||||
private ViewAttachHandler viewAttachHandler;
|
||||
private final List<ControllerHostedRouter> childRouters = new ArrayList<>();
|
||||
private final List<LifecycleListener> lifecycleListeners = new ArrayList<>();
|
||||
private final ArrayList<String> requestedPermissions = new ArrayList<>();
|
||||
private final ArrayList<RouterRequiringFunc> onRouterSetListeners = new ArrayList<>();
|
||||
private final List<Controller> childBackstack = new LinkedList<>();
|
||||
private WeakReference<View> destroyedView;
|
||||
|
||||
private final OnControllerPushedListener onControllerPushedListener = new OnControllerPushedListener() {
|
||||
@Override
|
||||
public void onControllerPushed(Controller controller) {
|
||||
onChildControllerPushed(controller);
|
||||
}
|
||||
};
|
||||
|
||||
@NonNull
|
||||
static Controller newInstance(@NonNull Bundle bundle) {
|
||||
final String className = bundle.getString(KEY_CLASS_NAME);
|
||||
@@ -182,7 +175,7 @@ public abstract class Controller {
|
||||
* the same container unless you have a great reason to do so (ex: ViewPagers).
|
||||
*
|
||||
* @param container The ViewGroup that hosts the child Router
|
||||
* @param tag The router's tag
|
||||
* @param tag The router's tag or {@code null} if none is needed
|
||||
*/
|
||||
@NonNull
|
||||
public final Router getChildRouter(@NonNull ViewGroup container, @Nullable String tag) {
|
||||
@@ -194,10 +187,12 @@ public abstract class Controller {
|
||||
* Retrieves the child {@link Router} for the given container/tag combination. Note that multiple
|
||||
* routers should not exist in the same container unless a lot of care is taken to maintain order
|
||||
* between them. Avoid using the same container unless you have a great reason to do so (ex: ViewPagers).
|
||||
* The only time this method will return {@code null} is when the child router does not exist prior
|
||||
* to calling this method and the createIfNeeded parameter is set to false.
|
||||
*
|
||||
* @param container The ViewGroup that hosts the child Router
|
||||
* @param tag The router's tag
|
||||
* @param createIfNeeded If true, a router will be created if one does not yet exist. Else false will be returned in this case.
|
||||
* @param tag The router's tag or {@code null} if none is needed
|
||||
* @param createIfNeeded If true, a router will be created if one does not yet exist. Else {@code null} will be returned in this case.
|
||||
*/
|
||||
@Nullable
|
||||
public final Router getChildRouter(@NonNull ViewGroup container, @Nullable String tag, boolean createIfNeeded) {
|
||||
@@ -214,22 +209,26 @@ public abstract class Controller {
|
||||
if (childRouter == null) {
|
||||
if (createIfNeeded) {
|
||||
childRouter = new ControllerHostedRouter(container.getId(), tag);
|
||||
monitorChildRouter(childRouter);
|
||||
childRouter.setHost(this, container);
|
||||
childRouters.add(childRouter);
|
||||
}
|
||||
} else if (!childRouter.hasHost()) {
|
||||
childRouter.setHost(this, container);
|
||||
monitorChildRouter(childRouter);
|
||||
childRouter.rebindIfNeeded();
|
||||
}
|
||||
|
||||
return childRouter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a child {@link Router} from this Controller. When removed, all Controllers currently managed by
|
||||
* the {@link Router} will be destroyed.
|
||||
*
|
||||
* @param childRouter The router to be removed
|
||||
*/
|
||||
public final void removeChildRouter(@NonNull Router childRouter) {
|
||||
if ((childRouter instanceof ControllerHostedRouter) && childRouters.remove(childRouter)) {
|
||||
childRouter.destroy();
|
||||
childRouter.destroy(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +254,8 @@ public abstract class Controller {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this Controller's View, if available.
|
||||
* Return this Controller's View or {@code null} if it has not yet been created or has been
|
||||
* destroyed.
|
||||
*/
|
||||
@Nullable
|
||||
public final View getView() {
|
||||
@@ -263,15 +263,17 @@ public abstract class Controller {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the host Activity of this Controller's {@link Router}
|
||||
* Returns the host Activity of this Controller's {@link Router} or {@code null} if this
|
||||
* Controller has not yet been attached to an Activity or if the Activity has been destroyed.
|
||||
*/
|
||||
@Nullable
|
||||
public final Activity getActivity() {
|
||||
return router.getActivity();
|
||||
return router != null ? router.getActivity() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Resources from the host Activity
|
||||
* Returns the Resources from the host Activity or {@code null} if this Controller has not
|
||||
* yet been attached to an Activity or if the Activity has been destroyed.
|
||||
*/
|
||||
@Nullable
|
||||
public final Resources getResources() {
|
||||
@@ -280,7 +282,8 @@ public abstract class Controller {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Application Context derived from the host Activity
|
||||
* Returns the Application Context derived from the host Activity or {@code null} if this Controller
|
||||
* has not yet been attached to an Activity or if the Activity has been destroyed.
|
||||
*/
|
||||
@Nullable
|
||||
public final Context getApplicationContext() {
|
||||
@@ -289,7 +292,8 @@ public abstract class Controller {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this Controller's parent Controller if it is a child Controller.
|
||||
* Returns this Controller's parent Controller if it is a child Controller or {@code null} if
|
||||
* it has no parent.
|
||||
*/
|
||||
@Nullable
|
||||
public final Controller getParentController() {
|
||||
@@ -306,10 +310,10 @@ public abstract class Controller {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Controller with the given instance id, if available.
|
||||
* May return the controller itself or a matching descendant
|
||||
* Returns the Controller with the given instance id or {@code null} if no such Controller
|
||||
* exists. May return the Controller itself or a matching descendant
|
||||
*
|
||||
* @param instanceId The instance ID being searched for
|
||||
* @return The matching Controller, if one exists
|
||||
*/
|
||||
@Nullable
|
||||
final Controller findController(@NonNull String instanceId) {
|
||||
@@ -354,7 +358,8 @@ public abstract class Controller {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the target Controller that was set with the {@link #setTargetController(Controller)} method
|
||||
* Returns the target Controller that was set with the {@link #setTargetController(Controller)}
|
||||
* method or {@code null} if this Controller has no target.
|
||||
*
|
||||
* @return This Controller's target
|
||||
*/
|
||||
@@ -490,6 +495,14 @@ public abstract class Controller {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls startIntentSenderForResult(IntentSender, int, Intent, int, int, int, Bundle) from this Controller's host Activity.
|
||||
*/
|
||||
public final void startIntentSenderForResult(@NonNull final IntentSender intent, final int requestCode, @Nullable final Intent fillInIntent, final int flagsMask,
|
||||
final int flagsValues, final int extraFlags, @Nullable final Bundle options) throws IntentSender.SendIntentException {
|
||||
router.startIntentSenderForResult(instanceId, intent, requestCode, fillInIntent, flagsMask, flagsValues, extraFlags, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this Controller to handle onActivityResult responses. Calling this method is NOT
|
||||
* necessary when calling {@link #startActivityForResult(Intent, int)}
|
||||
@@ -551,8 +564,22 @@ public abstract class Controller {
|
||||
* @return True if this Controller has consumed the back button press, otherwise false
|
||||
*/
|
||||
public boolean handleBack() {
|
||||
for (int i = childBackstack.size() - 1; i >= 0; i--) {
|
||||
Controller childController = childBackstack.get(i);
|
||||
List<RouterTransaction> childTransactions = new ArrayList<>();
|
||||
|
||||
for (ControllerHostedRouter childRouter : childRouters) {
|
||||
childTransactions.addAll(childRouter.getBackstack());
|
||||
}
|
||||
|
||||
Collections.sort(childTransactions, new Comparator<RouterTransaction>() {
|
||||
@Override
|
||||
public int compare(RouterTransaction o1, RouterTransaction o2) {
|
||||
return o2.transactionIndex - o1.transactionIndex;
|
||||
}
|
||||
});
|
||||
|
||||
for (RouterTransaction transaction : childTransactions) {
|
||||
Controller childController = transaction.controller;
|
||||
|
||||
if (childController.isAttached() && childController.getRouter().handleBack()) {
|
||||
return true;
|
||||
}
|
||||
@@ -743,6 +770,10 @@ public abstract class Controller {
|
||||
}
|
||||
|
||||
final void activityStarted(@NonNull Activity activity) {
|
||||
if (viewAttachHandler != null) {
|
||||
viewAttachHandler.onActivityStarted();
|
||||
}
|
||||
|
||||
onActivityStarted(activity);
|
||||
}
|
||||
|
||||
@@ -751,6 +782,7 @@ public abstract class Controller {
|
||||
attach(view);
|
||||
} else if (attached) {
|
||||
needsAttach = false;
|
||||
hasSavedViewState = false;
|
||||
}
|
||||
|
||||
onActivityResumed(activity);
|
||||
@@ -761,12 +793,15 @@ public abstract class Controller {
|
||||
}
|
||||
|
||||
final void activityStopped(@NonNull Activity activity) {
|
||||
if (viewAttachHandler != null) {
|
||||
viewAttachHandler.onActivityStopped();
|
||||
}
|
||||
onActivityStopped(activity);
|
||||
}
|
||||
|
||||
final void activityDestroyed(boolean isChangingConfigurations) {
|
||||
if (isChangingConfigurations) {
|
||||
detach(view, true);
|
||||
detach(view, true, false);
|
||||
} else {
|
||||
destroy(true);
|
||||
}
|
||||
@@ -800,14 +835,14 @@ public abstract class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
void detach(@NonNull View view, boolean forceViewRefRemoval) {
|
||||
void detach(@NonNull View view, boolean forceViewRefRemoval, boolean blockViewRefRemoval) {
|
||||
if (!attachedToUnownedParent) {
|
||||
for (ControllerHostedRouter router : childRouters) {
|
||||
router.prepareForHostDetach();
|
||||
}
|
||||
}
|
||||
|
||||
final boolean removeViewRef = forceViewRefRemoval || retainViewMode == RetainViewMode.RELEASE_DETACH || isBeingDestroyed;
|
||||
final boolean removeViewRef = !blockViewRefRemoval && (forceViewRefRemoval || retainViewMode == RetainViewMode.RELEASE_DETACH || isBeingDestroyed);
|
||||
|
||||
if (attached) {
|
||||
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
|
||||
@@ -846,7 +881,8 @@ public abstract class Controller {
|
||||
|
||||
onDestroyView(view);
|
||||
|
||||
view.removeOnAttachStateChangeListener(onAttachStateChangeListener);
|
||||
viewAttachHandler.unregisterAttachListener(view);
|
||||
viewAttachHandler = null;
|
||||
viewIsAttached = false;
|
||||
|
||||
if (isBeingDestroyed) {
|
||||
@@ -871,7 +907,7 @@ public abstract class Controller {
|
||||
|
||||
final View inflate(@NonNull ViewGroup parent) {
|
||||
if (view != null && view.getParent() != null && view.getParent() != parent) {
|
||||
detach(view, true);
|
||||
detach(view, true, false);
|
||||
removeViewReference();
|
||||
}
|
||||
|
||||
@@ -893,28 +929,32 @@ public abstract class Controller {
|
||||
|
||||
restoreViewState(view);
|
||||
|
||||
onAttachStateChangeListener = new OnAttachStateChangeListener() {
|
||||
viewAttachHandler = new ViewAttachHandler(new ViewAttachListener() {
|
||||
@Override
|
||||
public void onViewAttachedToWindow(View v) {
|
||||
if (v == view) {
|
||||
viewIsAttached = true;
|
||||
viewWasDetached = false;
|
||||
}
|
||||
attach(v);
|
||||
public void onAttached() {
|
||||
viewIsAttached = true;
|
||||
viewWasDetached = false;
|
||||
attach(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(View v) {
|
||||
public void onDetached(boolean fromActivityStop) {
|
||||
viewIsAttached = false;
|
||||
viewWasDetached = true;
|
||||
|
||||
if (!isDetachFrozen) {
|
||||
detach(v, false);
|
||||
detach(view, false, fromActivityStop);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
view.addOnAttachStateChangeListener(onAttachStateChangeListener);
|
||||
@Override
|
||||
public void onViewDetachAfterStop() {
|
||||
if (!isDetachFrozen) {
|
||||
detach(view, false, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
viewAttachHandler.listenForAttach(view);
|
||||
} else if (retainViewMode == RetainViewMode.RETAIN_DETACH) {
|
||||
restoreChildControllerHosts();
|
||||
}
|
||||
@@ -929,7 +969,6 @@ public abstract class Controller {
|
||||
|
||||
if (containerView != null && containerView instanceof ViewGroup) {
|
||||
childRouter.setHost(this, (ViewGroup)containerView);
|
||||
monitorChildRouter(childRouter);
|
||||
childRouter.rebindIfNeeded();
|
||||
}
|
||||
}
|
||||
@@ -968,20 +1007,20 @@ public abstract class Controller {
|
||||
}
|
||||
|
||||
for (ControllerHostedRouter childRouter : childRouters) {
|
||||
childRouter.destroy();
|
||||
childRouter.destroy(false);
|
||||
}
|
||||
|
||||
if (!attached) {
|
||||
removeViewReference();
|
||||
} else if (removeViews) {
|
||||
detach(view, true);
|
||||
detach(view, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveViewState(@NonNull View view) {
|
||||
hasSavedViewState = true;
|
||||
|
||||
viewState = new Bundle();
|
||||
viewState = new Bundle(getClass().getClassLoader());
|
||||
|
||||
SparseArray<Parcelable> hierarchyState = new SparseArray<>();
|
||||
view.saveHierarchyState(hierarchyState);
|
||||
@@ -1041,12 +1080,6 @@ public abstract class Controller {
|
||||
}
|
||||
outState.putParcelableArrayList(KEY_CHILD_ROUTERS, childBundles);
|
||||
|
||||
ArrayList<String> childBackstack = new ArrayList<>();
|
||||
for (Controller controller : this.childBackstack) {
|
||||
childBackstack.add(controller.instanceId);
|
||||
}
|
||||
outState.putStringArrayList(KEY_CHILD_BACKSTACK, childBackstack);
|
||||
|
||||
Bundle savedState = new Bundle();
|
||||
onSaveInstanceState(savedState);
|
||||
|
||||
@@ -1062,6 +1095,10 @@ public abstract class Controller {
|
||||
|
||||
private void restoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||
viewState = savedInstanceState.getBundle(KEY_VIEW_STATE);
|
||||
if (viewState != null) {
|
||||
viewState.setClassLoader(getClass().getClassLoader());
|
||||
}
|
||||
|
||||
instanceId = savedInstanceState.getString(KEY_INSTANCE_ID);
|
||||
targetInstanceId = savedInstanceState.getString(KEY_TARGET_INSTANCE_ID);
|
||||
requestedPermissions.addAll(savedInstanceState.getStringArrayList(KEY_REQUESTED_PERMISSIONS));
|
||||
@@ -1074,18 +1111,9 @@ public abstract class Controller {
|
||||
for (Bundle childBundle : childBundles) {
|
||||
ControllerHostedRouter childRouter = new ControllerHostedRouter();
|
||||
childRouter.restoreInstanceState(childBundle);
|
||||
monitorChildRouter(childRouter);
|
||||
childRouters.add(childRouter);
|
||||
}
|
||||
|
||||
List<String> childBackstackIds = savedInstanceState.getStringArrayList(KEY_CHILD_BACKSTACK);
|
||||
for (String controllerId : childBackstackIds) {
|
||||
Controller childController = findController(controllerId);
|
||||
if (childController != null) {
|
||||
childBackstack.add(childController);
|
||||
}
|
||||
}
|
||||
|
||||
this.savedInstanceState = savedInstanceState.getBundle(KEY_SAVED_STATE);
|
||||
performOnRestoreInstanceState();
|
||||
}
|
||||
@@ -1150,7 +1178,7 @@ public abstract class Controller {
|
||||
}
|
||||
|
||||
if (!frozen && view != null && viewWasDetached) {
|
||||
detach(view, false);
|
||||
detach(view, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1171,22 +1199,6 @@ public abstract class Controller {
|
||||
return attached && hasOptionsMenu && !optionsMenuHidden && onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void monitorChildRouter(@NonNull ControllerHostedRouter childRouter) {
|
||||
childRouter.setOnControllerPushedListener(onControllerPushedListener);
|
||||
}
|
||||
|
||||
private void onChildControllerPushed(@NonNull Controller controller) {
|
||||
if (!childBackstack.contains(controller)) {
|
||||
childBackstack.add(controller);
|
||||
controller.addLifecycleListener(new LifecycleListener() {
|
||||
@Override
|
||||
public void postDestroy(@NonNull Controller controller) {
|
||||
childBackstack.remove(controller);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
final void setParentController(@Nullable Controller controller) {
|
||||
parentController = controller;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import android.view.ViewParent;
|
||||
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler;
|
||||
import com.bluelinelabs.conductor.internal.ClassUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -28,13 +27,14 @@ public abstract class ControllerChangeHandler {
|
||||
private static final Map<String, ControllerChangeHandler> inProgressPushHandlers = new HashMap<>();
|
||||
|
||||
private boolean forceRemoveViewOnPush;
|
||||
private boolean hasBeenUsed;
|
||||
|
||||
/**
|
||||
* Responsible for swapping Views from one Controller to another.
|
||||
*
|
||||
* @param container The container these Views are hosted in.
|
||||
* @param from The previous View in the container, if any.
|
||||
* @param to The next View that should be put in the container, if any.
|
||||
* @param from The previous View in the container or {@code null} if there was no Controller before this transition
|
||||
* @param to The next View that should be put in the container or {@code null} if no Controller is being transitioned to
|
||||
* @param isPush True if this is a push transaction, false if it's a pop.
|
||||
* @param changeListener This listener must be called when any transitions or animations are completed.
|
||||
*/
|
||||
@@ -62,8 +62,9 @@ public abstract class ControllerChangeHandler {
|
||||
* Will be called on change handlers that push a controller if the controller being pushed is
|
||||
* popped before it has completed.
|
||||
*
|
||||
* @param newHandler the change handler that has caused this push to be aborted
|
||||
* @param newTop the controller that will now be at the top of the backstack
|
||||
* @param newHandler The change handler that has caused this push to be aborted
|
||||
* @param newTop The Controller that will now be at the top of the backstack or {@code null}
|
||||
* if there will be no new Controller at the top
|
||||
*/
|
||||
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable Controller newTop) { }
|
||||
|
||||
@@ -73,6 +74,26 @@ public abstract class ControllerChangeHandler {
|
||||
*/
|
||||
public void completeImmediately() { }
|
||||
|
||||
/**
|
||||
* Returns a copy of this ControllerChangeHandler. This method is internally used by the library, so
|
||||
* ensure it will return an exact copy of your handler if overriding. If not overriding, the handler
|
||||
* will be saved and restored from the Bundle format.
|
||||
*/
|
||||
@NonNull
|
||||
public ControllerChangeHandler copy() {
|
||||
return fromBundle(toBundle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not this is a reusable ControllerChangeHandler. Defaults to false and should
|
||||
* ONLY be overridden if there are absolutely no side effects to using this handler more than once.
|
||||
* In the case that a handler is not reusable, it will be copied using the {@link #copy()} method
|
||||
* prior to use.
|
||||
*/
|
||||
public boolean isReusable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
final Bundle toBundle() {
|
||||
Bundle bundle = new Bundle();
|
||||
@@ -85,11 +106,6 @@ public abstract class ControllerChangeHandler {
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
final ControllerChangeHandler copy() {
|
||||
return fromBundle(toBundle());
|
||||
}
|
||||
|
||||
private void ensureDefaultConstructor() {
|
||||
try {
|
||||
getClass().getConstructor();
|
||||
@@ -121,7 +137,7 @@ public abstract class ControllerChangeHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void abortPush(@NonNull Controller toAbort, @Nullable Controller newController, @NonNull ControllerChangeHandler newChangeHandler) {
|
||||
static void abortPush(@NonNull Controller toAbort, @Nullable Controller newController, @NonNull ControllerChangeHandler newChangeHandler) {
|
||||
ControllerChangeHandler handlerForPush = inProgressPushHandlers.get(toAbort.getInstanceId());
|
||||
if (handlerForPush != null) {
|
||||
handlerForPush.onAbortPush(newChangeHandler, newController);
|
||||
@@ -129,13 +145,21 @@ public abstract class ControllerChangeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public static void executeChange(@Nullable final Controller to, @Nullable final Controller from, boolean isPush, @NonNull ViewGroup container, @NonNull ControllerChangeHandler inHandler) {
|
||||
executeChange(to, from, isPush, container, inHandler, new ArrayList<ControllerChangeListener>());
|
||||
}
|
||||
static void executeChange(@Nullable final Controller to, @Nullable final Controller from, final boolean isPush, @Nullable final ViewGroup container, @Nullable final ControllerChangeHandler inHandler, @NonNull final List<ControllerChangeListener> listeners) {
|
||||
if (isPush && to != null && to.isDestroyed()) {
|
||||
throw new IllegalStateException("Trying to push a controller that has already been destroyed. (" + to.getClass().getSimpleName() + ")");
|
||||
}
|
||||
|
||||
public static void executeChange(@Nullable final Controller to, @Nullable final Controller from, final boolean isPush, @Nullable final ViewGroup container, @Nullable final ControllerChangeHandler inHandler, @NonNull final List<ControllerChangeListener> listeners) {
|
||||
if (container != null) {
|
||||
final ControllerChangeHandler handler = inHandler != null ? inHandler : new SimpleSwapChangeHandler();
|
||||
final ControllerChangeHandler handler;
|
||||
if (inHandler == null) {
|
||||
handler = new SimpleSwapChangeHandler();
|
||||
} else if (inHandler.hasBeenUsed && !inHandler.isReusable()) {
|
||||
handler = inHandler.copy();
|
||||
} else {
|
||||
handler = inHandler;
|
||||
}
|
||||
handler.hasBeenUsed = true;
|
||||
|
||||
if (isPush && to != null) {
|
||||
inProgressPushHandlers.put(to.getInstanceId(), handler);
|
||||
@@ -212,8 +236,8 @@ public abstract class ControllerChangeHandler {
|
||||
/**
|
||||
* Called when a {@link ControllerChangeHandler} has started changing {@link Controller}s
|
||||
*
|
||||
* @param to The new Controller
|
||||
* @param from The old Controller
|
||||
* @param to The new Controller or {@code null} if no Controller is being transitioned to
|
||||
* @param from The old Controller or {@code null} if there was no Controller before this transition
|
||||
* @param isPush True if this is a push operation, or false if it's a pop.
|
||||
* @param container The containing ViewGroup
|
||||
* @param handler The change handler being used.
|
||||
@@ -223,9 +247,9 @@ public abstract class ControllerChangeHandler {
|
||||
/**
|
||||
* Called when a {@link ControllerChangeHandler} has completed changing {@link Controller}s
|
||||
*
|
||||
* @param to The new Controller
|
||||
* @param from The old Controller
|
||||
* @param isPush True if this was a push operation, or false if it's a pop.
|
||||
* @param to The new Controller or {@code null} if no Controller is being transitioned to
|
||||
* @param from The old Controller or {@code null} if there was no Controller before this transition
|
||||
* @param isPush True if this was a push operation, or false if it's a pop
|
||||
* @param container The containing ViewGroup
|
||||
* @param handler The change handler that was used.
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.bluelinelabs.conductor;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.IntentSender.SendIntentException;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.IdRes;
|
||||
import android.support.annotation.NonNull;
|
||||
@@ -9,6 +11,7 @@ import android.support.annotation.Nullable;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeListener;
|
||||
import com.bluelinelabs.conductor.internal.TransactionIndexer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -51,12 +54,12 @@ class ControllerHostedRouter extends Router {
|
||||
final List<Controller> controllersToDestroy = new ArrayList<>(destroyingControllers);
|
||||
for (Controller controller : controllersToDestroy) {
|
||||
if (controller.getView() != null) {
|
||||
controller.detach(controller.getView(), true);
|
||||
controller.detach(controller.getView(), true, false);
|
||||
}
|
||||
}
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
if (transaction.controller.getView() != null) {
|
||||
transaction.controller.detach(transaction.controller.getView(), true);
|
||||
transaction.controller.detach(transaction.controller.getView(), true, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,9 +75,9 @@ class ControllerHostedRouter extends Router {
|
||||
}
|
||||
|
||||
@Override
|
||||
void destroy() {
|
||||
void destroy(boolean popViews) {
|
||||
setDetachFrozen(false);
|
||||
super.destroy();
|
||||
super.destroy(popViews);
|
||||
}
|
||||
|
||||
@Override @Nullable
|
||||
@@ -124,6 +127,13 @@ class ControllerHostedRouter extends Router {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void startIntentSenderForResult(@NonNull String instanceId, @NonNull IntentSender intent, int requestCode, @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, @Nullable Bundle options) throws SendIntentException {
|
||||
if (hostController != null && hostController.getRouter() != null) {
|
||||
hostController.getRouter().startIntentSenderForResult(instanceId, intent, requestCode, fillInIntent, flagsMask, flagsValues, extraFlags, options);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerForActivityResult(@NonNull String instanceId, int requestCode) {
|
||||
if (hostController != null && hostController.getRouter() != null) {
|
||||
@@ -197,4 +207,9 @@ class ControllerHostedRouter extends Router {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override @Nullable
|
||||
TransactionIndexer getTransactionIndexer() {
|
||||
return getRootRouter().getTransactionIndexer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ abstract public class RestoreViewOnCreateController extends Controller {
|
||||
* This Controller's view should NOT be added in this method. It is simply passed in
|
||||
* so that valid LayoutParams can be used during inflation.
|
||||
* @param savedViewState A bundle for the view's state, which would have been created in {@link #onSaveViewState(View, Bundle)},
|
||||
* or null if no saved state exists.
|
||||
* or {@code null} if no saved state exists.
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container, @Nullable Bundle savedViewState);
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.bluelinelabs.conductor;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
@@ -16,6 +17,7 @@ import com.bluelinelabs.conductor.Controller.LifecycleListener;
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeListener;
|
||||
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler;
|
||||
import com.bluelinelabs.conductor.internal.NoOpControllerChangeHandler;
|
||||
import com.bluelinelabs.conductor.internal.TransactionIndexer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -33,7 +35,6 @@ public abstract class Router {
|
||||
private static final String KEY_POPS_LAST_VIEW = "Router.popsLastView";
|
||||
|
||||
protected final Backstack backstack = new Backstack();
|
||||
private OnControllerPushedListener onControllerPushedListener;
|
||||
private final List<ControllerChangeListener> changeListeners = new ArrayList<>();
|
||||
final List<Controller> destroyingControllers = new ArrayList<>();
|
||||
|
||||
@@ -42,7 +43,8 @@ public abstract class Router {
|
||||
ViewGroup container;
|
||||
|
||||
/**
|
||||
* Returns this Router's host Activity
|
||||
* Returns this Router's host Activity or {@code null} if it has either not yet been attached to
|
||||
* an Activity or if the Activity has been destroyed.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract Activity getActivity();
|
||||
@@ -52,8 +54,8 @@ public abstract class Router {
|
||||
* of the controller that called startActivityForResult is not known.
|
||||
*
|
||||
* @param requestCode The Activity's onActivityResult requestCode
|
||||
* @param resultCode The Activity's onActivityResult resultCode
|
||||
* @param data The Activity's onActivityResult data
|
||||
* @param resultCode The Activity's onActivityResult resultCode
|
||||
* @param data The Activity's onActivityResult data
|
||||
*/
|
||||
public abstract void onActivityResult(int requestCode, int resultCode, @Nullable Intent data);
|
||||
|
||||
@@ -61,9 +63,9 @@ public abstract class Router {
|
||||
* This should be called by the host Activity when its onRequestPermissionsResult method is called. The call will be forwarded
|
||||
* to the {@link Controller} with the instanceId passed in.
|
||||
*
|
||||
* @param instanceId The instanceId of the Controller to which this result should be forwarded
|
||||
* @param requestCode The Activity's onRequestPermissionsResult requestCode
|
||||
* @param permissions The Activity's onRequestPermissionsResult permissions
|
||||
* @param instanceId The instanceId of the Controller to which this result should be forwarded
|
||||
* @param requestCode The Activity's onRequestPermissionsResult requestCode
|
||||
* @param permissions The Activity's onRequestPermissionsResult permissions
|
||||
* @param grantResults The Activity's onRequestPermissionsResult grantResults
|
||||
*/
|
||||
public void onRequestPermissionsResult(@NonNull String instanceId, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
@@ -98,6 +100,7 @@ public abstract class Router {
|
||||
*
|
||||
* @return Whether or not this Router still has controllers remaining on it after popping.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@UiThread
|
||||
public boolean popCurrentController() {
|
||||
RouterTransaction transaction = backstack.peek();
|
||||
@@ -159,6 +162,7 @@ public abstract class Router {
|
||||
* @param transaction The transaction detailing what should be pushed, including the {@link Controller},
|
||||
* and its push and pop {@link ControllerChangeHandler}, and its tag.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@UiThread
|
||||
public void replaceTopController(@NonNull RouterTransaction transaction) {
|
||||
RouterTransaction topTransaction = backstack.peek();
|
||||
@@ -168,11 +172,12 @@ public abstract class Router {
|
||||
|
||||
final ControllerChangeHandler handler = transaction.pushChangeHandler();
|
||||
if (topTransaction != null) {
|
||||
//noinspection ConstantConditions
|
||||
final boolean oldHandlerRemovedViews = topTransaction.pushChangeHandler() == null || topTransaction.pushChangeHandler().removesFromViewOnPush();
|
||||
final boolean newHandlerRemovesViews = handler == null || handler.removesFromViewOnPush();
|
||||
if (!oldHandlerRemovedViews && newHandlerRemovesViews) {
|
||||
for (RouterTransaction visibleTransaction : getVisibleTransactions(backstack.iterator())) {
|
||||
performControllerChange(null, visibleTransaction.controller, true, handler != null ? handler.copy() : new SimpleSwapChangeHandler());
|
||||
performControllerChange(null, visibleTransaction, true, handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,14 +190,26 @@ public abstract class Router {
|
||||
performControllerChange(transaction.pushChangeHandler(handler), topTransaction, true);
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
void destroy(boolean popViews) {
|
||||
popsLastView = true;
|
||||
List<RouterTransaction> poppedControllers = backstack.popAll();
|
||||
final List<RouterTransaction> poppedControllers = backstack.popAll();
|
||||
trackDestroyingControllers(poppedControllers);
|
||||
|
||||
if (poppedControllers.size() > 0) {
|
||||
trackDestroyingControllers(poppedControllers);
|
||||
if (popViews && poppedControllers.size() > 0) {
|
||||
RouterTransaction topTransaction = poppedControllers.get(0);
|
||||
topTransaction.controller().addLifecycleListener(new LifecycleListener() {
|
||||
@Override
|
||||
public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
|
||||
if (changeType == ControllerChangeType.POP_EXIT) {
|
||||
for (int i = poppedControllers.size() - 1; i > 0; i--) {
|
||||
RouterTransaction transaction = poppedControllers.get(i);
|
||||
performControllerChange(null, transaction, true, new SimpleSwapChangeHandler());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
performControllerChange(null, poppedControllers.get(0).controller, false, poppedControllers.get(0).popChangeHandler());
|
||||
performControllerChange(null, topTransaction, false, topTransaction.popChangeHandler());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,6 +244,7 @@ public abstract class Router {
|
||||
* @param changeHandler The {@link ControllerChangeHandler} to handle this transaction
|
||||
* @return Whether or not any {@link Controller}s were popped in order to get to the root transaction
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@UiThread
|
||||
public boolean popToRoot(@Nullable ControllerChangeHandler changeHandler) {
|
||||
if (backstack.size() > 1) {
|
||||
@@ -252,10 +270,11 @@ public abstract class Router {
|
||||
/**
|
||||
* Pops all {@link Controller}s until the {@link Controller} with the passed tag is at the top
|
||||
*
|
||||
* @param tag The tag being popped to
|
||||
* @param tag The tag being popped to
|
||||
* @param changeHandler The {@link ControllerChangeHandler} to handle this transaction
|
||||
* @return Whether or not the {@link Controller} with the passed tag is now at the top
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@UiThread
|
||||
public boolean popToTag(@NonNull String tag, @Nullable ControllerChangeHandler changeHandler) {
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
@@ -275,16 +294,15 @@ public abstract class Router {
|
||||
*/
|
||||
@UiThread
|
||||
public void setRoot(@NonNull RouterTransaction transaction) {
|
||||
List<RouterTransaction> transactions = new ArrayList<>();
|
||||
transactions.add(transaction);
|
||||
List<RouterTransaction> transactions = Collections.singletonList(transaction);
|
||||
setBackstack(transactions, transaction.pushChangeHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hosted Controller with the given instance id, if available.
|
||||
* Returns the hosted Controller with the given instance id or {@code null} if no such
|
||||
* Controller exists in this Router.
|
||||
*
|
||||
* @param instanceId The instance ID being searched for
|
||||
* @return The matching Controller, if one exists
|
||||
*/
|
||||
@Nullable
|
||||
public Controller getControllerWithInstanceId(@NonNull String instanceId) {
|
||||
@@ -298,10 +316,10 @@ public abstract class Router {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hosted Controller that was pushed with the given tag, if available.
|
||||
* Returns the hosted Controller that was pushed with the given tag or {@code null} if no
|
||||
* such Controller exists in this Router.
|
||||
*
|
||||
* @param tag The tag being searched for
|
||||
* @return The matching Controller, if one exists
|
||||
*/
|
||||
@Nullable
|
||||
public Controller getControllerWithTag(@NonNull String tag) {
|
||||
@@ -316,6 +334,7 @@ public abstract class Router {
|
||||
/**
|
||||
* Returns the number of {@link Controller}s currently in the backstack
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public int getBackstackSize() {
|
||||
return backstack.size();
|
||||
}
|
||||
@@ -337,60 +356,59 @@ public abstract class Router {
|
||||
* Sets the backstack, transitioning from the current top controller to the top of the new stack (if different)
|
||||
* using the passed {@link ControllerChangeHandler}
|
||||
*
|
||||
* @param newBackstack The new backstack
|
||||
* @param newBackstack The new backstack
|
||||
* @param changeHandler An optional change handler to be used to handle the root view of transition
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@UiThread
|
||||
public void setBackstack(@NonNull List<RouterTransaction> newBackstack, @Nullable ControllerChangeHandler changeHandler) {
|
||||
List<RouterTransaction> oldVisibleTransactions = getVisibleTransactions(backstack.iterator());
|
||||
|
||||
backstack.setBackstack(newBackstack);
|
||||
boolean newRootRequiresPush = !(newBackstack.size() > 0 && backstack.contains(newBackstack.get(0)));
|
||||
|
||||
removeAllExceptVisibleAndUnowned();
|
||||
ensureOrderedTransactionIndices(newBackstack);
|
||||
|
||||
backstack.setBackstack(newBackstack);
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
transaction.onAttachedToRouter();
|
||||
}
|
||||
|
||||
removeAllExceptVisibleAndUnowned();
|
||||
|
||||
if (newBackstack.size() > 0) {
|
||||
List<RouterTransaction> reverseNewBackstack = new ArrayList<>(newBackstack);
|
||||
Collections.reverse(reverseNewBackstack);
|
||||
List<RouterTransaction> newVisibleTransactions = getVisibleTransactions(reverseNewBackstack.iterator());
|
||||
|
||||
boolean visibleTransactionsChanged = newVisibleTransactions.size() != oldVisibleTransactions.size();
|
||||
if (!visibleTransactionsChanged) {
|
||||
for (int i = 0; i < oldVisibleTransactions.size(); i++) {
|
||||
if (oldVisibleTransactions.get(i).controller != newVisibleTransactions.get(i).controller) {
|
||||
visibleTransactionsChanged = true;
|
||||
break;
|
||||
boolean visibleTransactionsChanged = !backstacksAreEqual(newVisibleTransactions, oldVisibleTransactions);
|
||||
if (visibleTransactionsChanged) {
|
||||
RouterTransaction rootTransaction = oldVisibleTransactions.size() > 0 ? oldVisibleTransactions.get(0) : null;
|
||||
// Replace the old root with the new one
|
||||
if (rootTransaction == null || rootTransaction.controller != newVisibleTransactions.get(0).controller) {
|
||||
performControllerChange(newVisibleTransactions.get(0), rootTransaction, newRootRequiresPush, changeHandler);
|
||||
}
|
||||
|
||||
// Remove all visible controllers that were previously on the backstack
|
||||
for (int i = oldVisibleTransactions.size() - 1; i > 0; i--) {
|
||||
RouterTransaction transaction = oldVisibleTransactions.get(i);
|
||||
if (!newVisibleTransactions.contains(transaction)) {
|
||||
ControllerChangeHandler localHandler = changeHandler != null ? changeHandler.copy() : new SimpleSwapChangeHandler();
|
||||
localHandler.setForceRemoveViewOnPush(true);
|
||||
performControllerChange(null, transaction, newRootRequiresPush, localHandler);
|
||||
}
|
||||
}
|
||||
|
||||
// Add any new controllers to the backstack
|
||||
for (int i = 1; i < newVisibleTransactions.size(); i++) {
|
||||
RouterTransaction transaction = newVisibleTransactions.get(i);
|
||||
if (!oldVisibleTransactions.contains(transaction)) {
|
||||
performControllerChange(transaction, newVisibleTransactions.get(i - 1), true, transaction.pushChangeHandler());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (visibleTransactionsChanged) {
|
||||
ControllerChangeHandler handler = changeHandler != null ? changeHandler : new SimpleSwapChangeHandler();
|
||||
|
||||
Controller rootController = oldVisibleTransactions.size() > 0 ? oldVisibleTransactions.get(0).controller : null;
|
||||
performControllerChange(newVisibleTransactions.get(0).controller, rootController, true, handler);
|
||||
|
||||
for (int i = oldVisibleTransactions.size() - 1; i > 0; i--) {
|
||||
RouterTransaction transaction = oldVisibleTransactions.get(i);
|
||||
ControllerChangeHandler localHandler = handler.copy();
|
||||
localHandler.setForceRemoveViewOnPush(true);
|
||||
performControllerChange(null, transaction.controller, true, localHandler);
|
||||
}
|
||||
|
||||
for (int i = 1; i < newVisibleTransactions.size(); i++) {
|
||||
RouterTransaction transaction = newVisibleTransactions.get(i);
|
||||
handler = transaction.pushChangeHandler() != null ? transaction.pushChangeHandler().copy() : new SimpleSwapChangeHandler();
|
||||
performControllerChange(transaction.controller, newVisibleTransactions.get(i - 1).controller, true, handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (onControllerPushedListener != null) {
|
||||
// Ensure all new controllers have a valid router set
|
||||
for (RouterTransaction transaction : newBackstack) {
|
||||
onControllerPushedListener.onControllerPushed(transaction.controller);
|
||||
transaction.controller.setRouter(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -407,6 +425,7 @@ public abstract class Router {
|
||||
*
|
||||
* @param changeListener The listener
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public void addChangeListener(@NonNull ControllerChangeListener changeListener) {
|
||||
if (!changeListeners.contains(changeListener)) {
|
||||
changeListeners.add(changeListener);
|
||||
@@ -418,6 +437,7 @@ public abstract class Router {
|
||||
*
|
||||
* @param changeListener The listener to be removed
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public void removeChangeListener(@NonNull ControllerChangeListener changeListener) {
|
||||
changeListeners.remove(changeListener);
|
||||
}
|
||||
@@ -432,7 +452,7 @@ public abstract class Router {
|
||||
RouterTransaction transaction = backstackIterator.next();
|
||||
|
||||
if (transaction.controller.getNeedsAttach()) {
|
||||
performControllerChange(transaction.controller, null, true, new SimpleSwapChangeHandler(false));
|
||||
performControllerChange(transaction, null, true, new SimpleSwapChangeHandler(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -508,7 +528,7 @@ public abstract class Router {
|
||||
container = null;
|
||||
}
|
||||
|
||||
public void prepareForHostDetach() {
|
||||
void prepareForHostDetach() {
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
if (ControllerChangeHandler.completePushImmediately(transaction.controller.getInstanceId())) {
|
||||
transaction.controller.setNeedsAttach();
|
||||
@@ -529,6 +549,7 @@ public abstract class Router {
|
||||
|
||||
public void restoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||
Bundle backstackBundle = savedInstanceState.getParcelable(KEY_BACKSTACK);
|
||||
//noinspection ConstantConditions
|
||||
backstack.restoreInstanceState(backstackBundle);
|
||||
popsLastView = savedInstanceState.getBoolean(KEY_POPS_LAST_VIEW);
|
||||
|
||||
@@ -574,23 +595,28 @@ public abstract class Router {
|
||||
}
|
||||
|
||||
private void popToTransaction(@NonNull RouterTransaction transaction, @Nullable ControllerChangeHandler changeHandler) {
|
||||
RouterTransaction topTransaction = backstack.peek();
|
||||
List<RouterTransaction> poppedTransactions = backstack.popTo(transaction);
|
||||
trackDestroyingControllers(poppedTransactions);
|
||||
if (backstack.size() > 0) {
|
||||
RouterTransaction topTransaction = backstack.peek();
|
||||
|
||||
List<RouterTransaction> updatedBackstack = new ArrayList<>();
|
||||
Iterator<RouterTransaction> backstackIterator = backstack.reverseIterator();
|
||||
while (backstackIterator.hasNext()) {
|
||||
RouterTransaction existingTransaction = backstackIterator.next();
|
||||
updatedBackstack.add(existingTransaction);
|
||||
if (existingTransaction == transaction) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (poppedTransactions.size() > 0) {
|
||||
if (changeHandler == null) {
|
||||
//noinspection ConstantConditions
|
||||
changeHandler = topTransaction.popChangeHandler();
|
||||
}
|
||||
|
||||
performControllerChange(backstack.peek().controller, topTransaction.controller, false, changeHandler);
|
||||
setBackstack(updatedBackstack, changeHandler);
|
||||
}
|
||||
}
|
||||
|
||||
final void setOnControllerPushedListener(OnControllerPushedListener listener) {
|
||||
onControllerPushedListener = listener;
|
||||
}
|
||||
|
||||
void prepareForContainerRemoval() {
|
||||
if (container != null) {
|
||||
container.setOnHierarchyChangeListener(null);
|
||||
@@ -631,33 +657,30 @@ public abstract class Router {
|
||||
} else if (from != null) {
|
||||
changeHandler = from.popChangeHandler();
|
||||
} else {
|
||||
changeHandler = new SimpleSwapChangeHandler();
|
||||
changeHandler = null;
|
||||
}
|
||||
|
||||
performControllerChange(to, from, isPush, changeHandler);
|
||||
}
|
||||
|
||||
private void performControllerChange(@Nullable final RouterTransaction to, @Nullable final RouterTransaction from, boolean isPush, @Nullable ControllerChangeHandler changeHandler) {
|
||||
Controller toController = to != null ? to.controller : null;
|
||||
Controller fromController = from != null ? from.controller : null;
|
||||
|
||||
performControllerChange(toController, fromController, isPush, changeHandler);
|
||||
}
|
||||
|
||||
private void performControllerChange(@Nullable final Controller to, @Nullable final Controller from, boolean isPush, @Nullable ControllerChangeHandler changeHandler) {
|
||||
if (to != null) {
|
||||
setControllerRouter(to);
|
||||
to.ensureValidIndex(getTransactionIndexer());
|
||||
setControllerRouter(toController);
|
||||
} else if (backstack.size() == 0 && !popsLastView) {
|
||||
// We're emptying out the backstack. Views get weird if you transition them out, so just no-op it. The hosting
|
||||
// Activity should be handling this by finishing or at least hiding this view.
|
||||
changeHandler = new NoOpControllerChangeHandler();
|
||||
}
|
||||
|
||||
ControllerChangeHandler.executeChange(to, from, isPush, container, changeHandler, changeListeners);
|
||||
ControllerChangeHandler.executeChange(toController, fromController, isPush, container, changeHandler, changeListeners);
|
||||
}
|
||||
|
||||
private void pushToBackstack(@NonNull RouterTransaction entry) {
|
||||
backstack.push(entry);
|
||||
|
||||
if (onControllerPushedListener != null) {
|
||||
onControllerPushedListener.onControllerPushed(entry.controller);
|
||||
}
|
||||
}
|
||||
|
||||
private void trackDestroyingController(@NonNull RouterTransaction transaction) {
|
||||
@@ -703,6 +726,22 @@ public abstract class Router {
|
||||
}
|
||||
}
|
||||
|
||||
// Swap around transaction indicies to ensure they don't get thrown out of order by the
|
||||
// developer rearranging the backstack at runtime.
|
||||
private void ensureOrderedTransactionIndices(List<RouterTransaction> backstack) {
|
||||
List<Integer> indices = new ArrayList<>();
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
transaction.ensureValidIndex(getTransactionIndexer());
|
||||
indices.add(transaction.transactionIndex);
|
||||
}
|
||||
|
||||
Collections.sort(indices);
|
||||
|
||||
for (int i = 0; i < backstack.size(); i++) {
|
||||
backstack.get(i).transactionIndex = indices.get(i);
|
||||
}
|
||||
}
|
||||
|
||||
private void addRouterViewsToList(@NonNull Router router, @NonNull List<View> list) {
|
||||
for (Controller controller : router.getControllers()) {
|
||||
if (controller.getView() != null) {
|
||||
@@ -721,6 +760,7 @@ public abstract class Router {
|
||||
RouterTransaction transaction = backstackIterator.next();
|
||||
transactions.add(transaction);
|
||||
|
||||
//noinspection ConstantConditions
|
||||
if (transaction.pushChangeHandler() == null || transaction.pushChangeHandler().removesFromViewOnPush()) {
|
||||
break;
|
||||
}
|
||||
@@ -730,6 +770,20 @@ public abstract class Router {
|
||||
return transactions;
|
||||
}
|
||||
|
||||
private boolean backstacksAreEqual(List<RouterTransaction> lhs, List<RouterTransaction> rhs) {
|
||||
if (lhs.size() != rhs.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < rhs.size(); i++) {
|
||||
if (rhs.get(i).controller() != lhs.get(i).controller()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void setControllerRouter(@NonNull Controller controller) {
|
||||
controller.setRouter(this);
|
||||
}
|
||||
@@ -738,14 +792,14 @@ public abstract class Router {
|
||||
abstract void startActivity(@NonNull Intent intent);
|
||||
abstract void startActivityForResult(@NonNull String instanceId, @NonNull Intent intent, int requestCode);
|
||||
abstract void startActivityForResult(@NonNull String instanceId, @NonNull Intent intent, int requestCode, @Nullable Bundle options);
|
||||
abstract void startIntentSenderForResult(@NonNull String instanceId, @NonNull IntentSender intent, int requestCode, @Nullable Intent fillInIntent, int flagsMask,
|
||||
int flagsValues, int extraFlags, @Nullable Bundle options) throws IntentSender.SendIntentException;
|
||||
abstract void registerForActivityResult(@NonNull String instanceId, int requestCode);
|
||||
abstract void unregisterForActivityResults(@NonNull String instanceId);
|
||||
abstract void requestPermissions(@NonNull String instanceId, @NonNull String[] permissions, int requestCode);
|
||||
abstract boolean hasHost();
|
||||
@NonNull abstract List<Router> getSiblingRouters();
|
||||
@NonNull abstract Router getRootRouter();
|
||||
@Nullable abstract TransactionIndexer getTransactionIndexer();
|
||||
|
||||
interface OnControllerPushedListener {
|
||||
void onControllerPushed(Controller controller);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,20 @@ import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.bluelinelabs.conductor.internal.TransactionIndexer;
|
||||
|
||||
/**
|
||||
* Metadata used for adding {@link Controller}s to a {@link Router}.
|
||||
*/
|
||||
public class RouterTransaction {
|
||||
|
||||
private static int INVALID_INDEX = -1;
|
||||
|
||||
private static final String KEY_VIEW_CONTROLLER_BUNDLE = "RouterTransaction.controller.bundle";
|
||||
private static final String KEY_PUSH_TRANSITION = "RouterTransaction.pushControllerChangeHandler";
|
||||
private static final String KEY_POP_TRANSITION = "RouterTransaction.popControllerChangeHandler";
|
||||
private static final String KEY_TAG = "RouterTransaction.tag";
|
||||
private static final String KEY_INDEX = "RouterTransaction.transactionIndex";
|
||||
private static final String KEY_ATTACHED_TO_ROUTER = "RouterTransaction.attachedToRouter";
|
||||
|
||||
@NonNull final Controller controller;
|
||||
@@ -21,6 +26,7 @@ public class RouterTransaction {
|
||||
private ControllerChangeHandler pushControllerChangeHandler;
|
||||
private ControllerChangeHandler popControllerChangeHandler;
|
||||
private boolean attachedToRouter;
|
||||
int transactionIndex = INVALID_INDEX;
|
||||
|
||||
@NonNull
|
||||
public static RouterTransaction with(@NonNull Controller controller) {
|
||||
@@ -36,6 +42,7 @@ public class RouterTransaction {
|
||||
pushControllerChangeHandler = ControllerChangeHandler.fromBundle(bundle.getBundle(KEY_PUSH_TRANSITION));
|
||||
popControllerChangeHandler = ControllerChangeHandler.fromBundle(bundle.getBundle(KEY_POP_TRANSITION));
|
||||
tag = bundle.getString(KEY_TAG);
|
||||
transactionIndex = bundle.getInt(KEY_INDEX);
|
||||
attachedToRouter = bundle.getBoolean(KEY_ATTACHED_TO_ROUTER);
|
||||
}
|
||||
|
||||
@@ -101,6 +108,15 @@ public class RouterTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
void ensureValidIndex(@Nullable TransactionIndexer indexer) {
|
||||
if (indexer == null) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
if (transactionIndex == INVALID_INDEX && indexer != null) {
|
||||
transactionIndex = indexer.nextIndex();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to serialize this transaction into a Bundle
|
||||
*/
|
||||
@@ -118,6 +134,7 @@ public class RouterTransaction {
|
||||
}
|
||||
|
||||
bundle.putString(KEY_TAG, tag);
|
||||
bundle.putInt(KEY_INDEX, transactionIndex);
|
||||
bundle.putBoolean(KEY_ATTACHED_TO_ROUTER, attachedToRouter);
|
||||
|
||||
return bundle;
|
||||
|
||||
@@ -94,8 +94,8 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
|
||||
* Should be overridden to return the Animator to use while replacing Views.
|
||||
*
|
||||
* @param container The container these Views are hosted in.
|
||||
* @param from The previous View in the container, if any.
|
||||
* @param to The next View that should be put in the container, if any.
|
||||
* @param from The previous View in the container or {@code null} if there was no Controller before this transition
|
||||
* @param to The next View that should be put in the container or {@code null} if no Controller is being transitioned to
|
||||
* @param isPush True if this is a push transaction, false if it's a pop.
|
||||
* @param toAddedToContainer True if the "to" view was added to the container as a part of this ChangeHandler. False if it was already in the hierarchy.
|
||||
*/
|
||||
@@ -150,6 +150,7 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
|
||||
if (animatorListener != null) {
|
||||
animator.removeListener(animatorListener);
|
||||
}
|
||||
animator.cancel();
|
||||
animator = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import android.transition.Transition;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
|
||||
/**
|
||||
* A change handler that will use an AutoTransition.
|
||||
*/
|
||||
@@ -20,4 +22,9 @@ public class AutoTransitionChangeHandler extends TransitionChangeHandler {
|
||||
return new AutoTransition();
|
||||
}
|
||||
|
||||
@Override @NonNull
|
||||
public ControllerChangeHandler copy() {
|
||||
return new AutoTransitionChangeHandler();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import android.support.annotation.Nullable;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
|
||||
/**
|
||||
* An {@link AnimatorChangeHandler} that will cross fade two views
|
||||
*/
|
||||
@@ -30,11 +32,12 @@ public class FadeChangeHandler extends AnimatorChangeHandler {
|
||||
@Override @NonNull
|
||||
protected Animator getAnimator(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, boolean toAddedToContainer) {
|
||||
AnimatorSet animator = new AnimatorSet();
|
||||
if (to != null && toAddedToContainer) {
|
||||
animator.play(ObjectAnimator.ofFloat(to, View.ALPHA, 0, 1));
|
||||
if (to != null) {
|
||||
float start = toAddedToContainer ? 0 : to.getAlpha();
|
||||
animator.play(ObjectAnimator.ofFloat(to, View.ALPHA, start, 1));
|
||||
}
|
||||
|
||||
if (from != null) {
|
||||
if (from != null && removesFromViewOnPush()) {
|
||||
animator.play(ObjectAnimator.ofFloat(from, View.ALPHA, 0));
|
||||
}
|
||||
|
||||
@@ -45,4 +48,10 @@ public class FadeChangeHandler extends AnimatorChangeHandler {
|
||||
protected void resetFromView(@NonNull View from) {
|
||||
from.setAlpha(1);
|
||||
}
|
||||
|
||||
@Override @NonNull
|
||||
public ControllerChangeHandler copy() {
|
||||
return new FadeChangeHandler(getAnimationDuration(), removesFromViewOnPush());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import android.support.annotation.Nullable;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
|
||||
/**
|
||||
* An {@link AnimatorChangeHandler} that will slide the views left or right, depending on if it's a push or pop.
|
||||
*/
|
||||
@@ -43,7 +45,9 @@ public class HorizontalChangeHandler extends AnimatorChangeHandler {
|
||||
animatorSet.play(ObjectAnimator.ofFloat(from, View.TRANSLATION_X, from.getWidth()));
|
||||
}
|
||||
if (to != null) {
|
||||
animatorSet.play(ObjectAnimator.ofFloat(to, View.TRANSLATION_X, -to.getWidth(), 0));
|
||||
// Allow this to have a nice transition when coming off an aborted push animation
|
||||
float fromLeft = from != null ? from.getX() : 0;
|
||||
animatorSet.play(ObjectAnimator.ofFloat(to, View.TRANSLATION_X, fromLeft - to.getWidth(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,4 +58,10 @@ public class HorizontalChangeHandler extends AnimatorChangeHandler {
|
||||
protected void resetFromView(@NonNull View from) {
|
||||
from.setTranslationX(0);
|
||||
}
|
||||
|
||||
@Override @NonNull
|
||||
public ControllerChangeHandler copy() {
|
||||
return new HorizontalChangeHandler(getAnimationDuration(), removesFromViewOnPush());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -102,4 +102,14 @@ public class SimpleSwapChangeHandler extends ControllerChangeHandler implements
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(@NonNull View v) { }
|
||||
|
||||
@Override @NonNull
|
||||
public ControllerChangeHandler copy() {
|
||||
return new SimpleSwapChangeHandler(removesFromViewOnPush());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReusable() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,15 +19,19 @@ import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public abstract class TransitionChangeHandler extends ControllerChangeHandler {
|
||||
|
||||
public interface OnTransitionPreparedListener {
|
||||
public void onPrepared();
|
||||
}
|
||||
|
||||
private boolean canceled;
|
||||
|
||||
/**
|
||||
* Should be overridden to return the Transition to use while replacing Views.
|
||||
*
|
||||
* @param container The container these Views are hosted in.
|
||||
* @param from The previous View in the container, if any.
|
||||
* @param to The next View that should be put in the container, if any.
|
||||
* @param isPush True if this is a push transaction, false if it's a pop.
|
||||
* @param container The container these Views are hosted in
|
||||
* @param from The previous View in the container or {@code null} if there was no Controller before this transition
|
||||
* @param to The next View that should be put in the container or {@code null} if no Controller is being transitioned to
|
||||
* @param isPush True if this is a push transaction, false if it's a pop
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract Transition getTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush);
|
||||
@@ -40,13 +44,13 @@ public abstract class TransitionChangeHandler extends ControllerChangeHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performChange(@NonNull final ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) {
|
||||
public void performChange(@NonNull final ViewGroup container, @Nullable final View from, @Nullable final View to, final boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) {
|
||||
if (canceled) {
|
||||
changeListener.onChangeCompleted();
|
||||
return;
|
||||
}
|
||||
|
||||
Transition transition = getTransition(container, from, to, isPush);
|
||||
final Transition transition = getTransition(container, from, to, isPush);
|
||||
transition.addListener(new TransitionListener() {
|
||||
@Override
|
||||
public void onTransitionStart(Transition transition) { }
|
||||
@@ -68,8 +72,48 @@ public abstract class TransitionChangeHandler extends ControllerChangeHandler {
|
||||
public void onTransitionResume(Transition transition) { }
|
||||
});
|
||||
|
||||
TransitionManager.beginDelayedTransition(container, transition);
|
||||
if (from != null) {
|
||||
prepareForTransition(container, from, to, transition, isPush, new OnTransitionPreparedListener() {
|
||||
@Override
|
||||
public void onPrepared() {
|
||||
if (!canceled) {
|
||||
TransitionManager.beginDelayedTransition(container, transition);
|
||||
executePropertyChanges(container, from, to, transition, isPush);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removesFromViewOnPush() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before a transition occurs. This can be used to reorder views, set their transition names, etc. The transition will begin
|
||||
* when {@code onTransitionPreparedListener} is called.
|
||||
*
|
||||
* @param container The container these Views are hosted in
|
||||
* @param from The previous View in the container or {@code null} if there was no Controller before this transition
|
||||
* @param to The next View that should be put in the container or {@code null} if no Controller is being transitioned to
|
||||
* @param transition The transition that is being prepared for
|
||||
* @param isPush True if this is a push transaction, false if it's a pop
|
||||
*/
|
||||
public void prepareForTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @NonNull Transition transition, boolean isPush, @NonNull OnTransitionPreparedListener onTransitionPreparedListener) {
|
||||
onTransitionPreparedListener.onPrepared();
|
||||
}
|
||||
|
||||
/**
|
||||
* This should set all view properties needed for the transition to work properly. By default it removes the "from" view
|
||||
* and adds the "to" view.
|
||||
*
|
||||
* @param container The container these Views are hosted in
|
||||
* @param from The previous View in the container or {@code null} if there was no Controller before this transition
|
||||
* @param to The next View that should be put in the container or {@code null} if no Controller is being transitioned to
|
||||
* @param transition The transition with which {@code TransitionManager.beginDelayedTransition} has been called
|
||||
* @param isPush True if this is a push transaction, false if it's a pop
|
||||
*/
|
||||
public void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @NonNull Transition transition, boolean isPush) {
|
||||
if (from != null && (removesFromViewOnPush() || !isPush) && from.getParent() == container) {
|
||||
container.removeView(from);
|
||||
}
|
||||
if (to != null && to.getParent() == null) {
|
||||
@@ -77,8 +121,4 @@ public abstract class TransitionChangeHandler extends ControllerChangeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean removesFromViewOnPush() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,4 +69,13 @@ public class TransitionChangeHandlerCompat extends ControllerChangeHandler {
|
||||
return changeHandler.removesFromViewOnPush();
|
||||
}
|
||||
|
||||
@Override @NonNull
|
||||
public ControllerChangeHandler copy() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
return new TransitionChangeHandlerCompat((TransitionChangeHandler)changeHandler.copy(), null);
|
||||
} else {
|
||||
return new TransitionChangeHandlerCompat(null, changeHandler.copy());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import android.support.annotation.Nullable;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -49,4 +51,9 @@ public class VerticalChangeHandler extends AnimatorChangeHandler {
|
||||
@Override
|
||||
protected void resetFromView(@NonNull View from) { }
|
||||
|
||||
@Override @NonNull
|
||||
public ControllerChangeHandler copy() {
|
||||
return new VerticalChangeHandler(getAnimationDuration(), removesFromViewOnPush());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.app.Application.ActivityLifecycleCallbacks;
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
@@ -283,6 +284,14 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
|
||||
startActivityForResult(intent, requestCode, options);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
public void startIntentSenderForResult(@NonNull String instanceId, @NonNull IntentSender intent, int requestCode,
|
||||
@Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
|
||||
@Nullable Bundle options) throws IntentSender.SendIntentException {
|
||||
registerForActivityResult(instanceId, requestCode);
|
||||
startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask, flagsValues, extraFlags, options);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
public void requestPermissions(@NonNull String instanceId, @NonNull String[] permissions, int requestCode) {
|
||||
if (attached) {
|
||||
|
||||
@@ -14,4 +14,14 @@ public class NoOpControllerChangeHandler extends ControllerChangeHandler {
|
||||
changeListener.onChangeCompleted();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ControllerChangeHandler copy() {
|
||||
return new NoOpControllerChangeHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReusable() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.bluelinelabs.conductor.internal;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
public class TransactionIndexer {
|
||||
|
||||
private static final String KEY_INDEX = "TransactionIndexer.currentIndex";
|
||||
|
||||
private int currentIndex;
|
||||
|
||||
public int nextIndex() {
|
||||
return ++currentIndex;
|
||||
}
|
||||
|
||||
public void saveInstanceState(@NonNull Bundle outState) {
|
||||
outState.putInt(KEY_INDEX, currentIndex);
|
||||
}
|
||||
|
||||
public void restoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||
currentIndex = savedInstanceState.getInt(KEY_INDEX);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package com.bluelinelabs.conductor.internal;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.View.OnAttachStateChangeListener;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
public class ViewAttachHandler implements OnAttachStateChangeListener {
|
||||
|
||||
private enum ReportedState {
|
||||
VIEW_DETACHED,
|
||||
ACTIVITY_STOPPED,
|
||||
ATTACHED
|
||||
}
|
||||
|
||||
public interface ViewAttachListener {
|
||||
void onAttached();
|
||||
void onDetached(boolean fromActivityStop);
|
||||
void onViewDetachAfterStop();
|
||||
}
|
||||
|
||||
private interface ChildAttachListener {
|
||||
void onAttached();
|
||||
}
|
||||
|
||||
private boolean rootAttached = false;
|
||||
boolean childrenAttached = false;
|
||||
private boolean activityStopped = false;
|
||||
private ReportedState reportedState = ReportedState.VIEW_DETACHED;
|
||||
private ViewAttachListener attachListener;
|
||||
private OnAttachStateChangeListener childOnAttachStateChangeListener;
|
||||
|
||||
public ViewAttachHandler(ViewAttachListener attachListener) {
|
||||
this.attachListener = attachListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewAttachedToWindow(final View v) {
|
||||
if (rootAttached) {
|
||||
return;
|
||||
}
|
||||
|
||||
rootAttached = true;
|
||||
listenForDeepestChildAttach(v, new ChildAttachListener() {
|
||||
@Override
|
||||
public void onAttached() {
|
||||
childrenAttached = true;
|
||||
reportAttached();
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(View v) {
|
||||
rootAttached = false;
|
||||
if (childrenAttached) {
|
||||
childrenAttached = false;
|
||||
reportDetached();
|
||||
}
|
||||
}
|
||||
|
||||
public void listenForAttach(final View view) {
|
||||
view.addOnAttachStateChangeListener(this);
|
||||
}
|
||||
|
||||
public void unregisterAttachListener(View view) {
|
||||
view.removeOnAttachStateChangeListener(this);
|
||||
|
||||
if (childOnAttachStateChangeListener != null && view instanceof ViewGroup) {
|
||||
findDeepestChild((ViewGroup)view).removeOnAttachStateChangeListener(childOnAttachStateChangeListener);
|
||||
}
|
||||
}
|
||||
|
||||
public void onActivityStarted() {
|
||||
activityStopped = false;
|
||||
reportAttached();
|
||||
}
|
||||
|
||||
public void onActivityStopped() {
|
||||
activityStopped = true;
|
||||
reportDetached();
|
||||
}
|
||||
|
||||
void reportAttached() {
|
||||
if (rootAttached && childrenAttached && !activityStopped && reportedState != ReportedState.ATTACHED) {
|
||||
reportedState = ReportedState.ATTACHED;
|
||||
attachListener.onAttached();
|
||||
}
|
||||
}
|
||||
|
||||
void reportDetached() {
|
||||
boolean wasDetachedForActivity = reportedState == ReportedState.ACTIVITY_STOPPED;
|
||||
boolean isDetachedForActivity = rootAttached;
|
||||
|
||||
if (isDetachedForActivity) {
|
||||
reportedState = ReportedState.ACTIVITY_STOPPED;
|
||||
} else {
|
||||
reportedState = ReportedState.VIEW_DETACHED;
|
||||
}
|
||||
|
||||
if (wasDetachedForActivity && !isDetachedForActivity) {
|
||||
attachListener.onViewDetachAfterStop();
|
||||
} else {
|
||||
attachListener.onDetached(isDetachedForActivity);
|
||||
}
|
||||
}
|
||||
|
||||
void listenForDeepestChildAttach(final View view, final ChildAttachListener attachListener) {
|
||||
if (!(view instanceof ViewGroup)) {
|
||||
attachListener.onAttached();
|
||||
return;
|
||||
}
|
||||
|
||||
ViewGroup viewGroup = (ViewGroup)view;
|
||||
if (viewGroup.getChildCount() == 0) {
|
||||
attachListener.onAttached();
|
||||
return;
|
||||
}
|
||||
|
||||
childOnAttachStateChangeListener = new OnAttachStateChangeListener() {
|
||||
boolean attached = false;
|
||||
|
||||
@Override
|
||||
public void onViewAttachedToWindow(View v) {
|
||||
if (!attached) {
|
||||
attached = true;
|
||||
attachListener.onAttached();
|
||||
v.removeOnAttachStateChangeListener(this);
|
||||
childOnAttachStateChangeListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(View v) { }
|
||||
};
|
||||
findDeepestChild(viewGroup).addOnAttachStateChangeListener(childOnAttachStateChangeListener);
|
||||
}
|
||||
|
||||
private View findDeepestChild(ViewGroup viewGroup) {
|
||||
if (viewGroup.getChildCount() == 0) {
|
||||
return viewGroup;
|
||||
}
|
||||
|
||||
View lastChild = viewGroup.getChildAt(viewGroup.getChildCount() - 1);
|
||||
if (lastChild instanceof ViewGroup) {
|
||||
return findDeepestChild((ViewGroup)lastChild);
|
||||
} else {
|
||||
return lastChild;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import org.junit.Assert;
|
||||
import com.bluelinelabs.conductor.util.TestController;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class BackstackTests {
|
||||
|
||||
private Backstack backstack;
|
||||
@@ -15,20 +18,20 @@ public class BackstackTests {
|
||||
|
||||
@Test
|
||||
public void testPush() {
|
||||
Assert.assertEquals(0, backstack.size());
|
||||
assertEquals(0, backstack.size());
|
||||
backstack.push(RouterTransaction.with(new TestController()));
|
||||
Assert.assertEquals(1, backstack.size());
|
||||
assertEquals(1, backstack.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPop() {
|
||||
backstack.push(RouterTransaction.with(new TestController()));
|
||||
backstack.push(RouterTransaction.with(new TestController()));
|
||||
Assert.assertEquals(2, backstack.size());
|
||||
assertEquals(2, backstack.size());
|
||||
backstack.pop();
|
||||
Assert.assertEquals(1, backstack.size());
|
||||
assertEquals(1, backstack.size());
|
||||
backstack.pop();
|
||||
Assert.assertEquals(0, backstack.size());
|
||||
assertEquals(0, backstack.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -37,13 +40,13 @@ public class BackstackTests {
|
||||
RouterTransaction transaction2 = RouterTransaction.with(new TestController());
|
||||
|
||||
backstack.push(transaction1);
|
||||
Assert.assertEquals(transaction1, backstack.peek());
|
||||
assertEquals(transaction1, backstack.peek());
|
||||
|
||||
backstack.push(transaction2);
|
||||
Assert.assertEquals(transaction2, backstack.peek());
|
||||
assertEquals(transaction2, backstack.peek());
|
||||
|
||||
backstack.pop();
|
||||
Assert.assertEquals(transaction1, backstack.peek());
|
||||
assertEquals(transaction1, backstack.peek());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -56,11 +59,11 @@ public class BackstackTests {
|
||||
backstack.push(transaction2);
|
||||
backstack.push(transaction3);
|
||||
|
||||
Assert.assertEquals(3, backstack.size());
|
||||
assertEquals(3, backstack.size());
|
||||
|
||||
backstack.popTo(transaction1);
|
||||
|
||||
Assert.assertEquals(1, backstack.size());
|
||||
Assert.assertEquals(transaction1, backstack.peek());
|
||||
assertEquals(1, backstack.size());
|
||||
assertEquals(transaction1, backstack.peek());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@ package com.bluelinelabs.conductor;
|
||||
|
||||
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
|
||||
import com.bluelinelabs.conductor.util.TestController;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class ControllerChangeHandlerTests {
|
||||
|
||||
@Test
|
||||
@@ -21,17 +23,17 @@ public class ControllerChangeHandlerTests {
|
||||
ControllerChangeHandler restoredHorizontal = restoredTransaction.pushChangeHandler();
|
||||
ControllerChangeHandler restoredFade = restoredTransaction.popChangeHandler();
|
||||
|
||||
Assert.assertEquals(horizontalChangeHandler.getClass(), restoredHorizontal.getClass());
|
||||
Assert.assertEquals(fadeChangeHandler.getClass(), restoredFade.getClass());
|
||||
assertEquals(horizontalChangeHandler.getClass(), restoredHorizontal.getClass());
|
||||
assertEquals(fadeChangeHandler.getClass(), restoredFade.getClass());
|
||||
|
||||
HorizontalChangeHandler restoredHorizontalCast = (HorizontalChangeHandler)restoredHorizontal;
|
||||
FadeChangeHandler restoredFadeCast = (FadeChangeHandler)restoredFade;
|
||||
|
||||
Assert.assertEquals(horizontalChangeHandler.getAnimationDuration(), restoredHorizontalCast.getAnimationDuration());
|
||||
Assert.assertEquals(horizontalChangeHandler.removesFromViewOnPush(), restoredHorizontalCast.removesFromViewOnPush());
|
||||
assertEquals(horizontalChangeHandler.getAnimationDuration(), restoredHorizontalCast.getAnimationDuration());
|
||||
assertEquals(horizontalChangeHandler.removesFromViewOnPush(), restoredHorizontalCast.removesFromViewOnPush());
|
||||
|
||||
Assert.assertEquals(fadeChangeHandler.getAnimationDuration(), restoredFadeCast.getAnimationDuration());
|
||||
Assert.assertEquals(fadeChangeHandler.removesFromViewOnPush(), restoredFadeCast.removesFromViewOnPush());
|
||||
assertEquals(fadeChangeHandler.getAnimationDuration(), restoredFadeCast.getAnimationDuration());
|
||||
assertEquals(fadeChangeHandler.removesFromViewOnPush(), restoredFadeCast.removesFromViewOnPush());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.util.ActivityProxy;
|
||||
import com.bluelinelabs.conductor.util.MockChangeHandler;
|
||||
import com.bluelinelabs.conductor.util.TestController;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class ControllerLifecycleActivityReferenceTests {
|
||||
|
||||
private Router router;
|
||||
|
||||
private ActivityProxy activityProxy;
|
||||
|
||||
public void createActivityController(Bundle savedInstanceState, boolean includeStartAndResume) {
|
||||
activityProxy = new ActivityProxy().create(savedInstanceState);
|
||||
|
||||
if (includeStartAndResume) {
|
||||
activityProxy.start().resume();
|
||||
}
|
||||
|
||||
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState);
|
||||
router.setPopsLastView(true);
|
||||
if (!router.hasRootController()) {
|
||||
router.setRoot(RouterTransaction.with(new TestController()));
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
createActivityController(null, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleControllerActivityOnPush() {
|
||||
Controller controller = new TestController();
|
||||
|
||||
assertNull(controller.getActivity());
|
||||
|
||||
ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener();
|
||||
controller.addLifecycleListener(listener);
|
||||
|
||||
router.pushController(RouterTransaction.with(controller)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
assertEquals(Collections.singletonList(true), listener.changeEndReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postCreateViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postAttachReferences);
|
||||
assertEquals(Collections.emptyList(), listener.postDetachReferences);
|
||||
assertEquals(Collections.emptyList(), listener.postDestroyViewReferences);
|
||||
assertEquals(Collections.emptyList(), listener.postDestroyReferences);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildControllerActivityOnPush() {
|
||||
Controller parent = new TestController();
|
||||
router.pushController(RouterTransaction.with(parent)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
TestController child = new TestController();
|
||||
|
||||
assertNull(child.getActivity());
|
||||
|
||||
ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener();
|
||||
child.addLifecycleListener(listener);
|
||||
|
||||
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter.pushController(RouterTransaction.with(child)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
assertEquals(Collections.singletonList(true), listener.changeEndReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postCreateViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postAttachReferences);
|
||||
assertEquals(Collections.emptyList(), listener.postDetachReferences);
|
||||
assertEquals(Collections.emptyList(), listener.postDestroyViewReferences);
|
||||
assertEquals(Collections.emptyList(), listener.postDestroyReferences);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleControllerActivityOnPop() {
|
||||
Controller controller = new TestController();
|
||||
|
||||
ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener();
|
||||
controller.addLifecycleListener(listener);
|
||||
|
||||
router.pushController(RouterTransaction.with(controller)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
router.popCurrentController();
|
||||
|
||||
assertEquals(Arrays.asList(true, true), listener.changeEndReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postCreateViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postAttachReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDetachReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDestroyViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDestroyReferences);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildControllerActivityOnPop() {
|
||||
Controller parent = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(parent)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
TestController child = new TestController();
|
||||
|
||||
ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener();
|
||||
child.addLifecycleListener(listener);
|
||||
|
||||
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter.setPopsLastView(true);
|
||||
childRouter.pushController(RouterTransaction.with(child)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
childRouter.popCurrentController();
|
||||
|
||||
assertEquals(Arrays.asList(true, true), listener.changeEndReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postCreateViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postAttachReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDetachReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDestroyViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDestroyReferences);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildControllerActivityOnParentPop() {
|
||||
Controller parent = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(parent)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
TestController child = new TestController();
|
||||
|
||||
ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener();
|
||||
child.addLifecycleListener(listener);
|
||||
|
||||
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter.setPopsLastView(true);
|
||||
childRouter.pushController(RouterTransaction.with(child)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
router.popCurrentController();
|
||||
|
||||
assertEquals(Collections.singletonList(true), listener.changeEndReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postCreateViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postAttachReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDetachReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDestroyViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDestroyReferences);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleControllerActivityOnDestroy() {
|
||||
Controller controller = new TestController();
|
||||
|
||||
ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener();
|
||||
controller.addLifecycleListener(listener);
|
||||
|
||||
router.pushController(RouterTransaction.with(controller)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
activityProxy.pause().stop(false).destroy();
|
||||
|
||||
assertEquals(Collections.singletonList(true), listener.changeEndReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postCreateViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postAttachReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDetachReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDestroyViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDestroyReferences);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildControllerActivityOnDestroy() {
|
||||
Controller parent = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(parent)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
TestController child = new TestController();
|
||||
|
||||
ActivityReferencingLifecycleListener listener = new ActivityReferencingLifecycleListener();
|
||||
child.addLifecycleListener(listener);
|
||||
|
||||
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter.setPopsLastView(true);
|
||||
childRouter.pushController(RouterTransaction.with(child)
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
activityProxy.pause().stop(false).destroy();
|
||||
|
||||
assertEquals(Collections.singletonList(true), listener.changeEndReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postCreateViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postAttachReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDetachReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDestroyViewReferences);
|
||||
assertEquals(Collections.singletonList(true), listener.postDestroyReferences);
|
||||
}
|
||||
|
||||
static class ActivityReferencingLifecycleListener extends Controller.LifecycleListener {
|
||||
final List<Boolean> changeEndReferences = new ArrayList<>();
|
||||
final List<Boolean> postCreateViewReferences = new ArrayList<>();
|
||||
final List<Boolean> postAttachReferences = new ArrayList<>();
|
||||
final List<Boolean> postDetachReferences = new ArrayList<>();
|
||||
final List<Boolean> postDestroyViewReferences = new ArrayList<>();
|
||||
final List<Boolean> postDestroyReferences = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
|
||||
changeEndReferences.add(controller.getActivity() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postCreateView(@NonNull Controller controller, @NonNull View view) {
|
||||
postCreateViewReferences.add(controller.getActivity() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postAttach(@NonNull Controller controller, @NonNull View view) {
|
||||
postAttachReferences.add(controller.getActivity() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDetach(@NonNull Controller controller, @NonNull View view) {
|
||||
postDetachReferences.add(controller.getActivity() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDestroyView(@NonNull Controller controller) {
|
||||
postDestroyViewReferences.add(controller.getActivity() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDestroy(@NonNull Controller controller) {
|
||||
postDestroyReferences.add(controller.getActivity() != null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,18 +6,26 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller.LifecycleListener;
|
||||
import com.bluelinelabs.conductor.MockChangeHandler.ChangeHandlerListener;
|
||||
import com.bluelinelabs.conductor.util.ActivityProxy;
|
||||
import com.bluelinelabs.conductor.util.CallState;
|
||||
import com.bluelinelabs.conductor.util.MockChangeHandler;
|
||||
import com.bluelinelabs.conductor.util.MockChangeHandler.ChangeHandlerListener;
|
||||
import com.bluelinelabs.conductor.util.TestController;
|
||||
import com.bluelinelabs.conductor.util.ViewUtils;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class ControllerLifecycleTests {
|
||||
public class ControllerLifecycleCallbacksTests {
|
||||
|
||||
private Router router;
|
||||
|
||||
@@ -60,11 +68,42 @@ public class ControllerLifecycleTests {
|
||||
|
||||
router.popCurrentController();
|
||||
|
||||
Assert.assertNull(controller.getView());
|
||||
assertNull(controller.getView());
|
||||
|
||||
assertCalls(expectedCallState, controller);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLifecycleWithActivityStop() {
|
||||
TestController controller = new TestController();
|
||||
attachLifecycleListener(controller);
|
||||
|
||||
CallState expectedCallState = new CallState();
|
||||
|
||||
assertCalls(expectedCallState, controller);
|
||||
router.pushController(RouterTransaction.with(controller)
|
||||
.pushChangeHandler(getPushHandler(expectedCallState, controller)));
|
||||
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
activityProxy.getActivity().isDestroying = true;
|
||||
activityProxy.pause();
|
||||
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
activityProxy.stop(false);
|
||||
|
||||
expectedCallState.detachCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
assertNotNull(controller.getView());
|
||||
ViewUtils.reportAttached(controller.getView(), false);
|
||||
|
||||
expectedCallState.saveViewStateCalls++;
|
||||
expectedCallState.destroyViewCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLifecycleWithActivityDestroy() {
|
||||
TestController controller = new TestController();
|
||||
@@ -83,7 +122,7 @@ public class ControllerLifecycleTests {
|
||||
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
activityProxy.stop();
|
||||
activityProxy.stop(true);
|
||||
|
||||
expectedCallState.saveViewStateCalls++;
|
||||
expectedCallState.detachCalls++;
|
||||
@@ -122,7 +161,7 @@ public class ControllerLifecycleTests {
|
||||
activityProxy.pause();
|
||||
assertCalls(expectedCallState, controller);
|
||||
|
||||
activityProxy.stop();
|
||||
activityProxy.stop(true);
|
||||
expectedCallState.detachCalls++;
|
||||
expectedCallState.destroyViewCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
@@ -194,212 +233,212 @@ public class ControllerLifecycleTests {
|
||||
@Override
|
||||
public void preCreateView(@NonNull Controller controller) {
|
||||
callState.createViewCalls++;
|
||||
Assert.assertEquals(1, callState.createViewCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.createViewCalls);
|
||||
assertEquals(1, callState.createViewCalls);
|
||||
assertEquals(0, testController.currentCallState.createViewCalls);
|
||||
|
||||
Assert.assertEquals(0, callState.attachCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.attachCalls);
|
||||
assertEquals(0, callState.attachCalls);
|
||||
assertEquals(0, testController.currentCallState.attachCalls);
|
||||
|
||||
Assert.assertEquals(0, callState.detachCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.detachCalls);
|
||||
assertEquals(0, callState.detachCalls);
|
||||
assertEquals(0, testController.currentCallState.detachCalls);
|
||||
|
||||
Assert.assertEquals(0, callState.destroyViewCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls);
|
||||
assertEquals(0, callState.destroyViewCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyViewCalls);
|
||||
|
||||
Assert.assertEquals(0, callState.destroyCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
assertEquals(0, callState.destroyCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postCreateView(@NonNull Controller controller, @NonNull View view) {
|
||||
callState.createViewCalls++;
|
||||
Assert.assertEquals(2, callState.createViewCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
assertEquals(2, callState.createViewCalls);
|
||||
assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
|
||||
Assert.assertEquals(0, callState.attachCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.attachCalls);
|
||||
assertEquals(0, callState.attachCalls);
|
||||
assertEquals(0, testController.currentCallState.attachCalls);
|
||||
|
||||
Assert.assertEquals(0, callState.detachCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.detachCalls);
|
||||
assertEquals(0, callState.detachCalls);
|
||||
assertEquals(0, testController.currentCallState.detachCalls);
|
||||
|
||||
Assert.assertEquals(0, callState.destroyViewCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls);
|
||||
assertEquals(0, callState.destroyViewCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyViewCalls);
|
||||
|
||||
Assert.assertEquals(0, callState.destroyCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
assertEquals(0, callState.destroyCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preAttach(@NonNull Controller controller, @NonNull View view) {
|
||||
callState.attachCalls++;
|
||||
Assert.assertEquals(2, callState.createViewCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
assertEquals(2, callState.createViewCalls);
|
||||
assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
|
||||
Assert.assertEquals(1, callState.attachCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.attachCalls);
|
||||
assertEquals(1, callState.attachCalls);
|
||||
assertEquals(0, testController.currentCallState.attachCalls);
|
||||
|
||||
Assert.assertEquals(0, callState.detachCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.detachCalls);
|
||||
assertEquals(0, callState.detachCalls);
|
||||
assertEquals(0, testController.currentCallState.detachCalls);
|
||||
|
||||
Assert.assertEquals(0, callState.destroyViewCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls);
|
||||
assertEquals(0, callState.destroyViewCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyViewCalls);
|
||||
|
||||
Assert.assertEquals(0, callState.destroyCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
assertEquals(0, callState.destroyCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postAttach(@NonNull Controller controller, @NonNull View view) {
|
||||
callState.attachCalls++;
|
||||
Assert.assertEquals(2, callState.createViewCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
assertEquals(2, callState.createViewCalls);
|
||||
assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
|
||||
Assert.assertEquals(2, callState.attachCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.attachCalls);
|
||||
assertEquals(2, callState.attachCalls);
|
||||
assertEquals(1, testController.currentCallState.attachCalls);
|
||||
|
||||
Assert.assertEquals(0, callState.detachCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.detachCalls);
|
||||
assertEquals(0, callState.detachCalls);
|
||||
assertEquals(0, testController.currentCallState.detachCalls);
|
||||
|
||||
Assert.assertEquals(0, callState.destroyViewCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls);
|
||||
assertEquals(0, callState.destroyViewCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyViewCalls);
|
||||
|
||||
Assert.assertEquals(0, callState.destroyCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
assertEquals(0, callState.destroyCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preDetach(@NonNull Controller controller, @NonNull View view) {
|
||||
callState.detachCalls++;
|
||||
Assert.assertEquals(2, callState.createViewCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
assertEquals(2, callState.createViewCalls);
|
||||
assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
|
||||
Assert.assertEquals(2, callState.attachCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.attachCalls);
|
||||
assertEquals(2, callState.attachCalls);
|
||||
assertEquals(1, testController.currentCallState.attachCalls);
|
||||
|
||||
Assert.assertEquals(1, callState.detachCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.detachCalls);
|
||||
assertEquals(1, callState.detachCalls);
|
||||
assertEquals(0, testController.currentCallState.detachCalls);
|
||||
|
||||
Assert.assertEquals(0, callState.destroyViewCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls);
|
||||
assertEquals(0, callState.destroyViewCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyViewCalls);
|
||||
|
||||
Assert.assertEquals(0, callState.destroyCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
assertEquals(0, callState.destroyCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDetach(@NonNull Controller controller, @NonNull View view) {
|
||||
callState.detachCalls++;
|
||||
Assert.assertEquals(2, callState.createViewCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
assertEquals(2, callState.createViewCalls);
|
||||
assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
|
||||
Assert.assertEquals(2, callState.attachCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.attachCalls);
|
||||
assertEquals(2, callState.attachCalls);
|
||||
assertEquals(1, testController.currentCallState.attachCalls);
|
||||
|
||||
Assert.assertEquals(2, callState.detachCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.detachCalls);
|
||||
assertEquals(2, callState.detachCalls);
|
||||
assertEquals(1, testController.currentCallState.detachCalls);
|
||||
|
||||
Assert.assertEquals(0, callState.destroyViewCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls);
|
||||
assertEquals(0, callState.destroyViewCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyViewCalls);
|
||||
|
||||
Assert.assertEquals(0, callState.destroyCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
assertEquals(0, callState.destroyCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preDestroyView(@NonNull Controller controller, @NonNull View view) {
|
||||
callState.destroyViewCalls++;
|
||||
Assert.assertEquals(2, callState.createViewCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
assertEquals(2, callState.createViewCalls);
|
||||
assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
|
||||
Assert.assertEquals(2, callState.attachCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.attachCalls);
|
||||
assertEquals(2, callState.attachCalls);
|
||||
assertEquals(1, testController.currentCallState.attachCalls);
|
||||
|
||||
Assert.assertEquals(2, callState.detachCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.detachCalls);
|
||||
assertEquals(2, callState.detachCalls);
|
||||
assertEquals(1, testController.currentCallState.detachCalls);
|
||||
|
||||
Assert.assertEquals(1, callState.destroyViewCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyViewCalls);
|
||||
assertEquals(1, callState.destroyViewCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyViewCalls);
|
||||
|
||||
Assert.assertEquals(0, callState.destroyCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
assertEquals(0, callState.destroyCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDestroyView(@NonNull Controller controller) {
|
||||
callState.destroyViewCalls++;
|
||||
Assert.assertEquals(2, callState.createViewCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
assertEquals(2, callState.createViewCalls);
|
||||
assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
|
||||
Assert.assertEquals(2, callState.attachCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.attachCalls);
|
||||
assertEquals(2, callState.attachCalls);
|
||||
assertEquals(1, testController.currentCallState.attachCalls);
|
||||
|
||||
Assert.assertEquals(2, callState.detachCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.detachCalls);
|
||||
assertEquals(2, callState.detachCalls);
|
||||
assertEquals(1, testController.currentCallState.detachCalls);
|
||||
|
||||
Assert.assertEquals(2, callState.destroyViewCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.destroyViewCalls);
|
||||
assertEquals(2, callState.destroyViewCalls);
|
||||
assertEquals(1, testController.currentCallState.destroyViewCalls);
|
||||
|
||||
Assert.assertEquals(0, callState.destroyCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
assertEquals(0, callState.destroyCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preDestroy(@NonNull Controller controller) {
|
||||
callState.destroyCalls++;
|
||||
Assert.assertEquals(2, callState.createViewCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
assertEquals(2, callState.createViewCalls);
|
||||
assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
|
||||
Assert.assertEquals(2, callState.attachCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.attachCalls);
|
||||
assertEquals(2, callState.attachCalls);
|
||||
assertEquals(1, testController.currentCallState.attachCalls);
|
||||
|
||||
Assert.assertEquals(2, callState.detachCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.detachCalls);
|
||||
assertEquals(2, callState.detachCalls);
|
||||
assertEquals(1, testController.currentCallState.detachCalls);
|
||||
|
||||
Assert.assertEquals(2, callState.destroyViewCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.destroyViewCalls);
|
||||
assertEquals(2, callState.destroyViewCalls);
|
||||
assertEquals(1, testController.currentCallState.destroyViewCalls);
|
||||
|
||||
Assert.assertEquals(1, callState.destroyCalls);
|
||||
Assert.assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
assertEquals(1, callState.destroyCalls);
|
||||
assertEquals(0, testController.currentCallState.destroyCalls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDestroy(@NonNull Controller controller) {
|
||||
callState.destroyCalls++;
|
||||
Assert.assertEquals(2, callState.createViewCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
assertEquals(2, callState.createViewCalls);
|
||||
assertEquals(1, testController.currentCallState.createViewCalls);
|
||||
|
||||
Assert.assertEquals(2, callState.attachCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.attachCalls);
|
||||
assertEquals(2, callState.attachCalls);
|
||||
assertEquals(1, testController.currentCallState.attachCalls);
|
||||
|
||||
Assert.assertEquals(2, callState.detachCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.detachCalls);
|
||||
assertEquals(2, callState.detachCalls);
|
||||
assertEquals(1, testController.currentCallState.detachCalls);
|
||||
|
||||
Assert.assertEquals(2, callState.destroyViewCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.destroyViewCalls);
|
||||
assertEquals(2, callState.destroyViewCalls);
|
||||
assertEquals(1, testController.currentCallState.destroyViewCalls);
|
||||
|
||||
Assert.assertEquals(2, callState.destroyCalls);
|
||||
Assert.assertEquals(1, testController.currentCallState.destroyCalls);
|
||||
assertEquals(2, callState.destroyCalls);
|
||||
assertEquals(1, testController.currentCallState.destroyCalls);
|
||||
}
|
||||
});
|
||||
|
||||
router.pushController(RouterTransaction.with(testController)
|
||||
.pushChangeHandler(new MockChangeHandler())
|
||||
.popChangeHandler(new MockChangeHandler()));
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
router.popController(testController);
|
||||
|
||||
Assert.assertEquals(2, callState.createViewCalls);
|
||||
Assert.assertEquals(2, callState.attachCalls);
|
||||
Assert.assertEquals(2, callState.detachCalls);
|
||||
Assert.assertEquals(2, callState.destroyViewCalls);
|
||||
Assert.assertEquals(2, callState.destroyCalls);
|
||||
assertEquals(2, callState.createViewCalls);
|
||||
assertEquals(2, callState.attachCalls);
|
||||
assertEquals(2, callState.detachCalls);
|
||||
assertEquals(2, callState.destroyViewCalls);
|
||||
assertEquals(2, callState.destroyCalls);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildLifecycle() {
|
||||
Controller parent = new TestController();
|
||||
router.pushController(RouterTransaction.with(parent)
|
||||
.pushChangeHandler(new MockChangeHandler()));
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
TestController child = new TestController();
|
||||
attachLifecycleListener(child);
|
||||
@@ -425,8 +464,8 @@ public class ControllerLifecycleTests {
|
||||
public void testChildLifecycle2() {
|
||||
Controller parent = new TestController();
|
||||
router.pushController(RouterTransaction.with(parent)
|
||||
.pushChangeHandler(new MockChangeHandler())
|
||||
.popChangeHandler(new MockChangeHandler()));
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
TestController child = new TestController();
|
||||
attachLifecycleListener(child);
|
||||
@@ -445,26 +484,30 @@ public class ControllerLifecycleTests {
|
||||
|
||||
router.popCurrentController();
|
||||
|
||||
expectedCallState.detachCalls++;
|
||||
expectedCallState.destroyViewCalls++;
|
||||
expectedCallState.destroyCalls++;
|
||||
|
||||
assertCalls(expectedCallState, child);
|
||||
}
|
||||
|
||||
private MockChangeHandler getPushHandler(final CallState expectedCallState, final TestController controller) {
|
||||
return new MockChangeHandler(new ChangeHandlerListener() {
|
||||
return MockChangeHandler.listeningChangeHandler(new ChangeHandlerListener() {
|
||||
@Override
|
||||
void willStartChange() {
|
||||
public void willStartChange() {
|
||||
expectedCallState.changeStartCalls++;
|
||||
expectedCallState.createViewCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
void didAttachOrDetach() {
|
||||
public void didAttachOrDetach() {
|
||||
expectedCallState.attachCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
void didEndChange() {
|
||||
public void didEndChange() {
|
||||
expectedCallState.changeEndCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
}
|
||||
@@ -472,15 +515,15 @@ public class ControllerLifecycleTests {
|
||||
}
|
||||
|
||||
private MockChangeHandler getPopHandler(final CallState expectedCallState, final TestController controller) {
|
||||
return new MockChangeHandler(new ChangeHandlerListener() {
|
||||
return MockChangeHandler.listeningChangeHandler(new ChangeHandlerListener() {
|
||||
@Override
|
||||
void willStartChange() {
|
||||
public void willStartChange() {
|
||||
expectedCallState.changeStartCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
void didAttachOrDetach() {
|
||||
public void didAttachOrDetach() {
|
||||
expectedCallState.destroyViewCalls++;
|
||||
expectedCallState.detachCalls++;
|
||||
expectedCallState.destroyCalls++;
|
||||
@@ -488,7 +531,7 @@ public class ControllerLifecycleTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
void didEndChange() {
|
||||
public void didEndChange() {
|
||||
expectedCallState.changeEndCalls++;
|
||||
assertCalls(expectedCallState, controller);
|
||||
}
|
||||
@@ -496,8 +539,8 @@ public class ControllerLifecycleTests {
|
||||
}
|
||||
|
||||
private void assertCalls(CallState callState, TestController controller) {
|
||||
Assert.assertEquals("Expected call counts and controller call counts do not match.", callState, controller.currentCallState);
|
||||
Assert.assertEquals("Expected call counts and lifecycle call counts do not match.", callState, currentCallState);
|
||||
assertEquals("Expected call counts and controller call counts do not match.", callState, controller.currentCallState);
|
||||
assertEquals("Expected call counts and lifecycle call counts do not match.", callState, currentCallState);
|
||||
}
|
||||
|
||||
private void attachLifecycleListener(Controller controller) {
|
||||
@@ -7,14 +7,22 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller.RetainViewMode;
|
||||
import com.bluelinelabs.conductor.util.ActivityProxy;
|
||||
import com.bluelinelabs.conductor.util.CallState;
|
||||
import com.bluelinelabs.conductor.util.TestController;
|
||||
import com.bluelinelabs.conductor.util.ViewUtils;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class ControllerTests {
|
||||
@@ -42,26 +50,26 @@ public class ControllerTests {
|
||||
|
||||
// Test View getting released w/ RELEASE_DETACH
|
||||
controller.setRetainViewMode(RetainViewMode.RELEASE_DETACH);
|
||||
Assert.assertNull(controller.getView());
|
||||
assertNull(controller.getView());
|
||||
View view = controller.inflate(router.container);
|
||||
Assert.assertNotNull(controller.getView());
|
||||
assertNotNull(controller.getView());
|
||||
ViewUtils.reportAttached(view, true);
|
||||
Assert.assertNotNull(controller.getView());
|
||||
assertNotNull(controller.getView());
|
||||
ViewUtils.reportAttached(view, false);
|
||||
Assert.assertNull(controller.getView());
|
||||
assertNull(controller.getView());
|
||||
|
||||
// Test View getting retained w/ RETAIN_DETACH
|
||||
controller.setRetainViewMode(RetainViewMode.RETAIN_DETACH);
|
||||
view = controller.inflate(router.container);
|
||||
Assert.assertNotNull(controller.getView());
|
||||
assertNotNull(controller.getView());
|
||||
ViewUtils.reportAttached(view, true);
|
||||
Assert.assertNotNull(controller.getView());
|
||||
assertNotNull(controller.getView());
|
||||
ViewUtils.reportAttached(view, false);
|
||||
Assert.assertNotNull(controller.getView());
|
||||
assertNotNull(controller.getView());
|
||||
|
||||
// Ensure re-setting RELEASE_DETACH releases
|
||||
controller.setRetainViewMode(RetainViewMode.RELEASE_DETACH);
|
||||
Assert.assertNull(controller.getView());
|
||||
assertNull(controller.getView());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -263,48 +271,48 @@ public class ControllerTests {
|
||||
|
||||
router.pushController(RouterTransaction.with(parent));
|
||||
|
||||
Assert.assertEquals(0, parent.getChildRouters().size());
|
||||
Assert.assertNull(child1.getParentController());
|
||||
Assert.assertNull(child2.getParentController());
|
||||
assertEquals(0, parent.getChildRouters().size());
|
||||
assertNull(child1.getParentController());
|
||||
assertNull(child2.getParentController());
|
||||
|
||||
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter.setPopsLastView(true);
|
||||
childRouter.setRoot(RouterTransaction.with(child1));
|
||||
|
||||
Assert.assertEquals(1, parent.getChildRouters().size());
|
||||
Assert.assertEquals(childRouter, parent.getChildRouters().get(0));
|
||||
Assert.assertEquals(1, childRouter.getBackstackSize());
|
||||
Assert.assertEquals(child1, childRouter.getControllers().get(0));
|
||||
Assert.assertEquals(parent, child1.getParentController());
|
||||
Assert.assertNull(child2.getParentController());
|
||||
assertEquals(1, parent.getChildRouters().size());
|
||||
assertEquals(childRouter, parent.getChildRouters().get(0));
|
||||
assertEquals(1, childRouter.getBackstackSize());
|
||||
assertEquals(child1, childRouter.getControllers().get(0));
|
||||
assertEquals(parent, child1.getParentController());
|
||||
assertNull(child2.getParentController());
|
||||
|
||||
childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter.pushController(RouterTransaction.with(child2));
|
||||
|
||||
Assert.assertEquals(1, parent.getChildRouters().size());
|
||||
Assert.assertEquals(childRouter, parent.getChildRouters().get(0));
|
||||
Assert.assertEquals(2, childRouter.getBackstackSize());
|
||||
Assert.assertEquals(child1, childRouter.getControllers().get(0));
|
||||
Assert.assertEquals(child2, childRouter.getControllers().get(1));
|
||||
Assert.assertEquals(parent, child1.getParentController());
|
||||
Assert.assertEquals(parent, child2.getParentController());
|
||||
assertEquals(1, parent.getChildRouters().size());
|
||||
assertEquals(childRouter, parent.getChildRouters().get(0));
|
||||
assertEquals(2, childRouter.getBackstackSize());
|
||||
assertEquals(child1, childRouter.getControllers().get(0));
|
||||
assertEquals(child2, childRouter.getControllers().get(1));
|
||||
assertEquals(parent, child1.getParentController());
|
||||
assertEquals(parent, child2.getParentController());
|
||||
|
||||
childRouter.popController(child2);
|
||||
|
||||
Assert.assertEquals(1, parent.getChildRouters().size());
|
||||
Assert.assertEquals(childRouter, parent.getChildRouters().get(0));
|
||||
Assert.assertEquals(1, childRouter.getBackstackSize());
|
||||
Assert.assertEquals(child1, childRouter.getControllers().get(0));
|
||||
Assert.assertEquals(parent, child1.getParentController());
|
||||
Assert.assertNull(child2.getParentController());
|
||||
assertEquals(1, parent.getChildRouters().size());
|
||||
assertEquals(childRouter, parent.getChildRouters().get(0));
|
||||
assertEquals(1, childRouter.getBackstackSize());
|
||||
assertEquals(child1, childRouter.getControllers().get(0));
|
||||
assertEquals(parent, child1.getParentController());
|
||||
assertNull(child2.getParentController());
|
||||
|
||||
childRouter.popController(child1);
|
||||
|
||||
Assert.assertEquals(1, parent.getChildRouters().size());
|
||||
Assert.assertEquals(childRouter, parent.getChildRouters().get(0));
|
||||
Assert.assertEquals(0, childRouter.getBackstackSize());
|
||||
Assert.assertNull(child1.getParentController());
|
||||
Assert.assertNull(child2.getParentController());
|
||||
assertEquals(1, parent.getChildRouters().size());
|
||||
assertEquals(childRouter, parent.getChildRouters().get(0));
|
||||
assertEquals(0, childRouter.getBackstackSize());
|
||||
assertNull(child1.getParentController());
|
||||
assertNull(child2.getParentController());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -316,9 +324,9 @@ public class ControllerTests {
|
||||
|
||||
router.pushController(RouterTransaction.with(parent));
|
||||
|
||||
Assert.assertEquals(0, parent.getChildRouters().size());
|
||||
Assert.assertNull(child1.getParentController());
|
||||
Assert.assertNull(child2.getParentController());
|
||||
assertEquals(0, parent.getChildRouters().size());
|
||||
assertNull(child1.getParentController());
|
||||
assertNull(child2.getParentController());
|
||||
|
||||
Router childRouter1 = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_1));
|
||||
Router childRouter2 = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_2));
|
||||
@@ -326,37 +334,79 @@ public class ControllerTests {
|
||||
childRouter1.setRoot(RouterTransaction.with(child1));
|
||||
childRouter2.setRoot(RouterTransaction.with(child2));
|
||||
|
||||
Assert.assertEquals(2, parent.getChildRouters().size());
|
||||
Assert.assertEquals(childRouter1, parent.getChildRouters().get(0));
|
||||
Assert.assertEquals(childRouter2, parent.getChildRouters().get(1));
|
||||
Assert.assertEquals(1, childRouter1.getBackstackSize());
|
||||
Assert.assertEquals(1, childRouter2.getBackstackSize());
|
||||
Assert.assertEquals(child1, childRouter1.getControllers().get(0));
|
||||
Assert.assertEquals(child2, childRouter2.getControllers().get(0));
|
||||
Assert.assertEquals(parent, child1.getParentController());
|
||||
Assert.assertEquals(parent, child2.getParentController());
|
||||
assertEquals(2, parent.getChildRouters().size());
|
||||
assertEquals(childRouter1, parent.getChildRouters().get(0));
|
||||
assertEquals(childRouter2, parent.getChildRouters().get(1));
|
||||
assertEquals(1, childRouter1.getBackstackSize());
|
||||
assertEquals(1, childRouter2.getBackstackSize());
|
||||
assertEquals(child1, childRouter1.getControllers().get(0));
|
||||
assertEquals(child2, childRouter2.getControllers().get(0));
|
||||
assertEquals(parent, child1.getParentController());
|
||||
assertEquals(parent, child2.getParentController());
|
||||
|
||||
parent.removeChildRouter(childRouter2);
|
||||
|
||||
Assert.assertEquals(1, parent.getChildRouters().size());
|
||||
Assert.assertEquals(childRouter1, parent.getChildRouters().get(0));
|
||||
Assert.assertEquals(1, childRouter1.getBackstackSize());
|
||||
Assert.assertEquals(0, childRouter2.getBackstackSize());
|
||||
Assert.assertEquals(child1, childRouter1.getControllers().get(0));
|
||||
Assert.assertEquals(parent, child1.getParentController());
|
||||
Assert.assertNull(child2.getParentController());
|
||||
assertEquals(1, parent.getChildRouters().size());
|
||||
assertEquals(childRouter1, parent.getChildRouters().get(0));
|
||||
assertEquals(1, childRouter1.getBackstackSize());
|
||||
assertEquals(0, childRouter2.getBackstackSize());
|
||||
assertEquals(child1, childRouter1.getControllers().get(0));
|
||||
assertEquals(parent, child1.getParentController());
|
||||
assertNull(child2.getParentController());
|
||||
|
||||
parent.removeChildRouter(childRouter1);
|
||||
|
||||
Assert.assertEquals(0, parent.getChildRouters().size());
|
||||
Assert.assertEquals(0, childRouter1.getBackstackSize());
|
||||
Assert.assertEquals(0, childRouter2.getBackstackSize());
|
||||
Assert.assertNull(child1.getParentController());
|
||||
Assert.assertNull(child2.getParentController());
|
||||
assertEquals(0, parent.getChildRouters().size());
|
||||
assertEquals(0, childRouter1.getBackstackSize());
|
||||
assertEquals(0, childRouter2.getBackstackSize());
|
||||
assertNull(child1.getParentController());
|
||||
assertNull(child2.getParentController());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestoredChildRouterBackstack() {
|
||||
TestController parent = new TestController();
|
||||
router.pushController(RouterTransaction.with(parent));
|
||||
ViewUtils.reportAttached(parent.getView(), true);
|
||||
|
||||
RouterTransaction childTransaction1 = RouterTransaction.with(new TestController());
|
||||
RouterTransaction childTransaction2 = RouterTransaction.with(new TestController());
|
||||
|
||||
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_1));
|
||||
childRouter.setPopsLastView(true);
|
||||
childRouter.setRoot(childTransaction1);
|
||||
childRouter.pushController(childTransaction2);
|
||||
|
||||
Bundle savedState = new Bundle();
|
||||
childRouter.saveInstanceState(savedState);
|
||||
parent.removeChildRouter(childRouter);
|
||||
|
||||
childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_1));
|
||||
assertEquals(0, childRouter.getBackstackSize());
|
||||
|
||||
childRouter.restoreInstanceState(savedState);
|
||||
childRouter.rebindIfNeeded();
|
||||
|
||||
assertEquals(2, childRouter.getBackstackSize());
|
||||
|
||||
RouterTransaction restoredChildTransaction1 = childRouter.getBackstack().get(0);
|
||||
RouterTransaction restoredChildTransaction2 = childRouter.getBackstack().get(1);
|
||||
|
||||
assertEquals(childTransaction1.transactionIndex, restoredChildTransaction1.transactionIndex);
|
||||
assertEquals(childTransaction1.controller.getInstanceId(), restoredChildTransaction1.controller.getInstanceId());
|
||||
assertEquals(childTransaction2.transactionIndex, restoredChildTransaction2.transactionIndex);
|
||||
assertEquals(childTransaction2.controller.getInstanceId(), restoredChildTransaction2.controller.getInstanceId());
|
||||
|
||||
assertTrue(parent.handleBack());
|
||||
assertEquals(1, childRouter.getBackstackSize());
|
||||
assertEquals(restoredChildTransaction1, childRouter.getBackstack().get(0));
|
||||
|
||||
assertTrue(parent.handleBack());
|
||||
assertEquals(0, childRouter.getBackstackSize());
|
||||
}
|
||||
|
||||
private void assertCalls(CallState callState, TestController controller) {
|
||||
Assert.assertEquals("Expected call counts and controller call counts do not match.", callState, controller.currentCallState);
|
||||
assertEquals("Expected call counts and controller call counts do not match.", callState, controller.currentCallState);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@ import android.os.Bundle;
|
||||
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
|
||||
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import com.bluelinelabs.conductor.util.TestController;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class ControllerTransactionTests {
|
||||
|
||||
@Test
|
||||
@@ -22,10 +23,10 @@ public class ControllerTransactionTests {
|
||||
|
||||
RouterTransaction restoredTransaction = new RouterTransaction(bundle);
|
||||
|
||||
Assert.assertEquals(transaction.controller.getClass(), restoredTransaction.controller.getClass());
|
||||
Assert.assertEquals(transaction.pushChangeHandler().getClass(), restoredTransaction.pushChangeHandler().getClass());
|
||||
Assert.assertEquals(transaction.popChangeHandler().getClass(), restoredTransaction.popChangeHandler().getClass());
|
||||
Assert.assertEquals(transaction.tag(), restoredTransaction.tag());
|
||||
assertEquals(transaction.controller.getClass(), restoredTransaction.controller.getClass());
|
||||
assertEquals(transaction.pushChangeHandler().getClass(), restoredTransaction.pushChangeHandler().getClass());
|
||||
assertEquals(transaction.popChangeHandler().getClass(), restoredTransaction.popChangeHandler().getClass());
|
||||
assertEquals(transaction.tag(), restoredTransaction.tag());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
public class MockChangeHandler extends ControllerChangeHandler {
|
||||
|
||||
private static final String KEY_REMOVES_FROM_VIEW_ON_PUSH = "MockChangeHandler.removesFromViewOnPush";
|
||||
|
||||
static class ChangeHandlerListener {
|
||||
void willStartChange() { }
|
||||
void didAttachOrDetach() { }
|
||||
void didEndChange() { }
|
||||
}
|
||||
|
||||
final ChangeHandlerListener listener;
|
||||
boolean removesFromViewOnPush;
|
||||
|
||||
public MockChangeHandler() {
|
||||
this(true, null);
|
||||
}
|
||||
|
||||
public MockChangeHandler(boolean removesViewOnPush) {
|
||||
this(removesViewOnPush, null);
|
||||
}
|
||||
|
||||
public MockChangeHandler(@NonNull ChangeHandlerListener listener) {
|
||||
this(true, listener);
|
||||
}
|
||||
|
||||
public MockChangeHandler(boolean removesFromViewOnPush, ChangeHandlerListener listener) {
|
||||
this.removesFromViewOnPush = removesFromViewOnPush;
|
||||
|
||||
if (listener == null) {
|
||||
this.listener = new ChangeHandlerListener() { };
|
||||
} else {
|
||||
this.listener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performChange(@NonNull ViewGroup container, View from, View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
|
||||
listener.willStartChange();
|
||||
|
||||
if (isPush) {
|
||||
container.addView(to);
|
||||
listener.didAttachOrDetach();
|
||||
|
||||
if (removesFromViewOnPush && from != null) {
|
||||
container.removeView(from);
|
||||
}
|
||||
} else {
|
||||
container.removeView(from);
|
||||
listener.didAttachOrDetach();
|
||||
|
||||
if (to != null) {
|
||||
container.addView(to);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
changeListener.onChangeCompleted();
|
||||
listener.didEndChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removesFromViewOnPush() {
|
||||
return removesFromViewOnPush;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveToBundle(@NonNull Bundle bundle) {
|
||||
super.saveToBundle(bundle);
|
||||
bundle.putBoolean(KEY_REMOVES_FROM_VIEW_ON_PUSH, removesFromViewOnPush);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreFromBundle(@NonNull Bundle bundle) {
|
||||
super.restoreFromBundle(bundle);
|
||||
removesFromViewOnPush = bundle.getBoolean(KEY_REMOVES_FROM_VIEW_ON_PUSH);
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,19 @@ package com.bluelinelabs.conductor;
|
||||
import android.os.Bundle;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.junit.Assert;
|
||||
import com.bluelinelabs.conductor.util.ActivityProxy;
|
||||
import com.bluelinelabs.conductor.util.MockChangeHandler;
|
||||
import com.bluelinelabs.conductor.util.TestController;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class ReattachCaseTests {
|
||||
@@ -36,71 +42,71 @@ public class ReattachCaseTests {
|
||||
final TestController controllerB = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerA)
|
||||
.pushChangeHandler(new MockChangeHandler())
|
||||
.popChangeHandler(new MockChangeHandler()));
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
Assert.assertTrue(controllerA.isAttached());
|
||||
Assert.assertFalse(controllerB.isAttached());
|
||||
assertTrue(controllerA.isAttached());
|
||||
assertFalse(controllerB.isAttached());
|
||||
|
||||
sleepWakeDevice();
|
||||
|
||||
Assert.assertTrue(controllerA.isAttached());
|
||||
Assert.assertFalse(controllerB.isAttached());
|
||||
assertTrue(controllerA.isAttached());
|
||||
assertFalse(controllerB.isAttached());
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerB)
|
||||
.pushChangeHandler(new MockChangeHandler())
|
||||
.popChangeHandler(new MockChangeHandler()));
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
Assert.assertFalse(controllerA.isAttached());
|
||||
Assert.assertTrue(controllerB.isAttached());
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
|
||||
activityProxy.rotate();
|
||||
router.rebindIfNeeded();
|
||||
|
||||
Assert.assertFalse(controllerA.isAttached());
|
||||
Assert.assertTrue(controllerB.isAttached());
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildNeedsAttachOnPauseAndOrientation() {
|
||||
final TestController controllerA = new TestController();
|
||||
final TestController childController = new TestController();
|
||||
final TestController controllerB = new TestController();
|
||||
final Controller controllerA = new TestController();
|
||||
final Controller childController = new TestController();
|
||||
final Controller controllerB = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerA)
|
||||
.pushChangeHandler(new MockChangeHandler())
|
||||
.popChangeHandler(new MockChangeHandler()));
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
Router childRouter = controllerA.getChildRouter((ViewGroup)controllerA.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter.pushController(RouterTransaction.with(childController)
|
||||
.pushChangeHandler(new MockChangeHandler())
|
||||
.popChangeHandler(new MockChangeHandler()));
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
Assert.assertTrue(controllerA.isAttached());
|
||||
Assert.assertTrue(childController.isAttached());
|
||||
Assert.assertFalse(controllerB.isAttached());
|
||||
assertTrue(controllerA.isAttached());
|
||||
assertTrue(childController.isAttached());
|
||||
assertFalse(controllerB.isAttached());
|
||||
|
||||
sleepWakeDevice();
|
||||
|
||||
Assert.assertTrue(controllerA.isAttached());
|
||||
Assert.assertTrue(childController.isAttached());
|
||||
Assert.assertFalse(controllerB.isAttached());
|
||||
assertTrue(controllerA.isAttached());
|
||||
assertTrue(childController.isAttached());
|
||||
assertFalse(controllerB.isAttached());
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerB)
|
||||
.pushChangeHandler(new MockChangeHandler())
|
||||
.popChangeHandler(new MockChangeHandler()));
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
Assert.assertFalse(controllerA.isAttached());
|
||||
Assert.assertFalse(childController.isAttached());
|
||||
Assert.assertTrue(controllerB.isAttached());
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertFalse(childController.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
|
||||
activityProxy.rotate();
|
||||
router.rebindIfNeeded();
|
||||
|
||||
Assert.assertFalse(controllerA.isAttached());
|
||||
Assert.assertFalse(childController.isAttached());
|
||||
Assert.assertTrue(childController.getNeedsAttach());
|
||||
Assert.assertTrue(controllerB.isAttached());
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertFalse(childController.isAttached());
|
||||
assertTrue(childController.getNeedsAttach());
|
||||
assertTrue(controllerB.isAttached());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -110,45 +116,45 @@ public class ReattachCaseTests {
|
||||
final TestController childController = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerA)
|
||||
.pushChangeHandler(new MockChangeHandler())
|
||||
.popChangeHandler(new MockChangeHandler()));
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
Assert.assertTrue(controllerA.isAttached());
|
||||
Assert.assertFalse(controllerB.isAttached());
|
||||
Assert.assertFalse(childController.isAttached());
|
||||
assertTrue(controllerA.isAttached());
|
||||
assertFalse(controllerB.isAttached());
|
||||
assertFalse(childController.isAttached());
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerB)
|
||||
.pushChangeHandler(new MockChangeHandler())
|
||||
.popChangeHandler(new MockChangeHandler()));
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
Router childRouter = controllerB.getChildRouter((ViewGroup)controllerB.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter.setPopsLastView(true);
|
||||
childRouter.pushController(RouterTransaction.with(childController)
|
||||
.pushChangeHandler(new MockChangeHandler())
|
||||
.popChangeHandler(new MockChangeHandler()));
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
Assert.assertFalse(controllerA.isAttached());
|
||||
Assert.assertTrue(controllerB.isAttached());
|
||||
Assert.assertTrue(childController.isAttached());
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
assertTrue(childController.isAttached());
|
||||
|
||||
activityProxy.rotate();
|
||||
router.rebindIfNeeded();
|
||||
|
||||
Assert.assertFalse(controllerA.isAttached());
|
||||
Assert.assertTrue(controllerB.isAttached());
|
||||
Assert.assertTrue(childController.isAttached());
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
assertTrue(childController.isAttached());
|
||||
|
||||
router.handleBack();
|
||||
|
||||
Assert.assertFalse(controllerA.isAttached());
|
||||
Assert.assertTrue(controllerB.isAttached());
|
||||
Assert.assertFalse(childController.isAttached());
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
assertFalse(childController.isAttached());
|
||||
|
||||
router.handleBack();
|
||||
|
||||
Assert.assertTrue(controllerA.isAttached());
|
||||
Assert.assertFalse(controllerB.isAttached());
|
||||
Assert.assertFalse(childController.isAttached());
|
||||
assertTrue(controllerA.isAttached());
|
||||
assertFalse(controllerB.isAttached());
|
||||
assertFalse(childController.isAttached());
|
||||
}
|
||||
|
||||
// Attempt to test https://github.com/bluelinelabs/Conductor/issues/86#issuecomment-231381271
|
||||
@@ -159,71 +165,71 @@ public class ReattachCaseTests {
|
||||
TestController childController = new TestController();
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerA)
|
||||
.pushChangeHandler(new MockChangeHandler())
|
||||
.popChangeHandler(new MockChangeHandler()));
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
Assert.assertTrue(controllerA.isAttached());
|
||||
Assert.assertFalse(controllerB.isAttached());
|
||||
Assert.assertFalse(childController.isAttached());
|
||||
assertTrue(controllerA.isAttached());
|
||||
assertFalse(controllerB.isAttached());
|
||||
assertFalse(childController.isAttached());
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerB)
|
||||
.pushChangeHandler(new MockChangeHandler())
|
||||
.popChangeHandler(new MockChangeHandler()));
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
Router childRouter = controllerB.getChildRouter((ViewGroup)controllerB.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter.setPopsLastView(true);
|
||||
childRouter.pushController(RouterTransaction.with(childController)
|
||||
.pushChangeHandler(new MockChangeHandler())
|
||||
.popChangeHandler(new MockChangeHandler()));
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
Assert.assertFalse(controllerA.isAttached());
|
||||
Assert.assertTrue(controllerB.isAttached());
|
||||
Assert.assertTrue(childController.isAttached());
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
assertTrue(childController.isAttached());
|
||||
|
||||
router.handleBack();
|
||||
|
||||
Assert.assertFalse(controllerA.isAttached());
|
||||
Assert.assertTrue(controllerB.isAttached());
|
||||
Assert.assertFalse(childController.isAttached());
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
assertFalse(childController.isAttached());
|
||||
|
||||
childController = new TestController();
|
||||
childRouter.pushController(RouterTransaction.with(childController)
|
||||
.pushChangeHandler(new MockChangeHandler())
|
||||
.popChangeHandler(new MockChangeHandler()));
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
Assert.assertFalse(controllerA.isAttached());
|
||||
Assert.assertTrue(controllerB.isAttached());
|
||||
Assert.assertTrue(childController.isAttached());
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
assertTrue(childController.isAttached());
|
||||
|
||||
activityProxy.rotate();
|
||||
router.rebindIfNeeded();
|
||||
|
||||
Assert.assertFalse(controllerA.isAttached());
|
||||
Assert.assertTrue(controllerB.isAttached());
|
||||
Assert.assertTrue(childController.isAttached());
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
assertTrue(childController.isAttached());
|
||||
|
||||
router.handleBack();
|
||||
|
||||
childController = new TestController();
|
||||
childRouter.pushController(RouterTransaction.with(childController)
|
||||
.pushChangeHandler(new MockChangeHandler())
|
||||
.popChangeHandler(new MockChangeHandler()));
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
Assert.assertFalse(controllerA.isAttached());
|
||||
Assert.assertTrue(controllerB.isAttached());
|
||||
Assert.assertTrue(childController.isAttached());
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
assertTrue(childController.isAttached());
|
||||
|
||||
router.handleBack();
|
||||
|
||||
Assert.assertFalse(controllerA.isAttached());
|
||||
Assert.assertTrue(controllerB.isAttached());
|
||||
Assert.assertFalse(childController.isAttached());
|
||||
assertFalse(controllerA.isAttached());
|
||||
assertTrue(controllerB.isAttached());
|
||||
assertFalse(childController.isAttached());
|
||||
|
||||
router.handleBack();
|
||||
|
||||
Assert.assertTrue(controllerA.isAttached());
|
||||
Assert.assertFalse(controllerB.isAttached());
|
||||
Assert.assertFalse(childController.isAttached());
|
||||
assertTrue(controllerA.isAttached());
|
||||
assertFalse(controllerB.isAttached());
|
||||
assertFalse(childController.isAttached());
|
||||
}
|
||||
|
||||
private void sleepWakeDevice() {
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.bluelinelabs.conductor.util.ActivityProxy;
|
||||
import com.bluelinelabs.conductor.util.MockChangeHandler;
|
||||
import com.bluelinelabs.conductor.util.TestController;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class RouterChangeHandlerTests {
|
||||
|
||||
private Router router;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
ActivityProxy activityProxy = new ActivityProxy().create(null).start().resume();
|
||||
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetRootHandler() {
|
||||
MockChangeHandler handler = MockChangeHandler.taggedHandler("root", true);
|
||||
TestController rootController = new TestController();
|
||||
router.setRoot(RouterTransaction.with(rootController).pushChangeHandler(handler));
|
||||
|
||||
assertTrue(rootController.changeHandlerHistory.isValidHistory);
|
||||
assertNull(rootController.changeHandlerHistory.latestFromView());
|
||||
assertNotNull(rootController.changeHandlerHistory.latestToView());
|
||||
assertEquals(rootController.getView(), rootController.changeHandlerHistory.latestToView());
|
||||
assertTrue(rootController.changeHandlerHistory.latestIsPush());
|
||||
assertEquals(handler.tag, rootController.changeHandlerHistory.latestChangeHandler().tag);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPushPopHandlers() {
|
||||
TestController rootController = new TestController();
|
||||
router.setRoot(RouterTransaction.with(rootController).pushChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
View rootView = rootController.getView();
|
||||
|
||||
MockChangeHandler pushHandler = MockChangeHandler.taggedHandler("push", true);
|
||||
MockChangeHandler popHandler = MockChangeHandler.taggedHandler("pop", true);
|
||||
TestController pushController = new TestController();
|
||||
router.pushController(RouterTransaction.with(pushController).pushChangeHandler(pushHandler).popChangeHandler(popHandler));
|
||||
|
||||
assertTrue(rootController.changeHandlerHistory.isValidHistory);
|
||||
assertTrue(pushController.changeHandlerHistory.isValidHistory);
|
||||
|
||||
assertNotNull(pushController.changeHandlerHistory.latestFromView());
|
||||
assertNotNull(pushController.changeHandlerHistory.latestToView());
|
||||
assertEquals(rootView, pushController.changeHandlerHistory.latestFromView());
|
||||
assertEquals(pushController.getView(), pushController.changeHandlerHistory.latestToView());
|
||||
assertTrue(pushController.changeHandlerHistory.latestIsPush());
|
||||
assertEquals(pushHandler.tag, pushController.changeHandlerHistory.latestChangeHandler().tag);
|
||||
|
||||
View pushView = pushController.getView();
|
||||
router.popController(pushController);
|
||||
|
||||
assertNotNull(pushController.changeHandlerHistory.latestFromView());
|
||||
assertNotNull(pushController.changeHandlerHistory.latestToView());
|
||||
assertEquals(pushView, pushController.changeHandlerHistory.fromViewAt(1));
|
||||
assertEquals(rootController.getView(), pushController.changeHandlerHistory.latestToView());
|
||||
assertFalse(pushController.changeHandlerHistory.latestIsPush());
|
||||
assertEquals(popHandler.tag, pushController.changeHandlerHistory.latestChangeHandler().tag);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResetRootHandlers() {
|
||||
TestController initialController1 = new TestController();
|
||||
MockChangeHandler initialPushHandler1 = MockChangeHandler.taggedHandler("initialPush1", true);
|
||||
MockChangeHandler initialPopHandler1 = MockChangeHandler.taggedHandler("initialPop1", true);
|
||||
router.setRoot(RouterTransaction.with(initialController1).pushChangeHandler(initialPushHandler1).popChangeHandler(initialPopHandler1));
|
||||
TestController initialController2 = new TestController();
|
||||
MockChangeHandler initialPushHandler2 = MockChangeHandler.taggedHandler("initialPush2", false);
|
||||
MockChangeHandler initialPopHandler2 = MockChangeHandler.taggedHandler("initialPop2", false);
|
||||
router.pushController(RouterTransaction.with(initialController2).pushChangeHandler(initialPushHandler2).popChangeHandler(initialPopHandler2));
|
||||
|
||||
View initialView1 = initialController1.getView();
|
||||
View initialView2 = initialController2.getView();
|
||||
|
||||
TestController newRootController = new TestController();
|
||||
MockChangeHandler newRootHandler = MockChangeHandler.taggedHandler("newRootHandler", true);
|
||||
|
||||
router.setRoot(RouterTransaction.with(newRootController).pushChangeHandler(newRootHandler));
|
||||
|
||||
assertTrue(initialController1.changeHandlerHistory.isValidHistory);
|
||||
assertTrue(initialController2.changeHandlerHistory.isValidHistory);
|
||||
assertTrue(newRootController.changeHandlerHistory.isValidHistory);
|
||||
|
||||
assertEquals(3, initialController1.changeHandlerHistory.size());
|
||||
assertEquals(2, initialController2.changeHandlerHistory.size());
|
||||
assertEquals(1, newRootController.changeHandlerHistory.size());
|
||||
|
||||
assertNotNull(initialController1.changeHandlerHistory.latestToView());
|
||||
assertEquals(newRootController.getView(), initialController1.changeHandlerHistory.latestToView());
|
||||
assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView());
|
||||
assertEquals(newRootHandler.tag, initialController1.changeHandlerHistory.latestChangeHandler().tag);
|
||||
assertTrue(initialController1.changeHandlerHistory.latestIsPush());
|
||||
|
||||
assertNull(initialController2.changeHandlerHistory.latestToView());
|
||||
assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView());
|
||||
assertEquals(newRootHandler.tag, initialController2.changeHandlerHistory.latestChangeHandler().tag);
|
||||
assertTrue(initialController2.changeHandlerHistory.latestIsPush());
|
||||
|
||||
assertNotNull(newRootController.changeHandlerHistory.latestToView());
|
||||
assertEquals(newRootController.getView(), newRootController.changeHandlerHistory.latestToView());
|
||||
assertEquals(initialView1, newRootController.changeHandlerHistory.latestFromView());
|
||||
assertEquals(newRootHandler.tag, newRootController.changeHandlerHistory.latestChangeHandler().tag);
|
||||
assertTrue(newRootController.changeHandlerHistory.latestIsPush());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetBackstackHandlers() {
|
||||
TestController initialController1 = new TestController();
|
||||
MockChangeHandler initialPushHandler1 = MockChangeHandler.taggedHandler("initialPush1", true);
|
||||
MockChangeHandler initialPopHandler1 = MockChangeHandler.taggedHandler("initialPop1", true);
|
||||
router.setRoot(RouterTransaction.with(initialController1).pushChangeHandler(initialPushHandler1).popChangeHandler(initialPopHandler1));
|
||||
TestController initialController2 = new TestController();
|
||||
MockChangeHandler initialPushHandler2 = MockChangeHandler.taggedHandler("initialPush2", false);
|
||||
MockChangeHandler initialPopHandler2 = MockChangeHandler.taggedHandler("initialPop2", false);
|
||||
router.pushController(RouterTransaction.with(initialController2).pushChangeHandler(initialPushHandler2).popChangeHandler(initialPopHandler2));
|
||||
|
||||
View initialView1 = initialController1.getView();
|
||||
View initialView2 = initialController2.getView();
|
||||
|
||||
TestController newController1 = new TestController();
|
||||
TestController newController2 = new TestController();
|
||||
MockChangeHandler setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true);
|
||||
|
||||
List<RouterTransaction> newBackstack = Arrays.asList(
|
||||
RouterTransaction.with(newController1),
|
||||
RouterTransaction.with(newController2)
|
||||
);
|
||||
|
||||
router.setBackstack(newBackstack, setBackstackHandler);
|
||||
|
||||
assertTrue(initialController1.changeHandlerHistory.isValidHistory);
|
||||
assertTrue(initialController2.changeHandlerHistory.isValidHistory);
|
||||
assertTrue(newController1.changeHandlerHistory.isValidHistory);
|
||||
|
||||
assertEquals(3, initialController1.changeHandlerHistory.size());
|
||||
assertEquals(2, initialController2.changeHandlerHistory.size());
|
||||
assertEquals(0, newController1.changeHandlerHistory.size());
|
||||
assertEquals(1, newController2.changeHandlerHistory.size());
|
||||
|
||||
assertNotNull(initialController1.changeHandlerHistory.latestToView());
|
||||
assertEquals(newController2.getView(), initialController1.changeHandlerHistory.latestToView());
|
||||
assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView());
|
||||
assertEquals(setBackstackHandler.tag, initialController1.changeHandlerHistory.latestChangeHandler().tag);
|
||||
assertTrue(initialController1.changeHandlerHistory.latestIsPush());
|
||||
|
||||
assertNull(initialController2.changeHandlerHistory.latestToView());
|
||||
assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView());
|
||||
assertEquals(setBackstackHandler.tag, initialController2.changeHandlerHistory.latestChangeHandler().tag);
|
||||
assertTrue(initialController2.changeHandlerHistory.latestIsPush());
|
||||
|
||||
assertNotNull(newController2.changeHandlerHistory.latestToView());
|
||||
assertEquals(newController2.getView(), newController2.changeHandlerHistory.latestToView());
|
||||
assertEquals(initialView1, newController2.changeHandlerHistory.latestFromView());
|
||||
assertEquals(setBackstackHandler.tag, newController2.changeHandlerHistory.latestChangeHandler().tag);
|
||||
assertTrue(newController2.changeHandlerHistory.latestIsPush());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetBackstackWithTwoVisibleHandlers() {
|
||||
TestController initialController1 = new TestController();
|
||||
MockChangeHandler initialPushHandler1 = MockChangeHandler.taggedHandler("initialPush1", true);
|
||||
MockChangeHandler initialPopHandler1 = MockChangeHandler.taggedHandler("initialPop1", true);
|
||||
router.setRoot(RouterTransaction.with(initialController1).pushChangeHandler(initialPushHandler1).popChangeHandler(initialPopHandler1));
|
||||
TestController initialController2 = new TestController();
|
||||
MockChangeHandler initialPushHandler2 = MockChangeHandler.taggedHandler("initialPush2", false);
|
||||
MockChangeHandler initialPopHandler2 = MockChangeHandler.taggedHandler("initialPop2", false);
|
||||
router.pushController(RouterTransaction.with(initialController2).pushChangeHandler(initialPushHandler2).popChangeHandler(initialPopHandler2));
|
||||
|
||||
View initialView1 = initialController1.getView();
|
||||
View initialView2 = initialController2.getView();
|
||||
|
||||
TestController newController1 = new TestController();
|
||||
TestController newController2 = new TestController();
|
||||
MockChangeHandler setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true);
|
||||
|
||||
List<RouterTransaction> newBackstack = Arrays.asList(
|
||||
RouterTransaction.with(newController1),
|
||||
RouterTransaction.with(newController2).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler())
|
||||
);
|
||||
|
||||
router.setBackstack(newBackstack, setBackstackHandler);
|
||||
|
||||
assertTrue(initialController1.changeHandlerHistory.isValidHistory);
|
||||
assertTrue(initialController2.changeHandlerHistory.isValidHistory);
|
||||
assertTrue(newController1.changeHandlerHistory.isValidHistory);
|
||||
|
||||
assertEquals(3, initialController1.changeHandlerHistory.size());
|
||||
assertEquals(2, initialController2.changeHandlerHistory.size());
|
||||
assertEquals(2, newController1.changeHandlerHistory.size());
|
||||
assertEquals(1, newController2.changeHandlerHistory.size());
|
||||
|
||||
assertNotNull(initialController1.changeHandlerHistory.latestToView());
|
||||
assertEquals(newController1.getView(), initialController1.changeHandlerHistory.latestToView());
|
||||
assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView());
|
||||
assertEquals(setBackstackHandler.tag, initialController1.changeHandlerHistory.latestChangeHandler().tag);
|
||||
assertTrue(initialController1.changeHandlerHistory.latestIsPush());
|
||||
|
||||
assertNull(initialController2.changeHandlerHistory.latestToView());
|
||||
assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView());
|
||||
assertEquals(setBackstackHandler.tag, initialController2.changeHandlerHistory.latestChangeHandler().tag);
|
||||
assertTrue(initialController2.changeHandlerHistory.latestIsPush());
|
||||
|
||||
assertNotNull(newController1.changeHandlerHistory.latestToView());
|
||||
assertEquals(newController1.getView(), newController1.changeHandlerHistory.toViewAt(0));
|
||||
assertEquals(newController2.getView(), newController1.changeHandlerHistory.latestToView());
|
||||
assertEquals(initialView1, newController1.changeHandlerHistory.fromViewAt(0));
|
||||
assertEquals(newController1.getView(), newController1.changeHandlerHistory.latestFromView());
|
||||
assertEquals(setBackstackHandler.tag, newController1.changeHandlerHistory.latestChangeHandler().tag);
|
||||
assertTrue(newController1.changeHandlerHistory.latestIsPush());
|
||||
|
||||
assertNotNull(newController2.changeHandlerHistory.latestToView());
|
||||
assertEquals(newController2.getView(), newController2.changeHandlerHistory.latestToView());
|
||||
assertEquals(newController1.getView(), newController2.changeHandlerHistory.latestFromView());
|
||||
assertEquals(setBackstackHandler.tag, newController2.changeHandlerHistory.latestChangeHandler().tag);
|
||||
assertTrue(newController2.changeHandlerHistory.latestIsPush());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,15 +1,27 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import org.junit.Assert;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
|
||||
import com.bluelinelabs.conductor.util.ActivityProxy;
|
||||
import com.bluelinelabs.conductor.util.MockChangeHandler;
|
||||
import com.bluelinelabs.conductor.util.TestController;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class RouterTests {
|
||||
@@ -28,13 +40,13 @@ public class RouterTests {
|
||||
|
||||
Controller rootController = new TestController();
|
||||
|
||||
Assert.assertFalse(router.hasRootController());
|
||||
assertFalse(router.hasRootController());
|
||||
|
||||
router.setRoot(RouterTransaction.with(rootController).tag(rootTag));
|
||||
|
||||
Assert.assertTrue(router.hasRootController());
|
||||
assertTrue(router.hasRootController());
|
||||
|
||||
Assert.assertEquals(rootController, router.getControllerWithTag(rootTag));
|
||||
assertEquals(rootController, router.getControllerWithTag(rootTag));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -48,8 +60,8 @@ public class RouterTests {
|
||||
router.setRoot(RouterTransaction.with(oldRootController).tag(oldRootTag));
|
||||
router.setRoot(RouterTransaction.with(newRootController).tag(newRootTag));
|
||||
|
||||
Assert.assertNull(router.getControllerWithTag(oldRootTag));
|
||||
Assert.assertEquals(newRootController, router.getControllerWithTag(newRootTag));
|
||||
assertNull(router.getControllerWithTag(oldRootTag));
|
||||
assertEquals(newRootController, router.getControllerWithTag(newRootTag));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -58,8 +70,8 @@ public class RouterTests {
|
||||
|
||||
router.pushController(RouterTransaction.with(controller));
|
||||
|
||||
Assert.assertEquals(controller, router.getControllerWithInstanceId(controller.getInstanceId()));
|
||||
Assert.assertNull(router.getControllerWithInstanceId("fake id"));
|
||||
assertEquals(controller, router.getControllerWithInstanceId(controller.getInstanceId()));
|
||||
assertNull(router.getControllerWithInstanceId("fake id"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -76,8 +88,8 @@ public class RouterTests {
|
||||
router.pushController(RouterTransaction.with(controller2)
|
||||
.tag(controller2Tag));
|
||||
|
||||
Assert.assertEquals(controller1, router.getControllerWithTag(controller1Tag));
|
||||
Assert.assertEquals(controller2, router.getControllerWithTag(controller2Tag));
|
||||
assertEquals(controller1, router.getControllerWithTag(controller1Tag));
|
||||
assertEquals(controller2, router.getControllerWithTag(controller2Tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -91,26 +103,26 @@ public class RouterTests {
|
||||
router.pushController(RouterTransaction.with(controller1)
|
||||
.tag(controller1Tag));
|
||||
|
||||
Assert.assertEquals(1, router.getBackstackSize());
|
||||
assertEquals(1, router.getBackstackSize());
|
||||
|
||||
router.pushController(RouterTransaction.with(controller2)
|
||||
.tag(controller2Tag));
|
||||
|
||||
Assert.assertEquals(2, router.getBackstackSize());
|
||||
assertEquals(2, router.getBackstackSize());
|
||||
|
||||
router.popCurrentController();
|
||||
|
||||
Assert.assertEquals(1, router.getBackstackSize());
|
||||
assertEquals(1, router.getBackstackSize());
|
||||
|
||||
Assert.assertEquals(controller1, router.getControllerWithTag(controller1Tag));
|
||||
Assert.assertNull(router.getControllerWithTag(controller2Tag));
|
||||
assertEquals(controller1, router.getControllerWithTag(controller1Tag));
|
||||
assertNull(router.getControllerWithTag(controller2Tag));
|
||||
|
||||
router.popCurrentController();
|
||||
|
||||
Assert.assertEquals(0, router.getBackstackSize());
|
||||
assertEquals(0, router.getBackstackSize());
|
||||
|
||||
Assert.assertNull(router.getControllerWithTag(controller1Tag));
|
||||
Assert.assertNull(router.getControllerWithTag(controller2Tag));
|
||||
assertNull(router.getControllerWithTag(controller1Tag));
|
||||
assertNull(router.getControllerWithTag(controller2Tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -139,11 +151,11 @@ public class RouterTests {
|
||||
|
||||
router.popToTag(controller2Tag);
|
||||
|
||||
Assert.assertEquals(2, router.getBackstackSize());
|
||||
Assert.assertEquals(controller1, router.getControllerWithTag(controller1Tag));
|
||||
Assert.assertEquals(controller2, router.getControllerWithTag(controller2Tag));
|
||||
Assert.assertNull(router.getControllerWithTag(controller3Tag));
|
||||
Assert.assertNull(router.getControllerWithTag(controller4Tag));
|
||||
assertEquals(2, router.getBackstackSize());
|
||||
assertEquals(controller1, router.getControllerWithTag(controller1Tag));
|
||||
assertEquals(controller2, router.getControllerWithTag(controller2Tag));
|
||||
assertNull(router.getControllerWithTag(controller3Tag));
|
||||
assertNull(router.getControllerWithTag(controller4Tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -167,10 +179,10 @@ public class RouterTests {
|
||||
|
||||
router.popController(controller2);
|
||||
|
||||
Assert.assertEquals(2, router.getBackstackSize());
|
||||
Assert.assertEquals(controller1, router.getControllerWithTag(controller1Tag));
|
||||
Assert.assertNull(router.getControllerWithTag(controller2Tag));
|
||||
Assert.assertEquals(controller3, router.getControllerWithTag(controller3Tag));
|
||||
assertEquals(2, router.getBackstackSize());
|
||||
assertEquals(controller1, router.getControllerWithTag(controller1Tag));
|
||||
assertNull(router.getControllerWithTag(controller2Tag));
|
||||
assertEquals(controller3, router.getControllerWithTag(controller3Tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -179,81 +191,115 @@ public class RouterTests {
|
||||
RouterTransaction middleTransaction = RouterTransaction.with(new TestController());
|
||||
RouterTransaction topTransaction = RouterTransaction.with(new TestController());
|
||||
|
||||
List<RouterTransaction> backstack = new ArrayList<>();
|
||||
backstack.add(rootTransaction);
|
||||
backstack.add(middleTransaction);
|
||||
backstack.add(topTransaction);
|
||||
|
||||
List<RouterTransaction> backstack = Arrays.asList(rootTransaction, middleTransaction, topTransaction);
|
||||
router.setBackstack(backstack, null);
|
||||
|
||||
Assert.assertEquals(3, router.getBackstackSize());
|
||||
assertEquals(3, router.getBackstackSize());
|
||||
|
||||
List<RouterTransaction> fetchedBackstack = router.getBackstack();
|
||||
Assert.assertEquals(rootTransaction, fetchedBackstack.get(0));
|
||||
Assert.assertEquals(middleTransaction, fetchedBackstack.get(1));
|
||||
Assert.assertEquals(topTransaction, fetchedBackstack.get(2));
|
||||
assertEquals(rootTransaction, fetchedBackstack.get(0));
|
||||
assertEquals(middleTransaction, fetchedBackstack.get(1));
|
||||
assertEquals(topTransaction, fetchedBackstack.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNewSetBackstack() {
|
||||
router.setRoot(RouterTransaction.with(new TestController()));
|
||||
|
||||
Assert.assertEquals(1, router.getBackstackSize());
|
||||
assertEquals(1, router.getBackstackSize());
|
||||
|
||||
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
|
||||
RouterTransaction middleTransaction = RouterTransaction.with(new TestController());
|
||||
RouterTransaction topTransaction = RouterTransaction.with(new TestController());
|
||||
|
||||
List<RouterTransaction> backstack = new ArrayList<>();
|
||||
backstack.add(rootTransaction);
|
||||
backstack.add(middleTransaction);
|
||||
backstack.add(topTransaction);
|
||||
|
||||
List<RouterTransaction> backstack = Arrays.asList(rootTransaction, middleTransaction, topTransaction);
|
||||
router.setBackstack(backstack, null);
|
||||
|
||||
Assert.assertEquals(3, router.getBackstackSize());
|
||||
assertEquals(3, router.getBackstackSize());
|
||||
|
||||
List<RouterTransaction> fetchedBackstack = router.getBackstack();
|
||||
Assert.assertEquals(rootTransaction, fetchedBackstack.get(0));
|
||||
Assert.assertEquals(middleTransaction, fetchedBackstack.get(1));
|
||||
Assert.assertEquals(topTransaction, fetchedBackstack.get(2));
|
||||
assertEquals(rootTransaction, fetchedBackstack.get(0));
|
||||
assertEquals(middleTransaction, fetchedBackstack.get(1));
|
||||
assertEquals(topTransaction, fetchedBackstack.get(2));
|
||||
|
||||
assertEquals(router, rootTransaction.controller.getRouter());
|
||||
assertEquals(router, middleTransaction.controller.getRouter());
|
||||
assertEquals(router, topTransaction.controller.getRouter());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNewSetBackstackWithNoRemoveViewOnPush() {
|
||||
RouterTransaction oldRootTransaction = RouterTransaction.with(new TestController());
|
||||
RouterTransaction oldTopTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(new MockChangeHandler(false));
|
||||
RouterTransaction oldTopTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
|
||||
|
||||
router.setRoot(oldRootTransaction);
|
||||
router.pushController(oldTopTransaction);
|
||||
Assert.assertEquals(2, router.getBackstackSize());
|
||||
assertEquals(2, router.getBackstackSize());
|
||||
|
||||
Assert.assertTrue(oldRootTransaction.controller.isAttached());
|
||||
Assert.assertTrue(oldTopTransaction.controller.isAttached());
|
||||
assertTrue(oldRootTransaction.controller.isAttached());
|
||||
assertTrue(oldTopTransaction.controller.isAttached());
|
||||
|
||||
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
|
||||
RouterTransaction middleTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(new MockChangeHandler(false));
|
||||
RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(new MockChangeHandler(false));
|
||||
|
||||
List<RouterTransaction> backstack = new ArrayList<>();
|
||||
backstack.add(rootTransaction);
|
||||
backstack.add(middleTransaction);
|
||||
backstack.add(topTransaction);
|
||||
RouterTransaction middleTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
|
||||
RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
|
||||
|
||||
List<RouterTransaction> backstack = Arrays.asList(rootTransaction, middleTransaction, topTransaction);
|
||||
router.setBackstack(backstack, null);
|
||||
|
||||
Assert.assertEquals(3, router.getBackstackSize());
|
||||
assertEquals(3, router.getBackstackSize());
|
||||
|
||||
List<RouterTransaction> fetchedBackstack = router.getBackstack();
|
||||
Assert.assertEquals(rootTransaction, fetchedBackstack.get(0));
|
||||
Assert.assertEquals(middleTransaction, fetchedBackstack.get(1));
|
||||
Assert.assertEquals(topTransaction, fetchedBackstack.get(2));
|
||||
assertEquals(rootTransaction, fetchedBackstack.get(0));
|
||||
assertEquals(middleTransaction, fetchedBackstack.get(1));
|
||||
assertEquals(topTransaction, fetchedBackstack.get(2));
|
||||
|
||||
Assert.assertFalse(oldRootTransaction.controller.isAttached());
|
||||
Assert.assertFalse(oldTopTransaction.controller.isAttached());
|
||||
Assert.assertTrue(rootTransaction.controller.isAttached());
|
||||
Assert.assertTrue(middleTransaction.controller.isAttached());
|
||||
Assert.assertTrue(topTransaction.controller.isAttached());
|
||||
assertFalse(oldRootTransaction.controller.isAttached());
|
||||
assertFalse(oldTopTransaction.controller.isAttached());
|
||||
assertTrue(rootTransaction.controller.isAttached());
|
||||
assertTrue(middleTransaction.controller.isAttached());
|
||||
assertTrue(topTransaction.controller.isAttached());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPopToRoot() {
|
||||
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
|
||||
RouterTransaction transaction1 = RouterTransaction.with(new TestController());
|
||||
RouterTransaction transaction2 = RouterTransaction.with(new TestController());
|
||||
|
||||
List<RouterTransaction> backstack = Arrays.asList(rootTransaction, transaction1, transaction2);
|
||||
router.setBackstack(backstack, null);
|
||||
|
||||
assertEquals(3, router.getBackstackSize());
|
||||
|
||||
router.popToRoot();
|
||||
|
||||
assertEquals(1, router.getBackstackSize());
|
||||
assertEquals(rootTransaction, router.getBackstack().get(0));
|
||||
|
||||
assertTrue(rootTransaction.controller.isAttached());
|
||||
assertFalse(transaction1.controller.isAttached());
|
||||
assertFalse(transaction2.controller.isAttached());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPopToRootWithNoRemoveViewOnPush() {
|
||||
RouterTransaction rootTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(new HorizontalChangeHandler(false));
|
||||
RouterTransaction transaction1 = RouterTransaction.with(new TestController()).pushChangeHandler(new HorizontalChangeHandler(false));
|
||||
RouterTransaction transaction2 = RouterTransaction.with(new TestController()).pushChangeHandler(new HorizontalChangeHandler(false));
|
||||
|
||||
List<RouterTransaction> backstack = Arrays.asList(rootTransaction, transaction1, transaction2);
|
||||
router.setBackstack(backstack, null);
|
||||
|
||||
assertEquals(3, router.getBackstackSize());
|
||||
|
||||
router.popToRoot();
|
||||
|
||||
assertEquals(1, router.getBackstackSize());
|
||||
assertEquals(rootTransaction, router.getBackstack().get(0));
|
||||
|
||||
assertTrue(rootTransaction.controller.isAttached());
|
||||
assertFalse(transaction1.controller.isAttached());
|
||||
assertFalse(transaction2.controller.isAttached());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -261,61 +307,128 @@ public class RouterTests {
|
||||
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
|
||||
RouterTransaction topTransaction = RouterTransaction.with(new TestController());
|
||||
|
||||
List<RouterTransaction> backstack = new ArrayList<>();
|
||||
backstack.add(rootTransaction);
|
||||
backstack.add(topTransaction);
|
||||
|
||||
List<RouterTransaction> backstack = Arrays.asList(rootTransaction, topTransaction);
|
||||
router.setBackstack(backstack, null);
|
||||
|
||||
Assert.assertEquals(2, router.getBackstackSize());
|
||||
assertEquals(2, router.getBackstackSize());
|
||||
|
||||
List<RouterTransaction> fetchedBackstack = router.getBackstack();
|
||||
Assert.assertEquals(rootTransaction, fetchedBackstack.get(0));
|
||||
Assert.assertEquals(topTransaction, fetchedBackstack.get(1));
|
||||
assertEquals(rootTransaction, fetchedBackstack.get(0));
|
||||
assertEquals(topTransaction, fetchedBackstack.get(1));
|
||||
|
||||
RouterTransaction newTopTransaction = RouterTransaction.with(new TestController());
|
||||
router.replaceTopController(newTopTransaction);
|
||||
|
||||
Assert.assertEquals(2, router.getBackstackSize());
|
||||
assertEquals(2, router.getBackstackSize());
|
||||
|
||||
fetchedBackstack = router.getBackstack();
|
||||
Assert.assertEquals(rootTransaction, fetchedBackstack.get(0));
|
||||
Assert.assertEquals(newTopTransaction, fetchedBackstack.get(1));
|
||||
assertEquals(rootTransaction, fetchedBackstack.get(0));
|
||||
assertEquals(newTopTransaction, fetchedBackstack.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplaceTopControllerWithNoRemoveViewOnPush() {
|
||||
RouterTransaction rootTransaction = RouterTransaction.with(new TestController());
|
||||
RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(new MockChangeHandler(false));
|
||||
|
||||
List<RouterTransaction> backstack = new ArrayList<>();
|
||||
backstack.add(rootTransaction);
|
||||
backstack.add(topTransaction);
|
||||
RouterTransaction topTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
|
||||
|
||||
List<RouterTransaction> backstack = Arrays.asList(rootTransaction, topTransaction);
|
||||
router.setBackstack(backstack, null);
|
||||
|
||||
Assert.assertEquals(2, router.getBackstackSize());
|
||||
assertEquals(2, router.getBackstackSize());
|
||||
|
||||
Assert.assertTrue(rootTransaction.controller.isAttached());
|
||||
Assert.assertTrue(topTransaction.controller.isAttached());
|
||||
assertTrue(rootTransaction.controller.isAttached());
|
||||
assertTrue(topTransaction.controller.isAttached());
|
||||
|
||||
List<RouterTransaction> fetchedBackstack = router.getBackstack();
|
||||
Assert.assertEquals(rootTransaction, fetchedBackstack.get(0));
|
||||
Assert.assertEquals(topTransaction, fetchedBackstack.get(1));
|
||||
assertEquals(rootTransaction, fetchedBackstack.get(0));
|
||||
assertEquals(topTransaction, fetchedBackstack.get(1));
|
||||
|
||||
RouterTransaction newTopTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(new MockChangeHandler(false));
|
||||
RouterTransaction newTopTransaction = RouterTransaction.with(new TestController()).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler());
|
||||
router.replaceTopController(newTopTransaction);
|
||||
newTopTransaction.pushChangeHandler().completeImmediately();
|
||||
|
||||
Assert.assertEquals(2, router.getBackstackSize());
|
||||
assertEquals(2, router.getBackstackSize());
|
||||
|
||||
fetchedBackstack = router.getBackstack();
|
||||
Assert.assertEquals(rootTransaction, fetchedBackstack.get(0));
|
||||
Assert.assertEquals(newTopTransaction, fetchedBackstack.get(1));
|
||||
assertEquals(rootTransaction, fetchedBackstack.get(0));
|
||||
assertEquals(newTopTransaction, fetchedBackstack.get(1));
|
||||
|
||||
Assert.assertTrue(rootTransaction.controller.isAttached());
|
||||
Assert.assertFalse(topTransaction.controller.isAttached());
|
||||
Assert.assertTrue(newTopTransaction.controller.isAttached());
|
||||
assertTrue(rootTransaction.controller.isAttached());
|
||||
assertFalse(topTransaction.controller.isAttached());
|
||||
assertTrue(newTopTransaction.controller.isAttached());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRearrangeTransactionBackstack() {
|
||||
RouterTransaction transaction1 = RouterTransaction.with(new TestController());
|
||||
RouterTransaction transaction2 = RouterTransaction.with(new TestController());
|
||||
|
||||
List<RouterTransaction> backstack = Arrays.asList(transaction1, transaction2);
|
||||
router.setBackstack(backstack, null);
|
||||
|
||||
assertEquals(1, transaction1.transactionIndex);
|
||||
assertEquals(2, transaction2.transactionIndex);
|
||||
|
||||
backstack = Arrays.asList(transaction2, transaction1);
|
||||
router.setBackstack(backstack, null);
|
||||
|
||||
assertEquals(1, transaction2.transactionIndex);
|
||||
assertEquals(2, transaction1.transactionIndex);
|
||||
|
||||
router.handleBack();
|
||||
|
||||
assertEquals(1, router.getBackstackSize());
|
||||
assertEquals(transaction2, router.getBackstack().get(0));
|
||||
|
||||
router.handleBack();
|
||||
assertEquals(0, router.getBackstackSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChildRouterRearrangeTransactionBackstack() {
|
||||
Controller parent = new TestController();
|
||||
router.setRoot(RouterTransaction.with(parent));
|
||||
|
||||
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.CHILD_VIEW_ID_1));
|
||||
|
||||
RouterTransaction transaction1 = RouterTransaction.with(new TestController());
|
||||
RouterTransaction transaction2 = RouterTransaction.with(new TestController());
|
||||
|
||||
List<RouterTransaction> backstack = Arrays.asList(transaction1, transaction2);
|
||||
childRouter.setBackstack(backstack, null);
|
||||
|
||||
assertEquals(2, transaction1.transactionIndex);
|
||||
assertEquals(3, transaction2.transactionIndex);
|
||||
|
||||
backstack = Arrays.asList(transaction2, transaction1);
|
||||
childRouter.setBackstack(backstack, null);
|
||||
|
||||
assertEquals(2, transaction2.transactionIndex);
|
||||
assertEquals(3, transaction1.transactionIndex);
|
||||
|
||||
childRouter.handleBack();
|
||||
|
||||
assertEquals(1, childRouter.getBackstackSize());
|
||||
assertEquals(transaction2, childRouter.getBackstack().get(0));
|
||||
|
||||
childRouter.handleBack();
|
||||
assertEquals(0, childRouter.getBackstackSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemovesAllViewsOnDestroy() {
|
||||
Controller controller1 = new TestController();
|
||||
Controller controller2 = new TestController();
|
||||
|
||||
router.setRoot(RouterTransaction.with(controller1));
|
||||
router.pushController(RouterTransaction.with(controller2)
|
||||
.pushChangeHandler(new FadeChangeHandler(false)));
|
||||
|
||||
assertEquals(2, router.container.getChildCount());
|
||||
|
||||
router.destroy(true);
|
||||
|
||||
assertEquals(0, router.container.getChildCount());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,13 +3,19 @@ package com.bluelinelabs.conductor;
|
||||
import android.os.Bundle;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.junit.Assert;
|
||||
import com.bluelinelabs.conductor.util.ActivityProxy;
|
||||
import com.bluelinelabs.conductor.util.MockChangeHandler;
|
||||
import com.bluelinelabs.conductor.util.TestController;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class TargetControllerTests {
|
||||
@@ -34,21 +40,21 @@ public class TargetControllerTests {
|
||||
final TestController controllerA = new TestController();
|
||||
final TestController controllerB = new TestController();
|
||||
|
||||
Assert.assertNull(controllerA.getTargetController());
|
||||
Assert.assertNull(controllerB.getTargetController());
|
||||
assertNull(controllerA.getTargetController());
|
||||
assertNull(controllerB.getTargetController());
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerA)
|
||||
.pushChangeHandler(new MockChangeHandler())
|
||||
.popChangeHandler(new MockChangeHandler()));
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
controllerB.setTargetController(controllerA);
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerB)
|
||||
.pushChangeHandler(new MockChangeHandler())
|
||||
.popChangeHandler(new MockChangeHandler()));
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
Assert.assertNull(controllerA.getTargetController());
|
||||
Assert.assertEquals(controllerA, controllerB.getTargetController());
|
||||
assertNull(controllerA.getTargetController());
|
||||
assertEquals(controllerA, controllerB.getTargetController());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -56,22 +62,22 @@ public class TargetControllerTests {
|
||||
final TestController controllerA = new TestController();
|
||||
final TestController controllerB = new TestController();
|
||||
|
||||
Assert.assertNull(controllerA.getTargetController());
|
||||
Assert.assertNull(controllerB.getTargetController());
|
||||
assertNull(controllerA.getTargetController());
|
||||
assertNull(controllerB.getTargetController());
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerA)
|
||||
.pushChangeHandler(new MockChangeHandler())
|
||||
.popChangeHandler(new MockChangeHandler()));
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
controllerB.setTargetController(controllerA);
|
||||
|
||||
Router childRouter = controllerA.getChildRouter((ViewGroup)controllerA.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter.pushController(RouterTransaction.with(controllerB)
|
||||
.pushChangeHandler(new MockChangeHandler())
|
||||
.popChangeHandler(new MockChangeHandler()));
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
Assert.assertNull(controllerA.getTargetController());
|
||||
Assert.assertEquals(controllerA, controllerB.getTargetController());
|
||||
assertNull(controllerA.getTargetController());
|
||||
assertEquals(controllerA, controllerB.getTargetController());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -79,22 +85,22 @@ public class TargetControllerTests {
|
||||
final TestController controllerA = new TestController();
|
||||
final TestController controllerB = new TestController();
|
||||
|
||||
Assert.assertNull(controllerA.getTargetController());
|
||||
Assert.assertNull(controllerB.getTargetController());
|
||||
assertNull(controllerA.getTargetController());
|
||||
assertNull(controllerB.getTargetController());
|
||||
|
||||
router.pushController(RouterTransaction.with(controllerA)
|
||||
.pushChangeHandler(new MockChangeHandler())
|
||||
.popChangeHandler(new MockChangeHandler()));
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
controllerA.setTargetController(controllerB);
|
||||
|
||||
Router childRouter = controllerA.getChildRouter((ViewGroup)controllerA.getView().findViewById(TestController.VIEW_ID));
|
||||
childRouter.pushController(RouterTransaction.with(controllerB)
|
||||
.pushChangeHandler(new MockChangeHandler())
|
||||
.popChangeHandler(new MockChangeHandler()));
|
||||
.pushChangeHandler(MockChangeHandler.defaultHandler())
|
||||
.popChangeHandler(MockChangeHandler.defaultHandler()));
|
||||
|
||||
Assert.assertNull(controllerB.getTargetController());
|
||||
Assert.assertEquals(controllerB, controllerA.getTargetController());
|
||||
assertNull(controllerB.getTargetController());
|
||||
assertEquals(controllerB, controllerA.getTargetController());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.bluelinelabs.conductor.internal.ViewAttachHandler;
|
||||
import com.bluelinelabs.conductor.internal.ViewAttachHandler.ViewAttachListener;
|
||||
import com.bluelinelabs.conductor.util.ActivityProxy;
|
||||
import com.bluelinelabs.conductor.util.ViewUtils;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class ViewAttachHandlerTests {
|
||||
|
||||
private Activity activity;
|
||||
private ViewAttachHandler viewAttachHandler;
|
||||
private CountingViewAttachListener viewAttachListener;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
activity = new ActivityProxy().create(null).getActivity();
|
||||
viewAttachListener = new CountingViewAttachListener();
|
||||
viewAttachHandler = new ViewAttachHandler(viewAttachListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleViewAttachDetach() {
|
||||
View view = new View(activity);
|
||||
viewAttachHandler.listenForAttach(view);
|
||||
|
||||
assertEquals(0, viewAttachListener.attaches);
|
||||
assertEquals(0, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(view, true);
|
||||
assertEquals(1, viewAttachListener.attaches);
|
||||
assertEquals(0, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(view, true);
|
||||
assertEquals(1, viewAttachListener.attaches);
|
||||
assertEquals(0, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(view, false);
|
||||
assertEquals(1, viewAttachListener.attaches);
|
||||
assertEquals(1, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(view, false);
|
||||
assertEquals(1, viewAttachListener.attaches);
|
||||
assertEquals(1, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(view, true);
|
||||
assertEquals(2, viewAttachListener.attaches);
|
||||
assertEquals(1, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
viewAttachHandler.onActivityStopped();
|
||||
assertEquals(2, viewAttachListener.attaches);
|
||||
assertEquals(2, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(view, false);
|
||||
assertEquals(2, viewAttachListener.attaches);
|
||||
assertEquals(2, viewAttachListener.detaches);
|
||||
assertEquals(1, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(view, true);
|
||||
assertEquals(2, viewAttachListener.attaches);
|
||||
assertEquals(2, viewAttachListener.detaches);
|
||||
assertEquals(1, viewAttachListener.detachAfterStops);
|
||||
|
||||
viewAttachHandler.onActivityStarted();
|
||||
assertEquals(3, viewAttachListener.attaches);
|
||||
assertEquals(2, viewAttachListener.detaches);
|
||||
assertEquals(1, viewAttachListener.detachAfterStops);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleViewGroupAttachDetach() {
|
||||
View view = new View(activity);
|
||||
viewAttachHandler.listenForAttach(view);
|
||||
|
||||
assertEquals(0, viewAttachListener.attaches);
|
||||
assertEquals(0, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(view, true);
|
||||
assertEquals(1, viewAttachListener.attaches);
|
||||
assertEquals(0, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(view, true);
|
||||
assertEquals(1, viewAttachListener.attaches);
|
||||
assertEquals(0, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(view, false);
|
||||
assertEquals(1, viewAttachListener.attaches);
|
||||
assertEquals(1, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(view, false);
|
||||
assertEquals(1, viewAttachListener.attaches);
|
||||
assertEquals(1, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(view, true);
|
||||
assertEquals(2, viewAttachListener.attaches);
|
||||
assertEquals(1, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
viewAttachHandler.onActivityStopped();
|
||||
assertEquals(2, viewAttachListener.attaches);
|
||||
assertEquals(2, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(view, false);
|
||||
assertEquals(2, viewAttachListener.attaches);
|
||||
assertEquals(2, viewAttachListener.detaches);
|
||||
assertEquals(1, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(view, true);
|
||||
assertEquals(2, viewAttachListener.attaches);
|
||||
assertEquals(2, viewAttachListener.detaches);
|
||||
assertEquals(1, viewAttachListener.detachAfterStops);
|
||||
|
||||
viewAttachHandler.onActivityStarted();
|
||||
assertEquals(3, viewAttachListener.attaches);
|
||||
assertEquals(2, viewAttachListener.detaches);
|
||||
assertEquals(1, viewAttachListener.detachAfterStops);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedViewGroupAttachDetach() {
|
||||
ViewGroup view = new LinearLayout(activity);
|
||||
View child = new LinearLayout(activity);
|
||||
view.addView(child);
|
||||
viewAttachHandler.listenForAttach(view);
|
||||
|
||||
assertEquals(0, viewAttachListener.attaches);
|
||||
assertEquals(0, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(view, true, false);
|
||||
assertEquals(0, viewAttachListener.attaches);
|
||||
assertEquals(0, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(child, true, false);
|
||||
assertEquals(1, viewAttachListener.attaches);
|
||||
assertEquals(0, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(view, true, false);
|
||||
ViewUtils.reportAttached(child, true, false);
|
||||
assertEquals(1, viewAttachListener.attaches);
|
||||
assertEquals(0, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(view, false, false);
|
||||
assertEquals(1, viewAttachListener.attaches);
|
||||
assertEquals(1, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(view, false, false);
|
||||
assertEquals(1, viewAttachListener.attaches);
|
||||
assertEquals(1, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(view, true, false);
|
||||
assertEquals(1, viewAttachListener.attaches);
|
||||
assertEquals(1, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(child, true, false);
|
||||
assertEquals(2, viewAttachListener.attaches);
|
||||
assertEquals(1, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
viewAttachHandler.onActivityStopped();
|
||||
assertEquals(2, viewAttachListener.attaches);
|
||||
assertEquals(2, viewAttachListener.detaches);
|
||||
assertEquals(0, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(view, false, false);
|
||||
assertEquals(2, viewAttachListener.attaches);
|
||||
assertEquals(2, viewAttachListener.detaches);
|
||||
assertEquals(1, viewAttachListener.detachAfterStops);
|
||||
|
||||
ViewUtils.reportAttached(view, true, false);
|
||||
ViewUtils.reportAttached(child, true, false);
|
||||
assertEquals(2, viewAttachListener.attaches);
|
||||
assertEquals(2, viewAttachListener.detaches);
|
||||
assertEquals(1, viewAttachListener.detachAfterStops);
|
||||
|
||||
viewAttachHandler.onActivityStarted();
|
||||
assertEquals(3, viewAttachListener.attaches);
|
||||
assertEquals(2, viewAttachListener.detaches);
|
||||
assertEquals(1, viewAttachListener.detachAfterStops);
|
||||
}
|
||||
|
||||
private static class CountingViewAttachListener implements ViewAttachListener {
|
||||
int attaches;
|
||||
int detaches;
|
||||
int detachAfterStops;
|
||||
|
||||
@Override
|
||||
public void onAttached() {
|
||||
attaches++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetached(boolean fromActivityStop) {
|
||||
detaches++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachAfterStop() {
|
||||
detachAfterStops++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.util.ActivityProxy;
|
||||
import com.bluelinelabs.conductor.util.TestController;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class ViewLeakTests {
|
||||
|
||||
private ActivityProxy activityProxy;
|
||||
private Router router;
|
||||
|
||||
public void createActivityController(Bundle savedInstanceState) {
|
||||
activityProxy = new ActivityProxy().create(savedInstanceState).start().resume();
|
||||
router = Conductor.attachRouter(activityProxy.getActivity(), activityProxy.getView(), savedInstanceState);
|
||||
if (!router.hasRootController()) {
|
||||
router.setRoot(RouterTransaction.with(new TestController()));
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
createActivityController(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPop() {
|
||||
Controller controller = new TestController();
|
||||
router.pushController(RouterTransaction.with(controller));
|
||||
|
||||
assertNotNull(controller.getView());
|
||||
|
||||
router.popCurrentController();
|
||||
|
||||
assertNull(controller.getView());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPopWhenPushNeverAdded() {
|
||||
Controller controller = new TestController();
|
||||
router.pushController(RouterTransaction.with(controller).pushChangeHandler(new NeverAddChangeHandler()));
|
||||
|
||||
assertNotNull(controller.getView());
|
||||
|
||||
router.popCurrentController();
|
||||
|
||||
assertNull(controller.getView());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPopWhenPushNeverCompleted() {
|
||||
Controller controller = new TestController();
|
||||
router.pushController(RouterTransaction.with(controller).pushChangeHandler(new NeverCompleteChangeHandler()));
|
||||
|
||||
assertNotNull(controller.getView());
|
||||
|
||||
router.popCurrentController();
|
||||
|
||||
assertNull(controller.getView());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivityStop() {
|
||||
Controller controller = new TestController();
|
||||
router.pushController(RouterTransaction.with(controller));
|
||||
|
||||
assertNotNull(controller.getView());
|
||||
|
||||
activityProxy.stop(true);
|
||||
|
||||
assertNull(controller.getView());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivityStopWhenPushNeverAdded() {
|
||||
Controller controller = new TestController();
|
||||
router.pushController(RouterTransaction.with(controller).pushChangeHandler(new NeverAddChangeHandler()));
|
||||
|
||||
assertNotNull(controller.getView());
|
||||
|
||||
activityProxy.stop(true);
|
||||
|
||||
assertNull(controller.getView());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivityStopWhenPushNeverCompleted() {
|
||||
Controller controller = new TestController();
|
||||
router.pushController(RouterTransaction.with(controller).pushChangeHandler(new NeverCompleteChangeHandler()));
|
||||
|
||||
assertNotNull(controller.getView());
|
||||
|
||||
activityProxy.stop(true);
|
||||
|
||||
assertNull(controller.getView());
|
||||
}
|
||||
|
||||
public static class NeverAddChangeHandler extends ControllerChangeHandler {
|
||||
@Override
|
||||
public void performChange(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
|
||||
if (from != null) {
|
||||
container.removeView(from);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class NeverCompleteChangeHandler extends ControllerChangeHandler {
|
||||
@Override
|
||||
public void performChange(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
|
||||
if (from != null) {
|
||||
container.removeView(from);
|
||||
}
|
||||
container.addView(to);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
package com.bluelinelabs.conductor.util;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.IdRes;
|
||||
@@ -45,14 +45,19 @@ public class ActivityProxy {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ActivityProxy stop() {
|
||||
public ActivityProxy stop(boolean detachView) {
|
||||
activityController.stop();
|
||||
view.setAttached(false);
|
||||
|
||||
if (detachView) {
|
||||
view.setAttached(false);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ActivityProxy destroy() {
|
||||
activityController.destroy();
|
||||
view.setAttached(false);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
package com.bluelinelabs.conductor.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.IBinder;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
package com.bluelinelabs.conductor.util;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.bluelinelabs.conductor.util;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ChangeHandlerHistory {
|
||||
|
||||
private List<Entry> entries = new ArrayList<>();
|
||||
public boolean isValidHistory = true;
|
||||
|
||||
public void addEntry(View from, View to, boolean isPush, MockChangeHandler handler) {
|
||||
entries.add(new Entry(from, to, isPush, handler));
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return entries.size();
|
||||
}
|
||||
|
||||
public View fromViewAt(int index) {
|
||||
return entries.get(index).from;
|
||||
}
|
||||
|
||||
public View toViewAt(int index) {
|
||||
return entries.get(index).to;
|
||||
}
|
||||
|
||||
public boolean isPushAt(int index) {
|
||||
return entries.get(index).isPush;
|
||||
}
|
||||
|
||||
public MockChangeHandler changeHandlerAt(int index) {
|
||||
return entries.get(index).changeHandler;
|
||||
}
|
||||
|
||||
public View latestFromView() {
|
||||
return fromViewAt(size() - 1);
|
||||
}
|
||||
|
||||
public View latestToView() {
|
||||
return toViewAt(size() - 1);
|
||||
}
|
||||
|
||||
public boolean latestIsPush() {
|
||||
return isPushAt(size() - 1);
|
||||
}
|
||||
|
||||
public MockChangeHandler latestChangeHandler() {
|
||||
return changeHandlerAt(size() - 1);
|
||||
}
|
||||
|
||||
private static class Entry {
|
||||
final View from;
|
||||
final View to;
|
||||
final boolean isPush;
|
||||
final MockChangeHandler changeHandler;
|
||||
|
||||
Entry(View from, View to, boolean isPush, MockChangeHandler changeHandler) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.isPush = isPush;
|
||||
this.changeHandler = changeHandler;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.bluelinelabs.conductor.util;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
|
||||
public class MockChangeHandler extends ControllerChangeHandler {
|
||||
|
||||
private static final String KEY_REMOVES_FROM_VIEW_ON_PUSH = "MockChangeHandler.removesFromViewOnPush";
|
||||
private static final String KEY_TAG = "MockChangeHandler.tag";
|
||||
|
||||
public static class ChangeHandlerListener {
|
||||
public void willStartChange() { }
|
||||
public void didAttachOrDetach() { }
|
||||
public void didEndChange() { }
|
||||
}
|
||||
|
||||
private final ChangeHandlerListener listener;
|
||||
private boolean removesFromViewOnPush;
|
||||
|
||||
public View from;
|
||||
public View to;
|
||||
public String tag;
|
||||
|
||||
public static MockChangeHandler defaultHandler() {
|
||||
return new MockChangeHandler(true, null, null);
|
||||
}
|
||||
|
||||
public static MockChangeHandler noRemoveViewOnPushHandler() {
|
||||
return new MockChangeHandler(false, null, null);
|
||||
}
|
||||
|
||||
public static MockChangeHandler listeningChangeHandler(@NonNull ChangeHandlerListener listener) {
|
||||
return new MockChangeHandler(true, null, listener);
|
||||
}
|
||||
|
||||
public static MockChangeHandler taggedHandler(String tag, boolean removeViewOnPush) {
|
||||
return new MockChangeHandler(removeViewOnPush, tag, null);
|
||||
}
|
||||
|
||||
public MockChangeHandler() {
|
||||
listener = null;
|
||||
}
|
||||
|
||||
private MockChangeHandler(boolean removesFromViewOnPush, String tag, ChangeHandlerListener listener) {
|
||||
this.removesFromViewOnPush = removesFromViewOnPush;
|
||||
|
||||
if (listener == null) {
|
||||
this.listener = new ChangeHandlerListener() { };
|
||||
} else {
|
||||
this.listener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performChange(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, @NonNull ControllerChangeCompletedListener changeListener) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
|
||||
listener.willStartChange();
|
||||
|
||||
if (isPush) {
|
||||
if (to != null) {
|
||||
container.addView(to);
|
||||
listener.didAttachOrDetach();
|
||||
}
|
||||
|
||||
if (removesFromViewOnPush && from != null) {
|
||||
container.removeView(from);
|
||||
}
|
||||
} else {
|
||||
container.removeView(from);
|
||||
listener.didAttachOrDetach();
|
||||
|
||||
if (to != null) {
|
||||
container.addView(to);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
changeListener.onChangeCompleted();
|
||||
listener.didEndChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removesFromViewOnPush() {
|
||||
return removesFromViewOnPush;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveToBundle(@NonNull Bundle bundle) {
|
||||
super.saveToBundle(bundle);
|
||||
bundle.putBoolean(KEY_REMOVES_FROM_VIEW_ON_PUSH, removesFromViewOnPush);
|
||||
bundle.putString(KEY_TAG, tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreFromBundle(@NonNull Bundle bundle) {
|
||||
super.restoreFromBundle(bundle);
|
||||
removesFromViewOnPush = bundle.getBoolean(KEY_REMOVES_FROM_VIEW_ON_PUSH);
|
||||
tag = bundle.getString(KEY_TAG);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ControllerChangeHandler copy() {
|
||||
return new MockChangeHandler(removesFromViewOnPush, tag, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReusable() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
package com.bluelinelabs.conductor.util;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
package com.bluelinelabs.conductor.util;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
@@ -11,6 +11,10 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
import com.bluelinelabs.conductor.ControllerChangeType;
|
||||
|
||||
public class TestController extends Controller {
|
||||
|
||||
@IdRes public static final int VIEW_ID = 2342;
|
||||
@@ -19,11 +23,8 @@ public class TestController extends Controller {
|
||||
|
||||
private static final String KEY_CALL_STATE = "TestController.currentCallState";
|
||||
|
||||
public CallState currentCallState;
|
||||
|
||||
public TestController() {
|
||||
currentCallState = new CallState();
|
||||
}
|
||||
public CallState currentCallState = new CallState();
|
||||
public ChangeHandlerHistory changeHandlerHistory = new ChangeHandlerHistory();
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
@@ -32,11 +33,11 @@ public class TestController extends Controller {
|
||||
FrameLayout view = new AttachFakingFrameLayout(inflater.getContext());
|
||||
view.setId(VIEW_ID);
|
||||
|
||||
FrameLayout childContainer1 = new FrameLayout(inflater.getContext());
|
||||
FrameLayout childContainer1 = new AttachFakingFrameLayout(inflater.getContext());
|
||||
childContainer1.setId(CHILD_VIEW_ID_1);
|
||||
view.addView(childContainer1);
|
||||
|
||||
FrameLayout childContainer2 = new FrameLayout(inflater.getContext());
|
||||
FrameLayout childContainer2 = new AttachFakingFrameLayout(inflater.getContext());
|
||||
childContainer2.setId(CHILD_VIEW_ID_2);
|
||||
view.addView(childContainer2);
|
||||
|
||||
@@ -53,6 +54,13 @@ public class TestController extends Controller {
|
||||
protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
|
||||
super.onChangeEnded(changeHandler, changeType);
|
||||
currentCallState.changeEndCalls++;
|
||||
|
||||
if (changeHandler instanceof MockChangeHandler) {
|
||||
MockChangeHandler mockHandler = (MockChangeHandler)changeHandler;
|
||||
changeHandlerHistory.addEntry(mockHandler.from, mockHandler.to, changeType.isPush, mockHandler);
|
||||
} else {
|
||||
changeHandlerHistory.isValidHistory = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1,7 +1,8 @@
|
||||
package com.bluelinelabs.conductor;
|
||||
package com.bluelinelabs.conductor.util;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.View.OnAttachStateChangeListener;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.robolectric.util.ReflectionHelpers;
|
||||
|
||||
@@ -10,6 +11,10 @@ import java.util.List;
|
||||
public class ViewUtils {
|
||||
|
||||
public static void reportAttached(View view, boolean attached) {
|
||||
reportAttached(view, attached, true);
|
||||
}
|
||||
|
||||
public static void reportAttached(View view, boolean attached, boolean propogateToChildren) {
|
||||
if (view instanceof AttachFakingFrameLayout) {
|
||||
((AttachFakingFrameLayout)view).setAttached(attached, false);
|
||||
}
|
||||
@@ -38,6 +43,14 @@ public class ViewUtils {
|
||||
}
|
||||
}
|
||||
|
||||
if (propogateToChildren && view instanceof ViewGroup) {
|
||||
ViewGroup viewGroup = (ViewGroup)view;
|
||||
int childCount = viewGroup.getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
reportAttached(viewGroup.getChildAt(i), attached, true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static List<OnAttachStateChangeListener> getAttachStateListeners(View view) {
|
||||
@@ -11,11 +11,12 @@ apply plugin: 'com.android.application'
|
||||
apply plugin: 'com.neenbedankt.android-apt'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.2"
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
abortOnError true
|
||||
ignore 'UnusedResources'
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
@@ -25,10 +26,11 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.bluelinelabs.conductor.demo"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 23
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0.0"
|
||||
vectorDrawables.useSupportLibrary true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
||||
@@ -22,8 +22,9 @@ public class CircularRevealChangeHandlerCompat extends CircularRevealChangeHandl
|
||||
return super.getAnimator(container, from, to, isPush, toAddedToContainer);
|
||||
} else {
|
||||
AnimatorSet animator = new AnimatorSet();
|
||||
if (to != null && toAddedToContainer) {
|
||||
animator.play(ObjectAnimator.ofFloat(to, View.ALPHA, 0, 1));
|
||||
if (to != null) {
|
||||
float start = toAddedToContainer ? 0 : to.getAlpha();
|
||||
animator.play(ObjectAnimator.ofFloat(to, View.ALPHA, start, 1));
|
||||
}
|
||||
|
||||
if (from != null) {
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.bluelinelabs.conductor.demo.changehandler;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.transition.Fade;
|
||||
import android.transition.Transition;
|
||||
import android.transition.TransitionSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.changehandler.TransitionChangeHandler;
|
||||
import com.bluelinelabs.conductor.demo.R;
|
||||
import com.bluelinelabs.conductor.demo.changehandler.transitions.FabTransform;
|
||||
import com.bluelinelabs.conductor.demo.util.AnimUtils;
|
||||
import com.bluelinelabs.conductor.demo.util.AnimUtils.TransitionEndListener;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public class FabToDialogTransitionChangeHandler extends TransitionChangeHandler {
|
||||
|
||||
private View fab;
|
||||
private View dialogBackground;
|
||||
private ViewGroup fabParent;
|
||||
|
||||
@NonNull @Override
|
||||
protected Transition getTransition(@NonNull final ViewGroup container, @Nullable final View from, @Nullable final View to, boolean isPush) {
|
||||
Transition backgroundFade = new Fade();
|
||||
backgroundFade.addTarget(R.id.dialog_background);
|
||||
|
||||
Transition fabTransform = new FabTransform(ContextCompat.getColor(container.getContext(), R.color.colorAccent), R.drawable.ic_github_face);
|
||||
|
||||
TransitionSet set = new TransitionSet();
|
||||
set.addTransition(backgroundFade);
|
||||
set.addTransition(fabTransform);
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareForTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @NonNull Transition transition, boolean isPush, @NonNull OnTransitionPreparedListener onTransitionPreparedListener) {
|
||||
fab = isPush ? from.findViewById(R.id.fab) : to.findViewById(R.id.fab);
|
||||
fabParent = (ViewGroup)fab.getParent();
|
||||
|
||||
if (!isPush) {
|
||||
/*
|
||||
* Before we transition back we want to remove the fab
|
||||
* in order to add it again for the TransitionManager to be able to detect the change
|
||||
*/
|
||||
fabParent.removeView(fab);
|
||||
fab.setVisibility(View.VISIBLE);
|
||||
|
||||
/*
|
||||
* Before we transition back we need to move the dialog's background to the new view
|
||||
* so its fade won't take place over the fab transition
|
||||
*/
|
||||
dialogBackground = from.findViewById(R.id.dialog_background);
|
||||
((ViewGroup)dialogBackground.getParent()).removeView(dialogBackground);
|
||||
fabParent.addView(dialogBackground);
|
||||
}
|
||||
|
||||
onTransitionPreparedListener.onPrepared();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @NonNull Transition transition, boolean isPush) {
|
||||
if (isPush) {
|
||||
fabParent.removeView(fab);
|
||||
container.addView(to);
|
||||
|
||||
/*
|
||||
* After the transition is finished we have to add the fab back to the original container.
|
||||
* Because otherwise we will be lost when trying to transition back.
|
||||
* Set it to invisible because we don't want it to jump back after the transition
|
||||
*/
|
||||
transition.addListener(new AnimUtils.TransitionEndListener() {
|
||||
@Override
|
||||
public void onTransitionCompleted(Transition transition) {
|
||||
fab.setVisibility(View.GONE);
|
||||
fabParent.addView(fab);
|
||||
fab = null;
|
||||
fabParent = null;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
dialogBackground.setVisibility(View.INVISIBLE);
|
||||
fabParent.addView(fab);
|
||||
container.removeView(from);
|
||||
|
||||
transition.addListener(new TransitionEndListener() {
|
||||
@Override
|
||||
public void onTransitionCompleted(Transition transition) {
|
||||
fabParent.removeView(dialogBackground);
|
||||
dialogBackground = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,8 +18,9 @@ public class ScaleFadeChangeHandler extends AnimatorChangeHandler {
|
||||
@Override @NonNull
|
||||
protected Animator getAnimator(@NonNull ViewGroup container, View from, View to, boolean isPush, boolean toAddedToContainer) {
|
||||
AnimatorSet animator = new AnimatorSet();
|
||||
if (to != null && toAddedToContainer) {
|
||||
animator.play(ObjectAnimator.ofFloat(to, View.ALPHA, 0, 1));
|
||||
if (to != null) {
|
||||
float start = toAddedToContainer ? 0 : to.getAlpha();
|
||||
animator.play(ObjectAnimator.ofFloat(to, View.ALPHA, start, 1));
|
||||
}
|
||||
|
||||
if (from != null) {
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
package com.bluelinelabs.conductor.demo.changehandler;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.transition.Transition;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver.OnPreDrawListener;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A TransitionChangeHandler that will wait for views with the passed transition names to be fully laid out
|
||||
* before executing. An OnPreDrawListener will be added to the "to" view, then to all of its subviews that
|
||||
* match the transaction names we're interested in. Once all of the views are fully ready, the "to" view
|
||||
* is set to invisible so that it'll fade in nicely, and the views that we want to use as shared elements
|
||||
* are removed from their containers, then immediately re-added within the beginDelayedTransition call so
|
||||
* the system picks them up as shared elements.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public class SharedElementDelayingChangeHandler extends ArcFadeMoveChangeHandler {
|
||||
|
||||
private static final String KEY_WAIT_FOR_TRANSITION_NAMES = "SharedElementDelayingChangeHandler.waitForTransitionNames";
|
||||
|
||||
private final ArrayList<String> waitForTransitionNames;
|
||||
private final ArrayList<ViewParentPair> removedViews = new ArrayList<>();
|
||||
private OnPreDrawListener onPreDrawListener;
|
||||
|
||||
public SharedElementDelayingChangeHandler() {
|
||||
waitForTransitionNames = new ArrayList<>();
|
||||
}
|
||||
|
||||
public SharedElementDelayingChangeHandler(@NonNull List<String> waitForTransitionNames) {
|
||||
this.waitForTransitionNames = new ArrayList<>(waitForTransitionNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareForTransition(@NonNull final ViewGroup container, @Nullable View from, @Nullable final View to, @NonNull Transition transition, boolean isPush, @NonNull final OnTransitionPreparedListener onTransitionPreparedListener) {
|
||||
if (to != null && to.getParent() == null && waitForTransitionNames.size() > 0) {
|
||||
onPreDrawListener = new OnPreDrawListener() {
|
||||
boolean addedSubviewListeners;
|
||||
|
||||
@Override
|
||||
public boolean onPreDraw() {
|
||||
List<View> foundViews = new ArrayList<>();
|
||||
for (String transitionName : waitForTransitionNames) {
|
||||
foundViews.add(getViewWithTransitionName(to, transitionName));
|
||||
}
|
||||
|
||||
if (!foundViews.contains(null) && !addedSubviewListeners) {
|
||||
addedSubviewListeners = true;
|
||||
|
||||
for (final View view : foundViews) {
|
||||
view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
|
||||
@Override
|
||||
public boolean onPreDraw() {
|
||||
view.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
waitForTransitionNames.remove(view.getTransitionName());
|
||||
|
||||
ViewGroup parent = (ViewGroup)view.getParent();
|
||||
removedViews.add(new ViewParentPair(view, parent));
|
||||
parent.removeView(view);
|
||||
|
||||
if (waitForTransitionNames.size() == 0) {
|
||||
to.getViewTreeObserver().removeOnPreDrawListener(onPreDrawListener);
|
||||
|
||||
to.setVisibility(View.INVISIBLE);
|
||||
|
||||
onTransitionPreparedListener.onPrepared();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
to.getViewTreeObserver().addOnPreDrawListener(onPreDrawListener);
|
||||
|
||||
container.addView(to);
|
||||
} else {
|
||||
onTransitionPreparedListener.onPrepared();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @NonNull Transition transition, boolean isPush) {
|
||||
if (to != null) {
|
||||
to.setVisibility(View.VISIBLE);
|
||||
|
||||
for (ViewParentPair removedView : removedViews) {
|
||||
removedView.parent.addView(removedView.view);
|
||||
}
|
||||
|
||||
removedViews.clear();
|
||||
}
|
||||
|
||||
super.executePropertyChanges(container, from, to, transition, isPush);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveToBundle(@NonNull Bundle bundle) {
|
||||
bundle.putStringArrayList(KEY_WAIT_FOR_TRANSITION_NAMES, waitForTransitionNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreFromBundle(@NonNull Bundle bundle) {
|
||||
List<String> savedNames = bundle.getStringArrayList(KEY_WAIT_FOR_TRANSITION_NAMES);
|
||||
if (savedNames != null) {
|
||||
waitForTransitionNames.addAll(savedNames);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable Controller newTop) {
|
||||
super.onAbortPush(newHandler, newTop);
|
||||
|
||||
removedViews.clear();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
View getViewWithTransitionName(@NonNull View view, @NonNull String transitionName) {
|
||||
if (transitionName.equals(view.getTransitionName())) {
|
||||
return view;
|
||||
}
|
||||
|
||||
if (view instanceof ViewGroup) {
|
||||
ViewGroup viewGroup = (ViewGroup)view;
|
||||
int childCount = viewGroup.getChildCount();
|
||||
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View viewWithTransitionName = getViewWithTransitionName(viewGroup.getChildAt(i), transitionName);
|
||||
if (viewWithTransitionName != null) {
|
||||
return viewWithTransitionName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class ViewParentPair {
|
||||
View view;
|
||||
ViewGroup parent;
|
||||
|
||||
public ViewParentPair(View view, ViewGroup parent) {
|
||||
this.view = view;
|
||||
this.parent = parent;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* Copyright 2016 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Example from https://github.com/nickbutcher/plaid
|
||||
*/
|
||||
package com.bluelinelabs.conductor.demo.changehandler.transitions;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.TargetApi;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.transition.Transition;
|
||||
import android.transition.TransitionValues;
|
||||
import android.view.View;
|
||||
import android.view.ViewAnimationUtils;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.view.ViewTreeObserver.OnPreDrawListener;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import com.bluelinelabs.conductor.demo.util.AnimUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static android.view.View.MeasureSpec.makeMeasureSpec;
|
||||
|
||||
/**
|
||||
* A transition between a FAB & another surface using a circular reveal moving along an arc.
|
||||
* <p>
|
||||
* See: https://www.google.com/design/spec/motion/transforming-material.html#transforming-material-radial-transformation
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public class FabTransform extends Transition {
|
||||
|
||||
private static final long DEFAULT_DURATION = 240L;
|
||||
private static final String PROP_BOUNDS = "plaid:fabTransform:bounds";
|
||||
private static final String[] TRANSITION_PROPERTIES = {
|
||||
PROP_BOUNDS
|
||||
};
|
||||
|
||||
private final int color;
|
||||
private final int icon;
|
||||
|
||||
public FabTransform(@ColorInt int fabColor, @DrawableRes int fabIconResId) {
|
||||
color = fabColor;
|
||||
icon = fabIconResId;
|
||||
setPathMotion(new GravityArcMotion());
|
||||
setDuration(DEFAULT_DURATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getTransitionProperties() {
|
||||
return TRANSITION_PROPERTIES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureStartValues(TransitionValues transitionValues) {
|
||||
captureValues(transitionValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureEndValues(TransitionValues transitionValues) {
|
||||
captureValues(transitionValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Animator createAnimator(final ViewGroup sceneRoot,
|
||||
final TransitionValues startValues,
|
||||
final TransitionValues endValues) {
|
||||
if (startValues == null || endValues == null) return null;
|
||||
|
||||
final Rect startBounds = (Rect) startValues.values.get(PROP_BOUNDS);
|
||||
final Rect endBounds = (Rect) endValues.values.get(PROP_BOUNDS);
|
||||
|
||||
final boolean fromFab = endBounds.width() > startBounds.width();
|
||||
final View view = endValues.view;
|
||||
final Rect dialogBounds = fromFab ? endBounds : startBounds;
|
||||
final Interpolator fastOutSlowInInterpolator =
|
||||
AnimUtils.getFastOutSlowInInterpolator();
|
||||
final long duration = getDuration();
|
||||
final long halfDuration = duration / 2;
|
||||
final long twoThirdsDuration = duration * 2 / 3;
|
||||
|
||||
if (!fromFab) {
|
||||
// Force measure / layout the dialog back to it's original bounds
|
||||
view.measure(
|
||||
makeMeasureSpec(startBounds.width(), View.MeasureSpec.EXACTLY),
|
||||
makeMeasureSpec(startBounds.height(), View.MeasureSpec.EXACTLY));
|
||||
view.layout(startBounds.left, startBounds.top, startBounds.right, startBounds.bottom);
|
||||
}
|
||||
|
||||
final int translationX = startBounds.centerX() - endBounds.centerX();
|
||||
final int translationY = startBounds.centerY() - endBounds.centerY();
|
||||
if (fromFab) {
|
||||
view.setTranslationX(translationX);
|
||||
view.setTranslationY(translationY);
|
||||
}
|
||||
|
||||
// Add a color overlay to fake appearance of the FAB
|
||||
final ColorDrawable fabColor = new ColorDrawable(color);
|
||||
fabColor.setBounds(0, 0, dialogBounds.width(), dialogBounds.height());
|
||||
if (!fromFab) fabColor.setAlpha(0);
|
||||
view.getOverlay().add(fabColor);
|
||||
|
||||
// Add an icon overlay again to fake the appearance of the FAB
|
||||
final Drawable fabIcon =
|
||||
ContextCompat.getDrawable(sceneRoot.getContext(), icon).mutate();
|
||||
final int iconLeft = (dialogBounds.width() - fabIcon.getIntrinsicWidth()) / 2;
|
||||
final int iconTop = (dialogBounds.height() - fabIcon.getIntrinsicHeight()) / 2;
|
||||
fabIcon.setBounds(iconLeft, iconTop,
|
||||
iconLeft + fabIcon.getIntrinsicWidth(),
|
||||
iconTop + fabIcon.getIntrinsicHeight());
|
||||
if (!fromFab) fabIcon.setAlpha(0);
|
||||
view.getOverlay().add(fabIcon);
|
||||
|
||||
// Since the view that's being transition to always seems to be on the top (z-order), we have
|
||||
// to make a copy of the "from" view and put it in the "to" view's overlay, then fade it out.
|
||||
// There has to be another way to do this, right?
|
||||
Drawable dialogView = null;
|
||||
if (!fromFab) {
|
||||
startValues.view.setDrawingCacheEnabled(true);
|
||||
startValues.view.buildDrawingCache();
|
||||
Bitmap viewBitmap = startValues.view.getDrawingCache();
|
||||
dialogView = new BitmapDrawable(view.getResources(), viewBitmap);
|
||||
dialogView.setBounds(0, 0, dialogBounds.width(), dialogBounds.height());
|
||||
view.getOverlay().add(dialogView);
|
||||
}
|
||||
|
||||
// Circular clip from/to the FAB size
|
||||
final Animator circularReveal;
|
||||
if (fromFab) {
|
||||
circularReveal = ViewAnimationUtils.createCircularReveal(view,
|
||||
view.getWidth() / 2,
|
||||
view.getHeight() / 2,
|
||||
startBounds.width() / 2,
|
||||
(float) Math.hypot(endBounds.width() / 2, endBounds.height() / 2));
|
||||
circularReveal.setInterpolator(
|
||||
AnimUtils.getFastOutLinearInInterpolator());
|
||||
} else {
|
||||
circularReveal = ViewAnimationUtils.createCircularReveal(view,
|
||||
view.getWidth() / 2,
|
||||
view.getHeight() / 2,
|
||||
(float) Math.hypot(startBounds.width() / 2, startBounds.height() / 2),
|
||||
endBounds.width() / 2);
|
||||
circularReveal.setInterpolator(
|
||||
AnimUtils.getLinearOutSlowInInterpolator());
|
||||
|
||||
// Persist the end clip i.e. stay at FAB size after the reveal has run
|
||||
circularReveal.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
final ViewOutlineProvider fabOutlineProvider = view.getOutlineProvider();
|
||||
|
||||
view.setOutlineProvider(new ViewOutlineProvider() {
|
||||
boolean hasRun = false;
|
||||
|
||||
@Override
|
||||
public void getOutline(final View view, Outline outline) {
|
||||
final int left = (view.getWidth() - endBounds.width()) / 2;
|
||||
final int top = (view.getHeight() - endBounds.height()) / 2;
|
||||
|
||||
outline.setOval(
|
||||
left, top, left + endBounds.width(), top + endBounds.height());
|
||||
|
||||
if (!hasRun) {
|
||||
hasRun = true;
|
||||
view.setClipToOutline(true);
|
||||
|
||||
// We have to remove this as soon as it's laid out so we can get the shadow back
|
||||
view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
|
||||
@Override
|
||||
public boolean onPreDraw() {
|
||||
if (view.getWidth() == endBounds.width() && view.getHeight() == endBounds.height()) {
|
||||
view.setOutlineProvider(fabOutlineProvider);
|
||||
view.setClipToOutline(false);
|
||||
view.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
circularReveal.setDuration(duration);
|
||||
|
||||
// Translate to end position along an arc
|
||||
final Animator translate = ObjectAnimator.ofFloat(
|
||||
view,
|
||||
View.TRANSLATION_X,
|
||||
View.TRANSLATION_Y,
|
||||
fromFab ? getPathMotion().getPath(translationX, translationY, 0, 0)
|
||||
: getPathMotion().getPath(0, 0, -translationX, -translationY));
|
||||
translate.setDuration(duration);
|
||||
translate.setInterpolator(fastOutSlowInInterpolator);
|
||||
|
||||
// Fade contents of non-FAB view in/out
|
||||
List<Animator> fadeContents = null;
|
||||
if (view instanceof ViewGroup) {
|
||||
final ViewGroup vg = ((ViewGroup) view);
|
||||
fadeContents = new ArrayList<>(vg.getChildCount());
|
||||
for (int i = vg.getChildCount() - 1; i >= 0; i--) {
|
||||
final View child = vg.getChildAt(i);
|
||||
final Animator fade =
|
||||
ObjectAnimator.ofFloat(child, View.ALPHA, fromFab ? 1f : 0f);
|
||||
if (fromFab) {
|
||||
child.setAlpha(0f);
|
||||
}
|
||||
fade.setDuration(twoThirdsDuration);
|
||||
fade.setInterpolator(fastOutSlowInInterpolator);
|
||||
fadeContents.add(fade);
|
||||
}
|
||||
}
|
||||
|
||||
// Fade in/out the fab color & icon overlays
|
||||
final Animator colorFade = ObjectAnimator.ofInt(fabColor, "alpha", fromFab ? 0 : 255);
|
||||
final Animator iconFade = ObjectAnimator.ofInt(fabIcon, "alpha", fromFab ? 0 : 255);
|
||||
if (!fromFab) {
|
||||
colorFade.setStartDelay(halfDuration);
|
||||
iconFade.setStartDelay(halfDuration);
|
||||
}
|
||||
colorFade.setDuration(halfDuration);
|
||||
iconFade.setDuration(halfDuration);
|
||||
colorFade.setInterpolator(fastOutSlowInInterpolator);
|
||||
iconFade.setInterpolator(fastOutSlowInInterpolator);
|
||||
|
||||
// Run all animations together
|
||||
final AnimatorSet transition = new AnimatorSet();
|
||||
transition.playTogether(circularReveal, translate, colorFade, iconFade);
|
||||
transition.playTogether(fadeContents);
|
||||
if (dialogView != null) {
|
||||
final Animator dialogViewFade = ObjectAnimator.ofInt(dialogView, "alpha", 0).setDuration(twoThirdsDuration);
|
||||
dialogViewFade.setInterpolator(fastOutSlowInInterpolator);
|
||||
transition.playTogether(dialogViewFade);
|
||||
}
|
||||
transition.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
// Clean up
|
||||
view.getOverlay().clear();
|
||||
|
||||
if (!fromFab) {
|
||||
view.setTranslationX(0);
|
||||
view.setTranslationY(0);
|
||||
view.setTranslationZ(0);
|
||||
|
||||
view.measure(
|
||||
makeMeasureSpec(endBounds.width(), View.MeasureSpec.EXACTLY),
|
||||
makeMeasureSpec(endBounds.height(), View.MeasureSpec.EXACTLY));
|
||||
view.layout(endBounds.left, endBounds.top, endBounds.right, endBounds.bottom);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
return new AnimUtils.NoPauseAnimator(transition);
|
||||
}
|
||||
|
||||
private void captureValues(TransitionValues transitionValues) {
|
||||
final View view = transitionValues.view;
|
||||
if (view == null || view.getWidth() <= 0 || view.getHeight() <= 0) return;
|
||||
|
||||
transitionValues.values.put(PROP_BOUNDS, new Rect(view.getLeft(), view.getTop(),
|
||||
view.getRight(), view.getBottom()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright 2016 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bluelinelabs.conductor.demo.changehandler.transitions;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Path;
|
||||
import android.os.Build;
|
||||
import android.transition.ArcMotion;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
/**
|
||||
* A tweak to {@link ArcMotion} which slightly alters the path calculation. In the real world
|
||||
* gravity slows upward motion and accelerates downward motion. This class emulates this behavior
|
||||
* to make motion paths appear more natural.
|
||||
* <p>
|
||||
* See https://www.google.com/design/spec/motion/movement.html#movement-movement-within-screen-bounds
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public class GravityArcMotion extends ArcMotion {
|
||||
|
||||
private static final float DEFAULT_MIN_ANGLE_DEGREES = 0;
|
||||
private static final float DEFAULT_MAX_ANGLE_DEGREES = 70;
|
||||
private static final float DEFAULT_MAX_TANGENT = (float)
|
||||
Math.tan(Math.toRadians(DEFAULT_MAX_ANGLE_DEGREES/2));
|
||||
|
||||
private float mMinimumHorizontalAngle = 0;
|
||||
private float mMinimumVerticalAngle = 0;
|
||||
private float mMaximumAngle = DEFAULT_MAX_ANGLE_DEGREES;
|
||||
private float mMinimumHorizontalTangent = 0;
|
||||
private float mMinimumVerticalTangent = 0;
|
||||
private float mMaximumTangent = DEFAULT_MAX_TANGENT;
|
||||
|
||||
public GravityArcMotion() {}
|
||||
|
||||
public GravityArcMotion(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void setMinimumHorizontalAngle(float angleInDegrees) {
|
||||
mMinimumHorizontalAngle = angleInDegrees;
|
||||
mMinimumHorizontalTangent = toTangent(angleInDegrees);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public float getMinimumHorizontalAngle() {
|
||||
return mMinimumHorizontalAngle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void setMinimumVerticalAngle(float angleInDegrees) {
|
||||
mMinimumVerticalAngle = angleInDegrees;
|
||||
mMinimumVerticalTangent = toTangent(angleInDegrees);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public float getMinimumVerticalAngle() {
|
||||
return mMinimumVerticalAngle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void setMaximumAngle(float angleInDegrees) {
|
||||
mMaximumAngle = angleInDegrees;
|
||||
mMaximumTangent = toTangent(angleInDegrees);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public float getMaximumAngle() {
|
||||
return mMaximumAngle;
|
||||
}
|
||||
|
||||
private static float toTangent(float arcInDegrees) {
|
||||
if (arcInDegrees < 0 || arcInDegrees > 90) {
|
||||
throw new IllegalArgumentException("Arc must be between 0 and 90 degrees");
|
||||
}
|
||||
return (float) Math.tan(Math.toRadians(arcInDegrees / 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getPath(float startX, float startY, float endX, float endY) {
|
||||
// Here's a little ascii art to show how this is calculated:
|
||||
// c---------- b
|
||||
// \ / |
|
||||
// \ d |
|
||||
// \ / e
|
||||
// a----f
|
||||
// This diagram assumes that the horizontal distance is less than the vertical
|
||||
// distance between The start point (a) and end point (b).
|
||||
// d is the midpoint between a and b. c is the center point of the circle with
|
||||
// This path is formed by assuming that start and end points are in
|
||||
// an arc on a circle. The end point is centered in the circle vertically
|
||||
// and start is a point on the circle.
|
||||
|
||||
// Triangles bfa and bde form similar right triangles. The control points
|
||||
// for the cubic Bezier arc path are the midpoints between a and e and e and b.
|
||||
|
||||
Path path = new Path();
|
||||
path.moveTo(startX, startY);
|
||||
|
||||
float ex;
|
||||
float ey;
|
||||
if (startY == endY) {
|
||||
ex = (startX + endX) / 2;
|
||||
ey = startY + mMinimumHorizontalTangent * Math.abs(endX - startX) / 2;
|
||||
} else if (startX == endX) {
|
||||
ex = startX + mMinimumVerticalTangent * Math.abs(endY - startY) / 2;
|
||||
ey = (startY + endY) / 2;
|
||||
} else {
|
||||
float deltaX = endX - startX;
|
||||
|
||||
/**
|
||||
* This is the only change to ArcMotion
|
||||
*/
|
||||
float deltaY;
|
||||
if (endY < startY) {
|
||||
deltaY = startY - endY; // Y is inverted compared to diagram above.
|
||||
} else {
|
||||
deltaY = endY - startY;
|
||||
}
|
||||
/**
|
||||
* End changes
|
||||
*/
|
||||
|
||||
// hypotenuse squared.
|
||||
float h2 = deltaX * deltaX + deltaY * deltaY;
|
||||
|
||||
// Midpoint between start and end
|
||||
float dx = (startX + endX) / 2;
|
||||
float dy = (startY + endY) / 2;
|
||||
|
||||
// Distance squared between end point and mid point is (1/2 hypotenuse)^2
|
||||
float midDist2 = h2 * 0.25f;
|
||||
|
||||
float minimumArcDist2 = 0;
|
||||
|
||||
if (Math.abs(deltaX) < Math.abs(deltaY)) {
|
||||
// Similar triangles bfa and bde mean that (ab/fb = eb/bd)
|
||||
// Therefore, eb = ab * bd / fb
|
||||
// ab = hypotenuse
|
||||
// bd = hypotenuse/2
|
||||
// fb = deltaY
|
||||
float eDistY = h2 / (2 * deltaY);
|
||||
ey = endY + eDistY;
|
||||
ex = endX;
|
||||
|
||||
minimumArcDist2 = midDist2 * mMinimumVerticalTangent
|
||||
* mMinimumVerticalTangent;
|
||||
} else {
|
||||
// Same as above, but flip X & Y
|
||||
float eDistX = h2 / (2 * deltaX);
|
||||
ex = endX + eDistX;
|
||||
ey = endY;
|
||||
|
||||
minimumArcDist2 = midDist2 * mMinimumHorizontalTangent
|
||||
* mMinimumHorizontalTangent;
|
||||
}
|
||||
float arcDistX = dx - ex;
|
||||
float arcDistY = dy - ey;
|
||||
float arcDist2 = arcDistX * arcDistX + arcDistY * arcDistY;
|
||||
|
||||
float maximumArcDist2 = midDist2 * mMaximumTangent * mMaximumTangent;
|
||||
|
||||
float newArcDistance2 = 0;
|
||||
if (arcDist2 < minimumArcDist2) {
|
||||
newArcDistance2 = minimumArcDist2;
|
||||
} else if (arcDist2 > maximumArcDist2) {
|
||||
newArcDistance2 = maximumArcDist2;
|
||||
}
|
||||
if (newArcDistance2 != 0) {
|
||||
float ratio2 = newArcDistance2 / arcDist2;
|
||||
float ratio = (float) Math.sqrt(ratio2);
|
||||
ex = dx + (ratio * (ex - dx));
|
||||
ey = dy + (ratio * (ey - dy));
|
||||
}
|
||||
}
|
||||
float controlX1 = (startX + ex) / 2;
|
||||
float controlY1 = (startY + ey) / 2;
|
||||
float controlX2 = (ex + endX) / 2;
|
||||
float controlY2 = (ey + endY) / 2;
|
||||
path.cubicTo(controlX1, controlY1, controlX2, controlY2, endX, endY);
|
||||
return path;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package com.bluelinelabs.conductor.demo.controllers;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.bluelinelabs.conductor.demo.R;
|
||||
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
|
||||
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class CityDetailController extends BaseController {
|
||||
|
||||
private static final String KEY_TITLE = "CityDetailController.title";
|
||||
private static final String KEY_IMAGE = "CityDetailController.image";
|
||||
|
||||
private static final String[] LIST_ROWS = new String[] {
|
||||
"• This is a city.",
|
||||
"• There's some cool stuff about it.",
|
||||
"• But really this is just a demo, not a city guide app.",
|
||||
"• This demo is meant to show some nice transitions, as long as you're on Lollipop or later.",
|
||||
"• You should have seen some sweet shared element transitions using the ImageView and the TextView in the \"header\" above.",
|
||||
"• This transition utilized some callbacks to ensure all the necessary rows in the RecyclerView were laid about before the transition occurred.",
|
||||
"• Just adding some more lines so it scrolls now...\n\n\n\n\n\n\nThe end."
|
||||
};
|
||||
|
||||
@BindView(R.id.recycler_view) RecyclerView recyclerView;
|
||||
|
||||
@DrawableRes private int imageDrawableRes;
|
||||
private String title;
|
||||
|
||||
public CityDetailController(@DrawableRes int imageDrawableRes, String title) {
|
||||
this(new BundleBuilder(new Bundle())
|
||||
.putInt(KEY_IMAGE, imageDrawableRes)
|
||||
.putString(KEY_TITLE, title)
|
||||
.build());
|
||||
}
|
||||
|
||||
public CityDetailController(Bundle args) {
|
||||
super(args);
|
||||
imageDrawableRes = getArgs().getInt(KEY_IMAGE);
|
||||
title = getArgs().getString(KEY_TITLE);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
|
||||
return inflater.inflate(R.layout.controller_city_detail, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewBound(@NonNull View view) {
|
||||
super.onViewBound(view);
|
||||
|
||||
recyclerView.setHasFixedSize(true);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(view.getContext()));
|
||||
recyclerView.setAdapter(new CityDetailAdapter(LayoutInflater.from(view.getContext()), title, imageDrawableRes, LIST_ROWS, title));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
static class CityDetailAdapter extends RecyclerView.Adapter<CityDetailAdapter.ViewHolder> {
|
||||
|
||||
private static final int VIEW_TYPE_HEADER = 0;
|
||||
private static final int VIEW_TYPE_DETAIL = 1;
|
||||
|
||||
private final LayoutInflater inflater;
|
||||
private final String title;
|
||||
@DrawableRes private final int imageDrawableRes;
|
||||
private final String imageViewTransitionName;
|
||||
private final String textViewTransitionName;
|
||||
private final String[] details;
|
||||
|
||||
public CityDetailAdapter(LayoutInflater inflater, @DrawableRes String title, int imageDrawableRes, String[] details, String transitionNameBase) {
|
||||
this.inflater = inflater;
|
||||
this.title = title;
|
||||
this.imageDrawableRes = imageDrawableRes;
|
||||
this.details = details;
|
||||
imageViewTransitionName = inflater.getContext().getResources().getString(R.string.transition_tag_image_named, transitionNameBase);
|
||||
textViewTransitionName = inflater.getContext().getResources().getString(R.string.transition_tag_title_named, transitionNameBase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (position == 0) {
|
||||
return VIEW_TYPE_HEADER;
|
||||
} else {
|
||||
return VIEW_TYPE_DETAIL;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
if (viewType == VIEW_TYPE_HEADER) {
|
||||
return new HeaderViewHolder(inflater.inflate(R.layout.row_city_header, parent, false));
|
||||
} else {
|
||||
return new DetailViewHolder(inflater.inflate(R.layout.row_city_detail, parent, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
if (getItemViewType(position) == VIEW_TYPE_HEADER) {
|
||||
((HeaderViewHolder)holder).bind(imageDrawableRes, title, imageViewTransitionName, textViewTransitionName);
|
||||
} else {
|
||||
((DetailViewHolder)holder).bind(details[position - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return 1 + details.length;
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ButterKnife.bind(this, itemView);
|
||||
}
|
||||
}
|
||||
|
||||
static class HeaderViewHolder extends ViewHolder {
|
||||
|
||||
@BindView(R.id.image_view) ImageView imageView;
|
||||
@BindView(R.id.text_view) TextView textView;
|
||||
|
||||
public HeaderViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
void bind(@DrawableRes int imageDrawableRes, String title, String imageTransitionName, String textViewTransitionName) {
|
||||
imageView.setImageResource(imageDrawableRes);
|
||||
textView.setText(title);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
imageView.setTransitionName(imageTransitionName);
|
||||
textView.setTransitionName(textViewTransitionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class DetailViewHolder extends ViewHolder {
|
||||
|
||||
@BindView(R.id.text_view) TextView textView;
|
||||
|
||||
public DetailViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
void bind(String detail) {
|
||||
textView.setText(detail);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package com.bluelinelabs.conductor.demo.controllers;
|
||||
|
||||
import android.graphics.PorterDuff.Mode;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.bluelinelabs.conductor.RouterTransaction;
|
||||
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
|
||||
import com.bluelinelabs.conductor.changehandler.TransitionChangeHandlerCompat;
|
||||
import com.bluelinelabs.conductor.demo.R;
|
||||
import com.bluelinelabs.conductor.demo.changehandler.SharedElementDelayingChangeHandler;
|
||||
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
|
||||
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
|
||||
public class CityGridController extends BaseController {
|
||||
|
||||
private static final String KEY_TITLE = "CityGridController.title";
|
||||
private static final String KEY_DOT_COLOR = "CityGridController.dotColor";
|
||||
private static final String KEY_FROM_POSITION = "CityGridController.position";
|
||||
|
||||
private static final CityModel[] CITY_MODELS = new CityModel[] {
|
||||
new CityModel(R.drawable.chicago, "Chicago"),
|
||||
new CityModel(R.drawable.jakarta, "Jakarta"),
|
||||
new CityModel(R.drawable.london, "London"),
|
||||
new CityModel(R.drawable.sao_paulo, "Sao Paulo"),
|
||||
new CityModel(R.drawable.tokyo, "Tokyo")
|
||||
};
|
||||
|
||||
@BindView(R.id.tv_title) TextView tvTitle;
|
||||
@BindView(R.id.img_dot) ImageView imgDot;
|
||||
@BindView(R.id.recycler_view) RecyclerView recyclerView;
|
||||
|
||||
private String title;
|
||||
private int dotColor;
|
||||
private int fromPosition;
|
||||
|
||||
public CityGridController(String title, int dotColor, int fromPosition) {
|
||||
this(new BundleBuilder(new Bundle())
|
||||
.putString(KEY_TITLE, title)
|
||||
.putInt(KEY_DOT_COLOR, dotColor)
|
||||
.putInt(KEY_FROM_POSITION, fromPosition)
|
||||
.build());
|
||||
}
|
||||
|
||||
public CityGridController(Bundle args) {
|
||||
super(args);
|
||||
title = getArgs().getString(KEY_TITLE);
|
||||
dotColor = getArgs().getInt(KEY_DOT_COLOR);
|
||||
fromPosition = getArgs().getInt(KEY_FROM_POSITION);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
|
||||
return inflater.inflate(R.layout.controller_city_grid, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewBound(@NonNull View view) {
|
||||
super.onViewBound(view);
|
||||
|
||||
tvTitle.setText(title);
|
||||
imgDot.getDrawable().setColorFilter(ContextCompat.getColor(getActivity(), dotColor), Mode.SRC_ATOP);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
tvTitle.setTransitionName(getResources().getString(R.string.transition_tag_title_indexed, fromPosition));
|
||||
imgDot.setTransitionName(getResources().getString(R.string.transition_tag_dot_indexed, fromPosition));
|
||||
}
|
||||
|
||||
recyclerView.setHasFixedSize(true);
|
||||
recyclerView.setLayoutManager(new GridLayoutManager(view.getContext(), 2));
|
||||
recyclerView.setAdapter(new CityGridAdapter(LayoutInflater.from(view.getContext()), CITY_MODELS));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle() {
|
||||
return "Shared Element Demos";
|
||||
}
|
||||
|
||||
void onModelRowClick(CityModel model) {
|
||||
String imageTransitionName = getResources().getString(R.string.transition_tag_image_named, model.title);
|
||||
String titleTransitionName = getResources().getString(R.string.transition_tag_title_named, model.title);
|
||||
|
||||
List<String> names = new ArrayList<>();
|
||||
names.add(imageTransitionName);
|
||||
names.add(titleTransitionName);
|
||||
|
||||
getRouter().pushController(RouterTransaction.with(new CityDetailController(model.drawableRes, model.title))
|
||||
.pushChangeHandler(new TransitionChangeHandlerCompat(new SharedElementDelayingChangeHandler(names), new FadeChangeHandler()))
|
||||
.popChangeHandler(new TransitionChangeHandlerCompat(new SharedElementDelayingChangeHandler(names), new FadeChangeHandler())));
|
||||
}
|
||||
|
||||
class CityGridAdapter extends RecyclerView.Adapter<CityGridAdapter.ViewHolder> {
|
||||
|
||||
private final LayoutInflater inflater;
|
||||
private final CityModel[] items;
|
||||
|
||||
public CityGridAdapter(LayoutInflater inflater, CityModel[] items) {
|
||||
this.inflater = inflater;
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(inflater.inflate(R.layout.row_city_grid, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
holder.bind(items[position]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return items.length;
|
||||
}
|
||||
|
||||
class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@BindView(R.id.tv_title) TextView textView;
|
||||
@BindView(R.id.img_city) ImageView imageView;
|
||||
private CityModel model;
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ButterKnife.bind(this, itemView);
|
||||
}
|
||||
|
||||
void bind(CityModel item) {
|
||||
model = item;
|
||||
imageView.setImageResource(item.drawableRes);
|
||||
textView.setText(item.title);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
textView.setTransitionName(getResources().getString(R.string.transition_tag_title_named, model.title));
|
||||
imageView.setTransitionName(getResources().getString(R.string.transition_tag_image_named, model.title));
|
||||
}
|
||||
}
|
||||
|
||||
@OnClick(R.id.row_root)
|
||||
void onRowClick() {
|
||||
onModelRowClick(model);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static class CityModel {
|
||||
@DrawableRes int drawableRes;
|
||||
String title;
|
||||
|
||||
public CityModel(@DrawableRes int drawableRes, String title) {
|
||||
this.drawableRes = drawableRes;
|
||||
this.title = title;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.bluelinelabs.conductor.demo.controllers;
|
||||
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.bluelinelabs.conductor.demo.R;
|
||||
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
|
||||
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.OnClick;
|
||||
|
||||
public class DialogController extends BaseController {
|
||||
|
||||
private static final String KEY_TITLE = "DialogController.title";
|
||||
private static final String KEY_DESCRIPTION = "DialogController.description";
|
||||
|
||||
@BindView(R.id.tv_title) TextView tvTitle;
|
||||
@BindView(R.id.tv_description) TextView tvDescription;
|
||||
|
||||
public DialogController(CharSequence title, CharSequence description) {
|
||||
this(new BundleBuilder(new Bundle())
|
||||
.putCharSequence(KEY_TITLE, title)
|
||||
.putCharSequence(KEY_DESCRIPTION, description)
|
||||
.build());
|
||||
}
|
||||
|
||||
public DialogController(Bundle args) {
|
||||
super(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
|
||||
return inflater.inflate(R.layout.controller_dialog, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewBound(@NonNull View view) {
|
||||
super.onViewBound(view);
|
||||
tvTitle.setText(getArgs().getCharSequence(KEY_TITLE));
|
||||
tvDescription.setText(getArgs().getCharSequence(KEY_DESCRIPTION));
|
||||
tvDescription.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
|
||||
@OnClick({R.id.dismiss, R.id.dialog_window})
|
||||
public void dismissDialog() {
|
||||
getRouter().popController(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleBack() {
|
||||
return super.handleBack();
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import android.support.annotation.NonNull;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.bluelinelabs.conductor.demo.R;
|
||||
import com.bluelinelabs.conductor.demo.changehandler.ScaleFadeChangeHandler;
|
||||
@@ -14,13 +13,9 @@ import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
|
||||
import com.bluelinelabs.conductor.demo.widget.ElasticDragDismissFrameLayout;
|
||||
import com.bluelinelabs.conductor.demo.widget.ElasticDragDismissFrameLayout.ElasticDragDismissCallback;
|
||||
|
||||
import butterknife.BindView;
|
||||
|
||||
@TargetApi(VERSION_CODES.LOLLIPOP)
|
||||
public class DragDismissController extends BaseController {
|
||||
|
||||
@BindView(R.id.tv_lorem_ipsum) TextView tvLoremIpsum;
|
||||
|
||||
private final ElasticDragDismissCallback dragDismissListener = new ElasticDragDismissCallback() {
|
||||
@Override
|
||||
public void onDragDismissed() {
|
||||
@@ -38,11 +33,6 @@ public class DragDismissController extends BaseController {
|
||||
@Override
|
||||
protected void onViewBound(@NonNull View view) {
|
||||
((ElasticDragDismissFrameLayout)view).addListener(dragDismissListener);
|
||||
|
||||
tvLoremIpsum.setText("Lorem ipsum dolor sit amet, volutpat lacus egestas integer vitae, tempus potenti posuere dolore, elit cras ut vulputate pede eros. Pharetra curabitur, cum ultrices nisi nulla, non a est diamlorem in pede. Feugiat vivamus id, leo massa, pede ligula libero wisi, posuere nec interdum risus. Mauris eros. Scelerisque etiam dignissim, sem odio magna posuere libero in. Eget non posuere, rutrum nunc ut, ipsum ornare, vestibulum nisl turpis, urna interdum. Arcu mi velit. Sem dolor amet sed hymenaeos tempor. Cras felis.\n" +
|
||||
"Tempus risus vehicula, mauris nulla interdum purus sodales suspendisse, morbi ultrices tempus vitae vestibulum porttitor, vel mus enim tellus non massa in, quis nec fermentum scelerisque sem congue dolores. Accumsan lacinia urna eu feugiat, habitasse wisi cras id nonummy sapien, a pede. Turpis ac donec, adipiscing ut faucibus, odio ut morbi, habitant volutpat lacinia. At vitae ipsum, porttitor wisi hendrerit pellentesque sapien, suspendisse pellentesque praesent sit tellus varius. At in ligula neque imperdiet eget, viverra lectus risus est sem feugiat. Diam amet non phasellus, sed enim ante etiam lorem id at, feugiat eu urna, urna posuere. Amet vel, vehicula ac in fermentum id, elit mauris dolor, eget orci in nec non in vestibulum. Tortor risus mattis, massa vel condimentum non ornare, pede nunc curabitur dui, eu dolorum luctus duis pellentesque. Nec in imperdiet ac tortor pulvinar bibendum. Parturient lacus luctus posuere, quis luctus sociis tellus iaculis.\n" +
|
||||
"Eu nec, nulla ut neque ac suspendisse, ac purus mattis pretium orci orci, penatibus sollicitudin pellentesque massa, felis et molestie natoque justo vitae. Laoreet nunc nulla augue semper, nulla ridiculus elit in quis bibendum ultrices, integer duis. Dolor ut donec ligula vehicula odio, in lacinia mattis ut quos, semper libero nulla, et euismod ut, nec curabitur turpis aliquet mauris sit a. Tellus interdum dignissim felis. Ultrices dignissim ut, enim urna adipiscing, nunc accumsan justo odio fringilla magna penatibus. Amet integer metus sollicitudin tristique libero dolor, augue nulla pellentesque, massa in suspendisse, adipiscing donec neque nunc. Malesuada non, luctus vestibulum, et at taciti orci felis. Aliquam dolor nibh erat in tincidunt in, risus suscipit integer, quis dolor quis quam. Ultrices dictum, dignissim interdum aenean pede ornare pretium, vehicula adipiscing enim nec magna mollis duis. Imperdiet dolor velit phasellus, laoreet sed ridiculus sollicitudin sit viverra metus, integer sit nunc fusce nonummy augue. Maecenas adipiscing porttitor eu risus nunc malesuada, quo vitae blandit amet feugiat nunc. Pede hac duis in.\n" +
|
||||
"Lorem posuere ridiculus donec, volutpat vehicula erat nunc ut, justo occaecati vivamus aliquam massa, felis etiam, sed feugiat molestie eu. Et leo non dignissim nam. Fringilla pretium suspendisse vitae nascetur massa hymenaeos, lectus ut senectus amet, a enim. Pellentesque integer erat, mauris morbi pellentesque, sodales phasellus turpis purus nulla porta, massa vulputate consectetuer habitasse malesuada pulvinar, vehicula in elit eros interdum ut. Et enim vulputate, aliquam donec ullamcorper et, vel consequat tincidunt. Ipsum quam sed ante quis at, ultrices eget. Rutrum qui et velit possimus in, odio amet ut adipiscing sed nec, a tellus ut, quam molestie. Nisl adipiscing euismod nec, eget facilisis ac sit. Suspendisse elit amet consequat dolor senectus vivamus, at scelerisque erat, odio doloribus velit et felis neque, turpis adipiscing, arcu varius placerat leo.");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.bluelinelabs.conductor.demo.controllers;
|
||||
import android.content.Intent;
|
||||
import android.graphics.PorterDuff.Mode;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.ColorRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
@@ -26,7 +28,10 @@ import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
import com.bluelinelabs.conductor.ControllerChangeType;
|
||||
import com.bluelinelabs.conductor.RouterTransaction;
|
||||
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
|
||||
import com.bluelinelabs.conductor.changehandler.TransitionChangeHandlerCompat;
|
||||
import com.bluelinelabs.conductor.demo.R;
|
||||
import com.bluelinelabs.conductor.demo.changehandler.ArcFadeMoveChangeHandler;
|
||||
import com.bluelinelabs.conductor.demo.changehandler.FabToDialogTransitionChangeHandler;
|
||||
import com.bluelinelabs.conductor.demo.controllers.NavigationDemoController.DisplayUpMode;
|
||||
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
|
||||
|
||||
@@ -36,9 +41,10 @@ import butterknife.OnClick;
|
||||
|
||||
public class HomeController extends BaseController {
|
||||
|
||||
public enum HomeDemoModel {
|
||||
private enum HomeDemoModel {
|
||||
NAVIGATION("Navigation Demos", R.color.red_300),
|
||||
TRANSITIONS("Transition Demos", R.color.blue_grey_300),
|
||||
SHARED_ELEMENT_TRANSITIONS("Shared Element Demos", R.color.purple_300),
|
||||
CHILD_CONTROLLERS("Child Controllers", R.color.orange_300),
|
||||
VIEW_PAGER("ViewPager", R.color.green_300),
|
||||
TARGET_CONTROLLER("Target Controller", R.color.pink_300),
|
||||
@@ -46,8 +52,7 @@ public class HomeController extends BaseController {
|
||||
MASTER_DETAIL("Master Detail", R.color.grey_300),
|
||||
DRAG_DISMISS("Drag Dismiss", R.color.lime_300),
|
||||
RX_LIFECYCLE("Rx Lifecycle", R.color.teal_300),
|
||||
RX_LIFECYCLE_2("Rx Lifecycle 2", R.color.brown_300),
|
||||
OVERLAY("Overlay Controller", R.color.purple_300);
|
||||
RX_LIFECYCLE_2("Rx Lifecycle 2", R.color.brown_300);
|
||||
|
||||
String title;
|
||||
@ColorRes int color;
|
||||
@@ -58,8 +63,10 @@ public class HomeController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
private static final String KEY_FAB_VISIBILITY = "HomeController.fabVisibility";
|
||||
|
||||
@BindView(R.id.recycler_view) RecyclerView recyclerView;
|
||||
@BindView(R.id.overlay_root) ViewGroup overlayRoot;
|
||||
@BindView(R.id.fab) View fab;
|
||||
|
||||
public HomeController() {
|
||||
setHasOptionsMenu(true);
|
||||
@@ -80,6 +87,20 @@ public class HomeController extends BaseController {
|
||||
recyclerView.setAdapter(new HomeAdapter(LayoutInflater.from(view.getContext()), HomeDemoModel.values()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveViewState(@NonNull View view, @NonNull Bundle outState) {
|
||||
super.onSaveViewState(view, outState);
|
||||
outState.putInt(KEY_FAB_VISIBILITY, fab.getVisibility());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreViewState(@NonNull View view, @NonNull Bundle savedViewState) {
|
||||
super.onRestoreViewState(view, savedViewState);
|
||||
|
||||
//noinspection WrongConstant
|
||||
fab.setVisibility(savedViewState.getInt(KEY_FAB_VISIBILITY));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
@@ -98,30 +119,7 @@ public class HomeController extends BaseController {
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == R.id.about) {
|
||||
SpannableString details = new SpannableString("A small, yet full-featured framework that allows building View-based Android applications");
|
||||
details.setSpan(new AbsoluteSizeSpan(16, true), 0, details.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
|
||||
final String url = "https://github.com/bluelinelabs/Conductor";
|
||||
SpannableString link = new SpannableString(url);
|
||||
link.setSpan(new URLSpan(url) {
|
||||
@Override
|
||||
public void onClick(View widget) {
|
||||
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
|
||||
}
|
||||
}, 0, link.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
|
||||
SpannableStringBuilder content = new SpannableStringBuilder();
|
||||
content.append("Conductor");
|
||||
content.append("\n\n");
|
||||
content.append(details);
|
||||
content.append("\n\n");
|
||||
content.append(link);
|
||||
|
||||
getChildRouter(overlayRoot)
|
||||
.setPopsLastView(true)
|
||||
.setRoot(RouterTransaction.with(new OverlayController(content))
|
||||
.pushChangeHandler(new FadeChangeHandler())
|
||||
.popChangeHandler(new FadeChangeHandler()));
|
||||
onFabClicked(false);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
@@ -132,7 +130,40 @@ public class HomeController extends BaseController {
|
||||
return "Conductor Demos";
|
||||
}
|
||||
|
||||
void onModelRowClick(HomeDemoModel model) {
|
||||
@OnClick(R.id.fab)
|
||||
public void onFabClicked() {
|
||||
onFabClicked(true);
|
||||
}
|
||||
|
||||
private void onFabClicked(boolean fromFab) {
|
||||
SpannableString details = new SpannableString("A small, yet full-featured framework that allows building View-based Android applications");
|
||||
details.setSpan(new AbsoluteSizeSpan(16, true), 0, details.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
|
||||
final String url = "https://github.com/bluelinelabs/Conductor";
|
||||
SpannableString link = new SpannableString(url);
|
||||
link.setSpan(new URLSpan(url) {
|
||||
@Override
|
||||
public void onClick(View widget) {
|
||||
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
|
||||
}
|
||||
}, 0, link.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
|
||||
SpannableStringBuilder description = new SpannableStringBuilder();
|
||||
description.append(details);
|
||||
description.append("\n\n");
|
||||
description.append(link);
|
||||
|
||||
ControllerChangeHandler pushHandler = fromFab ? new TransitionChangeHandlerCompat(new FabToDialogTransitionChangeHandler(), new FadeChangeHandler(false)) : new FadeChangeHandler(false);
|
||||
ControllerChangeHandler popHandler = fromFab ? new TransitionChangeHandlerCompat(new FabToDialogTransitionChangeHandler(), new FadeChangeHandler()) : new FadeChangeHandler();
|
||||
|
||||
getRouter()
|
||||
.pushController(RouterTransaction.with(new DialogController("Conductor", description))
|
||||
.pushChangeHandler(pushHandler)
|
||||
.popChangeHandler(popHandler));
|
||||
|
||||
}
|
||||
|
||||
void onModelRowClick(HomeDemoModel model, int position) {
|
||||
switch (model) {
|
||||
case NAVIGATION:
|
||||
getRouter().pushController(RouterTransaction.with(new NavigationDemoController(0, DisplayUpMode.SHOW_FOR_CHILDREN_ONLY))
|
||||
@@ -160,12 +191,10 @@ public class HomeController extends BaseController {
|
||||
.pushChangeHandler(new FadeChangeHandler())
|
||||
.popChangeHandler(new FadeChangeHandler()));
|
||||
break;
|
||||
case OVERLAY:
|
||||
getChildRouter(overlayRoot)
|
||||
.setPopsLastView(true)
|
||||
.setRoot(RouterTransaction.with(new OverlayController("I'm an overlay!"))
|
||||
.pushChangeHandler(new FadeChangeHandler())
|
||||
.popChangeHandler(new FadeChangeHandler()));
|
||||
case SHARED_ELEMENT_TRANSITIONS:
|
||||
getRouter().pushController(RouterTransaction.with(new CityGridController(model.title, model.color, position))
|
||||
.pushChangeHandler(new TransitionChangeHandlerCompat(new ArcFadeMoveChangeHandler(), new FadeChangeHandler()))
|
||||
.popChangeHandler(new TransitionChangeHandlerCompat(new ArcFadeMoveChangeHandler(), new FadeChangeHandler())));
|
||||
break;
|
||||
case DRAG_DISMISS:
|
||||
getRouter().pushController(RouterTransaction.with(new DragDismissController())
|
||||
@@ -212,7 +241,7 @@ public class HomeController extends BaseController {
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
holder.bind(items[position]);
|
||||
holder.bind(position, items[position]);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -225,21 +254,28 @@ public class HomeController extends BaseController {
|
||||
@BindView(R.id.tv_title) TextView tvTitle;
|
||||
@BindView(R.id.img_dot) ImageView imgDot;
|
||||
private HomeDemoModel model;
|
||||
private int position;
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ButterKnife.bind(this, itemView);
|
||||
}
|
||||
|
||||
void bind(HomeDemoModel item) {
|
||||
void bind(int position, HomeDemoModel item) {
|
||||
model = item;
|
||||
tvTitle.setText(item.title);
|
||||
imgDot.getDrawable().setColorFilter(ContextCompat.getColor(getActivity(), item.color), Mode.SRC_ATOP);
|
||||
this.position = position;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
tvTitle.setTransitionName(getResources().getString(R.string.transition_tag_title_indexed, position));
|
||||
imgDot.setTransitionName(getResources().getString(R.string.transition_tag_dot_indexed, position));
|
||||
}
|
||||
}
|
||||
|
||||
@OnClick(R.id.row_root)
|
||||
void onRowClick() {
|
||||
onModelRowClick(model);
|
||||
onModelRowClick(model, position);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package com.bluelinelabs.conductor.demo.controllers;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.bluelinelabs.conductor.demo.R;
|
||||
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
|
||||
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
|
||||
|
||||
import butterknife.BindView;
|
||||
|
||||
public class OverlayController extends BaseController {
|
||||
|
||||
private static final String KEY_TEXT = "OverlayController.text";
|
||||
|
||||
@BindView(R.id.text_view) TextView textView;
|
||||
|
||||
public OverlayController(CharSequence text) {
|
||||
this(new BundleBuilder(new Bundle())
|
||||
.putCharSequence(KEY_TEXT, text)
|
||||
.build());
|
||||
}
|
||||
|
||||
public OverlayController(Bundle args) {
|
||||
super(args);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
|
||||
return inflater.inflate(R.layout.controller_overlay, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewBound(@NonNull View view) {
|
||||
super.onViewBound(view);
|
||||
textView.setText(getArgs().getCharSequence(KEY_TEXT));
|
||||
textView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,9 +8,11 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
import com.bluelinelabs.conductor.Router;
|
||||
import com.bluelinelabs.conductor.RouterTransaction;
|
||||
import com.bluelinelabs.conductor.demo.R;
|
||||
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
|
||||
import com.bluelinelabs.conductor.support.ControllerPagerAdapter;
|
||||
import com.bluelinelabs.conductor.support.RouterPagerAdapter;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -23,13 +25,16 @@ public class PagerController extends BaseController {
|
||||
@BindView(R.id.tab_layout) TabLayout tabLayout;
|
||||
@BindView(R.id.view_pager) ViewPager viewPager;
|
||||
|
||||
private final ControllerPagerAdapter pagerAdapter;
|
||||
private final RouterPagerAdapter pagerAdapter;
|
||||
|
||||
public PagerController() {
|
||||
pagerAdapter = new ControllerPagerAdapter(this, false) {
|
||||
pagerAdapter = new RouterPagerAdapter(this) {
|
||||
@Override
|
||||
public Controller getItem(int position) {
|
||||
return new ChildController(String.format(Locale.getDefault(), "Child #%d (Swipe to see more)", position), PAGE_COLORS[position], true);
|
||||
public void configureRouter(@NonNull Router router, int position) {
|
||||
if (!router.hasRootController()) {
|
||||
Controller page = new ChildController(String.format(Locale.getDefault(), "Child #%d (Swipe to see more)", position), PAGE_COLORS[position], true);
|
||||
router.setRoot(RouterTransaction.with(page));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -50,17 +50,19 @@ public class ParentController extends BaseController {
|
||||
childController.addLifecycleListener(new LifecycleListener() {
|
||||
@Override
|
||||
public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
|
||||
if (changeType == ControllerChangeType.PUSH_ENTER && !hasShownAll) {
|
||||
if (index < NUMBER_OF_CHILDREN - 1) {
|
||||
addChild(index + 1);
|
||||
} else {
|
||||
hasShownAll = true;
|
||||
}
|
||||
} else if (changeType == ControllerChangeType.POP_EXIT) {
|
||||
if (index > 0) {
|
||||
removeChild(index - 1);
|
||||
} else {
|
||||
getRouter().popController(ParentController.this);
|
||||
if (!isBeingDestroyed()) {
|
||||
if (changeType == ControllerChangeType.PUSH_ENTER && !hasShownAll) {
|
||||
if (index < NUMBER_OF_CHILDREN - 1) {
|
||||
addChild(index + 1);
|
||||
} else {
|
||||
hasShownAll = true;
|
||||
}
|
||||
} else if (changeType == ControllerChangeType.POP_EXIT) {
|
||||
if (index > 0) {
|
||||
removeChild(index - 1);
|
||||
} else {
|
||||
getRouter().popController(ParentController.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.bluelinelabs.conductor.demo.controllers;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -61,6 +62,7 @@ public class RxLifecycle2Controller extends RxController {
|
||||
Log.i(TAG, "onCreateView() called");
|
||||
|
||||
View view = inflater.inflate(R.layout.controller_rxlifecycle, container, false);
|
||||
view.setBackgroundColor(ContextCompat.getColor(container.getContext(), R.color.brown_300));
|
||||
unbinder = ButterKnife.bind(this, view);
|
||||
|
||||
tvTitle.setText(getResources().getString(R.string.rxlifecycle_title, TAG));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.bluelinelabs.conductor.demo.controllers;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -61,6 +62,7 @@ public class RxLifecycleController extends RxController {
|
||||
Log.i(TAG, "onCreateView() called");
|
||||
|
||||
View view = inflater.inflate(R.layout.controller_rxlifecycle, container, false);
|
||||
view.setBackgroundColor(ContextCompat.getColor(container.getContext(), R.color.teal_300));
|
||||
unbinder = ButterKnife.bind(this, view);
|
||||
|
||||
tvTitle.setText(getResources().getString(R.string.rxlifecycle_title, TAG));
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
/*
|
||||
* Copyright 2015 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.bluelinelabs.conductor.demo.util;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.TimeInterpolator;
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.view.animation.FastOutLinearInInterpolator;
|
||||
import android.support.v4.view.animation.FastOutSlowInInterpolator;
|
||||
import android.support.v4.view.animation.LinearOutSlowInInterpolator;
|
||||
import android.transition.Transition;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.FloatProperty;
|
||||
import android.util.IntProperty;
|
||||
import android.util.Property;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Utility methods for working with animations.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public class AnimUtils {
|
||||
|
||||
private AnimUtils() { }
|
||||
|
||||
private static Interpolator fastOutSlowIn;
|
||||
private static Interpolator fastOutLinearIn;
|
||||
private static Interpolator linearOutSlowIn;
|
||||
private static Interpolator linear;
|
||||
|
||||
@NonNull
|
||||
public static Interpolator getFastOutSlowInInterpolator() {
|
||||
if (fastOutSlowIn == null) {
|
||||
fastOutSlowIn = new FastOutSlowInInterpolator();
|
||||
}
|
||||
return fastOutSlowIn;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Interpolator getFastOutLinearInInterpolator() {
|
||||
if (fastOutLinearIn == null) {
|
||||
fastOutLinearIn = new FastOutLinearInInterpolator();
|
||||
}
|
||||
return fastOutLinearIn;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Interpolator getLinearOutSlowInInterpolator() {
|
||||
if (linearOutSlowIn == null) {
|
||||
linearOutSlowIn = new LinearOutSlowInInterpolator();
|
||||
}
|
||||
return linearOutSlowIn;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Interpolator getLinearInterpolator() {
|
||||
if (linear == null) {
|
||||
linear = new LinearInterpolator();
|
||||
}
|
||||
return linear;
|
||||
}
|
||||
|
||||
/**
|
||||
* Linear interpolate between a and b with parameter t.
|
||||
*/
|
||||
public static float lerp(float a, float b, float t) {
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
||||
/**
|
||||
* A delegate for creating a {@link Property} of <code>int</code> type.
|
||||
*/
|
||||
public static abstract class IntProp<T> {
|
||||
|
||||
public final String name;
|
||||
|
||||
public IntProp(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public abstract void set(T object, int value);
|
||||
public abstract int get(T object);
|
||||
}
|
||||
|
||||
/**
|
||||
* The animation framework has an optimization for <code>Properties</code> of type
|
||||
* <code>int</code> but it was only made public in API24, so wrap the impl in our own type
|
||||
* and conditionally create the appropriate type, delegating the implementation.
|
||||
*/
|
||||
public static <T> Property<T, Integer> createIntProperty(final IntProp<T> impl) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
return new IntProperty<T>(impl.name) {
|
||||
@Override
|
||||
public Integer get(T object) {
|
||||
return impl.get(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(T object, int value) {
|
||||
impl.set(object, value);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return new Property<T, Integer>(Integer.class, impl.name) {
|
||||
@Override
|
||||
public Integer get(T object) {
|
||||
return impl.get(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(T object, Integer value) {
|
||||
impl.set(object, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A delegate for creating a {@link Property} of <code>float</code> type.
|
||||
*/
|
||||
public static abstract class FloatProp<T> {
|
||||
|
||||
public final String name;
|
||||
|
||||
protected FloatProp(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public abstract void set(T object, float value);
|
||||
public abstract float get(T object);
|
||||
}
|
||||
|
||||
/**
|
||||
* The animation framework has an optimization for <code>Properties</code> of type
|
||||
* <code>float</code> but it was only made public in API24, so wrap the impl in our own type
|
||||
* and conditionally create the appropriate type, delegating the implementation.
|
||||
*/
|
||||
public static <T> Property<T, Float> createFloatProperty(final FloatProp<T> impl) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
return new FloatProperty<T>(impl.name) {
|
||||
@Override
|
||||
public Float get(T object) {
|
||||
return impl.get(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(T object, float value) {
|
||||
impl.set(object, value);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return new Property<T, Float>(Float.class, impl.name) {
|
||||
@Override
|
||||
public Float get(T object) {
|
||||
return impl.get(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(T object, Float value) {
|
||||
impl.set(object, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* https://halfthought.wordpress.com/2014/11/07/reveal-transition/
|
||||
* <p/>
|
||||
* Interrupting Activity transitions can yield an OperationNotSupportedException when the
|
||||
* transition tries to pause the animator. Yikes! We can fix this by wrapping the Animator:
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
public static class NoPauseAnimator extends Animator {
|
||||
private final Animator mAnimator;
|
||||
private final ArrayMap<AnimatorListener, AnimatorListener> mListeners = new ArrayMap<>();
|
||||
|
||||
public NoPauseAnimator(Animator animator) {
|
||||
mAnimator = animator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(AnimatorListener listener) {
|
||||
AnimatorListener wrapper = new AnimatorListenerWrapper(this, listener);
|
||||
if (!mListeners.containsKey(listener)) {
|
||||
mListeners.put(listener, wrapper);
|
||||
mAnimator.addListener(wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
mAnimator.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
mAnimator.end();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDuration() {
|
||||
return mAnimator.getDuration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeInterpolator getInterpolator() {
|
||||
return mAnimator.getInterpolator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInterpolator(TimeInterpolator timeInterpolator) {
|
||||
mAnimator.setInterpolator(timeInterpolator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<AnimatorListener> getListeners() {
|
||||
return new ArrayList<>(mListeners.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStartDelay() {
|
||||
return mAnimator.getStartDelay();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStartDelay(long delayMS) {
|
||||
mAnimator.setStartDelay(delayMS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPaused() {
|
||||
return mAnimator.isPaused();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return mAnimator.isRunning();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStarted() {
|
||||
return mAnimator.isStarted();
|
||||
}
|
||||
|
||||
/* We don't want to override pause or resume methods because we don't want them
|
||||
* to affect mAnimator.
|
||||
public void pause();
|
||||
|
||||
public void resume();
|
||||
|
||||
public void addPauseListener(AnimatorPauseListener listener);
|
||||
|
||||
public void removePauseListener(AnimatorPauseListener listener);
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void removeAllListeners() {
|
||||
mListeners.clear();
|
||||
mAnimator.removeAllListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(AnimatorListener listener) {
|
||||
AnimatorListener wrapper = mListeners.get(listener);
|
||||
if (wrapper != null) {
|
||||
mListeners.remove(listener);
|
||||
mAnimator.removeListener(wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Animator setDuration(long durationMS) {
|
||||
mAnimator.setDuration(durationMS);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTarget(Object target) {
|
||||
mAnimator.setTarget(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupEndValues() {
|
||||
mAnimator.setupEndValues();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupStartValues() {
|
||||
mAnimator.setupStartValues();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
mAnimator.start();
|
||||
}
|
||||
}
|
||||
|
||||
public static class AnimatorListenerWrapper implements Animator.AnimatorListener {
|
||||
private final Animator mAnimator;
|
||||
private final Animator.AnimatorListener mListener;
|
||||
|
||||
AnimatorListenerWrapper(Animator animator, Animator.AnimatorListener listener) {
|
||||
mAnimator = animator;
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animator animator) {
|
||||
mListener.onAnimationStart(mAnimator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animator) {
|
||||
mListener.onAnimationEnd(mAnimator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animator) {
|
||||
mListener.onAnimationCancel(mAnimator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animator) {
|
||||
mListener.onAnimationRepeat(mAnimator);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
public static abstract class TransitionEndListener implements Transition.TransitionListener {
|
||||
public abstract void onTransitionCompleted(Transition transition);
|
||||
|
||||
@Override
|
||||
public void onTransitionStart(Transition transition) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionEnd(Transition transition) {
|
||||
onTransitionCompleted(transition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionCancel(Transition transition) {
|
||||
onTransitionCompleted(transition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionPause(Transition transition) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionResume(Transition transition) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.bluelinelabs.conductor.demo.widget;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.support.v4.view.NestedScrollingParent;
|
||||
import android.support.v4.view.animation.FastOutSlowInInterpolator;
|
||||
@@ -32,6 +33,7 @@ import java.util.List;
|
||||
* Applies an elasticity factor to reduce movement as you approach the given dismiss distance.
|
||||
* Optionally also scales down content during drag.
|
||||
*/
|
||||
@TargetApi(21)
|
||||
public class ElasticDragDismissFrameLayout extends FrameLayout implements NestedScrollingParent {
|
||||
|
||||
public static abstract class ElasticDragDismissCallback {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:state_enabled="true"
|
||||
android:state_pressed="true">
|
||||
<objectAnimator
|
||||
android:duration="@android:integer/config_shortAnimTime"
|
||||
android:propertyName="translationZ"
|
||||
android:valueTo="@dimen/touch_raise" />
|
||||
</item>
|
||||
<item>
|
||||
<objectAnimator
|
||||
android:duration="@android:integer/config_shortAnimTime"
|
||||
android:propertyName="translationZ"
|
||||
android:valueTo="0dp" />
|
||||
</item>
|
||||
</selector>
|
||||
|
Before Width: | Height: | Size: 177 B |
|
Before Width: | Height: | Size: 149 B |
|
After Width: | Height: | Size: 487 KiB |
|
Before Width: | Height: | Size: 195 B |
|
After Width: | Height: | Size: 434 KiB |
|
After Width: | Height: | Size: 300 KiB |
|
After Width: | Height: | Size: 338 KiB |
|
After Width: | Height: | Size: 439 KiB |
|
Before Width: | Height: | Size: 259 B |
|
Before Width: | Height: | Size: 308 B |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="@dimen/dialog_corners" />
|
||||
<solid android:color="@color/dialog_background" />
|
||||
</shape>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="36dp"
|
||||
android:height="36dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="@color/white"
|
||||
android:pathData="M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,11 @@
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="@color/dark_icon"
|
||||
android:pathData="M20.38,8.53C20.54,8.13 21.06,6.54 20.21,4.39C20.21,4.39 18.9,4 15.91,6C14.66,5.67 13.33,5.62 12,5.62C10.68,5.62 9.34,5.67 8.09,6C5.1,3.97 3.79,4.39 3.79,4.39C2.94,6.54 3.46,8.13 3.63,8.53C2.61,9.62 2,11 2,12.72C2,19.16 6.16,20.61 12,20.61C17.79,20.61 22,19.16 22,12.72C22,11 21.39,9.62 20.38,8.53M12,19.38C7.88,19.38 4.53,19.19 4.53,15.19C4.53,14.24 5,13.34 5.8,12.61C7.14,11.38 9.43,12.03 12,12.03C14.59,12.03 16.85,11.38 18.2,12.61C19,13.34 19.5,14.23 19.5,15.19C19.5,19.18 16.13,19.38 12,19.38M8.86,13.12C8.04,13.12 7.36,14.12 7.36,15.34C7.36,16.57 8.04,17.58 8.86,17.58C9.69,17.58 10.36,16.58 10.36,15.34C10.36,14.11 9.69,13.12 8.86,13.12M15.14,13.12C14.31,13.12 13.64,14.11 13.64,15.34C13.64,16.58 14.31,17.58 15.14,17.58C15.96,17.58 16.64,16.58 16.64,15.34C16.64,14.11 16,13.12 15.14,13.12Z"
|
||||
/>
|
||||
</vector>
|
||||
@@ -1,9 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="InconsistentLayout">
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.v7.widget.RecyclerView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
/>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
android:id="@+id/city_grid_root"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingStart="@dimen/activity_horizontal_margin"
|
||||
android:paddingEnd="@dimen/activity_horizontal_margin"
|
||||
android:gravity="center_horizontal"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/img_dot"
|
||||
android:layout_width="96dp"
|
||||
android:layout_height="96dp"
|
||||
android:src="@drawable/circle"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_title"
|
||||
style="@style/Base.TextAppearance.AppCompat.Large"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="40dp"
|
||||
/>
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="16dp"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
android:id="@+id/dialog_window"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<View
|
||||
android:id="@+id/dialog_background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/immersive_background"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/dialog_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:orientation="vertical"
|
||||
android:elevation="@dimen/dialog_elevation"
|
||||
android:minWidth="280dp"
|
||||
android:transitionName="@string/fab_dialog_transition_name"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:background="@drawable/dialog_bg"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_title"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:layout_marginRight="24dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:gravity="center_vertical"
|
||||
style="@style/Base.DialogWindowTitle.AppCompat"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="24dp"
|
||||
android:layout_marginLeft="24dp"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/dismiss"
|
||||
style="@style/Widget.AppCompat.Button.Borderless.Colored"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:minWidth="64dp"
|
||||
android:text="@android:string/ok"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
@@ -17,6 +17,7 @@
|
||||
android:textSize="22sp"
|
||||
android:padding="16dp"
|
||||
android:text="@string/drag_to_dismiss"
|
||||
android:clickable="true"
|
||||
/>
|
||||
|
||||
<android.support.v4.widget.NestedScrollView
|
||||
@@ -33,6 +34,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp"
|
||||
android:text="@string/lorem_ipsum"
|
||||
/>
|
||||
|
||||
</android.support.v4.widget.NestedScrollView>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/home_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" >
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
@@ -10,10 +12,20 @@
|
||||
android:layout_height="match_parent"
|
||||
/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/overlay_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginBottom="@dimen/padding_normal"
|
||||
android:layout_marginEnd="@dimen/padding_normal"
|
||||
android:layout_marginRight="@dimen/padding_normal"
|
||||
android:elevation="@dimen/z_fab"
|
||||
android:scaleType="matrix"
|
||||
android:stateListAnimator="@animator/raise"
|
||||
android:transitionName="@string/fab_dialog_transition_name"
|
||||
app:srcCompat="@drawable/ic_github_face"
|
||||
/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#88000000" >
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="32dp"
|
||||
android:background="@android:color/white">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:padding="16dp"
|
||||
/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
@@ -13,6 +14,7 @@
|
||||
android:elevation="6dp"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
||||
tools:targetApi="lollipop"
|
||||
/>
|
||||
|
||||
<android.support.v4.view.ViewPager
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/teal_300"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
|
||||