2013年5月9日 星期四

[iOS] print Log

Because We will need a lot of  information to debug,
so we can use NSLog to print log.

However, these log information we do not want are still being printed out after the release,
we can define like below in project-Prefix.pch:
1
2
3
4
5
6
7
8
//enable NSLogs
#define DEBUG_MODE
#ifdef DEBUG_MODE
    #define DLog(...) NSLog(__VA_ARGS__)
#else
    #define DLog(...) 
#endif
#define ALog(...) NSLog(__VA_ARGS__)
Then use DLog to replace NSLog which one you want control it will be printed or not,
use ALog to replace the one you want always be printed.

When you dont need printed DLog,
 just comment this line #define DEBUG_MODE.




---------------------- By the way below is something about NSLog's usage -----------------------

In order to track bug occurred,
We can add below at each important function:

- (IBAction)showRangeList:(id)sender {
    DLog(@"%s Enter", __func__);
}
When APP executed to this line will print:
-[MainViewController showRangeList:] Enter



And NSLog already provide format,
so we don't need code like this:

NSLog(@"%@",[NSString stringWithFormat:@"%@ %@, %@", 
       errorMsgFormat, 
       error, 
       [error userInfo]]);
just need:

NSLog(@"%@ %@, %@", 
   errorMsgFormat, 
   error, 
   [error userInfo]);
if we only code:

NSLog([NSString stringWithFormat:@"msg:%@",msg]);
Although it's no problem when execute, but there will be a "format not a string literal and no format arguments" warning.

[iOS] Log的使用

開發APP時為了debug需要大量的資訊,
可以使用NSLog打印Log。

然而這些Log資訊我們並不想在發佈後仍被印出來,
可在 project-Prefix.pch 中用以下code將NSLog作一個中介的define:
1
2
3
4
5
6
7
8
//enable NSLogs
#define DEBUG_MODE
#ifdef DEBUG_MODE
    #define DLog(...) NSLog(__VA_ARGS__)
#else
    #define DLog(...) 
#endif
#define ALog(...) NSLog(__VA_ARGS__)
之後想看情況印的NSLog用DLog取代,
總是要印出的就用ALog取代。
這樣當不想印出DLog時就把 #define DEBUG_MODE 註解掉就行了。





----------------------以下順便提一下NSLog用法-----------------------

為了方便追蹤bug發生地點,
可於每個重要function加入:

- (IBAction)showRangeList:(id)sender {
    DLog(@"%s Enter", __func__);
}
這樣執行至此行時便會打印出如下資訊
-[MainViewController showRangeList:] Enter


另外NSLog本身有format功能
因此不需要寫成

NSLog(@"%@",[NSString stringWithFormat:@"%@ %@, %@", 
       errorMsgFormat, 
       error, 
       [error userInfo]]);
只需要

NSLog(@"%@ %@, %@", 
   errorMsgFormat, 
   error, 
   [error userInfo]);
即可。
若是只寫

NSLog([NSString stringWithFormat:@"msg:%@",msg]);
則雖然編譯沒問題但會出現"format not a string literal and no format arguments"的warning。

2013年4月25日 星期四

[Android] ScalableImageView


/*
 * Stan 2013/04/25
 * ver 2.0.8
 */
package com.stan.libs.imageview.scalable;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.ImageView;

/*
 * @author: Stan
 */
public class StanScalableImageView extends ImageView {

private Matrix matrix = new Matrix();

// mode can be in one of these 3 states
private static final int NONE = 0;
private static final int DRAG = 1;
private static final int ZOOM = 2;
private int mode = NONE;

private static final int CLICK = 3;

// Remember some things
private PointF last = new PointF();
private PointF start = new PointF();
private float minScale = 1f;// default
private float maxScale = 3f;// default
private float minScaleTemp;// measure
private float maxScaleTemp;// measure
private float[] m;
private float redundantXSpace, redundantYSpace;
private float width, height;
private float nowScale = 1f;
private float origWidth, origHeight, imageWidth, imageHeight,
redundantWidth, redundantHeight;
private boolean fit = false;

private ScaleGestureDetector mScaleDetector;

public StanScalableImageView(Context context) {
super(context);
initStanScalableImageView(context);
}

public StanScalableImageView(Context context, AttributeSet attrs) {
super(context, attrs);
initStanScalableImageView(context);
}

public StanScalableImageView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
initStanScalableImageView(context);
}

private void initStanScalableImageView(Context context) {
super.setScaleType(ScaleType.CENTER);
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
matrix.setTranslate(1f, 1f);
m = new float[9];
setImageMatrix(matrix);
setScaleType(ScaleType.MATRIX);
setOnTouchListener(new DragListener());
}

