Related
Rajawali Version or Branch
rajawali:1.0.186
Device and Android Version
ADV Nexus 5X API 19 Android 4.4
Summary
I'm trying to make the camera moves when a MotionEvent (touch) occurs. I tried the code from https://github.com/Rajawali/Rajawali/issues/428 but it did not work for me.
My code
public class Renderer extends RajawaliRenderer {
public Context context;
private DirectionalLight directionalLight;
public Renderer(Context context) {
super(context);
this.context = context;
setFrameRate(60);
}
private Object3D object, star;
public void initScene(){
getCurrentScene().setBackgroundColor(Color.rgb(5,104,255));
directionalLight = new DirectionalLight(1f, .2f, -1.0f);
directionalLight.setColor(1.0f, 1.0f, 1.0f);
directionalLight.setPower(2);
getCurrentScene().addLight(directionalLight);
//LoaderOBJ objParser = new LoaderOBJ(this,"Load/1c_obj");
LoaderOBJ objParser = new LoaderOBJ(mContext.getResources(),mTextureManager, R.raw.primo_obj);
LoaderOBJ starParser = new LoaderOBJ(mContext.getResources(),mTextureManager,R.raw.star_obj);
try {
objParser.parse();
starParser.parse();
object = objParser.getParsedObject();
star = starParser.getParsedObject();
getCurrentScene().addChild(star);
getCurrentScene().addChild(object);
} catch (ParsingException e) {
e.printStackTrace();
}
getCurrentCamera().setLookAt(object.getWorldPosition());
Log.d("->",getCurrentCamera().getX()+","+getCurrentCamera().getY()+","+getCurrentCamera().getZ());
getCurrentCamera().setZ(40);
}
#Override
public void onRender(final long elapsedTime, final double deltaTime) {
super.onRender(elapsedTime, deltaTime);
Camera cam = getCurrentCamera();
Vector3 s = cam.getPosition();
if (touchTurn != 0) {
if (flagMulti) {
Double r = Math.sqrt(s.x * s.x + s.z * s.z);
angle += touchTurn;
angle %= 360;
cam.setPosition((float) (r * Math.cos(angle)), s.y, (float) (r * Math.sin(angle)));
cam.setLookAt(object.getLookAt());
}
else{
s.x+=touchTurn*5;
cam.setPosition(s);
}
touchTurn = 0;
}
if (touchTurnUp != 0) {
if (!flagMulti) {
s.z += touchTurnUp * 5;
cam.setPosition(s);
}
touchTurnUp = 0;
}
}
#Override
public void onTouchEvent(MotionEvent me){
Log.d("touch ","Log touch");
int pointerCount = me.getPointerCount();
if(pointerCount == 2)
flagMulti = true;
if (me.getAction() == MotionEvent.ACTION_DOWN) {
xpos = me.getX();
ypos = me.getY();
}
if (me.getAction() == MotionEvent.ACTION_UP) {
xpos = -1;
ypos = -1;
touchTurn = 0;
touchTurnUp = 0;
}
if (me.getAction() == MotionEvent.ACTION_MOVE) {
float xd = me.getX() - xpos;
float yd = me.getY() - ypos;
xpos = me.getX();
ypos = me.getY();
touchTurn = xd / -100f;
touchTurnUp = yd / -100f;
Log.d("touchTurn ", touchTurn+" - "+touchTurnUp);
}
try {
Thread.sleep(15);
} catch (Exception e) {
}
}
public void onOffsetsChanged(float x, float y, float z, float w, int i, int j){
}}
Would you consider arcballCamera and target to your 3D object. You can then rotate the 3D object and zoom in/out with arcballCamera.
Can any body have solution regarding how to draw on canvas using finger after zoom in and zoom out? I done pinch to zoom for canvas but stucking point is not draw right coordinate. I done more R&D from google. I have CustomView Class which i share on below.
public class DrawingView extends View {
ArrayList<Path> pathList = new ArrayList<Path>();
Rect mRect = new Rect();
private static final int INVALID_POINTER_ID = -1;
public Bitmap mMyChracter;
private float mPosX;
private float mPosY;
private float mLastTouchX;
private float mLastTouchY;
private int mActivePointerId = INVALID_POINTER_ID;
View currentView;
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
private float focusX;
private float focusY;
private float lastFocusX = -1;
private float lastFocusY = -1;
static final int IMG_WIDTH = 640;
static final int IMG_HEIGHT = 480;
static final int IMAGE_X_POS = 560;
static final int IMAGE_Y_POS = 20;
boolean mFalgPath=false;
boolean mFlagDrawAgain;
float sy;
float sx;
public static Context context;
Path path = new Path();
MyPath p1 = new MyPath();
MyPath p2 = new MyPath();
Path mNewPath = new Path();
// -------------------------------------
// ...................................
/*private final Bitmap bitmap;
private final int width;
private final int height;*/
private Matrix transform = new Matrix();
private Vector2D position = new Vector2D();
private float scale = 1;
private float angle = 0;
private TouchManager touchManager = new TouchManager(2);
private boolean isInitialized = false;
// Debug helpers to draw lines between the two touch points
private Vector2D vca = null;
private Vector2D vcb = null;
private Vector2D vpa = null;
private Vector2D vpb = null;
int mWidth;
int mHeight;
// ...............................
private final Paint mDefaultPaint;
private Paint mFillPaint;
float x, y;
private Canvas mLayerCanvas = new Canvas();
private Bitmap mLayerBitmap;
private Stack<DrawOp> mDrawOps = new Stack<>();
private Stack<DrawOp> mUndoOps = new Stack<>();
private SparseArray<DrawOp> mCurrentOps = new SparseArray<>(0);
// For Drag and Pan zoom Code initialization
private static float MIN_ZOOM = 1f;
private static float MAX_ZOOM = 2f;
private float scaleFactor = 1.f;
private static ScaleGestureDetector detector;
boolean mFlagDrawing;
private final Matrix mMatrix = new Matrix();
int y_old=0,y_new=0;int zoomMode=0;
float pinch_dist_old=0,pinch_dist_new=0;
int zoomControllerScale=1;//new and old pinch distance to determine Zoom scale
// These matrices will be used to move and zoom image
Matrix matrix = new Matrix();
Matrix savedMatrix = new Matrix();
// Remember some things for zooming
PointF start = new PointF();
PointF mid = new PointF();
float oldDist = 1f;
// We can be in one of these 3 states
static final int NONE = 0;
static final int PAN = 1;
static final int ZOOM = 2;
int mode = NONE;
private static final String TAG = "DebugTag";
// New Code
private Bitmap imgBitmap = null;
private int containerWidth;
private int containerHeight;
Paint background;
//Matrices will be used to move and zoom image
// Matrix matrix = new Matrix();
// Matrix savedMatrix = new Matrix();
// PointF start = new PointF();
float currentScale;
float curX;
float curY;
//We can be in one of these 3 states
// static final int NONE = 0;
// static final int DRAG = 1;
// static final int ZOOM = 2;
// int mode = NONE;
//For animating stuff
float targetX;
float targetY;
float targetScale;
float targetScaleX;
float targetScaleY;
float scaleChange;
float targetRatio;
float transitionalRatio;
float easing = 0.2f;
boolean isAnimating = false;
float scaleDampingFactor = 0.5f;
//For pinch and zoom
// float oldDist = 1f;
// PointF mid = new PointF();
private Handler mHandler = new Handler();
float minScale;
float maxScale = 8.0f;
float wpRadius = 25.0f;
float wpInnerRadius = 20.0f;
float screenDensity;
private GestureDetector gestureDetector;
public static final int DEFAULT_SCALE_FIT_INSIDE = 0;
public static final int DEFAULT_SCALE_ORIGINAL = 1;
private int defaultScale;
private static final String EXTRA_EVENT_LIST = "event_list";
private static final String EXTRA_STATE = "instance_state";
private ArrayList<MotionEvent> eventList = new ArrayList<MotionEvent>(100);
public DrawingView(Context context) {
this(context, null, 0);
}
public DrawingView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
// detector = new ScaleGestureDetector(getContext(), new ScaleListener());
}
public DrawingView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mDefaultPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mDefaultPaint.setStyle(Paint.Style.STROKE);
mDefaultPaint.setStrokeJoin(Paint.Join.ROUND);
mDefaultPaint.setStrokeCap(Paint.Cap.ROUND);
mDefaultPaint.setStrokeWidth(40);
mDefaultPaint.setColor(Color.GREEN);
/*mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mFillPaint.setStyle(Paint.Style.STROKE);
mFillPaint.setStrokeJoin(Paint.Join.ROUND);
mFillPaint.setStrokeCap(Paint.Cap.ROUND);
mDefaultPaint.setStrokeWidth(40);
mFillPaint.setColor(Color.GREEN);*/
setFocusable(true);
setFocusableInTouchMode(true);
setBackgroundColor(Color.WHITE);
setLayerType(LAYER_TYPE_SOFTWARE, null);
setSaveEnabled(true);
// Code for Zoom start
// detector = new ScaleGestureDetector(getContext(), new ScaleListener());
// Code for Zoom finish
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}
private static float getDegreesFromRadians(float angle) {
return (float)(angle * 180.0 / Math.PI);
}
// Single Touch Code
#Override
public boolean onTouchEvent(#NonNull MotionEvent event) {
/* float j = event.getX() / mScaleFactor + mRect.left;
float k = event.getY() / mScaleFactor + mRect.top;*/
final int pointerCount = MotionEventCompat.getPointerCount(event);
switch (MotionEventCompat.getActionMasked(event)) {
// switch(event.getAction()){
// switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
// case MotionEventCompat.ACTION_POINTER_DOWN:
{
if (mFlagDrawing == true) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
// Code for Zoom start
// detector.onTouchEvent(event);
mScaleDetector.onTouchEvent(event);
// Code for Zoom finish
/* float dx = event.getX();
float dy = event.getY();
if(mScaleFactor==1.f){
dx = event.getX() - mPosX;
dy = event.getY() - mPosY;
}
// tempPath = new Path();
DrawOp current = new DrawOp(mDefaultPaint);
current.getPath().moveTo(dx, dy);*/
} else if (mFlagDrawing == false) {
DrawOp currentNew = new DrawOp(mDefaultPaint);
if(mFlagDrawAgain == true){
p1.moveTo(event.getX(), event.getY());
/* path.moveTo(event.getX(), event.getY());
mNewPath.addPath(path);*/
}
else{
System.out.println("mFlagDrawing: " + mFlagDrawing);
for (int p = 0; p < pointerCount; p++) {
final int id = MotionEventCompat.getPointerId(event, p);
DrawOp current = new DrawOp(mDefaultPaint);
current.getPath().moveTo(event.getX(), event.getY());
// current.getPath().setLastPoint(dx, dy)
// mNewPath.addPath(current.getPath());
mCurrentOps.put(id, current);
}
}
}
// mFlagZoom = true;
// }
}
break;
case MotionEvent.ACTION_MOVE: {
// for(int p = 0; p < pointerCount; p++){
if (mFlagDrawing == true) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
// Code for Zoom start
// detector.onTouchEvent(event);
mScaleDetector.onTouchEvent(event);
// Code for Zoom finish
/* float dx = event.getX();
float dy = event.getY();;
if(mScaleFactor==1.f){
dx = event.getX() - mPosX;
dy = event.getY() - mPosY;
}
// mFalgPath = true;
path.lineTo(dx, dy);*/
} else if (mFlagDrawing == false) {
// mFalgPath = true;
// Rect rect = new Rect();
if(mFlagDrawAgain == true){
float dx = event.getX();
float dy = event.getY();
final int id = MotionEventCompat.getPointerId(event, 0);
DrawOp current = mCurrentOps.get(id);
if(mScaleFactor==1.f){
dx = event.getX() - mPosX;
dy = event.getY() - mPosY;
}
p1.lineTo(dx, dy);
/*p1.addPath(current.getPath());*/
/*path.lineTo(dx, dy);
mNewPath.addPath(path);*/
}
else{
System.out.println("mFlagDrawing: " + mFlagDrawing);
final int id = MotionEventCompat.getPointerId(event, 0);
DrawOp current = mCurrentOps.get(id);
final int historySize = event.getHistorySize();
for (int h = 0; h < historySize; h++) {
x = event.getHistoricalX(h);
y = event.getHistoricalY(h);
current.getPath().lineTo(x, y);
// mNewPath.addPath(current.getPath());
// mNewPath.lineTo(x, y);
}
x = MotionEventCompat.getX(event, 0);
y = MotionEventCompat.getY(event, 0);
current.getPath().lineTo(x, y);
}
}
// path.lineTo(x, y);
// }
}
break;
case MotionEvent.ACTION_UP:
// case MotionEventCompat.ACTION_POINTER_UP:
// {
if (mFlagDrawing == true) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
// Code for Zoom start
// detector.onTouchEvent(event);
mScaleDetector.onTouchEvent(event);
// Code for Zoom finish
mActivePointerId = INVALID_POINTER_ID;
} else if (mFlagDrawing == false) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
for (int p = 0; p < pointerCount; p++) {
final int id = MotionEventCompat.getPointerId(event, p);
mDrawOps.push(mCurrentOps.get(id));
mCurrentOps.remove(id);
// }
updateLayer();
}
}
// }
break;
case MotionEvent.ACTION_CANCEL: {
if (mFlagDrawing == true) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
// Code for Zoom start
// detector.onTouchEvent(event);
mScaleDetector.onTouchEvent(event);
// Code for Zoom finish
mActivePointerId = INVALID_POINTER_ID;
} else if (mFlagDrawing == false) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
for (int p = 0; p < pointerCount; p++) {
mCurrentOps
.remove(MotionEventCompat.getPointerId(event, p));
}
}
// mFlagZoom = true;
// }
}
break;
case MotionEvent.ACTION_POINTER_UP: {
final int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = event.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastTouchX = event.getX(newPointerIndex) / mScaleFactor;
mLastTouchY = event.getY(newPointerIndex) / mScaleFactor;
mActivePointerId = event.getPointerId(newPointerIndex);
}
break;
}
default:
return false;
}
invalidate();
return true;
}
#Override
protected void onSizeChanged(int w, int h, int oldW, int oldH) {
super.onSizeChanged(w, h, oldW, oldH);
if(mLayerBitmap == null){
mLayerBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
}else{
Bitmap temporary = Bitmap.createScaledBitmap(mLayerBitmap, w, h, true);
mLayerBitmap = temporary;
}
mLayerCanvas = new Canvas(mLayerBitmap);
}
private void updateLayer() {
mLayerCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
for (DrawOp drawOp : mDrawOps) {
if (drawOp != null) {
drawOp.draw(mLayerCanvas);
}
}
invalidate();
}
#SuppressWarnings("null")
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isInEditMode()) {
return;
}
canvas.save();
canvas.scale(mScaleFactor, mScaleFactor, focusX, focusY);
canvas.translate(mPosX, mPosY);
canvas.drawBitmap(mLayerBitmap, 0, 0, null);
if(mScaleFactor!=1.f)
canvas.restore();
if(mScaleFactor==1.f)
canvas.restore();
if(mFlagDrawAgain == true){
// canvas.drawPath(path, mDefaultPaint);
canvas.drawPath(p1, mDefaultPaint);
}
else{
for (int i = 0; i < mCurrentOps.size(); i++) {
DrawOp current = mCurrentOps.valueAt(i);
if (current != null) {
current.draw(canvas);
}
}
}
canvas.restore();
}
public void operationClear() {
path.reset();
mDrawOps.clear();
mUndoOps.clear();
mCurrentOps.clear();
updateLayer();
}
public void operationUndo() {
if (mDrawOps.size() > 0) {
mUndoOps.push(mDrawOps.pop());
updateLayer();
}
}
public void operationRedo() {
if (mUndoOps.size() > 0) {
mDrawOps.push(mUndoOps.pop());
updateLayer();
}
}
public void setPaintStrokeWidth(float widthPx) {
mDefaultPaint.setStrokeWidth(widthPx);
}
public void setPaintOpacity(int percent) {
int alphaValue = (int) Math.round(percent * (255.0 / 100.0));
mDefaultPaint.setColor(combineAlpha(mDefaultPaint.getColor(),
alphaValue));
}
public void setPaintColor(String color) {
mDefaultPaint.setColor(combineAlpha(Color.parseColor(color), mDefaultPaint.getAlpha()));
}
public void setPaintColor(int color) {
mDefaultPaint.setColor(combineAlpha(color, mDefaultPaint.getAlpha()));
}
public void setPaintMaskFilter(MaskFilter filter) {
mDefaultPaint.setMaskFilter(filter);
}
public void setPaintShader(BitmapShader shader) {
mDefaultPaint.setShader(shader);
}
public void setPaintColorFilter(ColorFilter colorFilter) {
mDefaultPaint.setColorFilter(colorFilter);
}
private static int combineAlpha(int color, int alpha) {
return (color & 0x00FFFFFF) | ((alpha & 0xFF) << 24);
}
private static class DrawOp {
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Path mPath = new Path();
public DrawOp(Paint paint) {
reset(paint);
}
void reset(Paint paint) {
mPath.reset();
update(paint);
}
void update(Paint paint) {
mPaint.set(paint);
}
void draw(Canvas canvas) {
canvas.drawPath(mPath, mPaint);
}
public Path getPath() {
return mPath;
}
}
public void fillShapeColor(){
}
private class ScaleListener extends
ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
// float x = detector.getFocusX();
// float y = detector.getFocusY();
lastFocusX = -1;
lastFocusY = -1;
return true;
}
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
focusX = detector.getFocusX();
focusY = detector.getFocusY();
if (lastFocusX == -1)
lastFocusX = focusX;
if (lastFocusY == -1)
lastFocusY = focusY;
mPosX += (focusX - lastFocusX);
mPosY += (focusY - lastFocusY);
Log.v("Hi Zoom", "Factor:" + mScaleFactor);
// Don't let the object get too small or too large.
mScaleFactor = Math.max(1.f, Math.min(mScaleFactor, 2.0f));
lastFocusX = focusX;
lastFocusY = focusY;
invalidate();
return true;
}
}
public void setDrawingFlag(boolean flag) {
// System.out.println("Before Set mFlag " + mFlagDrawing);
this.mFlagDrawing = flag;
// System.out.println("After Set mFlag " + mFlagDrawing);
}
public void setDrawPath(boolean flag){
this.mFalgPath = flag;
}
public void setDrawAgain(boolean flag){
this.mFlagDrawAgain = flag;
}
}
After lots of research i found solution for moving actual canvas with finger and used ZoomControlButton for zoom in and zoom out canvas. I post my code here for someone help.
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isInEditMode()) {
return;
}
if (mScaleFactor == 1.f)
canvas.save();
canvas.drawRGB(224, 224, 224);
canvas.translate(-mPosX, -mPosY);
canvas.drawBitmap(mLayerBitmap, new Matrix(), mDefaultPaint);
Log.v("onDraw", "scale f : " + mScaleFactor);
for (int i = 0; i < mCurrentOps.size(); i++) {
Log.v("onDraw", "current ops");
DrawOp current = mCurrentOps.valueAt(i);
if (current != null) {
current.draw(canvas);
}
}
canvas.restore();
}
In onTouch() i used following code
#Override
public boolean onTouchEvent(#NonNull MotionEvent event) {
final int pointerCount = MotionEventCompat.getPointerCount(event);
switch (MotionEventCompat.getActionMasked(event)) {
// switch(event.getAction()){
// switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
// case MotionEventCompat.ACTION_POINTER_DOWN:
{
if (mFlagDrawing == true) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
// Code for Zoom start
// detector.onTouchEvent(event);
mScaleDetector.onTouchEvent(event);
// Code for Zoom finish
} else if (mFlagDrawing == false) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
float dx = event.getX();
float dy = event.getY();
if(mScaleFactor==1.f){
dx = event.getX() - mPosX;
dy = event.getY() - mPosY;
}
for (int p = 0; p < pointerCount; p++) {
final int id = MotionEventCompat.getPointerId(event, p);
DrawOp current = new DrawOp(mDefaultPaint);
current.getPath().moveTo(event.getX() + mPosX, event.getY() + mPosY);
mCurrentOps.put(id, current);
}
}
}
break;
case MotionEvent.ACTION_MOVE: {
// for(int p = 0; p < pointerCount; p++){
if (mFlagDrawing == true) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
// Code for Zoom start
// detector.onTouchEvent(event);
mScaleDetector.onTouchEvent(event);
// Code for Zoom finish
} else if (mFlagDrawing == false) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
final int id = MotionEventCompat.getPointerId(event, 0);
DrawOp current = mCurrentOps.get(id);
final int historySize = event.getHistorySize();
for (int h = 0; h < historySize; h++) {
x = event.getHistoricalX(h) + mPosX;
y = event.getHistoricalY(h) + mPosY;
current.getPath().lineTo(x, y);
}
x = MotionEventCompat.getX(event, 0) + mPosX;
y = MotionEventCompat.getY(event, 0) + mPosY;
current.getPath().lineTo(x, y);
}
}
break;
case MotionEvent.ACTION_UP:
// case MotionEventCompat.ACTION_POINTER_UP:
// {
if (mFlagDrawing == true) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
// Code for Zoom start
// detector.onTouchEvent(event);
mScaleDetector.onTouchEvent(event);
// Code for Zoom finish
mActivePointerId = INVALID_POINTER_ID;
} else if (mFlagDrawing == false) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
for (int p = 0; p < pointerCount; p++) {
final int id = MotionEventCompat.getPointerId(event, p);
mDrawOps.push(mCurrentOps.get(id));
mCurrentOps.remove(id);
// }
updateLayer();
}
}
// }
break;
case MotionEvent.ACTION_CANCEL: {
if (mFlagDrawing == true) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
// Code for Zoom start
// detector.onTouchEvent(event);
mScaleDetector.onTouchEvent(event);
// Code for Zoom finish
mActivePointerId = INVALID_POINTER_ID;
} else if (mFlagDrawing == false) {
System.out.println("mFlagDrawing: " + mFlagDrawing);
for (int p = 0; p < pointerCount; p++) {
mCurrentOps
.remove(MotionEventCompat.getPointerId(event, p));
}
}
// mFlagZoom = true;
// }
}
break;
default:
return false;
}
invalidate();
return true;
}
When updating the whole Layer then i used following syntax:
private void updateLayer() {
mLayerCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
mLayerCanvas.drawRGB(255, 255, 255);
mLayerCanvas.save();
for (DrawOp drawOp : mDrawOps) {
if (drawOp != null) {
drawOp.draw(mLayerCanvas);
}
}
mLayerCanvas.restore();
invalidate();
}
I’m developing an android application (API 19 4.4) and I encounter some issue with ImageViews.
I have a SurfaceView, in which I dynamically add ImageViews which I want to react to touch events.
On so far, I have managed to make the ImageView move and scale smoothly but I have an annoying behavior.
When I scale down the image to a certain limit (I would say half the original size) and I try to move it, the image flicker.
After a short analysis, it seems that it’s switching its position symmetrically around the finger point on the screen, cumulating distance, and finally gets out of sight (all that happens very fast ( < 1s).
I think I am missing something with the relative value of the touch event to the ImageView/SurfaceView, but I’m a quite a noob and I’m stucked…
Here is my code
public class MyImageView extends ImageView {
private ScaleGestureDetector mScaleDetector ;
private static final int MAX_SIZE = 1024;
private static final String TAG = "MyImageView";
PointF DownPT = new PointF(); // Record Mouse Position When Pressed Down
PointF StartPT = new PointF(); // Record Start Position of 'img'
public MyImageView(Context context) {
super(context);
mScaleDetector = new ScaleGestureDetector(context,new MySimpleOnScaleGestureListener());
setBackgroundColor(Color.RED);
setScaleType(ScaleType.MATRIX);
setAdjustViewBounds(true);
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
lp.setMargins(-MAX_SIZE, -MAX_SIZE, -MAX_SIZE, -MAX_SIZE);
this.setLayoutParams(lp);
this.setX(MAX_SIZE);
this.setY(MAX_SIZE);
}
int firstPointerID;
boolean inScaling=false;
#Override
public boolean onTouchEvent(MotionEvent event) {
// get pointer index from the event object
int pointerIndex = event.getActionIndex();
// get pointer ID
int pointerId = event.getPointerId(pointerIndex);
//First send event to scale detector to find out, if it's a scale
boolean res = mScaleDetector.onTouchEvent(event);
if (!mScaleDetector.isInProgress()) {
int eid = event.getAction();
switch (eid & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_MOVE :
if(pointerId == firstPointerID) {
PointF mv = new PointF( (int)(event.getX() - DownPT.x), (int)( event.getY() - DownPT.y));
this.setX((int)(StartPT.x+mv.x));
this.setY((int)(StartPT.y+mv.y));
StartPT = new PointF( this.getX(), this.getY() );
}
break;
case MotionEvent.ACTION_DOWN : {
firstPointerID = pointerId;
DownPT.x = (int) event.getX();
DownPT.y = (int) event.getY();
StartPT = new PointF( this.getX(), this.getY() );
break;
}
case MotionEvent.ACTION_POINTER_DOWN: {
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL: {
firstPointerID = -1;
break;
}
default :
break;
}
return true;
}
return true;
}
public boolean onScaling(ScaleGestureDetector detector) {
this.setScaleX(this.getScaleX()*detector.getScaleFactor());
this.setScaleY(this.getScaleY()*detector.getScaleFactor());
invalidate();
return true;
}
private class MySimpleOnScaleGestureListener extends SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
return onScaling(detector);
}
#Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
Log.d(TAG, "onScaleBegin");
return true;
}
#Override
public void onScaleEnd(ScaleGestureDetector arg0) {
Log.d(TAG, "onScaleEnd");
}
}
}
I have also another questions about rotations. How should I implement this?
Could I use the ScalegestureDetector in some way or have I to make this works in the view touch event? I would like to be able to scale and rotate in the same gesture (and move in another).
Thank for helping me, I would really appreciate!
Sorry for my english
this is a working example of two fingers move/scale/rotate (note: the code is quite short due to smart detector used - see MatrixGestureDetector):
class ViewPort extends View {
List<Layer> layers = new LinkedList<Layer>();
int[] ids = {R.drawable.layer0, R.drawable.layer1, R.drawable.layer2};
public ViewPort(Context context) {
super(context);
Resources res = getResources();
for (int i = 0; i < ids.length; i++) {
Layer l = new Layer(context, this, BitmapFactory.decodeResource(res, ids[i]));
layers.add(l);
}
}
#Override
protected void onDraw(Canvas canvas) {
for (Layer l : layers) {
l.draw(canvas);
}
}
private Layer target;
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
target = null;
for (int i = layers.size() - 1; i >= 0; i--) {
Layer l = layers.get(i);
if (l.contains(event)) {
target = l;
layers.remove(l);
layers.add(l);
invalidate();
break;
}
}
}
if (target == null) {
return false;
}
return target.onTouchEvent(event);
}
}
class Layer implements MatrixGestureDetector.OnMatrixChangeListener {
Matrix matrix = new Matrix();
Matrix inverse = new Matrix();
RectF bounds;
View parent;
Bitmap bitmap;
MatrixGestureDetector mgd = new MatrixGestureDetector(matrix, this);
public Layer(Context ctx, View p, Bitmap b) {
parent = p;
bitmap = b;
bounds = new RectF(0, 0, b.getWidth(), b.getHeight());
matrix.postTranslate(50 + (float) Math.random() * 50, 50 + (float) Math.random() * 50);
}
public boolean contains(MotionEvent event) {
matrix.invert(inverse);
float[] pts = {event.getX(), event.getY()};
inverse.mapPoints(pts);
if (!bounds.contains(pts[0], pts[1])) {
return false;
}
return Color.alpha(bitmap.getPixel((int) pts[0], (int) pts[1])) != 0;
}
public boolean onTouchEvent(MotionEvent event) {
mgd.onTouchEvent(event);
return true;
}
#Override
public void onChange(Matrix matrix) {
parent.invalidate();
}
public void draw(Canvas canvas) {
canvas.drawBitmap(bitmap, matrix, null);
}
}
class MatrixGestureDetector {
private static final String TAG = "MatrixGestureDetector";
private int ptpIdx = 0;
private Matrix mTempMatrix = new Matrix();
private Matrix mMatrix;
private OnMatrixChangeListener mListener;
private float[] mSrc = new float[4];
private float[] mDst = new float[4];
private int mCount;
interface OnMatrixChangeListener {
void onChange(Matrix matrix);
}
public MatrixGestureDetector(Matrix matrix, MatrixGestureDetector.OnMatrixChangeListener listener) {
this.mMatrix = matrix;
this.mListener = listener;
}
public void onTouchEvent(MotionEvent event) {
if (event.getPointerCount() > 2) {
return;
}
int action = event.getActionMasked();
int index = event.getActionIndex();
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
int idx = index * 2;
mSrc[idx] = event.getX(index);
mSrc[idx + 1] = event.getY(index);
mCount++;
ptpIdx = 0;
break;
case MotionEvent.ACTION_MOVE:
for (int i = 0; i < mCount; i++) {
idx = ptpIdx + i * 2;
mDst[idx] = event.getX(i);
mDst[idx + 1] = event.getY(i);
}
mTempMatrix.setPolyToPoly(mSrc, ptpIdx, mDst, ptpIdx, mCount);
mMatrix.postConcat(mTempMatrix);
if(mListener != null) {
mListener.onChange(mMatrix);
}
System.arraycopy(mDst, 0, mSrc, 0, mDst.length);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
if (event.getPointerId(index) == 0) ptpIdx = 2;
mCount--;
break;
}
}
}
I tried to implementation of multiple touch on view not on bitmap using matrix, now i success. Now i think it will helpful to you for individual gesture for multiple image. Try it, it work best for me.
public class MultiTouchImageView extends ImageView implements OnTouchListener{
float[] lastEvent = null;
float d = 0f;
float newRot = 0f;
public static String fileNAME;
public static int framePos = 0;
//private ImageView view;
private boolean isZoomAndRotate;
private boolean isOutSide;
// We 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 PointF start = new PointF();
private PointF mid = new PointF();
float oldDist = 1f;
public MultiTouchImageView(Context context) {
super(context);
}
public MultiTouchImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public MultiTouchImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#SuppressWarnings("deprecation")
#Override
public boolean onTouch(View v, MotionEvent event) {
//view = (ImageView) v;
bringToFront();
// Handle touch events here...
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
//savedMatrix.set(matrix);
start.set(event.getX(), event.getY());
mode = DRAG;
lastEvent = null;
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);
if (oldDist > 10f) {
midPoint(mid, event);
mode = ZOOM;
}
lastEvent = new float[4];
lastEvent[0] = event.getX(0);
lastEvent[1] = event.getX(1);
lastEvent[2] = event.getY(0);
lastEvent[3] = event.getY(1);
d = rotation(event);
break;
case MotionEvent.ACTION_UP:
isZoomAndRotate = false;
case MotionEvent.ACTION_OUTSIDE:
isOutSide = true;
mode = NONE;
lastEvent = null;
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
lastEvent = null;
break;
case MotionEvent.ACTION_MOVE:
if(!isOutSide){
if (mode == DRAG && !isZoomAndRotate) {
isZoomAndRotate = false;
setTranslationX((event.getX() - start.x) + getTranslationX());
setTranslationY((event.getY() - start.y) + getTranslationY());
} else if (mode == ZOOM && event.getPointerCount() == 2) {
isZoomAndRotate = true;
boolean isZoom = false;
if(!isRotate(event)){
float newDist = spacing(event);
if (newDist > 10f) {
float scale = newDist / oldDist * getScaleX();
setScaleX(scale);
setScaleY(scale);
isZoom = true;
}
}
else if(!isZoom){
newRot = rotation(event);
setRotation((float)(getRotation() + (newRot - d)));
}
}
}
break;
}
new GestureDetector(new MyGestureDectore());
Constants.currentSticker = this;
return true;
}
private class MyGestureDectore extends GestureDetector.SimpleOnGestureListener{
#Override
public boolean onDoubleTap(MotionEvent e) {
bringToFront();
return false;
}
#Override
public boolean onDoubleTapEvent(MotionEvent e) {
return false;
}
}
private float rotation(MotionEvent event) {
double delta_x = (event.getX(0) - event.getX(1));
double delta_y = (event.getY(0) - event.getY(1));
double radians = Math.atan2(delta_y, delta_x);
return (float) Math.toDegrees(radians);
}
private float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return FloatMath.sqrt(x * x + y * y);
}
private void midPoint(PointF point, MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
point.set(x / 2, y / 2);
}
private boolean isRotate(MotionEvent event){
int dx1 = (int) (event.getX(0) - lastEvent[0]);
int dy1 = (int) (event.getY(0) - lastEvent[2]);
int dx2 = (int) (event.getX(1) - lastEvent[1]);
int dy2 = (int) (event.getY(1) - lastEvent[3]);
Log.d("dx1 ", ""+ dx1);
Log.d("dx2 ", "" + dx2);
Log.d("dy1 ", "" + dy1);
Log.d("dy2 ", "" + dy2);
//pointer 1
if(Math.abs(dx1) > Math.abs(dy1) && Math.abs(dx2) > Math.abs(dy2)) {
if(dx1 >= 2.0 && dx2 <= -2.0){
Log.d("first pointer ", "right");
return true;
}
else if(dx1 <= -2.0 && dx2 >= 2.0){
Log.d("first pointer ", "left");
return true;
}
}
else {
if(dy1 >= 2.0 && dy2 <= -2.0){
Log.d("seccond pointer ", "top");
return true;
}
else if(dy1 <= -2.0 && dy2 >= 2.0){
Log.d("second pointer ", "bottom");
return true;
}
}
return false;
}
}
I finally use this (spacing is used to calculated the distance between two fingers), I offset the imageview after scaling to keep it centered, works fine for now :
float newDist = spacing(event);
float scale = newDist / oldDist;
int oldH =getLayoutParams().height;
int oldW =getLayoutParams().width;
int newH =(int) (getLayoutParams().height*scale);
int newW =(int) (getLayoutParams().width*scale);
if(newH<MAX_SIZE && newW<MAX_SIZE){
//scale the height and width of the view
getLayoutParams().height = newH;
getLayoutParams().width = newW;
//calculate the X and Y offset to apply after scaling to keep the image centered
int xOffset = (int)(getLayoutParams().height - oldH)/2;
int yOffset = (int)(getLayoutParams().width - oldW)/2;
setX(getX()-xOffset);
setY(getY()-yOffset);
requestLayout();
setAdjustViewBounds(true);
oldDist=newDist;
All these examples had a glitchy gesture support because of scaleType was set to matrix. When I tried to zoom, I was not able to keep the image in center and control the amount of zoom. So I did some study and wrote a small, easy but very pleasing code for this: https://stackoverflow.com/a/65697376/13339685
I'm new to Android, and I'm having problems moving an object inside onDraw(Canvas c).
Below is my code. I hope someone can tell me how to fix it.
public class GameviewLuzon1 extends ImageView {
int y = 0;
int x = 0;
int obj1 = R.drawable.obj_androidball;
int obj2 = R.drawable.obj_drum;
/****************************************/
Drawable dr1 = getResources().getDrawable(obj1);
Bitmap bitmap1 = ((BitmapDrawable) dr1).getBitmap();
Drawable d1 = new BitmapDrawable(getResources(), Bitmap.createScaledBitmap(bitmap1, 20, 20, true));
float d1_startX = (float) 0.00;
float d1_startY = (float) 0.00;
float d1_movementX = 2;
float d1_movementY = 2;
Drawable dr2 = getResources().getDrawable(obj2);
Bitmap bitmap2 = ((BitmapDrawable) dr2).getBitmap();
Drawable d2 = new BitmapDrawable(getResources(), Bitmap.createScaledBitmap(bitmap2, 35, 35, true));
float d2_startX = (float) 0.00;
float d2_startY = (float) 0.00;
/****************************************/
public GameviewLuzon1(Context context, AttributeSet attrs) {
super(context, attrs);
}
protected void onDraw(Canvas c) {
d1_startX = (float) (this.getWidth()/d1_movementX);
d1_startY = (float) (this.getHeight()/d1_movementY);
d2_startX = (float) (this.getWidth()/5.75);
d2_startY = (float) (this.getHeight()/7.99);
/****************************************/
c.drawBitmap(((BitmapDrawable) d1).getBitmap(), d1_startX, d1_startY, null);
c.drawBitmap(((BitmapDrawable) d2).getBitmap(), d2_startX, d2_startY, null);
/****************************************/
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
if( touchX >= d1_startX && touchX <= (d1_startX + d1.getIntrinsicWidth())
&& touchY >= d1_startY && touchY <= (d1_startY +d1.getIntrinsicHeight())) {
Log.w("d1 success","you touched object");
d1_movementX = 0;
d1_movementY = 0;
}
else if( touchX >= d2_startX && touchX <= (d2_startX + d2.getIntrinsicWidth())
&& touchY >= d2_startY && touchY <= (d2_startY +d2.getIntrinsicHeight())) {
Log.w("d2 success","you touched object");
invalidate();
}
else {
Log.w("epic","wala jud ka kaigu");
}
/*Commented out for debugging purpose only*/
/*Log.w("touch",Float.toString(touchX));
Log.w("start of Image",Float.toString(d1_startX));
Log.w("edge of image",Float.toString(d1_startX + d1.getIntrinsicWidth()));*/
return true;
}
}
public class Game1LuzonAnimation extends ImageView {
int y = 0;
int x = 0;
int zoomControler = 0;
/*****************TEST*******************/
private Context mContext;
private Handler h;
private final int FRAME_RATE = 30;
private int xVelocity = 10;
boolean animateObj = false;
boolean setVisibleObj = true;
/*****************TEST*******************/
/*Create variable obj1 and so on, so that
* we can create a dynamic objects..,
* if we need it to be dynamic.
* */
int obj1 = R.drawable.obj_androidball;
int obj2 = R.drawable.obj_drum;
/****************************************/
/* Assign some obj values into their respective places and
* set their sizes to fit in our screen sizes
* */
Drawable dr1 = getResources().getDrawable(obj1);
Bitmap bitmap1 = ((BitmapDrawable) dr1).getBitmap();
Drawable d1 = new BitmapDrawable(getResources(), Bitmap.createScaledBitmap(bitmap1, 20, 20, true));
float d1max_width = d1.getIntrinsicWidth() * 2;
float d1max_height = d1.getIntrinsicHeight() * 2;
float d1_startX = (float) 0.00; //initialize the position value of first object
float d1_startY = (float) 0.00; //initialize the position value of first object
/****************************************/
public Game1LuzonAnimation(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
h = new Handler();
}
private Runnable r = new Runnable() {
#Override
public void run() {
invalidate();
}
};
protected void onDraw(Canvas c) {
/* put the values for position of each object from database;
* */
if(animateObj == false){
d1_startX = (float) (c.getWidth()/2);
d1_startY = (float) (c.getHeight()/2);
}
else {
if (d1_startX < 0) {
animateObj = false;
setVisibleObj = false;
}
else {
d1_startX -= xVelocity;
if(d1_startX > c.getWidth()){
xVelocity = xVelocity*-1;
}
}
}
if(setVisibleObj == true) {
c.drawBitmap(((BitmapDrawable) d1).getBitmap(), d1_startX, d1_startY, null);
h.postDelayed(r, FRAME_RATE);
}
/*Log.w("d1max_width", Float.toString(d1max_width));
Log.w("d1max_height", Float.toString(d1max_height));
Log.w("d1_startX", Float.toString(d1_startX));
Log.w("d1_startY", Float.toString(d1_startY));*/
/****************************************/
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
if( touchX >= d1_startX && touchX <= (d1_startX + d1.getIntrinsicWidth())
&& touchY >= d1_startY && touchY <= (d1_startY +d1.getIntrinsicHeight())) {
Log.w("d1 success","you touched object");
if(d1.getIntrinsicWidth() < d1max_width && d1.getIntrinsicHeight() < d1max_height) {
d1 = new BitmapDrawable(getResources(), Bitmap.createScaledBitmap(bitmap1, d1.getIntrinsicWidth()*2, d1.getIntrinsicHeight()*2, true));
animateObj = true;
}
}
else {
Log.w("epic","wala jud ka kaigu");
}
return true;
}
}
please tell me how can i remove the exception. or give me idea to implement zoom view with curlview.....
my xml file is like this,
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/relat">
<com.example.image.ZoomView
>
<com.example.image.PageCurlView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/dcgpagecurlPageCurlView1"
>
</com.example.image.PageCurlView>
my java file is
package com.example.image;
public class StandaloneExample extends Activity {
Button bt;LinearLayout lr;
/** Decoded bitmap image */
private Bitmap mBitmap;
private ZoomView zoomview;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.standalone_example);
View v1 = ((LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.standalone_example, null, false);
v1.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
zoomview.addView(v1);
lr = (LinearLayout) findViewById(R.id.relat);
lr.addView(zoomview);
}
#Override
public void onDestroy(){
super.onDestroy();
System.gc();
finish();
}
public void lockOrientationLandscape() {
lockOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
public void lockOrientationPortrait() {
lockOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
public void lockOrientation( int orientation ) {
setRequestedOrientation(orientation);
}
}
i have implemented zoomView.java as described in a link
pagecurlview.java class is
public class PageCurlView extends View {
private final static String TAG = "PageCurlView";
private Paint mTextPaint;
private TextPaint mTextPaintShadow;
private int mCurlSpeed;
private int mUpdateRate;
private int mInitialEdgeOffset;
private int mCurlMode;
public static final int CURLMODE_SIMPLE = 0;
public static final int CURLMODE_DYNAMIC = 1;
private boolean bEnableDebugMode = false;
private WeakReference<Context> mContext;
private FlipAnimationHandler mAnimationHandler;
private float mFlipRadius;
private Vector2D mMovement;
private Vector2D mFinger;
private Vector2D mOldMovement;
private Paint mCurlEdgePaint;
private Vector2D mA, mB, mC, mD, mE, mF, mOldF, mOrigin;
private int mCurrentLeft, mCurrentTop;
private boolean bViewDrawn;
private boolean bFlipRight;
private boolean bFlipping;
private boolean bUserMoves;
private boolean bBlockTouchInput = false;
private boolean bEnableInputAfterDraw = false;
private Bitmap mForeground;
private Bitmap mBackground;
private ArrayList<Bitmap> mPages;
private int mIndex = 0;
private class Vector2D
{
public float x,y;
public Vector2D(float x, float y)
{
this.x = x;
this.y = y;
}
#Override
public String toString() {
// TODO Auto-generated method stub
return "("+this.x+","+this.y+")";
}
public float length() {
return (float) Math.sqrt(x * x + y * y);
}
public float lengthSquared() {
return (x * x) + (y * y);
}
public boolean equals(Object o) {
if (o instanceof Vector2D) {
Vector2D p = (Vector2D) o;
return p.x == x && p.y == y;
}
return false;
}
public Vector2D reverse() {
return new Vector2D(-x,-y);
}
public Vector2D sum(Vector2D b) {
return new Vector2D(x+b.x,y+b.y);
}
public Vector2D sub(Vector2D b) {
return new Vector2D(x-b.x,y-b.y);
}
public float dot(Vector2D vec) {
return (x * vec.x) + (y * vec.y);
}
public float cross(Vector2D a, Vector2D b) {
return a.cross(b);
}
public float cross(Vector2D vec) {
return x * vec.y - y * vec.x;
}
public float distanceSquared(Vector2D other) {
float dx = other.x - x;
float dy = other.y - y;
return (dx * dx) + (dy * dy);
}
public float distance(Vector2D other) {
return (float) Math.sqrt(distanceSquared(other));
}
public float dotProduct(Vector2D other) {
return other.x * x + other.y * y;
}
public Vector2D normalize() {
float magnitude = (float) Math.sqrt(dotProduct(this));
return new Vector2D(x / magnitude, y / magnitude);
}
public Vector2D mult(float scalar) {
return new Vector2D(x*scalar,y*scalar);
}
}
class FlipAnimationHandler extends Handler {
#Override
public void handleMessage(Message msg) {
PageCurlView.this.FlipAnimationStep();
}
public void sleep(long millis) {
this.removeMessages(0);
sendMessageDelayed(obtainMessage(0), millis);
}
}
public PageCurlView(Context context) {
super(context);
init(context);
ResetClipEdge();
}
public PageCurlView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
// Get the data from the XML AttributeSet
{
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PageCurlView);
// Get data
bEnableDebugMode = a.getBoolean(R.styleable.PageCurlView_enableDebugMode, bEnableDebugMode);
mCurlSpeed = a.getInt(R.styleable.PageCurlView_curlSpeed, mCurlSpeed);
mUpdateRate = a.getInt(R.styleable.PageCurlView_updateRate, mUpdateRate);
mInitialEdgeOffset = a.getInt(R.styleable.PageCurlView_initialEdgeOffset, mInitialEdgeOffset);
mCurlMode = a.getInt(R.styleable.PageCurlView_curlMode, mCurlMode);
Log.i(TAG, "mCurlSpeed: " + mCurlSpeed);
Log.i(TAG, "mUpdateRate: " + mUpdateRate);
Log.i(TAG, "mInitialEdgeOffset: " + mInitialEdgeOffset);
Log.i(TAG, "mCurlMode: " + mCurlMode);
// recycle object (so it can be used by others)
a.recycle();
}
ResetClipEdge();
}
private final void init(Context context) {
// Foreground text paint
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setTextSize(16);
mTextPaint.setColor(0xFF000000);
// The shadow
mTextPaintShadow = new TextPaint();
mTextPaintShadow.setAntiAlias(true);
mTextPaintShadow.setTextSize(16);
mTextPaintShadow.setColor(0x00000000);
// Cache the context
mContext = new WeakReference<Context>(context);
// Base padding
setPadding(3, 3, 3, 3);
// The focus flags are needed
setFocusable(true);
setFocusableInTouchMode(true);
mMovement = new Vector2D(0,0);
mFinger = new Vector2D(0,0);
mOldMovement = new Vector2D(0,0);
// Create our curl animation handler
mAnimationHandler = new FlipAnimationHandler();
// Create our edge paint
mCurlEdgePaint = new Paint();
mCurlEdgePaint.setColor(Color.WHITE);
mCurlEdgePaint.setAntiAlias(true);
mCurlEdgePaint.setStyle(Paint.Style.FILL);
mCurlEdgePaint.setShadowLayer(10, -5, 5, 0x99000000);
// Set the default props, those come from an XML :D
mCurlSpeed = 30;
mUpdateRate = 33;
mInitialEdgeOffset = 20;
mCurlMode = 1;
// LEGACY PAGE HANDLING!
// Create pages
mPages = new ArrayList<Bitmap>();
mPages.add(BitmapFactory.decodeResource(getResources(), R.drawable.princess));
mPages.add(BitmapFactory.decodeResource(getResources(), R.drawable.temp));
// Create some sample images
mForeground = mPages.get(0);
mBackground = mPages.get(1);
}
public void ResetClipEdge()
{
// Set our base movement
mMovement.x = mInitialEdgeOffset;
mMovement.y = mInitialEdgeOffset;
mOldMovement.x = 0;
mOldMovement.y = 0;
// Now set the points
// TODO: OK, those points MUST come from our measures and
// the actual bounds of the view!
mA = new Vector2D(mInitialEdgeOffset, 0);
mB = new Vector2D(this.getWidth(), this.getHeight());
mC = new Vector2D(this.getWidth(), 0);
mD = new Vector2D(0, 0);
mE = new Vector2D(0, 0);
mF = new Vector2D(0, 0);
mOldF = new Vector2D(0, 0);
// The movement origin point
mOrigin = new Vector2D(this.getWidth(), 0);
}
private Context GetContext() {
return mContext.get();
}
public boolean IsCurlModeDynamic()
{
return mCurlMode == CURLMODE_DYNAMIC;
}
public void SetCurlSpeed(int curlSpeed)
{
if ( curlSpeed < 1 )
throw new IllegalArgumentException("curlSpeed must be greated than 0");
mCurlSpeed = curlSpeed;
}
public int GetCurlSpeed()
{
return mCurlSpeed;
}
public void SetUpdateRate(int updateRate)
{
if ( updateRate < 1 )
throw new IllegalArgumentException("updateRate must be greated than 0");
mUpdateRate = updateRate;
}
public int GetUpdateRate()
{
return mUpdateRate;
}
public void SetInitialEdgeOffset(int initialEdgeOffset)
{
if ( initialEdgeOffset < 0 )
throw new IllegalArgumentException("initialEdgeOffset can not negative");
mInitialEdgeOffset = initialEdgeOffset;
}
public int GetInitialEdgeOffset()
{
return mInitialEdgeOffset;
}
public void SetCurlMode(int curlMode)
{
if ( curlMode != CURLMODE_SIMPLE &&
curlMode != CURLMODE_DYNAMIC )
throw new IllegalArgumentException("Invalid curlMode");
mCurlMode = curlMode;
}
public int GetCurlMode()
{
return mCurlMode;
}
public void SetEnableDebugMode(boolean bFlag)
{
bEnableDebugMode = bFlag;
}
public boolean IsDebugModeEnabled()
{
return bEnableDebugMode;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int finalWidth, finalHeight;
finalWidth = measureWidth(widthMeasureSpec);
finalHeight = measureHeight(heightMeasureSpec);
setMeasuredDimension(finalWidth, finalHeight);
}
private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else {
// Measure the text
result = specSize;
}
return result;
}
private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else {
// Measure the text (beware: ascent is a negative number)
result = specSize;
}
return result;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (!bBlockTouchInput) {
// Get our finger position
mFinger.x = event.getX();
mFinger.y = event.getY();
int width = getWidth();
// Depending on the action do what we need to
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mOldMovement.x = mFinger.x;
mOldMovement.y = mFinger.y;
// If we moved over the half of the display flip to next
if (mOldMovement.x > (width >> 1)) {
mMovement.x = mInitialEdgeOffset;
mMovement.y = mInitialEdgeOffset;
// Set the right movement flag
bFlipRight = true;
} else {
// Set the left movement flag
bFlipRight = false;
// go to next previous page
previousView();
// Set new movement
mMovement.x = IsCurlModeDynamic()?width<<1:width;
mMovement.y = mInitialEdgeOffset;
}
break;
case MotionEvent.ACTION_UP:
bUserMoves=false;
bFlipping=true;
FlipAnimationStep();
break;
case MotionEvent.ACTION_MOVE:
bUserMoves=true;
// Get movement
mMovement.x -= mFinger.x - mOldMovement.x;
mMovement.y -= mFinger.y - mOldMovement.y;
mMovement = CapMovement(mMovement, true);
// Make sure the y value get's locked at a nice level
if ( mMovement.y <= 1 )
mMovement.y = 1;
// Get movement direction
if (mFinger.x < mOldMovement.x ) {
bFlipRight = true;
} else {
bFlipRight = false;
}
// Save old movement values
mOldMovement.x = mFinger.x;
mOldMovement.y = mFinger.y;
// Force a new draw call
DoPageCurl();
this.invalidate();
break;
}
}
// TODO: Only consume event if we need to.
return true;
}
private Vector2D CapMovement(Vector2D point, boolean bMaintainMoveDir)
{
// Make sure we never ever move too much
if (point.distance(mOrigin) > mFlipRadius)
{
if ( bMaintainMoveDir )
{
// Maintain the direction
point = mOrigin.sum(point.sub(mOrigin).normalize().mult(mFlipRadius));
}
else
{
// Change direction
if ( point.x > (mOrigin.x+mFlipRadius))
point.x = (mOrigin.x+mFlipRadius);
else if ( point.x < (mOrigin.x-mFlipRadius) )
point.x = (mOrigin.x-mFlipRadius);
point.y = (float) (Math.sin(Math.acos(Math.abs(point.x-mOrigin.x)/mFlipRadius))*mFlipRadius);
}
}
return point;
}
public void FlipAnimationStep() {
if ( !bFlipping )
return;
int width = getWidth();
// No input when flipping
bBlockTouchInput = true;
// Handle speed
float curlSpeed = mCurlSpeed;
if ( !bFlipRight )
curlSpeed *= -1;
// Move us
mMovement.x += curlSpeed;
mMovement = CapMovement(mMovement, false);
// Create values
DoPageCurl();
// Check for endings :D
if (mA.x < 1 || mA.x > width - 1) {
bFlipping = false;
if (bFlipRight) {
//SwapViews();
nextView();
}
ResetClipEdge();
// Create values
DoPageCurl();
// Enable touch input after the next draw event
bEnableInputAfterDraw = true;
}
else
{
mAnimationHandler.sleep(mUpdateRate);
}
// Force a new draw call
this.invalidate();
}
private void DoPageCurl()
{
if(bFlipping){
if ( IsCurlModeDynamic() )
doDynamicCurl();
else
doSimpleCurl();
} else {
if ( IsCurlModeDynamic() )
doDynamicCurl();
else
doSimpleCurl();
}
}
private void doSimpleCurl() {
int width = getWidth();
int height = getHeight();
// Calculate point A
mA.x = width - mMovement.x;
mA.y = height;
// Calculate point D
mD.x = 0;
mD.y = 0;
if (mA.x > width / 2) {
mD.x = width;
mD.y = height - (width - mA.x) * height / mA.x;
} else {
mD.x = 2 * mA.x;
mD.y = 0;
}
double angle = Math.atan((height - mD.y) / (mD.x + mMovement.x - width));
double _cos = Math.cos(2 * angle);
double _sin = Math.sin(2 * angle);
mF.x = (float) (width - mMovement.x + _cos * mMovement.x);
mF.y = (float) (height - _sin * mMovement.x);
// If the x position of A is above half of the page we are still not
// folding the upper-right edge and so E and D are equal.
if (mA.x > width / 2) {
mE.x = mD.x;
mE.y = mD.y;
}
else
{
// So get E
mE.x = (float) (mD.x + _cos * (width - mD.x));
mE.y = (float) -(_sin * (width - mD.x));
}
}
private void doDynamicCurl() {
int width = getWidth();
int height = getHeight();
mF.x = width - mMovement.x+0.1f;
mF.y = height - mMovement.y+0.1f;
if(mA.x==0) {
mF.x= Math.min(mF.x, mOldF.x);
mF.y= Math.max(mF.y, mOldF.y);
}
// Get diffs
float deltaX = width-mF.x;
float deltaY = height-mF.y;
float BH = (float) (Math.sqrt(deltaX * deltaX + deltaY * deltaY) / 2);
double tangAlpha = deltaY / deltaX;
double alpha = Math.atan(deltaY / deltaX);
double _cos = Math.cos(alpha);
double _sin = Math.sin(alpha);
mA.x = (float) (width - (BH / _cos));
mA.y = height;
mD.y = (float) (height - (BH / _sin));
mD.x = width;
mA.x = Math.max(0,mA.x);
if(mA.x==0) {
mOldF.x = mF.x;
mOldF.y = mF.y;
}
// Get W
mE.x = mD.x;
mE.y = mD.y;
// Correct
if (mD.y < 0) {
mD.x = width + (float) (tangAlpha * mD.y);
mE.y = 0;
mE.x = width + (float) (Math.tan(2 * alpha) * mD.y);
}
}
#Deprecated
private void SwapViews() {
Bitmap temp = mForeground;
mForeground = mBackground;
mBackground = temp;
}
private void nextView() {
int foreIndex = mIndex + 1;
if(foreIndex >= mPages.size()) {
foreIndex = 0;
}
int backIndex = foreIndex + 1;
if(backIndex >= mPages.size()) {
backIndex = 0;
}
mIndex = foreIndex;
setViews(foreIndex, backIndex);
}
private void previousView() {
int backIndex = mIndex;
int foreIndex = backIndex - 1;
if(foreIndex < 0) {
foreIndex = mPages.size()-1;
}
mIndex = foreIndex;
setViews(foreIndex, backIndex);
}
private void setViews(int foreground, int background) {
mForeground = mPages.get(foreground);
mBackground = mPages.get(background);
}
#Override
protected void onDraw(Canvas canvas) {
mCurrentLeft = getLeft();
mCurrentTop = getTop();
if ( !bViewDrawn ) {
bViewDrawn = true;
onFirstDrawEvent(canvas);
}
canvas.drawColor(Color.WHITE);
Rect rect = new Rect();
rect.left = 0;
rect.top = 0;
rect.bottom = getHeight();
rect.right = getWidth();
// First Page render
Paint paint = new Paint();
// Draw our elements
drawForeground(canvas, rect, paint);
drawBackground(canvas, rect, paint);
drawCurlEdge(canvas);
// Draw any debug info once we are done
if ( bEnableDebugMode )
drawDebug(canvas);
// Check if we can re-enable input
if ( bEnableInputAfterDraw )
{
bBlockTouchInput = false;
bEnableInputAfterDraw = false;
}
// Restore canvas
//canvas.restore();
}
protected void onFirstDrawEvent(Canvas canvas) {
mFlipRadius = getWidth();
ResetClipEdge();
DoPageCurl();
}
private void drawForeground( Canvas canvas, Rect rect, Paint paint ) {
canvas.drawBitmap(mForeground, null, rect, paint);
// Draw the page number (first page is 1 in real life :D
// there is no page number 0 hehe)
drawPageNum(canvas, mIndex);
}
private Path createBackgroundPath() {
Path path = new Path();
path.moveTo(mA.x, mA.y);
path.lineTo(mB.x, mB.y);
path.lineTo(mC.x, mC.y);
path.lineTo(mD.x, mD.y);
path.lineTo(mA.x, mA.y);
return path;
}
private void drawBackground( Canvas canvas, Rect rect, Paint paint ) {
Path mask = createBackgroundPath();
// Save current canvas so we do not mess it up
canvas.save();
canvas.clipPath(mask);
canvas.drawBitmap(mBackground, null, rect, paint);
// Draw the page number (first page is 1 in real life :D
// there is no page number 0 hehe)
drawPageNum(canvas, mIndex);
canvas.restore();
}
private Path createCurlEdgePath() {
Path path = new Path();
path.moveTo(mA.x, mA.y);
path.lineTo(mD.x, mD.y);
path.lineTo(mE.x, mE.y);
path.lineTo(mF.x, mF.y);
path.lineTo(mA.x, mA.y);
return path;
}
private void drawCurlEdge( Canvas canvas )
{
Path path = createCurlEdgePath();
canvas.drawPath(path, mCurlEdgePaint);
}
private void drawPageNum(Canvas canvas, int pageNum)
{
mTextPaint.setColor(Color.WHITE);
String pageNumText = "- "+pageNum+" -";
drawCentered(canvas, pageNumText,canvas.getHeight()-mTextPaint.getTextSize()-5,mTextPaint,mTextPaintShadow);
}
public static void drawTextShadowed(Canvas canvas, String text, float x, float y, Paint textPain, Paint shadowPaint) {
canvas.drawText(text, x-1, y, shadowPaint);
canvas.drawText(text, x, y+1, shadowPaint);
canvas.drawText(text, x+1, y, shadowPaint);
canvas.drawText(text, x, y-1, shadowPaint);
canvas.drawText(text, x, y, textPain);
}
public static void drawCentered(Canvas canvas, String text, float y, Paint textPain, Paint shadowPaint)
{
float posx = (canvas.getWidth() - textPain.measureText(text))/2;
drawTextShadowed(canvas, text, posx, y, textPain, shadowPaint);
}
private void drawDebug(Canvas canvas)
{
float posX = 10;
float posY = 20;
Paint paint = new Paint();
paint.setStrokeWidth(5);
paint.setStyle(Style.STROKE);
paint.setColor(Color.BLACK);
canvas.drawCircle(mOrigin.x, mOrigin.y, getWidth(), paint);
paint.setStrokeWidth(3);
paint.setColor(Color.RED);
canvas.drawCircle(mOrigin.x, mOrigin.y, getWidth(), paint);
paint.setStrokeWidth(5);
paint.setColor(Color.BLACK);
canvas.drawLine(mOrigin.x, mOrigin.y, mMovement.x, mMovement.y, paint);
paint.setStrokeWidth(3);
paint.setColor(Color.RED);
canvas.drawLine(mOrigin.x, mOrigin.y, mMovement.x, mMovement.y, paint);
posY = debugDrawPoint(canvas,"A",mA,Color.RED,posX,posY);
posY = debugDrawPoint(canvas,"B",mB,Color.GREEN,posX,posY);
posY = debugDrawPoint(canvas,"C",mC,Color.BLUE,posX,posY);
posY = debugDrawPoint(canvas,"D",mD,Color.CYAN,posX,posY);
posY = debugDrawPoint(canvas,"E",mE,Color.YELLOW,posX,posY);
posY = debugDrawPoint(canvas,"F",mF,Color.LTGRAY,posX,posY);
posY = debugDrawPoint(canvas,"Mov",mMovement,Color.DKGRAY,posX,posY);
posY = debugDrawPoint(canvas,"Origin",mOrigin,Color.MAGENTA,posX,posY);
posY = debugDrawPoint(canvas,"Finger",mFinger,Color.GREEN,posX,posY);
}
private float debugDrawPoint(Canvas canvas, String name, Vector2D point, int color, float posX, float posY) {
return debugDrawPoint(canvas,name+" "+point.toString(),point.x, point.y, color, posX, posY);
}
private float debugDrawPoint(Canvas canvas, String name, float X, float Y, int color, float posX, float posY) {
mTextPaint.setColor(color);
drawTextShadowed(canvas,name,posX , posY, mTextPaint,mTextPaintShadow);
Paint paint = new Paint();
paint.setStrokeWidth(5);
paint.setColor(color);
canvas.drawPoint(X, Y, paint);
return posY+15;
}
}