Move clear all to recycler view (1/2)

First part of moving clear all button to recycler view.  This CL adds
support in the recycler view adapter for a clear all holder type and
hooks it up to the previous clear all animation.

Adding this breaks several assumptions made externally on the type of
the item and index which will be addressed in the second part.

Bug: 114136250
Test: Builds, testing pending 2nd part
Change-Id: Ib16790028d4e9f520945a987b3dace40d19f2468
(cherry pick from 8573ff04b4)
This commit is contained in:
Kevin 2019-04-17 09:28:36 -07:00 committed by Kevin Han
parent 56abdd7ade
commit eda02641a2
5 changed files with 140 additions and 71 deletions

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2019 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.
-->
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/clear_all_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="@drawable/clear_all_button"
android:gravity="center"
android:text="@string/recents_clear_all"
android:textAllCaps="false"
android:textColor="@color/clear_all_button_text"
android:textSize="14sp">
</Button>

View File

@ -19,29 +19,11 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recent_task_content_view" android:id="@+id/recent_task_recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:scrollbars="none"/>
android:visibility="gone">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recent_task_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"/>
<Button
android:id="@+id/clear_all_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="@drawable/clear_all_button"
android:gravity="center"
android:text="@string/recents_clear_all"
android:textAllCaps="false"
android:textColor="@color/clear_all_button_text"
android:textSize="14sp"/>
</LinearLayout>
<TextView <TextView
android:id="@+id/recent_task_empty_view" android:id="@+id/recent_task_empty_view"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) 2019 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.android.quickstep;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
/**
* Holder for clear all button view in task recycler view.
*/
final class ClearAllHolder extends ViewHolder {
public ClearAllHolder(@NonNull View itemView) {
super(itemView);
}
}

View File

