1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3; 17 18 import android.content.Context; 19 import android.graphics.Canvas; 20 import android.graphics.Rect; 21 import android.graphics.drawable.Drawable; 22 import android.util.AttributeSet; 23 import android.view.View; 24 25 import com.android.launcher3.celllayout.CellLayoutLayoutParams; 26 import com.android.launcher3.celllayout.ItemConfiguration; 27 import com.android.launcher3.celllayout.MulticellReorderAlgorithm; 28 import com.android.launcher3.util.CellAndSpan; 29 import com.android.launcher3.util.GridOccupancy; 30 import com.android.launcher3.util.MultiTranslateDelegate; 31 32 /** 33 * CellLayout that simulates a split in the middle for use in foldable devices. 34 */ 35 public class MultipageCellLayout extends CellLayout { 36 37 private final Drawable mLeftBackground; 38 private final Drawable mRightBackground; 39 40 private boolean mSeamWasAdded = false; 41 MultipageCellLayout(Context context)42 public MultipageCellLayout(Context context) { 43 this(context, null); 44 } 45 MultipageCellLayout(Context context, AttributeSet attrs)46 public MultipageCellLayout(Context context, AttributeSet attrs) { 47 this(context, attrs, 0); 48 } 49 50 @Override findNearestArea(int relativeXPos, int relativeYPos, int minSpanX, int minSpanY, int spanX, int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan)51 protected int[] findNearestArea(int relativeXPos, int relativeYPos, int minSpanX, int minSpanY, 52 int spanX, int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) { 53 return createReorderAlgorithm().simulateSeam( 54 () -> super.findNearestArea(relativeXPos, relativeYPos, minSpanX, minSpanY, spanX, 55 spanY, ignoreOccupied, result, resultSpan)); 56 } 57 58 @Override isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY, View dragView, int[] result)59 public boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY, 60 View dragView, int[] result) { 61 return createReorderAlgorithm().simulateSeam( 62 () -> super.isNearestDropLocationOccupied(pixelX, pixelY, spanX, spanY, dragView, 63 result)); 64 } 65 MultipageCellLayout(Context context, AttributeSet attrs, int defStyle)66 public MultipageCellLayout(Context context, AttributeSet attrs, int defStyle) { 67 super(context, attrs, defStyle); 68 mLeftBackground = getContext().getDrawable(R.drawable.bg_celllayout); 69 mLeftBackground.setCallback(this); 70 mLeftBackground.setAlpha(0); 71 72 mRightBackground = getContext().getDrawable(R.drawable.bg_celllayout); 73 mRightBackground.setCallback(this); 74 mRightBackground.setAlpha(0); 75 76 DeviceProfile deviceProfile = mActivity.getDeviceProfile(); 77 78 mCountX = deviceProfile.inv.numColumns * 2; 79 mCountY = deviceProfile.inv.numRows; 80 setGridSize(mCountX, mCountY); 81 } 82 83 @Override createAreaForResize(int cellX, int cellY, int spanX, int spanY, View dragView, int[] direction, boolean commit)84 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY, View dragView, 85 int[] direction, boolean commit) { 86 // Add seam to x position 87 if (cellX >= mCountX / 2) { 88 cellX++; 89 } 90 int finalCellX = cellX; 91 return createReorderAlgorithm().simulateSeam( 92 () -> super.createAreaForResize(finalCellX, cellY, spanX, spanY, dragView, 93 direction, commit)); 94 } 95 96 @Override performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, View dragView, int[] result, int[] resultSpan, int mode)97 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, 98 View dragView, int[] result, int[] resultSpan, int mode) { 99 if (pixelX >= getWidth() / 2) { 100 pixelX += getCellWidth(); 101 } 102 return super.performReorder(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, dragView, 103 result, resultSpan, mode); 104 } 105 106 @Override createReorderAlgorithm()107 public MulticellReorderAlgorithm createReorderAlgorithm() { 108 return new MulticellReorderAlgorithm(this); 109 } 110 111 @Override copyCurrentStateToSolution(ItemConfiguration solution)112 public void copyCurrentStateToSolution(ItemConfiguration solution) { 113 int childCount = mShortcutsAndWidgets.getChildCount(); 114 for (int i = 0; i < childCount; i++) { 115 View child = mShortcutsAndWidgets.getChildAt(i); 116 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams(); 117 int seamOffset = lp.getCellX() >= mCountX / 2 && lp.canReorder ? 1 : 0; 118 CellAndSpan c = new CellAndSpan(lp.getCellX() + seamOffset, lp.getCellY(), lp.cellHSpan, 119 lp.cellVSpan); 120 solution.add(child, c); 121 } 122 } 123 124 @Override getUnusedHorizontalSpace()125 public int getUnusedHorizontalSpace() { 126 return (int) Math.ceil( 127 (getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth) 128 - ((mCountX - 1) * mBorderSpace.x)) / 2f); 129 } 130 131 @Override onDraw(Canvas canvas)132 protected void onDraw(Canvas canvas) { 133 float animatedWorkspaceMargin = mSpaceBetweenCellLayoutsPx * mSpringLoadedProgress; 134 if (mLeftBackground.getAlpha() > 0) { 135 canvas.save(); 136 canvas.translate(-animatedWorkspaceMargin, 0); 137 mLeftBackground.setState(mBackground.getState()); 138 mLeftBackground.draw(canvas); 139 canvas.restore(); 140 } 141 if (mRightBackground.getAlpha() > 0) { 142 canvas.save(); 143 canvas.translate(animatedWorkspaceMargin, 0); 144 mRightBackground.setState(mBackground.getState()); 145 mRightBackground.draw(canvas); 146 canvas.restore(); 147 } 148 super.onDraw(canvas); 149 } 150 updateMarginBetweenCellLayouts()151 private void updateMarginBetweenCellLayouts() { 152 for (int i = 0; i < mShortcutsAndWidgets.getChildCount(); i++) { 153 View workspaceItem = mShortcutsAndWidgets.getChildAt(i); 154 if (!(workspaceItem instanceof Reorderable)) { 155 continue; 156 } 157 CellLayoutLayoutParams params = 158 (CellLayoutLayoutParams) workspaceItem.getLayoutParams(); 159 ((Reorderable) workspaceItem).getTranslateDelegate().setTranslation( 160 MultiTranslateDelegate.INDEX_CELLAYOUT_MULTIPAGE_SPACING, 161 getMarginForGivenCellParams(params), 162 0 163 ); 164 165 } 166 } 167 168 @Override getMarginForGivenCellParams(CellLayoutLayoutParams params)169 protected float getMarginForGivenCellParams(CellLayoutLayoutParams params) { 170 float margin = mSpaceBetweenCellLayoutsPx * mSpringLoadedProgress; 171 return params.getCellX() >= mCountX / 2 ? margin : -margin; 172 } 173 174 @Override setSpringLoadedProgress(float progress)175 public void setSpringLoadedProgress(float progress) { 176 super.setSpringLoadedProgress(progress); 177 updateMarginBetweenCellLayouts(); 178 } 179 180 @Override setSpaceBetweenCellLayoutsPx(int spaceBetweenCellLayoutsPx)181 public void setSpaceBetweenCellLayoutsPx(int spaceBetweenCellLayoutsPx) { 182 super.setSpaceBetweenCellLayoutsPx(spaceBetweenCellLayoutsPx); 183 updateMarginBetweenCellLayouts(); 184 } 185 186 @Override updateBgAlpha()187 protected void updateBgAlpha() { 188 mLeftBackground.setAlpha((int) (mSpringLoadedProgress * 255)); 189 mRightBackground.setAlpha((int) (mSpringLoadedProgress * 255)); 190 } 191 192 @Override onLayout(boolean changed, int l, int t, int r, int b)193 protected void onLayout(boolean changed, int l, int t, int r, int b) { 194 super.onLayout(changed, l, t, r, b); 195 Rect rect = mBackground.getBounds(); 196 int middlePointInPixels = rect.centerX(); 197 mLeftBackground.setBounds(rect.left, rect.top, middlePointInPixels, rect.bottom); 198 mRightBackground.setBounds(middlePointInPixels, rect.top, rect.right, rect.bottom); 199 } 200 setCountX(int countX)201 public void setCountX(int countX) { 202 mCountX = countX; 203 } 204 setCountY(int countY)205 public void setCountY(int countY) { 206 mCountY = countY; 207 } 208 setOccupied(GridOccupancy occupied)209 public void setOccupied(GridOccupancy occupied) { 210 mOccupied = occupied; 211 } 212 isSeamWasAdded()213 public boolean isSeamWasAdded() { 214 return mSeamWasAdded; 215 } 216 setSeamWasAdded(boolean seamWasAdded)217 public void setSeamWasAdded(boolean seamWasAdded) { 218 mSeamWasAdded = seamWasAdded; 219 } 220 } 221