@Override
public void setImageBitmap(Bitmap bitmap) {
super.setImageBitmap(bitmap);
imageWidth = bitmap.getWidth();
imageHeight = bitmap.getHeight();
}

@Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
imageWidth = drawable.getIntrinsicWidth();
imageHeight = drawable.getIntrinsicHeight();
}

@Override
public void setImageResource(int resourceID) {
super.setImageResource(resourceID);
imageWidth = getResources().getDrawable(resourceID).getIntrinsicWidth();
imageHeight = getResources().getDrawable(resourceID)
.getIntrinsicHeight();
}

public void setMaxZoom(float x) {
this.maxScale = x;
}

public void setMinZoom(float x) {
this.minScale = x;
}

public void setFit(boolean fit) {
this.fit = fit;
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = MeasureSpec.getSize(widthMeasureSpec);
height = MeasureSpec.getSize(heightMeasureSpec);
float scale;
if (fit) {
float scaleX = (float) width / (float) imageWidth;
float scaleY = (float) height / (float) imageHeight;
scale = Math.min(scaleX, scaleY);
matrix.setScale(scale, scale);
} else {
scale = 1;
}
minScaleTemp = minScale * scale;
maxScaleTemp = maxScale * scale;

setImageMatrix(matrix);
nowScale = scale;

// Center the image
redundantHeight = (scale * (float) imageHeight);
redundantWidth = (scale * (float) imageWidth);
redundantYSpace = (float) height - (scale * (float) imageHeight);
redundantXSpace = (float) width - redundantWidth;
redundantYSpace /= (float) 2;
redundantXSpace /= (float) 2;

matrix.getValues(m);
float x = m[Matrix.MTRANS_X];
float y = m[Matrix.MTRANS_Y];
matrix.postTranslate(redundantXSpace - x, redundantYSpace - y);

origWidth = width - 2 * redundantXSpace;
origHeight = height - 2 * redundantYSpace;
setImageMatrix(matrix);
}

private class DragListener implements OnTouchListener {

@Override
public boolean onTouch(View v, MotionEvent event) {
mScaleDetector.onTouchEvent(event);
matrix.getValues(m);
float x = m[Matrix.MTRANS_X];
float y = m[Matrix.MTRANS_Y];
PointF curr = new PointF(event.getX(), event.getY());

switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
last.set(event.getX(), event.getY());
start.set(last);
if (nowScale * imageWidth > width * 1.05
|| nowScale * imageHeight > height * 1.05) {
mode = DRAG;
}
break;
case MotionEvent.ACTION_MOVE:
float deltaX = curr.x - last.x;
float deltaY = curr.y - last.y;
if (mode == DRAG) {
float scaleWidth = Math.round(imageWidth * nowScale);
float scaleHeight = Math.round(imageHeight * nowScale);
// x
if (scaleWidth < width) {
deltaX = redundantXSpace - x
- (imageWidth * nowScale - redundantWidth) / 2;
} else {
if (deltaX > 0 && x + deltaX > 0) {
deltaX = -x;
} else if (deltaX < 0
&& x + deltaX + scaleWidth < width) {
deltaX = width - x - scaleWidth;
}
}
// y
if (scaleHeight < height) {
deltaY = redundantYSpace - y
- (imageHeight * nowScale - redundantHeight)
/ 2;
} else {
if (deltaY > 0 && y + deltaY > 0) {
deltaY = -y;
} else if (deltaY < 0
&& y + deltaY + scaleHeight < height) {
deltaY = height - y - scaleHeight;
}
}
matrix.postTranslate(deltaX, deltaY);
last.set(curr.x, curr.y);
}
break;

case MotionEvent.ACTION_UP:
mode = NONE;
int xDiff = (int) Math.abs(curr.x - start.x);
int yDiff = (int) Math.abs(curr.y - start.y);
if (xDiff < CLICK && yDiff < CLICK)
performClick();
break;

case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
}
setImageMatrix(matrix);
invalidate();
return true;
}
}

private class ScaleListener extends
ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
mode = ZOOM;
last.set(detector.getFocusX(), detector.getFocusY());
start.set(last);
return true;
}

