For my game Hashi I want to build a custom ViewGroup that allows zooming and scrolling (both axes), containing children that are affected on touches as well.
I have set setWillNotDraw(false) and overwritten draw(Canvas) to use canvas.scale(..) and canvas.translate(..):
#Override
public void draw(Canvas canvas)
{
canvas.save();
canvas.scale(mScaleFactor, mScaleFactor, mScaleCenter.x, mScaleCenter.y);
canvas.translate(mTranslation.x * mScaleFactor, mTranslation.y * mScaleFactor);
super.draw(canvas);
canvas.restore();
}
But then I realized, that the MotionEvent parameter of the onTouchEvent for children is offset by the value I set with the translation (so they are detected, as if the container did not have any zoom or offset).
I tried to overwrite dispatchTouchEvent in the parent ViewGroup and adjust the coordinates of the event. But MotionEvent is final and can only be instantiated via MotionEvent.obtain(..). And there is no version of obtain to accept multiple coordinates, when there is more than one finger down.
Is there any way to modify the MotionEvent parameters, so I don't need to do it in the onTouchEvent methods of the child views (that would be much more complex), or even a better approach to implement zoom and scroll?
I implemented my own zoom container because I need to control the touches strictly (children also listen to swipe gestures, but need to be intercepted, when a second finger is down).
Don't try and create a new MotionEvent by providing coordinates.
Copy the existing event and then transform it using a matrix.
e.g. something like
// Matrix used for drawing the view
Matrix matrix = new Matrix();
matrix.setScale(scaleFactor, scaleFactor);
matrix.postTranslate(panX, panY);
// used with canvas.concat(matrix);
// Matrix for Touch events
Matrix mappingMatrix = new Matrix();
// Touch event matrix needs to be inverted
matrix.invert(mappingMatrix);
// Copy the touch Event that is being mapped
MotionEvent transformEvent = MotionEvent.obtain(ev);
// Apply it's event mapping matrix
transformEvent.transform(mappingMatrix);
Obtain copy documentation
I do something very similar in my custom viewgroup for scaling MotionEvents to children in my FixedHeaderTableLayout Library.
The MotionEvent copying is done here
So for those having the same problem I post my solution (thanks again Andrew for your help).
Update
Because I experienced input issues while testing, I decided to use onLayout instead of dispatchTouchEvent and draw methods to apply scale factor and translation, as the old solution did only apply it to the drawing and the input detection, but the container still declared the position of the children in the original place. This was causing espresso to execute the touches in the wrong place. With this solution the children are completely offset, so it also works with testing (and probably other things I did not consider).
Note that both solutions only work, when you embed (an) other layout container(s) that match the parents size.
If you want to change that, you need to change the implementation of onLayout.
public class ZoomContainer extends ViewGroup
{
/**
* Limit the maximum/minimum scrolling to prevent scrolling the container,
* so that no more children are visible.
* The value is relative to the size of the container (so 0.1 means 10% of the size of this container)
*/
protected float mTranslationBounds = 0.1f;
/**
* The translation to be applied to the container
*/
protected PointF mTranslation;
/**
* The the current zoom factor.
* It is initialized with a value smaller than 1, to append some empty space around the view.
*/
protected float mScaleFactor = 0.95f;
/**
* The minimum scale factor to prevent endless zooming
*/
protected float mScaleFactorMin = 0.8f;
/**
* The maximum scale factor to prevent endless zooming.
*/
protected float mScaleFactorMax = 5.0f;
/**
* Used to indicate, whether or not this is the first touch event, which has no differences yet.
*/
protected boolean mIsTouchStarted = false;
/**
* Distance of the fingers from the last touch event
*/
protected float mStartDistance = 0;
/**
* Center of the two fingers from the last touch event.
*/
protected PointF mStartTouchPoint = new PointF(0, 0);
public ZoomContainer(Context context)
{
super(context);
}
public ZoomContainer(Context context, #Nullable AttributeSet attrs)
{
super(context, attrs);
}
public ZoomContainer(Context context, #Nullable AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}
/**
* Cancel all child touch events, if there is more than one finger down.
*/
#Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
boolean intercept = ev.getPointerCount() > 1;
if (intercept)
{
Log.d("TableView", "Intercepted");
mIsTouchStarted = false;
}
return intercept;
}
protected void initializeTranslation()
{
if (mTranslation == null)
{
mTranslation = new PointF(getWidth() * (1 - mScaleFactor) / 2f,
getHeight() * (1 - mScaleFactor) / 2f);
Log.d("TableView", "Translation: " + mTranslation);
}
}
/**
* Calculate the new zoom and scroll respecting the difference to the last touch event.
*/
#Override
public boolean onTouchEvent(MotionEvent event)
{
switch (event.getAction())
{
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mIsTouchStarted = false;
return true;
}
if (event.getPointerCount() <= 1)
{
mIsTouchStarted = false;
return true;
}
float[] currentPointArray = new float[]{event.getX(0),
event.getY(0),
event.getX(1),
event.getY(1)};
float currentFingerDistance = getDistance(currentPointArray[0],
currentPointArray[1],
currentPointArray[2],
currentPointArray[3]);
// Read the current center of the fingers to determine the the new translation
PointF currentPoint = getPoint(currentPointArray[0],
currentPointArray[1],
currentPointArray[2],
currentPointArray[3]);
if (mIsTouchStarted)
{
// 1 / oldScaleFactor - 1 / newScaleFactor is required to respect the relative translation,
// when zooming (translation is always from the upper left corner,
// but zooming should be performed centered to the fingers)
float scaleFactorDifference = 1f / mScaleFactor;
mScaleFactor = getBoundScaleFactor(mScaleFactor + (currentFingerDistance / mStartDistance - 1));
scaleFactorDifference -= 1f / mScaleFactor;
// Add the finger scroll since the last event to the current translation.
PointF newTranslation = new PointF(mTranslation.x + (currentPoint.x - mStartTouchPoint.x) / mScaleFactor,
mTranslation.y + (currentPoint.y - mStartTouchPoint.y) / mScaleFactor);
// Add the current point multiplied with the scale difference to make sure,
// zooming is always done from the center of the fingers. Otherwise zooming would always be
// applied from the upper left edge of the screen.
newTranslation.x -= currentPoint.x * scaleFactorDifference;
newTranslation.y -= currentPoint.y * scaleFactorDifference;
mTranslation = getBoundTranslation(newTranslation);
}
mStartTouchPoint = currentPoint;
mStartDistance = currentFingerDistance;
mIsTouchStarted = true;
requestLayout();
return true;
}
protected float getBoundValue(float value, float min, float max)
{
return Math.min(Math.max(value, min), max);
}
protected PointF getBoundTranslation(PointF translation)
{
translation.x = getBoundValue(translation.x,
-(getWidth() * (mScaleFactor - 1) + getWidth() * mTranslationBounds),
getWidth() * mTranslationBounds);
translation.y = getBoundValue(translation.y,
-(getHeight() * (mScaleFactor - 1) + getHeight() * mTranslationBounds),
getHeight() * mTranslationBounds);
return translation;
}
protected float getBoundScaleFactor(float scaleFactor)
{
return getBoundValue(scaleFactor, mScaleFactorMin, mScaleFactorMax);
}
protected PointF getPoint(float x1, float y1, float x2, float y2)
{
return new PointF(getCenter(x1, x2), getCenter(y1, y2));
}
protected float getCenter(float position1, float position2)
{
return (position1 + position2) / 2f;
}
protected float getDistance(float x1, float y1, float x2, float y2)
{
float distanceX = Math.abs(x1 - x2);
float distanceY = Math.abs(y1 - y2);
return (float) Math.sqrt(Math.pow(distanceX, 2) + Math.pow(distanceY, 2));
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
float width = (r - l);
float height = (b - t);
if (width <= 0 || height <= 0)
{
return;
}
initializeTranslation();
final int childCount = getChildCount();
l = (int) (mTranslation.x * mScaleFactor);
r = (int) ((width + mTranslation.x) * mScaleFactor);
t = (int) (mTranslation.y * mScaleFactor);
b = (int) ((height + mTranslation.y) * mScaleFactor);
for (int i = 0; i < childCount; i++)
{
View child = getChildAt(i);
child.layout(l, t, r, b);
}
}
}
Old solution
Using dispatchTouchEvent and draw methods to apply scale factor and tranlation:
public class ZoomContainer extends ViewGroup
{
/**
* Limit the maximum/minimum scrolling to prevent scrolling the container,
* so that no more children are visible.
* The value is relative to the size of the container (so 0.1 means 10% of the size of this container)
*/
protected float mTranslationBounds = 0.1f;
/**
* The translation to be applyed to the container
*/
protected PointF mTranslation;
/**
* The the current zoom factor. You might initialze it to a value smaller than 1, if you want to add some padding in the beginning.
*/
protected float mScaleFactor = 1f;
/**
* The minimum scale factor to prevent endless zooming
*/
protected float mScaleFactorMin = 0.8f;
/**
* The maximum scale factor to prevent endless zooming.
*/
protected float mScaleFactorMax = 4f;
/**
* Used to indicate, whether or not this is the first touch event, which has no differences yet.
*/
protected boolean mIsTouchStarted = false;
/**
* Distance of the fingers from the last touch event
*/
protected float mStartDistance = 0;
/**
* Center of the two fingers from the last touch event.
*/
protected PointF mStartTouchPoint = new PointF(0, 0);
public ZoomContainer(Context context)
{
super(context);
setWillNotDraw(false);
}
public ZoomContainer(Context context, #Nullable AttributeSet attrs)
{
super(context, attrs);
setWillNotDraw(false);
}
public ZoomContainer(Context context, #Nullable AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
setWillNotDraw(false);
}
#Override
public void draw(Canvas canvas)
{
canvas.save();
canvas.concat(createDrawMatrix());
super.draw(canvas);
canvas.restore();
}
/**
* Cancel all child touch events, if there is more than one finger down.
*/
#Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
boolean intercept = ev.getPointerCount() > 1;
if (intercept)
{
mIsTouchStarted = false;
}
return intercept;
}
/**
* Creates the transformation matrix for drawing and converting touch coordinates
*/
protected Matrix createDrawMatrix()
{
// Make sure children are centered, if initial scroll factor is not 1.
if (mTranslation == null)
{
mTranslation = new PointF(getWidth() * (1 - mScaleFactor) / 2f,
getHeight() * (1 - mScaleFactor) / 2f);
}
// Matrix used for drawing the view
Matrix matrix = new Matrix();
matrix.setScale(mScaleFactor, mScaleFactor);
matrix.postTranslate(mTranslation.x, mTranslation.y);
return matrix;
}
/**
* Transform the touch coordinates according to the current zoom and scroll,
* for children to get the appropriate ones.
*/
#Override
public boolean dispatchTouchEvent(MotionEvent ev)
{
// Matrix for Touch events
Matrix mappingMatrix = new Matrix();
// Touch event matrix needs to be inverted
createDrawMatrix().invert(mappingMatrix);
// Copy the touch Event that is being mapped
MotionEvent transformEvent = MotionEvent.obtain(ev);
// Apply it's event mapping matrix
transformEvent.transform(mappingMatrix);
boolean handled = super.dispatchTouchEvent(transformEvent);
transformEvent.recycle();
return handled;
}
/**
* Calculate the new zoom and scroll respecting the difference to the last touch event.
*/
#Override
public boolean onTouchEvent(MotionEvent event)
{
switch (event.getAction())
{
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mIsTouchStarted = false;
return true;
}
//
if (event.getPointerCount() <= 1)
{
mIsTouchStarted = false;
return true;
}
float[] currentPointArray = new float[]{event.getX(0),
event.getY(0),
event.getX(1),
event.getY(1)};
float currentDistance = mScaleFactor * getDistance(currentPointArray[0],
currentPointArray[1],
currentPointArray[2],
currentPointArray[3]);
Matrix transformation = createDrawMatrix();
transformation.mapPoints(currentPointArray);
PointF currentPoint = getPoint(currentPointArray[0],
currentPointArray[1],
currentPointArray[2],
currentPointArray[3]);
if (mIsTouchStarted)
{
float scaleFactorDifference = mScaleFactor;
mScaleFactor = getBoundScaleFactor(mScaleFactor + (currentDistance / mStartDistance - 1));
scaleFactorDifference -= mScaleFactor;
PointF newTranslation = new PointF(mTranslation.x + currentPoint.x - mStartTouchPoint.x,
mTranslation.y + currentPoint.y - mStartTouchPoint.y);
newTranslation.x += currentPoint.x * scaleFactorDifference;
newTranslation.y += currentPoint.y * scaleFactorDifference;
mTranslation = getBoundTranslation(newTranslation);
}
mStartTouchPoint = currentPoint;
mStartDistance = currentDistance;
mIsTouchStarted = true;
invalidate();
return true;
}
protected float getBoundValue(float value, float min, float max)
{
return Math.min(Math.max(value, min), max);
}
protected PointF getBoundTranslation(PointF translation)
{
translation.x = getBoundValue(translation.x,
-(getWidth() * (mScaleFactor - 1) + getWidth() * mTranslationBounds),
getWidth() * mTranslationBounds);
translation.y = getBoundValue(translation.y,
-(getHeight() * (mScaleFactor - 1) + getHeight() * mTranslationBounds),
getHeight() * mTranslationBounds);
return translation;
}
protected float getBoundScaleFactor(float scaleFactor)
{
return getBoundValue(scaleFactor, mScaleFactorMin, mScaleFactorMax);
}
protected PointF getPoint(float x1, float y1, float x2, float y2)
{
return new PointF(getCenter(x1, x2), getCenter(y1, y2));
}
protected float getCenter(float position1, float position2)
{
return (position1 + position2) / 2f;
}
protected float getDistance(float x1, float y1, float x2, float y2)
{
float distanceX = Math.abs(x1 - x2);
float distanceY = Math.abs(y1 - y2);
return (float) Math.sqrt(Math.pow(distanceX, 2) + Math.pow(distanceY, 2));
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++)
{
View child = getChildAt(i);
child.layout(0, 0, r - l, b - t);
}
}
}
Related
I want to do a "zoomable paint", I mean a paint that I can zoom/zoom out and pan/drag the canvas and then draw on it.
I have a problem that I can't solve: when I draw while the canvas is zoomed, I retrieve the X and Y coordinate and effectively drawing it on the canvas. But these coordinates are not correct because of the zoomed canvas.
I tried to correct these (multiply by (zoomHeigh/screenHeight)) but I can't find a way to retrieve where I must draw on the original/none-zoomed screen
This is my code :
public class PaintView extends View {
public static int BRUSH_SIZE = 20;
public static final int DEFAULT_COLOR = Color.BLACK;
public static final int DEFAULT_BG_COLOR = Color.WHITE;
private static final float TOUCH_TOLERANCE = 4;
private float mX, mY;
private SerializablePath mPath;
private Paint mPaint;
private ArrayList<FingerPath> paths = new ArrayList<>();
private ArrayList<FingerPath> tempPaths = new ArrayList<>();
private int currentColor;
private int backgroundColor = DEFAULT_BG_COLOR;
private int strokeWidth;
private boolean emboss;
private boolean blur;
private boolean eraser;
private MaskFilter mEmboss;
private MaskFilter mBlur;
private Bitmap mBitmap;
private Canvas mCanvas;
private Paint mBitmapPaint = new Paint(Paint.DITHER_FLAG);
public boolean zoomViewActivated = false;
//These two constants specify the minimum and maximum zoom
private static float MIN_ZOOM = 1f;
private static float MAX_ZOOM = 5f;
private float scaleFactor = 1.f;
private ScaleGestureDetector detector;
//These constants specify the mode that we're in
private static int NONE = 0;
private static int DRAG = 1;
private static int ZOOM = 2;
private int mode;
//These two variables keep track of the X and Y coordinate of the finger when it first
//touches the screen
private float startX = 0f;
private float startY = 0f;
//These two variables keep track of the amount we need to translate the canvas along the X
//and the Y coordinate
private float translateX = 0f;
private float translateY = 0f;
//These two variables keep track of the amount we translated the X and Y coordinates, the last time we
//panned.
private float previousTranslateX = 0f;
private float previousTranslateY = 0f;
int currentPositionX = 0;
int currentPositionY = 0;
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
scaleFactor *= detector.getScaleFactor();
scaleFactor = Math.max(MIN_ZOOM, Math.min(scaleFactor, MAX_ZOOM));
return true;
}
}
public ArrayList<FingerPath> getDividedPaths(float i){
for(FingerPath p : this.paths){
Matrix scaleMatrix = new Matrix();
scaleMatrix.setScale(i, i);
p.path.transform(scaleMatrix);
}
return this.paths;
}
public ArrayList<FingerPath> getPaths() {
return paths;
}
public void dividePath(float i) {
for(FingerPath p : this.paths){
Matrix scaleMatrix = new Matrix();
scaleMatrix.setScale(i, i);
p.path.transform(scaleMatrix);
}
}
public void setPaths(ArrayList<FingerPath> paths){
this.paths = paths;
}
public void setStrokeWidth(int value){
strokeWidth = value;
}
public PaintView(Context context) {
this(context, null);
detector = new ScaleGestureDetector(getContext(), new ScaleListener());
}
public PaintView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(DEFAULT_COLOR);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setXfermode(null);
mPaint.setAlpha(0xff);
mEmboss = new EmbossMaskFilter(new float[] {1, 1, 1}, 0.4f, 6, 3.5f);
mBlur = new BlurMaskFilter(5, BlurMaskFilter.Blur.NORMAL);
detector = new ScaleGestureDetector(getContext(), new ScaleListener());
}
public void init(DisplayMetrics metrics) {
int height = metrics.heightPixels;
int width = metrics.widthPixels;
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
currentColor = DEFAULT_COLOR;
strokeWidth = BRUSH_SIZE;
}
public void normal() {
emboss = false;
blur = false;
eraser = false;
}
public void emboss() {
emboss = true;
blur = false;
eraser = false;
}
public void blur() {
emboss = false;
blur = true;
eraser = false;
}
public void eraser() {
eraser = true;
}
public void cancel(){
if(paths.size() != 0){
tempPaths.add(paths.get(paths.size()-1));
paths.remove(paths.size()-1);
invalidate();
}
}
public void redo(){
if(tempPaths.size() != 0){
paths.add(tempPaths.get(tempPaths.size()-1));
tempPaths.remove(tempPaths.size()-1);
invalidate();
}
}
public void clear() {
backgroundColor = DEFAULT_BG_COLOR;
paths.clear();
normal();
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
//We're going to scale the X and Y coordinates by the same amount
canvas.scale(scaleFactor, scaleFactor);
//If translateX times -1 is lesser than zero, let's set it to zero. This takes care of the left bound
if((translateX * -1) < 0) {
translateX = 0;
}
//This is where we take care of the right bound. We compare translateX times -1 to (scaleFactor - 1) * displayWidth.
//If translateX is greater than that value, then we know that we've gone over the bound. So we set the value of
//translateX to (1 - scaleFactor) times the display width. Notice that the terms are interchanged; it's the same
//as doing -1 * (scaleFactor - 1) * displayWidth
else if((translateX * -1) > (scaleFactor - 1) * getWidth()) {
translateX = (1 - scaleFactor) * getWidth();
}
if(translateY * -1 < 0) {
translateY = 0;
}
//We do the exact same thing for the bottom bound, except in this case we use the height of the display
else if((translateY * -1) > (scaleFactor - 1) * getHeight()) {
translateY = (1 - scaleFactor) * getHeight();
}
//We need to divide by the scale factor here, otherwise we end up with excessive panning based on our zoom level
//because the translation amount also gets scaled according to how much we've zoomed into the canvas.
canvas.translate(translateX / scaleFactor, translateY / scaleFactor);
/* The rest of your canvas-drawing code */
mCanvas.drawColor(backgroundColor);
if(paths != null){
for (FingerPath fp : paths) {
mPaint.setColor(fp.color);
mPaint.setStrokeWidth(fp.strokeWidth);
mPaint.setMaskFilter(null);
if (fp.emboss)
mPaint.setMaskFilter(mEmboss);
else if (fp.blur)
mPaint.setMaskFilter(mBlur);
if(fp.eraser) {
mPaint.setColor(DEFAULT_BG_COLOR);
}
mCanvas.drawPath(fp.path, mPaint);
}
}
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
canvas.restore();
}
private void touchStart(float x, float y) {
mPath = new SerializablePath();
FingerPath fp = new FingerPath(currentColor, emboss, blur, eraser, strokeWidth, mPath);
paths.add(fp);
tempPaths = new ArrayList<>();
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
}
private void touchMove(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
private void touchUp() {
mPath.lineTo(mX, mY);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(zoomViewActivated){
boolean dragged = false;
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mode = DRAG;
//We assign the current X and Y coordinate of the finger to startX and startY minus the previously translated
//amount for each coordinates This works even when we are translating the first time because the initial
//values for these two variables is zero.
startX = event.getX() - previousTranslateX;
startY = event.getY() - previousTranslateY;
break;
case MotionEvent.ACTION_MOVE:
translateX = event.getX() - startX;
translateY = event.getY() - startY;
//We cannot use startX and startY directly because we have adjusted their values using the previous translation values.
//This is why we need to add those values to startX and startY so that we can get the actual coordinates of the finger.
double distance = Math.sqrt(Math.pow(event.getX() - (startX + previousTranslateX), 2) +
Math.pow(event.getY() - (startY + previousTranslateY), 2)
);
if(distance > 0) {
dragged = true;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = ZOOM;
break;
case MotionEvent.ACTION_UP:
mode = NONE;
dragged = false;
//All fingers went up, so let's save the value of translateX and translateY into previousTranslateX and
//previousTranslate
previousTranslateX = translateX;
previousTranslateY = translateY;
currentPositionX += previousTranslateX;
currentPositionY += previousTranslateY;
break;
case MotionEvent.ACTION_POINTER_UP:
mode = DRAG;
//This is not strictly necessary; we save the value of translateX and translateY into previousTranslateX
//and previousTranslateY when the second finger goes up
previousTranslateX = translateX;
previousTranslateY = translateY;
break;
}
detector.onTouchEvent(event);
//We redraw the canvas only in the following cases:
//
// o The mode is ZOOM
// OR
// o The mode is DRAG and the scale factor is not equal to 1 (meaning we have zoomed) and dragged is
// set to true (meaning the finger has actually moved)
if ((mode == DRAG && scaleFactor != 1f && dragged) || mode == ZOOM) {
invalidate();
}
}else{
float x = event.getX()*(getHeight()/scaleFactor)/getHeight()+currentPositionX;
float y = event.getY()*(getWidth()/scaleFactor)/getWidth()+currentPositionY;
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN :
touchStart(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE :
touchMove(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP :
touchUp();
invalidate();
break;
}
}
return true;
}
}
I have a CustomView and an Image view. The CustomView is a ball that moves around the screen and bounces off the walls. The Image is a quarter circle that you can rotate in a circle on touch. I am trying to make my game so that when the filled pixels from the CustomView cross paths with the Filled pixels from the ImageView a collision is detected. The problem that I am having is I do not know how to retrieve where the filled pixels are on each view.
Here is my XML code
<com.leytontaylor.bouncyballz.AnimatedView
android:id="#+id/anim_view"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
/>
<ImageView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/quartCircle"
android:layout_gravity="center_horizontal"
android:src="#drawable/quartercircle"
android:scaleType="matrix"/>
My MainActivity
public class MainActivity extends AppCompatActivity {
private static Bitmap imageOriginal, imageScaled;
private static Matrix matrix;
private ImageView dialer;
private int dialerHeight, dialerWidth;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// load the image only once
if (imageOriginal == null) {
imageOriginal = BitmapFactory.decodeResource(getResources(), R.drawable.quartercircle);
}
// initialize the matrix only once
if (matrix == null) {
matrix = new Matrix();
} else {
// not needed, you can also post the matrix immediately to restore the old state
matrix.reset();
}
dialer = (ImageView) findViewById(R.id.quartCircle);
dialer.setOnTouchListener(new MyOnTouchListener());
dialer.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// method called more than once, but the values only need to be initialized one time
if (dialerHeight == 0 || dialerWidth == 0) {
dialerHeight = dialer.getHeight();
dialerWidth = dialer.getWidth();
// resize
Matrix resize = new Matrix();
resize.postScale((float) Math.min(dialerWidth, dialerHeight) / (float) imageOriginal.getWidth(), (float) Math.min(dialerWidth, dialerHeight) / (float) imageOriginal.getHeight());
imageScaled = Bitmap.createBitmap(imageOriginal, 0, 0, imageOriginal.getWidth(), imageOriginal.getHeight(), resize, false);
// translate to the image view's center
float translateX = dialerWidth / 2 - imageScaled.getWidth() / 2;
float translateY = dialerHeight / 2 - imageScaled.getHeight() / 2;
matrix.postTranslate(translateX, translateY);
dialer.setImageBitmap(imageScaled);
dialer.setImageMatrix(matrix);
}
}
});
}
MyOnTouchListener class:
private class MyOnTouchListener implements View.OnTouchListener {
private double startAngle;
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startAngle = getAngle(event.getX(), event.getY());
break;
case MotionEvent.ACTION_MOVE:
double currentAngle = getAngle(event.getX(), event.getY());
rotateDialer((float) (startAngle - currentAngle));
startAngle = currentAngle;
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
}
private double getAngle(double xTouch, double yTouch) {
double x = xTouch - (dialerWidth / 2d);
double y = dialerHeight - yTouch - (dialerHeight / 2d);
switch (getQuadrant(x, y)) {
case 1:
return Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI;
case 2:
return 180 - Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI;
case 3:
return 180 + (-1 * Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI);
case 4:
return 360 + Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI;
default:
return 0;
}
}
/**
* #return The selected quadrant.
*/
private static int getQuadrant(double x, double y) {
if (x >= 0) {
return y >= 0 ? 1 : 4;
} else {
return y >= 0 ? 2 : 3;
}
}
/**
* Rotate the dialer.
*
* #param degrees The degrees, the dialer should get rotated.
*/
private void rotateDialer(float degrees) {
matrix.postRotate(degrees, dialerWidth / 2, dialerHeight / 2);
dialer.setImageMatrix(matrix);
}
And my AnimatedView
public class AnimatedView extends ImageView {
private Context mContext;
int x = -1;
int y = -1;
private int xVelocity = 10;
private int yVelocity = 5;
private Handler h;
private final int FRAME_RATE = 60;
public AnimatedView(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) {
BitmapDrawable ball = (BitmapDrawable) mContext.getResources().getDrawable(R.drawable.smallerball);
if (x<0 && y <0) {
x = this.getWidth()/2;
y = this.getHeight()/2;
} else {
x += xVelocity;
y += yVelocity;
if ((x > this.getWidth() - ball.getBitmap().getWidth()) || (x < 0)) {
xVelocity = xVelocity*-1;
}
if ((y > this.getHeight() - ball.getBitmap().getHeight()) || (y < 0)) {
yVelocity = yVelocity*-1;
}
}
c.drawBitmap(ball.getBitmap(), x, y, null);
h.postDelayed(r, FRAME_RATE);
}
public float getX() {
return x;
}
public float getY() {
return y;
}
}
My question is: How can I retrieve the filled pixels from both of these views, and pass them through a function that detects a collision.
Thanks in advance for the help!:)
You could encapsulate your images with Array of points marking your border coordinates(and update them on move/calculate them based on origin) then you'll be able to decide whether oposing object are in touch or not(if any of oposing arrays share the same point)
You really need to define "filled pixels". I assume you mean the non-transparent pixels. The easiest way to find those, is by converting your entire view into a bitmap and iterating through its pixels. You can convert a View into a Bitmap like this:
private Bitmap getBitmapFromView(View view) {
view.buildDrawingCache();
Bitmap returnedBitmap = Bitmap.createBitmap(view.measuredWidth,
view.measuredHeight,
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(returnedBitmap);
canvas.drawColor(Color.WHITE, PorterDuff.Mode.SRC_IN);
Drawable drawable = view.background;
drawable.draw(canvas);
view.draw(canvas);
return returnedBitmap;
}
You'd also need to get the absolute location of the views:
Point getViewLocationOnScreen(View v) {
int[] coor = new int[]{0, 0};
v.getLocationOnScreen(coor);
return new Point(coor[0], coor[1]);
}
Then you just have to iterate through the pixels of each Bitmap, check their colors to know whether they're "filled", and also check whether they're overlapping based on their coordination inside the bitmaps and the absolute location of the views on screen.
Iterating through a bitmap's is done like this:
for (int x = 0; x < myBitmap.getWidth(); x++) {
for (int y = 0; y < myBitmap.getHeight(); y++) {
int color = myBitmap.getPixel(x, y);
}
}
But I must say, I don't think performing this sort of heavy computations on UI thread is really a good idea. There are dozens of much better ways to detect collisions than pixel-perfect checking. This will probably come out extremely laggy.
I used the hexagon code in this tutorial and created a createHex class (Should I post the code?). The linked web page has used the following code to actually draw the hexagons using the math in createHex:
#Override
public void paint(Graphics g){
for(int j = 0; int j < BOARD_HEIGHT; j++){
for(int i = 0; i < BOARD_HEIGHT; I++){
mCellMetrics.setCellIndex(i, j);
if(mCells[j][i] != 0){
mCellMetrics.computeCorners(mCornersX, mCornersY);
g.setColor((mCells[j][i] == L_ON) ? COLOR.ORANGE):COLOR.GRAY;
g.fillPolygon(mCornersX, mCornersY, NUM_HEX_CORNERS);
g.setColor(COLOR.BLACK)
g.drawPolygon(mCornersX, mCornersY, NUM_HEX_CORNERS);
}
}
}
}
The problem I encountered is that Android does not have a Graphics class that contains all the required methods. I did about an hour and a half of fishing around the android documentation and the closest thing I found was the Path class but it doesn't have the methods I need. I want to use the hexagon code in the linked article at the top but I can't find the equivalent of the graphics class. If there isn't an equivalent, can someone show me how to get the results I want using the linked code?
My question: How can I port the code in the linked article to android?
EDIT:
I decided that it might be helpful to others (and potential answerers) if I included the hexagon code that calculates the sides and whatnot of a hexagon, so here is createHex.java:
package com.rush;
/**
* Uniform hexagonal grid cell's metrics utility class.
*/
public class HexGridCell {
private static final int[] NEIGHBORS_DI = { 0, 1, 1, 0, -1, -1 };
private static final int[][] NEIGHBORS_DJ = {
{ -1, -1, 0, 1, 0, -1 }, { -1, 0, 1, 1, 1, 0 } };
private final int[] CORNERS_DX; // array of horizontal offsets of the cell's corners
private final int[] CORNERS_DY; // array of
vertical offsets of the cell's corners
private final int SIDE;
private int mX = 0; // cell's left coordinate
private int mY = 0; // cell's top coordinate
private int mI = 0; // cell's horizontal grid coordinate
private int mJ = 0; // cell's vertical grid coordinate
/**
* Cell radius (distance from center to one of the corners)
*/
public final int RADIUS;
/**
* Cell height
*/
public final int HEIGHT;
/**
* Cell width
*/
public final int WIDTH;
public static final int NUM_NEIGHBORS = 6;
/**
* #param radius Cell radius (distance from the center to one of the corners)
*/
public HexGridCell(int radius) {
RADIUS = radius;
WIDTH = radius * 2;
HEIGHT = (int) (((float) radius) * Math.sqrt(3));
SIDE = radius * 3 / 2;
int cdx[] = { RADIUS / 2, SIDE, WIDTH, SIDE, RADIUS / 2, 0 };
CORNERS_DX = cdx;
int cdy[] = { 0, 0, HEIGHT / 2, HEIGHT, HEIGHT, HEIGHT / 2 };
CORNERS_DY = cdy;
}
/**
* #return X coordinate of the cell's top left corner.
*/
public int getLeft() {
return mX;
}
/**
* #return Y coordinate of the cell's top left corner.
*/
public int getTop() {
return mY;
}
/**
* #return X coordinate of the cell's center
*/
public int getCenterX() {
return mX + RADIUS;
}
/**
* #return Y coordinate of the cell's center
*/
public int getCenterY() {
return mY + HEIGHT / 2;
}
/**
* #return Horizontal grid coordinate for the cell.
*/
public int getIndexI() {
return mI;
}
/**
* #return Vertical grid coordinate for the cell.
*/
public int getIndexJ() {
return mJ;
}
/**
* #return Horizontal grid coordinate for the given neighbor.
*/
public int getNeighborI(int neighborIdx) {
return mI + NEIGHBORS_DI[neighborIdx];
}
/**
* #return Vertical grid coordinate for the given neighbor.
*/
public int getNeighborJ(int neighborIdx) {
return mJ + NEIGHBORS_DJ[mI % 2][neighborIdx];
}
/**
* Computes X and Y coordinates for all of the cell's 6 corners, clockwise,
* starting from the top left.
*
* #param cornersX Array to fill in with X coordinates of the cell's corners
* #param cornersX Array to fill in with Y coordinates of the cell's corners
*/
public void computeCorners(int[] cornersX, int[] cornersY) {
for (int k = 0; k < NUM_NEIGHBORS; k++) {
cornersX[k] = mX + CORNERS_DX[k];
cornersY[k] = mY + CORNERS_DY[k];
}
}
/**
* Sets the cell's horizontal and vertical grid coordinates.
*/
public void setCellIndex(int i, int j) {
mI = i;
mJ = j;
mX = i * SIDE;
mY = HEIGHT * (2 * j + (i % 2)) / 2;
}
/**
* Sets the cell as corresponding to some point inside it (can be used for
* e.g. mouse picking).
*/
public void setCellByPoint(int x, int y) {
int ci = (int)Math.floor((float)x/(float)SIDE);
int cx = x - SIDE*ci;
int ty = y - (ci % 2) * HEIGHT / 2;
int cj = (int)Math.floor((float)ty/(float)HEIGHT);
int cy = ty - HEIGHT*cj;
if (cx > Math.abs(RADIUS / 2 - RADIUS * cy / HEIGHT)) {
setCellIndex(ci, cj);
} else {
setCellIndex(ci - 1, cj + (ci % 2) - ((cy < HEIGHT / 2) ? 1 :
0));
}
}
}
For an explanation of how the code works please refer to the linked article.
Try this:
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Path;
import android.graphics.Region;
import android.util.AttributeSet;
import android.view.View;
public class HexagonMaskView extends View {
private Path hexagonPath;
private Path hexagonBorderPath;
private float radius;
private float width, height;
private int maskColor;
public HexagonMaskView(Context context) {
super(context);
init();
}
public HexagonMaskView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public HexagonMaskView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
hexagonPath = new Path();
hexagonBorderPath = new Path();
maskColor = 0xFF01FF77;
}
public void setRadius(float r) {
this.radius = r;
calculatePath();
}
public void setMaskColor(int color) {
this.maskColor = color;
invalidate();
}
private void calculatePath() {
float triangleHeight = (float) (Math.sqrt(3) * radius / 2);
float centerX = width/2;
float centerY = height/2;
hexagonPath.moveTo(centerX, centerY + radius);
hexagonPath.lineTo(centerX - triangleHeight, centerY + radius/2);
hexagonPath.lineTo(centerX - triangleHeight, centerY - radius/2);
hexagonPath.lineTo(centerX, centerY - radius);
hexagonPath.lineTo(centerX + triangleHeight, centerY - radius/2);
hexagonPath.lineTo(centerX + triangleHeight, centerY + radius/2);
hexagonPath.moveTo(centerX, centerY + radius);
float radiusBorder = radius - 5;
float triangleBorderHeight = (float) (Math.sqrt(3) * radiusBorder / 2);
hexagonBorderPath.moveTo(centerX, centerY + radiusBorder);
hexagonBorderPath.lineTo(centerX - triangleBorderHeight, centerY + radiusBorder/2);
hexagonBorderPath.lineTo(centerX - triangleBorderHeight, centerY - radiusBorder/2);
hexagonBorderPath.lineTo(centerX, centerY - radiusBorder);
hexagonBorderPath.lineTo(centerX + triangleBorderHeight, centerY - radiusBorder/2);
hexagonBorderPath.lineTo(centerX + triangleBorderHeight, centerY + radiusBorder/2);
hexagonBorderPath.moveTo(centerX, centerY + radiusBorder);
invalidate();
}
#Override
public void onDraw(Canvas c){
super.onDraw(c);
c.clipPath(hexagonBorderPath, Region.Op.DIFFERENCE);
c.drawColor(Color.WHITE);
c.save();
c.clipPath(hexagonPath, Region.Op.DIFFERENCE);
c.drawColor(maskColor);
c.save();
}
// getting the view size and default radius
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = MeasureSpec.getSize(widthMeasureSpec);
height = MeasureSpec.getSize(heightMeasureSpec);
radius = height / 2 - 10;
calculatePath();
}
}
Try this or download demo example.
public static Bitmap getHexagonalCroppedBitmap(Bitmap bitmap, int radius) {
Bitmap finalBitmap;
if (bitmap.getWidth() != radius || bitmap.getHeight() != radius)
finalBitmap = Bitmap.createScaledBitmap(bitmap, radius, radius,
false);
else
finalBitmap = bitmap;
Bitmap output = Bitmap.createBitmap(finalBitmap.getWidth(),
finalBitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);
Paint paint = new Paint();
final Rect rect = new Rect(0, 0, finalBitmap.getWidth(),
finalBitmap.getHeight());
Point point1_draw = new Point(75, 0);
Point point2_draw = new Point(0, 50);
Point point3_draw = new Point(0, 100);
Point point4_draw = new Point(75, 150);
Point point5_draw = new Point(150, 100);
Point point6_draw = new Point(150, 50);
Path path = new Path();
path.moveTo(point1_draw.x, point1_draw.y);
path.lineTo(point2_draw.x, point2_draw.y);
path.lineTo(point3_draw.x, point3_draw.y);
path.lineTo(point4_draw.x, point4_draw.y);
path.lineTo(point5_draw.x, point5_draw.y);
path.lineTo(point6_draw.x, point6_draw.y);
path.close();
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(Color.parseColor("#BAB399"));
canvas.drawPath(path, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(finalBitmap, rect, rect, paint);
return output;
}
pass your image to be cropped to hexagon as bitmap to this function
Bitmap myhexagon = getHexagonalCroppedBitmap(Myimage, yourHexagonalRadius);
this is page-curl view , i want when nextView() is called my TextView get the next string
but I can't use TextView from xml layout it gives me null ,
how I can but buttons and TextView in customView ?
can any one help me to solve this with code ?
I have searched with many tutorials but I get this understanding
activity class :
public class StandaloneExample extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.standalone_example);
}
#Override
public void onDestroy(){
super.onDestroy();
System.gc();
finish();
}
/**
* Set the current orientation to landscape. This will prevent the OS from changing
* the app's orientation.
*/
public void lockOrientationLandscape() {
lockOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
/**
* Set the current orientation to portrait. This will prevent the OS from changing
* the app's orientation.
*/
public void lockOrientationPortrait() {
lockOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
public void lockOrientation( int orientation ) {
setRequestedOrientation(orientation);
}
}
pageCurlView :
public class PageCurlView extends View {
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);
}
}
/**
* Inner class used to make a fixed timed animation of the curl effect.
*/
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);
}
}
/**
* Base
*
* #param context
*/
public PageCurlView(Context context) {
super(context);
init(context);
ResetClipEdge();
}
/**
* Construct the object from an XML file. Valid Attributes:
*
* #see android.view.View#View(android.content.Context,
* android.util.AttributeSet)
*/
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();
}
/**
* Initialize the view
*/
private final void init(Context context) {
LayoutInflater.from(context).inflate(R.layout.standalone_example, null,
true);
// left text view
textViewContent = (TextView) this.findViewById(R.id.textView);
// 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);
hesham = 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 = 120;
mUpdateRate = 66;
mInitialEdgeOffset = 30;
mCurlMode = 2;
// LEGACY PAGE HANDLING!
// Create pages
mPages = new ArrayList<Bitmap>();
mPages.add(BitmapFactory.decodeResource(getResources(),
R.drawable.page1));
mPages.add(BitmapFactory.decodeResource(getResources(),
R.drawable.page2));
// Create some sample images
mForeground = mPages.get(0);
mBackground = mPages.get(1);
}
/**
* Reset points to it's initial clip edge state
*/
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);
}
/**
* Return the context which created use. Can return null if the context has
* been erased.
*/
private Context GetContext() {
return mContext.get();
}
/**
* See if the current curl mode is dynamic
*
* #return TRUE if the mode is CURLMODE_DYNAMIC, FALSE otherwise
*/
public boolean IsCurlModeDynamic() {
return mCurlMode == CURLMODE_DYNAMIC;
}
/**
* Set the curl speed.
*
* #param curlSpeed
* - New speed in px/frame
* #throws IllegalArgumentException
* if curlspeed < 1
*/
public void SetCurlSpeed(int curlSpeed) {
if (curlSpeed < 1)
throw new IllegalArgumentException(
"curlSpeed must be greated than 0");
mCurlSpeed = curlSpeed;
}
/**
* Get the current curl speed
*
* #return int - Curl speed in px/frame
*/
public int GetCurlSpeed() {
return mCurlSpeed;
}
/**
* Set the update rate for the curl animation
*
* #param updateRate
* - Fixed animation update rate in fps
* #throws IllegalArgumentException
* if updateRate < 1
*/
public void SetUpdateRate(int updateRate) {
if (updateRate < 1)
throw new IllegalArgumentException(
"updateRate must be greated than 0");
mUpdateRate = updateRate;
}
/**
* Get the current animation update rate
*
* #return int - Fixed animation update rate in fps
*/
public int GetUpdateRate() {
return mUpdateRate;
}
/**
* Set the initial pixel offset for the curl edge
*
* #param initialEdgeOffset
* - px offset for curl edge
* #throws IllegalArgumentException
* if initialEdgeOffset < 0
*/
public void SetInitialEdgeOffset(int initialEdgeOffset) {
if (initialEdgeOffset < 0)
throw new IllegalArgumentException(
"initialEdgeOffset can not negative");
mInitialEdgeOffset = initialEdgeOffset;
}
/**
* Get the initial pixel offset for the curl edge
*
* #return int - px
*/
public int GetInitialEdgeOffset() {
return mInitialEdgeOffset;
}
/**
* Set the curl mode.
* <p>
* Can be one of the following values:
* </p>
* <table>
* <colgroup align="left" /> <colgroup align="left" />
* <tr>
* <th>Value</th>
* <th>Description</th>
* </tr>
* <tr>
* <td>
* <code>{#link #CURLMODE_SIMPLE com.dcg.pagecurl:CURLMODE_SIMPLE}</code></td>
* <td>Curl target will move only in one axis.</td>
* </tr>
* <tr>
* <td>
* <code>{#link #CURLMODE_DYNAMIC com.dcg.pagecurl:CURLMODE_DYNAMIC}</code></td>
* <td>Curl target will move on both X and Y axis.</td>
* </tr>
* </table>
*
* #see #CURLMODE_SIMPLE
* #see #CURLMODE_DYNAMIC
* #param curlMode
* #throws IllegalArgumentException
* if curlMode is invalid
*/
public void SetCurlMode(int curlMode) {
if (curlMode != CURLMODE_SIMPLE && curlMode != CURLMODE_DYNAMIC)
throw new IllegalArgumentException("Invalid curlMode");
mCurlMode = curlMode;
}
/**
* Return an integer that represents the current curl mode.
* <p>
* Can be one of the following values:
* </p>
* <table>
* <colgroup align="left" /> <colgroup align="left" />
* <tr>
* <th>Value</th>
* <th>Description</th>
* </tr>
* <tr>
* <td>
* <code>{#link #CURLMODE_SIMPLE com.dcg.pagecurl:CURLMODE_SIMPLE}</code></td>
* <td>Curl target will move only in one axis.</td>
* </tr>
* <tr>
* <td>
* <code>{#link #CURLMODE_DYNAMIC com.dcg.pagecurl:CURLMODE_DYNAMIC}</code></td>
* <td>Curl target will move on both X and Y axis.</td>
* </tr>
* </table>
*
* #see #CURLMODE_SIMPLE
* #see #CURLMODE_DYNAMIC
* #return int - current curl mode
*/
public int GetCurlMode() {
return mCurlMode;
}
/**
* Enable debug mode. This will draw a lot of data in the view so you can
* track what is happening
*
* #param bFlag
* - boolean flag
*/
public void SetEnableDebugMode(boolean bFlag) {
bEnableDebugMode = bFlag;
}
/**
* Check if we are currently in debug mode.
*
* #return boolean - If TRUE debug mode is on, FALSE otherwise.
*/
public boolean IsDebugModeEnabled() {
return bEnableDebugMode;
}
/**
* #see android.view.View#measure(int, int)
*/
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int finalWidth, finalHeight;
finalWidth = measureWidth(widthMeasureSpec);
finalHeight = measureHeight(heightMeasureSpec);
setMeasuredDimension(finalWidth, finalHeight);
}
/**
* Determines the width of this view
*
* #param measureSpec
* A measureSpec packed into an int
* #return The width of the view, honoring constraints from measureSpec
*/
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;
}
/**
* Determines the height of this view
*
* #param measureSpec
* A measureSpec packed into an int
* #return The height of the view, honoring constraints from measureSpec
*/
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;
}
/**
* Render the text
*
* #see android.view.View#onDraw(android.graphics.Canvas)
*/
// #Override
// protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
// canvas.drawText(mText, getPaddingLeft(), getPaddingTop() - mAscent,
// mTextPaint);
// }
// ---------------------------------------------------------------
// Curling. This handles touch events, the actual curling
// implementations and so on.
// ---------------------------------------------------------------
#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;
}
/**
* Make sure we never move too much, and make sure that if we move too much
* to add a displacement so that the movement will be still in our radius.
*
* #paramradius - radius form the flip origin
* #param bMaintainMoveDir
* - Cap movement but do not change the current movement
* direction
* #return Corrected point
*/
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;
}
/**
* Execute a step of the flip animation
*/
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();
}
/**
* Do the page curl depending on the methods we are using
*/
private void DoPageCurl() {
if (bFlipping) {
if (IsCurlModeDynamic())
doDynamicCurl();
else
doSimpleCurl();
} else {
if (IsCurlModeDynamic())
doDynamicCurl();
else
doSimpleCurl();
}
}
/**
* Do a simple page curl effect
*/
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;
}
// Now calculate E and F taking into account that the line
// AD is perpendicular to FB and EC. B and C are fixed points.
double angle = Math
.atan((height - mD.y) / (mD.x + mMovement.x - width));
double _cos = Math.cos(2 * angle);
double _sin = Math.sin(2 * angle);
// And get F
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));
}
}
/**
* Calculate the dynamic effect, that one that follows the users finger
*/
private void doDynamicCurl() {
int width = getWidth();
int height = getHeight();
// F will follow the finger, we add a small displacement
// So that we can see the edge
mF.x = width - mMovement.x + 0.1f;
mF.y = height - mMovement.y + 0.1f;
// Set min points
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);
}
}
/**
* Swap between the fore and back-ground.
*/
#Deprecated
private void SwapViews() {
Bitmap temp = mForeground;
mForeground = mBackground;
mBackground = temp;
}
/**
* Swap to next view
*/
private void nextView() {
MySQLiteHelper SqlLiteInstance = new MySQLiteHelper(hesham);
SqlLiteInstance.insertForTest("تايتل", "لبلب", "ثثث");
SqlLiteInstance.insertForTest("تايتل التاني", "5555", "5555");
SqlLiteInstance.insertForTest("التالت", "66666", "66666");
int foreIndex = mIndex + 1;
Cursor myDataBase = SqlLiteInstance.getCurrentPageData(1);
if (myDataBase.moveToFirst() && myDataBase.getCount() >= 1) {
do {
textViewContent.setText(myDataBase.getString(0));
} while (myDataBase.moveToNext());
}
if (foreIndex >= mPages.size()) {
foreIndex = 0;
}
int backIndex = foreIndex + 1;
if (backIndex >= mPages.size()) {
backIndex = 0;
}
mIndex = foreIndex;
setViews(foreIndex, backIndex);
}
/**
* Swap to previous view
*/
private void previousView() {
int backIndex = mIndex;
int foreIndex = backIndex - 1;
if (foreIndex < 0) {
foreIndex = mPages.size() - 1;
}
mIndex = foreIndex;
setViews(foreIndex, backIndex);
}
/**
* Set current fore and background
*
* #param foreground
* - Foreground view index
* #param background
* - Background view index
*/
private void setViews(int foreground, int background) {
mForeground = mPages.get(foreground);
mBackground = mPages.get(background);
}
// ---------------------------------------------------------------
// Drawing methods
// ---------------------------------------------------------------
#Override
protected void onDraw(Canvas canvas) {
// Always refresh offsets
mCurrentLeft = getLeft();
mCurrentTop = getTop();
// Translate the whole canvas
// canvas.translate(mCurrentLeft, mCurrentTop);
// We need to initialize all size data when we first draw the view
if (!bViewDrawn) {
bViewDrawn = true;
onFirstDrawEvent(canvas);
}
canvas.drawColor(Color.WHITE);
// Curl pages
// DoPageCurl();
// TODO: This just scales the views to the current
// width and height. We should add some logic for:
// 1) Maintain aspect ratio
// 2) Uniform scale
// 3) ...
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();
}
/**
* Called on the first draw event of the view
*
* #param canvas
*/
protected void onFirstDrawEvent(Canvas canvas) {
mFlipRadius = getWidth();
ResetClipEdge();
DoPageCurl();
}
/**
* Draw the foreground
*
* #param canvas
* #param rect
* #param paint
*/
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);
}
/**
* Create a Path used as a mask to draw the background page
*
* #return
*/
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;
}
/**
* Draw the background image.
*
* #param canvas
* #param rect
* #param paint
*/
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();
}
/**
* Creates a path used to draw the curl edge in.
*
* #return
*/
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;
}
/**
* Draw the curl page edge
*
* #param canvas
*/
private void drawCurlEdge(Canvas canvas) {
Path path = createCurlEdgePath();
canvas.drawPath(path, mCurlEdgePaint);
}
/**
* Draw page num (let this be a bit more custom)
*
* #param canvas
* #param pageNum
*/
private void drawPageNum(Canvas canvas, int pageNum) {
mTextPaint.setColor(Color.WHITE);
String pageNumText = "- " + pageNum + " -";
drawCentered(canvas, pageNumText,
canvas.getHeight() - mTextPaint.getTextSize() - 5, mTextPaint,
mTextPaintShadow);
}
// ---------------------------------------------------------------
// Debug draw methods
// ---------------------------------------------------------------
/**
* Draw a text with a nice shadow
*/
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);
}
/**
* Draw a text with a nice shadow centered in the X axis
*
* #param canvas
* #param text
* #param y
* #param textPain
* #param shadowPaint
*/
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);
}
/**
* Draw debug info
*
* #param canvas
*/
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);
// Draw some curl stuff (Just some test)
/*
* canvas.save(); Vector2D center = new
* Vector2D(getWidth()/2,getHeight()/2);
* //canvas.rotate(315,center.x,center.y);
*
* // Test each lines //float radius = mA.distance(mD)/2.f; //float
* radius = mA.distance(mE)/2.f; float radius = mA.distance(mF)/2.f;
* //float radius = 10; float reduction = 4.f; RectF oval = new RectF();
* oval.top = center.y-radius/reduction; oval.bottom =
* center.y+radius/reduction; oval.left = center.x-radius; oval.right =
* center.x+radius; canvas.drawArc(oval, 0, 360, false, paint);
* canvas.restore(); /*
*/
}
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;
}
}
xml code :
<RelativeLayout
android:id="#+id/game_layout"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.mystictreegames.pagecurl.PageCurlView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/dcgpagecurlPageCurlView1"
android:background="#drawable/facebook">
</com.mystictreegames.pagecurl.PageCurlView>
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_centerVertical="true"
android:text="جوهر كون المرء انه انسان لا يسعى الى الكمال"/>
log error:
FATAL EXCEPTION: main
E/AndroidRuntime(2497): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.mystictreegames.pagecurl/com.mystictreegames.pagecurl.StandaloneExample}: android.view.InflateException: Binary XML file line #13: Error inflating class com.mystictreegames.pagecurl.PageCurlView
In the init() of the PageCurlView class, you are inflating R.layout.standalone_example. Objects are added in a circular manner (R.layout.standalone_example has PageCurlView which has R.layout.standalone_example which has ...).
I have a native library which is invoked by the JNI function called from UI. It runs just for a one time, it execute one command and in second run it just exits. I get the following print.
D/Zygote ( 111): Process 921 terminated by signal (2)
What does signal (2) mean? Can I infer something out of this message as to why the process was terminated? The native library works completely fine with a first execute.
My second question is about the accelerometer:
package com.example.android.accelerometerplay;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.BitmapFactory.Options;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
/**
* This is an example of using the accelerometer to integrate the device's
* acceleration to a position using the Verlet method. This is illustrated with
* a very simple particle system comprised of a few iron balls freely moving on
* an inclined wooden table. The inclination of the virtual table is controlled
* by the device's accelerometer.
*
* #see SensorManager
* #see SensorEvent
* #see Sensor
*/
public class AccelerometerPlayActivity extends Activity {
private SimulationView mSimulationView;
private SensorManager mSensorManager;
private PowerManager mPowerManager;
private WindowManager mWindowManager;
private Display mDisplay;
private WakeLock mWakeLock;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Get an instance of the SensorManager
mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
// Get an instance of the PowerManager
mPowerManager = (PowerManager) getSystemService(POWER_SERVICE);
// Get an instance of the WindowManager
mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
mDisplay = mWindowManager.getDefaultDisplay();
// Create a bright wake lock
mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, getClass()
.getName());
// instantiate our simulation view and set it as the activity's content
mSimulationView = new SimulationView(this);
setContentView(mSimulationView);
}
#Override
protected void onResume() {
super.onResume();
/*
* when the activity is resumed, we acquire a wake-lock so that the
* screen stays on, since the user will likely not be fiddling with the
* screen or buttons.
*/
mWakeLock.acquire();
// Start the simulation
mSimulationView.startSimulation();
}
#Override
protected void onPause() {
super.onPause();
/*
* When the activity is paused, we make sure to stop the simulation,
* release our sensor resources and wake locks
*/
// Stop the simulation
mSimulationView.stopSimulation();
// and release our wake-lock
mWakeLock.release();
}
class SimulationView extends View implements SensorEventListener {
// diameter of the balls in meters
private static final float sBallDiameter = 0.004f;
private static final float sBallDiameter2 = sBallDiameter * sBallDiameter;
// friction of the virtual table and air
private static final float sFriction = 0.1f;
private Sensor mAccelerometer;
private long mLastT;
private float mLastDeltaT;
private float mXDpi;
private float mYDpi;
private float mMetersToPixelsX;
private float mMetersToPixelsY;
private Bitmap mBitmap;
private Bitmap mWood;
private float mXOrigin;
private float mYOrigin;
private float mSensorX;
private float mSensorY;
private long mSensorTimeStamp;
private long mCpuTimeStamp;
private float mHorizontalBound;
private float mVerticalBound;
private final ParticleSystem mParticleSystem = new ParticleSystem();
/*
* Each of our particle holds its previous and current position, its
* acceleration. for added realism each particle has its own friction
* coefficient.
*/
class Particle {
private float mPosX;
private float mPosY;
private float mAccelX;
private float mAccelY;
private float mLastPosX;
private float mLastPosY;
private float mOneMinusFriction;
Particle() {
// make each particle a bit different by randomizing its
// coefficient of friction
final float r = ((float) Math.random() - 0.5f) * 0.2f;
mOneMinusFriction = 1.0f - sFriction + r;
}
public void computePhysics(float sx, float sy, float dT, float dTC) {
// Force of gravity applied to our virtual object
final float m = 1000.0f; // mass of our virtual object
final float gx = -sx * m;
final float gy = -sy * m;
/*
* ·F = mA <=> A = ·F / m We could simplify the code by
* completely eliminating "m" (the mass) from all the equations,
* but it would hide the concepts from this sample code.
*/
final float invm = 1.0f / m;
final float ax = gx * invm;
final float ay = gy * invm;
/*
* Time-corrected Verlet integration The position Verlet
* integrator is defined as x(t+Æt) = x(t) + x(t) - x(t-Æt) +
* a(t)Ætö2 However, the above equation doesn't handle variable
* Æt very well, a time-corrected version is needed: x(t+Æt) =
* x(t) + (x(t) - x(t-Æt)) * (Æt/Æt_prev) + a(t)Ætö2 We also add
* a simple friction term (f) to the equation: x(t+Æt) = x(t) +
* (1-f) * (x(t) - x(t-Æt)) * (Æt/Æt_prev) + a(t)Ætö2
*/
final float dTdT = dT * dT;
final float x = mPosX + mOneMinusFriction * dTC * (mPosX - mLastPosX) + mAccelX
* dTdT;
final float y = mPosY + mOneMinusFriction * dTC * (mPosY - mLastPosY) + mAccelY
* dTdT;
mLastPosX = mPosX;
mLastPosY = mPosY;
mPosX = x;
mPosY = y;
mAccelX = ax;
mAccelY = ay;
}
/*
* Resolving constraints and collisions with the Verlet integrator
* can be very simple, we simply need to move a colliding or
* constrained particle in such way that the constraint is
* satisfied.
*/
public void resolveCollisionWithBounds() {
final float xmax = mHorizontalBound;
final float ymax = mVerticalBound;
final float x = mPosX;
final float y = mPosY;
if (x > xmax) {
mPosX = xmax;
} else if (x < -xmax) {
mPosX = -xmax;
}
if (y > ymax) {
mPosY = ymax;
} else if (y < -ymax) {
mPosY = -ymax;
}
}
}
/*
* A particle system is just a collection of particles
*/
class ParticleSystem {
static final int NUM_PARTICLES = 15;
private Particle mBalls[] = new Particle[NUM_PARTICLES];
ParticleSystem() {
/*
* Initially our particles have no speed or acceleration
*/
for (int i = 0; i < mBalls.length; i++) {
mBalls[i] = new Particle();
}
}
/*
* Update the position of each particle in the system using the
* Verlet integrator.
*/
private void updatePositions(float sx, float sy, long timestamp) {
final long t = timestamp;
if (mLastT != 0) {
final float dT = (float) (t - mLastT) * (1.0f / 1000000000.0f);
if (mLastDeltaT != 0) {
final float dTC = dT / mLastDeltaT;
final int count = mBalls.length;
for (int i = 0; i < count; i++) {
Particle ball = mBalls[i];
ball.computePhysics(sx, sy, dT, dTC);
}
}
mLastDeltaT = dT;
}
mLastT = t;
}
/*
* Performs one iteration of the simulation. First updating the
* position of all the particles and resolving the constraints and
* collisions.
*/
public void update(float sx, float sy, long now) {
// update the system's positions
updatePositions(sx, sy, now);
// We do no more than a limited number of iterations
final int NUM_MAX_ITERATIONS = 10;
/*
* Resolve collisions, each particle is tested against every
* other particle for collision. If a collision is detected the
* particle is moved away using a virtual spring of infinite
* stiffness.
*/
boolean more = true;
final int count = mBalls.length;
for (int k = 0; k < NUM_MAX_ITERATIONS && more; k++) {
more = false;
for (int i = 0; i < count; i++) {
Particle curr = mBalls[i];
for (int j = i + 1; j < count; j++) {
Particle ball = mBalls[j];
float dx = ball.mPosX - curr.mPosX;
float dy = ball.mPosY - curr.mPosY;
float dd = dx * dx + dy * dy;
// Check for collisions
if (dd <= sBallDiameter2) {
/*
* add a little bit of entropy, after nothing is
* perfect in the universe.
*/
dx += ((float) Math.random() - 0.5f) * 0.0001f;
dy += ((float) Math.random() - 0.5f) * 0.0001f;
dd = dx * dx + dy * dy;
// simulate the spring
final float d = (float) Math.sqrt(dd);
final float c = (0.5f * (sBallDiameter - d)) / d;
curr.mPosX -= dx * c;
curr.mPosY -= dy * c;
ball.mPosX += dx * c;
ball.mPosY += dy * c;
more = true;
}
}
/*
* Finally make sure the particle doesn't intersects
* with the walls.
*/
curr.resolveCollisionWithBounds();
}
}
}
public int getParticleCount() {
return mBalls.length;
}
public float getPosX(int i) {
return mBalls[i].mPosX;
}
public float getPosY(int i) {
return mBalls[i].mPosY;
}
}
public void startSimulation() {
/*
* It is not necessary to get accelerometer events at a very high
* rate, by using a slower rate (SENSOR_DELAY_UI), we get an
* automatic low-pass filter, which "extracts" the gravity component
* of the acceleration. As an added benefit, we use less power and
* CPU resources.
*/
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_UI);
}
public void stopSimulation() {
mSensorManager.unregisterListener(this);
}
public SimulationView(Context context) {
super(context);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
mXDpi = metrics.xdpi;
mYDpi = metrics.ydpi;
mMetersToPixelsX = mXDpi / 0.0254f;
mMetersToPixelsY = mYDpi / 0.0254f;
// rescale the ball so it's about 0.5 cm on screen
Bitmap ball = BitmapFactory.decodeResource(getResources(), R.drawable.ball);
final int dstWidth = (int) (sBallDiameter * mMetersToPixelsX + 0.5f);
final int dstHeight = (int) (sBallDiameter * mMetersToPixelsY + 0.5f);
mBitmap = Bitmap.createScaledBitmap(ball, dstWidth, dstHeight, true);
Options opts = new Options();
opts.inDither = true;
opts.inPreferredConfig = Bitmap.Config.RGB_565;
mWood = BitmapFactory.decodeResource(getResources(), R.drawable.wood, opts);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// compute the origin of the screen relative to the origin of
// the bitmap
mXOrigin = (w - mBitmap.getWidth()) * 0.5f;
mYOrigin = (h - mBitmap.getHeight()) * 0.5f;
mHorizontalBound = ((w / mMetersToPixelsX - sBallDiameter) * 0.5f);
mVerticalBound = ((h / mMetersToPixelsY - sBallDiameter) * 0.5f);
}
#Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER)
return;
/*
* record the accelerometer data, the event's timestamp as well as
* the current time. The latter is needed so we can calculate the
* "present" time during rendering. In this application, we need to
* take into account how the screen is rotated with respect to the
* sensors (which always return data in a coordinate space aligned
* to with the screen in its native orientation).
*/
switch (mDisplay.getRotation()) {
case Surface.ROTATION_0:
mSensorX = event.values[0];
mSensorY = event.values[1];
break;
case Surface.ROTATION_90:
mSensorX = -event.values[1];
mSensorY = event.values[0];
break;
case Surface.ROTATION_180:
mSensorX = -event.values[0];
mSensorY = -event.values[1];
break;
case Surface.ROTATION_270:
mSensorX = event.values[1];
mSensorY = -event.values[0];
break;
}
mSensorTimeStamp = event.timestamp;
mCpuTimeStamp = System.nanoTime();
}
#Override
protected void onDraw(Canvas canvas) {
/*
* draw the background
*/
canvas.drawBitmap(mWood, 0, 0, null);
/*
* compute the new position of our object, based on accelerometer
* data and present time.
*/
final ParticleSystem particleSystem = mParticleSystem;
final long now = mSensorTimeStamp + (System.nanoTime() - mCpuTimeStamp);
final float sx = mSensorX;
final float sy = mSensorY;
particleSystem.update(sx, sy, now);
final float xc = mXOrigin;
final float yc = mYOrigin;
final float xs = mMetersToPixelsX;
final float ys = mMetersToPixelsY;
final Bitmap bitmap = mBitmap;
final int count = particleSystem.getParticleCount();
for (int i = 0; i < count; i++) {
/*
* We transform the canvas so that the coordinate system matches
* the sensors coordinate system with the origin in the center
* of the screen and the unit is the meter.
*/
final float x = xc + particleSystem.getPosX(i) * xs;
final float y = yc - particleSystem.getPosY(i) * ys;
canvas.drawBitmap(bitmap, x, y, null);
}
// and make sure to redraw asap
invalidate();
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
}
}
How to move ball?
2 is SIGINT (Interrupt), you can check it in header file within your ndk directory