From 1ca6b6ec7d226b5aed6f9331378691edf36c3d2d Mon Sep 17 00:00:00 2001 From: Liam Clark Date: Mon, 26 Nov 2018 16:48:27 -0800 Subject: [PATCH] Put the launch form behind a FAB This greatly reduces the amount of scrolling needed to switch between the overview-view and the launch-view. When launching the next activity the launch view is dismissed and the user return to the overview-view if they press back. Test: Manual Change-Id: I051ba98b29d666cf56753feebfaa87d2e181a3ab --- samples/IntentPlayground/Android.mk | 5 +- .../res/layout/activity_main.xml | 14 ++++- .../intentplayground/BaseActivity.java | 58 +++++++++++++------ .../BaseActivityViewModel.java | 36 ++++++++++++ .../intentplayground/LaunchFragment.java | 49 ++++++++++++++++ .../intentplayground/LauncherActivity.java | 4 ++ .../intentplayground/TreeFragment.java | 23 +++++--- 7 files changed, 158 insertions(+), 31 deletions(-) create mode 100644 samples/IntentPlayground/src/com/example/android/intentplayground/BaseActivityViewModel.java create mode 100644 samples/IntentPlayground/src/com/example/android/intentplayground/LaunchFragment.java diff --git a/samples/IntentPlayground/Android.mk b/samples/IntentPlayground/Android.mk index 66e0bbb87..20c3225a7 100644 --- a/samples/IntentPlayground/Android.mk +++ b/samples/IntentPlayground/Android.mk @@ -9,8 +9,11 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res LOCAL_STATIC_ANDROID_LIBRARIES := \ + androidx.design_design \ androidx.appcompat_appcompat \ - androidx.recyclerview_recyclerview + androidx.recyclerview_recyclerview \ + androidx.lifecycle_lifecycle-livedata \ + androidx.lifecycle_lifecycle-viewmodel LOCAL_USE_AAPT2 := true diff --git a/samples/IntentPlayground/res/layout/activity_main.xml b/samples/IntentPlayground/res/layout/activity_main.xml index 71593ee9e..0bea19c0b 100644 --- a/samples/IntentPlayground/res/layout/activity_main.xml +++ b/samples/IntentPlayground/res/layout/activity_main.xml @@ -47,6 +47,14 @@ android:divider="@drawable/divider" android:visibility="visible"> - - - \ No newline at end of file + + + + diff --git a/samples/IntentPlayground/src/com/example/android/intentplayground/BaseActivity.java b/samples/IntentPlayground/src/com/example/android/intentplayground/BaseActivity.java index 37d431f0b..6f0e6508e 100644 --- a/samples/IntentPlayground/src/com/example/android/intentplayground/BaseActivity.java +++ b/samples/IntentPlayground/src/com/example/android/intentplayground/BaseActivity.java @@ -22,8 +22,8 @@ import android.content.ComponentName; import android.content.Intent; import android.os.Build; import android.os.Bundle; +import android.support.design.widget.FloatingActionButton; import android.util.Log; -import android.view.View; import android.view.Menu; import android.view.MenuItem; import android.view.ViewGroup; @@ -34,6 +34,7 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; +import androidx.lifecycle.ViewModelProvider; import java.util.ArrayList; @@ -63,6 +64,31 @@ public abstract class BaseActivity extends AppCompatActivity implements appBar.setTitle(this.getClass().getSimpleName()); setSupportActionBar(appBar); + FloatingActionButton launchButton = findViewById(R.id.launch_fab); + launchButton.setOnClickListener(l -> { + LaunchFragment fragment = new LaunchFragment(); + + getSupportFragmentManager().beginTransaction() + .addToBackStack(null) + .replace(R.id.fragment_container, fragment) + .commit(); + }); + + BaseActivityViewModel viewModel = (new ViewModelProvider(this, + new ViewModelProvider.NewInstanceFactory())).get(BaseActivityViewModel.class); + + viewModel.getFabActions().observe(this, action -> { + switch (action) { + case Show: + launchButton.show(); + break; + case Hide: + launchButton.hide(); + break; + } + }); + + loadMode(Mode.LAUNCH); } @@ -81,32 +107,23 @@ public abstract class BaseActivity extends AppCompatActivity implements * @param mode The mode to display. */ protected void loadMode(Mode mode) { - Intent intent = getIntent(); - ViewGroup container = findViewById(R.id.fragment_container); FragmentManager fragmentManager = getSupportFragmentManager(); - FragmentTransaction transaction = fragmentManager.beginTransaction() - .setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out); - if (mode == Mode.LAUNCH) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + + if (fragmentManager.findFragmentById(R.id.fragment_container) == null) { + FragmentTransaction transaction = fragmentManager.beginTransaction() + .setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out); + if (mode == Mode.LAUNCH) { TreeFragment currentTaskFragment = new TreeFragment(); Bundle args = new Bundle(); args.putString(TreeFragment.FRAGMENT_TITLE, getString(R.string.current_task_hierarchy_title)); currentTaskFragment.setArguments(args); transaction.add(R.id.fragment_container, currentTaskFragment, TREE_FRAGMENT); + transaction.add(R.id.fragment_container, new IntentFragment()); + transaction.commit(); + + mStatus = Mode.LAUNCH; } - transaction.add(R.id.fragment_container, new IntentFragment()); - transaction.commit(); - // Ensure IntentBuilderView is last by adding it to the container after commit() - transaction.runOnCommit(() -> { - IntentBuilderView builderView = new IntentBuilderView(this, mode); - builderView.setOnLaunchCallback(this::launchActivity); - View bottomAnchorView = new View(this); - bottomAnchorView.setId(R.id.fragment_container_bottom); - container.addView(builderView); - container.addView(bottomAnchorView); - }); - mStatus = Mode.LAUNCH; } } @@ -114,7 +131,10 @@ public abstract class BaseActivity extends AppCompatActivity implements * Launches activity with the selected options. */ public void launchActivity(Intent intent) { + // If people press back we want them to see the overview rather than the launch fragment. + // To achieve this we pop the launchFragment from the stack when we go to the next activity. startActivity(intent); + getSupportFragmentManager().popBackStack(); } @Override diff --git a/samples/IntentPlayground/src/com/example/android/intentplayground/BaseActivityViewModel.java b/samples/IntentPlayground/src/com/example/android/intentplayground/BaseActivityViewModel.java new file mode 100644 index 000000000..5ffa1d28f --- /dev/null +++ b/samples/IntentPlayground/src/com/example/android/intentplayground/BaseActivityViewModel.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * 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.example.android.intentplayground; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +public class BaseActivityViewModel extends ViewModel { + enum FabAction {Show, Hide } + + private MutableLiveData mFabActions = new MutableLiveData<>(); + + public void actOnFab(FabAction action) { + mFabActions.setValue(action); + } + + public LiveData getFabActions() { + return mFabActions; + } +} + diff --git a/samples/IntentPlayground/src/com/example/android/intentplayground/LaunchFragment.java b/samples/IntentPlayground/src/com/example/android/intentplayground/LaunchFragment.java new file mode 100644 index 000000000..8c68d521e --- /dev/null +++ b/samples/IntentPlayground/src/com/example/android/intentplayground/LaunchFragment.java @@ -0,0 +1,49 @@ +package com.example.android.intentplayground; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.ViewModelProvider; + +import com.example.android.intentplayground.BaseActivity.Mode; +import com.example.android.intentplayground.IntentBuilderView.OnLaunchCallback; + +public class LaunchFragment extends Fragment { + private IntentBuilderView mIntentBuilderView; + private BaseActivityViewModel mViewModel; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + mIntentBuilderView = new IntentBuilderView(getContext(), Mode.LAUNCH); + setOnLaunchCallBack(); + return mIntentBuilderView; + } + + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + mViewModel = (new ViewModelProvider(getActivity(), + new ViewModelProvider.NewInstanceFactory())).get(BaseActivityViewModel.class); + } + + @Override + public void onResume() { + super.onResume(); + mViewModel.actOnFab(BaseActivityViewModel.FabAction.Hide); + } + + private void setOnLaunchCallBack() { + FragmentActivity activity = this.getActivity(); + if (activity instanceof OnLaunchCallback) { + mIntentBuilderView.setOnLaunchCallback((OnLaunchCallback) activity); + } + } +} diff --git a/samples/IntentPlayground/src/com/example/android/intentplayground/LauncherActivity.java b/samples/IntentPlayground/src/com/example/android/intentplayground/LauncherActivity.java index 83aef304e..a604c5bc4 100644 --- a/samples/IntentPlayground/src/com/example/android/intentplayground/LauncherActivity.java +++ b/samples/IntentPlayground/src/com/example/android/intentplayground/LauncherActivity.java @@ -37,6 +37,7 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + import static com.example.android.intentplayground.Node.newTaskNode; /** @@ -64,6 +65,9 @@ public class LauncherActivity extends BaseActivity { @Override public void launchActivity(Intent intent) { startActivityForResult(intent, LAUNCH_REQUEST_CODE); + // If people press back we want them to see the overview rather than the launch fragment. + // To achieve this we pop the launchFragment from the stack when we go to the next activity. + getSupportFragmentManager().popBackStack(); } @Override diff --git a/samples/IntentPlayground/src/com/example/android/intentplayground/TreeFragment.java b/samples/IntentPlayground/src/com/example/android/intentplayground/TreeFragment.java index 7d75badc4..6c4adf2e6 100644 --- a/samples/IntentPlayground/src/com/example/android/intentplayground/TreeFragment.java +++ b/samples/IntentPlayground/src/com/example/android/intentplayground/TreeFragment.java @@ -25,7 +25,10 @@ import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -33,25 +36,31 @@ import androidx.recyclerview.widget.RecyclerView; * This fragment displays a hierarchy of tasks and activities in an expandable list. */ public class TreeFragment extends Fragment { - public static final String TREE_NODE = "com.example.android.NODE_TREE"; public static final String FRAGMENT_TITLE = "com.example.android.TREE_FRAGMENT_TITLE"; private Activity mActivity; - private Node mTree; private String mTitle; private ViewGroup mContainer; + private BaseActivityViewModel mViewModel; + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Bundle args = getArguments(); if (args != null) { - mTree = args.getParcelable(TREE_NODE); mTitle = args.getString(FRAGMENT_TITLE); } return inflater.inflate(R.layout.fragment_tree, container, false /* attachToRoot */); } + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mViewModel = (new ViewModelProvider(getActivity(), + new ViewModelProvider.NewInstanceFactory())).get(BaseActivityViewModel.class); + } + @Override public void onResume() { super.onResume(); @@ -62,11 +71,9 @@ public class TreeFragment extends Fragment { if (mTitle != null) { titleView.setText(mTitle); } - if (mTree != null) { - displayRecycler(mTree, recyclerView); - } else { - displayRecycler(TestBase.describeTaskHierarchy(mActivity), recyclerView); - } + + displayRecycler(TestBase.describeTaskHierarchy(mActivity), recyclerView); + mViewModel.actOnFab(BaseActivityViewModel.FabAction.Show); } private void displayRecycler(Node root, RecyclerView recyclerView) {