@Override
public boolean onScale(ScaleGestureDetector detector) {
float mScaleFactor = detector.getScaleFactor();
float origScale = nowScale;
nowScale *= mScaleFactor;
if (nowScale > maxScaleTemp) {
nowScale = maxScaleTemp;
mScaleFactor = maxScaleTemp / origScale;
} else if (nowScale < minScaleTemp) {
nowScale = minScaleTemp;
mScaleFactor = minScaleTemp / origScale;
}
if (origWidth * nowScale <= width
|| origHeight * nowScale <= height) {
matrix.postScale(mScaleFactor, mScaleFactor, width / 2,
height / 2);
} else {
matrix.postScale(mScaleFactor, mScaleFactor,
detector.getFocusX(), detector.getFocusY());
}
matrix.getValues(m);
float x = m[Matrix.MTRANS_X];
float y = m[Matrix.MTRANS_Y];
float scaleWidth = Math.round(imageWidth * nowScale);
float scaleHeight = Math.round(imageHeight * nowScale);
if (nowScale < origScale) {
if (x > 0 && x + scaleWidth > width) {
matrix.postTranslate(-x, 0);
} else if (x < 0 && x + scaleWidth < width) {
matrix.postTranslate(width - x - scaleWidth, 0);
} else if (x > 0 && x + scaleWidth < width) {
matrix.postTranslate((width - scaleWidth) / 2 - x, 0);
}
if (y > 0 && y + scaleHeight > height) {
matrix.postTranslate(0, -y);
} else if (y < 0 && y + scaleHeight < height) {
matrix.postTranslate(0, height - y - scaleHeight);
} else if (y > 0 && y + scaleHeight < height) {
matrix.postTranslate(0, (height - scaleHeight) / 2 - y);
}
}

PointF curr = new PointF(detector.getFocusX(), detector.getFocusY());
float deltaX = curr.x - last.x;
float deltaY = curr.y - last.y;
last.set(curr.x, curr.y);
// x
if (scaleWidth < width) {
deltaX = redundantXSpace - x
- (imageWidth * nowScale - redundantWidth) / 2;
return true;
} else {
if (deltaX > 0 && x + deltaX > 0) {
deltaX = -x;
} else if (deltaX < 0 && x + deltaX + scaleWidth < width) {
deltaX = width - x - scaleWidth;
}
}
// y
if (scaleHeight < height) {
deltaY = redundantYSpace - y
- (imageHeight * nowScale - redundantHeight) / 2;
return true;
} else {
if (deltaY > 0 && y + deltaY > 0) {
deltaY = -y;
} else if (deltaY < 0 && y + deltaY + scaleHeight < height) {
deltaY = height - y - scaleHeight;
}
}
matrix.postTranslate(deltaX, deltaY);
return true;
}
}
}

2013年4月24日 星期三

[Android]支援雙指手勢放大縮小移動的ImageView


/*
 * Stan 2013/04/25
 * ver 2.0.8
 */
package com.stan.libs.imageview.scalable;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.ImageView;

/*
 * @author: Stan
 */
public class StanScalableImageView extends ImageView {

 private Matrix matrix = new Matrix();

 // mode can be in one of these 3 states
 private static final int NONE = 0;
 private static final int DRAG = 1;
 private static final int ZOOM = 2;
 private int mode = NONE;

 private static final int CLICK = 3;

 // Remember some things
 private PointF last = new PointF();
 private PointF start = new PointF();
 private float minScale = 1f;// default
 private float maxScale = 3f;// default
 private float minScaleTemp;// measure
 private float maxScaleTemp;// measure
 private float[] m;
 private float redundantXSpace, redundantYSpace;
 private float width, height;
 private float nowScale = 1f;
 private float origWidth, origHeight, imageWidth, imageHeight,
   redundantWidth, redundantHeight;
 private boolean fit = false;

 private ScaleGestureDetector mScaleDetector;

 public StanScalableImageView(Context context) {
  super(context);
  initStanScalableImageView(context);
 }

 public StanScalableImageView(Context context, AttributeSet attrs) {
  super(context, attrs);
  initStanScalableImageView(context);
 }

 public StanScalableImageView(Context context, AttributeSet attrs,
   int defStyle) {
  super(context, attrs, defStyle);
  initStanScalableImageView(context);
 }

 private void initStanScalableImageView(Context context) {
  super.setScaleType(ScaleType.CENTER);
  mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
  matrix.setTranslate(1f, 1f);
  m = new float[9];
  setImageMatrix(matrix);
  setScaleType(ScaleType.MATRIX);
  setOnTouchListener(new DragListener());
 }

 @Override
 public void setImageBitmap(Bitmap bitmap) {
  super.setImageBitmap(bitmap);
  imageWidth = bitmap.getWidth();
  imageHeight = bitmap.getHeight();
 }

 @Override
 public void setImageDrawable(Drawable drawable) {
  super.setImageDrawable(drawable);
  imageWidth = drawable.getIntrinsicWidth();
  imageHeight = drawable.getIntrinsicHeight();
 }

