App
CurvesView.java
Go to the documentation of this file.
1 package mhr.appandroid.views;
2 
3 import java.util.ArrayList;
4 
5 import mhr.appandroid.R;
6 import android.content.Context;
7 import android.graphics.Bitmap;
8 import android.graphics.BitmapFactory;
9 import android.graphics.Canvas;
10 import android.graphics.Paint;
11 import android.graphics.Paint.Style;
12 import android.graphics.Path;
13 import android.graphics.Rect;
14 import android.graphics.RectF;
15 import android.graphics.drawable.ShapeDrawable;
16 import android.graphics.drawable.shapes.PathShape;
17 import android.util.AttributeSet;
18 import android.view.GestureDetector;
19 import android.view.MotionEvent;
20 import android.view.View;
21 import android.view.ViewParent;
22 
23 public class CurvesView extends View {
24 //===== INTERFACES, CLASSES, ENUMS ==========================================================================================================//
25 //----- NON-PUBLIC --------------------------------------------------------------------------------------------------------------------------//
26  protected class GestureListener extends GestureDetector.SimpleOnGestureListener {
27  @Override
28  public boolean onDown(MotionEvent e) {
29 
30  int X = (int) e.getX();
31  int Y = (int) e.getY();
32 
33  for (int i = 0; i < x.length; i++) {
34  cRect.offsetTo(xToGlob(x[i]) - cXCenter, yToGlob(y[i]) - cYCenter);
35  if (cRect.contains(X, Y)) {
36  selIndex = i;
37  invalidate();
38  if (listener != null) {
40  }
41  return true;
42  }
43  }
44 
45  int pos = addPoint(xToNorm(X), yToNorm(Y));
46  if (pos >= 0) {
47  selIndex = pos;
48  prepareCurve();
49  invalidate();
50  if (listener != null) {
53  }
54  return true;
55  }
56  return true;
57  }
58 
59  @Override
60  public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
61  if (selIndex < 0) {
62  return true;
63  } else {
64  float tmpx = x[selIndex] - (distanceX / xLength);
65  float tmpy = y[selIndex] - (distanceY / yLength);
66  if (selIndex == 0) {
67  x[selIndex] = 0;
68  y[selIndex] = (tmpy < 0) ? 0 : (tmpy > 1) ? 1 : tmpy;
69  } else if (selIndex == x.length - 1) {
70  x[selIndex] = 1;
71  y[selIndex] = (tmpy < 0) ? 0 : (tmpy > 1) ? 1 : tmpy;
72  } else {
73  tmpx = (tmpx < 0) ? 0 : (tmpx > 1) ? 1 : tmpx;
74  x[selIndex] = (tmpx - x[selIndex - 1]) < minDelta ? x[selIndex - 1] + minDelta : (x[selIndex + 1] - tmpx) < minDelta ? x[selIndex + 1] - minDelta : tmpx;
75  y[selIndex] = (tmpy < 0) ? 0 : (tmpy > 1) ? 1 : tmpy;
76  }
77  prepareCurve();
78  invalidate();
79  if (listener != null) {
81  }
82  }
83 
84  return true;
85  }
86 
87  @Override
88  public boolean onDoubleTapEvent (MotionEvent e) {
89  int X = (int) e.getX();
90  int Y = (int) e.getY();
91 
92  for (int i = 0; i < x.length; i++) {
93  cRect.offsetTo(xToGlob(x[i]) - cXCenter, yToGlob(y[i]) - cYCenter);
94  if (cRect.contains(X, Y)) {
95  selIndex = i;
96  if (removeSelPoint()) {
97  prepareCurve();
98  invalidate();
99  if (listener != null) {
102  }
103  }
104  return true;
105  }
106  }
107  return true;
108  }
109  }
110 
111  protected boolean removeSelPoint() {
112  if (selIndex < 1 || selIndex > x.length - 2) {
113  selIndex = -1;
114  return false;
115  }
116  int ptsCount = x.length;
117  float[] tx = new float[ptsCount - 1];
118  float[] ty = new float[ptsCount - 1];
119  int i = 0;
120  while (i < selIndex) {
121  tx[i] = x[i];
122  ty[i] = y[i];
123  i++;
124  }
125  i++;
126  while (i < x.length) {
127  tx[i - 1] = x[i];
128  ty[i - 1] = y[i];
129  i++;
130  }
131  x = tx;
132  y = ty;
133  y2 = new float[ptsCount - 1];
134  xc1 = new float[ptsCount - 2];
135  xc2 = new float[ptsCount - 2];
136  yc1 = new float[ptsCount - 2];
137  yc2 = new float[ptsCount - 2];
138  selIndex = -1;
139  return true;
140  }
141 
142 
143  protected int addPoint(float xx, float yy) {
144  int pos = -1;
145  if (xx < 0 || xx > 1 || yy < 0 || yy > 1) {
146  return pos;
147  }
148 
149  for (int i = 1; i < x.length; i++) { // Nula nema cenu, mensi by znamenalo BUG
150  if (x[i] > xx) {
151  pos = i;
152  break;
153  }
154  }
155 
156  if (pos < 0) {
157  return pos;
158  }
159 
160  xx = (xx - x[pos - 1]) < minDelta ? x[pos - 1] + minDelta : (x[pos] - xx) < minDelta ? x[pos] - minDelta : xx; // Korekce pokud nahodou koliduje
161  if (((x[pos] - xx) >= minDelta) && ((xx - x[pos - 1]) >= minDelta)) { // Na novy bod je misto..
162  int ptsCount = x.length;
163  float[] tx = new float[ptsCount + 1];
164  float[] ty = new float[ptsCount + 1];
165  int i = 0;
166  while (i < pos) {
167  tx[i] = x[i];
168  ty[i] = y[i];
169  i++;
170  }
171  tx[i] = xx;
172  ty[i] = yy;
173  while (i < x.length) {
174  tx[i+1] = x[i];
175  ty[i+1] = y[i];
176  i++;
177  }
178  x = tx;
179  y = ty;
180  y2 = new float[ptsCount + 1];
181  xc1 = new float[ptsCount];
182  xc2 = new float[ptsCount];
183  yc1 = new float[ptsCount];
184  yc2 = new float[ptsCount];
185  }
186  return pos;
187  }
188 
189 
190 //----- PUBLIC ------------------------------------------------------------------------------------------------------------------------------//
191  public interface CurvesViewChangeListener {
192  public void onCurvesChange(CurvesView v);
193  public void onCurvesChangeStart(CurvesView v);
194  public void onCurvesChangeStop(CurvesView v);
195  }
196 
197 
198 //===== FIELDS ==============================================================================================================================//
199 //----- NON-PUBLIC --------------------------------------------------------------------------------------------------------------------------//
200  protected GestureDetector detector;
201 
202  protected Bitmap cPressed;
203  protected Bitmap cNormal;
204 
205  protected Rect boundingRect;
206  protected Rect curvesRect;
207  protected Rect cRect;
208 
209  protected int xLength;
210  protected int yLength;
211 
212  protected float[] x;
213  protected float[] xc1;
214  protected float[] xc2;
215 
216  protected float[] y;
217  protected float[] y2;
218  protected float[] yc1;
219  protected float[] yc2;
220 
221  protected int cWidth;
222  protected int cHeight;
223  protected int cXCenter;
224  protected int cYCenter;
225 
226  protected int xOffset;
227  protected int yOffset;
228 
229  protected int selIndex = -1;
230 
231  protected Paint frame;
232  protected RectF marginRect;
233  protected Paint marginRectPaint;
234 
236 
237  protected ShapeDrawable curve;
238 
239  protected float minDelta = 0.001f; // ptz pokud bude bliz, numericka nestabilita atd...
240 //----- PUBLIC ------------------------------------------------------------------------------------------------------------------------------//
241 
242 //===== CONSTRUCTORS, DESTRUCTORS, RELATED METHODS ==========================================================================================//
243 //----- NON-PUBLIC --------------------------------------------------------------------------------------------------------------------------//
244  protected void init() {
245  setLayerType(View.LAYER_TYPE_SOFTWARE, null);
246  cPressed = BitmapFactory.decodeResource(getResources(), R.drawable.scrubber_control_pressed_bw);
247  cNormal = BitmapFactory.decodeResource(getResources(), R.drawable.scrubber_control_normal_bw);
248  detector = new GestureDetector(getContext(), new GestureListener());
249 
250  int ptsCount = 2;
251 
252  x = new float[] {0.0f, 1.0f};
253  y = new float[] {1.0f, 0.0f};
254  y2 = new float[ptsCount];
255 
256 
257  xc1 = new float[ptsCount - 1];
258  xc2 = new float[ptsCount - 1];
259  yc1 = new float[ptsCount - 1];
260  yc2 = new float[ptsCount - 1];
261 
262 
263  cWidth = cNormal.getWidth();
264  cHeight = cNormal.getHeight();
265 
266  cXCenter = cWidth / 2;
267  cYCenter = cHeight / 2;
268 
269  xOffset = cXCenter;
270  yOffset = cYCenter;
271 
272  cRect = new Rect(0, 0, cWidth, cHeight);
273 
274  frame = new Paint();
275  frame.setColor(0xFFFFFFFF);
276  frame.setStyle(Style.STROKE);
277  frame.setStrokeWidth(2);
278  frame.setAntiAlias(true);
279 
280  marginRectPaint = new Paint();
281  marginRectPaint.setColor(0xFF000000);
282  marginRectPaint.setStyle(Style.STROKE);
283 
284  prepareCurve();
285  }
286 //----- PUBLIC ------------------------------------------------------------------------------------------------------------------------------//
287  public CurvesView(Context context) {
288  super(context);
289  init();
290  }
291 
292  public CurvesView(Context context, AttributeSet attrs) {
293  super(context, attrs);
294  init();
295  }
296 
297  public CurvesView(Context context, AttributeSet attrs, int defStyle) {
298  super(context, attrs, defStyle);
299  init();
300  }
301 
302 //===== METHODS =============================================================================================================================//
303 //----- NON-PUBLIC --------------------------------------------------------------------------------------------------------------------------//
304  protected int xToGlob(float xVal) {
305  return (int) (xVal * xLength + xOffset);
306  }
307 
308  protected int yToGlob(float yVal) {
309  return (int) (yVal * yLength + yOffset);
310  }
311 
312  protected float xToNorm(float xVal) {
313  return (xVal - xOffset) / xLength;
314  }
315 
316  protected float yToNorm(float yVal) {
317  return (yVal - yOffset) / yLength;
318  }
319 
320  protected void prepareCurve() {
321  cpInit();
322 
323  Path p = new Path();
324  for (int i = 0; i < x.length - 1; i++) {
325  p.moveTo(x[i], y[i]);
326  p.cubicTo(xc1[i], yc1[i], xc2[i], yc2[i], x[i+1], y[i+1]);
327  }
328  curve = new ShapeDrawable(new PathShape(p, 1, 1));
329  Paint paint = curve.getPaint();
330  paint.setStyle(Style.STROKE);
331  paint.setColor(0xFFFFFFFF);
332  paint.setAntiAlias(true);
333 
334  }
335 
336  // Vypocita hodnoty druhych derivaci v interpolovanych bodech tak, aby odpovidaly interpolaci kubickym splajnem, shodne s C++ knihovnou
337  protected void y2ValsInit() {
338  int i, k;
339  double p, qn, sig, un;
340  double[] u = new double[x.length];
341  int valsCount = x.length;
342  int lastVal = valsCount - 1;
343  y2[0] = 0.0f;
344  u[0] = 0.0;
345  for (i = 1; i < lastVal; i++) {
346  sig = (x[i] - x[i - 1]) / (x[i + 1] - x[i - 1]);
347  p = sig * y2[i - 1] + 2.0;
348  y2[i] =(float)((sig - 1.0) / p);
349  u[i] = (y[i + 1] - y[i]) / (x[i + 1] - x[i])
350  - (y[i] - y[i - 1]) / (x[i] - x[i - 1]);
351  u[i] = (6.0 * u[i] / (x[i + 1] - x[i - 1]) - sig * u[i - 1]) / p;
352  }
353  qn = un = 0.0;
354  y2[lastVal] =(float) ( (un - qn * u[valsCount - 2]) / (qn * y2[valsCount - 2] + 1.0));
355  for (k = valsCount - 2; k >= 0; k--) {
356  y2[k] = (float) (y2[k] * y2[k + 1] + u[k]);
357  }
358  }
359 
365  protected void cpInit() {
366 
367  y2ValsInit();
368 
369  int n = x.length - 1;
370 
371  double d0;
372  double d1;
373  double delta;
374 
375  for (int i = 0; i < n; i++) {
376 
377  d0 = (y[i+1] - y[i]) / (x[i+1] - x[i]) - 2.0/6.0 * (x[i+1] - x[i]) * y2[i] - 1.0/6.0 * (x[i+1] - x[i]) * y2[i+1];
378  d1 = (y[i+1] - y[i]) / (x[i+1] - x[i]) + 1.0/6.0 * (x[i+1] - x[i]) * y2[i] + 2.0/6.0 * (x[i+1] - x[i]) * y2[i+1];
379 
380  delta = x[i + 1] - x[i];
381 
382  yc1[i] = (float) (y[i] + d0 * (delta) / 3.0);
383  yc2[i] = (float) (y[i+1] - d1 * (delta) / 3.0);
384 
385  xc1[i] = (float) (x[i] + delta / 3.0);
386  xc2[i] = (float) (x[i+1] - delta / 3.0);
387 
388  }
389  }
390 //----- PUBLIC ------------------------------------------------------------------------------------------------------------------------------//
392  listener = l;
393  }
394 
395  public float[][] getPoints() {
396  float[][] retVal = new float[2][x.length];
397 
398  for (int i = 0; i < x.length; i++) {
399  retVal[0][i] = x[i];
400  retVal[1][i] = 1.0f - y[i];
401  }
402 
403  return retVal;
404  }
405 
406  public void setPoints(float[][] pts) {
407  int ptsCount = pts[0].length;
408  x = new float[ptsCount];
409  y = new float[ptsCount];
410  for (int i = 0; i < ptsCount; i++) {
411  x[i] = pts[0][i];
412  y[i] = 1 - pts[1][i];
413  }
414 
415  y2 = new float[ptsCount];
416 
417  xc1 = new float[ptsCount - 1];
418  xc2 = new float[ptsCount - 1];
419  yc1 = new float[ptsCount - 1];
420  yc2 = new float[ptsCount - 1];
421 
422  prepareCurve();
423  invalidate();
424  }
425 
426 //===== CALLBACKS ===========================================================================================================================//
427 
428  @Override
429  public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
430  int width = MeasureSpec.getSize(widthMeasureSpec);
431  int height = MeasureSpec.getSize(heightMeasureSpec);
432  // Abychom získaly největší možný čtverec, je nutné nastavit řídící rozměr a ten co se má přizpůsobit na 0. Obrácený postup s hledáním minima nefunguje...
433  if (width < height) {
434  width = height;
435  } else {
436  height = width;
437  }
438  setMeasuredDimension(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
439  }
440 
442  @Override
443  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
444  super.onSizeChanged(w, h, oldw, oldh);
445 
446  xLength = w - 2 * xOffset;
447  yLength = h - 2 * yOffset;
448 
449  boundingRect = new Rect(0, 0, w, h);
450  curvesRect = new Rect(xOffset, yOffset, w - xOffset, h - yOffset);
451 
452  marginRect = new RectF(
453  xOffset / 2.0f,
454  yOffset / 2.0f,
455  w - xOffset / 2.0f,
456  h - yOffset / 2.0f);
457  marginRectPaint.setStrokeWidth(xOffset);
458  }
459 
460  @Override
461  protected void onDraw(Canvas canvas) {
462  canvas.drawRect(curvesRect, frame);
463  Paint paint = curve.getPaint();
464  paint.setStrokeWidth(2.0f / xLength);
465  curve.setBounds(curvesRect);
466  curve.draw(canvas);
467  canvas.drawRect(marginRect, marginRectPaint);
468  for (int i = 0; i < x.length; i++) {
469  if (i == selIndex) {
470  canvas.drawBitmap(cPressed, x[i] * xLength + xOffset - cXCenter, y[i] * yLength + yOffset - cYCenter, null);
471  } else {
472  canvas.drawBitmap(cNormal, x[i] * xLength + xOffset - cXCenter, y[i] * yLength + yOffset - cYCenter, null);
473  }
474  }
475  }
476 
477  @Override
478  public boolean onTouchEvent(MotionEvent ev) {
479  detector.onTouchEvent(ev);
480  ViewParent parent = getParent();
481  if (parent != null) {
482  parent.requestDisallowInterceptTouchEvent(true);
483  }
484  if (ev.getAction() == MotionEvent.ACTION_UP) {
485  selIndex = -1;
486  invalidate();
487  if (listener != null) {
489  }
490  }
491  return true;
492  }
493 }