--- /dev/null
+/*
+ * Copyright (C) 2016 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.systemui.recents.views;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+/**
+ * This is an optimized FrameLayout whose layout is completely directed by its parent, and as a
+ * result, does not propagate <code>requestLayout()</code> up the view hierarchy. Instead, it will
+ * relayout its children with the last known layout bounds when a layout is requested from a child
+ * view.
+ */
+public class FixedSizeFrameLayout extends FrameLayout {
+
+ private final Rect mLayoutBounds = new Rect();
+
+ public FixedSizeFrameLayout(Context context) {
+ super(context);
+ }
+
+ public FixedSizeFrameLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public FixedSizeFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public FixedSizeFrameLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected final void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ measureContents(MeasureSpec.getSize(widthMeasureSpec),
+ MeasureSpec.getSize(heightMeasureSpec));
+ }
+
+ @Override
+ protected final void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ mLayoutBounds.set(left, top, right, bottom);
+ layoutContents(mLayoutBounds, changed);
+ }
+
+ @Override
+ public final void requestLayout() {
+ // The base ViewGroup constructor attempts to call requestLayout() before this class's
+ // members are initialized so we should just propagate in that case
+ if (mLayoutBounds == null || mLayoutBounds.isEmpty()) {
+ super.requestLayout();
+ } else {
+ // If we are already laid out, then just reuse the same bounds to layout the children
+ // (but not itself)
+ // TODO: Investigate whether we should coalesce these to the next frame if needed
+ measureContents(getMeasuredWidth(), getMeasuredHeight());
+ layoutContents(mLayoutBounds, false);
+ }
+ }
+
+ /**
+ * Measures the contents of this fixed layout.
+ */
+ protected void measureContents(int width, int height) {
+ super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
+ }
+
+ /**
+ * Lays out the contents of this fixed layout.
+ */
+ protected void layoutContents(Rect bounds, boolean changed) {
+ super.onLayout(changed, bounds.left, bounds.top, bounds.right, bounds.bottom);
+ }
+
+}
import android.widget.ImageView;
/**
- * This is an optimized ImageView that does not trigger a requestLayout() or invalidate() when
- * setting the image to Null.
+ * This is an optimized ImageView that does not trigger a <code>requestLayout()</code> or
+ * <code>invalidate()</code> when setting the image to <code>null</code>.
*/
public class FixedSizeImageView extends ImageView {
- boolean mAllowRelayout = true;
- boolean mAllowInvalidate = true;
+ private boolean mAllowRelayout = true;
+ private boolean mAllowInvalidate = true;
public FixedSizeImageView(Context context) {
this(context, null);
// Report that this tasks's data is no longer being used
Recents.getTaskLoader().unloadTaskData(task);
- // Detach the view from the hierarchy
- detachViewFromParent(tv);
- // Update the task views list after removing the task view
- updateTaskViewsList();
-
// Reset the view properties and view state
tv.resetViewProperties();
tv.setFocusedState(false, false /* requestViewFocus */);
if (mScreenPinningEnabled) {
tv.hideActionButton(false /* fadeOut */, 0 /* duration */, false /* scaleDown */, null);
}
+
+ // Detach the view from the hierarchy
+ detachViewFromParent(tv);
+ // Update the task views list after removing the task view
+ updateTaskViewsList();
}
@Override
public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) {
- // Rebind the task and request that this task's data be filled into the TaskView
- tv.onTaskBound(task);
-
- // Load the task data
- Recents.getTaskLoader().loadTaskData(task);
-
- // If the doze trigger has already fired, then update the state for this task view
- tv.setNoUserInteractionState();
-
// Find the index where this task should be placed in the stack
int taskIndex = mStack.indexOfStackTask(task);
int insertIndex = findTaskViewInsertIndex(task, taskIndex);
// Update the task views list after adding the new task view
updateTaskViewsList();
+ // Rebind the task and request that this task's data be filled into the TaskView
+ tv.onTaskBound(task);
+
+ // Load the task data
+ Recents.getTaskLoader().loadTaskData(task);
+
+ // If the doze trigger has already fired, then update the state for this task view
+ tv.setNoUserInteractionState();
+
// Set the new state for this view, including the callbacks and view clipping
tv.setCallbacks(this);
tv.setTouchEnabled(true);
package com.android.systemui.recents.views;
import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
-import android.widget.FrameLayout;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivity;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-/* A task view */
-public class TaskView extends FrameLayout implements Task.TaskCallbacks,
+/**
+ * A {@link TaskView} represents a fixed view of a task. Because the TaskView's layout is directed
+ * solely by the {@link TaskStackView}, we make it a fixed size layout which allows relayouts down
+ * the view hierarchy, but not upwards from any of its children (the TaskView will relayout itself
+ * with the previous bounds if any child requests layout).
+ */
+public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks,
TaskStackAnimationHelper.Callbacks, View.OnClickListener, View.OnLongClickListener {
/** The TaskView callbacks */
return super.onInterceptTouchEvent(ev);
}
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int width = MeasureSpec.getSize(widthMeasureSpec);
- int height = MeasureSpec.getSize(heightMeasureSpec);
+ @Override
+ protected void measureContents(int width, int height) {
int widthWithoutPadding = width - mPaddingLeft - mPaddingRight;
int heightWithoutPadding = height - mPaddingTop - mPaddingBottom;
- int taskBarHeight = getResources().getDimensionPixelSize(R.dimen.recents_task_bar_height);
// Measure the content
mContent.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY));
- // Measure the bar view, and action button
- mHeaderView.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(taskBarHeight, MeasureSpec.EXACTLY));
- mActionButtonView.measure(
- MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.AT_MOST),
- MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.AT_MOST));
- // Measure the thumbnail to be square
- mThumbnailView.measure(
- MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY));
+ // Optimization: Prevent overdraw of the thumbnail under the header view
mThumbnailView.updateClipToTaskBar(mHeaderView);
setMeasuredDimension(width, height);
- invalidateOutline();
}
void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform,
* to match the frame changes.
*/
public void onTaskViewSizeChanged(int width, int height) {
- // Return early if the bounds have not changed
- if (mTaskViewRect.width() == width && mTaskViewRect.height() == height) {
- return;
- }
-
+ // TODO: Optimize this path
mTaskViewRect.set(0, 0, width, height);
boolean updateMoveTaskButton = mMoveTaskButton.getVisibility() != View.GONE;
boolean isFreeformTask = (mTask != null) && mTask.isFreeformTask();
if (!mInvisible) {
updateThumbnailPaintFilter();
}
- invalidate();
}
}