I'm trying to create a custom layout that will show child Views in a dynamic grid fashion. Kind of like pinterest, but I haven't gotten that far yet.
Here is my layout class called PostsLayout
/**
* Displays Post records in different sizes.
*/
public class PostsLayout extends RelativeLayout
{
private class PostPos
{
public Button view;
public int Row;
public int RowSpan;
public int Column;
public int ColSpan;
public int Size;
public PostPos(Context context, int row, int row_span, int column, int col_span)
{
view = new Button(context);
view.setText("btn");
Row = row;
RowSpan = row_span;
Column = column;
ColSpan = col_span;
}
public void update(int size)
{
Size = size;
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(size, size);
lp.leftMargin = size * Column;
lp.topMargin = size * Row;
view.setLayoutParams(lp);
}
}
private ArrayList<PostPos> _posts;
/**
* The number of columns.
*/
private int _columns;
/**
* Constructor.
*
* #param context
*/
public PostsLayout(Context context, int columns)
{
super(context);
_posts = new ArrayList<PostPos>();
_columns = columns;
int rows = 3;
for (int r = 0; r < rows; r++)
{
for (int c = 0; c < _columns; c++)
{
PostPos pp = new PostPos(context, r, 1, c, 1);
_posts.add(pp);
addView(pp.view);
}
}
}
#Override
protected void onSizeChanged(int width, int height, int old_width, int old_height)
{
for (PostPos pos : _posts)
{
pos.update(width / _columns);
}
super.onSizeChanged(width, height, old_width, old_height);
}
}
When I use this Layout I see a grid of 3x3 buttons that fill up the view. That's what I wanted, but it doesn't scroll. So I tried to use a ScrollView but when I do that everything disappears. The view is just blank.
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
ScrollView scroll = new ScrollView(this);
ScrollView.LayoutParams params = new ScrollView.LayoutParams(ScrollView.LayoutParams.MATCH_PARENT, ScrollView.LayoutParams.WRAP_CONTENT);
scroll.setLayoutParams(params);
PostsLayout posts = new PostsLayout(this, 4);
scroll.addView(posts);
setContentView(scroll);
}
I'm not sure what's going wrong.
EDIT:
If I change the PostsLayout constructor so just create 1 button with defaults, then that button appears with the ScrollView. So I'm must be doing something wrong with my other buttons.
Here is the code.
public PostsLayout(Context context, int columns)
{
super(context);
_posts = new ArrayList<PostPos>();
_columns = columns;
int rows = 3;
for (int r = 0; r < rows; r++)
{
for (int c = 0; c < _columns; c++)
{
//PostPos pp = new PostPos(context, r, 1, c, 1);
//_posts.add(pp);
//addView(pp.view);
}
}
// this button does show!?!?
addView(new Button(context));
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
this.setLayoutParams(params);
}
EDIT:
onSizeChanged called with wrong values. When there is no ScrollView the PostsLayout is resized to fill the full size of the device, but when it's in a ScrollView the onSizeChanged method is called with very small size values. This is the reason nothing is being shown on the device.
I added this to the constructor for PostsLayout but it didn't fix it.
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
this.setLayoutParams(params);
A Custom Layout
I was able to resolve the above issues by implementing my own ViewGroup class. This is a Tiles layout that positions the children in a grid. The child views can span across a rows and columns. The difference between this is the GridLayout is it fills holes left by spanning children.
Here is the source code.
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import com.cgtag.R;
/**
* This class positions child Views in a grid pattern, and allows some Views to
* span across more then one grid cell.
* <p>
* The layout works by absolutely position each child in a row/column position.
* As each child is positioned next to it's sibling the layout keeps track of
* the depth for that column. This is used to fill holes that are created by
* larger Views.
*/
public class TilesLayout extends ViewGroup
{
private static String TAG = TilesLayout.class.getName();
/**
* The parameters for this layout.
*/
public static class LayoutParams extends ViewGroup.LayoutParams
{
/**
* Number of Rows to span.
*/
private int _row_span;
/**
* Number of Columns to span.
*/
private int _column_span;
/**
* X location after measurements.
*/
private int _x;
/**
* Y location after measurements.
*/
private int _y;
/**
* Width of the child after measurements.
*/
private int _width;
/**
* Height of the child after measurements.
*/
private int _height;
/**
* #return the row_span
*/
public int getRowSpan()
{
return _row_span;
}
/**
* #param row_span
* the row_span to set
*/
public void setRowSpan(int row_span)
{
this._row_span = Math.max(1, row_span);
}
/**
* #return the column_span
*/
public int getColumnSpan()
{
return _column_span;
}
/**
* #param column_span
* the column_span to set
*/
public void setColumnSpan(int column_span)
{
this._column_span = Math.max(1, column_span);
}
/**
* Constructor
*/
public LayoutParams()
{
super(0, 0);
Init();
}
/**
* Constructor
*
* #param context
* The view context.
* #param attrs
* The attributes to read.
*/
public LayoutParams(Context context, AttributeSet attrs)
{
super(context, attrs);
Init();
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.TilesLayout_Layout, 0, 0);
try
{
setRowSpan(a.getInt(R.styleable.TilesLayout_Layout_row_span, 1));
setColumnSpan(a.getInt(R.styleable.TilesLayout_Layout_column_span, 1));
}
finally
{
a.recycle();
}
}
/**
* Width/Height constructor
*
* #param w
* #param h
*/
public LayoutParams(int w, int h)
{
super(w, h);
Init();
}
/**
* Set default values.
*/
private void Init()
{
_row_span = 1;
_column_span = 1;
_x = _y = _width = _height = 0;
}
}
/**
* The width of a column in pixels.
*/
private int _column_width;
/**
* Layout constructor.
*
* #param context
* #param attrs
*/
public TilesLayout(Context context, AttributeSet attrs)
{
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.TilesLayout, 0, 0);
try
{
_column_width = a.getDimensionPixelSize(R.styleable.TilesLayout_column_width, 200);
}
finally
{
a.recycle();
}
}
/**
* #return the column_width
*/
public int getColumnWidth()
{
return this._column_width;
}
/**
* #param pColumnWidth
* the column_width to set
*/
public void setColumnWidth(int pColumnWidth)
{
this._column_width = pColumnWidth;
this.invalidate();
this.requestLayout();
}
/**
* A simple measurement using the padding and suggested sizes.
*
* #see android.view.View#onMeasure(int, int)
*/
#SuppressLint("DrawAllocation")
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
Log.d(TAG, "onMeasure: width - " + MeasureSpec.toString(widthMeasureSpec));
Log.d(TAG, "onMeasure: height - " + MeasureSpec.toString(heightMeasureSpec));
// compute the width we need
int width = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
width = Math.max(width, MeasureSpec.getSize(widthMeasureSpec));
// compute the layout of all the children.
LayoutProcessor processor = new LayoutProcessor(width);
final int height = processor.MeasureAllChildren();
// measure all the children (required so children render correctly).
for(int i=0, c = getChildCount(); i < c; i++)
{
final View child = getChildAt(i);
final TilesLayout.LayoutParams params = (TilesLayout.LayoutParams)child.getLayoutParams();
final int cellWidthSpec = MeasureSpec.makeMeasureSpec(params._width, MeasureSpec.AT_MOST);
final int cellHeightSpec = MeasureSpec.makeMeasureSpec(params._height, MeasureSpec.AT_MOST);
child.measure(cellWidthSpec, cellHeightSpec);
}
// set out measure
setMeasuredDimension(width, height);
}
/**
* The minimum width should be at least the width of 1 column.
*
* #see android.view.View#getSuggestedMinimumWidth()
*/
#Override
protected int getSuggestedMinimumWidth()
{
return Math.max(super.getSuggestedMinimumWidth(), this._column_width);
}
/**
* The minimum height should be at least the height of 1 column.
*
* #see android.view.View#getSuggestedMinimumHeight()
*/
#Override
protected int getSuggestedMinimumHeight()
{
return Math.max(super.getSuggestedMinimumHeight(), this._column_width);
}
/**
* This layout positions the child views in the onSizeChanged event. Since
* the position of children is directly related to the size of this view.
*
* #see android.view.ViewGroup#onLayout(boolean, int, int, int, int)
*/
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
{
for (int i = 0, c = getChildCount(); i < c; i++)
{
final View child = getChildAt(i);
TilesLayout.LayoutParams params = (TilesLayout.LayoutParams) child.getLayoutParams();
child.layout(params._x, params._y, params._x + params._width, params._y + params._height);
}
}
/**
* Check if the parameters are our LayoutParam class. If this returns False
* then the generateDefaultLayoutParams() method will be called.
*
* #see android.view.ViewGroup#checkLayoutParams(android.view.ViewGroup.LayoutParams)
*/
#Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p)
{
return p instanceof LayoutParams;
}
/**
* Creates default layout parameters for child Views added to this
* ViewGroup.
*
* #see android.view.ViewGroup#generateDefaultLayoutParams()
*/
#Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams()
{
return new TilesLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
/**
* This is used to create LayoutParams for child views. After this is called
* the checkLayoutParams() is called to verify if the LayoutParams is
* something this view can use. If not, then defaults will be created using
* generateDefaultLayoutParams().
*
* #see android.view.ViewGroup#generateLayoutParams(android.util.AttributeSet)
*/
#Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs)
{
return new TilesLayout.LayoutParams(getContext(), attrs);
}
/**
* This is called to convert a LayoutParams that this View doesn't support
* into one it does. This is called when the checkLayoutParams() false, and
* LayoutParams need to be converted.
*
* #see android.view.ViewGroup#generateLayoutParams(android.view.ViewGroup.LayoutParams)
*/
#Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p)
{
return new TilesLayout.LayoutParams(p.width, p.height);
}
/**
* This class represents the algorithm used to position child views. The
* algorithm uses counters for each column so fill gaps as child views span
* across rows/columns.
*/
private class LayoutProcessor
{
/**
* Number of columns.
*/
private int _column_count;
/**
* The width of each column.
*/
private int _column_size;
/**
* The number of items in a column.
*/
private int _columns[];
/**
* Constructor
*
* #param width
* The width of the layout.
*/
public LayoutProcessor(int width)
{
// calculates the widest width a column can be so that they spac
// across the entire View, but not so large 1 column exceeds the
// width of the View.
_column_count = Math.max(1, ((int) (width / _column_width)));
_column_size = width / _column_count;
_columns = new int[_column_count];
}
/**
* Checks the spanning of a child to see if it fits in the current
* cursor position across multiple columns.
*
* #param child
* The view to check.
* #param row
* The row offset.
* #param column
* The column offset.
* #return True if it fits.
*/
private boolean childFits(View child, int row, int column)
{
TilesLayout.LayoutParams params = (TilesLayout.LayoutParams) child.getLayoutParams();
for (int i = column, c = column + Math.min(_column_count, params.getColumnSpan()); i < c; i++)
{
if (i == _column_count || _columns[i] > row)
{
return false;
}
}
return true;
}
/**
* Positions the child by setting it's LayoutParams. The position is
* calculated by the size of a column, and the row/column offset.
*
* #param child
* The child to position.
* #param row
* The row offset.
* #param column
* The column offset.
*
* #return The bottom of the view.
*/
private int MeasureChild(View child, int row, int column)
{
TilesLayout.LayoutParams params = (TilesLayout.LayoutParams) child.getLayoutParams();
final int col_span = Math.min(_column_count, params.getColumnSpan());
final int row_span = params.getRowSpan();
params._x = _column_size * column;
params._y = _column_size * row;
params._width = _column_size * col_span;
params._height = _column_size * row_span;
params._x += 4;
params._y += 4;
params._width -= 8;
params._height -= 8;
// increment the counters for each column this view covers
for (int r = row, rc = row + row_span; r < rc; r++)
{
for (int c = column, cc = Math.min(column + col_span, _column_count + 1); c < cc; c++)
{
_columns[c]++;
}
}
child.setLayoutParams(params);
return params._y + params._height;
}
/**
* Processes all the child for the View until they are positioned into a
* tile layout.
*
* #return The total height for the view.
*/
public int MeasureAllChildren()
{
// a list of children marked True if it has been used.
final int child_count = getChildCount();
boolean done[] = new boolean[child_count];
int offset = 0, row = 0, column = 0, height = 0;
// Continue to layout children until all the children
// have been positioned.
while (offset < child_count)
{
// Starting from the offset try to find a child
// that will fit. Continue to start from offset
// until that child is positioned, and skip
// over other child by checking `used[i]`
int i;
for (i = offset; i < child_count; i++)
{
if (!done[i])
{
View child = getChildAt(i);
// Does this child have room to span across
// columns/rows?
if (childFits(child, row, column))
{
// position the child
final int bottom = MeasureChild(child, row, column);
height = Math.max(bottom, height);
// don't position this child again
done[i] = true;
break;
}
}
}
// move the offset to the next available child.
while (offset < child_count && done[offset])
{
offset++;
}
// move to the next column
column++;
// wrap to next row
if (column == _column_count)
{
column = 0;
row++;
}
}
return height;
}
}
}
Here are the custom attrs.xml settings.
<declare-styleable name="TilesLayout">
<attr name="column_width" format="dimension" />
</declare-styleable>
<declare-styleable name="TilesLayout_Layout">
<attr name="column_span" format="integer"/>
<attr name="row_span" format="integer"/>
</declare-styleable>
Related
So I working on adding sound to an Android Snake game. What I am required to do is to add sound in for 3 events...
When the snake moves
When the snake eats
When the snake collides w/ itself or the wall
In order to do this I am using the SoundPool class and to sort of clean things up I just made separate class could SoundPlayer that sets up everything and has 3 methods to address the events. However, I am am trying not to restructure the code we were given too much but the primary code regarding movement and events is not in onCreate(). Where all examples I have practiced with say to generate the instantiate and add call the sound. Instead it is in its own .java file that is called in onCreate() so I am at my wits end on how and where I can instantiate the SoundPlayer class.
Can anyone out there help with how I can achieve the requirements and understand where I am going wrong with trying to use this class and its methods?
Snake.java
package com.example.android.snake;
import android.app.Activity;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.SoundPool;
import android.os.Build;
import android.os.Bundle;
import android.view.Window;
import android.widget.TextView;
/**
* Snake: a simple game that everyone can enjoy.
*
* This is an implementation of the classic Game "Snake", in which you control a
* serpent roaming around the garden looking for apples. Be careful, though,
* because when you catch one, not only will you become longer, but you'll move
* faster. Running into yourself or the walls will end the game.
*
*/
public class Snake extends Activity {
private SnakeView mSnakeView;
private static String ICICLE_KEY = "snake-view";
//U01A1: Initialize Sound class
SoundPlayer sound;
public SoundPlayer getSound() {
return sound;
}
/**
* Called when Activity is first created. Turns off the title bar, sets up
* the content views, and fires up the SnakeView.
*
*/
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.snake_layout);
mSnakeView = (SnakeView) findViewById(R.id.snake);
mSnakeView.setTextView((TextView) findViewById(R.id.text));
//sound
sound = new SoundPlayer(this); //sound
if (savedInstanceState == null) {
// We were just launched -- set up a new game
mSnakeView.setMode(SnakeView.READY);
} else {
// We are being restored
Bundle map = savedInstanceState.getBundle(ICICLE_KEY);
if (map != null) {
mSnakeView.restoreState(map);
} else {
mSnakeView.setMode(SnakeView.PAUSE);
}
}
}
#Override
protected void onPause() {
super.onPause();
// Pause the game along with the activity
mSnakeView.setMode(SnakeView.PAUSE);
}
#Override
public void onSaveInstanceState(Bundle outState) {
//Store the game state
outState.putBundle(ICICLE_KEY, mSnakeView.saveState());
}
}
SnakeView.java
package com.example.android.snake;
import java.util.ArrayList;
import java.util.Random;
import android.content.Context;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.TextView;
/**
* SnakeView: implementation of a simple game of Snake
*
*
*/
public class SnakeView extends TileView {
private static final String TAG = "SnakeView";
//private SoundPlayer sound;
/**
* Current mode of application: READY to run, RUNNING, or you have already
* lost. static final ints are used instead of an enum for performance
* reasons.
*/
private int mMode = READY;
public static final int PAUSE = 0;
public static final int READY = 1;
public static final int RUNNING = 2;
public static final int LOSE = 3;
/**
* Current direction the snake is headed.
*/
private int mDirection = NORTH;
private int mNextDirection = NORTH;
private static final int NORTH = 1;
private static final int SOUTH = 2;
private static final int EAST = 3;
private static final int WEST = 4;
/**
* Labels for the drawables that will be loaded into the TileView class
*/
private static final int RED_STAR = 1;
private static final int YELLOW_STAR = 2;
private static final int GREEN_STAR = 3;
/**
* mScore: used to track the number of apples captured mMoveDelay: number of
* milliseconds between snake movements. This will decrease as apples are
* captured.
*/
private long mScore = 0;
private long mMoveDelay = 600;
/**
* mLastMove: tracks the absolute time when the snake last moved, and is used
* to determine if a move should be made based on mMoveDelay.
*/
private long mLastMove;
/**
* mStatusText: text shows to the user in some run states
*/
private TextView mStatusText;
/**
* mSnakeTrail: a list of Coordinates that make up the snake's body
* mAppleList: the secret location of the juicy apples the snake craves.
*/
private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordinate>();
private ArrayList<Coordinate> mAppleList = new ArrayList<Coordinate>();
/**
* Everyone needs a little randomness in their life
*/
private static final Random RNG = new Random();
/**
* Create a simple handler that we can use to cause animation to happen. We
* set ourselves as a target and we can use the sleep()
* function to cause an update/invalidate to occur at a later date.
*/
private RefreshHandler mRedrawHandler = new RefreshHandler();
class RefreshHandler extends Handler {
#Override
public void handleMessage(Message msg) {
SnakeView.this.update();
SnakeView.this.invalidate();
}
public void sleep(long delayMillis) {
this.removeMessages(0);
sendMessageDelayed(obtainMessage(0), delayMillis);
}
};
/**
* Constructs a SnakeView based on inflation from XML
*
* #param context
* #param attrs
*/
public SnakeView(Context context, AttributeSet attrs) {
super(context, attrs);
initSnakeView();
}
public SnakeView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initSnakeView();
}
private void initSnakeView() {
setFocusable(true);
Resources r = this.getContext().getResources();
resetTiles(4);
loadTile(RED_STAR, r.getDrawable(R.drawable.redstar));
loadTile(YELLOW_STAR, r.getDrawable(R.drawable.yellowstar));
loadTile(GREEN_STAR, r.getDrawable(R.drawable.greenstar));
}
private void initNewGame() {
mSnakeTrail.clear();
mAppleList.clear();
// For now we're just going to load up a short default eastbound snake
// that's just turned north
mSnakeTrail.add(new Coordinate(7, 7));
mSnakeTrail.add(new Coordinate(6, 7));
mSnakeTrail.add(new Coordinate(5, 7));
mSnakeTrail.add(new Coordinate(4, 7));
mSnakeTrail.add(new Coordinate(3, 7));
mSnakeTrail.add(new Coordinate(2, 7));
mNextDirection = NORTH;
// Two apples to start with
addRandomApple();
addRandomApple();
mMoveDelay = 600;
mScore = 0;
}
/**
* Given a ArrayList of coordinates, we need to flatten them into an array of
* ints before we can stuff them into a map for flattening and storage.
*
* #param cvec : a ArrayList of Coordinate objects
* #return : a simple array containing the x/y values of the coordinates
* as [x1,y1,x2,y2,x3,y3...]
*/
private int[] coordArrayListToArray(ArrayList<Coordinate> cvec) {
int count = cvec.size();
int[] rawArray = new int[count * 2];
for (int index = 0; index < count; index++) {
Coordinate c = cvec.get(index);
rawArray[2 * index] = c.x;
rawArray[2 * index + 1] = c.y;
}
return rawArray;
}
/**
* Save game state so that the user does not lose anything
* if the game process is killed while we are in the
* background.
*
* #return a Bundle with this view's state
*/
public Bundle saveState() {
Bundle map = new Bundle();
map.putIntArray("mAppleList", coordArrayListToArray(mAppleList));
map.putInt("mDirection", Integer.valueOf(mDirection));
map.putInt("mNextDirection", Integer.valueOf(mNextDirection));
map.putLong("mMoveDelay", Long.valueOf(mMoveDelay));
map.putLong("mScore", Long.valueOf(mScore));
map.putIntArray("mSnakeTrail", coordArrayListToArray(mSnakeTrail));
return map;
}
/**
* Given a flattened array of ordinate pairs, we reconstitute them into a
* ArrayList of Coordinate objects
*
* #param rawArray : [x1,y1,x2,y2,...]
* #return a ArrayList of Coordinates
*/
private ArrayList<Coordinate> coordArrayToArrayList(int[] rawArray) {
ArrayList<Coordinate> coordArrayList = new ArrayList<Coordinate>();
int coordCount = rawArray.length;
for (int index = 0; index < coordCount; index += 2) {
Coordinate c = new Coordinate(rawArray[index], rawArray[index + 1]);
coordArrayList.add(c);
}
return coordArrayList;
}
/**
* Restore game state if our process is being relaunched
*
* #param icicle a Bundle containing the game state
*/
public void restoreState(Bundle icicle) {
setMode(PAUSE);
mAppleList = coordArrayToArrayList(icicle.getIntArray("mAppleList"));
mDirection = icicle.getInt("mDirection");
mNextDirection = icicle.getInt("mNextDirection");
mMoveDelay = icicle.getLong("mMoveDelay");
mScore = icicle.getLong("mScore");
mSnakeTrail = coordArrayToArrayList(icicle.getIntArray("mSnakeTrail"));
}
/*
* handles key events in the game. Update the direction our snake is traveling
* based on the DPAD. Ignore events that would cause the snake to immediately
* turn back on itself.
*
* (non-Javadoc)
*
* #see android.view.View#onKeyDown(int, android.os.KeyEvent)
*/
#Override
public boolean onKeyDown(int keyCode, KeyEvent msg) {
if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
if (mMode == READY | mMode == LOSE) {
/*
* At the beginning of the game, or the end of a previous one,
* we should start a new game.
*/
initNewGame();
setMode(RUNNING);
update();
return (true);
}
if (mMode == PAUSE) {
/*
* If the game is merely paused, we should just continue where
* we left off.
*/
setMode(RUNNING);
update();
return (true);
}
if (mDirection != SOUTH) {
mNextDirection = NORTH;
}
return (true);
}
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
if (mDirection != NORTH) {
mNextDirection = SOUTH;
}
return (true);
}
if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
if (mDirection != EAST) {
mNextDirection = WEST;
}
return (true);
}
if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
if (mDirection != WEST) {
mNextDirection = EAST;
}
return (true);
}
return super.onKeyDown(keyCode, msg);
}
/**
* Sets the TextView that will be used to give information (such as "Game
* Over" to the user.
*
* #param newView
*/
public void setTextView(TextView newView) {
mStatusText = newView;
}
/**
* Updates the current mode of the application (RUNNING or PAUSED or the like)
* as well as sets the visibility of textview for notification
*
* #param newMode
*/
public void setMode(int newMode) {
int oldMode = mMode;
mMode = newMode;
if (newMode == RUNNING & oldMode != RUNNING) {
mStatusText.setVisibility(View.INVISIBLE);
update();
return;
}
Resources res = getContext().getResources();
CharSequence str = "";
if (newMode == PAUSE) {
str = res.getText(R.string.mode_pause);
}
if (newMode == READY) {
str = res.getText(R.string.mode_ready);
}
if (newMode == LOSE) {
str = res.getString(R.string.mode_lose_prefix) + mScore
+ res.getString(R.string.mode_lose_suffix);
}
mStatusText.setText(str);
mStatusText.setVisibility(View.VISIBLE);
}
/**
* Selects a random location within the garden that is not currently covered
* by the snake. Currently _could_ go into an infinite loop if the snake
* currently fills the garden, but we'll leave discovery of this prize to a
* truly excellent snake-player.
*
*/
private void addRandomApple() {
Coordinate newCoord = null;
boolean found = false;
while (!found) {
// Choose a new location for our apple
int newX = 1 + RNG.nextInt(mXTileCount - 2);
int newY = 1 + RNG.nextInt(mYTileCount - 2);
newCoord = new Coordinate(newX, newY);
// Make sure it's not already under the snake
boolean collision = false;
int snakelength = mSnakeTrail.size();
for (int index = 0; index < snakelength; index++) {
if (mSnakeTrail.get(index).equals(newCoord)) {
collision = true;
}
}
// if we're here and there's been no collision, then we have
// a good location for an apple. Otherwise, we'll circle back
// and try again
found = !collision;
}
if (newCoord == null) {
Log.e(TAG, "Somehow ended up with a null newCoord!");
}
mAppleList.add(newCoord);
}
/**
* Handles the basic update loop, checking to see if we are in the running
* state, determining if a move should be made, updating the snake's location.
*/
public void update() {
if (mMode == RUNNING) {
long now = System.currentTimeMillis();
if (now - mLastMove > mMoveDelay) {
clearTiles();
updateWalls();
updateSnake();
updateApples();
mLastMove = now;
}
mRedrawHandler.sleep(mMoveDelay);
}
}
/**
* Draws some walls.
*
*/
private void updateWalls() {
for (int x = 0; x < mXTileCount; x++) {
setTile(GREEN_STAR, x, 0);
setTile(GREEN_STAR, x, mYTileCount - 1);
}
for (int y = 1; y < mYTileCount - 1; y++) {
setTile(GREEN_STAR, 0, y);
setTile(GREEN_STAR, mXTileCount - 1, y);
}
}
/**
* Draws some apples.
*
*/
private void updateApples() {
for (Coordinate c : mAppleList) {
setTile(YELLOW_STAR, c.x, c.y);
}
}
/**
* Figure out which way the snake is going, see if he's run into anything (the
* walls, himself, or an apple). If he's not going to die, we then add to the
* front and subtract from the rear in order to simulate motion. If we want to
* grow him, we don't subtract from the rear.
*
*/
private void updateSnake() {
boolean growSnake = false;
// grab the snake by the head
Coordinate head = mSnakeTrail.get(0);
Coordinate newHead = new Coordinate(1, 1);
mDirection = mNextDirection;
switch (mDirection) {
case EAST: {
newHead = new Coordinate(head.x + 1, head.y);
break;
}
case WEST: {
newHead = new Coordinate(head.x - 1, head.y);
break;
}
case NORTH: {
newHead = new Coordinate(head.x, head.y - 1);
break;
}
case SOUTH: {
newHead = new Coordinate(head.x, head.y + 1);
break;
}
}
// Collision detection
// For now we have a 1-square wall around the entire arena
if ((newHead.x < 1) || (newHead.y < 1) || (newHead.x > mXTileCount - 2)
|| (newHead.y > mYTileCount - 2)) {
setMode(LOSE);
return;
}
// Look for collisions with itself
int snakelength = mSnakeTrail.size();
for (int snakeindex = 0; snakeindex < snakelength; snakeindex++) {
Coordinate c = mSnakeTrail.get(snakeindex);
if (c.equals(newHead)) {
setMode(LOSE);
return;
}
}
// Look for apples
int applecount = mAppleList.size();
for (int appleindex = 0; appleindex < applecount; appleindex++) {
Coordinate c = mAppleList.get(appleindex);
if (c.equals(newHead)) {
mAppleList.remove(c);
addRandomApple();
mScore++;
mMoveDelay *= 0.9;
growSnake = true;
}
}
// push a new head onto the ArrayList and pull off the tail
mSnakeTrail.add(0, newHead);
// except if we want the snake to grow
if (!growSnake) {
mSnakeTrail.remove(mSnakeTrail.size() - 1);
}
int index = 0;
for (Coordinate c : mSnakeTrail) {
if (index == 0) {
setTile(YELLOW_STAR, c.x, c.y);
} else {
setTile(RED_STAR, c.x, c.y);
}
index++;
}
}
/**
* Simple class containing two integer values and a comparison function.
* There's probably something I should use instead, but this was quick and
* easy to build.
*
*/
private class Coordinate {
public int x;
public int y;
public Coordinate(int newX, int newY) {
x = newX;
y = newY;
}
public boolean equals(Coordinate other) {
if (x == other.x && y == other.y) {
return true;
}
return false;
}
#Override
public String toString() {
return "Coordinate: [" + x + "," + y + "]";
}
}
}
SoundPlayer.java
package com.example.android.snake;
import android.content.Context;
import android.media.AudioManager;
import android.media.SoundPool;
public class SoundPlayer {
// U01A1: variables for SoundPool
private SoundPool soundPool; //var for soundPool
private int sound1, sound2, sound3; //var for sound files, are int because ids are ints
public SoundPlayer(Context context) {
//SoundPool (int maxStreams, int streamType, int srcQuality)
soundPool = new SoundPool (3, AudioManager.STREAM_MUSIC, 0);
//loading the sounds; priority set to 1
sound1 = soundPool.load(context, R.raw.sound1, 1);
sound2 = soundPool.load(context, R.raw.sound2, 1);
sound3 = soundPool.load(context, R.raw.sound3, 1);
}
//method to play sound1
public void playMoveSound() {
// play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)
soundPool.play(sound1, 1,1,0,0,1);
}
//method to play sound2
public void playEatSound() {
soundPool.play(sound2, 1,1,0,0,1);
}
//method to play sound3
public void playHitsound() {
soundPool.play(sound3, 1,1,0,0,1);
}
}
I am new to working in Android Studio and only average with java, thus far. I am working on an assignment for school, but I am having problems getting SoundPool to work without crashing the application. I know the app runs without the sound I have added, but I am uncertain where my error lies as there seems to be so many different ways to approach this problem.
The error I am getting in debug is:
java.lang.NullPointerException: Attempt to invoke virtual method 'int
android.media.SoundPool.play(int, float, float, int, int, float)' on a
null object reference
My code is below.
public class Snake extends Activity {
private SnakeView mSnakeView;
private static String ICICLE_KEY = "snake-view";
/**
* Called when Activity is first created. Turns off the title bar, sets up
* the content views, and fires up the SnakeView.
*
*/
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//set the layout
setContentView(R.layout.snake_layout);
mSnakeView = (SnakeView) findViewById(R.id.snake);
mSnakeView.setTextView((TextView) findViewById(R.id.text));
if (savedInstanceState == null) {
// We were just launched -- set up a new game
mSnakeView.setMode(SnakeView.READY);
} else {
// We are being restored
Bundle map = savedInstanceState.getBundle(ICICLE_KEY);
if (map != null) {
mSnakeView.restoreState(map);
} else {
mSnakeView.setMode(SnakeView.PAUSE);
}
}
}
#Override
protected void onPause() {
super.onPause();
// Pause the game along with the activity
mSnakeView.setMode(SnakeView.PAUSE);
}
#Override
public void onSaveInstanceState(Bundle outState) {
//Store the game state
outState.putBundle(ICICLE_KEY, mSnakeView.saveState());
}
public class SnakeView extends TileView {
private static final String TAG = "SnakeView";
private Context m_context;
//Sound
//initialize sound variables
private SoundPool sounds;
private int ulose = -1;
private int apple = -1;
private int moving = -1;
/**
* Current mode of application: READY to run, RUNNING, or you have already
* lost. static final ints are used instead of an enum for performance
* reasons.
*/
private int mMode = READY;
public static final int PAUSE = 0;
public static final int READY = 1;
public static final int RUNNING = 2;
public static final int LOSE = 3;
/**
* Current direction the snake is headed.
*/
private int mDirection = NORTH;
private int mNextDirection = NORTH;
private static final int NORTH = 1;
private static final int SOUTH = 2;
private static final int EAST = 3;
private static final int WEST = 4;
/**
* Labels for the drawables that will be loaded into the TileView class
*/
private static final int RED_STAR = 1;
private static final int YELLOW_STAR = 2;
private static final int GREEN_STAR = 3;
/**
* mScore: used to track the number of apples captured mMoveDelay: number of
* milliseconds between snake movements. This will decrease as apples are
* captured.
*/
private long mScore = 0;
private long mMoveDelay = 600;
/**
* mLastMove: tracks the absolute time when the snake last moved, and is used
* to determine if a move should be made based on mMoveDelay.
*/
private long mLastMove;
/**
* mStatusText: text shows to the user in some run states
*/
private TextView mStatusText;
/**
* mSnakeTrail: a list of Coordinates that make up the snake's body
* mAppleList: the secret location of the juicy apples the snake craves.
*/
private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordinate>();
private ArrayList<Coordinate> mAppleList = new ArrayList<Coordinate>();
/**
* Everyone needs a little randomness in their life
*/
private static final Random RNG = new Random();
/**
* Create a simple handler that we can use to cause animation to happen. We
* set ourselves as a target and we can use the sleep()
* function to cause an update/invalidate to occur at a later date.
*/
private RefreshHandler mRedrawHandler = new RefreshHandler();
class RefreshHandler extends Handler {
#Override
public void handleMessage(Message msg) {
SnakeView.this.update();
SnakeView.this.invalidate();
}
public void sleep(long delayMillis) {
this.removeMessages(0);
sendMessageDelayed(obtainMessage(0), delayMillis);
}
};
/**
* Constructs a SnakeView based on inflation from XML
*
* #param context
* #param attrs
*/
public SnakeView(Context context, AttributeSet attrs) {
super(context, attrs);
initSnakeView();
}
public SnakeView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initSnakeView();
}
private void initSnakeView() {
setFocusable(true);
Resources r = this.getContext().getResources();
resetTiles(4);
loadTile(RED_STAR, r.getDrawable(R.drawable.redstar));
loadTile(YELLOW_STAR, r.getDrawable(R.drawable.yellowstar));
loadTile(GREEN_STAR, r.getDrawable(R.drawable.greenstar));
}
private void initNewGame() {
mSnakeTrail.clear();
mAppleList.clear();
// For now we're just going to load up a short default eastbound snake
// that's just turned north
mSnakeTrail.add(new Coordinate(7, 7));
mSnakeTrail.add(new Coordinate(6, 7));
mSnakeTrail.add(new Coordinate(5, 7));
mSnakeTrail.add(new Coordinate(4, 7));
mSnakeTrail.add(new Coordinate(3, 7));
mSnakeTrail.add(new Coordinate(2, 7));
mNextDirection = NORTH;
// Two apples to start with
addRandomApple();
addRandomApple();
mMoveDelay = 600;
mScore = 0;
}
/**
* Given a ArrayList of coordinates, we need to flatten them into an array of
* ints before we can stuff them into a map for flattening and storage.
*
* #param cvec : a ArrayList of Coordinate objects
* #return : a simple array containing the x/y values of the coordinates
* as [x1,y1,x2,y2,x3,y3...]
*/
private int[] coordArrayListToArray(ArrayList<Coordinate> cvec) {
int count = cvec.size();
int[] rawArray = new int[count * 2];
for (int index = 0; index < count; index++) {
Coordinate c = cvec.get(index);
rawArray[2 * index] = c.x;
rawArray[2 * index + 1] = c.y;
}
return rawArray;
}
/**
* Save game state so that the user does not lose anything
* if the game process is killed while we are in the
* background.
*
* #return a Bundle with this view's state
*/
public Bundle saveState() {
Bundle map = new Bundle();
map.putIntArray("mAppleList", coordArrayListToArray(mAppleList));
map.putInt("mDirection", Integer.valueOf(mDirection));
map.putInt("mNextDirection", Integer.valueOf(mNextDirection));
map.putLong("mMoveDelay", Long.valueOf(mMoveDelay));
map.putLong("mScore", Long.valueOf(mScore));
map.putIntArray("mSnakeTrail", coordArrayListToArray(mSnakeTrail));
return map;
}
/**
* Given a flattened array of ordinate pairs, we reconstitute them into a
* ArrayList of Coordinate objects
*
* #param rawArray : [x1,y1,x2,y2,...]
* #return a ArrayList of Coordinates
*/
private ArrayList<Coordinate> coordArrayToArrayList(int[] rawArray) {
ArrayList<Coordinate> coordArrayList = new ArrayList<Coordinate>();
int coordCount = rawArray.length;
for (int index = 0; index < coordCount; index += 2) {
Coordinate c = new Coordinate(rawArray[index], rawArray[index + 1]);
coordArrayList.add(c);
}
return coordArrayList;
}
/**
* Restore game state if our process is being relaunched
*
* #param icicle a Bundle containing the game state
*/
public void restoreState(Bundle icicle) {
setMode(PAUSE);
mAppleList = coordArrayToArrayList(icicle.getIntArray("mAppleList"));
mDirection = icicle.getInt("mDirection");
mNextDirection = icicle.getInt("mNextDirection");
mMoveDelay = icicle.getLong("mMoveDelay");
mScore = icicle.getLong("mScore");
mSnakeTrail = coordArrayToArrayList(icicle.getIntArray("mSnakeTrail"));
}
/*
* handles key events in the game. Update the direction our snake is traveling
* based on the DPAD. Ignore events that would cause the snake to immediately
* turn back on itself.
*
* (non-Javadoc)
*
* #see android.view.View#onKeyDown(int, android.os.KeyEvent)
*/
#Override
public boolean onKeyDown(int keyCode, KeyEvent msg) {
if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
if (mMode == READY | mMode == LOSE) {
/*
* At the beginning of the game, or the end of a previous one,
* we should start a new game.
*/
initNewGame();
setMode(RUNNING);
update();
return (true);
}
if (mMode == PAUSE) {
/*
* If the game is merely paused, we should just continue where
* we left off.
*/
setMode(RUNNING);
update();
return (true);
}
if (mDirection != SOUTH) {
mNextDirection = NORTH;
}
return (true);
}
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
if (mDirection != NORTH) {
mNextDirection = SOUTH;
}
return (true);
}
if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
if (mDirection != EAST) {
mNextDirection = WEST;
}
return (true);
}
if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
if (mDirection != WEST) {
mNextDirection = EAST;
}
return (true);
}
return super.onKeyDown(keyCode, msg);
}
public void loadSound(){
sounds = new SoundPool(3, AudioManager.STREAM_MUSIC, 0);
try{
//Create objects for the required classes
AssetManager assetManager = m_context.getAssets();
AssetFileDescriptor descriptor;
//Create the three sound effects in memory
descriptor = assetManager.openFd("wall.ogg");
ulose = sounds.load(descriptor, 0);
descriptor = assetManager.openFd("apple.ogg");
apple = sounds.load(descriptor, 0);
descriptor = assetManager.openFd("tiles.ogg");
moving = sounds.load(descriptor, 0);
}
catch (IOException e){
Log.e("error", "failed to load sound files");
}
}
/**
* Sets the TextView that will be used to give information (such as "Game
* Over" to the user.
*
* #param newView
*/
public void setTextView(TextView newView) {
mStatusText = newView;
}
/**
* Updates the current mode of the application (RUNNING or PAUSED or the like)
* as well as sets the visibility of textview for notification
*
* #param newMode
*/
public void setMode(int newMode) {
int oldMode = mMode;
mMode = newMode;
if (newMode == RUNNING & oldMode != RUNNING) {
mStatusText.setVisibility(View.INVISIBLE);
update();
return;
}
Resources res = getContext().getResources();
CharSequence str = "";
if (newMode == PAUSE) {
str = res.getText(R.string.mode_pause);
}
if (newMode == READY) {
str = res.getText(R.string.mode_ready);
}
if (newMode == LOSE) {
str = res.getString(R.string.mode_lose_prefix) + mScore
+ res.getString(R.string.mode_lose_suffix);
}
mStatusText.setText(str);
mStatusText.setVisibility(View.VISIBLE);
}
/**
* Selects a random location within the garden that is not currently covered
* by the snake. Currently _could_ go into an infinite loop if the snake
* currently fills the garden, but we'll leave discovery of this prize to a
* truly excellent snake-player.
*
*/
private void addRandomApple() {
Coordinate newCoord = null;
boolean found = false;
while (!found) {
// Choose a new location for our apple
int newX = 1 + RNG.nextInt(mXTileCount - 2);
int newY = 1 + RNG.nextInt(mYTileCount - 2);
newCoord = new Coordinate(newX, newY);
// Make sure it's not already under the snake
boolean collision = false;
int snakelength = mSnakeTrail.size();
for (int index = 0; index < snakelength; index++) {
if (mSnakeTrail.get(index).equals(newCoord)) {
collision = true;
}
}
// if we're here and there's been no collision, then we have
// a good location for an apple. Otherwise, we'll circle back
// and try again
found = !collision;
}
if (newCoord == null) {
Log.e(TAG, "Somehow ended up with a null newCoord!");
}
mAppleList.add(newCoord);
}
/**
* Handles the basic update loop, checking to see if we are in the running
* state, determining if a move should be made, updating the snake's location.
*/
public void update() {
if (mMode == RUNNING) {
long now = System.currentTimeMillis();
if (now - mLastMove > mMoveDelay) {
clearTiles();
updateWalls();
updateSnake();
updateApples();
mLastMove = now;
}
mRedrawHandler.sleep(mMoveDelay);
}
}
/**
* Draws some walls.
*
*/
private void updateWalls() {
for (int x = 0; x < mXTileCount; x++) {
setTile(GREEN_STAR, x, 0);
setTile(GREEN_STAR, x, mYTileCount - 1);
}
for (int y = 1; y < mYTileCount - 1; y++) {
setTile(GREEN_STAR, 0, y);
setTile(GREEN_STAR, mXTileCount - 1, y);
}
}
/**
* Draws some apples.
*
*/
private void updateApples() {
for (Coordinate c : mAppleList) {
setTile(YELLOW_STAR, c.x, c.y);
}
}
/**
* Figure out which way the snake is going, see if he's run into anything (the
* walls, himself, or an apple). If he's not going to die, we then add to the
* front and subtract from the rear in order to simulate motion. If we want to
* grow him, we don't subtract from the rear.
*
*/
private void updateSnake() {
boolean growSnake = false;
// grab the snake by the head
Coordinate head = mSnakeTrail.get(0);
Coordinate newHead = new Coordinate(1, 1);
mDirection = mNextDirection;
switch (mDirection) {
case EAST: {
newHead = new Coordinate(head.x + 1, head.y);
sounds.play(moving, 1.0f, 1.0f, 0, 0, 1.5f);
break;
}
case WEST: {
newHead = new Coordinate(head.x - 1, head.y);
sounds.play(moving, 1.0f, 1.0f, 0, 0, 1.5f);
break;
}
case NORTH: {
newHead = new Coordinate(head.x, head.y - 1);
sounds.play(moving, 1.0f, 1.0f, 0, 0, 1.5f);
break;
}
case SOUTH: {
newHead = new Coordinate(head.x, head.y + 1);
sounds.play(moving, 1.0f, 1.0f, 0, 0, 1.5f);
break;
}
}
// Collision detection
// For now we have a 1-square wall around the entire arena
if ((newHead.x < 1) || (newHead.y < 1) || (newHead.x > mXTileCount - 2)
|| (newHead.y > mYTileCount - 2)) {
sounds.play(ulose, 1.0f, 1.0f, 0, 0, 1.5f);
setMode(LOSE);
return;
}
// Look for collisions with itself
int snakelength = mSnakeTrail.size();
for (int snakeindex = 0; snakeindex < snakelength; snakeindex++) {
Coordinate c = mSnakeTrail.get(snakeindex);
if (c.equals(newHead)) {
sounds.play(ulose, 1.0f, 1.0f, 0, 0, 1.5f);
setMode(LOSE);
return;
}
}
// Look for apples
int applecount = mAppleList.size();
for (int appleindex = 0; appleindex < applecount; appleindex++) {
Coordinate c = mAppleList.get(appleindex);
if (c.equals(newHead)) {
sounds.play(apple, 1.0f, 1.0f, 0, 0, 1.5f);
mAppleList.remove(c);
addRandomApple();
mScore++;
mMoveDelay *= 0.9;
growSnake = true;
}
}
// push a new head onto the ArrayList and pull off the tail
mSnakeTrail.add(0, newHead);
// except if we want the snake to grow
if (!growSnake) {
mSnakeTrail.remove(mSnakeTrail.size() - 1);
}
int index = 0;
for (Coordinate c : mSnakeTrail) {
if (index == 0) {
setTile(YELLOW_STAR, c.x, c.y);
} else {
setTile(RED_STAR, c.x, c.y);
}
index++;
}
}
/**
* Simple class containing two integer values and a comparison function.
* There's probably something I should use instead, but this was quick and
* easy to build.
*
*/
private class Coordinate {
public int x;
public int y;
public Coordinate(int newX, int newY) {
x = newX;
y = newY;
}
public boolean equals(Coordinate other) {
if (x == other.x && y == other.y) {
return true;
}
return false;
}
#Override
public String toString() {
return "Coordinate: [" + x + "," + y + "]";
}
}
}
I am not looking for someone to do my work for me, I just want to be able to understand where I have made mistakes and the proper approach to correct them so I can learn from this as intended. Any suggestions are greatly appreciated.
(1) You don't ever seem to call the method loadSound().
You don't instantiate the soundPool object, that is the error you are getting. You need to call the loadSound() method.
(2) Your not setting m_context to a value, do this:
m_context = getApplicationContext();
(3) There is an easier way to load sounds from the raw directory:
apple = soundsload(getApplicationContext(),R.raw.apple,1);
I am making a webserver that generates checker-like tiles Images on java.
The class that models the checker is:
package app.app.image;
import app.app.image.iterate.ImageElementVisitorInterface;
import app.app.image.tiles.Tile;
import java.awt.*;
import java.util.ArrayList;
import org.apache.commons.collections4.CollectionUtils;
/**
* Created by pcmagas on 5/10/2016.
*
* Methos that Implements a Checkerboard with multiple color tiles
*/
public class Checker implements ImageElement
{
/**
* The tiles that can be used to Draw the checker
*/
private ArrayList<Tile> availableTiles=null;
/**
* The grid where the tiles are drawn
*/
private Tile[][] checkerGrid=null;
/**
* The checker width
*/
private int width=0;
/**
* The checker Height
*/
private int height=0;
private int tileSize=0;
/**
* Creates a new Checker
* #param width the checker's width
* #param height the checker's height
*/
public Checker(int width, int height)
{
availableTiles= new ArrayList<Tile>();
this.setHeight(height);
this.setWidth(width);
}
/**
* #param height The checker's height
*/
public void setHeight(int height)
{
this.height=height;
}
/**
* #return The checker's height
*/
public int getHeight()
{
return this.height;
}
/**
* Seth the tile width
* #param width
*/
public void setWidth(int width)
{
this.width=width;
}
/**
* Returns how wide is the tile
* #return
*/
public int getWidth()
{
return this.width;
}
/**
* Method that Allows us to append a tile to the Checker
*/
public void addTile(Tile t) throws Exception
{
if(this.tileSize >0 && t.getSize()!= this.tileSize)
{
throw new Exception("The tile does not have the same size with the orthers");
}
else if(!this.availableTiles.contains(t))
{
this.availableTiles.add(t);
this.tileSize=t.getSize();
}
}
/**
* Method that initializes the grid for the checker
*/
private void initGrid()
{
if(this.checkerGrid==null)
{
int width = this.width/this.tileSize;
int height = this.height/this.tileSize;
this.checkerGrid=new Tile[height][width];
}
}
/**
* Method that selects a tile during the grid generation
* #param top The tile that is on top of the current tile
* #param previous The tile that is on previously from the current tile
*
* #return The correctly selected tile
*/
private Tile selectTile(Tile top,Tile previous)
{
ArrayList<Tile> tilesNotToUse=new ArrayList<Tile>();
if(top==null)
{
tilesNotToUse.add(top);
}
if(previous==null)
{
tilesNotToUse.add(previous);
}
//TODO: Create a diff Between this.availableTiles and tilesNotToUse
return new Tile(Color.black,10,this);
}
#Override
public void draw(ImageElementVisitorInterface visitor)
{
this.initGrid();
for(int i=0;i<this.checkerGrid.length;i++)
{
for(int j=0;j<this.checkerGrid[i].length;j++)
{
Tile top=(i>0)?this.checkerGrid[i-1][j]:null;
Tile previous=(j>0)?this.checkerGrid[i][j-1]:null;
Tile currentTile=this.selectTile(top,previous);
this.checkerGrid[i][j]= currentTile;
currentTile.draw(visitor);
}
}
}
}
Please I have some do-not-know-hot-to-do on this method:
/**
* Method that selects a tile during the grid generation
* #param top The tile that is on top of the current tile
* #param previous The tile that is on previously from the current tile
*
* #return The correctly selected tile
*/
private Tile selectTile(Tile top,Tile previous)
{
ArrayList<Tile> tilesNotToUse=new ArrayList<Tile>();
if(top==null)
{
tilesNotToUse.add(top);
}
if(previous==null)
{
tilesNotToUse.add(previous);
}
//TODO: Create a diff Between this.availableTiles and tilesNotToUse
return new Tile(Color.black,10,this);
}
And I want to do a diff between this.availableTiles and tilesNotToUse. I have seen the CollectionUtils but I cannot figure out how to do it. I want to acheive similar result with php's http://php.net/manual/en/function.array-diff.php
You can use list1.removeAll(list2); Java Docs Thus your list1 will have only what belongs to list1 and not to list2. And the other way around if you want.
How can i do this with android (java)?
You can use below Range-Seek-Bar
check below class souce code available here
public class RangeSeekBar<T extends Number> extends ImageView {
public static final Integer DEFAULT_MINIMUM = 0;
public static final Integer DEFAULT_MAXIMUM = 100;
public static final int HEIGHT_IN_DP = 30;
public static final int TEXT_LATERAL_PADDING_IN_DP = 3;
private static final int INITIAL_PADDING_IN_DP = 8;
private final int LINE_HEIGHT_IN_DP = 1;
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Bitmap thumbImage = BitmapFactory.decodeResource(getResources(), R.drawable.seek_thumb_normal);
private final Bitmap thumbPressedImage = BitmapFactory.decodeResource(getResources(),
R.drawable.seek_thumb_pressed);
private final Bitmap thumbDisabledImage = BitmapFactory.decodeResource(getResources(),
R.drawable.seek_thumb_disabled);
private final float thumbWidth = thumbImage.getWidth();
private final float thumbHalfWidth = 0.5f * thumbWidth;
private final float thumbHalfHeight = 0.5f * thumbImage.getHeight();
private float INITIAL_PADDING;
private float padding;
private T absoluteMinValue, absoluteMaxValue;
private NumberType numberType;
private double absoluteMinValuePrim, absoluteMaxValuePrim;
private double normalizedMinValue = 0d;
private double normalizedMaxValue = 1d;
private Thumb pressedThumb = null;
private boolean notifyWhileDragging = false;
private OnRangeSeekBarChangeListener<T> listener;
/**
* Default color of a {#link RangeSeekBar}, #FF33B5E5. This is also known as "Ice Cream Sandwich" blue.
*/
public static final int DEFAULT_COLOR = Color.argb(0xFF, 0x33, 0xB5, 0xE5);
/**
* An invalid pointer id.
*/
public static final int INVALID_POINTER_ID = 255;
// Localized constants from MotionEvent for compatibility
// with API < 8 "Froyo".
public static final int ACTION_POINTER_UP = 0x6, ACTION_POINTER_INDEX_MASK = 0x0000ff00, ACTION_POINTER_INDEX_SHIFT = 8;
private float mDownMotionX;
private int mActivePointerId = INVALID_POINTER_ID;
private int mScaledTouchSlop;
private boolean mIsDragging;
private int mTextOffset;
private int mTextSize;
private int mDistanceToTop;
private RectF mRect;
private static final int DEFAULT_TEXT_SIZE_IN_DP = 14;
private static final int DEFAULT_TEXT_DISTANCE_TO_BUTTON_IN_DP = 8;
private static final int DEFAULT_TEXT_DISTANCE_TO_TOP_IN_DP = 8;
private boolean mSingleThumb;
public RangeSeekBar(Context context) {
super(context);
init(context, null);
}
public RangeSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public RangeSeekBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
private T extractNumericValueFromAttributes(TypedArray a, int attribute, int defaultValue) {
TypedValue tv = a.peekValue(attribute);
if (tv == null) {
return (T) Integer.valueOf(defaultValue);
}
int type = tv.type;
if (type == TypedValue.TYPE_FLOAT) {
return (T) Float.valueOf(a.getFloat(attribute, defaultValue));
} else {
return (T) Integer.valueOf(a.getInteger(attribute, defaultValue));
}
}
private void init(Context context, AttributeSet attrs) {
if (attrs == null) {
setRangeToDefaultValues();
} else {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.RangeSeekBar, 0, 0);
setRangeValues(
extractNumericValueFromAttributes(a, R.styleable.RangeSeekBar_absoluteMinValue, DEFAULT_MINIMUM),
extractNumericValueFromAttributes(a, R.styleable.RangeSeekBar_absoluteMaxValue, DEFAULT_MAXIMUM));
mSingleThumb = a.getBoolean(R.styleable.RangeSeekBar_singleThumb, false);
a.recycle();
}
setValuePrimAndNumberType();
INITIAL_PADDING = PixelUtil.dpToPx(context, INITIAL_PADDING_IN_DP);
mTextSize = PixelUtil.dpToPx(context, DEFAULT_TEXT_SIZE_IN_DP);
mDistanceToTop = PixelUtil.dpToPx(context, DEFAULT_TEXT_DISTANCE_TO_TOP_IN_DP);
mTextOffset = this.mTextSize + PixelUtil.dpToPx(context,
DEFAULT_TEXT_DISTANCE_TO_BUTTON_IN_DP) + this.mDistanceToTop;
float lineHeight = PixelUtil.dpToPx(context, LINE_HEIGHT_IN_DP);
mRect = new RectF(padding,
mTextOffset + thumbHalfHeight - lineHeight / 2,
getWidth() - padding,
mTextOffset + thumbHalfHeight + lineHeight / 2);
// make RangeSeekBar focusable. This solves focus handling issues in case EditText widgets are being used along with the RangeSeekBar within ScollViews.
setFocusable(true);
setFocusableInTouchMode(true);
mScaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
public void setRangeValues(T minValue, T maxValue) {
this.absoluteMinValue = minValue;
this.absoluteMaxValue = maxValue;
setValuePrimAndNumberType();
}
#SuppressWarnings("unchecked")
// only used to set default values when initialised from XML without any values specified
private void setRangeToDefaultValues() {
this.absoluteMinValue = (T) DEFAULT_MINIMUM;
this.absoluteMaxValue = (T) DEFAULT_MAXIMUM;
setValuePrimAndNumberType();
}
private void setValuePrimAndNumberType() {
absoluteMinValuePrim = absoluteMinValue.doubleValue();
absoluteMaxValuePrim = absoluteMaxValue.doubleValue();
numberType = NumberType.fromNumber(absoluteMinValue);
}
public void resetSelectedValues() {
setSelectedMinValue(absoluteMinValue);
setSelectedMaxValue(absoluteMaxValue);
}
public boolean isNotifyWhileDragging() {
return notifyWhileDragging;
}
/**
* Should the widget notify the listener callback while the user is still dragging a thumb? Default is false.
*
* #param flag
*/
public void setNotifyWhileDragging(boolean flag) {
this.notifyWhileDragging = flag;
}
/**
* Returns the absolute minimum value of the range that has been set at construction time.
*
* #return The absolute minimum value of the range.
*/
public T getAbsoluteMinValue() {
return absoluteMinValue;
}
/**
* Returns the absolute maximum value of the range that has been set at construction time.
*
* #return The absolute maximum value of the range.
*/
public T getAbsoluteMaxValue() {
return absoluteMaxValue;
}
/**
* Returns the currently selected min value.
*
* #return The currently selected min value.
*/
public T getSelectedMinValue() {
return normalizedToValue(normalizedMinValue);
}
/**
* Sets the currently selected minimum value. The widget will be invalidated and redrawn.
*
* #param value The Number value to set the minimum value to. Will be clamped to given absolute minimum/maximum range.
*/
public void setSelectedMinValue(T value) {
// in case absoluteMinValue == absoluteMaxValue, avoid division by zero when normalizing.
if (0 == (absoluteMaxValuePrim - absoluteMinValuePrim)) {
setNormalizedMinValue(0d);
} else {
setNormalizedMinValue(valueToNormalized(value));
}
}
/**
* Returns the currently selected max value.
*
* #return The currently selected max value.
*/
public T getSelectedMaxValue() {
return normalizedToValue(normalizedMaxValue);
}
/**
* Sets the currently selected maximum value. The widget will be invalidated and redrawn.
*
* #param value The Number value to set the maximum value to. Will be clamped to given absolute minimum/maximum range.
*/
public void setSelectedMaxValue(T value) {
// in case absoluteMinValue == absoluteMaxValue, avoid division by zero when normalizing.
if (0 == (absoluteMaxValuePrim - absoluteMinValuePrim)) {
setNormalizedMaxValue(1d);
} else {
setNormalizedMaxValue(valueToNormalized(value));
}
}
/**
* Registers given listener callback to notify about changed selected values.
*
* #param listener The listener to notify about changed selected values.
*/
public void setOnRangeSeekBarChangeListener(OnRangeSeekBarChangeListener<T> listener) {
this.listener = listener;
}
/**
* Handles thumb selection and movement. Notifies listener callback on certain events.
*/
#Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;
}
int pointerIndex;
final int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
// Remember where the motion event started
mActivePointerId = event.getPointerId(event.getPointerCount() - 1);
pointerIndex = event.findPointerIndex(mActivePointerId);
mDownMotionX = event.getX(pointerIndex);
pressedThumb = evalPressedThumb(mDownMotionX);
// Only handle thumb presses.
if (pressedThumb == null) {
return super.onTouchEvent(event);
}
setPressed(true);
invalidate();
onStartTrackingTouch();
trackTouchEvent(event);
attemptClaimDrag();
break;
case MotionEvent.ACTION_MOVE:
if (pressedThumb != null) {
if (mIsDragging) {
trackTouchEvent(event);
} else {
// Scroll to follow the motion event
pointerIndex = event.findPointerIndex(mActivePointerId);
final float x = event.getX(pointerIndex);
if (Math.abs(x - mDownMotionX) > mScaledTouchSlop) {
setPressed(true);
invalidate();
onStartTrackingTouch();
trackTouchEvent(event);
attemptClaimDrag();
}
}
if (notifyWhileDragging && listener != null) {
listener.onRangeSeekBarValuesChanged(this, getSelectedMinValue(), getSelectedMaxValue());
}
}
break;
case MotionEvent.ACTION_UP:
if (mIsDragging) {
trackTouchEvent(event);
onStopTrackingTouch();
setPressed(false);
} else {
// Touch up when we never crossed the touch slop threshold
// should be interpreted as a tap-seek to that location.
onStartTrackingTouch();
trackTouchEvent(event);
onStopTrackingTouch();
}
pressedThumb = null;
invalidate();
if (listener != null) {
listener.onRangeSeekBarValuesChanged(this, getSelectedMinValue(), getSelectedMaxValue());
}
break;
case MotionEvent.ACTION_POINTER_DOWN: {
final int index = event.getPointerCount() - 1;
// final int index = ev.getActionIndex();
mDownMotionX = event.getX(index);
mActivePointerId = event.getPointerId(index);
invalidate();
break;
}
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(event);
invalidate();
break;
case MotionEvent.ACTION_CANCEL:
if (mIsDragging) {
onStopTrackingTouch();
setPressed(false);
}
invalidate(); // see above explanation
break;
}
return true;
}
private final void onSecondaryPointerUp(MotionEvent ev) {
final int pointerIndex = (ev.getAction() & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose
// a new active pointer and adjust accordingly.
// TODO: Make this decision more intelligent.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mDownMotionX = ev.getX(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
}
}
private final void trackTouchEvent(MotionEvent event) {
final int pointerIndex = event.findPointerIndex(mActivePointerId);
final float x = event.getX(pointerIndex);
if (Thumb.MIN.equals(pressedThumb) && !mSingleThumb) {
setNormalizedMinValue(screenToNormalized(x));
} else if (Thumb.MAX.equals(pressedThumb)) {
setNormalizedMaxValue(screenToNormalized(x));
}
}
/**
* Tries to claim the user's drag motion, and requests disallowing any ancestors from stealing events in the drag.
*/
private void attemptClaimDrag() {
if (getParent() != null) {
getParent().requestDisallowInterceptTouchEvent(true);
}
}
/**
* This is called when the user has started touching this widget.
*/
void onStartTrackingTouch() {
mIsDragging = true;
}
/**
* This is called when the user either releases his touch or the touch is canceled.
*/
void onStopTrackingTouch() {
mIsDragging = false;
}
/**
* Ensures correct size of the widget.
*/
#Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = 200;
if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(widthMeasureSpec)) {
width = MeasureSpec.getSize(widthMeasureSpec);
}
int height = thumbImage.getHeight() + PixelUtil.dpToPx(getContext(), HEIGHT_IN_DP);
if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(heightMeasureSpec)) {
height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec));
}
setMeasuredDimension(width, height);
}
/**
* Draws the widget on the given canvas.
*/
#Override
protected synchronized void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setTextSize(mTextSize);
paint.setStyle(Style.FILL);
paint.setColor(Color.GRAY);
paint.setAntiAlias(true);
// draw min and max labels
String minLabel = getContext().getString(R.string.demo_min_label);
String maxLabel = getContext().getString(R.string.demo_max_label);
float minMaxLabelSize = Math.max(paint.measureText(minLabel), paint.measureText(maxLabel));
float minMaxHeight = mTextOffset + thumbHalfHeight + mTextSize / 3;
canvas.drawText(minLabel, 0, minMaxHeight, paint);
canvas.drawText(maxLabel, getWidth() - minMaxLabelSize, minMaxHeight, paint);
padding = INITIAL_PADDING + minMaxLabelSize + thumbHalfWidth;
// draw seek bar background line
mRect.left = padding;
mRect.right = getWidth() - padding;
canvas.drawRect(mRect, paint);
boolean selectedValuesAreDefault = (getSelectedMinValue().equals(getAbsoluteMinValue()) &&
getSelectedMaxValue().equals(getAbsoluteMaxValue()));
int colorToUseForButtonsAndHighlightedLine = selectedValuesAreDefault ?
Color.GRAY : // default values
DEFAULT_COLOR; //non default, filter is active
// draw seek bar active range line
mRect.left = normalizedToScreen(normalizedMinValue);
mRect.right = normalizedToScreen(normalizedMaxValue);
paint.setColor(colorToUseForButtonsAndHighlightedLine);
canvas.drawRect(mRect, paint);
// draw minimum thumb if not a single thumb control
if (!mSingleThumb) {
drawThumb(normalizedToScreen(normalizedMinValue), Thumb.MIN.equals(pressedThumb), canvas,
selectedValuesAreDefault);
}
// draw maximum thumb
drawThumb(normalizedToScreen(normalizedMaxValue), Thumb.MAX.equals(pressedThumb), canvas,
selectedValuesAreDefault);
// draw the text if sliders have moved from default edges
if (!selectedValuesAreDefault) {
paint.setTextSize(mTextSize);
paint.setColor(Color.WHITE);
// give text a bit more space here so it doesn't get cut off
int offset = PixelUtil.dpToPx(getContext(), TEXT_LATERAL_PADDING_IN_DP);
String minText = String.valueOf(getSelectedMinValue());
String maxText = String.valueOf(getSelectedMaxValue());
float minTextWidth = paint.measureText(minText) + offset;
float maxTextWidth = paint.measureText(maxText) + offset;
if (!mSingleThumb) {
canvas.drawText(minText,
normalizedToScreen(normalizedMinValue) - minTextWidth * 0.5f,
mDistanceToTop + mTextSize,
paint);
}
canvas.drawText(maxText,
normalizedToScreen(normalizedMaxValue) - maxTextWidth * 0.5f,
mDistanceToTop + mTextSize,
paint);
}
}
/**
* Overridden to save instance state when device orientation changes. This method is called automatically if you assign an id to the RangeSeekBar widget using the {#link #setId(int)} method. Other members of this class than the normalized min and max values don't need to be saved.
*/
#Override
protected Parcelable onSaveInstanceState() {
final Bundle bundle = new Bundle();
bundle.putParcelable("SUPER", super.onSaveInstanceState());
bundle.putDouble("MIN", normalizedMinValue);
bundle.putDouble("MAX", normalizedMaxValue);
return bundle;
}
/**
* Overridden to restore instance state when device orientation changes. This method is called automatically if you assign an id to the RangeSeekBar widget using the {#link #setId(int)} method.
*/
#Override
protected void onRestoreInstanceState(Parcelable parcel) {
final Bundle bundle = (Bundle) parcel;
super.onRestoreInstanceState(bundle.getParcelable("SUPER"));
normalizedMinValue = bundle.getDouble("MIN");
normalizedMaxValue = bundle.getDouble("MAX");
}
/**
* Draws the "normal" resp. "pressed" thumb image on specified x-coordinate.
*
* #param screenCoord The x-coordinate in screen space where to draw the image.
* #param pressed Is the thumb currently in "pressed" state?
* #param canvas The canvas to draw upon.
*/
private void drawThumb(float screenCoord, boolean pressed, Canvas canvas, boolean areSelectedValuesDefault) {
Bitmap buttonToDraw;
if (areSelectedValuesDefault) {
buttonToDraw = thumbDisabledImage;
} else {
buttonToDraw = pressed ? thumbPressedImage : thumbImage;
}
canvas.drawBitmap(buttonToDraw, screenCoord - thumbHalfWidth,
mTextOffset,
paint);
}
/**
* Decides which (if any) thumb is touched by the given x-coordinate.
*
* #param touchX The x-coordinate of a touch event in screen space.
* #return The pressed thumb or null if none has been touched.
*/
private Thumb evalPressedThumb(float touchX) {
Thumb result = null;
boolean minThumbPressed = isInThumbRange(touchX, normalizedMinValue);
boolean maxThumbPressed = isInThumbRange(touchX, normalizedMaxValue);
if (minThumbPressed && maxThumbPressed) {
// if both thumbs are pressed (they lie on top of each other), choose the one with more room to drag. this avoids "stalling" the thumbs in a corner, not being able to drag them apart anymore.
result = (touchX / getWidth() > 0.5f) ? Thumb.MIN : Thumb.MAX;
} else if (minThumbPressed) {
result = Thumb.MIN;
} else if (maxThumbPressed) {
result = Thumb.MAX;
}
return result;
}
/**
* Decides if given x-coordinate in screen space needs to be interpreted as "within" the normalized thumb x-coordinate.
*
* #param touchX The x-coordinate in screen space to check.
* #param normalizedThumbValue The normalized x-coordinate of the thumb to check.
* #return true if x-coordinate is in thumb range, false otherwise.
*/
private boolean isInThumbRange(float touchX, double normalizedThumbValue) {
return Math.abs(touchX - normalizedToScreen(normalizedThumbValue)) <= thumbHalfWidth;
}
/**
* Sets normalized min value to value so that 0 <= value <= normalized max value <= 1. The View will get invalidated when calling this method.
*
* #param value The new normalized min value to set.
*/
private void setNormalizedMinValue(double value) {
normalizedMinValue = Math.max(0d, Math.min(1d, Math.min(value, normalizedMaxValue)));
invalidate();
}
/**
* Sets normalized max value to value so that 0 <= normalized min value <= value <= 1. The View will get invalidated when calling this method.
*
* #param value The new normalized max value to set.
*/
private void setNormalizedMaxValue(double value) {
normalizedMaxValue = Math.max(0d, Math.min(1d, Math.max(value, normalizedMinValue)));
invalidate();
}
/**
* Converts a normalized value to a Number object in the value space between absolute minimum and maximum.
*
* #param normalized
* #return
*/
#SuppressWarnings("unchecked")
private T normalizedToValue(double normalized) {
double v = absoluteMinValuePrim + normalized * (absoluteMaxValuePrim - absoluteMinValuePrim);
// TODO parameterize this rounding to allow variable decimal points
return (T) numberType.toNumber(Math.round(v * 100) / 100d);
}
/**
* Converts the given Number value to a normalized double.
*
* #param value The Number value to normalize.
* #return The normalized double.
*/
private double valueToNormalized(T value) {
if (0 == absoluteMaxValuePrim - absoluteMinValuePrim) {
// prevent division by zero, simply return 0.
return 0d;
}
return (value.doubleValue() - absoluteMinValuePrim) / (absoluteMaxValuePrim - absoluteMinValuePrim);
}
/**
* Converts a normalized value into screen space.
*
* #param normalizedCoord The normalized value to convert.
* #return The converted value in screen space.
*/
private float normalizedToScreen(double normalizedCoord) {
return (float) (padding + normalizedCoord * (getWidth() - 2 * padding));
}
/**
* Converts screen space x-coordinates into normalized values.
*
* #param screenCoord The x-coordinate in screen space to convert.
* #return The normalized value.
*/
private double screenToNormalized(float screenCoord) {
int width = getWidth();
if (width <= 2 * padding) {
// prevent division by zero, simply return 0.
return 0d;
} else {
double result = (screenCoord - padding) / (width - 2 * padding);
return Math.min(1d, Math.max(0d, result));
}
}
/**
* Callback listener interface to notify about changed range values.
*
* #param <T> The Number type the RangeSeekBar has been declared with.
* #author Stephan Tittel (stephan.tittel#kom.tu-darmstadt.de)
*/
public interface OnRangeSeekBarChangeListener<T> {
public void onRangeSeekBarValuesChanged(RangeSeekBar<?> bar, T minValue, T maxValue);
}
/**
* Thumb constants (min and max).
*/
private static enum Thumb {
MIN, MAX
}
;
/**
* Utility enumeration used to convert between Numbers and doubles.
*
* #author Stephan Tittel (stephan.tittel#kom.tu-darmstadt.de)
*/
private static enum NumberType {
LONG, DOUBLE, INTEGER, FLOAT, SHORT, BYTE, BIG_DECIMAL;
public static <E extends Number> NumberType fromNumber(E value) throws IllegalArgumentException {
if (value instanceof Long) {
return LONG;
}
if (value instanceof Double) {
return DOUBLE;
}
if (value instanceof Integer) {
return INTEGER;
}
if (value instanceof Float) {
return FLOAT;
}
if (value instanceof Short) {
return SHORT;
}
if (value instanceof Byte) {
return BYTE;
}
if (value instanceof BigDecimal) {
return BIG_DECIMAL;
}
throw new IllegalArgumentException("Number class '" + value.getClass().getName() + "' is not supported");
}
public Number toNumber(double value) {
switch (this) {
case LONG:
return Long.valueOf((long) value);
case DOUBLE:
return value;
case INTEGER:
return Integer.valueOf((int) value);
case FLOAT:
return Float.valueOf((float)value);
case SHORT:
return Short.valueOf((short) value);
case BYTE:
return Byte.valueOf((byte) value);
case BIG_DECIMAL:
return BigDecimal.valueOf(value);
}
throw new InstantiationError("can't convert " + this + " to a Number object");
}
}
}
Ok, you can use the predefined library to acheive that, there will two Thumbs and you'll get left and right thumb value.
Use this Material Range Bar
I have been working on an application for blackberry, which orginally had two buttons (btn1 and btn2) on the screen. Now I've added a third and i am experiencing some difficulties (btn3).
Originally btn1 and btn2 were side by side, and clicking outside the button, but below it would activate the button ... a design flaw but could be overlooked.
However, I need to add a button below btn1 and when i did that two weird things occured: First is that, even if i click btn3 which is BELOW btn1, focus shifts to btn1 and btn1 is called. And clicking btn2 shifts focus to btn3 and it is activated.
I'm not entirely sure why this is happening but i'm playing around with the code pasted below. Any little help is appreciated.
btn1 = new CustomButtonField("", Bitmap.getBitmapResource("button-disabled_1a.png"), Bitmap.getBitmapResource("button-normal_2.png"));
btn2 = new CustomButtonField("", Bitmap.getBitmapResource("button-disabled_3.png"), Bitmap.getBitmapResource("button-normal_4.png"));
btn3 = new CustomButtonField("", Bitmap.getBitmapResource("button-disabled5.png"), Bitmap.getBitmapResource("button-normal_6.png"));
Background bg = BackgroundFactory.createBitmapBackground(Bitmap.getBitmapResource("background.png"));
HorizontalFieldManager vfm = new HorizontalFieldManager(){
public int getPreferredHeight() {
// TODO Auto-generated method stub
return Display.getHeight();
}
public int getPreferredWidth() {
// TODO Auto-generated method stub
return Display.getWidth();
}
protected void sublayout(int maxWidth, int maxHeight) {
// TODO Auto-generated method stub
int count = getFieldCount();
for(int i = 0 ; i < count ; i++ ){
Field f = getField(i);
if(f == btn1 ){
setPositionChild(f, (getPreferredWidth() >> 1) - f.getPreferredWidth(), getPreferredHeight()>>1);
layoutChild(f, getPreferredWidth(), getPreferredHeight());
}else if (f == btn2 ){
setPositionChild(f, (getPreferredWidth() >> 1) +30, getPreferredHeight()>>1);
layoutChild(f, getPreferredWidth(), getPreferredHeight());
}else if (f == lblName ){
setPositionChild(f, 30, getPreferredHeight()>>1 - btnLicense.getPreferredHeight());
layoutChild(f, ( getPreferredWidth() * 3 ) >> 2, getPreferredHeight());
}else if (f == btn3 ){
setPositionChild(f, (getPreferredWidth() >> 1) - f.getPreferredWidth() -0 , getPreferredHeight()- getPreferredHeight()+280);
layoutChild(f, getPreferredWidth(), getPreferredHeight());
}
}
setExtent(getPreferredWidth(),getPreferredHeight());
}
public void subpaint(Graphics graphics){
int count = getFieldCount();
for(int i = 0 ; i < count ; i++ ){
net.rim.device.api.ui.Field f = getField(i);
paintChild(graphics,f);
}
}
};
Custom Button Field
package com.app.ui.component;
import net.rim.device.api.system.Bitmap;
import net.rim.device.api.ui.Color;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.Font;
import net.rim.device.api.ui.Graphics;
public class CustomButtonField extends Field {
/** To set background image for button field */
private Bitmap bkImage;
/** To set Focus image for button field */
private Bitmap bkFocusImage;
/** int value for Field Width */
private int fieldWidth;
/** int value for Field Height */
private int fieldHeight;
/** Text to write on Button */
private String text;
/** Text Color on Button */
private int textColor = Color.WHITE;
/** Default Font for Button */
private Font defaultFont = Font.getDefault();
/**
* Constructor with
* #param text
* #param image
* #param focusImage
*/
public CustomButtonField (String text, Bitmap image, Bitmap focusImage) {
this(text, image, focusImage, 0);
}
/**
* Constructor with
* #param text
* #param image
* #param focusImage
* #param style
*/
public CustomButtonField(String text, Bitmap image, Bitmap focusImage, long style) {
super(Field.FOCUSABLE | style);
this.text = text;
bkImage = image;
this.bkFocusImage = focusImage;
fieldHeight = bkImage.getHeight();
fieldWidth = bkImage.getWidth();
}
/**
* To get the exact width needed by the field borderWidth - used to show the
* width of focused rectangle around the button
*/
public int getPreferredWidth() {
return fieldWidth;
}
/**
* To get the exact width needed by the field borderHeight - used to show
* the height of focused rectangle around the button
*/
public int getPreferredHeight() {
return fieldHeight;
}
protected void layout(int width, int height) {
setExtent(getPreferredWidth(), getPreferredHeight());
}
/**
* To set the background according to focused state of the field
*/
protected void drawFocus(Graphics graphics, boolean flag) {
graphics.setFont(defaultFont);
if (bkFocusImage != null) {
graphics.drawBitmap((getPreferredWidth() - bkFocusImage.getWidth()) / 2,(getPreferredHeight() - bkFocusImage.getHeight()) / 2,
bkFocusImage.getWidth(), bkFocusImage.getHeight(),bkFocusImage, 0, 0);
}
graphics.setColor(Color.WHITE);
int textWidth = defaultFont.getAdvance(text);
graphics.drawText(text, (fieldWidth - textWidth) / 2,(fieldHeight - defaultFont.getHeight()) / 2);
}
protected void paint(Graphics graphics) {
graphics.setFont(defaultFont);
if (bkImage != null) {
graphics.drawBitmap((getPreferredWidth() - bkImage.getWidth()) / 2,(getPreferredHeight() - bkImage.getHeight()) / 2,
bkImage.getWidth(), bkImage.getHeight(), bkImage, 0, 0);
}
graphics.setColor(textColor);
int color = (isEnabled())?Color.BLACK:Color.DARKGRAY;
graphics.setColor(color);
int textWidth = defaultFont.getAdvance(text);
graphics.drawText(text, (fieldWidth - textWidth) / 2,(fieldHeight - defaultFont.getHeight()) / 2);
}
protected boolean navigationClick(int status, int time) {
fieldChangeNotify(0);
return true;
}
}
This is a pretty easy problem to have when you're first implementing custom BlackBerry buttons and fields. First of all, the problem here is that your CustomButtonField class, which is a button field written from scratch, is not properly determining which touch events (or navigation events) are within its extent (inside the field's area).
One way to fix this is to modify your navigationClick() method, and implement the touchEvent() method:
protected boolean touchEvent( TouchEvent message ) {
int x = message.getX( 1 );
int y = message.getY( 1 );
if( x < 0 || y < 0 || x > getExtent().width || y > getExtent().height ) {
// Outside the field
return false;
}
switch( message.getEvent() ) {
case TouchEvent.UNCLICK:
fieldChangeNotify(0);
return true;
}
return super.touchEvent( message );
}
protected boolean navigationClick(int status, int time) {
if (status != 0) { // you did not have this check
fieldChangeNotify(0);
}
return true;
}
Another option, that I would actually recommend, is to replace your entire CustomButtonField class with one of the samples from BlackBerry's Advanced UI library
You can use the BitmapButtonField, and the BaseButtonField that it extends, to achieve the same functionality, with proper touch / click handling.
While you're there, take a look at some of the other UI classes in that library, as you'll probably find them quite useful.