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
This commit is contained in:
Liam Clark
2018-11-26 16:48:27 -08:00
parent a2f0b5d456
commit 1ca6b6ec7d
7 changed files with 158 additions and 31 deletions

View File

@@ -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

View File

@@ -47,6 +47,14 @@
android:divider="@drawable/divider"
android:visibility="visible">
</LinearLayout>
</ScrollView>
</LinearLayout>
</FrameLayout>
</ScrollView>
</LinearLayout>
<android.support.design.widget.FloatingActionButton
android:id="@+id/launch_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_marginEnd="@dimen/smallMargin"
android:layout_marginBottom="@dimen/smallMargin"
android:src="@drawable/icon_launch" />
</FrameLayout>

View File

@@ -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

View File

@@ -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<FabAction> mFabActions = new MutableLiveData<>();
public void actOnFab(FabAction action) {
mFabActions.setValue(action);
}
public LiveData<FabAction> getFabActions() {
return mFabActions;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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) {