mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-20 11:18:21 +00:00
Implement non-disappearing View for split staging instructions
The instructions for how to perform a splitscreen operation, previously conveyed through a disappearing Toast, are now conveyed through a custom View object. Fixes: 219987907 Test: Manual Change-Id: Iff2bb6e334e0325e8a091d76a5f9b8767071365f
This commit is contained in:
21
quickstep/res/drawable/split_instructions_background.xml
Normal file
21
quickstep/res/drawable/split_instructions_background.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2022 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.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="?androidprv:attr/colorAccentPrimary" />
|
||||
<corners android:radius="@dimen/split_instructions_radius" />
|
||||
</shape>
|
||||
35
quickstep/res/layout/split_instructions_view.xml
Normal file
35
quickstep/res/layout/split_instructions_view.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2022 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.
|
||||
-->
|
||||
<com.android.quickstep.views.SplitInstructionsView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/split_instructions_background"
|
||||
android:paddingRight="@dimen/split_instructions_horizontal_padding"
|
||||
android:paddingLeft="@dimen/split_instructions_horizontal_padding"
|
||||
android:paddingTop="@dimen/split_instructions_vertical_padding"
|
||||
android:paddingBottom="@dimen/split_instructions_vertical_padding"
|
||||
android:elevation="@dimen/split_instructions_elevation"
|
||||
android:visibility="gone">
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/split_instructions_text"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:gravity="center"
|
||||
android:textColor="?androidprv:attr/textColorOnAccent"
|
||||
android:text="@string/toast_split_select_app" />
|
||||
</com.android.quickstep.views.SplitInstructionsView>
|
||||
@@ -630,6 +630,8 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
private final Toast mSplitUnsupportedToast = Toast.makeText(getContext(),
|
||||
R.string.toast_split_app_unsupported, Toast.LENGTH_SHORT);
|
||||
|
||||
private SplitInstructionsView mSplitInstructionsView;
|
||||
|
||||
@Nullable
|
||||
private QuickstepSystemShortcut.SplitSelectSource mSplitSelectSource;
|
||||
|
||||
@@ -2764,11 +2766,15 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
mFirstFloatingTaskView.addAnimation(anim, startingTaskRect, mTempRect,
|
||||
false /* fadeWithThumbnail */, true /* isStagedTask */);
|
||||
}
|
||||
|
||||
mSplitInstructionsView = SplitInstructionsView.getSplitInstructionsView(mActivity);
|
||||
mSplitInstructionsView.setAlpha(0);
|
||||
anim.addFloat(mSplitInstructionsView, SplitInstructionsView.ALPHA_FLOAT, 0, 1, ACCEL);
|
||||
|
||||
InteractionJankMonitorWrapper.begin(this,
|
||||
InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "First tile selected");
|
||||
anim.addEndListener(success -> {
|
||||
if (success) {
|
||||
mSplitToast.show();
|
||||
InteractionJankMonitorWrapper.end(
|
||||
InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
|
||||
} else {
|
||||
@@ -4099,6 +4105,10 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
@SuppressLint("WrongCall")
|
||||
protected void resetFromSplitSelectionState() {
|
||||
if (mSplitSelectSource != null || mSplitHiddenTaskViewIndex != -1) {
|
||||
if (mSplitInstructionsView != null) {
|
||||
mActivity.getDragLayer().removeView(mSplitInstructionsView);
|
||||
mSplitInstructionsView = null;
|
||||
}
|
||||
if (mFirstFloatingTaskView != null) {
|
||||
mActivity.getRootView().removeView(mFirstFloatingTaskView);
|
||||
mFirstFloatingTaskView = null;
|
||||
@@ -4164,6 +4174,10 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
taskViewsFloat.first.set(this, getSplitSelectTranslation());
|
||||
taskViewsFloat.second.set(this, 0f);
|
||||
|
||||
if (mSplitInstructionsView != null) {
|
||||
mSplitInstructionsView.ensureProperRotation();
|
||||
}
|
||||
|
||||
applySplitPrimaryScrollOffset();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2022 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.views;
|
||||
|
||||
import static com.android.launcher3.util.DisplayController.NavigationMode.THREE_BUTTONS;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.FloatProperty;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.statemanager.StatefulActivity;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
|
||||
/**
|
||||
* A rounded rectangular component containing a single TextView.
|
||||
* Appears when a split is in progress, and tells the user to select a second app to initiate
|
||||
* splitscreen.
|
||||
*
|
||||
* Appears and disappears concurrently with a FloatingTaskView.
|
||||
*/
|
||||
public class SplitInstructionsView extends FrameLayout {
|
||||
private final StatefulActivity mLauncher;
|
||||
|
||||
public static final FloatProperty<SplitInstructionsView> ALPHA_FLOAT =
|
||||
new FloatProperty<SplitInstructionsView>("SplitInstructionsAlpha") {
|
||||
@Override
|
||||
public void setValue(SplitInstructionsView splitInstructionsView, float v) {
|
||||
splitInstructionsView.setVisibility(v != 0 ? VISIBLE : GONE);
|
||||
splitInstructionsView.setAlpha(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(SplitInstructionsView splitInstructionsView) {
|
||||
return splitInstructionsView.getAlpha();
|
||||
}
|
||||
};
|
||||
|
||||
public SplitInstructionsView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public SplitInstructionsView(Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public SplitInstructionsView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mLauncher = (StatefulActivity) context;
|
||||
}
|
||||
|
||||
static SplitInstructionsView getSplitInstructionsView(StatefulActivity launcher) {
|
||||
ViewGroup dragLayer = launcher.getDragLayer();
|
||||
final SplitInstructionsView splitInstructionsView =
|
||||
(SplitInstructionsView) launcher.getLayoutInflater().inflate(
|
||||
R.layout.split_instructions_view,
|
||||
dragLayer,
|
||||
false
|
||||
);
|
||||
|
||||
dragLayer.addView(splitInstructionsView);
|
||||
return splitInstructionsView;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
ensureProperRotation();
|
||||
}
|
||||
|
||||
void ensureProperRotation() {
|
||||
((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler()
|
||||
.setSplitInstructionsParams(
|
||||
this,
|
||||
mLauncher.getDeviceProfile(),
|
||||
getMeasuredHeight(),
|
||||
getMeasuredWidth(),
|
||||
getThreeButtonNavShift()
|
||||
);
|
||||
}
|
||||
|
||||
// In some cases, when user is using 3-button nav, there isn't enough room for both the
|
||||
// 3-button nav and a centered SplitInstructionsView. This function will return an int that will
|
||||
// be used to shift the SplitInstructionsView over a bit so that everything looks well-spaced.
|
||||
// In many cases, this will return 0, since we don't need to shift it away from the center.
|
||||
int getThreeButtonNavShift() {
|
||||
DeviceProfile dp = mLauncher.getDeviceProfile();
|
||||
if ((DisplayController.getNavigationMode(getContext()) == THREE_BUTTONS)
|
||||
&& ((dp.isTwoPanels) || (dp.isTablet && !dp.isLandscape))) {
|
||||
int navButtonWidth = getResources().getDimensionPixelSize(
|
||||
R.dimen.taskbar_nav_buttons_size);
|
||||
int extraMargin = getResources().getDimensionPixelSize(
|
||||
R.dimen.taskbar_contextual_button_margin);
|
||||
// Explanation: The 3-button nav for non-phones sits on one side of the screen, taking
|
||||
// up 3 buttons + a side margin worth of space. Our splitInstructionsView starts in the
|
||||
// center of the screen and we want to center it in the remaining space, therefore we
|
||||
// want to shift it over by half the 3-button layout's width.
|
||||
// If the user is using an RtL layout, we shift it the opposite way.
|
||||
return -((3 * navButtonWidth + extraMargin) / 2) * (isLayoutRtl() ? -1 : 1);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -390,8 +390,17 @@
|
||||
<dimen name="split_placeholder_inset">16dp</dimen>
|
||||
<dimen name="split_placeholder_icon_size">44dp</dimen>
|
||||
<dimen name="task_menu_width_grid">216dp</dimen>
|
||||
|
||||
|
||||
<dimen name="split_instructions_radius">22dp</dimen>
|
||||
<dimen name="split_instructions_elevation">1dp</dimen>
|
||||
<dimen name="split_instructions_horizontal_padding">24dp</dimen>
|
||||
<dimen name="split_instructions_vertical_padding">12dp</dimen>
|
||||
<dimen name="split_instructions_bottom_margin_tablet_landscape">32dp</dimen>
|
||||
<dimen name="split_instructions_bottom_margin_tablet_portrait">44dp</dimen>
|
||||
<dimen name="split_instructions_bottom_margin_twopanels_landscape">33dp</dimen>
|
||||
<dimen name="split_instructions_bottom_margin_twopanels_portrait">51dp</dimen>
|
||||
<dimen name="split_instructions_bottom_margin_phone_landscape">24dp</dimen>
|
||||
<dimen name="split_instructions_bottom_margin_phone_portrait">60dp</dimen>
|
||||
|
||||
<!-- Workspace grid visualization parameters -->
|
||||
<dimen name="grid_visualization_rounding_radius">28dp</dimen>
|
||||
<dimen name="grid_visualization_horizontal_cell_spacing">6dp</dimen>
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.launcher3.touch;
|
||||
import static android.view.Gravity.BOTTOM;
|
||||
import static android.view.Gravity.CENTER_VERTICAL;
|
||||
import static android.view.Gravity.END;
|
||||
import static android.view.Gravity.LEFT;
|
||||
import static android.view.Gravity.START;
|
||||
import static android.view.Gravity.TOP;
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
@@ -424,6 +425,28 @@ public class LandscapePagedViewHandler implements PagedOrientationHandler {
|
||||
- 1.0f * drawableHeight / 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
|
||||
int splitInstructionsWidth, int threeButtonNavShift) {
|
||||
out.setPivotX(0);
|
||||
out.setPivotY(0);
|
||||
out.setRotation(getDegreesRotated());
|
||||
int distanceToEdge = out.getResources().getDimensionPixelSize(
|
||||
R.dimen.split_instructions_bottom_margin_phone_landscape);
|
||||
// Adjust for any insets on the left edge
|
||||
int insetCorrectionX = dp.getInsets().left;
|
||||
// Center the view in case of unbalanced insets on top or bottom of screen
|
||||
int insetCorrectionY = (dp.getInsets().bottom - dp.getInsets().top) / 2;
|
||||
out.setTranslationX(splitInstructionsHeight + distanceToEdge - insetCorrectionX);
|
||||
out.setTranslationY(((splitInstructionsHeight - splitInstructionsWidth) / 2f)
|
||||
+ insetCorrectionY);
|
||||
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
|
||||
// Setting gravity to LEFT instead of the lint-recommended START because we always want this
|
||||
// view to be screen-left when phone is in landscape, regardless of the RtL setting.
|
||||
lp.gravity = LEFT | CENTER_VERTICAL;
|
||||
out.setLayoutParams(lp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
|
||||
@StagePosition int stagePosition, Rect out1, Rect out2) {
|
||||
|
||||
@@ -134,6 +134,16 @@ public interface PagedOrientationHandler {
|
||||
int drawableWidth, int drawableHeight, DeviceProfile dp,
|
||||
@StagePosition int stagePosition);
|
||||
|
||||
/**
|
||||
* Sets positioning and rotation for a SplitInstructionsView.
|
||||
* @param out The SplitInstructionsView that needs to be positioned.
|
||||
* @param dp The device profile, used to report rotation and device type.
|
||||
* @param splitInstructionsHeight The SplitInstructionView's height.
|
||||
* @param splitInstructionsWidth The SplitInstructionView's width.
|
||||
*/
|
||||
void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
|
||||
int splitInstructionsWidth, int threeButtonNavShift);
|
||||
|
||||
/**
|
||||
* @param splitDividerSize height of split screen drag handle in portrait, width in landscape
|
||||
* @param stagePosition the split position option (top/left, bottom/right) of the first
|
||||
|
||||
@@ -27,6 +27,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
|
||||
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
|
||||
import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
|
||||
import static com.android.launcher3.util.DisplayController.NavigationMode.THREE_BUTTONS;
|
||||
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
|
||||
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
|
||||
|
||||
@@ -47,7 +48,9 @@ import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
|
||||
@@ -484,6 +487,58 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
|
||||
int splitInstructionsWidth, int threeButtonNavShift) {
|
||||
out.setPivotX(0);
|
||||
out.setPivotY(0);
|
||||
out.setRotation(getDegreesRotated());
|
||||
int distanceToEdge;
|
||||
if ((DisplayController.getNavigationMode(out.getContext()) == THREE_BUTTONS)
|
||||
&& (dp.isTwoPanels || dp.isTablet)) {
|
||||
// If 3-button nav is active, align the splitInstructionsView with it.
|
||||
distanceToEdge = dp.getTaskbarOffsetY()
|
||||
+ ((dp.taskbarSize - splitInstructionsHeight) / 2);
|
||||
} else {
|
||||
// If 3-button nav is not active, set bottom margin according to spec.
|
||||
if (dp.isPhone) {
|
||||
if (dp.isLandscape) {
|
||||
distanceToEdge = out.getResources().getDimensionPixelSize(
|
||||
R.dimen.split_instructions_bottom_margin_phone_landscape);
|
||||
} else {
|
||||
distanceToEdge = out.getResources().getDimensionPixelSize(
|
||||
R.dimen.split_instructions_bottom_margin_phone_portrait);
|
||||
}
|
||||
} else if (dp.isTwoPanels) {
|
||||
if (dp.isLandscape) {
|
||||
distanceToEdge = out.getResources().getDimensionPixelSize(
|
||||
R.dimen.split_instructions_bottom_margin_twopanels_landscape);
|
||||
} else {
|
||||
distanceToEdge = out.getResources().getDimensionPixelSize(
|
||||
R.dimen.split_instructions_bottom_margin_twopanels_portrait);
|
||||
}
|
||||
} else {
|
||||
if (dp.isLandscape) {
|
||||
distanceToEdge = out.getResources().getDimensionPixelSize(
|
||||
R.dimen.split_instructions_bottom_margin_tablet_landscape);
|
||||
} else {
|
||||
distanceToEdge = out.getResources().getDimensionPixelSize(
|
||||
R.dimen.split_instructions_bottom_margin_tablet_portrait);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Center the view in case of unbalanced insets on left or right of screen
|
||||
int insetCorrectionX = (dp.getInsets().right - dp.getInsets().left) / 2;
|
||||
// Adjust for any insets on the bottom edge
|
||||
int insetCorrectionY = dp.getInsets().bottom;
|
||||
out.setTranslationX(insetCorrectionX + threeButtonNavShift);
|
||||
out.setTranslationY(-distanceToEdge + insetCorrectionY);
|
||||
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
|
||||
lp.gravity = CENTER_HORIZONTAL | BOTTOM;
|
||||
out.setLayoutParams(lp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
|
||||
@StagePosition int stagePosition, Rect out1, Rect out2) {
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.launcher3.touch;
|
||||
import static android.view.Gravity.BOTTOM;
|
||||
import static android.view.Gravity.CENTER_VERTICAL;
|
||||
import static android.view.Gravity.END;
|
||||
import static android.view.Gravity.RIGHT;
|
||||
import static android.view.Gravity.START;
|
||||
|
||||
import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
|
||||
@@ -166,6 +167,29 @@ public class SeascapePagedViewHandler extends LandscapePagedViewHandler {
|
||||
STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
|
||||
int splitInstructionsWidth, int threeButtonNavShift) {
|
||||
out.setPivotX(0);
|
||||
out.setPivotY(0);
|
||||
out.setRotation(getDegreesRotated());
|
||||
int distanceToEdge = out.getResources().getDimensionPixelSize(
|
||||
R.dimen.split_instructions_bottom_margin_phone_landscape);
|
||||
// Adjust for any insets on the right edge
|
||||
int insetCorrectionX = dp.getInsets().right;
|
||||
// Center the view in case of unbalanced insets on top or bottom of screen
|
||||
int insetCorrectionY = (dp.getInsets().bottom - dp.getInsets().top) / 2;
|
||||
out.setTranslationX(splitInstructionsWidth - splitInstructionsHeight - distanceToEdge
|
||||
+ insetCorrectionX);
|
||||
out.setTranslationY(((splitInstructionsHeight + splitInstructionsWidth) / 2f)
|
||||
+ insetCorrectionY);
|
||||
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
|
||||
// Setting gravity to RIGHT instead of the lint-recommended END because we always want this
|
||||
// view to be screen-right when phone is in seascape, regardless of the RtL setting.
|
||||
lp.gravity = RIGHT | CENTER_VERTICAL;
|
||||
out.setLayoutParams(lp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTaskIconParams(FrameLayout.LayoutParams iconParams,
|
||||
int taskIconMargin, int taskIconHeight, int thumbnailTopMargin, boolean isRtl) {
|
||||
|
||||
Reference in New Issue
Block a user