 @Override
 public void setImageResource(int resourceID) {
  super.setImageResource(resourceID);
  imageWidth = getResources().getDrawable(resourceID).getIntrinsicWidth();
  imageHeight = getResources().getDrawable(resourceID)
    .getIntrinsicHeight();
 }

 public void setMaxZoom(float x) {
  this.maxScale = x;
 }

 public void setMinZoom(float x) {
  this.minScale = x;
 }

 public void setFit(boolean fit) {
  this.fit = fit;
 }
 
 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  width = MeasureSpec.getSize(widthMeasureSpec);
  height = MeasureSpec.getSize(heightMeasureSpec);
  float scale;
  if (fit) {
   float scaleX = (float) width / (float) imageWidth;
   float scaleY = (float) height / (float) imageHeight;
   scale = Math.min(scaleX, scaleY);
   matrix.setScale(scale, scale);
  } else {
   scale = 1;
  }
  minScaleTemp = minScale * scale;
  maxScaleTemp = maxScale * scale;

  setImageMatrix(matrix);
  nowScale = scale;

  // Center the image
  redundantHeight = (scale * (float) imageHeight);
  redundantWidth = (scale * (float) imageWidth);
  redundantYSpace = (float) height - (scale * (float) imageHeight);
  redundantXSpace = (float) width - redundantWidth;
  redundantYSpace /= (float) 2;
  redundantXSpace /= (float) 2;

  matrix.getValues(m);
  float x = m[Matrix.MTRANS_X];
  float y = m[Matrix.MTRANS_Y];
  matrix.postTranslate(redundantXSpace - x, redundantYSpace - y);

