Android Open Source - DroidSweeper Game Grid View






From Project

Back to project page DroidSweeper.

License

The source code is released under:

MIT License

If you think the Android project DroidSweeper listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package de.nisble.droidsweeper.gui.grid;
/*www .  j  ava 2  s.  co m*/
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import de.nisble.droidsweeper.R;
import de.nisble.droidsweeper.config.GameConfig;
import de.nisble.droidsweeper.game.Position;
import de.nisble.droidsweeper.game.jni.FieldStatus;
import de.nisble.droidsweeper.game.jni.MineSweeperMatrix;
import de.nisble.droidsweeper.utilities.LogDog;

/** This class abstracts the logic that is needed to correctly arrange the
 * {@link FieldView field widgets} in a table (called the game grid).
 * It is an extension of the ViewGroup RelativeLayout thats attributes can be
 * used in a XML-File to specify the LayoutParms of this view. Internally a
 * TableLayout is used to arrange the {@link FieldView field widgets}. Use the
 * {@link #update(GameConfig, FieldWidgetConnector)} method to build a new grid.
 * The field widgets of the last grid are recycled if possible.<br>
 * For the user to be able to connect the field widgets to different classes
 * that can update the {@link FieldStatus} of the widgets, a
 * {@link FieldWidgetConnector} must be passed to
 * {@link #update(GameConfig, FieldWidgetConnector)}. From inside this callback
 * the user should connect the field widget to e.g.
 * {@link MineSweeperMatrix#setFieldListener(de.nisble.droidsweeper.game.jni.FieldListener)}
 * or another instance that should update the status of the field widgets.<br>
 * Also, the user should pass an implementation of {@link FieldClickListener} to
 * {@link #setFieldClickListener(FieldClickListener)}. Clicks on the field
 * widgets are passed to the methods of that interface.
 * @author Moritz Nisbl moritz.nisble@gmx.de */
public class GameGridView extends RelativeLayout {
  private static final String CLASSNAME = GameGridView.class.getSimpleName();

  /** Interface that should be passed to
   * {@link GameGridView#update(GameConfig, FieldWidgetConnector)}.
   * @author Moritz Nisbl moritz.nisble@gmx.de */
  public interface FieldWidgetConnector {
    /** <a href=
     * "http://steve-yegge.blogspot.de/2006/03/execution-in-kingdom-of-nouns.html"
     * >Execution in the Kingdom of Nouns</a> */
    void connect(FieldView field);
  }

  /** Interface that is invoked on clicks to the {@link FieldView field
   * widgets}.
   * @author Moritz Nisbl moritz.nisble@gmx.de */
  public interface FieldClickListener {
    /** Invoked on click to a {@link FieldView}
     * @param field The {@link FieldView} that was clicked. */
    void onClick(FieldView field);

    /** Invoked on long click to a {@link FieldView}.
     * @param field The {@link FieldView} that was long clicked.
     * @return true if the callback consumed the long click, false
     *         otherwise. */
    boolean onLongClick(FieldView field);
  }

  private class GridLayout extends TableLayout {
    private GridLayout(Context context, AttributeSet attrs) {
      super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      /* Ensure that table layout has the minimum size (wraps its
       * children). */

      // Get count of children
      int rowCount = getChildCount();
      int columnCount = (rowCount > 0) ? ((TableRow) getChildAt(0)).getChildCount() : 0;

      /* Calculate the maximum allowed side length of the fields by
       * dividing the maximum allowed size passed by parent for both
       * directions by the count of widgets in the corresponding
       * direction.
       * Finally take the minimum of both sizes to ensure that the
       * complete matrix fits to into the bounds passed by parent while
       * single views stays quadratic. */
      int maxSideLength = Math.min(MeasureSpec.getSize(heightMeasureSpec) / ((rowCount > 0) ? rowCount : 1),
          MeasureSpec.getSize(widthMeasureSpec) / ((columnCount > 0) ? columnCount : 1));

      /* Take this maximum allowed side length for each field, multiply it
       * with the count of fields in each direction and pass it back to
       * parent. */
      super.onMeasure(MeasureSpec.makeMeasureSpec(maxSideLength * ((columnCount > 0) ? columnCount : 1),
          MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(maxSideLength
          * ((rowCount > 0) ? rowCount : 1), MeasureSpec.UNSPECIFIED));
    }
  }

  private final GridLayout mGrid;
  private TextView mOverlayText = null;
  private FieldClickListener mFieldClickListener = null;

  /** Constructor. */
  public GameGridView(Context context) {
    this(context, null);
  }