@ -16,12 +16,14 @@
package com.android.quickstep; package com.android.quickstep;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView.Adapter; import androidx.recyclerview.widget.RecyclerView.Adapter;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R; import com.android.launcher3.R;
import com.android.quickstep.views.TaskItemView; import com.android.quickstep.views.TaskItemView;
import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.Task;
@ -34,14 +36,17 @@ import java.util.Optional;
* Recycler view adapter that dynamically inflates and binds {@link TaskHolder} instances with the * Recycler view adapter that dynamically inflates and binds {@link TaskHolder} instances with the
* appropriate {@link Task} from the recents task list. * appropriate {@link Task} from the recents task list.
*/ */
public final class TaskAdapter extends Adapter<TaskHolder> { public final class TaskAdapter extends Adapter<ViewHolder> {
public static final int CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT = 0; public static final int CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT = 0;
public static final int MAX_TASKS_TO_DISPLAY = 6; public static final int MAX_TASKS_TO_DISPLAY = 6;
private static final String TAG = "TaskAdapter"; private static final String TAG = "TaskAdapter";
private static final int ITEM_TYPE_TASK = 0;
private static final int ITEM_TYPE_CLEAR_ALL = 1;
private final TaskListLoader mLoader; private final TaskListLoader mLoader;
private TaskActionController mTaskActionController; private TaskActionController mTaskActionController;
private OnClickListener mClearAllListener;
private boolean mIsShowingLoadingUi; private boolean mIsShowingLoadingUi;
public TaskAdapter(@NonNull TaskListLoader loader) { public TaskAdapter(@NonNull TaskListLoader loader) {
@ -52,6 +57,10 @@ public final class TaskAdapter extends Adapter<TaskHolder> {
mTaskActionController = taskActionController; mTaskActionController = taskActionController;
} }
public void setOnClearAllClickListener(OnClickListener listener) {
mClearAllListener = listener;
}
/** /**
* Sets all positions in the task adapter to loading views, binding new views if necessary. * Sets all positions in the task adapter to loading views, binding new views if necessary.
* This changes the task adapter's view of the data, so the appropriate notify events should be * This changes the task adapter's view of the data, so the appropriate notify events should be
@ -65,21 +74,32 @@ public final class TaskAdapter extends Adapter<TaskHolder> {
} }
@Override @Override
public TaskHolder onCreateViewHolder(ViewGroup parent, int viewType) { public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
TaskItemView itemView = (TaskItemView) LayoutInflater.from(parent.getContext()) switch (viewType) {
.inflate(R.layout.task_item_view, parent, false); case ITEM_TYPE_TASK:
TaskHolder holder = new TaskHolder(itemView); TaskItemView itemView = (TaskItemView) LayoutInflater.from(parent.getContext())
itemView.setOnClickListener(view -> mTaskActionController.launchTask(holder)); .inflate(R.layout.task_item_view, parent, false);
return holder; TaskHolder taskHolder = new TaskHolder(itemView);
itemView.setOnClickListener(view -> mTaskActionController.launchTask(taskHolder));
return taskHolder;
case ITEM_TYPE_CLEAR_ALL:
Button clearView = (Button) LayoutInflater.from(parent.getContext())
.inflate(R.layout.clear_all_button, parent, false);
ClearAllHolder clearAllHolder = new ClearAllHolder(clearView);
clearView.setOnClickListener(mClearAllListener);
return clearAllHolder;
default:
throw new IllegalArgumentException("No known holder for item type: " + viewType);
}
} }
@Override @Override
public void onBindViewHolder(TaskHolder holder, int position) { public void onBindViewHolder(ViewHolder holder, int position) {
onBindViewHolderInternal(holder, position, false /* willAnimate */); onBindViewHolderInternal(holder, position, false /* willAnimate */);
} }
@Override @Override
public void onBindViewHolder(@NonNull TaskHolder holder, int position, public void onBindViewHolder(@NonNull ViewHolder holder, int position,
@NonNull List<Object> payloads) { @NonNull List<Object> payloads) {
if (payloads.isEmpty()) { if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads); super.onBindViewHolder(holder, position, payloads);
@ -95,40 +115,60 @@ public final class TaskAdapter extends Adapter<TaskHolder> {
} }
} }
private void onBindViewHolderInternal(@NonNull TaskHolder holder, int position, private void onBindViewHolderInternal(@NonNull ViewHolder holder, int position,
boolean willAnimate) { boolean willAnimate) {
if (mIsShowingLoadingUi) { int itemType = getItemViewType(position);
holder.bindEmptyUi(); switch (itemType) {
return; case ITEM_TYPE_TASK:
TaskHolder taskHolder = (TaskHolder) holder;
if (mIsShowingLoadingUi) {
taskHolder.bindEmptyUi();
return;
}
List<Task> tasks = mLoader.getCurrentTaskList();
if (position >= tasks.size()) {
// Task list has updated.
return;
}
Task task = tasks.get(position);
taskHolder.bindTask(task, willAnimate /* willAnimate */);
mLoader.loadTaskIconAndLabel(task, () -> {
// Ensure holder still has the same task.
if (Objects.equals(Optional.of(task), taskHolder.getTask())) {
taskHolder.getTaskItemView().setIcon(task.icon);
taskHolder.getTaskItemView().setLabel(task.titleDescription);
}
});
mLoader.loadTaskThumbnail(task, () -> {
if (Objects.equals(Optional.of(task), taskHolder.getTask())) {
taskHolder.getTaskItemView().setThumbnail(task.thumbnail.thumbnail);
}
});
break;
case ITEM_TYPE_CLEAR_ALL:
// Nothing to bind.
break;
default:
throw new IllegalArgumentException("No known holder for item type: " + itemType);
} }
List<Task> tasks = mLoader.getCurrentTaskList(); }
if (position >= tasks.size()) {
// Task list has updated. @Override
return; public int getItemViewType(int position) {
} // Bottom is always clear all button.
Task task = tasks.get(position); return (position == 0) ? ITEM_TYPE_CLEAR_ALL : ITEM_TYPE_TASK;
holder.bindTask(task, willAnimate /* willAnimate */);
mLoader.loadTaskIconAndLabel(task, () -> {
// Ensure holder still has the same task.
if (Objects.equals(Optional.of(task), holder.getTask())) {
holder.getTaskItemView().setIcon(task.icon);
holder.getTaskItemView().setLabel(task.titleDescription);
}
});
mLoader.loadTaskThumbnail(task, () -> {
if (Objects.equals(Optional.of(task), holder.getTask())) {
holder.getTaskItemView().setThumbnail(task.thumbnail.thumbnail);
}
});
} }
@Override @Override
public int getItemCount() { public int getItemCount() {
// Always at least one for clear all button.
int itemCount = 1;
if (mIsShowingLoadingUi) { if (mIsShowingLoadingUi) {
// Show loading version of all items. // Show loading version of all items.
return MAX_TASKS_TO_DISPLAY; itemCount += MAX_TASKS_TO_DISPLAY;
} else { } else {
return Math.min(mLoader.getCurrentTaskList().size(), MAX_TASKS_TO_DISPLAY); itemCount += Math.min(mLoader.getCurrentTaskList().size(), MAX_TASKS_TO_DISPLAY);
} }
return itemCount;
} }
} }

View File

