自定义视图未完全绘制

时间:2013-06-28 02:01:16

标签: android android-custom-view

我正在尝试制作一款打印在Android开发者手册中的大炮游戏,并且无法绘制实际游戏。我可以听到我的声音,游戏结束时的警报对话框显示在我的计时器用完后,即使我看不到计时器,并且显示白色背景,只是没有游戏元素

以下是主要活动:

//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;
}

}

以下是主要活动调用的自定义视图及其所有属性:

//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

最后,这是我的自定义视图使用的xml约束:

<?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"/>

我应该提一下,我在他们的网站上尝试了这本书的示例文件,我得到了完全相同的问题。

2 个答案:

答案 0 :(得分:0)

更改xml

android:background="@android:color/transparent"

答案 1 :(得分:0)

很可能是你被花哨的机器人过度粗暴地焚烧了。我建议您使用它:

在你的清单中

只需将其放在您的应用程序信息之后,如下所示:

<?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>