I'm still fairly new to android and I'm currently trying to create pong.
At the moment I am trying to animate two images, one is pong's ball and the second is the paddle. Right now I have those two images and I've got them animating. The ball behaves like I want it to bouncing around the x and y axis and I have the paddle sliding back and forth across the x axis but for some reason the paddle behaves weirdly.
The paddle for some reason animates choppy, stopping and starting whenever it wants. But whenever I remove the ball animation from the code the paddle acts and behaves like I intended it to
If anyone has any suggestion for what I am doing wrong or overlooking, I am all ears, Thanks!
package com.example.pongtest;
import java.util.Timer;
import java.util.TimerTask;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.app.Activity;
public class MainActivity extends Activity {
LinearLayout lay1;
ImageView ballImg, paddleImg;
TimerTask thisTimerTask;
Timer thisTimer;
LinearLayout.LayoutParams params;
float ballPosX=10;
float ballPosY=10;
float paddlePosX=200;
float paddlePosY=10;
float ballOnLeftWall=0;
float ballOnRightWall=270;
float ballOnBottomWall=400;
float ballOnTopWall=0;
float paddleOnLeftWall=0;
float paddleOnRightWall=200;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
lay1 = new LinearLayout(this);
ballImg = new ImageView(this);
ballImg.setBackgroundResource(R.drawable.circlewdot);
setPosition(0,0,50,50);
ballImg.setLayoutParams(params);
paddleImg=new ImageView(this);
paddleImg.setBackgroundResource(R.drawable.paddle);
setPosition(0,0,50,10);
paddleImg.setLayoutParams(params);
lay1.addView(ballImg);
lay1.addView(paddleImg);
setContentView(lay1);
thisTimerTask = new ThisTimerClass();
thisTimer = new Timer();
//setup frame rate of timer
thisTimer.scheduleAtFixedRate(thisTimerTask, 2000, 16);
}
public void setPosition(float x, float y, float width, float height) {
params = new LinearLayout.LayoutParams(0,0);
params.topMargin = (int)y;
params.leftMargin = (int)x;
params.width = (int)width;
params.height = (int)height;
}
class ThisTimerClass extends TimerTask {
boolean directionOfBall_isRight = true;
boolean directionOfBall_isDown = true;
boolean directionOfPaddle_isLeft = true;
#Override
public void run() {
// Will be called each time the timer is fired.
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
setPosition(ballPosX,ballPosY,50,50);
ballImg.setLayoutParams(params);
setPosition(paddlePosX,paddlePosY,50,10);
paddleImg.setLayoutParams(params);
//animates ball across the screen
if (directionOfBall_isRight == true)
{ballPosX = (ballPosX + 1);}
else if (directionOfBall_isRight == false)
{ballPosX = (ballPosX - 1);}
if (directionOfBall_isDown == true)
{ballPosY = (ballPosY + 1);}
else if (directionOfBall_isDown == false)
{ballPosY = (ballPosY - 1);}
//redirects ball
if (ballPosX > ballOnRightWall)
{directionOfBall_isRight = false;}
else if (ballPosX < ballOnLeftWall)
{directionOfBall_isRight = true;}
if (ballPosY > ballOnBottomWall)
{directionOfBall_isDown = false;}
else if (ballPosY < ballOnTopWall)
{directionOfBall_isDown = true;}
//animates paddle across the screen
if (directionOfPaddle_isLeft == true)
{paddlePosX = (paddlePosX - 1);}
else if (directionOfPaddle_isLeft == false)
{paddlePosX = (paddlePosX + 1);}
//redirects paddle
if (paddlePosX < paddleOnLeftWall)
{directionOfPaddle_isLeft = false;}
else if (paddlePosX > paddleOnRightWall)
{directionOfPaddle_isLeft = true;}
}
});
}
}
}
this is possibly because you are controlling the ball and the paddle on the same thread. the system can't concurrently do two things from a single thread!
Related
I am android developer .I create system window overlay .I want to bounce bubble only stay left top corner, right top corner,bottom left corner.bottom right corner .you stretch ball from top right corner to center then ball direct bounce top right corner .ball just stay only four corner like Facebook messenger . i also try some days but i don't know to do this.here is my code any one please tell what is change in this code.
package com.example.bubble;
import java.io.IOException;
import java.io.InputStream;
import java.util.Timer;
import java.util.TimerTask;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.graphics.drawable.BitmapDrawable;
import android.os.Handler;
import android.os.IBinder;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.widget.RelativeLayout;
#SuppressWarnings("deprecation")
public class PlayerService extends Service
{
private static final int HIDDEN_FRACTION = 20; // Controls fraction of the tray hidden when open
private static final int MOVEMENT_REGION_FRACTION = 20; // Controls fraction of y-axis on screen within which the tray stays.
private static final int VIEW_CROP_FRACTION = 12; // Controls fraction of the tray chipped at the right end.
private static final int ANIMATION_FRAME_RATE = 30; // Animation frame rate per second.
private static final int VIEW_DIM_X_DP = 170; // Width of the tray in dps
private static final int VIEW_DIM_Y_DP = 170; // Height of the tray in dps
// Layout containers for various widgets
private WindowManager mWindowManager; // Reference to the window
private WindowManager.LayoutParams mWindowParams; // Parameters of the root layout
private RelativeLayout mRootLayout; // Root layout
private RelativeLayout mCoverLayout; // Contains album cover of the active song
// Variables that control drag
private int mStartDragX;
//private int mStartDragY; // Unused as yet
private int mPrevDragX;
private int mPrevDragY;
private boolean mIsTrayOpen = true;
// Controls for animations
private Timer mTrayAnimationTimer;
private TrayAnimationTimerTask mTrayTimerTask;
private Handler mAnimationHandler = new Handler();
#Override
public IBinder onBind(Intent intent) {
// Not used
return null;
}
#Override
public void onCreate() {
// Get references to all the views and add them to root view as needed.
mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
mRootLayout = (RelativeLayout) LayoutInflater.from(this).
inflate(R.layout.service_player, null);
mCoverLayout = (RelativeLayout) mRootLayout.findViewById(R.id.cover_layout);
mCoverLayout.setOnTouchListener(new TrayTouchListener());
mWindowParams = new WindowManager.LayoutParams(
Utils.dpToPixels(VIEW_DIM_X_DP, getResources()),
Utils.dpToPixels(VIEW_DIM_Y_DP, getResources()),
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
PixelFormat.TRANSLUCENT);
mWindowParams.gravity = Gravity.TOP | Gravity.LEFT;
mWindowManager.addView(mRootLayout, mWindowParams);
// Post these actions at the end of looper message queue so that the layout is
// fully inflated once these functions execute
mRootLayout.postDelayed(new Runnable() {
#Override
public void run() {
// Reusable variables
RelativeLayout.LayoutParams params;
InputStream is;
Bitmap bmap;
int containerNewWidth = (VIEW_CROP_FRACTION-1)*mCoverLayout.getHeight()/VIEW_CROP_FRACTION;
// Setup background album cover
is=null;
try {
//is = getAssets().open(mPlaylist.getCurrentSongInfo().mAlbumCoverPath);
is=getAssets().open("adele.png");
} catch (IOException e) {
e.printStackTrace();
}
bmap = Utils.loadMaskedBitmap(is, mCoverLayout.getHeight(), containerNewWidth);
params = (RelativeLayout.LayoutParams) mCoverLayout.getLayoutParams();
params.width = (bmap.getWidth() * mCoverLayout.getHeight()) / bmap.getHeight();
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,0);
mCoverLayout.setLayoutParams(params);
mCoverLayout.requestLayout();
mCoverLayout.setBackgroundDrawable(new BitmapDrawable(getResources(), bmap));
// Setup the root layout
mWindowParams.x = -mCoverLayout.getLayoutParams().width;
mWindowParams.y = (getApplicationContext().getResources().getDisplayMetrics().heightPixels-mRootLayout.getHeight()) / 2;
mWindowManager.updateViewLayout(mRootLayout, mWindowParams);
// Make everything visible
mRootLayout.setVisibility(View.VISIBLE);
// Animate the Tray
mTrayTimerTask = new TrayAnimationTimerTask();
mTrayAnimationTimer = new Timer();
mTrayAnimationTimer.schedule(mTrayTimerTask, 0, ANIMATION_FRAME_RATE);
}
}, ANIMATION_FRAME_RATE);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent.getBooleanExtra("stop_service", false)){
// If it's a call from the notification, stop the service.
stopSelf();
}else{
// Make the service run in foreground so that the system does not shut it down.
Intent notificationIntent = new Intent(this, PlayerService.class);
notificationIntent.putExtra("stop_service", true);
PendingIntent pendingIntent = PendingIntent.getService(this, 0, notificationIntent, 0);
Notification notification = new Notification(
R.drawable.ic_launcher,
"Spotify tray launched",
System.currentTimeMillis());
notification.setLatestEventInfo(
this,
"Spotify tray",
"Tap to close the widget.",
pendingIntent);
startForeground(86, notification);
}
return START_STICKY;
}
// The app is closing.
#Override
public void onDestroy() {
if (mRootLayout != null)
mWindowManager.removeView(mRootLayout);
}
// Drags the tray as per touch info
private void dragTray(int action, int x, int y){
switch (action){
case MotionEvent.ACTION_DOWN:
// Cancel any currently running animations/automatic tray movements.
if (mTrayTimerTask!=null){
mTrayTimerTask.cancel();
mTrayAnimationTimer.cancel();
}
// Store the start points
mStartDragX = x;
//mStartDragY = y;
mPrevDragX = x;
mPrevDragY = y;
break;
case MotionEvent.ACTION_MOVE:
// Calculate position of the whole view according to the drag, and update layout.
float deltaX = x-mPrevDragX;
float deltaY = y-mPrevDragY;
mWindowParams.x += deltaX;
mWindowParams.y += deltaY;
mPrevDragX = x;
mPrevDragY = y;
mWindowManager.updateViewLayout(mRootLayout, mWindowParams);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// When the view is released, bring it back to "open" or "closed" state.
if ((mIsTrayOpen && (x-mStartDragX)<=0) ||
(!mIsTrayOpen && (x-mStartDragX)>=0))
mIsTrayOpen = !mIsTrayOpen;
mTrayTimerTask = new TrayAnimationTimerTask();
mTrayAnimationTimer = new Timer();
mTrayAnimationTimer.schedule(mTrayTimerTask, 0, ANIMATION_FRAME_RATE);
break;
}
}
// Listens to the touch events on the tray.
private class TrayTouchListener implements OnTouchListener {
#Override
public boolean onTouch(View v, MotionEvent event) {
final int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// Filter and redirect the events to dragTray()
dragTray(action, (int)event.getRawX(), (int)event.getRawY());
break;
default:
return false;
}
return true;
}
}
// Timer for animation/automatic movement of the tray.
private class TrayAnimationTimerTask extends TimerTask{
// Ultimate destination coordinates toward which the tray will move
int mDestX;
int mDestY;
public TrayAnimationTimerTask(){
// Setup destination coordinates based on the tray state.
super();
if (!mIsTrayOpen){
mDestX = -mCoverLayout.getWidth();
}else{
mDestX = -mRootLayout.getWidth()/HIDDEN_FRACTION;
}
// Keep upper edge of the widget within the upper limit of screen
int screenHeight = getResources().getDisplayMetrics().heightPixels;
mDestY = Math.max(
screenHeight/MOVEMENT_REGION_FRACTION,
mWindowParams.y);
// Keep lower edge of the widget within the lower limit of screen
mDestY = Math.min(
((MOVEMENT_REGION_FRACTION-1)*screenHeight)/MOVEMENT_REGION_FRACTION - mRootLayout.getWidth(),
mDestY);
mDestX = Math.max(mWindowParams.x,screenHeight/MOVEMENT_REGION_FRACTION);
// Keep lower edge of the widget within the lower limit of screen
mDestX = Math.min(mDestX,((MOVEMENT_REGION_FRACTION-1)*screenHeight)/MOVEMENT_REGION_FRACTION - mRootLayout.getWidth());
}
// This function is called after every frame.
#Override
public void run() {
// handler is used to run the function on main UI thread in order to
// access the layouts and UI elements.
mAnimationHandler.post(new Runnable() {
#Override
public void run() {
// Update coordinates of the tray
mWindowParams.x = (2*(mWindowParams.x-mDestX))/3 + mDestX;
mWindowParams.y = (2*(mWindowParams.y-mDestY))/3 + mDestY;
mWindowManager.updateViewLayout(mRootLayout, mWindowParams);
// Cancel animation when the destination is reached
if (Math.abs(mWindowParams.x-mDestX)<2 && Math.abs(mWindowParams.y-mDestY)<2){
TrayAnimationTimerTask.this.cancel();
mTrayAnimationTimer.cancel();
}
}
});
}
}
}
I am working on multi finger gestures, I tried using Google gesture builder but it will not support for multi finger gestures, How to recognize two finger V shape gesture in android.?
I'm sure you can use a ScaleGestureDetector for this.
After all a "V" from the top is just a pinch with some translation on the Y axis.
So I think you can analyse the focus point and scale factor to determine a "V" has taken place.
Here is a working example. In the end I didn't need to look at the scale. There are a couple of sensitivity values you can adjust such as the range of acceptable angles and the ratio of Y to X movement.
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.TextView;
public class MainActivity extends Activity {
public static class VListener extends
ScaleGestureDetector.SimpleOnScaleGestureListener {
private float initialFocusX;
private float initialFocusY;
#Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
Log.d(TAG, String.format("%.2f,%.2f s:%.2f", detector.getFocusX(),
detector.getFocusY(), detector.getScaleFactor()));
initialFocusX = detector.getFocusX();
initialFocusY = detector.getFocusY();
return true;
}
#Override
public void onScaleEnd(ScaleGestureDetector detector) {
float deltaX = detector.getFocusX() - initialFocusX;
float deltaY = detector.getFocusY() - initialFocusY;
if (deltaY == 0) {
Log.d(TAG, "Not a V, no Y movement");
onNonV();
return;
}
float yMovementRatio = Math.abs(deltaY / deltaX);
if (yMovementRatio < 4) {
Log.d(TAG,
String.format(
"Not a V, the ratio of Y movement to X was not high enough: %.2f",
yMovementRatio));
onNonV();
return;
}
float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
if (angle > 80 && angle < 100) {
Log.d(TAG, "V!");
onV();
return;
} else {
Log.d(TAG,
String.format(
"Not a V, the angle shows the V was drawn in the wrong direction: %.2f",
angle));
onNonV();
}
}
protected void onV() {
}
protected void onNonV() {
}
}
protected static final String TAG = "MainActivity";
private ScaleGestureDetector mScaleGestureDetector;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView t = (TextView) findViewById(R.id.vTextIndicator);
mScaleGestureDetector = new ScaleGestureDetector(this, new VListener() {
#Override
protected void onV() {
t.setText("V!");
}
#Override
protected void onNonV() {
t.setText("Non V");
}
});
}
public boolean onTouchEvent(MotionEvent event) {
boolean retVal = mScaleGestureDetector.onTouchEvent(event);
return retVal || super.onTouchEvent(event);
}
}
activity_main.xml Layout is just:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="#+id/vTextIndicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
</RelativeLayout>
Sample app - InteractiveChart.zip
This links might help - Draggin & Scaling and Handling Multi-Touch Gestures
I'm learning Android programming.
So I managed to implement a simple app that rolls a ball over the screen if you tilt yout phone. But right now it is as simple as:
if roll > 0 then xpos++ else xpos-- end and the same for ypos.
So I want to calculate a more exact direction and also I would like the ball to roll faster the more the phone is tilting.
So if I know the tilt in the roll direction and the pitch direction, how do I calculate the direction and speed of the ball?
Here is how:
package benyamephrem.tiltball;
import android.graphics.Point;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity;
import android.os.Handler;
import android.view.Display;
import android.view.Window;
import android.view.WindowManager.LayoutParams;
import android.widget.FrameLayout;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorManager;
import android.content.Context;
import android.content.res.Configuration;
import android.hardware.SensorEventListener;
import android.widget.Toast;
public class TiltBall extends ActionBarActivity {
BallView mBallView = null;
Handler RedrawHandler = new Handler(); //so redraw occurs in main thread
Timer mTmr = null;
TimerTask mTsk = null;
int mScrWidth, mScrHeight;
android.graphics.PointF mBallPos, mBallSpd;
#Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE); //hide title bar
//set app to full screen and keep screen on
getWindow().setFlags(0xFFFFFFFF, LayoutParams.FLAG_FULLSCREEN | LayoutParams.FLAG_KEEP_SCREEN_ON);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tilt_ball);
//create pointer to main screen
final FrameLayout mainView = (android.widget.FrameLayout) findViewById(R.id.main_view);
//get screen dimensions
Display display = getWindowManager().getDefaultDisplay();
mScrWidth = display.getWidth();
mScrHeight = display.getHeight();
mBallPos = new android.graphics.PointF();
mBallSpd = new android.graphics.PointF();
//create variables for ball position and speed
mBallPos.x = mScrWidth / 2;
mBallPos.y = mScrHeight / 5;
mBallSpd.x = 0;
mBallSpd.y = 0;
//create initial ball
mBallView = new BallView(this, mBallPos.x, mBallPos.y, 75);
mainView.addView(mBallView); //add ball to main screen
mBallView.invalidate(); //call onDraw in BallView
//listener for accelerometer, use anonymous class for simplicity
((SensorManager) getSystemService(Context.SENSOR_SERVICE)).registerListener(
new SensorEventListener() {
#Override
public void onSensorChanged(SensorEvent event) {
//set ball speed based on phone tilt (ignore Z axis)
//***Change speed here by multiplying event values by bigger numbers***
mBallSpd.x = -event.values[0] * (30/10);
mBallSpd.y = event.values[1] * (30/10);
//timer event will redraw ball
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
} //ignore
},
((SensorManager) getSystemService(Context.SENSOR_SERVICE))
.getSensorList(Sensor.TYPE_ACCELEROMETER).get(0),
SensorManager.SENSOR_DELAY_NORMAL);
//listener for touch event
mainView.setOnTouchListener(new android.view.View.OnTouchListener() {
public boolean onTouch(android.view.View v, android.view.MotionEvent e) {
//set ball position based on screen touch
mBallPos.x = e.getX();
mBallPos.y = e.getY();
//timer event will redraw ball
return true;
}
});
} //OnCreate
//listener for menu button on phone
#Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add("Exit"); //only one menu item
return super.onCreateOptionsMenu(menu);
}
//listener for menu item clicked
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
if (item.getTitle() == "Exit") //user clicked Exit
finish(); //will call onPause
return super.onOptionsItemSelected(item);
}
//For state flow see http://developer.android.com/reference/android/app/Activity.html
#Override
public void onPause() //app moved to background, stop background threads
{
mTmr.cancel(); //kill\release timer (our only background thread)
mTmr = null;
mTsk = null;
super.onPause();
}
#Override
public void onResume() //app moved to foreground (also occurs at app startup)
{
//create timer to move ball to new position
mTmr = new Timer();
mTsk = new TimerTask() {
public void run() {
//if debugging with external device,
// a log cat viewer will be needed on the device
Log.d("TiltBall", "Timer Hit - " + mBallPos.x + ":" + mBallPos.y);
//move ball based on current speed
mBallPos.x += mBallSpd.x;
mBallPos.y += mBallSpd.y;
//if ball goes off screen, reposition to opposite side of screen
if (mBallPos.x > mScrWidth) mBallPos.x = 0;
if (mBallPos.y > mScrHeight) mBallPos.y = 0;
if (mBallPos.x < 0) mBallPos.x = mScrWidth;
if (mBallPos.y < 0) mBallPos.y = mScrHeight;
//update ball class instance
mBallView.x = mBallPos.x;
mBallView.y = mBallPos.y;
//redraw ball. Must run in background thread to prevent thread lock.
RedrawHandler.post(new Runnable() {
public void run() {
mBallView.invalidate();
}
});
}
}; // TimerTask
mTmr.schedule(mTsk, 10, 10); //start timer
super.onResume();
} // onResume
#Override
public void onDestroy() //main thread stopped
{
super.onDestroy();
//wait for threads to exit before clearing app
System.runFinalizersOnExit(true);
//remove app from memory
android.os.Process.killProcess(android.os.Process.myPid());
}
//listener for config change.
//This is called when user tilts phone enough to trigger landscape view
// we want our app to stay in portrait view, so bypass event
#Override
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
}
} //TiltBallActivity
Here is the BallView class;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.View;
public class BallView extends View {
public float x;
public float y;
private final int r;
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
//construct new ball object
public BallView(Context context, float x, float y, int r) {
super(context);
//color hex is [transparncy][red][green][blue]
mPaint.setColor(0xFF1325E0); //not transparent. color is blue
this.x = x;
this.y = y;
this.r = r; //radius
}
//qcalled by invalidate()
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(x, y, r, mPaint);
}
public int getRadius(){
return r;
}
}
This is not my code but I forgot its source so credits to whoever made it. I am using it for a current project I'm working on.
I am trying to make a cannon game that is printed in an Android Developer's book and having trouble with the drawing of the actual game. I can hear my sounds, the alert dialog at the end of the game is being shown after my timer runs out even though I cant see the timer, and the white background is shown, just no game elements
Here is the main activity:
//CannonGame.java
//Main Activity for the Cannon game app.
package com.deitel.cannongame;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.media.AudioManager;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.GestureDetector.SimpleOnGestureListener;
public class CannonGame extends Activity {
private GestureDetector gestureDetector; //listens for double taps
private CannonView cannonView; //custom view to display the game
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); //call super's onCreate method
setContentView(R.layout.main); //inflate the layout
//get the cannonview
cannonView = (CannonView) findViewById(R.id.cannonView);
//initialize the GuestureDetector
gestureDetector = new GestureDetector(this, gestureListener);
//allow for volume keys to set game volume
setVolumeControlStream(AudioManager.STREAM_MUSIC);
} //end method onCreate
//when the app is pushed to the background, pause it
#Override
public void onPause(){
super.onPause(); //call the super method
cannonView.stopGame(); //terminates the game
} //end method onPause
//release resources
#Override
protected void onDestroy(){
super.onDestroy();
cannonView.releaseResources();
} //end method onDestroy
//called when the user touches the screen in this Activity
#Override
public boolean onTouchEvent(MotionEvent event){
//get int representing the type of action which caused this event
int action = event.getAction();
//the user touched the screen or dragged aong the screen
if (action == (MotionEvent.ACTION_DOWN) || action == MotionEvent.ACTION_MOVE)
cannonView.alignCannon(event); //align the cannon
//call the GuestureDetector's onTouchEvent method
return gestureDetector.onTouchEvent(event);
} //end onTouchEvent
//listens for touch events sent to the GuestureDetector
SimpleOnGestureListener gestureListener = new SimpleOnGestureListener(){
//called when the use doubletaps the screen
#Override
public boolean onDoubleTap(MotionEvent e){
cannonView.fireCannonball(e); //fire the cannonball
return true; //the event was handled
} //end method onDoubleTap
}; //end gestureListener
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.cannon_game, menu);
return true;
}
}
Here is the custom view the main activity calls and all it's properties:
//CannonView.java
//Displays the Cannon Game
package com.deitel.cannongame;
import java.util.HashMap;
import java.util.Map;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Paint;
import android.media.SoundPool;
import android.media.AudioManager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class CannonView extends SurfaceView implements SurfaceHolder.Callback{
private CannonThread cannonThread; //controls the game loop
private Activity activity; //to display Game Over dialog in GUI thread
private boolean dialogIsDisplayed = false;
//constants for game play
public static final int TARGET_PIECES = 7; //sections in the target
public static final int MISS_PENALTY = 2; //seconds deducted on a miss
public static final int HIT_REWARD = 3; //seconds added on a hit
//variables for the game loop and tracking statistics
private boolean gameOver; //is the game over?
private double timeLeft; //the amount of time left in seconds
private int shotsFired; //the number of shots the user has fired
private double totalTimeElapsed; //the number of seconds elapsed
//variables for the blocker and target
private Line blocker; //start and end points of the blocker
private int blockerDistance; //blocker distance from the left
private int blockerBeginning; //blocker distance from the top
private int blockerEnd; //blocker bottom edge distance from the top
private int initialBlockerVelocity; //initial blocker speed multiplier
private float blockerVelocity; //blocker speed multiplier during game
private Line target; //start and end points of the target
private int targetDistance; //target distance from left
private int targetBeginning; //target distance from the top
private double pieceLength; //length of target piece
private int targetEnd; //target bottom distance from the top
private int initialTargetVelocity; //initial target speed multiplier
private float targetVelocity; //target speed multiplier during game
private int lineWidth; //width of the target and blocker
private boolean[] hitStates; //is each target piece hit?
private int targetPiecesHit; //number of target pieces hit (out of 7)
//variables for the cannon and cannonball
private Point cannonball; //cannonball image's upper-left corner
private int cannonballVelocityX; //cannonball's x velocity
private int cannonballVelocityY; //cannonball's y velocity
private boolean cannonballOnScreen; //is the cannonball on the screen
private int cannonballRadius; //cannonball radius
private int cannonballSpeed; //cannonball speed
private int cannonBaseRadius; //cannon base radius
private int cannonLength; //cannon barrel length
private Point barrelEnd; //the endpoint of the cannon's barrel
private int screenWidth; //width of the screen
private int screenHeight; //height ofthe screen
//constants and variables for managing sounds
private static final int TARGET_SOUND_ID = 0;
private static final int CANNON_SOUND_ID = 1;
private static final int BLOCKER_SOUND_ID = 2;
private SoundPool soundPool; //plays sound effects
private Map<Integer, Integer> soundMap; //maps IDs to SoundPool
//Paint vairables used when drawing each item on the screen
private Paint textPaint; //paint used to draw text
private Paint cannonballPaint; //paint used to draw the cannonball
private Paint cannonPaint; //paint used to draw the cannon
private Paint blockerPaint; //paint used to draw the blocker
private Paint targetPaint; //paint used to draw the target
private Paint backgroundPaint; //paint used to clear the drawing area
//public constructor
public CannonView(Context context, AttributeSet attrs){
super(context, attrs); //call super's constructor
activity = (Activity) context;
//register SurfaceHolder.Calback listener
getHolder().addCallback(this);
//initiate Lines and points representing game items
blocker = new Line(); //create the blocker as a Line
target = new Line(); //create the target as a Line
cannonball = new Point(); //create the cannonball as a point
//initialize hitStates as a boolean array
hitStates = new boolean[TARGET_PIECES];
//initialize SoundPool to play the app's three sound effects
soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
//create Map of sounds and pre-load sounds
soundMap = new HashMap<Integer, Integer>(); //create new HashMap
soundMap.put(TARGET_SOUND_ID, soundPool.load(context, R.raw.target_hit, 1));
soundMap.put(CANNON_SOUND_ID, soundPool.load(context, R.raw.cannon_fire, 1));
soundMap.put(BLOCKER_SOUND_ID, soundPool.load(context, R.raw.blocker_hit, 1));
//construct Paints for drawing text, cannonball, cannon, blocker, and target; these are configured
//in method onSizeChanged
textPaint = new Paint(); //paint for drawing text
cannonballPaint = new Paint(); //paint for drawing the cannonball
cannonPaint = new Paint(); //paint for drawing the cannon
blockerPaint = new Paint(); //Paint for drawing the blocker
targetPaint = new Paint(); //Paint for drawing the target
backgroundPaint = new Paint(); //paint for drawing the background
} //end CannonView constructor
//called when the size of this view changes --including when this view is first added to the view hierarchy
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh){
super.onSizeChanged(w, h, oldw, oldh);
screenWidth = w; //store the width
screenHeight = h; //store the height
cannonBaseRadius = h / 18; //cannon base radius 1/18 screen height
cannonLength = w / 8; //cannon length 1/8 screen width
cannonballRadius = w / 36; //cannonball radious 1/36 screen width
cannonballSpeed = w * 3 / 2; //cannonball speed multiplier
lineWidth = w / 24; //target and blocker 1/24 screen width
//configure instance variables related to the blocker
blockerDistance = w * 5 / 8; //blocker 5/8 screen width from left
blockerBeginning = h / 8; //distance from top 1/8 screen height
blockerEnd = h * 3 / 8; //distance from top 3/8 screen height
initialBlockerVelocity = h / 2; //initial blocker speed multiplier
blocker.start = new Point(blockerDistance, blockerBeginning);
blocker.end = new Point(blockerDistance, blockerEnd);
//configure instance variables related to the target
targetDistance = w * 7 / 8; //target 7/8 screen width from left
targetBeginning = h / 8; //distance from top 1/8 screen height
targetEnd = h * 7 / 8; //distance from top 7/8 screen height
pieceLength = (targetEnd - targetBeginning) / TARGET_PIECES;
initialTargetVelocity = -h / 4; //initial target speed multiplier
target.start = new Point(targetDistance, targetBeginning);
target.end = new Point(targetDistance, targetEnd);
//endpoint of the cannon's barrel initially points horizontally
barrelEnd = new Point(cannonLength, h / 2);
//configure Paint objects for drawing game elements
textPaint.setTextSize(w / 20); //text size 1/20 of screen width
textPaint.setAntiAlias(true); //smoothes the text
cannonPaint.setStrokeWidth(lineWidth * 1.5f); //set line thickness
blockerPaint.setStrokeWidth(lineWidth); //set line thickness
targetPaint.setStrokeWidth(lineWidth); //set line thickness
backgroundPaint.setColor(Color.WHITE); //set background color
newGame(); //set up and start a new game
} //end method onSizeChange
public void newGame(){
//set every element of hitStates to false--restores target pieces
for (int i = 0; i < TARGET_PIECES; ++i)
hitStates[i] = false;
targetPiecesHit = 0; //no target pieces have been hit
blockerVelocity = initialBlockerVelocity; //set initial blocker velocity
targetVelocity = initialTargetVelocity; //set initial target velocity
timeLeft = 10; //start the countdown at 10 seconds
cannonballOnScreen = false; //the cannonball is not on the screen
shotsFired = 0; //set the initial number of shots fired
totalTimeElapsed = 0.0; //set the time elapsed to zero
blocker.start.set(blockerDistance, blockerEnd);
blocker.end.set(blockerDistance, blockerEnd);
target.start.set(targetDistance, targetBeginning);
target.end.set(targetDistance, targetEnd);
if (gameOver){
gameOver = false; //the game is not over
cannonThread = new CannonThread(getHolder());
cannonThread.start();
} //end if
} //end method newGame
//called repleatedly by the CannonThread to update the game elements
private void updatePositions(double elapsedtimeMS){
double interval = elapsedtimeMS / 1000.0; //convert to seconds
if(cannonballOnScreen){ //if there is currently a shot fired
//update cannonball position
cannonball.x += interval * cannonballVelocityX;
cannonball.y += interval * cannonballVelocityY;
//check for collision with blocker
if(cannonball.x + cannonballRadius > blockerDistance && cannonball.x - cannonballRadius
< blockerDistance && cannonball.y + cannonballRadius > blocker.start.y && cannonball.y - cannonballRadius < blocker.end.y){
cannonballVelocityX *= -1; //reverse cannonball's direction
timeLeft -= MISS_PENALTY; //penalize the user
//play blocker sound
soundPool.play(soundMap.get(BLOCKER_SOUND_ID), 1, 1, 1, 0, 1f);
} //end if
//check for collisions with left and right walls
else if(cannonball.x + cannonballRadius > screenWidth || cannonball.x - cannonballRadius < 0)
cannonballOnScreen = false; //remove cannonball from screen
else if(cannonball.y + cannonballRadius > screenHeight || cannonball.y - cannonballRadius < 0)
cannonballOnScreen = false; //remove cannonball from screen
else if(cannonball.x + cannonballRadius > targetDistance && cannonball.x - cannonballRadius <
targetDistance && cannonball.y + cannonballRadius > target.start.y && cannonball.y - cannonballRadius < target.end.y){
//determine target section number (0 is the top)
int section = (int) ((cannonball.y - target.start.y) / pieceLength);
//check if the piece hasn't been hit yet
if((section >= 0 && section < TARGET_PIECES) && !hitStates[section]){
hitStates[section] = true; //section was hit
cannonballOnScreen = false; //remove cannonball
timeLeft += HIT_REWARD; //add reward to remaining time
//play target hit sound
soundPool.play(soundMap.get(TARGET_SOUND_ID), 1, 1, 1, 0, 1F);
//if all pieces have been hit
if (++targetPiecesHit == TARGET_PIECES){
cannonThread.setRunning(false); //show winning dialog
gameOver = true; //the game is over
} //end if
} //end if
} //end else if
} //end if
//update the blockers position
double blockerUpdate = interval * blockerVelocity;
blocker.start.y += blockerUpdate;
blocker.end.y += blockerUpdate;
//update the target's position
double targetUpdate = interval * targetVelocity;
target.start.y += targetUpdate;
target.end.y += targetUpdate;
//if the blocker hit the top or bottom, reverse direction
if(blocker.start.y < 0 || blocker.end.y > screenHeight)
blockerVelocity *= -1;
//if the target hit the top or bottom, reverse direction
if(target.start.y < 0 || target.end.y > screenHeight)
targetVelocity *= -1;
timeLeft -= interval; //subtract from time left
//if the trimer reached zero
if(timeLeft <= 0){
timeLeft = 0.0;
gameOver = true; //the game is over
cannonThread.setRunning(false);
showGameOverDialog(R.string.lose); //show the losing dialog
} //end if
} //end method updatePositions
//fires a cannonball
public void fireCannonball(MotionEvent event){
if(cannonballOnScreen) //if a cannonball is already on the screen
return; //do nothing
double angle = alignCannon(event); //get the cannon barrel's angle
//move the cannonball to be inside the cannon
cannonball.x = cannonballRadius; //align x-coordinate with cannon
cannonball.y = screenHeight / 2; //centers ball vertically
//get the x component of the total velocity
cannonballVelocityX = (int) (cannonballSpeed * Math.sin(angle));
//get the y component of the total velocity
cannonballVelocityY = (int)(-cannonballSpeed * Math.cos(angle));
cannonballOnScreen = true; //the cannonball is on the screen
++shotsFired; //increment shots fired
//play cannon fired sound
soundPool.play(soundMap.get(CANNON_SOUND_ID), 1, 1, 1, 0, 1f);
} //end method fireCannonball
//aligns the cannon in response to a user touch
public double alignCannon(MotionEvent event){
//get the location of the touch in this view
Point touchPoint = new Point((int) event.getX(), (int) event.getY());
//compute the touch's distance from the center of the screen on the Y axis
double centerMinusY = (screenHeight / 2 - touchPoint.y);
double angle = 0; //initialize angle to 0
//calculate the angle the barrel makes with the horizontal
if(centerMinusY != 0) //prevent division by 0
angle = Math.atan((double) touchPoint.x / centerMinusY);
//if the touch is on the lower half of the screen
if(touchPoint.y > screenHeight / 2)
angle += Math.PI; //adjust the angle
//calculate the endpoint of the cannon barrel
barrelEnd.x = (int) (cannonLength * Math.sin(angle));
barrelEnd.y = (int) (-cannonLength * Math.cos(angle) + screenHeight / 2);
return angle; //return the computed angle
} //end method alignCannon
//draws the game to the given Canvas
public void drawGameElements(Canvas canvas){
//clear the background
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), backgroundPaint);
//display the time remaining
canvas.drawText(getResources().getString(R.string.time_remaining_format, timeLeft), 30, 50, textPaint);
//if a cannonball is currently on the screen, draw it
if(cannonballOnScreen)
canvas.drawCircle(cannonball.x, cannonball.y, cannonballRadius, cannonballPaint);
//draw the cannon barrel
canvas.drawLine(0, screenHeight / 2, barrelEnd.x, barrelEnd.y, cannonPaint);
//draw the cannon base
canvas.drawCircle(0, (int) screenHeight / 2, (int) cannonBaseRadius, cannonPaint);
//draw the blocker
canvas.drawLine(blocker.start.x, blocker.start.y, blocker.end.x, blocker.end.y, blockerPaint);
Point currentPoint = new Point(); //start of current target section
//initialize curPoint to the starting point of the target
currentPoint.x = target.start.x;
currentPoint.y = target.start.y;
//draw the target
for (int i = 1; i <= TARGET_PIECES; ++i){
//if this target piece is not hit, draw it
if(!hitStates[i - 1]){
//alternate coloring the pieces yellow and blue
if(i % 2 == 0)
targetPaint.setColor(Color.YELLOW);
else
targetPaint.setColor(Color.BLUE);
canvas.drawLine(currentPoint.x, currentPoint.y, target.end.x,
(int) (currentPoint.y + pieceLength), targetPaint);
} //end if
//move curPoint to the start of the next piece
currentPoint.y += pieceLength;
} //end for
} //end method drawGameElements
//display an AlertDialog when the game ends
private void showGameOverDialog(int messageId){
//create a dialog dislaying the given String
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext());
dialogBuilder.setTitle(getResources().getString(messageId));
dialogBuilder.setCancelable(false);
//display number of shots fired an total time elapsed
dialogBuilder.setMessage(getResources().getString(R.string.results_format, shotsFired, totalTimeElapsed));
dialogBuilder.setPositiveButton(R.string.reset_game,
new DialogInterface.OnClickListener() {
//called when "Reset Game" Button is pressed
#Override
public void onClick(DialogInterface dialog, int which) {
dialogIsDisplayed = false;
newGame(); //set up and start a new game
} //end method onClick
} //end anonymous inner class
); //end call to setPositiveButton
activity.runOnUiThread(
new Runnable() {
public void run(){
dialogIsDisplayed = true;
dialogBuilder.show(); //display the dialog
} //end run
}//end runnable
); //end call to runOnUiThread
} //end method showGameOverDialog
//pauses the game
public void stopGame(){
if(cannonThread != null)
cannonThread.setRunning(false);
} //end method stopGame
//releases reources; called by CannonGame's onDestroy method
public void releaseResources(){
soundPool.release(); //release all resource used by the SoundPool
soundPool = null;
} //end method releaseResources
//called when surface changes size
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height){
} //end method surfaceChanged
//called when surface is first created
#Override
public void surfaceCreated(SurfaceHolder holder){
cannonThread = new CannonThread(holder);
cannonThread.setRunning(true);
cannonThread.start(); //start the game loop thread
} //end method surfaceCreated
//called when surface is destroyed
#Override
public void surfaceDestroyed(SurfaceHolder holder){
//ensure that thread terminates properly
boolean retry = true;
cannonThread.setRunning(false);
while(retry){
try{
cannonThread.join();
retry = false;
} //end try
catch(InterruptedException e){
} //end catch
} //end while
} //end method surfaceDestroyed
//Thread subclass to control the game loop
private class CannonThread extends Thread{
private SurfaceHolder surfaceHolder; //for manipulating canvas
private boolean threadIsRunning = true; //running by default
//initialize the surface holder
public CannonThread(SurfaceHolder holder){
surfaceHolder = holder;
setName("CannonThread");
} //end constructor
//changes the running state
public void setRunning(boolean running){
threadIsRunning = running;
} //end method setRunning
//controls the game loop
#Override
public void run(){
Canvas canvas = null; //used for drawing
long previousFrameTime = System.currentTimeMillis();
while(threadIsRunning){
try{
canvas = surfaceHolder.lockCanvas(null);
//lock the surafceHolder for drawing
synchronized(surfaceHolder){
long currentTime = System.currentTimeMillis();
double elapsedTimeMS = currentTime - previousFrameTime;
totalTimeElapsed += elapsedTimeMS / 1000.0;
updatePositions(elapsedTimeMS); //update game stats
drawGameElements(canvas); //draw
previousFrameTime = currentTime; //updateprevious time
} //end synchronized block
} //end try
finally{
if(canvas != null)
surfaceHolder.unlockCanvasAndPost(canvas);
} //end finally
} //end while
} //end method run
} //end nested class CannonThread
} //end class CannonView
Finally, here is the xml constraint that my custom view uses:
<?xml version="1.0" encoding="utf-8"?>
<com.deitel.cannongame.CannonView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/cannonView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/white"/>
I should mention that I tried the book's example files off their website, and I get the exact same problem.
Change the xml
android:background="#android:color/transparent"
It is likely that you are being burned by overzealous scaling by your fancy android. I would recommend that you use this:
in your manifest. Just put it after your Application information, like this:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yourstuff.app"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="10"
android:targetSdkVersion="15" />
<application android:label="#string/app_name" android:icon="#drawable/r">
<activity android:name=".LandingActivity"
android:theme="#android:style/Theme.NoTitleBar.Fullscreen"
android:launchMode="singleTask"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".yourOtherActivity"
android:theme="#android:style/Theme.NoTitleBar.Fullscreen"
/>
</application>
<supports-screens android:anyDensity="true" />
</manifest>
I have a list of buttons. When I press a button, a View should slide in a downwards motion out of the button, like this:
Start:
Halfway:
End:
How would I go about this? The View that should slide out is bigger than the button, so first hiding the View behind the button and then sliding it downwards causes the View to be visible above the button. That should not happen.
Any ideas or examples on how to approach this?
I believe the simplest approach is to extend Animation class and override applyTransformation() to change the view's height as follows:
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.LinearLayout;
public class MyCustomAnimation extends Animation {
public final static int COLLAPSE = 1;
public final static int EXPAND = 0;
private View mView;
private int mEndHeight;
private int mType;
private LinearLayout.LayoutParams mLayoutParams;
public MyCustomAnimation(View view, int duration, int type) {
setDuration(duration);
mView = view;
mEndHeight = mView.getHeight();
mLayoutParams = ((LinearLayout.LayoutParams) view.getLayoutParams());
mType = type;
if(mType == EXPAND) {
mLayoutParams.height = 0;
} else {
mLayoutParams.height = LayoutParams.WRAP_CONTENT;
}
view.setVisibility(View.VISIBLE);
}
public int getHeight(){
return mView.getHeight();
}
public void setHeight(int height){
mEndHeight = height;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
if (interpolatedTime < 1.0f) {
if(mType == EXPAND) {
mLayoutParams.height = (int)(mEndHeight * interpolatedTime);
} else {
mLayoutParams.height = (int) (mEndHeight * (1 - interpolatedTime));
}
mView.requestLayout();
} else {
if(mType == EXPAND) {
mLayoutParams.height = LayoutParams.WRAP_CONTENT;
mView.requestLayout();
}else{
mView.setVisibility(View.GONE);
}
}
}
}
To use it, set your onclick() as follows:
int height;
#Override
public void onClick(View v) {
if(view2.getVisibility() == View.VISIBLE){
MyCustomAnimation a = new MyCustomAnimation(view2, 1000, MyCustomAnimation.COLLAPSE);
height = a.getHeight();
view2.startAnimation(a);
}else{
MyCustomAnimation a = new MyCustomAnimation(view2, 1000, MyCustomAnimation.EXPAND);
a.setHeight(height);
view2.startAnimation(a);
}
}
Regards.
Use something like:
Animation a = new ScaleAnimation(1, 1, 0, 1, Animation.RELATIVE_TO_SELF, (float) 0.5, Animation.RELATIVE_TO_SELF, (float) 0);
a.setFillAfter(true);
view.setAnimation(a);
a.setDuration(1000);
view.startAnimation(a);
Here is simple example of hand-made animation, that provide what you want. It works in test app, but I'm not sure that there is no bugs:
public class MainActivity extends Activity implements OnClickListener {
private Timer timer;
private TimerTask animationTask;
private View view1;
private View view2;
boolean animating;
boolean increasing = true;
int initHeight = -1;
private LayoutParams params;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
timer = new Timer();
view1 = findViewById(R.id.view1);// clickable view
view1.setOnClickListener(this);
view2 = findViewById(R.id.view2);// animated view
params = view2.getLayoutParams();
}
#Override
protected void onDestroy() {
super.onDestroy();
timer.cancel();
}
#Override
public void onClick(View v) {
Toast.makeText(this, "start animating...", Toast.LENGTH_SHORT).show();
animationTask = new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
if (animationFinished()) {
animating = false;
cancel();//canceling animating task
return;
}
params.height += increasing ? 1 : -1;
view2.setLayoutParams(params);
}
});
}
private boolean animationFinished() {
int viewHeight = view2.getHeight();
if (increasing && viewHeight >= initHeight) {
return true;
}
if (!increasing && viewHeight <= 0) {
return true;
}
return false;
}
};
//if we already animating - we just change direction of animation
increasing = !increasing;
if (!animating) {
animating = true;
int height = view2.getHeight();
params.height = height;
view2.setLayoutParams(params);//change param "height" from "wrap_conent" to real height
if (initHeight < 0) {//height of view - we setup it only once
initHeight = height;
}
timer.schedule(animationTask, 0, 10);//changing repeat time here will fasten or slow down animation
}
}
}
Maybe you can set the height to 0 and gradually increase the height. But then you will have the problem that you have to be sure your text is aligned at the bottom of the view. And also to know what the maximal height of the view should be.
use a sliding list adapter so much easier than messing around with animations
https://github.com/tjerkw/Android-SlideExpandableListView
Simply pass android:animateLayoutChanges to LinearLayout that holds all the views, you will achieve your desired result.
I would do it like that. First the layout for the whole collapsible panel component: (pseudo xml)
RelativeLayout (id=panel, clip)
LinearLayout (id=content, alignParentBottom=true)
LinearLayout (id=handle, above=content)
This should ensure that the content is always below the handle.
Then when you need to collapse:
Animate the top margin of content from 0 to -content.height
Animate the height of the panel from current to current-content.height