• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
17 package com.android.wallpapercropper;
18 
19 import android.content.Context;
20 import android.graphics.Matrix;
21 import android.graphics.Point;
22 import android.graphics.RectF;
23 import android.util.AttributeSet;
24 import android.util.FloatMath;
25 import android.view.MotionEvent;
26 import android.view.ScaleGestureDetector;
27 import android.view.ScaleGestureDetector.OnScaleGestureListener;
28 import android.view.ViewConfiguration;
29 import android.view.ViewTreeObserver;
30 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
31 
32 import com.android.photos.views.TiledImageRenderer.TileSource;
33 import com.android.photos.views.TiledImageView;
34 
35 public class CropView extends TiledImageView implements OnScaleGestureListener {
36 
37     private ScaleGestureDetector mScaleGestureDetector;
38     private long mTouchDownTime;
39     private float mFirstX, mFirstY;
40     private float mLastX, mLastY;
41     private float mCenterX, mCenterY;
42     private float mMinScale;
43     private boolean mTouchEnabled = true;
44     private RectF mTempEdges = new RectF();
45     private float[] mTempPoint = new float[] { 0, 0 };
46     private float[] mTempCoef = new float[] { 0, 0 };
47     private float[] mTempAdjustment = new float[] { 0, 0 };
48     private float[] mTempImageDims = new float[] { 0, 0 };
49     private float[] mTempRendererCenter = new float[] { 0, 0 };
50     TouchCallback mTouchCallback;
51     Matrix mRotateMatrix;
52     Matrix mInverseRotateMatrix;
53 
54     public interface TouchCallback {
onTouchDown()55         void onTouchDown();
onTap()56         void onTap();
onTouchUp()57         void onTouchUp();
58     }
59 
CropView(Context context)60     public CropView(Context context) {
61         this(context, null);
62     }
63 
CropView(Context context, AttributeSet attrs)64     public CropView(Context context, AttributeSet attrs) {
65         super(context, attrs);
66         mScaleGestureDetector = new ScaleGestureDetector(context, this);
67         mRotateMatrix = new Matrix();
68         mInverseRotateMatrix = new Matrix();
69     }
70 
getImageDims()71     private float[] getImageDims() {
72         final float imageWidth = mRenderer.source.getImageWidth();
73         final float imageHeight = mRenderer.source.getImageHeight();
74         float[] imageDims = mTempImageDims;
75         imageDims[0] = imageWidth;
76         imageDims[1] = imageHeight;
77         mRotateMatrix.mapPoints(imageDims);
78         imageDims[0] = Math.abs(imageDims[0]);
79         imageDims[1] = Math.abs(imageDims[1]);
80         return imageDims;
81     }
82 
getEdgesHelper(RectF edgesOut)83     private void getEdgesHelper(RectF edgesOut) {
84         final float width = getWidth();
85         final float height = getHeight();
86         final float[] imageDims = getImageDims();
87         final float imageWidth = imageDims[0];
88         final float imageHeight = imageDims[1];
89 
90         float initialCenterX = mRenderer.source.getImageWidth() / 2f;
91         float initialCenterY = mRenderer.source.getImageHeight() / 2f;
92 
93         float[] rendererCenter = mTempRendererCenter;
94         rendererCenter[0] = mCenterX - initialCenterX;
95         rendererCenter[1] = mCenterY - initialCenterY;
96         mRotateMatrix.mapPoints(rendererCenter);
97         rendererCenter[0] += imageWidth / 2;
98         rendererCenter[1] += imageHeight / 2;
99 
100         final float scale = mRenderer.scale;
101         float centerX = (width / 2f - rendererCenter[0] + (imageWidth - width) / 2f)
102                 * scale + width / 2f;
103         float centerY = (height / 2f - rendererCenter[1] + (imageHeight - height) / 2f)
104                 * scale + height / 2f;
105         float leftEdge = centerX - imageWidth / 2f * scale;
106         float rightEdge = centerX + imageWidth / 2f * scale;
107         float topEdge = centerY - imageHeight / 2f * scale;
108         float bottomEdge = centerY + imageHeight / 2f * scale;
109 
110         edgesOut.left = leftEdge;
111         edgesOut.right = rightEdge;
112         edgesOut.top = topEdge;
113         edgesOut.bottom = bottomEdge;
114     }
115 
getImageRotation()116     public int getImageRotation() {
117         return mRenderer.rotation;
118     }
119 
getCrop()120     public RectF getCrop() {
121         final RectF edges = mTempEdges;
122         getEdgesHelper(edges);
123         final float scale = mRenderer.scale;
124 
125         float cropLeft = -edges.left / scale;
126         float cropTop = -edges.top / scale;
127         float cropRight = cropLeft + getWidth() / scale;
128         float cropBottom = cropTop + getHeight() / scale;
129 
130         return new RectF(cropLeft, cropTop, cropRight, cropBottom);
131     }
132 
getSourceDimensions()133     public Point getSourceDimensions() {
134         return new Point(mRenderer.source.getImageWidth(), mRenderer.source.getImageHeight());
135     }
136 
setTileSource(TileSource source, Runnable isReadyCallback)137     public void setTileSource(TileSource source, Runnable isReadyCallback) {
138         super.setTileSource(source, isReadyCallback);
139         mCenterX = mRenderer.centerX;
140         mCenterY = mRenderer.centerY;
141         mRotateMatrix.reset();
142         mRotateMatrix.setRotate(mRenderer.rotation);
143         mInverseRotateMatrix.reset();
144         mInverseRotateMatrix.setRotate(-mRenderer.rotation);
145         updateMinScale(getWidth(), getHeight(), source, true);
146     }
147 
onSizeChanged(int w, int h, int oldw, int oldh)148     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
149         updateMinScale(w, h, mRenderer.source, false);
150     }
151 
setScale(float scale)152     public void setScale(float scale) {
153         synchronized (mLock) {
154             mRenderer.scale = scale;
155         }
156     }
157 
updateMinScale(int w, int h, TileSource source, boolean resetScale)158     private void updateMinScale(int w, int h, TileSource source, boolean resetScale) {
159         synchronized (mLock) {
160             if (resetScale) {
161                 mRenderer.scale = 1;
162             }
163             if (source != null) {
164                 final float[] imageDims = getImageDims();
165                 final float imageWidth = imageDims[0];
166                 final float imageHeight = imageDims[1];
167                 mMinScale = Math.max(w / imageWidth, h / imageHeight);
168                 mRenderer.scale =
169                         Math.max(mMinScale, resetScale ? Float.MIN_VALUE : mRenderer.scale);
170             }
171         }
172     }
173 
174     @Override
onScaleBegin(ScaleGestureDetector detector)175     public boolean onScaleBegin(ScaleGestureDetector detector) {
176         return true;
177     }
178 
179     @Override
onScale(ScaleGestureDetector detector)180     public boolean onScale(ScaleGestureDetector detector) {
181         // Don't need the lock because this will only fire inside of
182         // onTouchEvent
183         mRenderer.scale *= detector.getScaleFactor();
184         mRenderer.scale = Math.max(mMinScale, mRenderer.scale);
185         invalidate();
186         return true;
187     }
188 
189     @Override
onScaleEnd(ScaleGestureDetector detector)190     public void onScaleEnd(ScaleGestureDetector detector) {
191     }
192 
moveToLeft()193     public void moveToLeft() {
194         if (getWidth() == 0 || getHeight() == 0) {
195             final ViewTreeObserver observer = getViewTreeObserver();
196             observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
197                     public void onGlobalLayout() {
198                         moveToLeft();
199                         getViewTreeObserver().removeOnGlobalLayoutListener(this);
200                     }
201                 });
202         }
203         final RectF edges = mTempEdges;
204         getEdgesHelper(edges);
205         final float scale = mRenderer.scale;
206         mCenterX += Math.ceil(edges.left / scale);
207         updateCenter();
208     }
209 
updateCenter()210     private void updateCenter() {
211         mRenderer.centerX = Math.round(mCenterX);
212         mRenderer.centerY = Math.round(mCenterY);
213     }
214 
setTouchEnabled(boolean enabled)215     public void setTouchEnabled(boolean enabled) {
216         mTouchEnabled = enabled;
217     }
218 
setTouchCallback(TouchCallback cb)219     public void setTouchCallback(TouchCallback cb) {
220         mTouchCallback = cb;
221     }
222 
223     @Override
onTouchEvent(MotionEvent event)224     public boolean onTouchEvent(MotionEvent event) {
225         int action = event.getActionMasked();
226         final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
227         final int skipIndex = pointerUp ? event.getActionIndex() : -1;
228 
229         // Determine focal point
230         float sumX = 0, sumY = 0;
231         final int count = event.getPointerCount();
232         for (int i = 0; i < count; i++) {
233             if (skipIndex == i)
234                 continue;
235             sumX += event.getX(i);
236             sumY += event.getY(i);
237         }
238         final int div = pointerUp ? count - 1 : count;
239         float x = sumX / div;
240         float y = sumY / div;
241 
242         if (action == MotionEvent.ACTION_DOWN) {
243             mFirstX = x;
244             mFirstY = y;
245             mTouchDownTime = System.currentTimeMillis();
246             if (mTouchCallback != null) {
247                 mTouchCallback.onTouchDown();
248             }
249         } else if (action == MotionEvent.ACTION_UP) {
250             ViewConfiguration config = ViewConfiguration.get(getContext());
251 
252             float squaredDist = (mFirstX - x) * (mFirstX - x) + (mFirstY - y) * (mFirstY - y);
253             float slop = config.getScaledTouchSlop() * config.getScaledTouchSlop();
254             long now = System.currentTimeMillis();
255             if (mTouchCallback != null) {
256                 // only do this if it's a small movement
257                 if (squaredDist < slop &&
258                         now < mTouchDownTime + ViewConfiguration.getTapTimeout()) {
259                     mTouchCallback.onTap();
260                 }
261                 mTouchCallback.onTouchUp();
262             }
263         }
264 
265         if (!mTouchEnabled) {
266             return true;
267         }
268 
269         synchronized (mLock) {
270             mScaleGestureDetector.onTouchEvent(event);
271             switch (action) {
272                 case MotionEvent.ACTION_MOVE:
273                     float[] point = mTempPoint;
274                     point[0] = (mLastX - x) / mRenderer.scale;
275                     point[1] = (mLastY - y) / mRenderer.scale;
276                     mInverseRotateMatrix.mapPoints(point);
277                     mCenterX += point[0];
278                     mCenterY += point[1];
279                     updateCenter();
280                     invalidate();
281                     break;
282             }
283             if (mRenderer.source != null) {
284                 // Adjust position so that the wallpaper covers the entire area
285                 // of the screen
286                 final RectF edges = mTempEdges;
287                 getEdgesHelper(edges);
288                 final float scale = mRenderer.scale;
289 
290                 float[] coef = mTempCoef;
291                 coef[0] = 1;
292                 coef[1] = 1;
293                 mRotateMatrix.mapPoints(coef);
294                 float[] adjustment = mTempAdjustment;
295                 mTempAdjustment[0] = 0;
296                 mTempAdjustment[1] = 0;
297                 if (edges.left > 0) {
298                     adjustment[0] = edges.left / scale;
299                 } else if (edges.right < getWidth()) {
300                     adjustment[0] = (edges.right - getWidth()) / scale;
301                 }
302                 if (edges.top > 0) {
303                     adjustment[1] = FloatMath.ceil(edges.top / scale);
304                 } else if (edges.bottom < getHeight()) {
305                     adjustment[1] = (edges.bottom - getHeight()) / scale;
306                 }
307                 for (int dim = 0; dim <= 1; dim++) {
308                     if (coef[dim] > 0) adjustment[dim] = FloatMath.ceil(adjustment[dim]);
309                 }
310 
311                 mInverseRotateMatrix.mapPoints(adjustment);
312                 mCenterX += adjustment[0];
313                 mCenterY += adjustment[1];
314                 updateCenter();
315             }
316         }
317 
318         mLastX = x;
319         mLastY = y;
320         return true;
321     }
322 }
323