CropView.java revision e72aa7f0a64ff55f747751b7972ccb8acebab7da
1/* 2 * Copyright (C) 2013 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/* Copied from Launcher3 */ 17package com.android.wallpapercropper; 18 19import android.content.Context; 20import android.graphics.Point; 21import android.graphics.RectF; 22import android.util.AttributeSet; 23import android.view.MotionEvent; 24import android.view.ScaleGestureDetector; 25import android.view.ScaleGestureDetector.OnScaleGestureListener; 26import android.view.ViewConfiguration; 27import android.view.ViewTreeObserver; 28import android.view.ViewTreeObserver.OnGlobalLayoutListener; 29 30import com.android.photos.views.TiledImageRenderer.TileSource; 31import com.android.photos.views.TiledImageView; 32 33public class CropView extends TiledImageView implements OnScaleGestureListener { 34 35 private ScaleGestureDetector mScaleGestureDetector; 36 private long mTouchDownTime; 37 private float mFirstX, mFirstY; 38 private float mLastX, mLastY; 39 private float mMinScale; 40 private boolean mTouchEnabled = true; 41 private RectF mTempEdges = new RectF(); 42 TouchCallback mTouchCallback; 43 44 public interface TouchCallback { 45 void onTouchDown(); 46 void onTap(); 47 void onTouchUp(); 48 } 49 50 public CropView(Context context) { 51 this(context, null); 52 } 53 54 public CropView(Context context, AttributeSet attrs) { 55 super(context, attrs); 56 mScaleGestureDetector = new ScaleGestureDetector(context, this); 57 } 58 59 private void getEdgesHelper(RectF edgesOut) { 60 final float width = getWidth(); 61 final float height = getHeight(); 62 final float imageWidth = mRenderer.source.getImageWidth(); 63 final float imageHeight = mRenderer.source.getImageHeight(); 64 final float scale = mRenderer.scale; 65 float centerX = (width / 2f - mRenderer.centerX + (imageWidth - width) / 2f) 66 * scale + width / 2f; 67 float centerY = (height / 2f - mRenderer.centerY + (imageHeight - height) / 2f) 68 * scale + height / 2f; 69 float leftEdge = centerX - imageWidth / 2f * scale; 70 float rightEdge = centerX + imageWidth / 2f * scale; 71 float topEdge = centerY - imageHeight / 2f * scale; 72 float bottomEdge = centerY + imageHeight / 2f * scale; 73 74 edgesOut.left = leftEdge; 75 edgesOut.right = rightEdge; 76 edgesOut.top = topEdge; 77 edgesOut.bottom = bottomEdge; 78 } 79 80 public RectF getCrop() { 81 final RectF edges = mTempEdges; 82 getEdgesHelper(edges); 83 final float scale = mRenderer.scale; 84 85 float cropLeft = -edges.left / scale; 86 float cropTop = -edges.top / scale; 87 float cropRight = cropLeft + getWidth() / scale; 88 float cropBottom = cropTop + getHeight() / scale; 89 90 return new RectF(cropLeft, cropTop, cropRight, cropBottom); 91 } 92 93 public Point getSourceDimensions() { 94 return new Point(mRenderer.source.getImageWidth(), mRenderer.source.getImageHeight()); 95 } 96 97 public void setTileSource(TileSource source, Runnable isReadyCallback) { 98 super.setTileSource(source, isReadyCallback); 99 updateMinScale(getWidth(), getHeight(), source, true); 100 } 101 102 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 103 updateMinScale(w, h, mRenderer.source, false); 104 } 105 106 public void setScale(float scale) { 107 synchronized (mLock) { 108 mRenderer.scale = scale; 109 } 110 } 111 112 private void updateMinScale(int w, int h, TileSource source, boolean resetScale) { 113 synchronized (mLock) { 114 if (resetScale) { 115 mRenderer.scale = 1; 116 } 117 if (source != null) { 118 mMinScale = Math.max(w / (float) source.getImageWidth(), 119 h / (float) source.getImageHeight()); 120 mRenderer.scale = Math.max(mMinScale, mRenderer.scale); 121 } 122 } 123 } 124 125 @Override 126 public boolean onScaleBegin(ScaleGestureDetector detector) { 127 return true; 128 } 129 130 @Override 131 public boolean onScale(ScaleGestureDetector detector) { 132 // Don't need the lock because this will only fire inside of 133 // onTouchEvent 134 mRenderer.scale *= detector.getScaleFactor(); 135 mRenderer.scale = Math.max(mMinScale, mRenderer.scale); 136 invalidate(); 137 return true; 138 } 139 140 @Override 141 public void onScaleEnd(ScaleGestureDetector detector) { 142 } 143 144 public void moveToLeft() { 145 if (getWidth() == 0 || getHeight() == 0) { 146 final ViewTreeObserver observer = getViewTreeObserver(); 147 observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { 148 public void onGlobalLayout() { 149 moveToLeft(); 150 getViewTreeObserver().removeOnGlobalLayoutListener(this); 151 } 152 }); 153 } 154 final RectF edges = mTempEdges; 155 getEdgesHelper(edges); 156 final float scale = mRenderer.scale; 157 mRenderer.centerX += Math.ceil(edges.left / scale); 158 } 159 160 public void setTouchEnabled(boolean enabled) { 161 mTouchEnabled = enabled; 162 } 163 164 public void setTouchCallback(TouchCallback cb) { 165 mTouchCallback = cb; 166 } 167 168 @Override 169 public boolean onTouchEvent(MotionEvent event) { 170 int action = event.getActionMasked(); 171 final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP; 172 final int skipIndex = pointerUp ? event.getActionIndex() : -1; 173 174 // Determine focal point 175 float sumX = 0, sumY = 0; 176 final int count = event.getPointerCount(); 177 for (int i = 0; i < count; i++) { 178 if (skipIndex == i) 179 continue; 180 sumX += event.getX(i); 181 sumY += event.getY(i); 182 } 183 final int div = pointerUp ? count - 1 : count; 184 float x = sumX / div; 185 float y = sumY / div; 186 187 if (action == MotionEvent.ACTION_DOWN) { 188 mFirstX = x; 189 mFirstY = y; 190 mTouchDownTime = System.currentTimeMillis(); 191 if (mTouchCallback != null) { 192 mTouchCallback.onTouchDown(); 193 } 194 } else if (action == MotionEvent.ACTION_UP) { 195 ViewConfiguration config = ViewConfiguration.get(getContext()); 196 197 float squaredDist = (mFirstX - x) * (mFirstX - x) + (mFirstY - y) * (mFirstY - y); 198 float slop = config.getScaledTouchSlop() * config.getScaledTouchSlop(); 199 long now = System.currentTimeMillis(); 200 if (mTouchCallback != null) { 201 // only do this if it's a small movement 202 if (squaredDist < slop && 203 now < mTouchDownTime + ViewConfiguration.getTapTimeout()) { 204 mTouchCallback.onTap(); 205 } 206 mTouchCallback.onTouchUp(); 207 } 208 } 209 210 if (!mTouchEnabled) { 211 return true; 212 } 213 214 synchronized (mLock) { 215 mScaleGestureDetector.onTouchEvent(event); 216 switch (action) { 217 case MotionEvent.ACTION_MOVE: 218 mRenderer.centerX += (mLastX - x) / mRenderer.scale; 219 mRenderer.centerY += (mLastY - y) / mRenderer.scale; 220 invalidate(); 221 break; 222 } 223 if (mRenderer.source != null) { 224 // Adjust position so that the wallpaper covers the entire area 225 // of the screen 226 final RectF edges = mTempEdges; 227 getEdgesHelper(edges); 228 final float scale = mRenderer.scale; 229 if (edges.left > 0) { 230 mRenderer.centerX += Math.ceil(edges.left / scale); 231 } 232 if (edges.right < getWidth()) { 233 mRenderer.centerX += (edges.right - getWidth()) / scale; 234 } 235 if (edges.top > 0) { 236 mRenderer.centerY += Math.ceil(edges.top / scale); 237 } 238 if (edges.bottom < getHeight()) { 239 mRenderer.centerY += (edges.bottom - getHeight()) / scale; 240 } 241 } 242 } 243 244 mLastX = x; 245 mLastY = y; 246 return true; 247 } 248} 249