From 3be4397dd2be94fd486cf4b0ce32af673a497ce1 Mon Sep 17 00:00:00 2001 From: qii Date: Fri, 22 Nov 2019 12:56:02 +0800 Subject: [PATCH] feature: add GroupSceneCompatUtility, provides the ability to bind GroupScene to Fragment directly --- demo/src/main/AndroidManifest.xml | 5 +- .../com/bytedance/scenedemo/MigrateSamples.kt | 13 +- ...e.kt => GroupSceneBindToActivitySample.kt} | 2 +- .../migrate/GroupSceneBindToFragmentSample.kt | 39 ++++ ...=> NavigationSceneBindToFragmentSample.kt} | 2 +- .../res/layout/parent_scene_viewpager.xml | 1 + demo/src/main/res/values-zh/strings.xml | 1 + demo/src/main/res/values/strings.xml | 1 + .../scene/ui/GroupSceneCompatUtility.java | 208 ++++++++++++++++++ .../ui/NavigationSceneCompatUtility.java | 12 +- 10 files changed, 272 insertions(+), 12 deletions(-) rename demo/src/main/java/com/bytedance/scenedemo/migrate/{GroupSceneBindingSample.kt => GroupSceneBindToActivitySample.kt} (88%) create mode 100644 demo/src/main/java/com/bytedance/scenedemo/migrate/GroupSceneBindToFragmentSample.kt rename demo/src/main/java/com/bytedance/scenedemo/migrate/{FragmentBindingDemoActivity.kt => NavigationSceneBindToFragmentSample.kt} (96%) create mode 100644 library/scene_ui/src/main/java/com/bytedance/scene/ui/GroupSceneCompatUtility.java diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index d4ea9c8..6631db1 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -22,13 +22,14 @@ - + - + + diff --git a/demo/src/main/java/com/bytedance/scenedemo/MigrateSamples.kt b/demo/src/main/java/com/bytedance/scenedemo/MigrateSamples.kt index 2b7f425..1f6387b 100644 --- a/demo/src/main/java/com/bytedance/scenedemo/MigrateSamples.kt +++ b/demo/src/main/java/com/bytedance/scenedemo/MigrateSamples.kt @@ -8,8 +8,9 @@ import android.view.ViewGroup import android.widget.LinearLayout import android.widget.ScrollView import com.bytedance.scene.Scene -import com.bytedance.scenedemo.migrate.FragmentBindingDemoActivity -import com.bytedance.scenedemo.migrate.GroupSceneBindingSample +import com.bytedance.scenedemo.migrate.NavigationSceneBindToFragmentSample +import com.bytedance.scenedemo.migrate.GroupSceneBindToFragmentSample +import com.bytedance.scenedemo.migrate.GroupSceneBindToActivitySample import com.bytedance.scenedemo.migrate.TestSceneToViewActivity import com.bytedance.scenedemo.migrate.migrate_from_classic_activity_fragment.MigrateFromClassicAndroidActivitySamplesActivity import com.bytedance.scenedemo.utility.addButton @@ -32,11 +33,15 @@ class MigrateSamples : Scene() { addTitle(layout, getString(R.string.main_title_basic)) addButton(layout, getString(R.string.main_part_btn_bind_navigationscene_to_fragment), View.OnClickListener { - requireNavigationScene().startActivity(Intent(activity, FragmentBindingDemoActivity::class.java)) + requireNavigationScene().startActivity(Intent(activity, NavigationSceneBindToFragmentSample::class.java)) }) addButton(layout, getString(R.string.main_part_btn_bind_groupscene_to_activity), View.OnClickListener { - requireNavigationScene().startActivity(Intent(activity, GroupSceneBindingSample::class.java)) + requireNavigationScene().startActivity(Intent(activity, GroupSceneBindToActivitySample::class.java)) + }) + + addButton(layout, getString(R.string.main_part_btn_bind_groupscene_to_fragment), View.OnClickListener { + requireNavigationScene().startActivity(Intent(activity, GroupSceneBindToFragmentSample::class.java)) }) addButton(layout, getString(R.string.main_part_btn_bind_to_view), View.OnClickListener { diff --git a/demo/src/main/java/com/bytedance/scenedemo/migrate/GroupSceneBindingSample.kt b/demo/src/main/java/com/bytedance/scenedemo/migrate/GroupSceneBindToActivitySample.kt similarity index 88% rename from demo/src/main/java/com/bytedance/scenedemo/migrate/GroupSceneBindingSample.kt rename to demo/src/main/java/com/bytedance/scenedemo/migrate/GroupSceneBindToActivitySample.kt index 4b2a0ed..a85c737 100644 --- a/demo/src/main/java/com/bytedance/scenedemo/migrate/GroupSceneBindingSample.kt +++ b/demo/src/main/java/com/bytedance/scenedemo/migrate/GroupSceneBindToActivitySample.kt @@ -5,7 +5,7 @@ import android.os.Bundle import com.bytedance.scene.GroupSceneUtility import com.bytedance.scenedemo.group.viewpager.ViewPagerSample -class GroupSceneBindingSample : Activity() { +class GroupSceneBindToActivitySample : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) GroupSceneUtility.setupWithActivity(this, ViewPagerSample::class.java).build() diff --git a/demo/src/main/java/com/bytedance/scenedemo/migrate/GroupSceneBindToFragmentSample.kt b/demo/src/main/java/com/bytedance/scenedemo/migrate/GroupSceneBindToFragmentSample.kt new file mode 100644 index 0000000..6c0fc9d --- /dev/null +++ b/demo/src/main/java/com/bytedance/scenedemo/migrate/GroupSceneBindToFragmentSample.kt @@ -0,0 +1,39 @@ +package com.bytedance.scenedemo.migrate + +import android.os.Bundle +import android.support.v4.app.Fragment +import android.support.v7.app.AppCompatActivity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import com.bytedance.scene.GroupSceneDelegate +import com.bytedance.scene.ui.GroupSceneCompatUtility +import com.bytedance.scene.utlity.ViewIdGenerator +import com.bytedance.scenedemo.group.viewpager.ViewPagerSample + +/** + * Created by JiangQi on 9/5/18. + */ +class GroupSceneBindToFragmentSample : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + supportFragmentManager.beginTransaction().add(android.R.id.content, AFragment()).commitNow() + } + + class AFragment : Fragment() { + private val viewId = ViewIdGenerator.generateViewId() + private var delegate: GroupSceneDelegate? = null + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val frameLayout = FrameLayout(activity!!) + frameLayout.id = viewId + return frameLayout + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + delegate = GroupSceneCompatUtility.setupWithFragment(this, ViewPagerSample::class.java, viewId).build() + } + } +} diff --git a/demo/src/main/java/com/bytedance/scenedemo/migrate/FragmentBindingDemoActivity.kt b/demo/src/main/java/com/bytedance/scenedemo/migrate/NavigationSceneBindToFragmentSample.kt similarity index 96% rename from demo/src/main/java/com/bytedance/scenedemo/migrate/FragmentBindingDemoActivity.kt rename to demo/src/main/java/com/bytedance/scenedemo/migrate/NavigationSceneBindToFragmentSample.kt index 469760e..a65b814 100644 --- a/demo/src/main/java/com/bytedance/scenedemo/migrate/FragmentBindingDemoActivity.kt +++ b/demo/src/main/java/com/bytedance/scenedemo/migrate/NavigationSceneBindToFragmentSample.kt @@ -18,7 +18,7 @@ import com.bytedance.scenedemo.group.viewpager.ViewPagerSample /** * Created by JiangQi on 9/5/18. */ -class FragmentBindingDemoActivity : AppCompatActivity() { +class NavigationSceneBindToFragmentSample : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) supportFragmentManager.beginTransaction().add(android.R.id.content, AFragment()).commitNow() diff --git a/demo/src/main/res/layout/parent_scene_viewpager.xml b/demo/src/main/res/layout/parent_scene_viewpager.xml index 571bb3b..fcd8c62 100644 --- a/demo/src/main/res/layout/parent_scene_viewpager.xml +++ b/demo/src/main/res/layout/parent_scene_viewpager.xml @@ -3,6 +3,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/holo_green_light" + android:id="@+id/scene_root" android:orientation="vertical"> 异步 Inflate NavigationScene 绑定到 Fragment GroupScene 绑定到 Activity + GroupScene 绑定到 Fragment 绑定到 View 悬浮窗(未完成) 继承的 Scene diff --git a/demo/src/main/res/values/strings.xml b/demo/src/main/res/values/strings.xml index dbbc6a4..0062315 100644 --- a/demo/src/main/res/values/strings.xml +++ b/demo/src/main/res/values/strings.xml @@ -51,6 +51,7 @@ ScenePlaceHolderView(xml) Bind NavigationScene to Fragment Bind GroupScene to Activity + Bind GroupScene to Fragment Bind to View Migrate from classic Android app Async Inflate diff --git a/library/scene_ui/src/main/java/com/bytedance/scene/ui/GroupSceneCompatUtility.java b/library/scene_ui/src/main/java/com/bytedance/scene/ui/GroupSceneCompatUtility.java new file mode 100644 index 0000000..fe80b68 --- /dev/null +++ b/library/scene_ui/src/main/java/com/bytedance/scene/ui/GroupSceneCompatUtility.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2019 ByteDance 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.bytedance.scene.ui; + +import android.os.Bundle; +import android.support.annotation.IdRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.view.View; +import com.bytedance.scene.*; +import com.bytedance.scene.group.GroupScene; +import com.bytedance.scene.utlity.SceneInstanceUtility; +import com.bytedance.scene.utlity.ThreadUtility; +import com.bytedance.scene.utlity.Utility; + +import java.util.HashSet; +import java.util.WeakHashMap; + +public class GroupSceneCompatUtility { + private GroupSceneCompatUtility() { + } + + public static final class Builder { + @NonNull + private final Fragment mFragment; + @NonNull + private final Class mRootSceneClazz; + @Nullable + private Bundle mRootSceneArguments; + @IdRes + private final int mIdRes; + private boolean mSupportRestore = false; + @Nullable + private SceneComponentFactory mRootSceneComponentFactory; + @NonNull + private String mTag = NavigationSceneCompatUtility.LIFE_CYCLE_FRAGMENT_TAG; + private boolean mImmediate = true; + + private Builder(@NonNull Fragment fragment, @NonNull Class rootSceneClazz, @IdRes int containerId) { + this.mFragment = Utility.requireNonNull(fragment, "Fragment can't be null"); + this.mRootSceneClazz = Utility.requireNonNull(rootSceneClazz, "Root Scene class can't be null"); + this.mIdRes = containerId; + } + + @NonNull + public GroupSceneCompatUtility.Builder rootSceneArguments(@Nullable Bundle rootSceneArguments) { + this.mRootSceneArguments = rootSceneArguments; + return this; + } + + @NonNull + public GroupSceneCompatUtility.Builder supportRestore(boolean supportRestore) { + this.mSupportRestore = supportRestore; + return this; + } + + @NonNull + public GroupSceneCompatUtility.Builder rootSceneComponentFactory(@Nullable SceneComponentFactory rootSceneComponentFactory) { + this.mRootSceneComponentFactory = rootSceneComponentFactory; + return this; + } + + @NonNull + public GroupSceneCompatUtility.Builder tag(@NonNull String tag) { + this.mTag = Utility.requireNonNull(tag, "Tag can't be null"); + return this; + } + + @NonNull + public GroupSceneCompatUtility.Builder immediate(boolean immediate) { + this.mImmediate = immediate; + return this; + } + + @NonNull + public GroupSceneDelegate build() { + return setupWithFragment(this.mFragment, this.mIdRes, this.mRootSceneClazz, this.mRootSceneArguments, this.mRootSceneComponentFactory, this.mSupportRestore, this.mTag, this.mImmediate); + } + } + + @NonNull + public static GroupSceneCompatUtility.Builder setupWithFragment(@NonNull final Fragment fragment, + @NonNull Class rootScene, + @IdRes int containerId) { + return new GroupSceneCompatUtility.Builder(fragment, rootScene, containerId); + } + + @NonNull + private static GroupSceneDelegate setupWithFragment(@NonNull final Fragment fragment, + @IdRes int containerId, + @NonNull Class sceneClazz, + @Nullable Bundle sceneArguments, + @Nullable SceneComponentFactory rootSceneComponentFactory, + final boolean supportRestore, + @NonNull final String tag, + final boolean immediate) { + ThreadUtility.checkUIThread(); + if (tag == null) { + throw new IllegalArgumentException("tag cant be null"); + } + NavigationSceneCompatUtility.checkDuplicateTag(fragment, tag); + + final FragmentManager fragmentManager = fragment.getChildFragmentManager(); + LifeCycleCompatFragment lifeCycleFragment = (LifeCycleCompatFragment) fragmentManager.findFragmentByTag(tag); + if (lifeCycleFragment != null && !supportRestore) { + FragmentTransaction transaction = fragmentManager.beginTransaction(); + transaction.remove(lifeCycleFragment); + FragmentUtility.commitFragment(transaction, immediate); + lifeCycleFragment = null; + } + + ViewFinder viewFinder = new FragmentViewFinder(fragment); + GroupScene groupScene = null; + if (rootSceneComponentFactory != null) { + groupScene = (GroupScene) rootSceneComponentFactory.instantiateScene(fragment.getClass().getClassLoader(), sceneClazz.getName(), sceneArguments); + } + if (groupScene == null) { + groupScene = (GroupScene) SceneInstanceUtility.getInstanceFromClass(sceneClazz, sceneArguments); + } + + ScopeHolderCompatFragment targetScopeHolderFragment = null; + SceneLifecycleDispatcher dispatcher = null; + if (lifeCycleFragment != null) { + final ScopeHolderCompatFragment scopeHolderFragment = ScopeHolderCompatFragment.install(fragment, tag, false, immediate); + targetScopeHolderFragment = scopeHolderFragment; + + dispatcher = new SceneLifecycleDispatcher<>(containerId, viewFinder, groupScene, scopeHolderFragment, supportRestore); + lifeCycleFragment.setSceneContainerLifecycleCallback(dispatcher); + } else { + final ScopeHolderCompatFragment scopeHolderFragment = ScopeHolderCompatFragment.install(fragment, tag, !supportRestore, immediate); + lifeCycleFragment = LifeCycleCompatFragment.newInstance(supportRestore); + + dispatcher = new SceneLifecycleDispatcher<>(containerId, viewFinder, groupScene, scopeHolderFragment, supportRestore); + lifeCycleFragment.setSceneContainerLifecycleCallback(dispatcher); + + FragmentTransaction transaction = fragmentManager.beginTransaction(); + transaction.add(containerId, lifeCycleFragment, tag); + FragmentUtility.commitFragment(transaction, immediate); + targetScopeHolderFragment = scopeHolderFragment; + } + + final LifeCycleCompatFragment finalLifeCycleFragment = lifeCycleFragment; + final ScopeHolderCompatFragment finalTargetScopeHolderFragment = targetScopeHolderFragment; + final GroupScene finalGroupScene = groupScene; + final GroupSceneDelegate proxy = new GroupSceneDelegate() { + private boolean mAbandoned = false; + + @Nullable + @Override + public GroupScene getGroupScene() { + if (this.mAbandoned) { + return null; + } + return finalGroupScene; + } + + @Override + public void abandon() { + if (this.mAbandoned) { + return; + } + this.mAbandoned = true; + final View view = finalGroupScene.getView(); + FragmentTransaction transaction = fragmentManager.beginTransaction().remove(finalLifeCycleFragment).remove(finalTargetScopeHolderFragment); + if (immediate) { + fragmentManager.registerFragmentLifecycleCallbacks(new FragmentManager.FragmentLifecycleCallbacks() { + @Override + public void onFragmentDetached(FragmentManager fm, Fragment f) { + super.onFragmentDetached(fm, f); + if (f != finalLifeCycleFragment) { + return; + } + fragmentManager.unregisterFragmentLifecycleCallbacks(this); + NavigationSceneCompatUtility.removeTag(fragment, tag); + } + }, false); + FragmentUtility.commitFragment(transaction, true); + if (view != null) { + Utility.removeFromParentView(view); + } + } else { + FragmentUtility.commitFragment(transaction, false); + NavigationSceneCompatUtility.removeTag(fragment, tag); + if (view != null) { + Utility.removeFromParentView(view); + } + } + } + }; + return proxy; + } +} diff --git a/library/scene_ui/src/main/java/com/bytedance/scene/ui/NavigationSceneCompatUtility.java b/library/scene_ui/src/main/java/com/bytedance/scene/ui/NavigationSceneCompatUtility.java index 356d540..03e38c2 100644 --- a/library/scene_ui/src/main/java/com/bytedance/scene/ui/NavigationSceneCompatUtility.java +++ b/library/scene_ui/src/main/java/com/bytedance/scene/ui/NavigationSceneCompatUtility.java @@ -38,7 +38,7 @@ * Created by JiangQi on 9/4/18. */ public final class NavigationSceneCompatUtility { - private static final String LIFE_CYCLE_FRAGMENT_TAG = "LifeCycleCompatFragment"; + static final String LIFE_CYCLE_FRAGMENT_TAG = "LifeCycleCompatFragment"; private static final WeakHashMap> CHECK_DUPLICATE_TAG_MAP = new WeakHashMap<>(); private NavigationSceneCompatUtility() { @@ -306,7 +306,7 @@ public void onFragmentDetached(FragmentManager fm, Fragment f) { return; } fragmentManager.unregisterFragmentLifecycleCallbacks(this); - CHECK_DUPLICATE_TAG_MAP.get(fragment).remove(tag); + removeTag(fragment, tag); if (view != null) { Utility.removeFromParentView(view); } @@ -315,7 +315,7 @@ public void onFragmentDetached(FragmentManager fm, Fragment f) { FragmentUtility.commitFragment(transaction, true); } else { FragmentUtility.commitFragment(transaction, false); - CHECK_DUPLICATE_TAG_MAP.get(fragment).remove(tag); + removeTag(fragment, tag); if (view != null) { Utility.removeFromParentView(view); } @@ -325,7 +325,7 @@ public void onFragmentDetached(FragmentManager fm, Fragment f) { return proxy; } - private static void checkDuplicateTag(@NonNull Fragment fragment, @NonNull String tag) { + static void checkDuplicateTag(@NonNull Fragment fragment, @NonNull String tag) { if (CHECK_DUPLICATE_TAG_MAP.get(fragment) != null && CHECK_DUPLICATE_TAG_MAP.get(fragment).contains(tag)) { throw new IllegalArgumentException("tag duplicate, use another tag when invoke setupWithActivity for the second time in same Fragment"); } else { @@ -337,4 +337,8 @@ private static void checkDuplicateTag(@NonNull Fragment fragment, @NonNull Strin set.add(tag); } } + + static void removeTag(@NonNull Fragment fragment, @NonNull String tag) { + CHECK_DUPLICATE_TAG_MAP.get(fragment).remove(tag); + } }