  /** Constructor. */
  public GameGridView(Context context, AttributeSet attrs) {
    super(context, attrs);

    RelativeLayout.LayoutParams gridLayoutParams = new RelativeLayout.LayoutParams(
        RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
    gridLayoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);

    mGrid = new GridLayout(context, attrs);
    mGrid.setLayoutParams(gridLayoutParams);
    mGrid.setShrinkAllColumns(true);

    addView(mGrid);

    TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.GameGridView, 0, 0);

    try {
      if (a.getBoolean(R.styleable.GameGridView_hasOverlay, false)) {
        mOverlayText = new TextView(context);

        RelativeLayout.LayoutParams overlayLayoutParams = new RelativeLayout.LayoutParams(
            RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
        overlayLayoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);

        mOverlayText.setLayoutParams(overlayLayoutParams);

        mOverlayText.setClickable(false);
        mOverlayText.setGravity(Gravity.CENTER);
        mOverlayText.setTextSize(a.getDimension(R.styleable.GameGridView_overlayTextSize, 26));
        mOverlayText.setTextColor(a.getColor(R.styleable.GameGridView_overlayTextColor, Color.RED));
        mOverlayText.setBackgroundColor(a.getColor(R.styleable.GameGridView_overlayBackgroundColor,
            Color.LTGRAY));

        mOverlayText.setVisibility(INVISIBLE);
        addView(mOverlayText);
      }
    } finally {
      a.recycle();
    }

    bringChildToFront(mGrid);
  }

  /** Register a {@link FieldClickListener} thats methods are invoked on clicks
   * to the {@link FieldView field widgets}.
   * @param l An instance of a {@link FieldClickListener}. */
  public void setFieldClickListener(FieldClickListener l) {
    mFieldClickListener = l;
  }

  /** Update the game grid to the dimensions passed in the given
   * {@link GameConfig}. The dimensions of the grid are adjusted to the
   * orientation of the device.
   * @param config The new {@link GameConfig}.
   * @param connector A callback that is responsible for connecting the
   *            {@link FieldView field widgets} to a class that is able
   *            to update its {@link FieldStatus status} (e.g.
   *            {@link MineSweeperMatrix#setFieldListener(de.nisble.droidsweeper.game.jni.FieldListener)}
   *            ). */
  public void update(GameConfig config, FieldWidgetConnector connector) {
    // Get an orientation corrected version of the config
    GameConfig cv = config.adjustOrientation(getContext());

    // Adjust row count while recycling rows already present
    if (mGrid.getChildCount() < cv.Y) {
      LogDog.d(CLASSNAME, "Adding FieldView rows: " + mGrid.getChildCount() + " -> " + cv.Y);

      // Add left rows
      while (mGrid.getChildCount() < cv.Y) {
        TableRow row = new TableRow(getContext());
        row.setLayoutParams(new GameGridView.LayoutParams(LayoutParams.WRAP_CONTENT, 0));
        mGrid.addView(row);
      }
    } else if (mGrid.getChildCount() > cv.Y) {
      LogDog.d(CLASSNAME, "Removing FieldView rows: " + mGrid.getChildCount() + " -> " + cv.Y);

      // Remove redundant rows
      mGrid.removeViews(cv.Y, mGrid.getChildCount() - cv.Y);
    }

    // Adjusting column count (FieldViewS) in each row while recycling
    // already present FieldViewS
    for (int y = 0; y < mGrid.getChildCount(); ++y) {
      TableRow row = (TableRow) mGrid.getChildAt(y);

      if (row.getChildCount() < cv.X) {
        LogDog.d(CLASSNAME, "Adding FieldViewS: " + row.getChildCount() + " -> " + cv.X);

        // Add left columns to each row
        while (row.getChildCount() < cv.X) {
          FieldView field = new FieldView(getContext());
          field.setLayoutParams(new TableRow.LayoutParams(LayoutParams.WRAP_CONTENT,
              LayoutParams.WRAP_CONTENT));
          field.setAdjustViewBounds(true);
          field.setScaleType(ImageView.ScaleType.CENTER_INSIDE);

          field.setLongClickable(true);

          field.setOnClickListener(mOnFieldClick);
          field.setOnLongClickListener(mOnFieldLongClick);
          row.addView(field);
        }
      } else if (row.getChildCount() > cv.X) {
        LogDog.d(CLASSNAME, "Removing FieldViewS: " + row.getChildCount() + " -> " + cv.X);

        // Remove redundant columns from each row
        row.removeViews(cv.X, row.getChildCount() - cv.X);
      }
    }

    /* Iterate over all FieldViewS in the TableLayout, inform the views
     * about its Position in the Matrix and let the caller connect the view
     * to any module that controls the status of the view. */
    for (int y = 0; y < cv.Y; ++y) {
      TableRow row = (TableRow) mGrid.getChildAt(y);

      for (int x = 0; x < cv.X; ++x) {
        FieldView field = (FieldView) row.getChildAt(x);

        /* NOTE: Game/libmsm and Replay only know the PORTRAIT layout.
         * Only the view adapts to the orientation of the device!
         * So if the device is in LANDSCAPE mode, the internal Position
         * of the FieldViewS differs from its position in the
         * TableLayout. Therefore we have to correct this Position back
         * to PORTRAIT, if the Grind was built for LANDSCAPE.
         * Because the game logic gets the coordinates of each field by
         * calling FieldListener.getPosition(), this is the only place
         * where we have to consider this. */
        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)
          /* Transpose or rotate?
           * This is the code for rotate right by 90 degree:
           * field.reset(new Position(y, config.Y - 1 - x)); */
          field.reset(new Position(y, x));
        else
          field.reset(new Position(x, y));

        /* Let the caller connect the field to any module that is able
         * to control the FieldStatus. */
        connector.connect(field);
      }
    }

