I have custom view which draws bitmaps on canvas. I want to improve my code so the memory chart in profiler looks smooth. I want to know why there are such leaps and GC removing objects every few second. It's worth mentioning that if I remove characterCreator.draw(canvas); from draw() method then the chart is smooth. Here is how my chart looks like now:
And now the important code:
CharacterCreatorView.java
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
if(characterCreator != null)
characterCreator.draw(canvas);
invalidate();
}
CharacterCreator.java
private static final int ROW_BOTTOM_TO_TOP = 8;
private static final int ROW_RIGHT_TO_LEFT = 9;
private static final int ROW_TOP_TO_BOTTOM = 10;
private static final int ROW_LEFT_TO_RIGHT = 11;
private static final int COL_COUNT = 13;
private static final int ROW_COUNT = 21;
private final Bitmap[] leftToRights;
private final Bitmap[] rightToLefts;
private final Bitmap[] topToBottoms;
private final Bitmap[] bottomToTops;
public CharacterCreator(Bitmap baseSprite) {
this.image = baseSprite;
this.widthAllCols = image.getWidth();
this.heightAllRows = image.getHeight();
this.widthOneFrame = this.widthAllCols / 13;
this.heightOneFrame = this.heightAllRows / ROW_COUNT;
this.topToBottoms = new Bitmap[COL_COUNT];
this.rightToLefts = new Bitmap[COL_COUNT];
this.leftToRights = new Bitmap[COL_COUNT];
this.bottomToTops = new Bitmap[COL_COUNT];
imageResized = Bitmap.createScaledBitmap(getMoveBitmaps()[0], 300, 300, false);
}
(...)
public void draw(Canvas canvas) {
imageResized = Bitmap.createScaledBitmap(getCurrentMoveBitmap(), 300, 300, false);
// canvas.drawBitmap(imageResized, 0, 0, null);
}
public Bitmap getCurrentMoveBitmap() {
Bitmap[] bitmaps = this.getMoveBitmaps();
return bitmaps[this.colUsing];
}
public Bitmap[] getMoveBitmaps() {
switch (rowUsing) {
case ROW_BOTTOM_TO_TOP:
return this.bottomToTops;
case ROW_RIGHT_TO_LEFT:
return this.rightToLefts;
case ROW_TOP_TO_BOTTOM:
return this.topToBottoms;
case ROW_LEFT_TO_RIGHT:
return this.leftToRights;
default:
return null;
}
}
Related
My code does not work correctly. SurfaceView shows a black screen instead of Bitmaps. I make a game loop that doesn't depend on CPU, like Fix Your Timestep!, but I can't get it to work.
public class DrawView extends SurfaceView implements SurfaceHolder.Callback {
private DrawThreat drawThread;
public Background background;
private int height;
private int width;
public DrawView(Context context) {
super(context);
getHolder().addCallback(this);
drawThread = new DrawThreat(getHolder(), this);
setFocusable(true);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
Rect surface = getHolder().getSurfaceFrame();
this.width = surface.width();
this.height = surface.height();
Bitmap back= BitmapFactory.decodeResource(App.getContext().getResources(), R.drawable.fon);
back = Bitmap.createScaledBitmap(back, width, height, false);
background = new Background(back, back, 0, 0, 0, -height);
drawThread = new DrawThreat(getHolder(), this);
drawThread.start();
}
public class DrawThreat extends Thread {
private final DrawView drawView;
private SurfaceHolder surfaceHolder;
private boolean running=true;
private static final int UPDATES_PER_SECOND = 25;
private static final int UPDATE_INTERVAL = 1000 / UPDATES_PER_SECOND * 1000000;
private static final int MAX_FRAMESKIP = 5;
private long nextUpdate = System.nanoTime();
public DrawThreat(SurfaceHolder surfaceHolder, DrawView drawView){
super();
this.surfaceHolder = surfaceHolder;
this.drawView = drawView;
}
I tried to implement a game loop, but only a black screen is displayed
#Override
public void run(){
while (running){
Canvas canvas = surfaceHolder.lockCanvas();
int skippedFrames = 0;
while (System.nanoTime() > this.nextUpdate && skippedFrames < MAX_FRAMESKIP) {
long delta = UPDATE_INTERVAL;
this.drawView.update(delta);
this.nextUpdate += UPDATE_INTERVAL;
skippedFrames++;
}
double interpolation = (System.nanoTime() + UPDATE_INTERVAL - this.nextUpdate) / (double) UPDATE_INTERVAL;
this.drawView.draw(canvas);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
Please point out the errors and tell me the right way to implement the idea.
I am trying to change the speed of an item in a game (Tetris) and I am making a logic error that means when the key is pressed, the speed of the falling shape doesn't change. The error has something to do with the variables normalSpeed and fastSpeed as they're currently set to 600 and 50 but even when I change the values the speed remains the same.
Would you be able to see where I am going wrong, please:)
public class gamePanel extends JPanel implements KeyListener {
private static int framesPerSec = 60;
private static int delayForFrames = 1000 / framesPerSec;
public static final int boardWidth = 10;
public static final int boardHeight = 20;
public static final int pieceSize = 30;
private Timer looper;
private Color[][] board = new Color[boardWidth][boardHeight];
private Color[][] shape = {
{Color.RED, Color.RED, Color.RED},
{null, Color.RED, null}
};
private int x = 4, y = 0;
private int normalSpeed = 600;
private int fastSpeed = 50;
private int delayForShape = normalSpeed;
private long beginTime;
public gamePanel() {
// this lets the shape fall down the screen
looper = new Timer(delayForFrames, new ActionListener() {
int n =0;
#Override
public void actionPerformed(ActionEvent e) {
if(System.currentTimeMillis() - beginTime > delayForShape){
y++;
beginTime = System.currentTimeMillis();
}
repaint();
}
});
looper.start();
}
}
i have an issue with inserting custom views into my relative layout that takes only a part of the screen. When the activity loads it should populate views with specific width and height at x,y position that i get from my server, then when i click insert mode and click on the relative layout it should draw the view where i clicked as well...The problem is, sometimes it draws rectangle(should be square), sometimes just a line, sometimes nothing. It behaves differently if i change my width and height.
Custom view :
public class TableView extends View {
private static final String TAG = "TableView";
private int numberOfSeats;
private int tableId;
private int positionX;
private int positionY;
private int objectWidth;
private int objectHeight;
private boolean isTaken;
private String tableKind;
private Rect rectangle;
private Paint paint;
/* public TableView(Context context, AttributeSet attrs) {
super(context, attrs);
}*/
public TableView(Context context,int numberOfSeats,int tableId,int positionX,int positionY,int width,int height
,boolean isTaken, String tableKind) {
super(context);
this.numberOfSeats = numberOfSeats;
this.tableId = tableId;
this.positionX = positionX;
this.positionY = positionY;
this.objectWidth = width;
this.objectHeight = height;
this.isTaken = isTaken;
this.tableId = tableId;
this.tableKind = tableKind;
//defining shape
rectangle = new Rect(positionX,positionY,width,height);
//defining shape color
paint = new Paint();
paint.setColor(Color.GRAY);
Log.i(TAG, "TableView: tableId: "+tableId+" isTaken: "+isTaken);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int desiredWidth = objectWidth;
int desiredHeight = objectHeight;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
//Measure Width
if (widthMode == MeasureSpec.EXACTLY) {
//Must be this size
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
width = Math.min(desiredWidth, widthSize);
} else {
//Be whatever you want
width = desiredWidth;
}
//Measure Height
if (heightMode == MeasureSpec.EXACTLY) {
//Must be this size
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
height = Math.min(desiredHeight, heightSize);
} else {
//Be whatever you want
height = desiredHeight;
}
//MUST CALL THIS
setMeasuredDimension(width, height);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//canvas.drawColor(Color.BLUE);
canvas.drawRect(rectangle, paint);
}
public int getNumberOfSeats() {
return numberOfSeats;
}
public int getTableId() {
return tableId;
}
public int getPositionX() {
return positionX;
}
public int getPositionY() {
return positionY;
}
public int getObjectWidth() {
return objectWidth;
}
public int getObjectHeight() {
return objectHeight;
}
public boolean isTaken() {
return isTaken;
}
public String getTableKind() {
return tableKind;
}
}
Main activity:
public class MainActivity extends AppCompatActivity {
int numberOfSeats = 1;
int tableId = 0;
int positionX = 100;
int positionY = 208;
boolean isTaken = true;
String tableKind = "table";
int objectWidth =200;
int objectHeight=200;
TextView topTable;
RelativeLayout floorPlan ;
Button saveBtn ;
private boolean insertTableCheck = false;
private static final String TAG = "MainActivity";
private int idTest = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
topTable =(TextView) findViewById(R.id.tableView);
floorPlan = (RelativeLayout) findViewById(R.id.floor_plan);
saveBtn = (Button) findViewById(R.id.saveBtn);
for (int i = 0;i<15;i++) {
TableView tv = new TableView(MainActivity.this, numberOfSeats, tableId, positionX, positionY, objectWidth, objectHeight
, isTaken, tableKind);
tv.setTag(i);
positionX +=25;
positionY +=34;
//tv.setY(positionY);
//tv.setX(positionX);
floorPlan.addView(tv);
}
floorPlan.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (insertTableCheck) {
saveBtn.setVisibility(View.VISIBLE);
int x = (int)motionEvent.getX()-objectWidth/2;
int y =(int) motionEvent.getY()-objectHeight/2;
Log.i(TAG, "insertTable: insertingTable on: " + x + " " + y);
TableView tav = new TableView(MainActivity.this,numberOfSeats,tableId,x,
y,objectWidth,objectHeight,isTaken,tableKind);
tav.setX(x);
tav.setY(y);
floorPlan.addView(tav);
/*TextView tableForInsert = new TextView(MainActivity.this);
tableForInsert.setBackgroundColor(Color.BLACK);
tableForInsert.setWidth(objectWidth);
tableForInsert.setHeight(objectHeight);
tableForInsert.setX(x);
tableForInsert.setY(y);
floorPlan.addView(tableForInsert);*/
insertTableCheck = false;
} else {
Log.i(TAG, "onTouch: just clicked on layout");
}
return true;
}
});
}
Note that for loop and the data above is just for testing! Any help would be much appreciated. Thanks
I'm trying to implement smooth TMX map scrolling and zooming with SurfaceScrollDetector and PinchZoomDetector.
Here is my code:
public class TMXTiledMapExample extends SimpleBaseGameActivity implements IOnSceneTouchListener, IScrollDetectorListener, IPinchZoomDetectorListener {
private static final int CAMERA_WIDTH = 480;
private static final int CAMERA_HEIGHT = 320;
private SmoothCamera camera;
private TMXTiledMap mTMXTiledMap;
private SurfaceScrollDetector mScrollDetector;
private PinchZoomDetector mPinchZoomDetector;
private float mPinchZoomStartedCameraZoomFactor;
#Override
public EngineOptions onCreateEngineOptions() {
this.camera = new SmoothCamera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT, 400, 400, 10f);
final CroppedResolutionPolicy canvasSurface = new CroppedResolutionPolicy(CAMERA_WIDTH, CAMERA_HEIGHT);
EngineOptions engineOptions = new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, canvasSurface, this.camera);
if (MultiTouch.isSupported(this)) {
if (MultiTouch.isSupportedDistinct(this)) {
Toast.makeText(this, "MultiTouch detected --> Both controls will work properly!", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "MultiTouch detected, but your device has problems distinguishing between fingers.\n\nControls are placed at different vertical locations.", Toast.LENGTH_LONG)
.show();
}
} else {
Toast.makeText(this, "Sorry your device does NOT support MultiTouch!\n\n(Falling back to SingleTouch.)\n\nControls are placed at different vertical locations.", Toast.LENGTH_LONG).show();
}
return engineOptions;
}
#Override
public void onCreateResources() {
BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("gfx/");
}
#Override
public Scene onCreateScene() {
this.mEngine.registerUpdateHandler(new FPSLogger());
final Scene scene = new Scene();
scene.setOnAreaTouchTraversalFrontToBack();
this.mScrollDetector = new SurfaceScrollDetector(this);
this.mPinchZoomDetector = new PinchZoomDetector(this);
scene.setOnSceneTouchListener(this);
scene.setTouchAreaBindingOnActionDownEnabled(true);
try {
final TMXLoader tmxLoader = new TMXLoader(this.getAssets(), this.mEngine.getTextureManager(), TextureOptions.BILINEAR, this.getVertexBufferObjectManager(),
new ITMXTilePropertiesListener() {
#Override
public void onTMXTileWithPropertiesCreated(final TMXTiledMap pTMXTiledMap, final TMXLayer pTMXLayer, final TMXTile pTMXTile,
final TMXProperties<TMXTileProperty> pTMXTileProperties) {
}
});
this.mTMXTiledMap = tmxLoader.loadFromAsset("tmx/desert2.tmx");
} catch (final TMXLoadException e) {
Debug.e(e);
}
final TMXLayer tmxLayer = this.mTMXTiledMap.getTMXLayers().get(0);
scene.attachChild(tmxLayer);
this.camera.setBounds(0, 0, tmxLayer.getHeight(), tmxLayer.getWidth());
this.camera.setBoundsEnabled(true);
return scene;
}
#Override
public void onScrollStarted(final ScrollDetector pScollDetector, final int pPointerID, final float pDistanceX, final float pDistanceY) {
final float zoomFactor = this.camera.getZoomFactor();
this.camera.offsetCenter(-pDistanceX / zoomFactor, -pDistanceY / zoomFactor);
}
#Override
public void onScroll(final ScrollDetector pScollDetector, final int pPointerID, final float pDistanceX, final float pDistanceY) {
final float zoomFactor = this.camera.getZoomFactor();
this.camera.offsetCenter(-pDistanceX / zoomFactor, -pDistanceY / zoomFactor);
}
#Override
public void onScrollFinished(final ScrollDetector pScollDetector, final int pPointerID, final float pDistanceX, final float pDistanceY) {
final float zoomFactor = this.camera.getZoomFactor();
this.camera.offsetCenter(-pDistanceX / zoomFactor, -pDistanceY / zoomFactor);
}
#Override
public void onPinchZoomStarted(final PinchZoomDetector pPinchZoomDetector, final TouchEvent pTouchEvent) {
this.mPinchZoomStartedCameraZoomFactor = this.camera.getZoomFactor();
}
#Override
public void onPinchZoom(final PinchZoomDetector pPinchZoomDetector, final TouchEvent pTouchEvent, final float pZoomFactor) {
this.camera.setZoomFactor(this.mPinchZoomStartedCameraZoomFactor * pZoomFactor);
}
#Override
public void onPinchZoomFinished(final PinchZoomDetector pPinchZoomDetector, final TouchEvent pTouchEvent, final float pZoomFactor) {
this.camera.setZoomFactor(this.mPinchZoomStartedCameraZoomFactor * pZoomFactor);
}
#Override
public boolean onSceneTouchEvent(final Scene pScene, final TouchEvent pSceneTouchEvent) {
this.mPinchZoomDetector.onTouchEvent(pSceneTouchEvent);
if (this.mPinchZoomDetector.isZooming()) {
this.mScrollDetector.setEnabled(false);
} else {
if (pSceneTouchEvent.isActionDown()) {
this.mScrollDetector.setEnabled(true);
}
this.mScrollDetector.onTouchEvent(pSceneTouchEvent);
}
return true;
}
}
Scrolling and zooming in this code are not very smooth. Where is the issue with it ? How to create nice, inertial scrolling ?
One more question for zooming. How it could be restricted with min and max zoomfactor ? Should I place a special conditions into the IPinchZoomDetectorListener methods or it could be achived with some parameters provided to my camera object ?
Also, what is a "normal" values for the three last SmoothCamera parameters - final float pMaxVelocityX, final float pMaxVelocityY, final float pMaxZoomFactorChange ?
I have done similar type of work before so I can help you in achieving your objective.
First of all if you want zoom in/out functionality then you have to use ZoomCamera in which you have to only give width and height, all other parameters are used by default value.
mZoomCamera = new ZoomCamera(0, 0, Constants.CAMERA_WIDTH,
Constants.CAMERA_HEIGHT);
mZoomCamera.setBounds(0f, 0f, Constants.CAMERA_WIDTH,
Constants.CAMERA_HEIGHT);
mZoomCamera.setBoundsEnabled(true);
In the example code, you see how I set bounds for camera so there is no chance camera can go out of this. And also I thing you get abnormal behaviour because of inappropriate velocity you are providing.
For for setting specific zoom value you can use following code,
private void setCameraZoomFactor(float pZoomFactor) {
newZoomFactor = mPinchZoomStartedCameraZoomFactor * pZoomFactor;
if (newZoomFactor < 1f)
newZoomFactor = 1f;
if (newZoomFactor > 1.5f)
newZoomFactor = 1.5f;
mZoomCamera.setZoomFactor(newZoomFactor);
}
You have to call this method in your onPinchZoom method.
I think now I have given answer of your all of the questions.
I'm working on Android app which main purpose is to display CNN breaking news styled bar on the bottom and some pictures. I created two custom views, one for displaying photos and the second one for displaying bar. It displays one picture at a time for specified amount of time and swaps current picture with next one from the queue.
To animate text in bottom bar I used canvas, onDraw() and handler.postDelayed. This solution gives poor result. Text movement is not smooth especially when it comes to swap image.
What should I use instead of canvas? Is there any OpenGL-based lib which could make this task relatively painless? I tried to use AndEngine, but its lack of documentation and problems with threads discouraged me to working with it anymore.
public class Infobar extends View {
private List<Message> messages;
private Handler handler;
private Paint boxPaint;
private Paint defaultTextPaint;
private Paint importantTextPaint;
private long offset = 0;
private long maxOffset = 1000;
private int textWidth = 1000;
private int textHeight = 50;
private int measuredWidth;
private int measuredHeight;
private Runnable animateRunnable = new Runnable() {
public void run() {
animateMessage();
}
};
long startTime = new Date().getTime();
private int backgroundCol = Color.parseColor("#ffff00");
private int textColor = Color.parseColor("#000000");
private static final int FRAME_DELAY = 10;
private static final int FRAME_SHIFT = 3;
private static final int EMPTY_SPACE = 2;
private static final String SEPARATOR = " ✩ ";
private static final int TEXT_SIZE = 35;
public Infobar(Context context, AttributeSet attrs) {
super(context, attrs);
handler = new Handler();
messages = new ArrayList<Message>();
boxPaint = new Paint();
defaultTextPaint = new Paint();
defaultTextPaint.setColor(getResources().getColor(R.color.info_bar_default_text_color));
importantTextPaint = new Paint();
importantTextPaint.setColor(getResources().getColor(R.color.info_bar_important_text_color));
handler.postDelayed(animateRunnable, FRAME_DELAY);
}
public void setMessagesList(List<Message> list) {
messages = list;
}
public void setBackgroundColor(String color) {
backgroundCol = Color.parseColor(color);
}
public void setTextColor(String color) {
textColor = Color.parseColor(color);
}
public List<Message> getMessagesList() {
return messages;
}
private String getMessagesString() {
StringBuilder builder = new StringBuilder();
for(Message message : messages) {
builder.append(message.content);
if(messages.indexOf(message) != (messages.size() - 1)) {
builder.append(SEPARATOR);
}
}
return builder.toString();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measuredWidth = getMeasuredWidth();
measuredHeight = getMeasuredHeight();
}
#Override
protected void onDraw(Canvas canvas) {
drawBackground(canvas, false);
drawMessage(canvas, getMessagesString());
super.onDraw(canvas);
}
private void drawBackground(Canvas canvas, boolean important) {
boxPaint.setColor(backgroundCol) ;
canvas.drawRect(0, 0, measuredWidth, measuredHeight, boxPaint);
}
private void drawMessage(Canvas canvas, String message) {
defaultTextPaint.setTextSize(TEXT_SIZE);
Rect bounds = new Rect();
defaultTextPaint.getTextBounds(message, 0, message.length(), bounds);
defaultTextPaint.setColor(textColor);
textWidth = bounds.width();
textHeight = bounds.height();
int positionX = measuredWidth - (int)offset;
int positionY = measuredHeight - textHeight/2;
if(offset > (measuredWidth + textWidth)) {
offset = 0;
positionX = measuredWidth;
}
canvas.drawText(message, positionX, positionY, defaultTextPaint);
}
private void animateMessage() {
offset += FRAME_SHIFT;
//offset = Math.round((new Date().getTime() - startTime) * 0.2) % (measuredWidth + textWidth);
invalidate();
handler.removeCallbacks(animateRunnable);
handler.postDelayed(animateRunnable, FRAME_DELAY);
}
}
Unless there is a specific reason, you can use the built-in marquee function of TextView:
<LinearLayout>
<TextView
android:id="#+id/myText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lines="1"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:text="Your long text goes here. You can also change it programmatically" />
</LinearLayout>
Then in the activity code:
TextView myText=(TextView) findViewById(R.id.myText);
myText.setSelected(true); //needs this to work