  origWidth = width - 2 * redundantXSpace;
  origHeight = height - 2 * redundantYSpace;
  setImageMatrix(matrix);
 }

 private class DragListener implements OnTouchListener {

  @Override
  public boolean onTouch(View v, MotionEvent event) {
   mScaleDetector.onTouchEvent(event);
   matrix.getValues(m);
   float x = m[Matrix.MTRANS_X];
   float y = m[Matrix.MTRANS_Y];
   PointF curr = new PointF(event.getX(), event.getY());

   switch (event.getAction()) {
   case MotionEvent.ACTION_DOWN:
    last.set(event.getX(), event.getY());
    start.set(last);
    if (nowScale * imageWidth > width * 1.05
      || nowScale * imageHeight > height * 1.05) {
     mode = DRAG;
    }
    break;
   case MotionEvent.ACTION_MOVE:
    float deltaX = curr.x - last.x;
    float deltaY = curr.y - last.y;
    if (mode == DRAG) {
     float scaleWidth = Math.round(imageWidth * nowScale);
     float scaleHeight = Math.round(imageHeight * nowScale);
     // x
     if (scaleWidth < width) {// 不超過邊框時置中
      deltaX = redundantXSpace - x
        - (imageWidth * nowScale - redundantWidth) / 2;
     } else {
      if (deltaX > 0 && x + deltaX > 0) {// 往右拉&&左邊拉過頭
       deltaX = -x;
      } else if (deltaX < 0
        && x + deltaX + scaleWidth < width) {// 往左拉&&右邊拉過頭
       deltaX = width - x - scaleWidth;
      }
     }
     // y
     if (scaleHeight < height) {// 不超過邊框時置中
      deltaY = redundantYSpace - y
        - (imageHeight * nowScale - redundantHeight)
        / 2;
     } else {
      if (deltaY > 0 && y + deltaY > 0) {// 往下拉&&上邊拉過頭
       deltaY = -y;
      } else if (deltaY < 0
        && y + deltaY + scaleHeight < height) {// 往上拉&&下邊拉過頭
       deltaY = height - y - scaleHeight;
      }
     }
     matrix.postTranslate(deltaX, deltaY);
     last.set(curr.x, curr.y);
    }
    break;

   case MotionEvent.ACTION_UP:
    mode = NONE;
    int xDiff = (int) Math.abs(curr.x - start.x);
    int yDiff = (int) Math.abs(curr.y - start.y);
    if (xDiff < CLICK && yDiff < CLICK)
     performClick();
    break;

   case MotionEvent.ACTION_POINTER_UP:
    mode = NONE;
    break;
   }
   setImageMatrix(matrix);
   invalidate();
   return true;
  }
 }

 private class ScaleListener extends
   ScaleGestureDetector.SimpleOnScaleGestureListener {
  @Override
  public boolean onScaleBegin(ScaleGestureDetector detector) {
   mode = ZOOM;
   last.set(detector.getFocusX(), detector.getFocusY());
   start.set(last);
   return true;
  }

  @Override
  public boolean onScale(ScaleGestureDetector detector) {
   float mScaleFactor = detector.getScaleFactor();
   float origScale = nowScale;
   nowScale *= mScaleFactor;
   if (nowScale > maxScaleTemp) {
    nowScale = maxScaleTemp;
    mScaleFactor = maxScaleTemp / origScale;
   } else if (nowScale < minScaleTemp) {
    nowScale = minScaleTemp;
    mScaleFactor = minScaleTemp / origScale;
   }
   if (origWidth * nowScale <= width
     || origHeight * nowScale <= height) {
    matrix.postScale(mScaleFactor, mScaleFactor, width / 2,
      height / 2);
   } else {
    matrix.postScale(mScaleFactor, mScaleFactor,
      detector.getFocusX(), detector.getFocusY());
   }
   matrix.getValues(m);
   float x = m[Matrix.MTRANS_X];
   float y = m[Matrix.MTRANS_Y];
   float scaleWidth = Math.round(imageWidth * nowScale);
   float scaleHeight = Math.round(imageHeight * nowScale);
   if (nowScale < origScale) {// 縮小時置中
    if (x > 0 && x + scaleWidth > width) {// 只有左邊有空隙
     matrix.postTranslate(-x, 0);
    } else if (x < 0 && x + scaleWidth < width) {// 只有右邊有空隙
     matrix.postTranslate(width - x - scaleWidth, 0);
    } else if (x > 0 && x + scaleWidth < width) {// 兩邊都有空隙
     matrix.postTranslate((width - scaleWidth) / 2 - x, 0);
    }
    if (y > 0 && y + scaleHeight > height) {// 只有上邊有空隙
     matrix.postTranslate(0, -y);
    } else if (y < 0 && y + scaleHeight < height) {// 只有下邊有空隙
     matrix.postTranslate(0, height - y - scaleHeight);
    } else if (y > 0 && y + scaleHeight < height) {// 兩邊都有空隙
     matrix.postTranslate(0, (height - scaleHeight) / 2 - y);
    }
   }

   PointF curr = new PointF(detector.getFocusX(), detector.getFocusY());
   float deltaX = curr.x - last.x;
   float deltaY = curr.y - last.y;
   last.set(curr.x, curr.y);
   // x
   if (scaleWidth < width) {// 不超過邊框時置中
    deltaX = redundantXSpace - x
      - (imageWidth * nowScale - redundantWidth) / 2;
    return true;// 縮放這邊已經置中過了,跳調
   } else {
    if (deltaX > 0 && x + deltaX > 0) {// 往右拉&&左邊拉過頭
     deltaX = -x;
    } else if (deltaX < 0 && x + deltaX + scaleWidth < width) {// 往左拉&&右邊拉過頭
     deltaX = width - x - scaleWidth;
    }
   }
   // y
   if (scaleHeight < height) {// 不超過邊框時置中
    deltaY = redundantYSpace - y
      - (imageHeight * nowScale - redundantHeight) / 2;
    return true;// 縮放這邊已經置中過了,跳調
   } else {
    if (deltaY > 0 && y + deltaY > 0) {// 往下拉&&上邊拉過頭
     deltaY = -y;
    } else if (deltaY < 0 && y + deltaY + scaleHeight < height) {// 往上拉&&下邊拉過頭
     deltaY = height - y - scaleHeight;
    }
   }
   matrix.postTranslate(deltaX, deltaY);
   return true;
  }
 }
}

2013年4月23日 星期二

[Android] Gallery Scroll One Image At A Time


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class ScrollOneImageAtATimeGallery extends Gallery {

 public ScrollOneImageAtATimeGallery(Context context) {
  super(context);
 }

 public ScrollOneImageAtATimeGallery(Context context, AttributeSet attrs) {
  super(context, attrs);
 }

 public ScrollOneImageAtATimeGallery(Context context, AttributeSet attrs,
   int defStyle) {
  super(context, attrs, defStyle);
 }

 private boolean isScrollingLeft(MotionEvent e1, MotionEvent e2) {
  return e2.getX() > e1.getX();
 }

 @Override
 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
   float velocityY) {
  int kEvent;
  if (isScrollingLeft(e1, e2)) {
   // Check if scrolling left
   kEvent = KeyEvent.KEYCODE_DPAD_LEFT;
  } else {
   // Otherwise scrolling right
   kEvent = KeyEvent.KEYCODE_DPAD_RIGHT;
  }
  onKeyDown(kEvent, null);
  return true;
 }
}