new feature: scheme

This commit is contained in:
cgspine
2020-05-15 22:17:02 +08:00
parent 235f723616
commit d356f06ad0
31 changed files with 1824 additions and 125 deletions
@@ -0,0 +1,34 @@
/*
* Tencent is pleased to support the open source community by making QMUI_Android available.
*
* Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* 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.qmuiteam.qmui.arch.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface ActivityScheme {
String name();
String[] required() default {};
Class<?> customFactory() default void.class;
String[] keysWithIntValue() default {};
String[] keysWithBoolValue() default {};
String[] keysWithLongValue() default {};
String[] keysWithFloatValue() default {};
String[] keysWithDoubleValue() default {};
}
@@ -0,0 +1,37 @@
/*
* Tencent is pleased to support the open source community by making QMUI_Android available.
*
* Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* 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.qmuiteam.qmui.arch.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface FragmentScheme {
String name();
Class<?>[] activities();
String[] required() default {};
boolean forceNewActivity() default false;
String forceNewActivityKey() default "";
Class<?> customFactory() default void.class;
String[] keysWithIntValue() default {};
String[] keysWithBoolValue() default {};
String[] keysWithLongValue() default {};
String[] keysWithFloatValue() default {};
String[] keysWithDoubleValue() default {};
}
@@ -1,3 +1,18 @@
/*
* Tencent is pleased to support the open source community by making QMUI_Android available.
*
* Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* 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.qmuiteam.qmui.arch;
import com.squareup.javapoet.ClassName;
@@ -5,12 +20,15 @@ import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.WildcardTypeName;
import java.util.List;
import java.util.Map;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
@@ -25,14 +43,22 @@ import static javax.lang.model.element.ElementKind.INTERFACE;
public abstract class BaseProcessor extends AbstractProcessor {
static final String ACTIVITY_TYPE = "android.app.Activity";
static final String FRAGMENT_ACTIVITY_TYPE = "androidx.fragment.app.FragmentActivity";
static final String FRAGMENT_TYPE = "androidx.fragment.app.Fragment";
static final String QMUI_FRAGMENT_ACTIVITY_TYPE = "com.qmuiteam.qmui.arch.QMUIFragmentActivity";
static final String QMUI_FRAGMENT_TYPE = "com.qmuiteam.qmui.arch.QMUIFragment";
static final String QMUI_ACTIVITY_TYPE = "com.qmuiteam.qmui.arch.QMUIActivity";
static final ClassName QMUIFragmentActivityName = ClassName.get(
"com.qmuiteam.qmui.arch", "QMUIFragmentActivity");
static final ClassName QMUIFragmentName = ClassName.get(
"com.qmuiteam.qmui.arch", "QMUIFragment");
static ClassName MapName = ClassName.get("java.util", "Map");
static ClassName ListName = ClassName.get("java.util", "List");
static ClassName ArrayMapName = ClassName.get("android.util", "ArrayMap");
static ClassName ArrayListName = ClassName.get("java.util", "ArrayList");
static ClassName HashMapName = ClassName.get("java.util", "HashMap");
static ClassName IntegerName = ClassName.get("java.lang", "Integer");
static ClassName StringName = ClassName.get("java.lang", "String");
@@ -140,4 +166,27 @@ public abstract class BaseProcessor extends AbstractProcessor {
return typeMirror instanceof DeclaredType
&& ((DeclaredType) typeMirror).asElement().getKind() == INTERFACE;
}
static AnnotationMirror getAnnotationMirror(Element element, Class<?> annotation) {
List<? extends AnnotationMirror> list = element.getAnnotationMirrors();
if (list == null || list.isEmpty()) {
return null;
}
for (AnnotationMirror item : list) {
if (item.getAnnotationType().toString().equals(annotation.getName())) {
return item;
}
}
return null;
}
static AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror, String key) {
Map<? extends ExecutableElement, ? extends AnnotationValue> map = annotationMirror.getElementValues();
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> item : map.entrySet()) {
if (item.getKey().getSimpleName().toString().equals(key)) {
return item.getValue();
}
}
return null;
}
}
@@ -1,3 +1,18 @@
/*
* Tencent is pleased to support the open source community by making QMUI_Android available.
*
* Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* 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.qmuiteam.qmui.arch;
import com.google.auto.service.AutoService;
@@ -0,0 +1,388 @@
/*
* Tencent is pleased to support the open source community by making QMUI_Android available.
*
* Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* 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.qmuiteam.qmui.arch;
import com.google.auto.service.AutoService;
import com.google.common.collect.Lists;
import com.qmuiteam.qmui.arch.annotation.ActivityScheme;
import com.qmuiteam.qmui.arch.annotation.FragmentScheme;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.MirroredTypesException;
import javax.lang.model.type.TypeMirror;
@AutoService(Processor.class)
public class SchemeProcessor extends BaseProcessor {
private static String QMUISchemeIntentFactoryType = "com.qmuiteam.qmui.arch.scheme.QMUISchemeIntentFactory";
private static String QMUISchemeFragmentFactoryType = "com.qmuiteam.qmui.arch.scheme.QMUISchemeFragmentFactory";
private static ClassName SchemeMap = ClassName.get(
"com.qmuiteam.qmui.arch.scheme", "SchemeMap");
private static ClassName SchemeItem = ClassName.get(
"com.qmuiteam.qmui.arch.scheme", "SchemeItem");
private static ClassName ActivitySchemeItem = ClassName.get(
"com.qmuiteam.qmui.arch.scheme", "ActivitySchemeItem");
private static ClassName FragmentSchemeItem = ClassName.get(
"com.qmuiteam.qmui.arch.scheme", "FragmentSchemeItem");
private static TypeName SchemeItemList = ParameterizedTypeName.get(ListName, SchemeItem);
private static TypeName MapByAction = ParameterizedTypeName.get(MapName,
StringName, SchemeItemList);
private static TypeName MapForSchemeRequired = ParameterizedTypeName.get(ArrayMapName,
StringName, StringName);
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> activitySchemes = roundEnv.getElementsAnnotatedWith(ActivityScheme.class);
Set<? extends Element> fragmentSchemes = roundEnv.getElementsAnnotatedWith(FragmentScheme.class);
if (activitySchemes.isEmpty() && fragmentSchemes.isEmpty()) {
return true;
}
TypeSpec.Builder classBuilder = TypeSpec
.classBuilder(SchemeMap.simpleName() + "Impl")
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(SchemeMap);
classBuilder.addField(FieldSpec.builder(MapByAction, "mSchemeMap")
.addModifiers(Modifier.PRIVATE)
.build());
MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addStatement("mSchemeMap = new $T<>()", HashMapName);
Map<String, List<Item>> schemeMap = new HashMap<>();
for (Element element : activitySchemes) {
if (element instanceof TypeElement) {
TypeElement classElement = (TypeElement) element;
TypeMirror elementType = classElement.asType();
boolean isActivity = isSubtypeOfType(elementType, ACTIVITY_TYPE);
if (!isActivity) {
error(element, "Must annotated on subclasses of Activity");
} else {
ActivityScheme annotation = element.getAnnotation(ActivityScheme.class);
String name = annotation.name();
List<Item> elements = schemeMap.get(name);
if (elements == null) {
elements = new ArrayList<>();
schemeMap.put(name, elements);
}
elements.add(new Item(true, classElement, elementType, annotation.required()));
}
}
}
for (Element element : fragmentSchemes) {
if (element instanceof TypeElement) {
TypeElement classElement = (TypeElement) element;
TypeMirror elementType = classElement.asType();
boolean isQMUIFragment = isSubtypeOfType(elementType, QMUI_FRAGMENT_TYPE);
if (!isQMUIFragment) {
error(element, "Must annotated on subclasses of QMUIFragment");
} else {
FragmentScheme annotation = element.getAnnotation(FragmentScheme.class);
String name = annotation.name();
List<Item> elements = schemeMap.get(name);
if (elements == null) {
elements = new ArrayList<>();
schemeMap.put(name, elements);
}
elements.add(new Item(false, classElement, elementType, annotation.required()));
}
}
}
constructorBuilder.addStatement("$T elements", SchemeItemList);
constructorBuilder.addStatement("$T required = null", MapForSchemeRequired);
for (String key : schemeMap.keySet()) {
List<Item> items = schemeMap.get(key);
constructorBuilder.addStatement("elements = new $T<>()", ArrayListName);
items.sort(new Comparator<Item>() {
@Override
public int compare(Item item, Item t1) {
int c1 = item.getRequiredCount();
int c2 = t1.getRequiredCount();
return Integer.compare(c2, c1);
}
});
for (Item item : items) {
ClassName elementName = ClassName.get(item.element);
if (item.isActivity) {
ActivityScheme annotation = item.element.getAnnotation(ActivityScheme.class);
AnnotationMirror annotationMirror = getAnnotationMirror(item.element, ActivityScheme.class);
if (annotationMirror == null) {
continue;
}
appendRequired(constructorBuilder, annotation.required());
CodeBlock customFactory = generateCustomFactory(true, annotationMirror);
CodeBlock intParam = generateTypedParams(annotation.keysWithIntValue());
CodeBlock boolParam = generateTypedParams(annotation.keysWithBoolValue());
CodeBlock longParam = generateTypedParams(annotation.keysWithLongValue());
CodeBlock floatParam = generateTypedParams(annotation.keysWithFloatValue());
CodeBlock doubleParam = generateTypedParams(annotation.keysWithDoubleValue());
CodeBlock codeBlock = CodeBlock.builder()
.add("elements.add(")
/**/.add("new $T(", ActivitySchemeItem)
/*---*/.add("$T.class", elementName)
/*---*/.add(",")
/*---*/.add(customFactory)
/*---*/.add(",")
/*---*/.add("required")
/*---*/.add(",")
/*---*/.add(intParam)
/*---*/.add(",")
/*---*/.add(boolParam)
/*---*/.add(",")
/*---*/.add(longParam)
/*---*/.add(",")
/*---*/.add(floatParam)
/*---*/.add(",")
/*---*/.add(doubleParam)
/**/.add(")")
.add(")")
.build();
constructorBuilder.addStatement(codeBlock);
} else {
FragmentScheme annotation = item.element.getAnnotation(FragmentScheme.class);
AnnotationMirror annotationMirror = getAnnotationMirror(item.element, FragmentScheme.class);
if (annotationMirror == null) {
continue;
}
appendRequired(constructorBuilder, annotation.required());
CodeBlock customFactory = generateCustomFactory(false, annotationMirror);
CodeBlock activities = generateFragmentHostActivityList(annotation);
CodeBlock intParam = generateTypedParams(annotation.keysWithIntValue());
CodeBlock boolParam = generateTypedParams(annotation.keysWithBoolValue());
CodeBlock longParam = generateTypedParams(annotation.keysWithLongValue());
CodeBlock floatParam = generateTypedParams(annotation.keysWithFloatValue());
CodeBlock doubleParam = generateTypedParams(annotation.keysWithDoubleValue());
CodeBlock codeBlock = CodeBlock.builder()
.add("elements.add(")
/**/.add("new $T(", FragmentSchemeItem)
/*---*/.add("$T.class", elementName)
/*---*/.add(",")
/*---*/.add(activities)
/*---*/.add(",")
/*---*/.add(customFactory)
/*---*/.add(",")
/*---*/.add("$L", annotation.forceNewActivity())
/*---*/.add(",")
/*---*/.add("$S", annotation.forceNewActivityKey())
/*---*/.add(",")
/*---*/.add("required")
/*---*/.add(",")
/*---*/.add(intParam)
/*---*/.add(",")
/*---*/.add(boolParam)
/*---*/.add(",")
/*---*/.add(longParam)
/*---*/.add(",")
/*---*/.add(floatParam)
/*---*/.add(",")
/*---*/.add(doubleParam)
/**/.add(")")
.add(")")
.build();
constructorBuilder.addStatement(codeBlock);
}
}
constructorBuilder.addStatement("mSchemeMap.put($S, elements)", key);
}
ExecutableElement findScheme = getOverrideMethod(
SchemeMap, "findScheme");
List<? extends VariableElement> findSchemeParams = findScheme.getParameters();
String schemeAction = findSchemeParams.get(0).getSimpleName().toString();
String schemeParam = findSchemeParams.get(1).getSimpleName().toString();
MethodSpec.Builder getRecordMetaById = MethodSpec.overriding(findScheme)
.addStatement("$T list = mSchemeMap.get($L)", SchemeItemList, schemeAction)
.beginControlFlow("if(list == null || list.isEmpty())")
/**/.addStatement("return null")
.endControlFlow()
.beginControlFlow("for (int i = 0; i < list.size(); i++)")
/**/.addStatement("$T item = list.get(i)", SchemeItem)
/**/.beginControlFlow("if(item.match($L))", schemeParam)
/*--*/.addStatement("return item")
/**/.endControlFlow()
.endControlFlow()
.addStatement("return null");
ExecutableElement exists = getOverrideMethod(
SchemeMap, "exists");
MethodSpec.Builder getRecordMetaByClass = MethodSpec.overriding(exists)
.addStatement("return mSchemeMap.containsKey($L)", exists.getParameters().get(0).getSimpleName().toString());
classBuilder
.addMethod(constructorBuilder.build())
.addMethod(getRecordMetaById.build())
.addMethod(getRecordMetaByClass.build());
try {
JavaFile.builder(SchemeMap.packageName(), classBuilder.build())
.build().writeTo(mFiler);
} catch (IOException e) {
error(null, "Unable to generate RecordMetaMapImpl: %s", e.getMessage());
}
return true;
}
private void appendRequired(MethodSpec.Builder constructorBuilder, String[] required) {
if (required == null || required.length == 0) {
constructorBuilder.addStatement("required =null");
return;
}
constructorBuilder.addStatement("required = new $T<>()", ArrayMapName);
for (int i = 0; i < required.length; i++) {
String condition = required[i];
if (condition == null || condition.isEmpty()) {
continue;
}
int index = condition.indexOf("=");
if (index < 0 || index >= condition.length()) {
constructorBuilder.addStatement("required.put($S, null)", condition);
} else {
String key = condition.substring(0, index);
String value = index == condition.length() - 1 ? "" : condition.substring(index + 1);
constructorBuilder.addStatement("required.put($S, $S)", key, value);
}
}
}
private CodeBlock generateTypedParams(String[] keys) {
CodeBlock.Builder builder = CodeBlock.builder();
if (keys == null || keys.length == 0) {
builder.add("null");
} else {
builder.add("new $T[]{", StringName);
for (int i = 0; i < keys.length; i++) {
if (i != 0) {
builder.add(",");
}
builder.add("$S", keys[i]);
}
builder.add("}");
}
return builder.build();
}
private CodeBlock generateCustomFactory(boolean isActivity, AnnotationMirror annotationMirror){
AnnotationValue customFactory = getAnnotationValue(annotationMirror, "customFactory");
if (customFactory == null) {
return CodeBlock.of("null");
}
TypeMirror typeMirror = (TypeMirror) customFactory.getValue();
if(isActivity){
if (!isSubtypeOfType(typeMirror, QMUISchemeIntentFactoryType)) {
throw new IllegalStateException("customFactory must implement interface QMUISchemeIntentFactory.");
}
}else{
if (!isSubtypeOfType(typeMirror, QMUISchemeFragmentFactoryType)) {
throw new IllegalStateException("customFactory must implement interface QMUISchemeFragmentFactory.");
}
}
return CodeBlock.of("$T.class", typeMirror);
}
private CodeBlock generateFragmentHostActivityList(FragmentScheme fragmentScheme){
CodeBlock.Builder builder = CodeBlock.builder();
TypeMirror[] activities = null;
try {
fragmentScheme.activities();
} catch (MirroredTypesException mte) {
List<? extends TypeMirror> containerMirrors = mte.getTypeMirrors();
activities = new TypeMirror[containerMirrors.size()];
for (int i = 0; i < activities.length; i++) {
activities[i] = containerMirrors.get(i);
}
}
if(activities == null || activities.length == 0){
throw new IllegalStateException("FragmentScheme#activities can not be empty.");
}
builder.add("new $T[]{", OriginClassName);
for(int i=0; i < activities.length; i++){
TypeMirror item = activities[i];
if(!isSubtypeOfType(item, QMUI_FRAGMENT_ACTIVITY_TYPE)){
throw new IllegalStateException("FragmentScheme#activities must be QMUIFragmentActivity.");
}
if(i > 0){
builder.add(",");
}
builder.add("$T.class", ClassName.get(item));
}
builder.add("}");
return builder.build();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
types.add(ActivityScheme.class.getCanonicalName());
types.add(FragmentScheme.class.getCanonicalName());
return types;
}
static class Item {
boolean isActivity = false;
TypeElement element;
TypeMirror type;
String[] required;
public Item(boolean isActivity, TypeElement element, TypeMirror type, String[] required) {
this.isActivity = isActivity;
this.element = element;
this.type = type;
this.required = required;
}
int getRequiredCount(){
return required == null ? 0 : required.length;
}
}
}
@@ -321,8 +321,8 @@ public abstract class QMUIFragment extends Fragment implements
* @param useNewTransitionConfigWhenPop if true, use animation generated by transition C->D,
* else, use animation generated by transition B->C
*/
protected int startFragmentAndDestroyCurrent(final QMUIFragment fragment,
final boolean useNewTransitionConfigWhenPop) {
protected int startFragmentAndDestroyCurrent(QMUIFragment fragment,
boolean useNewTransitionConfigWhenPop) {
if (!checkStateLoss("startFragmentAndDestroyCurrent")) {
return -1;
}
@@ -337,7 +337,7 @@ public abstract class QMUIFragment extends Fragment implements
}
}
final QMUIFragment.TransitionConfig transitionConfig = fragment.onFetchTransitionConfig();
QMUIFragment.TransitionConfig transitionConfig = fragment.onFetchTransitionConfig();
String tagName = fragment.getClass().getSimpleName();
FragmentManager fragmentManager = provider.getContainerFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction()
@@ -346,52 +346,7 @@ public abstract class QMUIFragment extends Fragment implements
transitionConfig.popenter, transitionConfig.popout)
.replace(provider.getContextViewId(), fragment, tagName);
int index = transaction.commit();
Utils.findAndModifyOpInBackStackRecord(fragmentManager, -1, new Utils.OpHandler() {
@Override
public boolean handle(Object op) {
Field cmdField = null;
try {
cmdField = Utils.getOpCmdField(op);
cmdField.setAccessible(true);
int cmd = (int) cmdField.get(op);
if (cmd == 1) {
if (useNewTransitionConfigWhenPop) {
Field popEnterAnimField = Utils.getOpPopEnterAnimField(op);
popEnterAnimField.setAccessible(true);
popEnterAnimField.set(op, transitionConfig.popenter);
Field popExitAnimField = Utils.getOpPopExitAnimField(op);
popExitAnimField.setAccessible(true);
popExitAnimField.set(op, transitionConfig.popout);
}
Field oldFragmentField = Utils.getOpFragmentField(op);
oldFragmentField.setAccessible(true);
Object fragmentObj = oldFragmentField.get(op);
oldFragmentField.set(op, fragment);
Field backStackNestField = Fragment.class.getDeclaredField("mBackStackNesting");
backStackNestField.setAccessible(true);
int oldFragmentBackStackNest = (int) backStackNestField.get(fragmentObj);
backStackNestField.set(fragment, oldFragmentBackStackNest);
backStackNestField.set(fragmentObj, --oldFragmentBackStackNest);
return true;
}
} catch (Throwable e) {
e.printStackTrace();
}
return false;
}
@Override
public boolean needReNameTag() {
return true;
}
@Override
public String newTagName() {
return fragment.getClass().getSimpleName();
}
});
Utils.modifyOpForStartFragmentAndDestroyCurrent(fragmentManager, fragment, useNewTransitionConfigWhenPop, transitionConfig);
return index;
}
@@ -44,8 +44,6 @@ import com.qmuiteam.qmui.util.QMUIStatusBarHelper;
import com.qmuiteam.qmui.util.QMUIWindowInsetHelper;
import com.qmuiteam.qmui.widget.QMUIWindowInsetLayout;
import java.lang.reflect.Field;
/**
* the container activity for {@link QMUIFragment}.
* Created by cgspine on 15/9/14.
@@ -217,67 +215,22 @@ public abstract class QMUIFragmentActivity extends InnerBaseActivity implements
* @param fragment new fragment to start
* @param useNewTransitionConfigWhenPop if true, use animation generated by transition C->D,
* else, use animation generated by transition B->C
*
* @deprecated use {@link QMUIFragment#startFragmentAndDestroyCurrent(QMUIFragment, boolean)}
*/
@Deprecated
public int startFragmentAndDestroyCurrent(final QMUIFragment fragment, final boolean useNewTransitionConfigWhenPop) {
final QMUIFragment.TransitionConfig transitionConfig = fragment.onFetchTransitionConfig();
String tagName = fragment.getClass().getSimpleName();
public int startFragmentAndDestroyCurrent(QMUIFragment fragment, final boolean useNewTransitionConfigWhenPop) {
FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.isStateSaved()) {
QMUILog.d(TAG, "startFragment can not be invoked after onSaveInstanceState");
return -1;
}
QMUIFragment.TransitionConfig transitionConfig = fragment.onFetchTransitionConfig();
String tagName = fragment.getClass().getSimpleName();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction()
.setCustomAnimations(transitionConfig.enter, transitionConfig.exit,
transitionConfig.popenter, transitionConfig.popout)
.replace(getContextViewId(), fragment, tagName);
int index = transaction.commit();
Utils.findAndModifyOpInBackStackRecord(fragmentManager, -1, new Utils.OpHandler() {
@Override
public boolean handle(Object op) {
Field cmdField = null;
try {
cmdField = Utils.getOpCmdField(op);
cmdField.setAccessible(true);
int cmd = (int) cmdField.get(op);
if (cmd == 1) {
if (useNewTransitionConfigWhenPop) {
Field popEnterAnimField = Utils.getOpPopEnterAnimField(op);
popEnterAnimField.setAccessible(true);
popEnterAnimField.set(op, transitionConfig.popenter);
Field popExitAnimField = Utils.getOpPopExitAnimField(op);
popExitAnimField.setAccessible(true);
popExitAnimField.set(op, transitionConfig.popout);
}
Field oldFragmentField = Utils.getOpFragmentField(op);
oldFragmentField.setAccessible(true);
Object fragmentObj = oldFragmentField.get(op);
oldFragmentField.set(op, fragment);
Field backStackNestField = Fragment.class.getDeclaredField("mBackStackNesting");
backStackNestField.setAccessible(true);
int oldFragmentBackStackNest = (int) backStackNestField.get(fragmentObj);
backStackNestField.set(fragment, oldFragmentBackStackNest);
backStackNestField.set(fragmentObj, --oldFragmentBackStackNest);
return true;
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return false;
}
@Override
public boolean needReNameTag() {
return true;
}
@Override
public String newTagName() {
return fragment.getClass().getSimpleName();
}
});
Utils.modifyOpForStartFragmentAndDestroyCurrent(fragmentManager, fragment, useNewTransitionConfigWhenPop, transitionConfig);
return index;
}
@@ -286,15 +239,17 @@ public abstract class QMUIFragmentActivity extends InnerBaseActivity implements
* @param fragment target fragment to start
* @return commit id
*
* @deprecated use {@link QMUIFragment#startFragment(QMUIFragment)}
*/
@Deprecated
public int startFragment(QMUIFragment fragment) {
Log.i(TAG, "startFragment");
FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.isStateSaved()) {
QMUILog.d(TAG, "startFragment can not be invoked after onSaveInstanceState");
return -1;
}
QMUIFragment.TransitionConfig transitionConfig = fragment.onFetchTransitionConfig();
String tagName = fragment.getClass().getSimpleName();
return getSupportFragmentManager()
.beginTransaction()
return fragmentManager.beginTransaction()
.setCustomAnimations(transitionConfig.enter, transitionConfig.exit, transitionConfig.popenter, transitionConfig.popout)
.replace(getContextViewId(), fragment, tagName)
.addToBackStack(tagName)
@@ -29,6 +29,7 @@ import java.util.Stack;
public class QMUISwipeBackActivityManager implements Application.ActivityLifecycleCallbacks {
private static QMUISwipeBackActivityManager sInstance;
private Stack<Activity> mActivityStack = new Stack<>();
private Activity mCurrentActivity = null;
@MainThread
@@ -51,41 +52,45 @@ public class QMUISwipeBackActivityManager implements Application.ActivityLifecyc
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
public void onActivityCreated(@NonNull Activity activity, Bundle savedInstanceState) {
mActivityStack.add(activity);
}
@Override
public void onActivityDestroyed(Activity activity) {
public void onActivityDestroyed(@NonNull Activity activity) {
mActivityStack.remove(activity);
}
@Override
public void onActivityStarted(Activity activity) {
public void onActivityStarted(@NonNull Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
public void onActivityResumed(@NonNull Activity activity) {
mCurrentActivity = activity;
}
@Override
public void onActivityPaused(@NonNull Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
public void onActivityStopped(@NonNull Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
@Nullable
public Activity getCurrentActivity(){
return mCurrentActivity;
}
/**
*
* refer to https://github.com/bingoogolapple/BGASwipeBackLayout-Android/
@@ -29,6 +29,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
@@ -138,6 +139,58 @@ public class Utils {
}
}
static void modifyOpForStartFragmentAndDestroyCurrent(FragmentManager fragmentManager,
final QMUIFragment fragment,
final boolean useNewTransitionConfigWhenPop,
final QMUIFragment.TransitionConfig transitionConfig){
findAndModifyOpInBackStackRecord(fragmentManager, -1, new Utils.OpHandler() {
@Override
public boolean handle(Object op) {
Field cmdField = null;
try {
cmdField = Utils.getOpCmdField(op);
cmdField.setAccessible(true);
int cmd = (int) cmdField.get(op);
if (cmd == 1) {
if (useNewTransitionConfigWhenPop) {
Field popEnterAnimField = Utils.getOpPopEnterAnimField(op);
popEnterAnimField.setAccessible(true);
popEnterAnimField.set(op, transitionConfig.popenter);
Field popExitAnimField = Utils.getOpPopExitAnimField(op);
popExitAnimField.setAccessible(true);
popExitAnimField.set(op, transitionConfig.popout);
}
Field oldFragmentField = Utils.getOpFragmentField(op);
oldFragmentField.setAccessible(true);
Object fragmentObj = oldFragmentField.get(op);
oldFragmentField.set(op, fragment);
Field backStackNestField = Fragment.class.getDeclaredField("mBackStackNesting");
backStackNestField.setAccessible(true);
int oldFragmentBackStackNest = (int) backStackNestField.get(fragmentObj);
backStackNestField.set(fragment, oldFragmentBackStackNest);
backStackNestField.set(fragmentObj, --oldFragmentBackStackNest);
return true;
}
} catch (Throwable e) {
e.printStackTrace();
}
return false;
}
@Override
public boolean needReNameTag() {
return true;
}
@Override
public String newTagName() {
return fragment.getClass().getSimpleName();
}
});
}
static void findAndModifyOpInBackStackRecord(FragmentManager fragmentManager, int backStackIndex, OpHandler handler) {
if (fragmentManager == null || handler == null) {
return;
@@ -0,0 +1,84 @@
/*
* Tencent is pleased to support the open source community by making QMUI_Android available.
*
* Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* 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.qmuiteam.qmui.arch.scheme;
import android.app.Activity;
import android.content.Intent;
import android.util.ArrayMap;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.qmuiteam.qmui.QMUILog;
import java.util.HashMap;
import java.util.Map;
class ActivitySchemeItem extends SchemeItem {
private static HashMap<Class<? extends QMUISchemeIntentFactory>, QMUISchemeIntentFactory> sFactories;
@NonNull
private final Class<? extends Activity> mActivityClass;
@NonNull
private final Class<? extends QMUISchemeIntentFactory> mIntentFactoryCls;
public ActivitySchemeItem(@NonNull Class<? extends Activity> activityClass,
@Nullable Class<? extends QMUISchemeIntentFactory> intentFactoryCls,
@Nullable ArrayMap<String, String> required,
@Nullable String[] keysForInt,
@Nullable String[] keysForBool,
@Nullable String[] keysForLong,
@Nullable String[] keysForFloat,
@Nullable String[] keysForDouble) {
super(required, keysForInt, keysForBool, keysForLong, keysForFloat, keysForDouble);
mActivityClass = activityClass;
if (intentFactoryCls == null) {
mIntentFactoryCls = QMUIDefaultSchemeIntentFactory.class;
} else {
mIntentFactoryCls = intentFactoryCls;
}
}
@Override
public boolean handle(@NonNull Activity activity, @NonNull Map<String, SchemeValue> scheme) {
if (sFactories == null) {
sFactories = new HashMap<>();
}
QMUISchemeIntentFactory factory = sFactories.get(mIntentFactoryCls);
if (factory == null) {
try {
factory = mIntentFactoryCls.newInstance();
sFactories.put(mIntentFactoryCls, factory);
} catch (Exception e) {
QMUILog.printErrStackTrace(QMUISchemeHandler.TAG, e,
"error to instance QMUISchemeIntentFactory: %d", mIntentFactoryCls.getSimpleName());
}
}
if (factory != null) {
if (factory.shouldBlockJump(activity, mActivityClass, scheme)) {
return true;
}
Intent intent = factory.factory(activity, mActivityClass, scheme);
activity.startActivity(intent);
return true;
}
return false;
}
}
@@ -0,0 +1,152 @@
/*
* Tencent is pleased to support the open source community by making QMUI_Android available.
*
* Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* 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.qmuiteam.qmui.arch.scheme;
import android.app.Activity;
import android.content.Intent;
import android.util.ArrayMap;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.qmuiteam.qmui.QMUILog;
import com.qmuiteam.qmui.arch.QMUIFragment;
import com.qmuiteam.qmui.arch.QMUIFragmentActivity;
import java.util.HashMap;
import java.util.Map;
class FragmentSchemeItem extends SchemeItem {
private static HashMap<Class<? extends QMUISchemeFragmentFactory>, QMUISchemeFragmentFactory> sFactories;
private final Class<? extends QMUIFragment> mFragmentCls;
@NonNull
private final Class<? extends QMUIFragmentActivity>[] mActivityClsList;
private final boolean mForceNewActivity;
private final String mForceNewActivityKey;
@NonNull
private final Class<? extends QMUISchemeFragmentFactory> mFragmentFactoryCls;
public FragmentSchemeItem(@NonNull Class<? extends QMUIFragment> fragmentCls,
@NonNull Class<? extends QMUIFragmentActivity>[] activityClsList,
@Nullable Class<? extends QMUISchemeFragmentFactory> fragmentFactoryCls,
boolean forceNewActivity,
@Nullable String forceNewActivityKey,
@Nullable ArrayMap<String, String> required,
@Nullable String[] keysForInt,
@Nullable String[] keysForBool,
@Nullable String[] keysForLong,
@Nullable String[] keysForFloat,
@Nullable String[] keysForDouble) {
super(required, keysForInt, keysForBool, keysForLong, keysForFloat, keysForDouble);
mFragmentCls = fragmentCls;
mActivityClsList = activityClsList;
mForceNewActivity = forceNewActivity;
mForceNewActivityKey = forceNewActivityKey;
if(fragmentFactoryCls == null){
mFragmentFactoryCls = QMUIDefaultSchemeFragmentFactory.class;
}else{
mFragmentFactoryCls = fragmentFactoryCls;
}
}
@Override
public boolean handle(@NonNull Activity activity, @Nullable Map<String, SchemeValue> scheme) {
if (mActivityClsList.length == 0) {
QMUILog.d(QMUISchemeHandler.TAG, "Can not start a new fragment because the host is't provided");
return false;
}
if (sFactories == null) {
sFactories = new HashMap<>();
}
QMUISchemeFragmentFactory factory = sFactories.get(mFragmentFactoryCls);
if (factory == null) {
try {
factory = mFragmentFactoryCls.newInstance();
sFactories.put(mFragmentFactoryCls, factory);
} catch (Exception e) {
QMUILog.printErrStackTrace(QMUISchemeHandler.TAG, e,
"error to instance QMUISchemeFragmentFactory: %d", mFragmentFactoryCls.getSimpleName());
}
}
if (factory == null) {
return false;
}
if(factory.shouldBlockJump(activity, mFragmentCls, scheme)){
return true;
}
if (!isCurrentActivityCanStartFragment(activity) || isForceNewActivity(scheme)) {
Intent intent = factory.factory(activity, mActivityClsList, mFragmentCls, scheme);
if (intent != null) {
activity.startActivity(intent);
return true;
}
return false;
}
QMUIFragmentActivity fragmentActivity = (QMUIFragmentActivity) activity;
QMUIFragment fragment = factory.factory(mFragmentCls, scheme);
if (fragment != null) {
int commitId = fragmentActivity.startFragment(fragment);
if (commitId == -1) {
QMUILog.d(QMUISchemeHandler.TAG, "start fragment failed.");
return false;
}
return true;
}
return false;
}
private boolean isCurrentActivityCanStartFragment(Activity activity) {
if (!(activity instanceof QMUIFragmentActivity)) {
return false;
}
QMUIFragmentActivity fragmentActivity = (QMUIFragmentActivity) activity;
if (fragmentActivity.getSupportFragmentManager().isStateSaved()) {
// use new activity if the state has already been saved.
return false;
}
for (Class<? extends QMUIFragmentActivity> aClass : mActivityClsList) {
if (aClass.isAssignableFrom(activity.getClass())) {
return true;
}
}
return false;
}
private boolean isForceNewActivity(@Nullable Map<String, SchemeValue> scheme) {
if (mForceNewActivity) {
return true;
}
if (scheme == null || scheme.isEmpty()) {
return false;
}
if (scheme.get(QMUISchemeHandler.ARG_FORCE_TO_NEW_ACTIVITY) != null) {
return true;
}
if (mForceNewActivityKey != null) {
SchemeValue schemeValue = scheme.get(mForceNewActivityKey);
return schemeValue != null && schemeValue.type == Boolean.TYPE && ((Boolean) schemeValue.value);
}
return false;
}
}
@@ -0,0 +1,98 @@
/*
* Tencent is pleased to support the open source community by making QMUI_Android available.
*
* Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* 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.qmuiteam.qmui.arch.scheme;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.qmuiteam.qmui.QMUILog;
import com.qmuiteam.qmui.arch.QMUIFragment;
import com.qmuiteam.qmui.arch.QMUIFragmentActivity;
import java.util.Map;
import static com.qmuiteam.qmui.arch.scheme.QMUISchemeHandler.ARG_FROM_SCHEME;
public class QMUIDefaultSchemeFragmentFactory implements QMUISchemeFragmentFactory {
@Override
@Nullable
public QMUIFragment factory(@NonNull Class<? extends QMUIFragment> fragmentCls,
@Nullable Map<String, SchemeValue> scheme) {
try {
QMUIFragment fragment = fragmentCls.newInstance();
fragment.setArguments(createBundleForScheme(scheme));
return fragment;
} catch (Exception e) {
QMUILog.printErrStackTrace(QMUISchemeHandler.TAG, e,
"Error to create fragment: %s", fragmentCls.getSimpleName());
return null;
}
}
@Override
@Nullable
public Intent factory(@NonNull Activity activity,
@NonNull Class<? extends QMUIFragmentActivity>[] activityClassList,
@NonNull Class<? extends QMUIFragment> fragmentCls,
@Nullable Map<String, SchemeValue> scheme) {
Bundle bundle = createBundleForScheme(scheme);
if (activityClassList.length == 0) {
return null;
}
Intent intent = QMUIFragmentActivity.intentOf(activity, activityClassList[0], fragmentCls, bundle);
intent.putExtra(ARG_FROM_SCHEME, true);
return intent;
}
@NonNull
private Bundle createBundleForScheme(@Nullable Map<String, SchemeValue> scheme) {
Bundle bundle = new Bundle();
bundle.putBoolean(QMUISchemeHandler.ARG_FROM_SCHEME, true);
if (scheme != null && !scheme.isEmpty()) {
for (Map.Entry<String, SchemeValue> item : scheme.entrySet()) {
String name = item.getKey();
SchemeValue schemeValue = item.getValue();
if (schemeValue.type == Integer.TYPE) {
bundle.putInt(name, ((int) schemeValue.value));
} else if (schemeValue.type == Boolean.TYPE) {
bundle.putBoolean(name, ((boolean) schemeValue.value));
} else if (schemeValue.type == Long.TYPE) {
bundle.putLong(name, ((long) schemeValue.value));
} else if (schemeValue.type == Float.TYPE) {
bundle.putFloat(name, ((float) schemeValue.value));
} else if (schemeValue.type == Double.TYPE) {
bundle.putDouble(name, ((double) schemeValue.value));
} else {
bundle.putString(name, schemeValue.origin);
}
}
}
return bundle;
}
@Override
public boolean shouldBlockJump(@NonNull Activity activity,
@NonNull Class<? extends QMUIFragment> fragmentCls,
@Nullable Map<String, SchemeValue> scheme) {
return false;
}
}
@@ -0,0 +1,64 @@
/*
* Tencent is pleased to support the open source community by making QMUI_Android available.
*
* Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* 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.qmuiteam.qmui.arch.scheme;
import android.app.Activity;
import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Map;
import static com.qmuiteam.qmui.arch.scheme.QMUISchemeHandler.ARG_FROM_SCHEME;
public class QMUIDefaultSchemeIntentFactory implements QMUISchemeIntentFactory {
@Override
public Intent factory(@NonNull Activity activity,
@NonNull Class<? extends Activity> activityClass,
@Nullable Map<String, SchemeValue> scheme) {
Intent intent = new Intent(activity, activityClass);
intent.putExtra(ARG_FROM_SCHEME, true);
if (scheme != null && !scheme.isEmpty()) {
for (Map.Entry<String, SchemeValue> item : scheme.entrySet()) {
String name = item.getKey();
SchemeValue schemeValue = item.getValue();
if (schemeValue.type == Integer.TYPE) {
intent.putExtra(name, ((int) schemeValue.value));
} else if (schemeValue.type == Boolean.TYPE) {
intent.putExtra(name, ((boolean) schemeValue.value));
} else if (schemeValue.type == Long.TYPE) {
intent.putExtra(name, ((long) schemeValue.value));
} else if (schemeValue.type == Float.TYPE) {
intent.putExtra(name, ((float) schemeValue.value));
} else if (schemeValue.type == Double.TYPE) {
intent.putExtra(name, ((double) schemeValue.value));
} else {
intent.putExtra(name, schemeValue.origin);
}
}
}
return intent;
}
@Override
public boolean shouldBlockJump(@NonNull Activity activity,
@NonNull Class<? extends Activity> activityClass,
@Nullable Map<String, SchemeValue> scheme) {
return false;
}
}
@@ -0,0 +1,44 @@
/*
* Tencent is pleased to support the open source community by making QMUI_Android available.
*
* Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* 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.qmuiteam.qmui.arch.scheme;
import android.app.Activity;
import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.qmuiteam.qmui.arch.QMUIFragment;
import com.qmuiteam.qmui.arch.QMUIFragmentActivity;
import java.util.Map;
public interface QMUISchemeFragmentFactory {
@Nullable
QMUIFragment factory(@NonNull Class<? extends QMUIFragment> fragmentCls,
@Nullable Map<String, SchemeValue> scheme);
@Nullable
Intent factory(@NonNull Activity activity,
@NonNull Class<? extends QMUIFragmentActivity>[] activityClassList,
@NonNull Class<? extends QMUIFragment> fragmentCls,
@Nullable Map<String, SchemeValue> scheme);
boolean shouldBlockJump(@NonNull Activity activity,
@NonNull Class<? extends QMUIFragment> fragmentCls,
@Nullable Map<String, SchemeValue> scheme);
}
@@ -0,0 +1,25 @@
/*
* Tencent is pleased to support the open source community by making QMUI_Android available.
*
* Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* 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.qmuiteam.qmui.arch.scheme;
import android.app.Activity;
import java.util.Map;
public interface QMUISchemeHandleInterpolator {
boolean intercept(Activity activity, String action, Map<String, String> params, String origin);
}
@@ -0,0 +1,202 @@
/*
* Tencent is pleased to support the open source community by making QMUI_Android available.
*
* Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* 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.qmuiteam.qmui.arch.scheme;
import android.app.Activity;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.qmuiteam.qmui.arch.QMUISwipeBackActivityManager;
import com.qmuiteam.qmui.arch.record.QMUILatestVisitStorage;
import com.qmuiteam.qmui.arch.record.RecordIdClassMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class QMUISchemeHandler {
static final String TAG = "QMUISchemeHandler";
public static String ARG_FROM_SCHEME = "__qmui_arg_from_scheme";
public static String ARG_FORCE_TO_NEW_ACTIVITY = "__qmui_force_to_new_activity";
private static SchemeMap sSchemeMap;
static {
try {
Class<?> cls = Class.forName(SchemeMap.class.getCanonicalName() + "Impl");
sSchemeMap = (SchemeMap) cls.newInstance();
} catch (ClassNotFoundException e) {
sSchemeMap = new SchemeMap() {
@Nullable
@Override
public SchemeItem findScheme(@NonNull String schemeName, @Nullable Map<String, String> params) {
return null;
}
@Override
public boolean exists(@NonNull String schemeName) {
return false;
}
};
} catch (IllegalAccessException e) {
throw new RuntimeException("Can not access the Class SchemeMapImpl. " +
"Please file a issue to report this.");
} catch (InstantiationException e) {
throw new RuntimeException("Can not instance the Class SchemeMapImpl. " +
"Please file a issue to report this.");
}
}
private final String mPrefix;
private final List<QMUISchemeHandleInterpolator> mInterpolatorList;
private final long mBlockSameSchemeTimeout;
private String mLastHandledScheme = null;
private long mLastSchemeHandledTime = 0;
private QMUISchemeHandler(Builder builder) {
mPrefix = builder.mPrefix;
List<QMUISchemeHandleInterpolator> interpolatorList = builder.mInterpolatorList;
if(interpolatorList != null && !interpolatorList.isEmpty()){
mInterpolatorList = new ArrayList<>(interpolatorList);
}else{
mInterpolatorList = null;
}
mBlockSameSchemeTimeout = builder.mBlockSameSchemeTimeout;
}
public boolean handle(String scheme){
if(scheme == null || !scheme.startsWith(mPrefix)){
return false;
}
if(scheme.equals(mLastHandledScheme) && System.currentTimeMillis() - mLastSchemeHandledTime < mBlockSameSchemeTimeout){
return true;
}
Activity currentActivity = QMUISwipeBackActivityManager.getInstance().getCurrentActivity();
if(currentActivity == null){
return false;
}
scheme = scheme.substring(mPrefix.length());
String[] elements = scheme.split("\\?");
if(elements.length == 0 || elements[0] == null || elements[0].isEmpty()){
return false;
}
String action = elements[0];
if(!sSchemeMap.exists(action)){
return false;
}
Map<String, String> params;
if(elements.length < 2){
params = null;
}else{
params = parseParams(elements[1]);
}
boolean handled = false;
if(mInterpolatorList != null && !mInterpolatorList.isEmpty()){
for(QMUISchemeHandleInterpolator interpolator: mInterpolatorList){
if(interpolator.intercept(currentActivity, action, params, scheme)){
handled = true;
break;
}
}
}
if(!handled){
SchemeItem schemeItem = sSchemeMap.findScheme(action, params);
if(schemeItem != null){
handled = schemeItem.handle(currentActivity, schemeItem.convertFrom(params));
}
}
if(handled){
mLastHandledScheme = scheme;
mLastSchemeHandledTime = System.currentTimeMillis();
}
return handled;
}
@Nullable
public Map<String, String> parseParams(@Nullable String schemeParams) {
if (schemeParams == null || schemeParams.isEmpty()) {
return null;
}
Map<String, String> queryMap = new HashMap<>();
int start = 0;
do {
int next = schemeParams.indexOf('&', start);
int end = (next == -1) ? schemeParams.length() : next;
if (start == end) {
start += 1;
continue;
}
int separator = schemeParams.indexOf('=', start);
if (separator > end || separator == -1) {
separator = end;
}
if (separator == start) {
start = end + 1;
continue;
}
String name = schemeParams.substring(start, separator);
String value = separator == end ? "" : schemeParams.substring(separator + 1, end);
queryMap.put(name, value);
start = end + 1;
} while (start < schemeParams.length());
return queryMap;
}
public static class Builder {
public static final long BLOCK_SAME_SCHEME_DEFAULT_TIMEOUT = 500;
private String mPrefix;
private List<QMUISchemeHandleInterpolator> mInterpolatorList;
private long mBlockSameSchemeTimeout = BLOCK_SAME_SCHEME_DEFAULT_TIMEOUT;
public Builder(@NonNull String prefix) {
mPrefix = prefix;
}
public Builder addInterpolator(QMUISchemeHandleInterpolator interpolator) {
if(mInterpolatorList == null){
mInterpolatorList = new ArrayList<>();
}
mInterpolatorList.add(interpolator);
return this;
}
public Builder setBlockSameSchemeTimeout(long blockSameSchemeTimeout) {
mBlockSameSchemeTimeout = blockSameSchemeTimeout;
return this;
}
public QMUISchemeHandler build() {
return new QMUISchemeHandler(this);
}
}
}
@@ -0,0 +1,35 @@
/*
* Tencent is pleased to support the open source community by making QMUI_Android available.
*
* Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* 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.qmuiteam.qmui.arch.scheme;
import android.app.Activity;
import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Map;
public interface QMUISchemeIntentFactory {
Intent factory(@NonNull Activity activity,
@NonNull Class<? extends Activity> activityClass,
@Nullable Map<String, SchemeValue> scheme);
boolean shouldBlockJump(@NonNull Activity activity,
@NonNull Class<? extends Activity> activityClass,
@Nullable Map<String, SchemeValue> scheme);
}
@@ -0,0 +1,38 @@
/*
* Tencent is pleased to support the open source community by making QMUI_Android available.
*
* Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* 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.qmuiteam.qmui.arch.scheme;
import android.app.Activity;
import android.net.Uri;
import java.util.Map;
public class QMUISchemeParamValueDecoder implements QMUISchemeHandleInterpolator {
@Override
public boolean intercept(Activity activity, String action, Map<String, String> params, String origin) {
if (params != null) {
for (String key : params.keySet()) {
String oldValue = params.get(key);
if (oldValue != null && oldValue.length() > 0) {
params.put(key, Uri.decode(oldValue));
}
}
}
return false;
}
}
@@ -0,0 +1,132 @@
/*
* Tencent is pleased to support the open source community by making QMUI_Android available.
*
* Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* 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.qmuiteam.qmui.arch.scheme;
import android.app.Activity;
import android.util.ArrayMap;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.qmuiteam.qmui.QMUILog;
import java.util.HashMap;
import java.util.Map;
public abstract class SchemeItem {
@Nullable
private ArrayMap<String, String> mRequired;
@Nullable
private String[] mKeysForInt;
@Nullable
private String[] mKeysForBool;
@Nullable
private String[] mKeysForLong;
@Nullable
private String[] mKeysForFloat;
@Nullable
private String[] mKeysForDouble;
public SchemeItem(@Nullable ArrayMap<String, String> required,
@Nullable String[] keysForInt,
@Nullable String[] keysForBool,
@Nullable String[] keysForLong,
@Nullable String[] keysForFloat,
@Nullable String[] keysForDouble) {
mRequired = required;
mKeysForInt = keysForInt;
mKeysForBool = keysForBool;
mKeysForLong = keysForLong;
mKeysForFloat = keysForFloat;
mKeysForDouble = keysForDouble;
}
@Nullable
public Map<String, SchemeValue> convertFrom(@Nullable Map<String, String> schemeParams) {
if (schemeParams == null || schemeParams.isEmpty()) {
return null;
}
Map<String, SchemeValue> queryMap = new HashMap<>();
for(Map.Entry<String, String> param: schemeParams.entrySet()){
String name = param.getKey();
String value = param.getValue();
if(name == null || name.isEmpty()){
continue;
}
try {
if (contains(mKeysForInt, name)) {
queryMap.put(name, new SchemeValue(value, Integer.valueOf(value), Integer.TYPE));
} else if (QMUISchemeHandler.ARG_FORCE_TO_NEW_ACTIVITY.equals(name) || contains(mKeysForBool, name)) {
boolean isFalse = "0".equals(value) || "false".equals(value.toLowerCase());
queryMap.put(name, new SchemeValue(value, !isFalse, Boolean.TYPE));
} else if (contains(mKeysForLong, name)) {
queryMap.put(name, new SchemeValue(value, Long.valueOf(value), Long.TYPE));
} else if (contains(mKeysForFloat, name)) {
queryMap.put(name, new SchemeValue(value, Float.valueOf(value), Float.TYPE));
} else if (contains(mKeysForDouble, name)) {
queryMap.put(name, new SchemeValue(value, Double.valueOf(value), Double.TYPE));
} else {
queryMap.put(name, new SchemeValue(value, value, String.class));
}
} catch (Exception e) {
QMUILog.printErrStackTrace(QMUISchemeHandler.TAG, e,
"error to parse scheme param: %s = %s", name, value);
}
}
return queryMap;
}
private static boolean contains(@Nullable String[] array, @NonNull String key) {
if (array == null || array.length == 0) {
return false;
}
for (int i = 0; i < array.length; i++) {
if (key.equals(array[i])) {
return true;
}
}
return false;
}
// used by generated code(SchemeMapImpl)
boolean match(@Nullable Map<String, String> scheme) {
if (mRequired == null || mRequired.isEmpty()) {
return true;
}
if (scheme == null || scheme.isEmpty()) {
return false;
}
for (int i = 0; i < mRequired.size(); i++) {
String key = mRequired.keyAt(i);
if(!scheme.containsKey(key)){
return false;
}
String value = mRequired.valueAt(i);
if(value == null){
// if no value. that means scheme must provide this key.
continue;
}
String actual = scheme.get(key);
if (actual == null || !actual.equals(value)) {
return false;
}
}
return true;
}
public abstract boolean handle(@NonNull Activity activity, @Nullable Map<String, SchemeValue> scheme);
}
@@ -0,0 +1,30 @@
/*
* Tencent is pleased to support the open source community by making QMUI_Android available.
*
* Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* 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.qmuiteam.qmui.arch.scheme;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Map;
public interface SchemeMap {
@Nullable
SchemeItem findScheme(@NonNull String schemeAction, @Nullable Map<String, String> params);
boolean exists(@NonNull String schemeAction);
}
@@ -0,0 +1,30 @@
/*
* Tencent is pleased to support the open source community by making QMUI_Android available.
*
* Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* 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.qmuiteam.qmui.arch.scheme;
import androidx.annotation.NonNull;
public class SchemeValue {
final String origin;
final Object value;
final Class<?> type;
public SchemeValue(@NonNull String origin, Object value, Class<?> type) {
this.origin = origin;
this.value = value;
this.type = type;
}
}
@@ -22,6 +22,7 @@ import androidx.core.content.ContextCompat;
import android.view.LayoutInflater;
import android.view.View;
import com.qmuiteam.qmui.arch.annotation.ActivityScheme;
import com.qmuiteam.qmui.widget.QMUITopBarLayout;
import com.qmuiteam.qmuidemo.R;
import com.qmuiteam.qmuidemo.base.BaseActivity;
@@ -30,6 +31,7 @@ import com.qmuiteam.qmuidemo.fragment.lab.QDArchTestFragment;
import butterknife.BindView;
import butterknife.ButterKnife;
@ActivityScheme(name = "arch", required = {"aa", "bb=3"}, keysWithBoolValue = {"aa"})
public class ArchTestActivity extends BaseActivity {
@BindView(R.id.topbar) QMUITopBarLayout mTopBar;
@@ -23,18 +23,20 @@ import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.Toast;
import com.qmuiteam.qmui.arch.QMUILatestVisit;
import com.qmuiteam.qmuidemo.QDMainActivity;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.qmuiteam.qmui.arch.QMUILatestVisit;
import com.qmuiteam.qmui.arch.annotation.ActivityScheme;
import com.qmuiteam.qmuidemo.QDMainActivity;
/**
* @author cginechen
* @date 2016-12-08
*/
@ActivityScheme(name = "launcher")
public class LauncherActivity extends Activity {
private static final int PERMISSIONS_REQUEST_CODE = 10;
@@ -79,9 +81,9 @@ public class LauncherActivity extends Activity {
}
private boolean allPermissionsGranted(){
for(String permission: PERMISSIONS_REQUIRED){
if(ContextCompat.checkSelfPermission(getBaseContext(), permission) != PackageManager.PERMISSION_GRANTED){
private boolean allPermissionsGranted() {
for (String permission : PERMISSIONS_REQUIRED) {
if (ContextCompat.checkSelfPermission(getBaseContext(), permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
@@ -24,6 +24,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.qmuiteam.qmui.arch.annotation.FragmentScheme;
import com.qmuiteam.qmui.arch.annotation.LatestVisitRecord;
import com.qmuiteam.qmui.util.QMUIDisplayHelper;
import com.qmuiteam.qmui.util.QMUIResHelper;
@@ -33,6 +34,7 @@ import com.qmuiteam.qmui.widget.tab.QMUITab;
import com.qmuiteam.qmui.widget.tab.QMUITabBuilder;
import com.qmuiteam.qmui.widget.tab.QMUITabIndicator;
import com.qmuiteam.qmui.widget.tab.QMUITabSegment;
import com.qmuiteam.qmuidemo.QDMainActivity;
import com.qmuiteam.qmuidemo.R;
import com.qmuiteam.qmuidemo.base.BaseFragment;
import com.qmuiteam.qmuidemo.lib.Group;
@@ -56,6 +58,11 @@ import butterknife.ButterKnife;
@LatestVisitRecord
@Widget(group = Group.Other, name = "固定宽度,内容均分")
@FragmentScheme(
name = "tab",
activities = {QDMainActivity.class},
required = {"mode=1"},
keysWithIntValue = {"mode"})
public class QDTabSegmentFixModeFragment extends BaseFragment {
@BindView(R.id.topbar)
QMUITopBarLayout mTopBar;
@@ -19,9 +19,11 @@ package com.qmuiteam.qmuidemo.fragment.components;
import android.view.LayoutInflater;
import android.view.View;
import com.qmuiteam.qmui.arch.annotation.FragmentScheme;
import com.qmuiteam.qmui.widget.QMUITopBarLayout;
import com.qmuiteam.qmui.widget.grouplist.QMUIGroupListView;
import com.qmuiteam.qmui.widget.tab.QMUITabSegment;
import com.qmuiteam.qmuidemo.QDMainActivity;
import com.qmuiteam.qmuidemo.R;
import com.qmuiteam.qmuidemo.base.BaseFragment;
import com.qmuiteam.qmuidemo.lib.annotation.Widget;
@@ -36,6 +38,7 @@ import butterknife.ButterKnife;
* @date 2016-10-21
*/
@FragmentScheme(name = "tab", activities = {QDMainActivity.class})
@Widget(widgetClass = QMUITabSegment.class, iconRes = R.mipmap.icon_grid_tab_segment)
public class QDTabSegmentFragment extends BaseFragment {
@@ -31,6 +31,7 @@ import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import com.qmuiteam.qmui.arch.annotation.FragmentScheme;
import com.qmuiteam.qmui.skin.QMUISkinHelper;
import com.qmuiteam.qmui.skin.QMUISkinValueBuilder;
import com.qmuiteam.qmui.skin.SkinWriter;
@@ -40,6 +41,7 @@ import com.qmuiteam.qmui.widget.tab.QMUITabBuilder;
import com.qmuiteam.qmui.widget.tab.QMUITabIndicator;
import com.qmuiteam.qmui.widget.tab.QMUITabSegment;
import com.qmuiteam.qmui.widget.QMUITopBarLayout;
import com.qmuiteam.qmuidemo.QDMainActivity;
import com.qmuiteam.qmuidemo.R;
import com.qmuiteam.qmuidemo.base.BaseFragment;
import com.qmuiteam.qmuidemo.fragment.components.viewpager.QDLazyTestObserver;
@@ -55,6 +57,11 @@ import butterknife.BindView;
import butterknife.ButterKnife;
@Widget(group = Group.Other, name = "内容自适应,超过父容器则滚动")
@FragmentScheme(
name = "tab",
activities = {QDMainActivity.class},
required = {"mode=2"},
keysWithIntValue = {"mode"})
public class QDTabSegmentScrollableModeFragment extends BaseFragment {
@SuppressWarnings("FieldCanBeLocal") private final int TAB_COUNT = 10;
@@ -25,12 +25,14 @@ import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import com.qmuiteam.qmui.arch.annotation.FragmentScheme;
import com.qmuiteam.qmui.util.QMUIDisplayHelper;
import com.qmuiteam.qmui.widget.QMUITopBarLayout;
import com.qmuiteam.qmui.widget.tab.QMUITab;
import com.qmuiteam.qmui.widget.tab.QMUITabBuilder;
import com.qmuiteam.qmui.widget.tab.QMUITabIndicator;
import com.qmuiteam.qmui.widget.tab.QMUITabSegment;
import com.qmuiteam.qmuidemo.QDMainActivity;
import com.qmuiteam.qmuidemo.R;
import com.qmuiteam.qmuidemo.base.BaseFragment;
import com.qmuiteam.qmuidemo.fragment.components.viewpager.QDLazyTestObserver;
@@ -56,6 +58,11 @@ import butterknife.ButterKnife;
*/
@Widget(group = Group.Other, name = "内容自适应,添加weight控制间距")
@FragmentScheme(
name = "tab",
activities = {QDMainActivity.class},
required = {"mode=3"},
keysWithIntValue = {"mode"})
public class QDTabSegmentSpaceWeightFragment extends BaseFragment {
@SuppressWarnings("FieldCanBeLocal") private final int TAB_COUNT = 3;
@@ -0,0 +1,99 @@
/*
* Tencent is pleased to support the open source community by making QMUI_Android available.
*
* Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* 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.qmuiteam.qmuidemo.fragment.lab;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import com.qmuiteam.qmui.arch.annotation.LatestVisitRecord;
import com.qmuiteam.qmui.widget.QMUITopBarLayout;
import com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton;
import com.qmuiteam.qmuidemo.R;
import com.qmuiteam.qmuidemo.base.BaseFragment;
import com.qmuiteam.qmuidemo.lib.Group;
import com.qmuiteam.qmuidemo.lib.annotation.Widget;
import com.qmuiteam.qmuidemo.manager.QDDataManager;
import com.qmuiteam.qmuidemo.manager.QDSchemeManager;
import butterknife.BindView;
import butterknife.ButterKnife;
@LatestVisitRecord
@Widget(group = Group.Lab, name = "Scheme", iconRes = R.mipmap.icon_grid_in_progress)
public class QDSchemeFragment extends BaseFragment {
@BindView(R.id.topbar)
QMUITopBarLayout mTopBar;
@BindView(R.id.edit_text)
EditText mEditText;
@BindView(R.id.button)
QMUIRoundButton mBtn;
@Override
protected View onCreateView() {
View view = LayoutInflater.from(getContext()).inflate(R.layout.fragment_scheme, null);
ButterKnife.bind(this, view);
initTopBar();
initEditStuff();
return view;
}
private void initTopBar() {
mTopBar.addLeftBackImageButton().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
popBackStack();
}
});
mTopBar.setTitle(QDDataManager.getInstance().getDescription(this.getClass()).getName());
}
private void initEditStuff() {
mBtn.setEnabled(false);
mEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (s == null || s.length() == 0) {
mBtn.setEnabled(false);
}
mBtn.setEnabled(true);
}
});
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
QDSchemeManager.getInstance().handle(mEditText.getText().toString());
}
});
}
}
@@ -46,6 +46,7 @@ import com.qmuiteam.qmuidemo.fragment.components.viewpager.QDViewPagerFragment;
import com.qmuiteam.qmuidemo.fragment.lab.QDAnimationListViewFragment;
import com.qmuiteam.qmuidemo.fragment.lab.QDArchTestFragment;
import com.qmuiteam.qmuidemo.fragment.lab.QDContinuousNestedScrollFragment;
import com.qmuiteam.qmuidemo.fragment.lab.QDSchemeFragment;
import com.qmuiteam.qmuidemo.fragment.lab.QDSnapHelperFragment;
import com.qmuiteam.qmuidemo.fragment.lab.QDWebViewFragment;
import com.qmuiteam.qmuidemo.fragment.util.QDColorHelperFragment;
@@ -144,6 +145,7 @@ public class QDDataManager {
mLabNames.add(QDSnapHelperFragment.class);
mLabNames.add(QDArchTestFragment.class);
mLabNames.add(QDWebViewFragment.class);
mLabNames.add(QDSchemeFragment.class);
}
public QDItemDescription getDescription(Class<? extends BaseFragment> cls) {
@@ -0,0 +1,68 @@
/*
* Tencent is pleased to support the open source community by making QMUI_Android available.
*
* Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* 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.qmuiteam.qmuidemo.manager;
import android.app.Activity;
import android.util.Log;
import android.widget.Toast;
import com.qmuiteam.qmui.arch.QMUISwipeBackActivityManager;
import com.qmuiteam.qmui.arch.scheme.QMUISchemeHandleInterpolator;
import com.qmuiteam.qmui.arch.scheme.QMUISchemeHandler;
import com.qmuiteam.qmui.arch.scheme.QMUISchemeParamValueDecoder;
import java.util.Map;
public class QDSchemeManager {
private static final String TAG = "QDSchemeManager";
public static final String SCHEME_PREFIX = "qmui://";
private static QDSchemeManager sInstance;
public static QDSchemeManager getInstance() {
if (sInstance == null) {
sInstance = new QDSchemeManager();
}
return sInstance;
}
private QMUISchemeHandler mSchemeHandler;
private QDSchemeManager() {
mSchemeHandler = new QMUISchemeHandler.Builder(SCHEME_PREFIX)
.setBlockSameSchemeTimeout(1000)
.addInterpolator(new QMUISchemeHandleInterpolator() {
@Override
public boolean intercept(Activity activity, String action, Map<String, String> params, String origin) {
// Log the scheme.
Log.i(TAG, "handle scheme: " + origin);
return false;
}
})
.addInterpolator(new QMUISchemeParamValueDecoder())
.build();
}
public boolean handle(String scheme) {
if (!mSchemeHandler.handle(scheme)) {
Log.i(TAG, "scheme can not be handled: " + scheme);
Toast.makeText(QMUISwipeBackActivityManager.getInstance().getCurrentActivity(),
"scheme can not be handled: " + scheme, Toast.LENGTH_SHORT).show();
return false;
}
return true;
}
}
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?><!--
Tencent is pleased to support the open source community by making QMUI_Android available.
Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
http://opensource.org/licenses/MIT
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.
-->
<com.qmuiteam.qmui.widget.QMUIWindowInsetLayout2 xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/app_skin_common_background"
app:qmui_skin_background="?attr/app_skin_common_background">
<com.qmuiteam.qmui.widget.QMUITopBarLayout
android:id="@+id/topbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/topbar"
android:padding="40dp">
<TextView
style="@style/QDCommonTitle"
android:text="请输入 scheme: " />
<EditText
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
android:background="@drawable/qmui_divider_bottom_bitmap"
android:hint="qmui://xxx"
android:paddingBottom="6dp"
android:singleLine="true"
android:textColor="@color/app_color_description"
android:textSize="16sp" />
<com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
android:gravity="center"
android:padding="10dp"
android:text="点击跳转"
android:textColor="@color/app_color_blue"
app:qmui_backgroundColor="@color/app_color_blue_3"
app:qmui_borderColor="@color/app_color_blue"
app:qmui_borderWidth="1px"
app:qmui_skin_background="?attr/app_skin_btn_test_bg_single"
app:qmui_skin_border="?attr/app_skin_btn_test_border_single"
app:qmui_skin_text_color="?attr/app_skin_btn_test_border_single" />
</LinearLayout>
</com.qmuiteam.qmui.widget.QMUIWindowInsetLayout2>