    invalidate();
    requestLayout();
  }

  /** Bring an overlay with the given text in front.
   * @param text The text to show. */
  public void showOverlay(String text) {
    if (mOverlayText != null) {
      mOverlayText.setText(text);
      mOverlayText.setVisibility(VISIBLE);

      mOverlayText.bringToFront();

      invalidate();
      requestLayout();
    }
  }

  /** Hide the overlay and bring the game grid in front. */
  public void hideOverlay() {
    if (mOverlayText != null) {
      mOverlayText.setVisibility(INVISIBLE);

      mGrid.bringToFront();

      invalidate();
      requestLayout();
    }
  }

  private final OnClickListener mOnFieldClick = new OnClickListener() {
    @Override
    public void onClick(View v) {
      if (mFieldClickListener != null) {
        mFieldClickListener.onClick((FieldView) v);
      } else {
        LogDog.w(CLASSNAME, "Register a FieldClickListener to get informed about clicks on FieldViewS");
      }
    }
  };

  private final OnLongClickListener mOnFieldLongClick = new OnLongClickListener() {
    @Override
    public boolean onLongClick(View v) {
      if (mFieldClickListener != null) {
        return mFieldClickListener.onLongClick((FieldView) v);
      } else {
        LogDog.w(CLASSNAME, "Register a FieldClickListener to get informed about clicks on FieldViewS");
        return false;
      }
    }
  };
}




Java Source Code List

de.nisble.droidsweeper.config.ApplicationConfig.java
de.nisble.droidsweeper.config.Constants.java
de.nisble.droidsweeper.config.GameConfig.java
de.nisble.droidsweeper.config.Level.java
de.nisble.droidsweeper.game.Field.java
de.nisble.droidsweeper.game.GameObserver.java
de.nisble.droidsweeper.game.Game.java
de.nisble.droidsweeper.game.Position.java
de.nisble.droidsweeper.game.database.DSDBAdapter.java
de.nisble.droidsweeper.game.database.DSDBContract.java
de.nisble.droidsweeper.game.database.DSDBGameEntry.java
de.nisble.droidsweeper.game.database.DSDBHelper.java
de.nisble.droidsweeper.game.jni.FieldListener.java
de.nisble.droidsweeper.game.jni.FieldStatus.java
de.nisble.droidsweeper.game.jni.GameStatus.java
de.nisble.droidsweeper.game.jni.MatrixObserver.java
de.nisble.droidsweeper.game.jni.MineSweeperMatrix.java
de.nisble.droidsweeper.game.replay.PlayerObserver.java
de.nisble.droidsweeper.game.replay.Player.java
de.nisble.droidsweeper.game.replay.Recorder.java
de.nisble.droidsweeper.game.replay.Replay.java
de.nisble.droidsweeper.game.replay.TimeStep.java
de.nisble.droidsweeper.gui.DroidSweeperActivity.java
de.nisble.droidsweeper.gui.HighScoreActivity.java
de.nisble.droidsweeper.gui.HighScoreListAdapter.java
de.nisble.droidsweeper.gui.SettingsActivity.java
de.nisble.droidsweeper.gui.grid.FieldDrawables.java
de.nisble.droidsweeper.gui.grid.FieldView.java
de.nisble.droidsweeper.gui.grid.GameGridView.java
de.nisble.droidsweeper.utilities.LogDog.java
de.nisble.droidsweeper.utilities.Timer.java