1 /* 2 * Copyright (C) 2016 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 17 package com.android.launcher3.graphics; 18 19 import android.content.Context; 20 import android.graphics.Bitmap; 21 import android.graphics.Canvas; 22 import android.graphics.Rect; 23 import android.graphics.Region.Op; 24 import android.graphics.drawable.Drawable; 25 import android.view.View; 26 import android.widget.TextView; 27 28 import com.android.launcher3.Launcher; 29 import com.android.launcher3.LauncherAppWidgetHostView; 30 import com.android.launcher3.R; 31 import com.android.launcher3.Workspace; 32 import com.android.launcher3.config.ProviderConfig; 33 import com.android.launcher3.folder.FolderIcon; 34 35 /** 36 * A utility class to generate preview bitmap for dragging. 37 */ 38 public class DragPreviewProvider { 39 40 private final Rect mTempRect = new Rect(); 41 42 protected final View mView; 43 44 // The padding added to the drag view during the preview generation. 45 public final int previewPadding; 46 47 protected final int blurSizeOutline; 48 49 public Bitmap generatedDragOutline; 50 DragPreviewProvider(View view)51 public DragPreviewProvider(View view) { 52 this(view, view.getContext()); 53 } 54 DragPreviewProvider(View view, Context context)55 public DragPreviewProvider(View view, Context context) { 56 mView = view; 57 blurSizeOutline = 58 context.getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline); 59 60 if (mView instanceof TextView) { 61 Drawable d = Workspace.getTextViewIcon((TextView) mView); 62 Rect bounds = getDrawableBounds(d); 63 previewPadding = blurSizeOutline - bounds.left - bounds.top; 64 } else { 65 previewPadding = blurSizeOutline; 66 } 67 } 68 69 /** 70 * Draws the {@link #mView} into the given {@param destCanvas}. 71 */ drawDragView(Canvas destCanvas)72 private void drawDragView(Canvas destCanvas) { 73 destCanvas.save(); 74 if (mView instanceof TextView) { 75 Drawable d = Workspace.getTextViewIcon((TextView) mView); 76 Rect bounds = getDrawableBounds(d); 77 destCanvas.translate(blurSizeOutline / 2 - bounds.left, 78 blurSizeOutline / 2 - bounds.top); 79 d.draw(destCanvas); 80 } else { 81 final Rect clipRect = mTempRect; 82 mView.getDrawingRect(clipRect); 83 84 boolean textVisible = false; 85 if (mView instanceof FolderIcon) { 86 // For FolderIcons the text can bleed into the icon area, and so we need to 87 // hide the text completely (which can't be achieved by clipping). 88 if (((FolderIcon) mView).getTextVisible()) { 89 ((FolderIcon) mView).setTextVisible(false); 90 textVisible = true; 91 } 92 } 93 destCanvas.translate(-mView.getScrollX() + blurSizeOutline / 2, 94 -mView.getScrollY() + blurSizeOutline / 2); 95 destCanvas.clipRect(clipRect, Op.REPLACE); 96 mView.draw(destCanvas); 97 98 // Restore text visibility of FolderIcon if necessary 99 if (textVisible) { 100 ((FolderIcon) mView).setTextVisible(true); 101 } 102 } 103 destCanvas.restore(); 104 } 105 106 /** 107 * Returns a new bitmap to show when the {@link #mView} is being dragged around. 108 * Responsibility for the bitmap is transferred to the caller. 109 */ createDragBitmap(Canvas canvas)110 public Bitmap createDragBitmap(Canvas canvas) { 111 float scale = 1f; 112 int width = mView.getWidth(); 113 int height = mView.getHeight(); 114 115 if (mView instanceof TextView) { 116 Drawable d = Workspace.getTextViewIcon((TextView) mView); 117 Rect bounds = getDrawableBounds(d); 118 width = bounds.width(); 119 height = bounds.height(); 120 } else if (mView instanceof LauncherAppWidgetHostView) { 121 scale = ((LauncherAppWidgetHostView) mView).getScaleToFit(); 122 width = (int) (mView.getWidth() * scale); 123 height = (int) (mView.getHeight() * scale); 124 } 125 126 Bitmap b = Bitmap.createBitmap(width + blurSizeOutline, height + blurSizeOutline, 127 Bitmap.Config.ARGB_8888); 128 canvas.setBitmap(b); 129 130 canvas.save(); 131 canvas.scale(scale, scale); 132 drawDragView(canvas); 133 canvas.restore(); 134 135 canvas.setBitmap(null); 136 137 return b; 138 } 139 generateDragOutline(Canvas canvas)140 public final void generateDragOutline(Canvas canvas) { 141 if (ProviderConfig.IS_DOGFOOD_BUILD && generatedDragOutline != null) { 142 throw new RuntimeException("Drag outline generated twice"); 143 } 144 145 generatedDragOutline = createDragOutline(canvas); 146 } 147 148 /** 149 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location. 150 * Responsibility for the bitmap is transferred to the caller. 151 */ createDragOutline(Canvas canvas)152 public Bitmap createDragOutline(Canvas canvas) { 153 float scale = 1f; 154 int width = mView.getWidth(); 155 int height = mView.getHeight(); 156 157 if (mView instanceof LauncherAppWidgetHostView) { 158 scale = ((LauncherAppWidgetHostView) mView).getScaleToFit(); 159 width = (int) Math.floor(mView.getWidth() * scale); 160 height = (int) Math.floor(mView.getHeight() * scale); 161 } 162 163 Bitmap b = Bitmap.createBitmap(width + blurSizeOutline, height + blurSizeOutline, 164 Bitmap.Config.ALPHA_8); 165 canvas.setBitmap(b); 166 167 canvas.save(); 168 canvas.scale(scale, scale); 169 drawDragView(canvas); 170 canvas.restore(); 171 172 HolographicOutlineHelper.getInstance(mView.getContext()) 173 .applyExpensiveOutlineWithBlur(b, canvas); 174 175 canvas.setBitmap(null); 176 return b; 177 } 178 getDrawableBounds(Drawable d)179 protected static Rect getDrawableBounds(Drawable d) { 180 Rect bounds = new Rect(); 181 d.copyBounds(bounds); 182 if (bounds.width() == 0 || bounds.height() == 0) { 183 bounds.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); 184 } else { 185 bounds.offsetTo(0, 0); 186 } 187 return bounds; 188 } 189 getScaleAndPosition(Bitmap preview, int[] outPos)190 public float getScaleAndPosition(Bitmap preview, int[] outPos) { 191 float scale = Launcher.getLauncher(mView.getContext()) 192 .getDragLayer().getLocationInDragLayer(mView, outPos); 193 if (mView instanceof LauncherAppWidgetHostView) { 194 // App widgets are technically scaled, but are drawn at their expected size -- so the 195 // app widget scale should not affect the scale of the preview. 196 scale /= ((LauncherAppWidgetHostView) mView).getScaleToFit(); 197 } 198 199 outPos[0] = Math.round(outPos[0] - 200 (preview.getWidth() - scale * mView.getWidth() * mView.getScaleX()) / 2); 201 outPos[1] = Math.round(outPos[1] - (1 - scale) * preview.getHeight() / 2 202 - previewPadding / 2); 203 return scale; 204 } 205 } 206