@ -18,9 +18,6 @@ package com.android.quickstep.views;
import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL; import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL;
import static com.android.quickstep.TaskAdapter.CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT; import static com.android.quickstep.TaskAdapter.CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT;
import static com.android.quickstep.views.TaskLayoutUtils.getClearAllButtonHeight;
import static com.android.quickstep.views.TaskLayoutUtils.getClearAllButtonTopBottomMargin;
import static com.android.quickstep.views.TaskLayoutUtils.getClearAllButtonWidth;
import static com.android.quickstep.views.TaskLayoutUtils.getTaskListHeight; import static com.android.quickstep.views.TaskLayoutUtils.getTaskListHeight;
import android.animation.Animator; import android.animation.Animator;
@ -114,7 +111,6 @@ public final class IconRecentsView extends FrameLayout {
private View mShowingContentView; private View mShowingContentView;
private View mEmptyView; private View mEmptyView;
private View mContentView; private View mContentView;
private View mClearAllView;
private boolean mTransitionedFromApp; private boolean mTransitionedFromApp;
private AnimatorSet mLayoutAnimation; private AnimatorSet mLayoutAnimation;
private final ArraySet<View> mLayingOutViews = new ArraySet<>(); private final ArraySet<View> mLayingOutViews = new ArraySet<>();
@ -141,6 +137,7 @@ public final class IconRecentsView extends FrameLayout {
mDeviceProfile = activity.getDeviceProfile(); mDeviceProfile = activity.getDeviceProfile();
mTaskLoader = new TaskListLoader(mContext); mTaskLoader = new TaskListLoader(mContext);
mTaskAdapter = new TaskAdapter(mTaskLoader); mTaskAdapter = new TaskAdapter(mTaskLoader);
mTaskAdapter.setOnClearAllClickListener(view -> animateClearAllTasks());
mTaskActionController = new TaskActionController(mTaskLoader, mTaskAdapter); mTaskActionController = new TaskActionController(mTaskLoader, mTaskAdapter);
mTaskAdapter.setActionController(mTaskActionController); mTaskAdapter.setActionController(mTaskActionController);
RecentsModel.INSTANCE.get(context).addThumbnailChangeListener(listener); RecentsModel.INSTANCE.get(context).addThumbnailChangeListener(listener);
@ -178,7 +175,7 @@ public final class IconRecentsView extends FrameLayout {
() -> mTaskRecyclerView.setItemAnimator(new DefaultItemAnimator())); () -> mTaskRecyclerView.setItemAnimator(new DefaultItemAnimator()));
mEmptyView = findViewById(R.id.recent_task_empty_view); mEmptyView = findViewById(R.id.recent_task_empty_view);
mContentView = findViewById(R.id.recent_task_content_view); mContentView = mTaskRecyclerView;
mTaskAdapter.registerAdapterDataObserver(new AdapterDataObserver() { mTaskAdapter.registerAdapterDataObserver(new AdapterDataObserver() {
@Override @Override
public void onChanged() { public void onChanged() {
@ -190,16 +187,7 @@ public final class IconRecentsView extends FrameLayout {
updateContentViewVisibility(); updateContentViewVisibility();
} }
}); });
// TODO: Move clear all button to recycler view so that it can scroll off screen.
// TODO: Move layout param logic into onMeasure // TODO: Move layout param logic into onMeasure
mClearAllView = findViewById(R.id.clear_all_button);
MarginLayoutParams clearAllParams =
(MarginLayoutParams) mClearAllView.getLayoutParams();
clearAllParams.height = getClearAllButtonHeight(mDeviceProfile);
clearAllParams.width = getClearAllButtonWidth(mDeviceProfile);
clearAllParams.topMargin = getClearAllButtonTopBottomMargin(mDeviceProfile);
clearAllParams.bottomMargin = getClearAllButtonTopBottomMargin(mDeviceProfile);
mClearAllView.setOnClickListener(v -> animateClearAllTasks());
} }
} }
@ -210,7 +198,7 @@ public final class IconRecentsView extends FrameLayout {
for (TaskItemView itemView : itemViews) { for (TaskItemView itemView : itemViews) {
itemView.setEnabled(enabled); itemView.setEnabled(enabled);
} }
mClearAllView.setEnabled(enabled); // TODO: Disable clear all button.
} }
/** /**
@ -365,6 +353,7 @@ public final class IconRecentsView extends FrameLayout {
* @return array of attached task item views * @return array of attached task item views
*/ */
private TaskItemView[] getTaskViews() { private TaskItemView[] getTaskViews() {
// TODO: Check that clear all button isn't here..
int taskCount = mTaskRecyclerView.getChildCount(); int taskCount = mTaskRecyclerView.getChildCount();
TaskItemView[] itemViews = new TaskItemView[taskCount]; TaskItemView[] itemViews = new TaskItemView[taskCount];
for (int i = 0; i < taskCount; i ++) { for (int i = 0; i < taskCount; i ++) {