Как исправить проблему в старом Android Studio проекте - отображать AlertDialog, когда игра заканчивается - метод .show () - PullRequest
0 голосов
/ 24 апреля 2020

Я скачал с Inte rnet книгу и примеры кода для изучения Android разработки. Я попытался выполнить одно приложение из этого набора в android studio 3.6.3, но моя попытка закончилась неудачей. Я немного знаю Java и не могу исправить проблему в коде. Кто-нибудь может знать, как решить эту проблему?

Это был старый проект, и я создал новый с пустой активностью. Затем я сделал операцию копирования / вставки файлов в каталоге \ src \ main (. java и \ res). Я изменил что-то в структуре моего проекта. Но я не могу исправить одну проблему в моем новом проекте, и поэтому приложение не работает должным образом.

// CannonView.java
// Displays and controls the Cannon Game
package com.example.cannongame_new;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.media.AudioAttributes;
import android.media.SoundPool;
import android.os.Build;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;

import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;

import java.util.ArrayList;
import java.util.Random;

public class CannonView extends SurfaceView
   implements SurfaceHolder.Callback {

   private static final String TAG = "CannonView"; // for logging errors

   // constants for game play
   public static final int MISS_PENALTY = 2; // seconds deducted on a miss
   public static final int HIT_REWARD = 3; // seconds added on a hit

   // constants for the Cannon
   public static final double CANNON_BASE_RADIUS_PERCENT = 3.0 / 40;
   public static final double CANNON_BARREL_WIDTH_PERCENT = 3.0 / 40;
   public static final double CANNON_BARREL_LENGTH_PERCENT = 1.0 / 10;

   // constants for the Cannonball
   public static final double CANNONBALL_RADIUS_PERCENT = 3.0 / 80;
   public static final double CANNONBALL_SPEED_PERCENT = 3.0 / 2;

   // constants for the Targets
   public static final double TARGET_WIDTH_PERCENT = 1.0 / 40;
   public static final double TARGET_LENGTH_PERCENT = 3.0 / 20;
   public static final double TARGET_FIRST_X_PERCENT = 3.0 / 5;
   public static final double TARGET_SPACING_PERCENT = 1.0 / 60;
   public static final double TARGET_PIECES = 9;
   public static final double TARGET_MIN_SPEED_PERCENT = 3.0 / 4;
   public static final double TARGET_MAX_SPEED_PERCENT = 6.0 / 4;

   // constants for the Blocker
   public static final double BLOCKER_WIDTH_PERCENT = 1.0 / 40;
   public static final double BLOCKER_LENGTH_PERCENT = 1.0 / 4;
   public static final double BLOCKER_X_PERCENT = 1.0 / 2;
   public static final double BLOCKER_SPEED_PERCENT = 1.0;

   // text size 1/18 of screen width
   public static final double TEXT_SIZE_PERCENT = 1.0 / 18;

   private CannonThread cannonThread; // controls the game loop
   private Activity activity; // to display Game Over dialog in GUI thread
   private boolean dialogIsDisplayed = false;

   // game objects
   private Cannon cannon;
   private Blocker blocker;
   private ArrayList<Target> targets;

   // dimension variables
   private int screenWidth;
   private int screenHeight;

   // variables for the game loop and tracking statistics
   private boolean gameOver; // is the game over?
   private double timeLeft; // time remaining in seconds
   private int shotsFired; // shots the user has fired
   private double totalElapsedTime; // elapsed seconds

   // constants and variables for managing sounds
   public static final int TARGET_SOUND_ID = 0;
   public static final int CANNON_SOUND_ID = 1;
   public static final int BLOCKER_SOUND_ID = 2;
   private SoundPool soundPool; // plays sound effects
   private SparseIntArray soundMap; // maps IDs to SoundPool

   // Paint variables used when drawing each item on the screen
   private Paint textPaint; // Paint used to draw text
   private Paint backgroundPaint; // Paint used to clear the drawing area

   private FragmentManager fm;

   // constructor
public CannonView(Context context, AttributeSet attrs) {
         super(context, attrs); // call superclass constructor
         activity = (Activity) context; // store reference to MainActivity

      // register SurfaceHolder.Callback listener
      getHolder().addCallback(this);

      // configure audio attributes for game audio
      AudioAttributes.Builder attrBuilder = new AudioAttributes.Builder();
      attrBuilder.setUsage(AudioAttributes.USAGE_GAME);

      // initialize SoundPool to play the app's three sound effects
      SoundPool.Builder builder = new SoundPool.Builder();
      builder.setMaxStreams(1);
      builder.setAudioAttributes(attrBuilder.build());
      soundPool = builder.build();

      // create Map of sounds and pre-load sounds
      soundMap = new SparseIntArray(3); // create new SparseIntArray
      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));

      textPaint = new Paint();
      backgroundPaint = new Paint();
      backgroundPaint.setColor(Color.WHITE);
   }

   // called when the size of the SurfaceView changes,
   // such as when it's 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 CannonView's width
      screenHeight = h; // store CannonView's height

      // configure text properties
      textPaint.setTextSize((int) (TEXT_SIZE_PERCENT * screenHeight));
      textPaint.setAntiAlias(true); // smoothes the text
   }

   // get width of the game screen
   public int getScreenWidth() {
      return screenWidth;
   }

   // get height of the game screen
   public int getScreenHeight() {
      return screenHeight;
   }

   // plays a sound with the given soundId in soundMap
   public void playSound(int soundId) {
      soundPool.play(soundMap.get(soundId), 1, 1, 1, 0, 1f);
   }

   // reset all the screen elements and start a new game
   public void newGame() {
      // construct a new Cannon
      cannon = new Cannon(this,
         (int) (CANNON_BASE_RADIUS_PERCENT * screenHeight),
         (int) (CANNON_BARREL_LENGTH_PERCENT * screenWidth),
         (int) (CANNON_BARREL_WIDTH_PERCENT * screenHeight));

      Random random = new Random(); // for determining random velocities
      targets = new ArrayList<>(); // construct a new Target list

      // initialize targetX for the first Target from the left
      int targetX = (int) (TARGET_FIRST_X_PERCENT * screenWidth);

      // calculate Y coordinate of Targets
      int targetY = (int) ((0.5 - TARGET_LENGTH_PERCENT / 2) *
         screenHeight);

      // add TARGET_PIECES Targets to the Target list
      for (int n = 0; n < TARGET_PIECES; n++) {

         // determine a random velocity between min and max values
         // for Target n
         double velocity = screenHeight * (random.nextDouble() *
            (TARGET_MAX_SPEED_PERCENT - TARGET_MIN_SPEED_PERCENT) +
            TARGET_MIN_SPEED_PERCENT);

         // alternate Target colors between dark and light
         int color =  (n % 2 == 0) ?
                 getResources().getColor(R.color.dark,
                         getContext().getTheme()) :
                 getResources().getColor(R.color.light,
                         getContext().getTheme());

         velocity *= -1; // reverse the initial velocity for next Target

         // create and add a new Target to the Target list
         targets.add(new Target(this, color, HIT_REWARD, targetX, targetY,
            (int) (TARGET_WIDTH_PERCENT * screenWidth),
            (int) (TARGET_LENGTH_PERCENT * screenHeight),
            (int) velocity));

         // increase the x coordinate to position the next Target more
         // to the right
         targetX += (TARGET_WIDTH_PERCENT + TARGET_SPACING_PERCENT) *
            screenWidth;
      }

      // create a new Blocker
      blocker = new Blocker(this, Color.BLACK, MISS_PENALTY,
         (int) (BLOCKER_X_PERCENT * screenWidth),
         (int) ((0.5 - BLOCKER_LENGTH_PERCENT / 2) * screenHeight),
         (int) (BLOCKER_WIDTH_PERCENT * screenWidth),
         (int) (BLOCKER_LENGTH_PERCENT * screenHeight),
         (float) (BLOCKER_SPEED_PERCENT * screenHeight));

      timeLeft = 10; // start the countdown at 10 seconds

      shotsFired = 0; // set the initial number of shots fired
      totalElapsedTime = 0.0; // set the time elapsed to zero

      if (gameOver) { // start a new game after the last game ended
         gameOver = false; // the game is not over
         cannonThread = new CannonThread(getHolder()); // create thread
         cannonThread.start(); // start the game loop thread
      }

      hideSystemBars();
   }

   // called repeatedly by the CannonThread to update game elements
   private void updatePositions(double elapsedTimeMS) {
      double interval = elapsedTimeMS / 1000.0; // convert to seconds

      // update cannonball's position if it is on the screen
      if (cannon.getCannonball() != null)
         cannon.getCannonball().update(interval);

      blocker.update(interval); // update the blocker's position

      for (GameElement target : targets)
         target.update(interval); // update the target's position

      timeLeft -= interval; // subtract from time left

      // if the timer reached zero
      if (timeLeft <= 0) {
         timeLeft = 0.0;
         gameOver = true; // the game is over
         cannonThread.setRunning(false); // terminate thread
         showGameOverDialog(R.string.lose); // show the losing dialog
      }

      // if all pieces have been hit
      if (targets.isEmpty()) {
         cannonThread.setRunning(false); // terminate thread
         showGameOverDialog(R.string.win); // show winning dialog
         gameOver = true;
      }
   }

   // aligns the barrel and fires a Cannonball if a Cannonball is not
   // already on the screen
   public void alignAndFireCannonball(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 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
      angle = Math.atan2(touchPoint.x, centerMinusY);

      // point the barrel at the point where the screen was touched
      cannon.align(angle);

      // fire Cannonball if there is not already a Cannonball on screen
      if (cannon.getCannonball() == null ||
         !cannon.getCannonball().isOnScreen()) {
         cannon.fireCannonball();
         ++shotsFired;
      }
   }

   // display an AlertDialog when the game ends
   private void showGameOverDialog(final int messageId) {
      // DialogFragment to display game stats and start new game
      final DialogFragment gameResult = new DialogFragment() {
            // create an AlertDialog and return it
            @Override
            public Dialog onCreateDialog(Bundle bundle) {
               // create dialog displaying String resource for messageId
               AlertDialog.Builder builder =
                  new AlertDialog.Builder(getActivity());
               builder.setTitle(getResources().getString(messageId));

               // display number of shots fired and total time elapsed
               builder.setMessage(getResources().getString(
                  R.string.results_format, shotsFired, totalElapsedTime));
               builder.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
                     }
                  }
               );

               return builder.create(); // return the AlertDialog
            }
         };

      // in GUI thread, use FragmentManager to display the DialogFragment


      activity.runOnUiThread(
         new Runnable() {
            public void run() {
               showSystemBars();
               dialogIsDisplayed = true;
               gameResult.setCancelable(false); // modal dialog
                fm =( (FragmentActivity) activity).getSupportFragmentManager();
                gameResult.show( fm,  "results");

            }
         }
      );
   }

   // 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 time remaining
      canvas.drawText(getResources().getString(
         R.string.time_remaining_format, timeLeft), 50, 100, textPaint);

      cannon.draw((Canvas) canvas); // draw the cannon

      // draw the GameElements
      if (cannon.getCannonball() != null &&
         cannon.getCannonball().isOnScreen())
         cannon.getCannonball().draw(canvas);

      blocker.draw(canvas); // draw the blocker

      // draw all of the Targets
      for (GameElement target : targets)
         target.draw(canvas);
   }

   // checks if the ball collides with the Blocker or any of the Targets
   // and handles the collisions
   public void testForCollisions() {
      // remove any of the targets that the Cannonball
      // collides with
      if (cannon.getCannonball() != null &&
         cannon.getCannonball().isOnScreen()) {
         for (int n = 0; n < targets.size(); n++) {
            if (cannon.getCannonball().collidesWith(targets.get(n))) {
               targets.get(n).playSound(); // play Target hit sound

               // add hit rewards time to remaining time
               timeLeft += targets.get(n).getHitReward();

               cannon.removeCannonball(); // remove Cannonball from game
               targets.remove(n); // remove the Target that was hit
               --n; // ensures that we don't skip testing new target n
               break;
            }
         }
      }
      else { // remove the Cannonball if it should not beon the screen
         cannon.removeCannonball();
      }

      // check if ball collides with blocker
      if (cannon.getCannonball() != null &&
         cannon.getCannonball().collidesWith(blocker)) {
         blocker.playSound(); // play Blocker hit sound

         // reverse ball direction
         cannon.getCannonball().reverseVelocityX();

         // deduct blocker's miss penalty from remaining time
         timeLeft -= blocker.getMissPenalty();
      }
   }

   // stops the game: called by CannonGameFragment's onPause method
   public void stopGame() {
      if (cannonThread != null)
         cannonThread.setRunning(false); // tell thread to terminate
   }

   // release resources: called by CannonGame's onDestroy method
   public void releaseResources() {
      soundPool.release(); // release all resources used by the SoundPool
      soundPool = null;
   }

   // called when surface changes size
   @Override
   public void surfaceChanged(SurfaceHolder holder, int format,
      int width, int height) { }

   // called when surface is first created
   @Override
   public void surfaceCreated(SurfaceHolder holder) {
      if (!dialogIsDisplayed) {
         newGame(); // set up and start a new game
         cannonThread = new CannonThread(holder); // create thread
         cannonThread.setRunning(true); // start game running
         cannonThread.start(); // start the game loop thread
      }
   }

   // called when the surface is destroyed
   @Override
   public void surfaceDestroyed(SurfaceHolder holder) {
      // ensure that thread terminates properly
      boolean retry = true;
      cannonThread.setRunning(false); // terminate cannonThread

      while (retry) {
         try {
            cannonThread.join(); // wait for cannonThread to finish
            retry = false;
         }
         catch (InterruptedException e) {
            Log.e(TAG, "Thread interrupted", e);
         }
      }
   }

   // called when the user touches the screen in this activity
   @Override
   public boolean onTouchEvent(MotionEvent e) {
      // get int representing the type of action which caused this event
      int action = e.getAction();

      // the user touched the screen or dragged along the screen
      if (action == MotionEvent.ACTION_DOWN ||
         action == MotionEvent.ACTION_MOVE) {
         // fire the cannonball toward the touch point
         alignAndFireCannonball(e);
      }

      return true;
   }

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

      // initializes the surface holder
      public CannonThread(SurfaceHolder holder) {
         surfaceHolder = holder;
         setName("CannonThread");
      }

      // changes running state
      public void setRunning(boolean running) {
         threadIsRunning = running;
      }

      // controls the game loop
      @Override
      public void run() {
         Canvas canvas = null; // used for drawing
         long previousFrameTime = System.currentTimeMillis();

         while (threadIsRunning) {
            try {
               // get Canvas for exclusive drawing from this thread
               canvas = surfaceHolder.lockCanvas(null);

               // lock the surfaceHolder for drawing
               synchronized(surfaceHolder) {
                  long currentTime = System.currentTimeMillis();
                  double elapsedTimeMS = currentTime - previousFrameTime;
                  totalElapsedTime += elapsedTimeMS / 1000.0;
                  updatePositions(elapsedTimeMS); // update game state
                  testForCollisions(); // test for GameElement collisions
                  drawGameElements(canvas); // draw using the canvas
                  previousFrameTime = currentTime; // update previous time
               }
            }
            finally {
               // display canvas's contents on the CannonView
               // and enable other threads to use the Canvas
               if (canvas != null)
                  surfaceHolder.unlockCanvasAndPost(canvas);
            }
         }
      }
   }

   // hide system bars and app bar
   private void hideSystemBars() {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
         setSystemUiVisibility(
            View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
            View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
            View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
            View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
            View.SYSTEM_UI_FLAG_FULLSCREEN |
            View.SYSTEM_UI_FLAG_IMMERSIVE);
   }

   // show system bars and app bar
   private void showSystemBars() {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
         setSystemUiVisibility(
            View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
            View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
            View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
   }
}

Проблема заключается в следующем:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.cannongame_new, PID: 8801
    java.lang.IllegalStateException: Fragment null must be a public static class to be  properly recreated from instance state.
        at androidx.fragment.app.FragmentTransaction.doAddOp(FragmentTransaction.java:165)
        at androidx.fragment.app.BackStackRecord.doAddOp(BackStackRecord.java:179)
        at androidx.fragment.app.FragmentTransaction.add(FragmentTransaction.java:125)
        at androidx.fragment.app.DialogFragment.show(DialogFragment.java:154)
        at com.example.cannongame_new.CannonView$2.run(CannonView.java:331)
        at android.os.Handler.handleCallback(Handler.java:790)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

Строка CannonView. java: 331 (см. выше) выглядит как

      gameResult.show( fm,  "results");
...