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