I have been developing a board game using hexagon boards in android, but when I move the board, the whole 'map' or dozens of entities starts to lag. I believe that it is the cause of using a for-loop, but I have no idea how to fix it. Im using the method of threaded drawing, dont know if it affects though.
I can clearly see lags on my cell phone when moving through the board.
My Game class:
public class Game extends SurfaceView implements SurfaceHolder.Callback {
// Game Vars
int mapWidth = 150, mapHeight = 150;
public static double hexagonSideLength = 80;
public static double cellWidth = 2 * hexagonSideLength, cellHeight = Math
.sqrt(3) * hexagonSideLength;
public static double downwardShift = (0.5) * (cellHeight);
public static double rightShift = hexagonSideLength / 2;
public static int boundaryWidth = 0, boundaryHeight = 0;
public static int error = (int) ((hexagonSideLength) * (0.06));
public static int buffer = 2;
public static int draws = 0;
// Touch Handle
// Offset to the upper left corner of the map
private int xOffset = 0;
private int yOffset = 0;
// last touch point
private int _xTouch = 0;
private int _yTouch = 0;
// scrolling active?
private boolean _isMoving = false;
public Cell[][] map;
private GameThread thread;
static String TAG = Game.class.getSimpleName();
Bitmap image[] = new Bitmap[100];
Paint paint;
public Game(Context context) {
super(context);
Log.i(TAG, "Loaded Game");
// adding the callback (this) to the surface holder to intercept events
getHolder().addCallback(this);
// make the GamePanel focusable so it can handle events
thread = new GameThread(getHolder(), this);
map = new Cell[mapWidth][mapHeight]; // Create new Map
boolean isShiftedDownwards = false;
for (int i = 0; i < mapWidth; i++) {
for (int j = 0; j < mapHeight; j++) {
map[i][j] = new Cell(j, i, isShiftedDownwards);
}
if (isShiftedDownwards == true)
isShiftedDownwards = false;
else
isShiftedDownwards = true;
}
if (mapWidth % 2 != 0) {
boundaryWidth = (int) ((((mapWidth - 1) / 2) * hexagonSideLength)
+ ((mapWidth / 2) * cellWidth) + (2) * hexagonSideLength);
} else {
boundaryWidth = (int) (((((mapWidth - 1) / 2) * hexagonSideLength) + ((mapWidth / 2) * cellWidth)) + (1.5) * hexagonSideLength);
}
boundaryHeight = (int) (mapHeight * cellHeight + 0.5 * cellHeight);
setFocusable(true);
image[0] = Bitmap.createBitmap(BitmapFactory.decodeResource(
this.getResources(), R.drawable.hexagonrgb));
image[1] = Bitmap.createScaledBitmap(image[0], (int) cellWidth + error,
(int) cellHeight + error, false);
Log.i(TAG, "Got Resources");
paint = new Paint();
Log.i(TAG, "Prepared paint");
}
// called every Frame
#Override
protected void onDraw(Canvas canvas) {
Log.i(TAG, "onDraw Called");
// BG
canvas.drawColor(Color.BLACK);
// Resize
// Redraw Map
draws = 0;
for (int column = updateArea(0); column < updateArea(1); column++) {
for (int row = updateArea(2); row < updateArea(3); row++) {
canvas.drawBitmap(image[1], map[column][row].x - xOffset,
map[column][row].y - yOffset, paint);
paint.setColor(Color.WHITE);
canvas.drawText(row + "," + column, map[column][row].x
- xOffset + 80, map[column][row].y - yOffset + 80,
paint);
draws++;
}
}
/** Log */
paint.setColor(Color.WHITE);
paint.setTextSize(20);
canvas.drawText("xOffset: " + xOffset, 0, 30, paint);
canvas.drawText("yOffset: " + yOffset, 0, 50, paint);
canvas.drawText("activeTitlesX: " + updateArea(0) + " - "
+ updateArea(1), 0, 70, paint);
canvas.drawText("activeTitlesY: " + updateArea(2) + " - "
+ updateArea(3), 0, 90, paint);
canvas.drawText("DimX: " + MainActivity.DimX, 0, 110, paint);
canvas.drawText("DimY: " + MainActivity.DimY, 0, 130, paint);
canvas.drawText("Draws: " + draws, 0, 150, paint);
Log.i(TAG, "Cleared canvas");
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
// called by thread
public static void update() {
Log.i(TAG, "Updated Game");
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
thread.setRunning(true);
thread.start();
Log.i(TAG, "Thread Started");
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
// try again shutting down the thread
}
}
Log.i(TAG, "Thread Destroyed");
}
#Override
public boolean onTouchEvent(MotionEvent event) {
// touch down
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// start of a new event, reset the flag
_isMoving = false;
// store the current touch coordinates for scroll calculation
_xTouch = (int) event.getX();
_yTouch = (int) event.getY();
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
// touch starts moving, set the flag
_isMoving = true;
// get the new offset
xOffset += _xTouch - (int) event.getX();
yOffset += _yTouch - (int) event.getY();
// secure, that the offset is never out of view bounds
if (xOffset < 0) {
xOffset = 0;
} else if (xOffset > boundaryWidth - getWidth()) {
xOffset = boundaryWidth - getWidth();
}
if (yOffset < 0) {
yOffset = 0;
} else if (yOffset > boundaryHeight - getHeight()) {
yOffset = boundaryHeight - getHeight();
}
// store the last position
_xTouch = (int) event.getX();
_yTouch = (int) event.getY();
} else if (event.getAction() == MotionEvent.ACTION_UP) {
/*
* // touch released if (!_isMoving) { // calculate the touched cell
* int column = (int) Math.ceil((_xOffset + event.getX()) /
* _cellSize) - 1; int row = (int) Math.ceil((_yOffset +
* event.getY()) / _cellSize) - 1; Cell cell =
* _mapCells.get(row).get(column); // show the id of the touched
* cell Toast.makeText(getContext(), "Cell id #" + cell._id,
* Toast.LENGTH_SHORT).show(); }
*/
}
return true;
}
public int updateArea(int i) {
switch (i) {
case 0: // Left
return Math.max(
(int) (xOffset / (cellWidth - rightShift)) - buffer, 0);
case 1: // Right
return Math
.min((int) (xOffset / (cellWidth - rightShift) + (int) (MainActivity.DimX / (cellWidth - rightShift)))
+ buffer, mapWidth);
case 2: // Up
return Math.max((int) (yOffset / cellHeight) - buffer, 0);
case 3: // Down
return Math.min(((int) (yOffset / cellHeight))
+ (int) (MainActivity.DimY / cellHeight) + buffer,
mapHeight);
}
return 0;
}
/* CELL CLASS */
class Cell {
int x, y;
boolean isShiftedDown;
public Cell(int row, int col, boolean isShifted) {
if (col % 2 != 0) {
isShiftedDown = true;
y = (int) (row * Game.cellHeight + Game.downwardShift);
x = (int) (col * Game.cellWidth - col * Game.rightShift);
Log.i("Shift", "Shifted" + Game.downwardShift);
} else {
isShiftedDown = false;
y = (int) (row * Game.cellHeight);
x = (int) (col * Game.cellWidth - col * Game.rightShift);
Log.i("Shift", "Not Shifted");
}
}
}
}
Thanks in Advance :)
Basic algorithmic analysis shows you are doing this operation:
map[i][j] = new Cell(j, i, isShiftedDownwards);
22500 times. Do you really need to create a new object each time, or can you just change a variable in an existing object?
Update:
for (int column = updateArea(0); column < updateArea(1); column++) {
for (int row = updateArea(2); row < updateArea(3); row++) {
canvas.drawBitmap(image[1], map[column][row].x - xOffset,
map[column][row].y - yOffset, paint);
paint.setColor(Color.WHITE);
canvas.drawText(row + "," + column, map[column][row].x
- xOffset + 80, map[column][row].y - yOffset + 80,
paint);
draws++;
}
}
So this is the double for-loop in question. I'm going to run with the assumption that this needs to be run as many times as it runs. Can the function calls in the loop statements be reduced to variables prior to the loop (they are evaluated with each loop)? This paint.setColor(Color.WHITE); should not be called on each iteration. If you need to change to white after the very first draw, just make a second paint object on startup and and change it before you enter the loop.
If this doesn't help, you need to determine if you can iterate over a smaller area.
Update 2
Rewrite your for loops like so
int minCol = updateArea(0);
int maxCol = updateArea(1);
int minRow = updateArea(2);
int maxRow = updateArea(3);
for(int column = minCol; column < maxCol; column++)
for (int row = minRow; column < maxRow; row++)
....
This will represent a significant reduction in the operations in each iteration.
Related
I'm developing a game using SurfaceView which listens to touch events. The onTouchEvent method in SurfaceView works fine for many of the devices, but in some devices, sometimes it doesn't get called (Moto X Style is the one) and my app also stops responding.
I guess that this might be due to the overloading of main thread due to which onTouchEvent is starving.
Could some Android experts over here give me some tips to reduce the load on main thread if it's getting overloaded, or there might be some other reason which may cause this
The code is quite complex but still I'm posting some if you want to go through it
GameLoopThread
public class GameLoopThread extends Thread{
private GameView view;
// desired fps
private final static int MAX_FPS = 120;
// maximum number of frames to be skipped
private final static int MAX_FRAME_SKIPS = 5;
// the frame period
private final static int FRAME_PERIOD = 1000 / MAX_FPS;
private boolean running = false;
public GameLoopThread(GameView view){
this.view = view;
}
public void setRunning(boolean running){
this.running = running;
}
public boolean isRunning() {
return running;
}
#Override
public void run() {
Canvas canvas;
long beginTime; // the time when the cycle begun
long timeDiff; // the time it took for the cycle to execute
int sleepTime; // ms to sleep (<0 if we're behind)
int framesSkipped; // number of frames being skipped
while (running) {
canvas = null;
// try locking the canvas for exclusive pixel editing
// in the surface
try {
canvas = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
beginTime = System.nanoTime();
framesSkipped = 0; // resetting the frames skipped
// update game state
// render state to the screen
// draws the canvas on the panel
this.view.draw(canvas);
// calculate how long did the cycle take
timeDiff = System.nanoTime() - beginTime;
// calculate sleep time
sleepTime = (int)(FRAME_PERIOD - timeDiff/1000000);
if (sleepTime > 0) {
// if sleepTime > 0 we're OK
try {
// send the thread to sleep for a short period
// very useful for battery saving
Thread.sleep(sleepTime);
} catch (InterruptedException e) {}
}
while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {
// update without rendering
// add frame period to check if in next frame
sleepTime += FRAME_PERIOD;
framesSkipped++;
}
}
}
finally {
// in case of an exception the surface is not left in
// an inconsistent state
view.getHolder().unlockCanvasAndPost(canvas);
} // end finally
}
}
}
GameView
public class GameView extends SurfaceView {
ArrayList<Bitmap> circles = new ArrayList<>();
int color;
public static boolean isGameOver;
public GameLoopThread gameLoopThread;
Circle circle; // Code for Circle class is provided below
public static int score = 0;
public static int stars = 0;
final Handler handler = new Handler();
int remainingTime;
boolean oneTimeFlag;
Bitmap replay;
Bitmap home;
Bitmap star;
int highScore;
boolean isLeaving;
public GameView(Context context, ArrayList<Bitmap> circles, int color) {
super(context);
this.circles = circles;
this.color = color;
oneTimeFlag = true;
gameLoopThread = new GameLoopThread(GameView.this);
getHolder().addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceCreated(SurfaceHolder holder) {
if (!gameLoopThread.isRunning()) {
gameLoopThread.setRunning(true);
gameLoopThread.start();
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
gameLoopThread.setRunning(false);
gameLoopThread = new GameLoopThread(GameView.this);
}
});
initializeCircles();
if(!gameLoopThread.isRunning()) {
gameLoopThread.setRunning(true);
gameLoopThread.start();
}
}
public void initializeCircles() {
ArrayList<String> numbers = new ArrayList<>();
for(int i=0;i<10;i++)
numbers.add(i+"");
Random random = new Random();
int position = random.nextInt(4);
numbers.remove(color + "");
int p1 = position;
int r1 = Integer.valueOf(numbers.get(random.nextInt(9)));
numbers.remove(r1+"");
int r2 = Integer.valueOf(numbers.get(random.nextInt(8)));
numbers.remove(r2 + "");
int r3 = Integer.valueOf(numbers.get(random.nextInt(7)));
ArrayList<Bitmap> bitmaps = new ArrayList<>();
if(position == 0) {
bitmaps.add(circles.get(color));
bitmaps.add(circles.get(r1));
bitmaps.add(circles.get(r2));
bitmaps.add(circles.get(r3));
}
else if(position == 1) {
bitmaps.add(circles.get(r1));
bitmaps.add(circles.get(color));
bitmaps.add(circles.get(r2));
bitmaps.add(circles.get(r3));
}
else if(position == 2) {
bitmaps.add(circles.get(r1));
bitmaps.add(circles.get(r2));
bitmaps.add(circles.get(color));
bitmaps.add(circles.get(r3));
}
else {
bitmaps.add(circles.get(r1));
bitmaps.add(circles.get(r2));
bitmaps.add(circles.get(r3));
bitmaps.add(circles.get(color));
}
numbers = new ArrayList<>();
for(int i=0;i<10;i++)
numbers.add(i+"");
position = random.nextInt(4);
numbers.remove(color + "");
r1 = Integer.valueOf(numbers.get(random.nextInt(9)));
numbers.remove(r1 + "");
r2 = Integer.valueOf(numbers.get(random.nextInt(8)));
numbers.remove(r2 + "");
r3 = Integer.valueOf(numbers.get(random.nextInt(7)));
if(position == 0) {
bitmaps.add(circles.get(color));
bitmaps.add(circles.get(r1));
bitmaps.add(circles.get(r2));
bitmaps.add(circles.get(r3));
}
else if(position == 1) {
bitmaps.add(circles.get(r1));
bitmaps.add(circles.get(color));
bitmaps.add(circles.get(r2));
bitmaps.add(circles.get(r3));
}
else if(position == 2) {
bitmaps.add(circles.get(r1));
bitmaps.add(circles.get(r2));
bitmaps.add(circles.get(color));
bitmaps.add(circles.get(r3));
}
else {
bitmaps.add(circles.get(r1));
bitmaps.add(circles.get(r2));
bitmaps.add(circles.get(r3));
bitmaps.add(circles.get(color));
}
circle = new Circle(this, bitmaps, circles, p1, position, color, getContext());
}
#Override
public void draw(Canvas canvas) {
if(canvas != null) {
super.draw(canvas);
canvas.drawColor(Color.WHITE);
if(!isGameOver && timer != null)
stopTimerTask();
try {
circle.draw(canvas);
} catch (GameOverException e) {
isGameOver = true;
if(isLeaving)
gameOver(canvas);
else if(GameActivity.counter > 0) {
gameOver(canvas);
GameActivity.counter++;
} else {
if (oneTimeFlag) {
int size1 = 200 * GameActivity.SCREEN_HEIGHT / 1280;
int size2 = 125 * GameActivity.SCREEN_HEIGHT / 1280;
float ratio = (float) GameActivity.SCREEN_HEIGHT / 1280;
replay = GameActivity.decodeSampledBitmapFromResource(getResources(), R.drawable.replay, size1, size1);
home = GameActivity.decodeSampledBitmapFromResource(getResources(), R.drawable.home, size2, size2);
continueButton = GameActivity.decodeSampledBitmapFromResource(getContext().getResources(), R.drawable.button, (int) (540 * ratio), (int) (100 * ratio));
star = GameActivity.decodeSampledBitmapFromResource(getContext().getResources(), R.drawable.star1, (int) (220 * ratio), (int) (220 * ratio));
int w = (int) ((float) GameActivity.SCREEN_WIDTH * 0.9);
oneTimeFlag = false;
}
if (askPurchaseScreen == 2) {
gameOver(canvas);
} else {
canvas.drawColor(Circle.endColor);
}
}
}
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
circle.onTouch(x, y);
return true;
}
}
Circle
public class Circle {
int x;
int y1;
int y2;
public static float speedY1 = 12.5f*(float)GameActivity.SCREEN_HEIGHT/1280;
public static float speedY2 = 12.5f*(float)GameActivity.SCREEN_HEIGHT/1280;
ArrayList<Bitmap> bitmaps;
GameView gameView;
int p1; // Position of required circle in slot 1
int p2; // Position of required circle in slot 2
int color;
int tempColor;
int width;
Context context;
// Centers of required circle
float centerX1;
float centerX2;
float centerY1;
float centerY2;
ArrayList<Bitmap> circles = new ArrayList<>();
boolean touchedFirst;
boolean touchedSecond;
int count1 = 1; // Slot 1 circle radius animation
int count2 = 1; // Slot 2 circle radius animation
float tempSpeedY1;
float tempSpeedY2;
boolean stopY1;
boolean stopY2;
int barCounter = 1;
int loopCount = 0;
int endGameCount = 0; // Count to move circle upwards
double limit;
float endRadiusSpeed;
int endSlot; // Where you died
int endRadiusCount = 0; // Count to increase circle radius
int barEndCounter = 1;
final Handler handler = new Handler();
boolean exception;
public static int endColor;
public Circle(GameView gameView, ArrayList<Bitmap> bitmaps, ArrayList<Bitmap> circles, int p1, int p2, int color, Context context) {
this.gameView = gameView;
this.bitmaps = bitmaps;
this.circles = circles;
this.p1 = p1;
this.p2 = p2;
this.color = color;
this.context = context;
width = GameActivity.SCREEN_WIDTH / 4 - 10;
x = 10;
y1 = 0;
y2 = -(GameActivity.SCREEN_HEIGHT + width) / 2;
centerX1 = x + p1 * (10 + width) + width / 2;
centerY1 = y1 + width / 2;
centerX2 = x + p2 * (10 + width) + width / 2;
centerY2 = y2 + width / 2;
}
public void update() throws GameOverException {
y1+= speedY1;
y2+= speedY2;
centerY1+= speedY1;
centerY2+= speedY2;
float ratio = (float)GameActivity.SCREEN_HEIGHT/1280;
limit = width/(20*ratio);
if(y1 >= gameView.getHeight()) {
loopCount++;
if(touchedFirst)
touchedFirst = false;
else {
speedY1 = speedY2 = -(12.5f * ratio);
endColor = bitmaps.get(p1).getPixel(width/2, width/2);
endGameCount += 1;
endSlot = 1;
}
if(endGameCount == 0) {
if (stopY1) {
tempSpeedY1 = speedY1;
speedY1 = 0;
ArrayList<Integer> numbers = new ArrayList<>();
for (int i = 0; i < 10; i++) {
if (i != color)
numbers.add(i);
}
tempColor = numbers.get(new Random().nextInt(9));
}
y1 = -(gameView.getWidth() / 4 - 10);
count1 = 1;
setBitmaps(1);
}
}
else if(y2 >= gameView.getHeight()) {
loopCount++;
if(touchedSecond)
touchedSecond = false;
else {
speedY1 = speedY2 = -(12.5f * ratio);
endColor = bitmaps.get(p2 + 4
).getPixel(width/2, width/2);
endGameCount += 1;
endSlot = 2;
}
if(endGameCount == 0) {
if (stopY2) {
tempSpeedY2 = speedY2;
speedY2 = 0;
}
y2 = -(gameView.getWidth() / 4 - 10);
count2 = 1;
setBitmaps(2);
}
}
}
public void setBitmaps(int slot) {
ArrayList<String> numbers = new ArrayList<>();
for(int i=0;i<10;i++)
numbers.add(i+"");
Random random = new Random();
int position = random.nextInt(4);
numbers.remove(color + "");
int r1 = Integer.valueOf(numbers.get(random.nextInt(9)));
numbers.remove(r1+"");
int r2 = Integer.valueOf(numbers.get(random.nextInt(8)));
numbers.remove(r2 + "");
int r3 = Integer.valueOf(numbers.get(random.nextInt(7)));
if(position == 0) {
bitmaps.set((slot - 1)*4, circles.get(color));
bitmaps.set((slot - 1)*4 + 1, circles.get(r1));
bitmaps.set((slot - 1)*4 + 2, circles.get(r2));
bitmaps.set((slot - 1)*4 + 3, circles.get(r3));
}
else if(position == 1) {
bitmaps.set((slot - 1)*4, circles.get(r1));
bitmaps.set((slot - 1)*4 + 1, circles.get(color));
bitmaps.set((slot - 1)*4 + 2, circles.get(r2));
bitmaps.set((slot - 1)*4 + 3, circles.get(r3));
}
else if(position == 2) {
bitmaps.set((slot - 1)*4, circles.get(r1));
bitmaps.set((slot - 1)*4 + 1, circles.get(r2));
bitmaps.set((slot - 1)*4 + 2, circles.get(color));
bitmaps.set((slot - 1)*4 + 3, circles.get(r3));
} else {
bitmaps.set((slot - 1)*4,circles.get(r1));
bitmaps.set((slot - 1)*4 + 1,circles.get(r2));
bitmaps.set((slot - 1)*4 + 2,circles.get(r3));
bitmaps.set((slot - 1)*4 + 3,circles.get(color));
}
if(slot == 1) {
p1 = position;
centerX1 = x+position*(10 + width) + width/2;
centerY1 = y1 + width/2;
}
else if(slot == 2) {
p2 = position;
centerX2 = x+position*(10 + width) + width/2;
centerY2 = y2 + width/2;
}
}
public void onTouch(float X, float Y) {
int radius = (gameView.getWidth() / 4 - 10) / 2;
if(endGameCount == 0) {
if ((X >= centerX1 - radius) && (X <= centerX1 + radius) && (Y >= centerY1 - radius) && (Y <= centerY1 + radius)) {
GameView.score++;
touchedFirst = true;
centerX1 = centerY1 = -1;
if(p1 == (timerCount - 1) && timer != null && starSlot == 1) {
GameView.stars++;
starCollected = true;
timerCount = 0;
stopTimerTask(0);
}
} else if ((X >= centerX2 - radius) && (X <= centerX2 + radius) && (Y >= centerY2 - radius) && (Y <= centerY2 + radius)) {
GameView.score++;
touchedSecond = true;
centerX2 = centerY2 = -1;
if(p2 == (timerCount - 1) && timer != null && starSlot == 2) {
GameView.stars++;
starCollected = true;
timerCount = 0;
stopTimerTask(0);
}
} else {
endSlot = 0;
if ((Y >= centerY1 - radius) && (Y <= centerY1 + radius)) {
endSlot = 1;
if (X >= 10 && X <= 10 + 2 * radius) {
p1 = 0;
centerX1 = 10 + radius;
} else if (X >= 20 + 2 * radius && X <= 20 + 4 * radius) {
p1 = 1;
centerX1 = 20 + 3 * radius;
} else if (X >= 30 + 4 * radius && X <= 30 + 6 * radius) {
p1 = 2;
centerX1 = 30 + 5 * radius;
} else if (X >= 40 + 6 * radius && X <= 40 + 8 * radius) {
p1 = 3;
centerX1 = 40 + 2 * radius;
} else
endSlot = 0;
} else if ((Y >= centerY2 - radius) && (Y <= centerY2 + radius)) {
endSlot = 2;
if (X >= 10 && X <= 10 + 2 * radius) {
p2 = 0;
centerX2 = 10 + radius;
} else if (X >= 20 + 2 * radius && X <= 20 + 4 * radius) {
p2 = 1;
centerX2 = 20 + 3 * radius;
} else if (X >= 30 + 4 * radius && X <= 30 + 6 * radius) {
p2 = 2;
centerX2 = 30 + 5 * radius;
} else if (X >= 40 + 6 * radius && X <= 40 + 8 * radius) {
p2 = 3;
centerX2 = 40 + 2 * radius;
} else
endSlot = 0;
}
if (endSlot != 0) {
speedY1 = speedY2 = 0;
limit = endGameCount = 6;
if (endSlot == 1) {
endColor= bitmaps.get(p1).getPixel(width/2, width/2);
} else {
endColor = bitmaps.get(p2 + 4).getPixel(width/2, width/2);
}
}
}
if (GameView.score % 5 == 0 && GameView.score <= 110 && barCounter == 1) {
float ratio = (float)GameActivity.SCREEN_HEIGHT/1280;
speedY1 += ratio*0.5;
speedY2 += ratio*0.5;
}
if (GameView.score > 0 && GameView.score % 15 == 14) {
if(isOddScore)
stopY1 = true;
else
stopY2 = true;
}
if (GameView.score > 0 && GameView.score % 15 == 0 && barCounter == 1) {
if(isOddScore)
stopY2 = true;
else
stopY1 = true;
}
if (GameView.score % 15 == 1)
barCounter = 1;
}
}
public void draw(Canvas canvas) throws GameOverException {
GameView.isGameOver = false;
if(exception)
throw new GameOverException(color);
update();
for(int i=0;i<bitmaps.size();i++) {
if(i<4) {
Rect rect = new Rect(x+i*(10 + width),y1,(x+width)*(i+1),y1+width);
if(endGameCount == Math.ceil(limit) && endSlot == 1) {
if(i == p1) {
endRadiusCount += 1;
if (endRadiusCount > 23) {
star.recycle();
loopCount = loopCount%starInterval;
Cryptography.saveFile((loopCount + "").getBytes(), context, "interval");
endGameCount = 0;
exception = true;
throw new GameOverException(color);
}
rect = new Rect(x + i * (10 + width) - endRadiusCount*(int)Math.ceil(endRadiusSpeed), y1 - endRadiusCount*(int)Math.ceil(endRadiusSpeed), (x + width) * (i + 1) + endRadiusCount*(int)Math.ceil(endRadiusSpeed), y1 + width + endRadiusCount*(int)Math.ceil(endRadiusSpeed));
canvas.drawBitmap(bitmaps.get(i), null, rect, null);
}
}
// TOUCH ANIMATION : DIMINISH CIRCLE
else if(i==p1 && touchedFirst) {
rect = new Rect(x + i * (10 + width) + 3*count1 + ((int)speedY1-15), y1 + 3*count1 + ((int)speedY1-15), (x + width) * (i + 1) - 3*count1 - ((int)speedY1-15), y1 + width - 3*count1 - ((int)speedY1-15));
canvas.drawBitmap(bitmaps.get(i), null, rect, null);
count1++;
}
else if(endSlot != 2) {
canvas.drawBitmap(bitmaps.get(i), null, rect, null);
if(timerCount > 0 && starSlot == 1) {
int size = width * 30 / 50;
int difference = (width - size) / 2;
Rect starRect = new Rect(x + (timerCount - 1) * (10 + width) + difference, y1 + difference, (x + width) * (timerCount) - difference, y1 + width - difference);
canvas.drawBitmap(star, null, starRect, null);
}
}
}
if(i >= 4) {
Rect rect = new Rect(x + (i % 4) * (10 + width), y2, (x + width) * ((i % 4) + 1), y2 + width);
if(endGameCount == Math.ceil(limit) && endSlot == 2) {
if((i%4)==p2) {
endRadiusCount += 1;
if (endRadiusCount > 23) {
star.recycle();
loopCount = loopCount%starInterval;
Cryptography.saveFile((loopCount + "").getBytes(), context, "interval");
endGameCount = 0;
exception = true;
throw new GameOverException(color);
}
rect = new Rect(x + (i % 4) * (10 + width) - endRadiusCount*(int)Math.ceil(endRadiusSpeed), y2 - endRadiusCount*(int)Math.ceil(endRadiusSpeed), (x + width) * ((i % 4) + 1) + endRadiusCount*(int)Math.ceil(endRadiusSpeed), y2 + width + endRadiusCount*(int)Math.ceil(endRadiusSpeed));
canvas.drawBitmap(bitmaps.get(i), null, rect, null);
}
}
else if((i%4)==p2 && touchedSecond) {
rect = new Rect(x + (i % 4) * (10 + width) + 3*count2 + ((int)speedY1-15), y2 + 3*count2 + ((int)speedY1-15), (x + width) * ((i % 4) + 1) - 3*count2 - ((int)speedY1-15), y2 + width - 3*count2 - ((int)speedY1-15));
canvas.drawBitmap(bitmaps.get(i), null, rect, null);
count2++;
}
else if(endSlot != 1) {
canvas.drawBitmap(bitmaps.get(i), null, rect, null);
if(timerCount > 0 && starSlot == 2) {
int size = width * 30 / 50;
int difference = (width - size) / 2;
Rect starRect = new Rect(x + (timerCount - 1) * (10 + width) + difference, y2 + difference, (x + width) * (timerCount) - difference, y2 + width - difference);
canvas.drawBitmap(star, null, starRect, null);
}
}
}
}
Rect src = new Rect(circles.get(color).getWidth()/2 - 10,circles.get(color).getHeight()/2 - 10,circles.get(color).getWidth()/2 + 10,circles.get(color).getHeight()/2 + 10);
Rect dst;
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setTextAlign(Paint.Align.RIGHT);
paint.setTypeface(Typeface.SANS_SERIF);
paint.setTextSize(72 * ratio);
canvas.drawText(GameView.score + " ", GameActivity.SCREEN_WIDTH, width / 2, paint);
dst = new Rect(5,5, (int) (120 * ratio - 5), (int) (120 * ratio - 5));
canvas.drawBitmap(star,null,dst,null);
paint.setTextAlign(Paint.Align.LEFT);
canvas.drawText("" + GameView.stars, 120 * ratio, width/2, paint);
}
}
Don't override draw(). That's used to render the View, not the Surface, and you generally shouldn't override that method even if you're creating a custom View:
When implementing a view, implement onDraw(android.graphics.Canvas) instead of overriding this method.
SurfaceViews have two parts, the Surface and the View. The View part is handled like any other View, but is generally just a transparent "hole" in the layout. The Surface is a separate layer that, by default, sits behind the View layer. Whatever you draw on the Surface "shows through" the transparent hole.
By overriding draw() you're drawing on the View whenever the View UI is invalidated. You're also calling draw() from the render thread, so you're drawing on the Surface, but with default Z-ordering you can't see that because the View contents are fully opaque. You will reduce your impact on the UI thread by not drawing everything in two different layers.
Unless you're deliberately drawing on the View, it's best to avoid subclassing SurfaceView entirely, and just use it as a member.
Because your draw code is synchronized, the two draw passes will not execute concurrently. That means your View layer draw call will block waiting for the Surface layer rendering to complete. Canvas rendering on a Surface is not hardware-accelerated, so if you're touching a lot of pixels it can get slow, and the UI thread will have to wait for it to run. That wouldn't be so bad, but you're holding on to the mutex while you're sleeping, which means the only opportunity for the main UI thread to run comes during the brief instant when the loop wraps around. The thread scheduler does not guarantee fairness, so it's entirely possible to starve the main UI thread this way.
If you change #override draw() to myDraw() things should get better. You should probably move your sleep call out of the synchronized block just on general principles, or work to eliminate it entirely. You might also want to consider using a custom View instead of SurfaceView.
On an unrelated note, you should probably avoid doing this every update:
Random random = new Random();
for the reasons noted here.
Successfully solved the issue. Can't imagine that the solution would be this much simple as compared to the problem that I was considering that complex. Just reduced the frame rate from 120 to 90 and guess what, it worked like charm!
Due to a high frame rate, the SurfaceView was busy doing all the drawing and onTouchEvent() method had to starve
I want to implement the Ringdroid waveform in my android app.But for some songs,the waveform created after choosing the song is larger than the screen size and for songs the waveform width is smaller than the mobile screen width .
Plz suggest me the change in code of Ringdroid ,so that every time the waveform of song totally covers the width of screen.
This is the link of Ringdroid project
https://github.com/google/ringdroid
After a whole lot of searching and surfing about it. I tried it myself and got the desired output with it
This is the WaveformView class of Ringdroid
package com.ringdroid;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import com.ringdroid.soundfile.SoundFile;
/**
* WaveformView is an Android view that displays a visual representation
* of an audio waveform. It retrieves the frame gains from a CheapSoundFile
* object and recomputes the shape contour at several zoom levels.
*
* This class doesn't handle selection or any of the touch interactions
* directly, so it exposes a listener interface. The class that embeds
* this view should add itself as a listener and make the view scroll
* and respond to other events appropriately.
*
* WaveformView doesn't actually handle selection, but it will just display
* the selected part of the waveform in a different color.
*/
public class WaveformView extends View {
public interface WaveformListener {
public void waveformTouchStart(float x);
public void waveformTouchMove(float x);
public void waveformTouchEnd();
public void waveformFling(float x);
public void waveformDraw();
public void waveformZoomIn();
public void waveformZoomOut();
};
// Colors
private Paint mGridPaint;
private Paint mSelectedLinePaint;
private Paint mUnselectedLinePaint;
private Paint mUnselectedBkgndLinePaint;
private Paint mBorderLinePaint;
private Paint mPlaybackLinePaint;
private Paint mTimecodePaint;
private SoundFile mSoundFile;
private int[] mLenByZoomLevel;
private double[][] mValuesByZoomLevel;
private double[] mZoomFactorByZoomLevel;
private int[] mHeightsAtThisZoomLevel;
private int mZoomLevel;
private int mNumZoomLevels;
private int mSampleRate;
private int mSamplesPerFrame;
private int mOffset;
private int mSelectionStart;
private int mSelectionEnd;
private int mPlaybackPos;
private float mDensity;
private float mInitialScaleSpan;
private WaveformListener mListener;
private GestureDetector mGestureDetector;
private ScaleGestureDetector mScaleGestureDetector;
private boolean mInitialized;
public WaveformView(Context context, AttributeSet attrs) {
super(context, attrs);
// We don't want keys, the markers get these
setFocusable(false);
Resources res = getResources();
mGridPaint = new Paint();
mGridPaint.setAntiAlias(false);
mGridPaint.setColor(res.getColor(R.color.grid_line));
mSelectedLinePaint = new Paint();
mSelectedLinePaint.setAntiAlias(false);
mSelectedLinePaint.setColor(res.getColor(R.color.waveform_selected));
mUnselectedLinePaint = new Paint();
mUnselectedLinePaint.setAntiAlias(false);
mUnselectedLinePaint.setColor(res.getColor(R.color.waveform_unselected));
mUnselectedBkgndLinePaint = new Paint();
mUnselectedBkgndLinePaint.setAntiAlias(false);
mUnselectedBkgndLinePaint.setColor(res.getColor(R.color.waveform_unselected_bkgnd_overlay));
mBorderLinePaint = new Paint();
mBorderLinePaint.setAntiAlias(true);
mBorderLinePaint.setStrokeWidth(1.5f);
mBorderLinePaint.setPathEffect(new DashPathEffect(new float[] { 3.0f, 2.0f }, 0.0f));
mBorderLinePaint.setColor(res.getColor(R.color.selection_border));
mPlaybackLinePaint = new Paint();
mPlaybackLinePaint.setAntiAlias(false);
mPlaybackLinePaint.setColor(res.getColor(R.color.playback_indicator));
mTimecodePaint = new Paint();
mTimecodePaint.setTextSize(12);
mTimecodePaint.setAntiAlias(true);
mTimecodePaint.setColor(res.getColor(R.color.timecode));
mTimecodePaint.setShadowLayer(2, 1, 1, res.getColor(R.color.timecode_shadow));
mGestureDetector = new GestureDetector(
context,
new GestureDetector.SimpleOnGestureListener() {
public boolean onFling(MotionEvent e1, MotionEvent e2, float vx, float vy) {
mListener.waveformFling(vx);
return true;
}
}
);
mScaleGestureDetector = new ScaleGestureDetector(
context,
new ScaleGestureDetector.SimpleOnScaleGestureListener() {
public boolean onScaleBegin(ScaleGestureDetector d) {
Log.v("Ringdroid", "ScaleBegin " + d.getCurrentSpanX());
mInitialScaleSpan = Math.abs(d.getCurrentSpanX());
return true;
}
public boolean onScale(ScaleGestureDetector d) {
float scale = Math.abs(d.getCurrentSpanX());
Log.v("Ringdroid", "Scale " + (scale - mInitialScaleSpan));
if (scale - mInitialScaleSpan > 40) {
mListener.waveformZoomIn();
mInitialScaleSpan = scale;
}
if (scale - mInitialScaleSpan < -40) {
mListener.waveformZoomOut();
mInitialScaleSpan = scale;
}
return true;
}
public void onScaleEnd(ScaleGestureDetector d) {
Log.v("Ringdroid", "ScaleEnd " + d.getCurrentSpanX());
}
}
);
mSoundFile = null;
mLenByZoomLevel = null;
mValuesByZoomLevel = null;
mHeightsAtThisZoomLevel = null;
mOffset = 0;
mPlaybackPos = -1;
mSelectionStart = 0;
mSelectionEnd = 0;
mDensity = 1.0f;
mInitialized = false;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
mScaleGestureDetector.onTouchEvent(event);
if (mGestureDetector.onTouchEvent(event)) {
return true;
}
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
mListener.waveformTouchStart(event.getX());
break;
case MotionEvent.ACTION_MOVE:
mListener.waveformTouchMove(event.getX());
break;
case MotionEvent.ACTION_UP:
mListener.waveformTouchEnd();
break;
}
return true;
}
public boolean hasSoundFile() {
return mSoundFile != null;
}
public void setSoundFile(SoundFile soundFile) {
mSoundFile = soundFile;
mSampleRate = mSoundFile.getSampleRate();
mSamplesPerFrame = mSoundFile.getSamplesPerFrame();
computeDoublesForAllZoomLevels();
mHeightsAtThisZoomLevel = null;
}
public boolean isInitialized() {
return mInitialized;
}
public int getZoomLevel() {
return mZoomLevel;
}
public void setZoomLevel(int zoomLevel) {
while (mZoomLevel > zoomLevel) {
zoomIn();
}
while (mZoomLevel < zoomLevel) {
zoomOut();
}
}
public boolean canZoomIn() {
return (mZoomLevel > 0);
}
public void zoomIn() {
if (canZoomIn()) {
mZoomLevel--;
mSelectionStart *= 2;
mSelectionEnd *= 2;
mHeightsAtThisZoomLevel = null;
int offsetCenter = mOffset + getMeasuredWidth() / 2;
offsetCenter *= 2;
mOffset = offsetCenter - getMeasuredWidth() / 2;
if (mOffset < 0)
mOffset = 0;
invalidate();
}
}
public boolean canZoomOut() {
return (mZoomLevel < mNumZoomLevels - 1);
}
public void zoomOut() {
if (canZoomOut()) {
mZoomLevel++;
mSelectionStart /= 2;
mSelectionEnd /= 2;
int offsetCenter = mOffset + getMeasuredWidth() / 2;
offsetCenter /= 2;
mOffset = offsetCenter - getMeasuredWidth() / 2;
if (mOffset < 0)
mOffset = 0;
mHeightsAtThisZoomLevel = null;
invalidate();
}
}
public int maxPos() {
return mLenByZoomLevel[mZoomLevel];
}
public int secondsToFrames(double seconds) {
return (int)(1.0 * seconds * mSampleRate / mSamplesPerFrame + 0.5);
}
public int secondsToPixels(double seconds) {
double z = mZoomFactorByZoomLevel[mZoomLevel];
return (int)(z * seconds * mSampleRate / mSamplesPerFrame + 0.5);
}
public double pixelsToSeconds(int pixels) {
double z = mZoomFactorByZoomLevel[mZoomLevel];
return (pixels * (double)mSamplesPerFrame / (mSampleRate * z));
}
public int millisecsToPixels(int msecs) {
double z = mZoomFactorByZoomLevel[mZoomLevel];
return (int)((msecs * 1.0 * mSampleRate * z) /
(1000.0 * mSamplesPerFrame) + 0.5);
}
public int pixelsToMillisecs(int pixels) {
double z = mZoomFactorByZoomLevel[mZoomLevel];
return (int)(pixels * (1000.0 * mSamplesPerFrame) /
(mSampleRate * z) + 0.5);
}
public void setParameters(int start, int end, int offset) {
mSelectionStart = start;
mSelectionEnd = end;
mOffset = offset;
}
public int getStart() {
return mSelectionStart;
}
public int getEnd() {
return mSelectionEnd;
}
public int getOffset() {
return mOffset;
}
public void setPlayback(int pos) {
mPlaybackPos = pos;
}
public void setListener(WaveformListener listener) {
mListener = listener;
}
public void recomputeHeights(float density) {
mHeightsAtThisZoomLevel = null;
mDensity = density;
mTimecodePaint.setTextSize((int)(12 * density));
invalidate();
}
protected void drawWaveformLine(Canvas canvas,
int x, int y0, int y1,
Paint paint) {
canvas.drawLine(x, y0, x, y1, paint);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mSoundFile == null)
return;
if (mHeightsAtThisZoomLevel == null)
computeIntsForThisZoomLevel();
// Draw waveform
int measuredWidth = getMeasuredWidth();
int measuredHeight = getMeasuredHeight();
int start = mOffset;
int width = mHeightsAtThisZoomLevel.length - start;
int ctr = measuredHeight / 2;
if (width > measuredWidth)
width = measuredWidth;
// Draw grid
double onePixelInSecs = pixelsToSeconds(1);
boolean onlyEveryFiveSecs = (onePixelInSecs > 1.0 / 50.0);
double fractionalSecs = mOffset * onePixelInSecs;
int integerSecs = (int) fractionalSecs;
int i = 0;
while (i < width) {
i++;
fractionalSecs += onePixelInSecs;
int integerSecsNew = (int) fractionalSecs;
if (integerSecsNew != integerSecs) {
integerSecs = integerSecsNew;
if (!onlyEveryFiveSecs || 0 == (integerSecs % 5)) {
canvas.drawLine(i, 0, i, measuredHeight, mGridPaint);
}
}
}
// Draw waveform
for (i = 0; i < width; i++) {
Paint paint;
if (i + start >= mSelectionStart &&
i + start < mSelectionEnd) {
paint = mSelectedLinePaint;
} else {
drawWaveformLine(canvas, i, 0, measuredHeight,
mUnselectedBkgndLinePaint);
paint = mUnselectedLinePaint;
}
drawWaveformLine(
canvas, i,
ctr - mHeightsAtThisZoomLevel[start + i],
ctr + 1 + mHeightsAtThisZoomLevel[start + i],
paint);
if (i + start == mPlaybackPos) {
canvas.drawLine(i, 0, i, measuredHeight, mPlaybackLinePaint);
}
}
// If we can see the right edge of the waveform, draw the
// non-waveform area to the right as unselected
for (i = width; i < measuredWidth; i++) {
drawWaveformLine(canvas, i, 0, measuredHeight,
mUnselectedBkgndLinePaint);
}
// Draw borders
canvas.drawLine(
mSelectionStart - mOffset + 0.5f, 30,
mSelectionStart - mOffset + 0.5f, measuredHeight,
mBorderLinePaint);
canvas.drawLine(
mSelectionEnd - mOffset + 0.5f, 0,
mSelectionEnd - mOffset + 0.5f, measuredHeight - 30,
mBorderLinePaint);
// Draw timecode
double timecodeIntervalSecs = 1.0;
if (timecodeIntervalSecs / onePixelInSecs < 50) {
timecodeIntervalSecs = 5.0;
}
if (timecodeIntervalSecs / onePixelInSecs < 50) {
timecodeIntervalSecs = 15.0;
}
// Draw grid
fractionalSecs = mOffset * onePixelInSecs;
int integerTimecode = (int) (fractionalSecs / timecodeIntervalSecs);
i = 0;
while (i < width) {
i++;
fractionalSecs += onePixelInSecs;
integerSecs = (int) fractionalSecs;
int integerTimecodeNew = (int) (fractionalSecs /
timecodeIntervalSecs);
if (integerTimecodeNew != integerTimecode) {
integerTimecode = integerTimecodeNew;
// Turn, e.g. 67 seconds into "1:07"
String timecodeMinutes = "" + (integerSecs / 60);
String timecodeSeconds = "" + (integerSecs % 60);
if ((integerSecs % 60) < 10) {
timecodeSeconds = "0" + timecodeSeconds;
}
String timecodeStr = timecodeMinutes + ":" + timecodeSeconds;
float offset = (float) (
0.5 * mTimecodePaint.measureText(timecodeStr));
canvas.drawText(timecodeStr,
i - offset,
(int)(12 * mDensity),
mTimecodePaint);
}
}
if (mListener != null) {
mListener.waveformDraw();
}
}
/**
* Called once when a new sound file is added
*/
private void computeDoublesForAllZoomLevels() {
int numFrames = mSoundFile.getNumFrames();
int[] frameGains = mSoundFile.getFrameGains();
double[] smoothedGains = new double[numFrames];
if (numFrames == 1) {
smoothedGains[0] = frameGains[0];
} else if (numFrames == 2) {
smoothedGains[0] = frameGains[0];
smoothedGains[1] = frameGains[1];
} else if (numFrames > 2) {
smoothedGains[0] = (double)(
(frameGains[0] / 2.0) +
(frameGains[1] / 2.0));
for (int i = 1; i < numFrames - 1; i++) {
smoothedGains[i] = (double)(
(frameGains[i - 1] / 3.0) +
(frameGains[i ] / 3.0) +
(frameGains[i + 1] / 3.0));
}
smoothedGains[numFrames - 1] = (double)(
(frameGains[numFrames - 2] / 2.0) +
(frameGains[numFrames - 1] / 2.0));
}
// Make sure the range is no more than 0 - 255
double maxGain = 1.0;
for (int i = 0; i < numFrames; i++) {
if (smoothedGains[i] > maxGain) {
maxGain = smoothedGains[i];
}
}
double scaleFactor = 1.0;
if (maxGain > 255.0) {
scaleFactor = 255 / maxGain;
}
// Build histogram of 256 bins and figure out the new scaled max
maxGain = 0;
int gainHist[] = new int[256];
for (int i = 0; i < numFrames; i++) {
int smoothedGain = (int)(smoothedGains[i] * scaleFactor);
if (smoothedGain < 0)
smoothedGain = 0;
if (smoothedGain > 255)
smoothedGain = 255;
if (smoothedGain > maxGain)
maxGain = smoothedGain;
gainHist[smoothedGain]++;
}
// Re-calibrate the min to be 5%
double minGain = 0;
int sum = 0;
while (minGain < 255 && sum < numFrames / 20) {
sum += gainHist[(int)minGain];
minGain++;
}
// Re-calibrate the max to be 99%
sum = 0;
while (maxGain > 2 && sum < numFrames / 100) {
sum += gainHist[(int)maxGain];
maxGain--;
}
// Compute the heights
double[] heights = new double[numFrames];
double range = maxGain - minGain;
for (int i = 0; i < numFrames; i++) {
double value = (smoothedGains[i] * scaleFactor - minGain) / range;
if (value < 0.0)
value = 0.0;
if (value > 1.0)
value = 1.0;
heights[i] = value * value;
}
mNumZoomLevels = 5;
mLenByZoomLevel = new int[5];
mZoomFactorByZoomLevel = new double[5];
mValuesByZoomLevel = new double[5][];
// Level 0 is doubled, with interpolated values
mLenByZoomLevel[0] = numFrames * 2;
mZoomFactorByZoomLevel[0] = 2.0;
mValuesByZoomLevel[0] = new double[mLenByZoomLevel[0]];
if (numFrames > 0) {
mValuesByZoomLevel[0][0] = 0.5 * heights[0];
mValuesByZoomLevel[0][1] = heights[0];
}
for (int i = 1; i < numFrames; i++) {
mValuesByZoomLevel[0][2 * i] = 0.5 * (heights[i - 1] + heights[i]);
mValuesByZoomLevel[0][2 * i + 1] = heights[i];
}
// Level 1 is normal
mLenByZoomLevel[1] = numFrames;
mValuesByZoomLevel[1] = new double[mLenByZoomLevel[1]];
mZoomFactorByZoomLevel[1] = 1.0;
for (int i = 0; i < mLenByZoomLevel[1]; i++) {
mValuesByZoomLevel[1][i] = heights[i];
}
// 3 more levels are each halved
for (int j = 2; j < 5; j++) {
mLenByZoomLevel[j] = mLenByZoomLevel[j - 1] / 2;
mValuesByZoomLevel[j] = new double[mLenByZoomLevel[j]];
mZoomFactorByZoomLevel[j] = mZoomFactorByZoomLevel[j - 1] / 2.0;
for (int i = 0; i < mLenByZoomLevel[j]; i++) {
mValuesByZoomLevel[j][i] =
0.5 * (mValuesByZoomLevel[j - 1][2 * i] +
mValuesByZoomLevel[j - 1][2 * i + 1]);
}
}
if (numFrames > 5000) {
mZoomLevel = 3;
} else if (numFrames > 1000) {
mZoomLevel = 2;
} else if (numFrames > 300) {
mZoomLevel = 1;
} else {
mZoomLevel = 0;
}
mInitialized = true;
}
/**
* Called the first time we need to draw when the zoom level has changed
* or the screen is resized
*/
private void computeIntsForThisZoomLevel() {
int halfHeight = (getMeasuredHeight() / 2) - 1;
mHeightsAtThisZoomLevel = new int[mLenByZoomLevel[mZoomLevel]];
for (int i = 0; i < mLenByZoomLevel[mZoomLevel]; i++) {
mHeightsAtThisZoomLevel[i] =
(int)(mValuesByZoomLevel[mZoomLevel][i] * halfHeight);
}
}
}
The change is here in this part of code
DisplayMetrics displaymetrics = getContext().getResources().getDisplayMetrics();
int ScreenWidth= displaymetrics.widthPixels;
// Draw waveform
for ( i = 0; i < width; i++) {
Paint paint;
if (i + start >= mSelectionStart &&
i + start < mSelectionEnd) {
paint = mSelectedLinePaint;
} else {
drawWaveformLine(canvas, ((ScreenWidth/width)*i), 0, measuredHeight,
mUnselectedBkgndLinePaint);
paint = mUnselectedLinePaint;
}
drawWaveformLine(
canvas, ((ScreenWidth/width)*i),
ctr - mHeightsAtThisZoomLevel[start + i],
ctr + 1 + mHeightsAtThisZoomLevel[start + i],
paint);
you have to change the x-axis of draw line method according to your screen size
import java.util.LinkedList;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.SurfaceView;
/**
* A view that displays audio data on the screen as a waveform.
*/
public class WaveformView extends SurfaceView {
// The number of buffer frames to keep around (for a nice fade-out
// visualization.
private static final int HISTORY_SIZE = 6;
// To make quieter sounds still show up well on the display, we use
// +/- 8192 as the amplitude that reaches the top/bottom of the view
// instead of +/- 32767. Any samples that have magnitude higher than this
// limit will simply be clipped during drawing.
private static final float MAX_AMPLITUDE_TO_DRAW = 8192.0f;
// The queue that will hold historical audio data.
private LinkedList<short[]> mAudioData;
private Paint mPaint;
public WaveformView(Context context) {
this(context, null, 0);
}
public WaveformView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WaveformView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mAudioData = new LinkedList<short[]>();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
mPaint.setStrokeWidth(0);
mPaint.setAntiAlias(true);
}
/**
* Updates the waveform view with a new "frame" of samples and renders it.
* The new frame gets added to the front of the rendering queue, pushing the
* previous frames back, causing them to be faded out visually.
*
* #param buffer the most recent buffer of audio samples.
*/
public synchronized void updateAudioData(short[] buffer) {
short[] newBuffer;
// We want to keep a small amount of history in the view to provide a nice
// fading effect. We use a linked list that we treat as a queue for this.
if (mAudioData.size() == HISTORY_SIZE) {
newBuffer = mAudioData.removeFirst();
System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
} else {
newBuffer = buffer.clone();
}
mAudioData.addLast(newBuffer);
// Update the display.
Canvas canvas = getHolder().lockCanvas();
if (canvas != null) {
drawWaveform(canvas);
getHolder().unlockCanvasAndPost(canvas);
}
}
/**
* Repaints the view's surface.
*
* #param canvas the {#link Canvas} object on which to draw.
*/
private void drawWaveform(Canvas canvas) {
// Clear the screen each time because SurfaceView won't do this for us.
canvas.drawColor(Color.BLACK);
float width = getWidth();
float height = getHeight();
float centerY = height / 2;
// We draw the history from oldest to newest so that the older audio
// data is further back and darker than the most recent data.
int colorDelta = 255 / (HISTORY_SIZE + 1);
int brightness = colorDelta;
for (short[] buffer : mAudioData) {
mPaint.setColor(Color.argb(brightness, 128, 255, 192));
float lastX = -1;
float lastY = -1;
// For efficiency, we don't draw all of the samples in the buffer,
// but only the ones that align with pixel boundaries.
for (int x = 0; x < width; x++) {
int index = (int) ((x / width) * buffer.length);
short sample = buffer[index];
float y = (sample / MAX_AMPLITUDE_TO_DRAW) * centerY + centerY;
if (lastX != -1) {
canvas.drawLine(lastX, lastY, x, y, mPaint);
}
lastX = x;
lastY = y;
}
brightness += colorDelta;
}
}
}
I have drawn a Cubic Curve on canvas using
myPath.cubicTo(10, 10, w, h/2, 10, h-10);
I have four ImageView on that screen and I want to move that ImageViews on the drawn curve when I drag that image with touch.
I have referred the links :
Move Image on Curve Path
Move object on Curve
Move imageview on curve
What I get is, Animation to move the Image on Curve with the duration defined by t.
But I want to move that ImageView on touch in direction of that curve area only.
Following is my Screen :
So, I want all the (x,y) co-ordinates of the curve to move ImageView on that curve only.
Else I want an equation to draw a curve so that I can interpolate x value for the touched y value.
I have goggled a lot but didn't succeed.
Any advice or guidance will help me a lot.
Approach
I would suggest a different approach than using bezier as you would need to reproduce the math for it in order to get the positions.
By using simple trigonometry you can achieve the same visual result but in addition have full control of the positions.
Trigonometry
For example:
THIS ONLINE DEMO produces this result (simplified version for sake of demo):
Define an array with the circles and angle positions instead of y and x positions. You can filter angles later if they (e.g. only show angles between -90 and 90 degrees).
Using angles will make sure they stay ordered when moved.
var balls = [-90, -45, 0, 45]; // example "positions"
To replace the Bezier curve you can do this instead:
/// some setup variables
var xCenter = -80, /// X center of circle
yCenter = canvas.height * 0.5, /// Y center of circle
radius = 220, /// radius of circle
x, y; /// to calculate line position
/// draw half circle
ctx.arc(xCenter, yCenter, radius, 0, 2 * Math.PI);
ctx.stroke();
Now we can use an Y value from mouse move/touch etc. to move around the circles:
/// for demo, mousemove - adopt as needed for touch
canvas.onmousemove = function(e) {
/// get Y position which is used as delta to angle
var rect = demo.getBoundingClientRect();
dlt = e.clientY - rect.top;
/// render the circles in new positions
render();
}
The rendering iterates through the balls array and render them in their angle + delta:
for(var i = 0, angle; i < balls.length; i++) {
angle = balls[i];
pos = getPosfromAngle(angle);
/// draw circles etc. here
}
The magic function is this:
function getPosfromAngle(a) {
/// get angle from circle and add delta
var angle = Math.atan2(delta - yCenter, radius) + a * Math.PI / 180;
return [xCenter + radius * Math.cos(angle),
yCenter + radius * Math.sin(angle)];
}
radius is used as a pseudo position. You can replace this with an actual X position but is frankly not needed.
In this demo, to keep it simple, I have only attached mouse move. Move the mouse over the canvas to see the effect.
As this is demo code it's not structured optimal (separate render of background and the circles etc.).
Feel free to adopt and modify to suit your needs.
This code I have used to achieve this functionality and it works perfect as per your requirement...
public class YourActivity extends Activity {
private class ButtonInfo {
public Button btnObj;
public PointF OrigPos;
public double origAngle;
public double currentAngle;
public double minAngle;
public double maxAngle;
boolean isOnClick = false;
}
private int height;
private double radius;
private PointF centerPoint;
private final int NUM_BUTTONS = 4;
private final int FIRST_INDEX = 0;
private final int SECOND_INDEX = 1;
private final int THIRD_INDEX = 2;
private final int FORTH_INDEX = 3;
private final String FIRST_TAG = "FiRST_BUTTON";
private final String SECOND_TAG = "SECOND_BUTTON";
private final String THIRD_TAG = "THIRD_BUTTON";
private final String FORTH_TAG = "FORTH_BUTTON";
private boolean animInProgress = false;
private int currentButton = -1;
private ButtonInfo[] buttonInfoArray = new ButtonInfo[NUM_BUTTONS];
private int curveImageResource = -1;
private RelativeLayout parentContainer;
private int slop;
private boolean initFlag = false;
private int touchDownY = -1;
private int touchDownX = -1;
private int animCount;
private Context context;
#SuppressWarnings("deprecation")
#SuppressLint("NewApi")
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
overridePendingTransition(R.anim.fadeinleft, R.anim.fadeoutleft);
// hide action bar in view
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
Thread.setDefaultUncaughtExceptionHandler(
new MyDefaultExceptionHandler(this, getLocalClassName()));
setContentView(R.layout.your_layout);
context = this;
final ImageView curve_image = (ImageView) findViewById(R.id.imageView1);
parentContainer = (RelativeLayout) findViewById(R.id.llView);
// Set buttons on their location
for (int i = 0; i < NUM_BUTTONS; i++) {
buttonInfoArray[i] = new ButtonInfo();
}
Button img1 = (Button) findViewById(R.id.button_option1);
Button img2 = (Button) findViewById(R.id.button_option2);
Button img3 = (Button) findViewById(R.id.button_option3);
Button img4 = (Button) findViewById(R.id.button_option4);
//1st button
buttonInfoArray[FIRST_INDEX].btnObj = (Button) this
.findViewById(R.id.setting_button_option);
buttonInfoArray[FIRST_INDEX].btnObj.setTag(FIRST_TAG);
// 2nd button
buttonInfoArray[SECOND_INDEX].btnObj = (Button) this
.findViewById(R.id.scanning_button_option);
buttonInfoArray[SECOND_INDEX].btnObj.setTag(SECOND_TAG);
// 3rd button
buttonInfoArray[THIRD_INDEX].btnObj = (Button) this
.findViewById(R.id.manual_button_option);
buttonInfoArray[THIRD_INDEX].btnObj.setTag(THIRD_TAG);
// 4th button
buttonInfoArray[FORTH_INDEX].btnObj = (Button) this
.findViewById(R.id.logout_button_option);
buttonInfoArray[FORTH_INDEX].btnObj.setTag(FORTH_TAG);
for (ButtonInfo currentButtonInfo : buttonInfoArray) {
currentButtonInfo.btnObj.setClickable(false);
}
for (ButtonInfo currentButtonInfo : buttonInfoArray) {
currentButtonInfo.btnObj.bringToFront();
}
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
ViewTreeObserver vtoLayout = parentContainer.getViewTreeObserver();
vtoLayout.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (initFlag == true)
return;
centerPoint = new PointF(0, (parentContainer.getHeight()) / 2);
curve_image.setImageResource(curveImageResource);
ViewTreeObserver vtoCurveImage = curve_image
.getViewTreeObserver();
vtoCurveImage
.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (initFlag == true)
return;
ViewConfiguration vc = ViewConfiguration.get(parentContainer
.getContext());
slop = vc.getScaledTouchSlop();
parentContainer.setOnTouchListener(tlobj);
height = curve_image.getMeasuredHeight();
curve_image.getMeasuredWidth();
radius = (height / 2);
double angleDiff = Math.PI / (NUM_BUTTONS + 1);
double initialAngle = (Math.PI / 2 - angleDiff);
for (ButtonInfo currentButtonInfo : buttonInfoArray) {
currentButtonInfo.origAngle = initialAngle;
initialAngle -= angleDiff;
}
double tempCurrentAngle;
double maxAngle = (-1 * Math.PI / 2);
tempCurrentAngle = maxAngle;
for (int i = NUM_BUTTONS - 1; i >= 0; i--) {
buttonInfoArray[i].maxAngle = tempCurrentAngle;
int buttonHeight = buttonInfoArray[i].btnObj
.getHeight();
if (buttonHeight < 30) {
buttonHeight = 80;
}
tempCurrentAngle = findNextMaxAngle(
tempCurrentAngle,
(buttonHeight + 5));
}
double minAngle = (Math.PI / 2);
tempCurrentAngle = minAngle;
for (int i = 0; i < NUM_BUTTONS; i++) {
buttonInfoArray[i].minAngle = tempCurrentAngle;
int buttonHeight = buttonInfoArray[i].btnObj
.getHeight();
if (buttonHeight < 30) {
buttonHeight = 80;
}
tempCurrentAngle = findNextMinAngle(
tempCurrentAngle, (buttonHeight + 5));
}
for (ButtonInfo currentButtonInfo : buttonInfoArray) {
PointF newPos = getPointByAngle(currentButtonInfo.origAngle);
currentButtonInfo.OrigPos = newPos;
currentButtonInfo.currentAngle = currentButtonInfo.origAngle;
setTranslationX(
currentButtonInfo.btnObj,
(int) currentButtonInfo.OrigPos.x - 50);
setTranslationY(
currentButtonInfo.btnObj,
(int) currentButtonInfo.OrigPos.y - 50);
currentButtonInfo.btnObj.requestLayout();
}
initFlag = true;
}
});
}
});
}
/**
* Find next max angle
* #param inputAngle
* #param yDist
* #return
*/
private double findNextMaxAngle(double inputAngle, int yDist) {
float initYPos = (float) (centerPoint.y - (Math.sin(inputAngle) * radius));
float finalYPos = initYPos - yDist;
float finalXPos = getXPos(finalYPos);
double newAngle = getNewAngle(new PointF(finalXPos, finalYPos));
return newAngle;
}
/**
* Find next min angle
* #param inputAngle
* #param yDist
* #return
*/
private double findNextMinAngle(double inputAngle, int yDist) {
float initYPos = (int) (centerPoint.y - (Math.sin(inputAngle) * radius));
float finalYPos = initYPos + yDist;
float finalXPos = getXPos(finalYPos);
double newAngle = getNewAngle(new PointF(finalXPos, finalYPos));
return newAngle;
}
/**
* Apply reset transformation when user release touch
* #param buttonInfoObj
*/
public void applyResetAnimation(final ButtonInfo buttonInfoObj) {
ValueAnimator animator = ValueAnimator.ofFloat(0, 1); // values from 0
// to 1
animator.setDuration(1000); // 5 seconds duration from 0 to 1
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = ((Float) (animation.getAnimatedValue()))
.floatValue();
// Set translation of your view here. Position can be calculated
// out of value. This code should move the view in a half
// circle.
double effectiveAngle = buttonInfoObj.origAngle
+ ((buttonInfoObj.currentAngle - buttonInfoObj.origAngle) * (1.0 - value));
PointF newPos = getPointByAngle(effectiveAngle);
setTranslationX(buttonInfoObj.btnObj, newPos.x - 50);
setTranslationY(buttonInfoObj.btnObj, newPos.y - 50);
}
});
animator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
animCount++;
if (animCount == NUM_BUTTONS) {
animCount = 0;
currentButton = -1;
animInProgress = false;
for (ButtonInfo currentButtonInfo : buttonInfoArray) {
setTranslationX(currentButtonInfo.btnObj,
currentButtonInfo.OrigPos.x - 50);
setTranslationY(currentButtonInfo.btnObj,
currentButtonInfo.OrigPos.y - 50);
currentButtonInfo.isOnClick = false;
currentButtonInfo.currentAngle = currentButtonInfo.origAngle;
currentButtonInfo.btnObj.setPressed(false);
currentButtonInfo.btnObj.requestLayout();
}
}
}
});
animator.start();
}
/**
* On Touch start animation
*/
private OnTouchListener tlobj = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent motionEvent) {
switch (MotionEventCompat.getActionMasked(motionEvent)) {
case MotionEvent.ACTION_MOVE:
if (currentButton < 0) {
return false;
}
if (animInProgress == true) {
return true;
}
float delta_y = motionEvent.getRawY() - touchDownY;
float delta_x = motionEvent.getRawX() - touchDownX;
updateButtonPos(new PointF((int) delta_x, (int) delta_y));
if (Math.abs(delta_x) > slop || Math.abs(delta_y) > slop) {
buttonInfoArray[currentButton].isOnClick = false;
parentContainer.requestDisallowInterceptTouchEvent(true);
}
return true;
case MotionEvent.ACTION_UP:
animCount = 0;
if (currentButton < 0) {
return false;
}
if(animInProgress == true) {
return true;
}
animInProgress = true;
for (ButtonInfo currentButtonInfo : buttonInfoArray) {
applyResetAnimation(currentButtonInfo);
if (currentButtonInfo.isOnClick) {
// TODO onClick code
String currentTag = (String) currentButtonInfo.btnObj.getTag();
if(currentTag.equalsIgnoreCase(FIRST_TAG)) {
//handle first button click
} else if(currentTag.equalsIgnoreCase(SECOND_TAG)) {
//handle second button click
} else if(currentTag.equalsIgnoreCase(THIRD_TAG)) {
//handle third button click
} else if(currentTag.equalsIgnoreCase(FORTH_TAG)) {
//handle forth button click
}
}
}
return true;
case MotionEvent.ACTION_DOWN:
if (currentButton >= 0) {
return false;
}
if (animInProgress == true) {
return true;
}
animCount = 0;
int buttonIndex = 0;
for (buttonIndex = 0; buttonIndex < NUM_BUTTONS; buttonIndex++) {
final ButtonInfo currentButtonInfo = buttonInfoArray[buttonIndex];
if (isRectHit(currentButtonInfo.btnObj, motionEvent,
currentButtonInfo.OrigPos)) {
currentButton = buttonIndex;
touchDownX = (int) motionEvent.getRawX();
touchDownY = (int) motionEvent.getRawY();
currentButtonInfo.isOnClick = true;
currentButtonInfo.btnObj.setPressed(true);
break;
}
}
if (buttonIndex == NUM_BUTTONS) {
currentButton = -1;
}
break;
default:
break;
}
return false;
}
};
/**
* Get X POS
* #param yPos
* #return
*/
public float getXPos(float yPos) {
float xPos = (float) (centerPoint.x
+ Math.sqrt((radius * radius)
- ((yPos - centerPoint.y) * (yPos - centerPoint.y))));
return xPos;
}
/**
* Get YPos based on X
* #param xPos
* #param isPositive
* #return
*/
public float getYPos(float xPos, boolean isPositive) {
if (isPositive)
return (float) (centerPoint.y - Math.sqrt((radius * radius)
- ((xPos - centerPoint.x) * (xPos - centerPoint.x))));
else
return (float) (centerPoint.y + Math.sqrt((radius * radius)
- ((xPos - centerPoint.x) * (xPos - centerPoint.x))));
}
/**
* Get New angle from define point
* #param newPoint
* #return
*/
private double getNewAngle(PointF newPoint) {
double deltaY = newPoint.y - centerPoint.y;
double deltaX = newPoint.x - centerPoint.x;
double newPointAngle = Math.atan(-1.0 * deltaY / deltaX);
return newPointAngle;
}
/**
* get Point By Angle
* #param angle
* #return
*/
private PointF getPointByAngle(double angle) {
PointF newPos;
double newX = centerPoint.x + Math.cos(angle) * radius;
double newY = (centerPoint.y) - (Math.sin(angle) * radius);
newPos = new PointF((int) newX, (int) newY);
return newPos;
}
/**
* Set new location for passed button
* #param currentButtonIndex
* #param effectiveDelta
* #param percentageCompleted
* #return
*/
private double updateControl(int currentButtonIndex, PointF effectiveDelta,
double percentageCompleted) {
PointF newPos = new PointF();
StringBuilder s1 = new StringBuilder();
double maxAngleForCurrentButton = buttonInfoArray[currentButtonIndex].maxAngle;
double minAngleForCurrentButton = buttonInfoArray[currentButtonIndex].minAngle;
double targetAngleForCurrentButton;
if (effectiveDelta.y > 0) {
targetAngleForCurrentButton = maxAngleForCurrentButton;
} else {
targetAngleForCurrentButton = minAngleForCurrentButton;
}
if (percentageCompleted == -1) {
boolean isYDisplacement = effectiveDelta.y > effectiveDelta.x ? true
: false;
isYDisplacement = true;
if (isYDisplacement) {
float newY = buttonInfoArray[currentButtonIndex].OrigPos.y
+ effectiveDelta.y;
if (newY > (centerPoint.y) + (int) radius) {
newY = (centerPoint.y) + (int) radius;
} else if (newY < (centerPoint.y) - (int) radius) {
newY = (centerPoint.y) - (int) radius;
}
float newX = getXPos(newY);
newPos = new PointF(newX, newY);
s1.append("isYDisplacement true : ");
}
} else {
double effectiveAngle = buttonInfoArray[currentButtonIndex].origAngle
+ ((targetAngleForCurrentButton - buttonInfoArray[currentButtonIndex].origAngle) * percentageCompleted);
newPos = getPointByAngle(effectiveAngle);
s1.append("percentage completed : " + percentageCompleted + " : "
+ effectiveAngle);
}
double newAngle = getNewAngle(newPos);
// For angle, reverse condition, because in 1st quarter, it is +ve, in
// 4th quarter, it is -ve.
if (newAngle < maxAngleForCurrentButton) {
newAngle = maxAngleForCurrentButton;
newPos = getPointByAngle(newAngle);
s1.append("max angle : " + newAngle);
}
if (newAngle > minAngleForCurrentButton) {
newAngle = minAngleForCurrentButton;
newPos = getPointByAngle(newAngle);
s1.append("min angle : " + newAngle);
}
setTranslationX(buttonInfoArray[currentButtonIndex].btnObj,
newPos.x - 50);
setTranslationY(buttonInfoArray[currentButtonIndex].btnObj,
newPos.y - 50);
return newAngle;
}
/**
* Set button Position
* #param deltaPoint
*/
public void updateButtonPos(PointF deltaPoint) {
for (int buttonIndex = 0; buttonIndex < NUM_BUTTONS; buttonIndex++) {
if (currentButton == buttonIndex) {
buttonInfoArray[buttonIndex].currentAngle = updateControl(
buttonIndex, deltaPoint, -1);
double targetAngleForCurrentButton;
if (deltaPoint.y > 0) {
targetAngleForCurrentButton = buttonInfoArray[buttonIndex].maxAngle;
} else {
targetAngleForCurrentButton = buttonInfoArray[buttonIndex].minAngle;
}
double percentageCompleted = (1.0 * (buttonInfoArray[buttonIndex].currentAngle - buttonInfoArray[buttonIndex].origAngle))
/ (targetAngleForCurrentButton - buttonInfoArray[buttonIndex].origAngle);
for (int innerButtonIndex = 0; innerButtonIndex < NUM_BUTTONS; innerButtonIndex++) {
if (innerButtonIndex == buttonIndex)
continue;
buttonInfoArray[innerButtonIndex].currentAngle = updateControl(
innerButtonIndex, deltaPoint, percentageCompleted);
}
break;
}
}
}
/**
* Find whether touch in button's rectanlge or not
* #param v
* #param rect
*/
private static void getHitRect(View v, Rect rect) {
rect.left = (int) com.nineoldandroids.view.ViewHelper.getX(v);
rect.top = (int) com.nineoldandroids.view.ViewHelper.getY(v);
rect.right = rect.left + v.getWidth();
rect.bottom = rect.top + v.getHeight();
}
private boolean isRectHit(View viewObj, MotionEvent motionEvent,
PointF viewOrigPos) {
Rect outRect = new Rect();
int x = (int) motionEvent.getX();
int y = (int) motionEvent.getY();
getHitRect(viewObj, outRect);
if (outRect.contains(x, y)) {
return true;
} else {
return false;
}
}
/**
* On Finish update transition
*/
#Override
public void finish() {
super.finish();
overridePendingTransition(R.anim.activityfinishin, R.anim.activityfinishout);
}
/**
* On Native Back Pressed
*/
#Override
public void onBackPressed() {
super.onBackPressed();
finish();
}
}
Here is the image URL for the alphabetical list view. I couldn't post it here as stackoverflow restricts me not having more reputations.
http://www.freeimagehosting.net/hwg9g
How to show this alphabetical scrollview in the left side of the screen. I have got a sample application from internet for alphabetical scrollview and i have implemented with my project :-( As a beginner i do not understand their way of coding. They have used drawRoundRect method to draw this. I regret drawRoundRect and some paint stuffs are not familiar to me..!
public class IndexScroller {
private float mIndexbarWidth;
private float mIndexbarMargin;
private float mPreviewPadding;
private float mDensity;
private float mScaledDensity;
private float mAlphaRate;
private int mState = STATE_HIDDEN;
private int mListViewWidth;
private int mListViewHeight;
private int mCurrentSection = -1;
private boolean mIsIndexing = false;
private ListView mListView = null;
private SectionIndexer mIndexer = null;
private String[] mSections = null;
private RectF mIndexbarRect;
private static final int STATE_HIDDEN = 0;
private static final int STATE_SHOWING = 1;
private static final int STATE_SHOWN = 2;
private static final int STATE_HIDING = 3;
public IndexScroller(Context context, ListView lv) {
mDensity = context.getResources().getDisplayMetrics().density;
mScaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
mListView = lv;
setAdapter(mListView.getAdapter());
mIndexbarWidth = 20 * mDensity;
mIndexbarMargin = 10 * mDensity;
mPreviewPadding = 5 * mDensity;
}
public void draw(Canvas canvas) {
if (mState == STATE_HIDDEN)
return;
// mAlphaRate determines the rate of opacity
Paint indexbarPaint = new Paint();
indexbarPaint.setColor(Color.BLACK);
indexbarPaint.setAlpha((int) (64 * mAlphaRate));
indexbarPaint.setAntiAlias(true);
canvas.drawRoundRect(mIndexbarRect, 5 * mDensity, 5 * mDensity,
indexbarPaint);
if (mSections != null && mSections.length > 0) {
// Preview is shown when mCurrentSection is set
if (mCurrentSection >= 0) {
Paint previewPaint = new Paint();
previewPaint.setColor(Color.BLACK);
previewPaint.setAlpha(96);
previewPaint.setAntiAlias(true);
previewPaint.setShadowLayer(3, 0, 0, Color.argb(64, 0, 0, 0));
Paint previewTextPaint = new Paint();
previewTextPaint.setColor(Color.WHITE);
previewTextPaint.setAntiAlias(true);
previewTextPaint.setTextSize(50 * mScaledDensity);
float previewTextWidth = previewTextPaint
.measureText(mSections[mCurrentSection]);
float previewSize = 2 * mPreviewPadding
+ previewTextPaint.descent()
- previewTextPaint.ascent();
RectF previewRect = new RectF(
(mListViewWidth - previewSize) / 2,
(mListViewHeight - previewSize) / 2,
(mListViewWidth - previewSize) / 2 + previewSize,
(mListViewHeight - previewSize) / 2 + previewSize);
canvas.drawRoundRect(previewRect, 5 * mDensity, 5 * mDensity,
previewPaint);
canvas.drawText(
mSections[mCurrentSection],
previewRect.left + (previewSize - previewTextWidth) / 2
- 1,
previewRect.top + mPreviewPadding
- previewTextPaint.ascent() + 1,
previewTextPaint);
}
Paint indexPaint = new Paint();
indexPaint.setColor(Color.WHITE);
indexPaint.setAlpha((int) (255 * mAlphaRate));
indexPaint.setAntiAlias(true);
indexPaint.setTextSize(12 * mScaledDensity);
float sectionHeight = (mIndexbarRect.height() - 2 * mIndexbarMargin)
/ mSections.length;
float paddingTop = (sectionHeight - (indexPaint.descent() - indexPaint
.ascent())) / 2;
for (int i = 0; i < mSections.length; i++) {
float paddingLeft = (mIndexbarWidth - indexPaint
.measureText(mSections[i])) / 2;
canvas.drawText(mSections[i], mIndexbarRect.left + paddingLeft,
mIndexbarRect.top + mIndexbarMargin + sectionHeight * i
+ paddingTop - indexPaint.ascent(), indexPaint);
}
}
}
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// If down event occurs inside index bar region, start indexing
if (mState != STATE_HIDDEN && contains(ev.getX(), ev.getY())) {
setState(STATE_SHOWN);
// It demonstrates that the motion event started from index bar
mIsIndexing = true;
// Determine which section the point is in, and move the list to
// that section
mCurrentSection = getSectionByPoint(ev.getY());
mListView.setSelection(mIndexer
.getPositionForSection(mCurrentSection));
return true;
}
break;
case MotionEvent.ACTION_MOVE:
if (mIsIndexing) {
// If this event moves inside index bar
if (contains(ev.getX(), ev.getY())) {
// Determine which section the point is in, and move the
// list to that section
mCurrentSection = getSectionByPoint(ev.getY());
mListView.setSelection(mIndexer
.getPositionForSection(mCurrentSection));
}
return true;
}
break;
case MotionEvent.ACTION_UP:
if (mIsIndexing) {
mIsIndexing = false;
mCurrentSection = -1;
}
if (mState == STATE_SHOWN)
setState(STATE_HIDING);
break;
}
return false;
}
public void onSizeChanged(int w, int h, int oldw, int oldh) {
mListViewWidth = w;
mListViewHeight = h;
mIndexbarRect = new RectF(w - mIndexbarMargin - mIndexbarWidth,
mIndexbarMargin, w - mIndexbarMargin, h - mIndexbarMargin);
}
public void show() {
if (mState == STATE_HIDDEN)
setState(STATE_SHOWING);
else if (mState == STATE_HIDING)
setState(STATE_HIDING);
}
public void hide() {
if (mState == STATE_SHOWN)
setState(STATE_HIDING);
}
public void setAdapter(Adapter adapter) {
if (adapter instanceof SectionIndexer) {
mIndexer = (SectionIndexer) adapter;
mSections = (String[]) mIndexer.getSections();
}
}
private void setState(int state) {
if (state < STATE_HIDDEN || state > STATE_HIDING)
return;
mState = state;
switch (mState) {
case STATE_HIDDEN:
// Cancel any fade effect
mHandler.removeMessages(0);
break;
case STATE_SHOWING:
// Start to fade in
mAlphaRate = 0;
fade(0);
break;
case STATE_SHOWN:
// Cancel any fade effect
mHandler.removeMessages(0);
break;
case STATE_HIDING:
// Start to fade out after three seconds
mAlphaRate = 1;
fade(5000);
break;
}
}
private boolean contains(float x, float y) {
// Determine if the point is in index bar region, which includes the
// right margin of the bar
return (x >= mIndexbarRect.left && y >= mIndexbarRect.top && y <= mIndexbarRect.top
+ mIndexbarRect.height());
}
private int getSectionByPoint(float y) {
if (mSections == null || mSections.length == 0)
return 0;
if (y < mIndexbarRect.top + mIndexbarMargin)
return 0;
if (y >= mIndexbarRect.top + mIndexbarRect.height() - mIndexbarMargin)
return mSections.length - 1;
return (int) ((y - mIndexbarRect.top - mIndexbarMargin) / ((mIndexbarRect
.height() - 2 * mIndexbarMargin) / mSections.length));
}
private void fade(long delay) {
mHandler.removeMessages(0);
mHandler.sendEmptyMessageAtTime(0, SystemClock.uptimeMillis() + delay);
}
private Handler mHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (mState) {
case STATE_SHOWING:
// Fade in effect
mAlphaRate += (1 - mAlphaRate) * 0.2;
if (mAlphaRate > 0.9) {
mAlphaRate = 1;
setState(STATE_SHOWN);
}
mListView.invalidate();
fade(10);
break;
case STATE_SHOWN:
// If no action, hide automatically
setState(STATE_HIDING);
break;
case STATE_HIDING:
// Fade out effect
mAlphaRate -= mAlphaRate * 0.2;
if (mAlphaRate < 0.1) {
mAlphaRate = 0;
setState(STATE_HIDDEN);
}
mListView.invalidate();
fade(10);
break;
}
}
};
It would be really nice if you help me to show this alphabetical scroll bar in the left side of the screen.
I got that this way,
public void onSizeChanged(int w, int h, int oldw, int oldh) {
mListViewWidth = w;
mListViewHeight = h;
mIndexbarRect = new RectF(mIndexbarMargin, mIndexbarMargin,
mIndexbarMargin + mIndexbarWidth, h - mIndexbarMargin);
}
This is the method to draw the rectangle in left side of the screen.
private boolean contains(float x, float y) {
return (x <= mIndexbarRect.right && y >= mIndexbarRect.top && y <= mIndexbarRect.top
+ mIndexbarRect.height());
}
It will work smoothly if you change the function to the below. sorry for the posting this answer little late.
please check out : Indexable list view
It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 10 years ago.
I want to create the wave forms of audio file in android. Can anyone provide me an example source code?
Create class and extend View. Then create Listener interface for your class.
public class WaveformCls extends View {
public interface WaveformListener {
public void waveformTouchStart(float x);
public void waveformTouchMove(float x);
public void waveformTouchEnd();
public void waveformFling(float x);
public void waveformDraw();
};
Create Paint object as per your requirement.
Create one method which is initialized all Paint object.
public WaveformView(Context context, AttributeSet attrs) {
super(context, attrs);
// We don't want keys, the markers get these
setFocusable(false);
mGridPaint = new Paint();
mGridPaint.setAntiAlias(false);
mGridPaint.setColor(
getResources().getColor(R.drawable.grid_line));
mSelectedLinePaint = new Paint();
mSelectedLinePaint.setAntiAlias(false);
mSelectedLinePaint.setColor(
getResources().getColor(R.drawable.waveform_selected));
mUnselectedLinePaint = new Paint();
mUnselectedLinePaint.setAntiAlias(false);
mUnselectedLinePaint.setColor(
getResources().getColor(R.drawable.waveform_unselected));
mUnselectedBkgndLinePaint = new Paint();
mUnselectedBkgndLinePaint.setAntiAlias(false);
mUnselectedBkgndLinePaint.setColor(
getResources().getColor(
R.drawable.waveform_unselected_bkgnd_overlay));
mBorderLinePaint = new Paint();
mBorderLinePaint.setAntiAlias(true);
mBorderLinePaint.setStrokeWidth(1.5f);
mBorderLinePaint.setPathEffect(
new DashPathEffect(new float[] { 3.0f, 2.0f }, 0.0f));
mBorderLinePaint.setColor(
getResources().getColor(R.drawable.selection_border));
mPlaybackLinePaint = new Paint();
mPlaybackLinePaint.setAntiAlias(false);
mPlaybackLinePaint.setColor(
getResources().getColor(R.drawable.playback_indicator));
mTimecodePaint = new Paint();
mTimecodePaint.setTextSize(12);
mTimecodePaint.setAntiAlias(true);
mTimecodePaint.setColor(
getResources().getColor(R.drawable.timecode));
mTimecodePaint.setShadowLayer(
2, 1, 1,
getResources().getColor(R.drawable.timecode_shadow));
mGestureDetector = new GestureDetector(
context,
new GestureDetector.SimpleOnGestureListener() {
public boolean onFling(
MotionEvent e1, MotionEvent e2, float vx, float vy) {
mListener.waveformFling(vx);
return true;
}
});
mSoundFile = null;
mLenByZoomLevel = null;
mValuesByZoomLevel = null;
mHeightsAtThisZoomLevel = null;
mOffset = 0;
mPlaybackPos = -1;
mSelectionStart = 0;
mSelectionEnd = 0;
mDensity = 1.0f;
mInitialized = false;
}
You need to override onTouchEvent
#Override
public boolean onTouchEvent(MotionEvent event) {
if (mGestureDetector.onTouchEvent(event)) {
return true;
}
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
mListener.waveformTouchStart(event.getX());
break;
case MotionEvent.ACTION_MOVE:
mListener.waveformTouchMove(event.getX());
break;
case MotionEvent.ACTION_UP:
mListener.waveformTouchEnd();
break;
}
return true;
}
public void setSoundFile(CheapSoundFile soundFile) {
mSoundFile = soundFile;
mSampleRate = mSoundFile.getSampleRate();
mSamplesPerFrame = mSoundFile.getSamplesPerFrame();
computeDoublesForAllZoomLevels();
mHeightsAtThisZoomLevel = null;
}
override the draw method which draw wave on your screen.
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mSoundFile == null)
return;
if (mHeightsAtThisZoomLevel == null)
computeIntsForThisZoomLevel();
// Draw waveform
int measuredWidth = getMeasuredWidth();
int measuredHeight = getMeasuredHeight();
int start = mOffset;
int width = mHeightsAtThisZoomLevel.length - start;
int ctr = measuredHeight / 2;
if (width > measuredWidth)
width = measuredWidth;
// Draw grid
double onePixelInSecs = pixelsToSeconds(1);
boolean onlyEveryFiveSecs = (onePixelInSecs > 1.0 / 50.0);
double fractionalSecs = mOffset * onePixelInSecs;
int integerSecs = (int) fractionalSecs;
int i = 0;
while (i < width) {
i++;
fractionalSecs += onePixelInSecs;
int integerSecsNew = (int) fractionalSecs;
if (integerSecsNew != integerSecs) {
integerSecs = integerSecsNew;
if (!onlyEveryFiveSecs || 0 == (integerSecs % 5)) {
canvas.drawLine(i, 0, i, measuredHeight, mGridPaint);
}
}
}
// Draw waveform
for (i = 0; i < width; i++) {
Paint paint;
if (i + start >= mSelectionStart &&
i + start < mSelectionEnd) {
paint = mSelectedLinePaint;
} else {
drawWaveformLine(canvas, i, 0, measuredHeight,
mUnselectedBkgndLinePaint);
paint = mUnselectedLinePaint;
}
drawWaveformLine(
canvas, i,
ctr - mHeightsAtThisZoomLevel[start + i],
ctr + 1 + mHeightsAtThisZoomLevel[start + i],
paint);
if (i + start == mPlaybackPos) {
canvas.drawLine(i, 0, i, measuredHeight, mPlaybackLinePaint);
}
}
// If we can see the right edge of the waveform, draw the
// non-waveform area to the right as unselected
for (i = width; i < measuredWidth; i++) {
drawWaveformLine(canvas, i, 0, measuredHeight,
mUnselectedBkgndLinePaint);
}
// Draw borders
canvas.drawLine(
mSelectionStart - mOffset + 0.5f, 30,
mSelectionStart - mOffset + 0.5f, measuredHeight,
mBorderLinePaint);
canvas.drawLine(
mSelectionEnd - mOffset + 0.5f, 0,
mSelectionEnd - mOffset + 0.5f, measuredHeight - 30,
mBorderLinePaint);
// Draw timecode
double timecodeIntervalSecs = 1.0;
if (timecodeIntervalSecs / onePixelInSecs < 50) {
timecodeIntervalSecs = 5.0;
}
if (timecodeIntervalSecs / onePixelInSecs < 50) {
timecodeIntervalSecs = 15.0;
}
// Draw grid
fractionalSecs = mOffset * onePixelInSecs;
int integerTimecode = (int) (fractionalSecs / timecodeIntervalSecs);
i = 0;
while (i < width) {
i++;
fractionalSecs += onePixelInSecs;
integerSecs = (int) fractionalSecs;
int integerTimecodeNew = (int) (fractionalSecs /
timecodeIntervalSecs);
if (integerTimecodeNew != integerTimecode) {
integerTimecode = integerTimecodeNew;
// Turn, e.g. 67 seconds into "1:07"
String timecodeMinutes = "" + (integerSecs / 60);
String timecodeSeconds = "" + (integerSecs % 60);
if ((integerSecs % 60) < 10) {
timecodeSeconds = "0" + timecodeSeconds;
}
String timecodeStr = timecodeMinutes + ":" + timecodeSeconds;
float offset = (float) (
0.5 * mTimecodePaint.measureText(timecodeStr));
canvas.drawText(timecodeStr,
i - offset,
(int)(12 * mDensity),
mTimecodePaint);
}
}
if (mListener != null) {
mListener.waveformDraw();
}
}
Here is complete source code of Rindroid which is useful for you
Source code for waveform