Seems Androidplot (androidplot.com) website/forums are down so I'll try asking this question here.
I have multi touch zoom and scrolling. Similar code to http://mlepicki.com/2012/03/androidplot-multitouch-zoom-scroll/ except that I have a bar graph instead. However with 100 data points it has noticeable lag. Even with 10 bars are just showing. Sounds like its drawing/calculating/etc all bars.
Any idea on how I could optimise this?
I can't use hardware rendering as I want to support Android 2.1 and the library doesn't support it(it breaks).
I made a custom renderer to solve my lagging issues. Seems to be much more smooth. This code is based on version 0.5. No idea if it works on v0.51.
import android.graphics.*;
import com.androidplot.exception.PlotRenderException;
import com.androidplot.series.XYSeries;
import com.androidplot.util.ValPixConverter;
import com.androidplot.xy.BarFormatter;
import com.androidplot.xy.XYPlot;
import com.androidplot.xy.XYSeriesRenderer;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
* Renders a point as a Bar
*/
public class OptimisedBarRenderer extends XYSeriesRenderer<BarFormatter> {
private BarWidthStyle style = BarWidthStyle.FIXED_WIDTH;
private float barWidth = 5;
public OptimisedBarRenderer(XYPlot plot) {
super(plot);
}
/**
* Sets the width of the bars draw.
* #param barWidth
*/
public void setBarWidth(float barWidth) {
this.barWidth = barWidth;
}
private final TreeMap<Number, XYSeries> tempSeriesMap = new TreeMap<Number, XYSeries>();
#Override
public void onRender(Canvas canvas, RectF plotArea) throws PlotRenderException {
int longest = getLongestSeries();
if(longest == 0) {
return; // no data, nothing to do.
}
tempSeriesMap.clear();
for(int i = 0; i < longest; i++) {
tempSeriesMap.clear();
List<XYSeries> seriesList = getPlot().getSeriesListForRenderer(this.getClass());
for(XYSeries series : seriesList) {
if(i < series.size()) {
tempSeriesMap.put(series.getY(i), series);
}
}
drawBars(canvas, plotArea, tempSeriesMap, i);
}
}
#Override
public void doDrawLegendIcon(Canvas canvas, RectF rect, BarFormatter formatter) {
canvas.drawRect(rect, formatter.getFillPaint());
canvas.drawRect(rect, formatter.getBorderPaint());
}
private int getLongestSeries() {
int longest = 0;
List<XYSeries> seriesList = getPlot().getSeriesListForRenderer(this.getClass());
if(seriesList == null)
return 0;
for(XYSeries series :seriesList) {
int seriesSize = series.size();
if(seriesSize > longest) {
longest = seriesSize;
}
}
return longest;
}
private void drawBars(Canvas canvas, RectF plotArea, TreeMap<Number, XYSeries> seriesMap, int x) {
// Paint p = new Paint();
// p.setColor(Color.RED);
Object[] oa = seriesMap.entrySet().toArray();
Map.Entry<Number, XYSeries> entry;
Number yVal = null;
Number xVal = null;
float halfWidth = barWidth * 0.5f;
for(int i = oa.length-1; i >= 0; i--) {
entry = (Map.Entry<Number, XYSeries>) oa[i];
XYSeries tempEntry = entry.getValue();
if(tempEntry != null) {
yVal = tempEntry.getY(x);
xVal = tempEntry.getX(x);
if (yVal != null && xVal != null) { // make sure there's a real value to draw
switch (style) {
case FIXED_WIDTH:
float pixX = ValPixConverter.valToPix(xVal.doubleValue(), getPlot().getCalculatedMinX().doubleValue(), getPlot().getCalculatedMaxX().doubleValue(), plotArea.width(), false) + plotArea.left;
float left = pixX - halfWidth;
float right = pixX + halfWidth;
boolean offScreen = left > plotArea.right || right < plotArea.left;
if(!offScreen){
float pixY = ValPixConverter.valToPix(yVal.doubleValue(), getPlot().getCalculatedMinY().doubleValue(), getPlot().getCalculatedMaxY().doubleValue(), plotArea.height(), true) + plotArea.top;
BarFormatter formatter = getFormatter(tempEntry);
if(Math.abs (left - right) > 1f){//Don't draw as it will be hidden anyway.
canvas.drawRect(left, pixY, right, plotArea.bottom, formatter.getFillPaint());
}
canvas.drawRect(left, pixY, right, plotArea.bottom, formatter.getBorderPaint());
}
break;
default:
throw new UnsupportedOperationException("Not yet implemented.");
}
}
}
}
}
}
BarRenderer was optimized quite a bit in Androidplot 1.4.0 so a custom Renderer should no longer be necessary.
Related
I'd like to display a text inside a TextView justified with some of its content being clickable (e.g. a link inside).
The text is comming from xml, like the following example:
<string name="demo_text">Lorem <a href='http://www.google.at'>ipsum dolor sit amet</a>, consetetur sadipscing elitr ...</string>
For Android versions below Oreo, there is no justification available per default, whereas I'm using some 'legacy-implementation' of justification by directly drawing the text on the canvas.
=> This is working fine + hereby ClickableSpan touches are mapped within the View's onTouch().
Below you can find the used code.
Please note, that for Android Oreo and above, the default Android implementation of justification would be used.
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.os.Build;
import android.support.v7.widget.AppCompatTextView;
import android.text.Layout;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.BackgroundColorSpan;
import android.text.style.CharacterStyle;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.UnderlineSpan;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/* Base implementation found on: https://www.codementor.io/rugvedambekar/one-textview-to-rule-them-all-justifying-text-on-android-eq6ihy455
*
* Adapted to also support the following text spans:
* - BackgroundColorSpan
* - ForegroundColorSpan
* - StrikethroughSpan
* - UnderlineSpan
* - StyleSpan (bold / italic)
* - ClickableSpans
*
* TODO: possibile adaptions (bugfixes & improvements):
* - B: StrikethroughSpan & UnderlineSpan only apply to single words in justified lines
* - B: change getTextBounds() calculation for initial line-definitions to also include styles already
* - B: add the SpannableCharacterStyleMap to overridden setText()-methods as well (they'd change with API level 28)
* - I: get rid of Strings an use char-arrays instead
*/
public class JustifyTextView extends AppCompatTextView implements View.OnTouchListener {
//ParagraphStyle spannables are not yet required though
private class SpannableCharacterStyleMapping extends ArrayList<CharacterStyle> {
private int mStartIndex;
private int mEndIndex;
SpannableCharacterStyleMapping(int startIndex, int endIndex, CharacterStyle[] characterStyles) {
super();
mStartIndex = startIndex;
mEndIndex = endIndex;
if (characterStyles != null) {
addAll(Arrays.asList(characterStyles));
}
}
ClickableSpan getClickableSpan() {
for(CharacterStyle characterStyle : this) {
if (characterStyle instanceof ClickableSpan) {
return (ClickableSpan)characterStyle;
}
}
return null;
}
}
private class SpannableCharacterStyleMap extends ArrayList<SpannableCharacterStyleMapping> {
SpannableCharacterStyleMap() {
super();
}
SpannableCharacterStyleMap(Spanned styledText) {
super();
createFromText(styledText);
}
void createFromText(Spanned styledText) {
clear();
//avoid iterating over empty string
if (TextUtils.isEmpty(styledText)) return;
int next;
for (int i = 0; i < styledText.length(); i = next) {
//find the next span transition (if none was found, styledText.length() will be returned resulting in the loop to end)
next = styledText.nextSpanTransition(i, styledText.length(), CharacterStyle.class);
if ((next - 1) >= i) {
//check for all character style spans within this range
CharacterStyle[] spansInRange = styledText.getSpans(i, next, CharacterStyle.class);
if (spansInRange.length > 0) {
//spans in range have been found ... thus add them to our mapping!
add(new SpannableCharacterStyleMapping(i, next, spansInRange));
}
}
}
}
ArrayList<SpannableCharacterStyleMapping> getStylesWithinRange(int start, int end) {
ArrayList<SpannableCharacterStyleMapping> stylesInRange = new ArrayList<>();
for (SpannableCharacterStyleMapping styleMapping : this) {
if ((start <= styleMapping.mStartIndex && end > styleMapping.mStartIndex) ||
(start < styleMapping.mEndIndex && end >= styleMapping.mEndIndex) ||
(start >= styleMapping.mStartIndex && end <= styleMapping.mEndIndex)) {
stylesInRange.add(styleMapping);
}
}
return stylesInRange;
}
boolean hasClickableStylesWithinRange(int start, int end) {
for (SpannableCharacterStyleMapping styleMapping : this) {
if (((start <= styleMapping.mStartIndex && end > styleMapping.mStartIndex) ||
(start < styleMapping.mEndIndex && end >= styleMapping.mEndIndex) ||
(start >= styleMapping.mStartIndex && end <= styleMapping.mEndIndex)) &&
styleMapping.getClickableSpan() != null) {
return true;
}
}
return false;
}
}
private int mFirstLineTextHeight = 0;
private Rect mLineBounds = new Rect();
private SpannableCharacterStyleMap mCharacterStylesMap;
private int mDefaultTextBg;
private int mDefaultTextFg;
private int mDefaultTextHighlightFg;
private Typeface mDefaultTypeface;
private boolean mDefaultTextStrikethrough;
private boolean mDefaultTextUnderline;
private HashMap<ClickableSpan, ArrayList<RectF>> mClickableSpanAreas;
public JustifyTextView(Context context) {
super(context);
init();
}
public JustifyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public JustifyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mClickableSpanAreas = new HashMap<>();
CharSequence textViewText = getText();
mCharacterStylesMap = new SpannableCharacterStyleMap(new SpannableString(textViewText));
//check if we can actually use the default implementation (Android Oreo and above)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
super.setJustificationMode(Layout.JUSTIFICATION_MODE_INTER_WORD);
//check if there are clickable spans (if so, they should be clickable)
if (mCharacterStylesMap.hasClickableStylesWithinRange(0, textViewText.length())) {
setMovementMethod(LinkMovementMethod.getInstance());
}
return;
}
//check if there are clickable spans
if (mCharacterStylesMap.hasClickableStylesWithinRange(0, textViewText.length())) {
setClickable(true);
setFocusable(true);
setOnTouchListener(this);
}
//now remember the default styles
mDefaultTextBg = getPaint().bgColor;
mDefaultTextFg = getCurrentTextColor();
mDefaultTextHighlightFg = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN ? getHighlightColor() : mDefaultTextFg;
mDefaultTypeface = getPaint().getTypeface();
mDefaultTextStrikethrough = getPaint().isStrikeThruText();
mDefaultTextUnderline = getPaint().isUnderlineText();
}
#Override
protected void onDraw(Canvas canvas) {
//check if we can actually use the default implementation (Android Oreo and above)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
super.onDraw(canvas);
return;
}
if (mClickableSpanAreas != null) {
mClickableSpanAreas.clear();
}
//so we cannot use the default-Android implementation ... stick to the 'compatibility' code of ours
TextPaint paint = getPaint();
paint.drawableState = getDrawableState();
resetStyling(paint); //apply default styling at first
String fullText = getText().toString();
float drawableWidth = getDrawableWidth();
int lineNum = 1, lineStartIndex = 0;
int lastWordEnd, currWordEnd = 0;
while (currWordEnd >= 0) {
lastWordEnd = currWordEnd + 1;
currWordEnd = fullText.indexOf(' ', lastWordEnd);
if (currWordEnd != -1) {
paint.getTextBounds(fullText, lineStartIndex, currWordEnd, mLineBounds);
if (mLineBounds.width() >= drawableWidth) {
flushLine(canvas, paint, lineNum, lineStartIndex, fullText.substring(lineStartIndex, lastWordEnd));
lineStartIndex = lastWordEnd;
lineNum++;
}
} else {
paint.getTextBounds(fullText, lineStartIndex, fullText.length(), mLineBounds);
if (mLineBounds.width() >= drawableWidth) {
flushLine(canvas, paint, lineNum, lineStartIndex, fullText.substring(lineStartIndex, lastWordEnd));
rawFlushLine(canvas, paint, ++lineNum, lastWordEnd, fullText.substring(lastWordEnd));
} else {
if (lineNum == 1) {
rawFlushLine(canvas, paint, lineNum, lineStartIndex, fullText);
}
else {
rawFlushLine(canvas, paint, lineNum, lineStartIndex, fullText.substring(lineStartIndex));
}
}
}
}
}
private void resetStyling(TextPaint paint) {
//revert to defaults (luckily remembered previously) here
paint.bgColor = mDefaultTextBg; //BackgroundColorSpan
paint.setColor(mDefaultTextFg); //ForegroundColorSpan
paint.setTypeface(mDefaultTypeface); //StyleSpan
paint.setStrikeThruText(mDefaultTextStrikethrough); //StrikethroughSpan
paint.setUnderlineText(mDefaultTextUnderline); //UnderlineSpan
}
private void applyStyling(TextPaint paint, SpannableCharacterStyleMapping styling) {
resetStyling(paint);
if (styling != null && styling.size() > 0) {
//sadly a StyleSpan won't combine bold & italic already on its own ...
boolean fontShallBeBold = false;
boolean fontShallBeItalic = false;
//apply defined styling here now
for (CharacterStyle style : styling) {
if (style instanceof BackgroundColorSpan) {
paint.bgColor = ((BackgroundColorSpan) style).getBackgroundColor();
} else if (style instanceof ForegroundColorSpan) {
paint.setColor(((ForegroundColorSpan) style).getForegroundColor());
} else if (style instanceof StrikethroughSpan) {
paint.setStrikeThruText(true);
} else if (style instanceof UnderlineSpan) {
paint.setUnderlineText(true);
} else if (style instanceof ClickableSpan) {
paint.setColor(mDefaultTextHighlightFg);
// right now, the text will be rendered word by word, whereas underlining works only word by word right now
// => thus skip underlining in this scenario, until it is 'fixed' (only will be fixed if indeed necessary)
paint.setUnderlineText(false);
} else if (style instanceof StyleSpan) {
switch (((StyleSpan) style).getStyle()) {
case Typeface.BOLD:
fontShallBeBold = true;
break;
case Typeface.ITALIC:
fontShallBeItalic = true;
break;
case Typeface.BOLD_ITALIC:
fontShallBeBold = true;
fontShallBeItalic = true;
break;
}
}
}
//in the end apply the StyleSpan bold & italic params (if set)
if (fontShallBeBold && fontShallBeItalic) {
paint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD_ITALIC));
} else if (fontShallBeBold) {
paint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
} else if (fontShallBeItalic) {
paint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC));
}
}
}
private float getDrawableWidth() {
return getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
}
private void setFirstLineTextHeight(TextPaint paint, String firstLine) {
paint.getTextBounds(firstLine, 0, firstLine.length(), mLineBounds);
mFirstLineTextHeight = mLineBounds.height();
}
//Prints the line at lineNum on the canvas without adjusting the spacing between words at all.
private void rawFlushLine(Canvas canvas, TextPaint paint, int lineNum, int lineCharacterStart, String line) {
if (lineNum == 1) setFirstLineTextHeight(paint, line);
float yLine = getPaddingTop() + mFirstLineTextHeight + (lineNum - 1) * getLineHeight();
flushStyledText(canvas, paint, getPaddingLeft(), yLine, lineCharacterStart, line);
}
//Calculates the space between all words required to strech the line out to fit the full width of the view.
private void flushLine(Canvas canvas, TextPaint paint, int lineNum, int lineCharacterStart, String line) {
if (lineNum == 1) setFirstLineTextHeight(paint, line);
float yLine = getPaddingTop() + mFirstLineTextHeight + (lineNum - 1) * getLineHeight();
String[] words = line.split("\\s+");
StringBuilder lineBuilder = new StringBuilder();
for (String word : words) {
lineBuilder.append(word);
}
float xStart = getPaddingLeft();
float wordWidth = paint.measureText(lineBuilder.toString());
float spacingWidth = (getDrawableWidth() - wordWidth) / (words.length - 1);
for (String word : words) {
flushStyledText(canvas, paint, xStart, yLine, lineCharacterStart, word);
resetStyling(paint); //before checking new text bounds, revert paint
xStart += paint.measureText(word) + spacingWidth;
lineCharacterStart += word.length() + 1;
}
}
private void flushStyledText(Canvas canvas, TextPaint paint, float posX, float posY, int textPositionStart, String text) {
int textPositionEnd = textPositionStart + text.length();
ArrayList<SpannableCharacterStyleMapping> stylesInText = mCharacterStylesMap.getStylesWithinRange(textPositionStart, textPositionEnd);
if (stylesInText.size() > 0) {
int styleIndex = 0;
int textPositionNext;
ClickableSpan clickableSpan;
for (int textPosition = textPositionStart; textPosition < textPositionEnd; textPosition = textPositionNext) {
if (styleIndex <= stylesInText.size() - 1) {
SpannableCharacterStyleMapping styleToPossiblyApply = stylesInText.get(styleIndex);
if (styleToPossiblyApply.mStartIndex <= textPosition) {
//this style shall be applied now
applyStyling(paint, styleToPossiblyApply);
clickableSpan = styleToPossiblyApply.getClickableSpan();
textPositionNext = Math.min(textPositionEnd, styleToPossiblyApply.mEndIndex);
styleIndex++;
} else {
//this style shall not yet be applied => coming up next
resetStyling(paint);
clickableSpan = null;
textPositionNext = Math.min(textPositionEnd, styleToPossiblyApply.mStartIndex);
}
} else {
resetStyling(paint);
clickableSpan = null;
textPositionNext = textPositionEnd;
}
String textToDraw = text.substring(textPosition - textPositionStart, textPositionNext - textPositionStart);
canvas.drawText(textToDraw, posX, posY, paint);
if (clickableSpan != null && mClickableSpanAreas != null) {
ArrayList<RectF> clickableSpanAreaList = null;
if (mClickableSpanAreas.containsKey(clickableSpan)) {
clickableSpanAreaList = mClickableSpanAreas.get(clickableSpan);
} else {
clickableSpanAreaList = new ArrayList<>();
mClickableSpanAreas.put(clickableSpan, clickableSpanAreaList);
}
clickableSpanAreaList.add(new RectF(posX, posY, posX + paint.measureText(textToDraw), posY + getLineHeight()));
}
}
} else {
resetStyling(paint);
canvas.drawText(text, posX, posY, paint);
}
}
#Override
public boolean onTouch(View v, MotionEvent event) {
//check if the touch ended inside a clickable span area => if so, execute its click listener
if (event.getAction() == MotionEvent.ACTION_UP && mClickableSpanAreas != null) {
for (Map.Entry<ClickableSpan, ArrayList<RectF>> clickableAreaList : mClickableSpanAreas.entrySet()) {
if (clickableAreaList.getValue() != null) {
for (RectF clickableArea : clickableAreaList.getValue()) {
if (clickableArea.contains(event.getX(), event.getY())) {
clickableAreaList.getKey().onClick(this);
return true;
}
}
}
}
}
return false;
}
}
However, if I'd like to use the default implementation of setJustificationMode(Layout.JUSTIFICATION_MODE_INTER_WORD) on Android Oreo or above, I'm facing the following scenarios:
ClickableSpans are not clickable (but still highlightened) but text is justified
ClickableSpans are clickable due to calling setMovementMethod(LinkMovementMethod.getInstance()) but the text no longer is justified
Therefore I'm wondering if someone knows any way of making ClickableSpan clickable with keeping the justification mode on Android Oreo and above.
(Or maybe if there is any way of making URLSpan clickable without applying the LinkMovementMethod.)
Thanks. BR Thomas
As this seems to be a recurring topic on 'the stack', I am going to reinforce my problem as something not covered. What has been covered is 2D tile collision for platform games etc., but with the way I have made my game, there are no tiles. I am also using no extra libraries, everything is written by my own hand.
What I have is bounding Rect's for every object in the game. So far there are only two object classes in use, Platform and Entity. Entity contains all the stuff for player movement etc. while Platform is for a solid non-moving platform.
Platform.java:
package com.toongames.game.objects;
import android.graphics.Color;
import android.graphics.Rect;
import com.toongames.framework.Graphics;
public class Platform {
private int x, y;
private int width, height;
public Platform(int par1, int par2, int par3, int par4) {
x = par1;
y = par2;
width = par3;
height = par4;
}
public Rect getBounds() {
return new Rect(x, y, x + width, y + height);
}
}
Entity.java:
package com.toongames.game.entity;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import com.toongames.framework.Graphics;
import com.toongames.framework.Image;
public class Entity {
public final float GRAVITY = 0.1F;
private String entityID;
private Point pos;
private int dx;
private float vel;
public Point desiredPos;
public boolean onGround;
public Entity(String par0String, int par1, int par2) {
entityID = par0String;
pos = new Point(par1, par2);
desiredPos = pos;
dx = 0;
vel = 0;
}
public void update(float deltaTime) {
vel = vel + (GRAVITY * deltaTime);
pos.y += (vel * deltaTime);
pos.x += dx;
}
public void setDx(int par1) {
dx = par1;
}
public int getDx() {
return dx;
}
public void setVelocity(int par1) {
vel = par1;
}
public float getVelocity() {
return vel;
}
public void setPos() {
pos = desiredPos;
}
public Rect getBounds() {
return new Rect(desiredPos.x, desiredPos.y, desiredPos.x + 80, desiredPos.y + 80);
}
}
I have successfully made the player collide with things both up and down, but I cannot for the life of me manage to make the player collide right and left. Whenever I collide with a platform while moving left or right, I just jump to the top of the platform I collided with.
I know it has something to do with my logic, but I cannot figure out the correct logic to use.
ScreenGame.java:
package com.toongames.game.screen;
// Imports here...
public class ScreenGame extends Screen {
private Entity player;
private Button left, right, jump;
private Platform floor, p, p2, p3;
private ArrayList<Platform> platforms;
public ScreenGame(Game game) {
super(game);
player = new Entity("PLAYER", 300, 100, Assets.charRight);
left = new Button(Assets.move_left, 10, 790 - Assets.move_left.getHeight(), Assets.move_left.getWidth(), Assets.move_left.getHeight());
right = new Button(Assets.move_right, 20 + Assets.move_left.getWidth(), 790 - Assets.move_right.getHeight(), Assets.move_right.getWidth(), Assets.move_right.getHeight());
jump = new Button(Assets.jump, 1270 - Assets.jump.getWidth(), 790 - Assets.jump.getHeight(), Assets.jump.getWidth(), Assets.jump.getHeight());
floor = new Platform(0, 790, 1280, 80);
p = new Platform(1280 - 500, 500, 400, 80);
p2 = new Platform(0, 200, 400, 80);
p3 = new Platform(400, 120, 200, 80);
platforms = new ArrayList<Platform>();
platforms.add(floor);
platforms.add(p);
platforms.add(p2);
platforms.add(p3);
}
// An update method calls these
public void updateMovement(float deltaTime) {
List<TouchEvent> touchEvents = game.getInput().getTouchEvents();
int len = touchEvents.size();
for (int i = 0; i < len; i++) {
TouchEvent event = touchEvents.get(i);
if (event.type == TouchEvent.TOUCH_DOWN) {
if (inBounds(left.getBounds(), event)) {
player.setDx((int) -(deltaTime * 1.5F));
} else if (inBounds(right.getBounds(), event)) {
player.setDx((int) deltaTime * 2);
} else if (inBounds(jump.getBounds(), event)) {
if (player.onGround) {
player.setVelocity(-8);
}
}
} else if (event.type == TouchEvent.TOUCH_DRAGGED) {
if (inBounds(left.getBounds(), event)) {
player.setDx((int) -deltaTime * 2);
} else if (inBounds(right.getBounds(), event)) {
player.setDx((int) deltaTime * 2);
} else if (inBounds(jump.getBounds(), event)) {
if (player.onGround) {
player.setVelocity(-8);
}
} else {
player.setDx(0);
player.jumpCounter = 0;
}
} else if (event.type == TouchEvent.TOUCH_UP) {
player.setDx(0);
player.jumpCounter = 0;
}
}
}
// An update method calls these
public void updateGameObjects(float deltaTime) {
for (Platform p : platforms)
p.update();
player.update(deltaTime);
}
// An update method calls these
public void checkCollisions() {
Rect playerRect = player.getBounds();
for (Platform p : platforms) {
Rect pRect = p.getBounds();
if (Rect.intersects(playerRect, pRect)) {
Rect intersection = playerRect;
intersection.intersect(pRect);
if (player.getVelocity() != player.GRAVITY) {
int resolutionHeight;
if (player.getVelocity() < player.GRAVITY)
resolutionHeight = intersection.height();
else {
resolutionHeight = -intersection.height();
player.onGround = true;
}
player.setVelocity(0);
player.desiredPos = new Point(player.desiredPos.x, player.desiredPos.y + resolutionHeight);
}
}
}
player.setPos();
}
}
As an extra note, I have cut out some of the unnecessary code to do with images for the entity and entity health etc.. Also I have cut out empty methods and stuff like that that have no relevance what so ever.
[EDIT] Cut out most of the drawing code and imports. All the absolutely necessary stuff is there now.
player.desiredPos = new Point(player.desiredPos.x, player.desiredPos.y + resolutionHeight);
isn't this "move above, never right/left?"
I think your Rect.intersects method should return one of { NONE, LEFT, RIGHT, UP, DOWN } indicating in which direction the collision occured. So you can react to the collision in the right direction...
This is my first Android App and I'm running into some difficulty with drawText(). I have been self-learning Android programming though Mario Zechner's Beginning Android Games. I am adapting the code he uses for the Mr. Nom game in his book to create my own game. This is a color learning game in which the user taps on a grid of colors. I am able to successfully draw the color grid and a space showing the color the user is to "find" using graphics.drawRect(), and a couple of other graphical assets using graphics.drawPixmap(). I am trying to put the name of each color over top of each color filled rectangle. I have thrown in an arbitrary line of text toward the bottom of the code as follows:
p.setTypeface(Typeface.SERIF);
p.setTextSize(100);
p.setColor(Color.WHITE);
p.setStyle(Style.FILL);
canvas.drawPaint(p);
canvas.drawText("RED",120, 120, p);
Reading similar quesitons I believe my issue is that I'm not properly telling it to draw to the same screen as the rest of the graphical elements. I'm sorry if I'm not being clear - again this is my first project. Please let me know what other information you might need in helping me.
Thank you for your time and consideration!
Below is the rest of the java code used:
package com.lilyandrosieshow.colors;
import java.util.List;
import java.util.Random;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Canvas;
import android.graphics.Typeface;
import android.graphics.Paint.Style;
import com.badlogic.androidgames.framework.Game;
import com.badlogic.androidgames.framework.Graphics;
import com.badlogic.androidgames.framework.Input.TouchEvent;
import com.badlogic.androidgames.framework.Screen;
import com.badlogic.androidgames.framework.Sound;
import com.lilyandrosieshow.colors.ColorGrid;
public class GameScreen extends Screen {
Random random = new Random();
World world;
ColorGrid colorGrid = new ColorGrid();
Canvas canvas = new Canvas();
Paint p = new Paint();
static Sound[] colorSoundL = { Assets.lred, Assets.lorange, Assets.lyellow, Assets.lgreen,
Assets.lblue, Assets.lpurple, Assets.lbrown, Assets.lgrey,
Assets.lblack, Assets.lpink, Assets.lcyan, Assets.lwhite };
static Sound[] colorSoundR = { Assets.rred, Assets.rorange, Assets.ryellow, Assets.rgreen,
Assets.rblue, Assets.rpurple, Assets.rbrown, Assets.rgrey,
Assets.rblack, Assets.rpink, Assets.rcyan, Assets.rwhite };
static Sound[] correct1 = { Assets.correct1, Assets.correct2, Assets.correct3, Assets.correct4, Assets.correct5 };
static Sound[] correct2 = { Assets.correcta, Assets.correctb, Assets.correctc, Assets.correctd, Assets.correcte };
static Sound[] wrong1 = {Assets.wrong1, Assets.wrong2, Assets.wrong3, Assets.wrong4, Assets.wrong5 };
static Sound[] wrong2 = {Assets.wronga, Assets.wrongb, Assets.wrongc, Assets.wrongd, Assets.wronge };
static Sound[] ask = {Assets.ask1, Assets.ask2, Assets.ask3, Assets.ask4, Assets.ask5 };
static Sound[] repeat = { Assets.again1, Assets.again2, Assets.again3, Assets.again4, Assets.again5 };
int row = -1;
int column = -1;
public GameScreen(Game game) {
super(game);
this.world = new World();
}
#Override
public void update(float deltaTime) {
List<TouchEvent> touchEvents = game.getInput().getTouchEvents();
int len = touchEvents.size();
for (int i = 0; i < len; i++) {
TouchEvent event = touchEvents.get(i);
// Color Touched
if (event.type == TouchEvent.TOUCH_UP &&
event.x >= 0 && event.x <= 320 && event.y >= 0 && event.y <= 240) {
for (int j = 1; j <= 4; j++){
if (event.x < (j*80)) {
column = j;
break;
}
}
for (int j = 1; j <= 3; j++){
if (event.y < (j*80)) {
row = j;
break;
}
}
updateColorTouched(column, row);
if (world.colorTouched.x == world.colorWanted.x && world.colorTouched.y == world.colorWanted.y) {
correct2[new Random().nextInt(5)].play(1);
if (world.whichHead == World.WhichHead.LILY) colorSoundL[world.colorTouched.id].play(1);
if (world.whichHead == World.WhichHead.ROSIE) colorSoundR[world.colorTouched.id].play(1);
//world.colorTouched.s.play(1);
world.colorWanted = colorGrid.squares.get(random.nextInt(12));
//Assets.ask1.play(1);
//colorSet[colorWanted-1].play(1);
} else {
wrong2[new Random().nextInt(5)].play(1);
//colorSet[colorTouched-1].play(1);
}
}
// A Head Was Touched
if (event.type == TouchEvent.TOUCH_UP && event.y > 240 && event.y <= 480){
if (event.x > 0 && event.x < 160) world.changeVoice(World.WhichHead.LILY);
if (event.x > 161 && event.x < 320) world.changeVoice(World.WhichHead.ROSIE);
}
}
}
#Override
public void present(float deltaTime) { // Draw everything to the screen here
Graphics g = game.getGraphics();
g.drawRect(0, 0, g.getWidth(), g.getHeight(), Color.rgb(75, 75, 75));
g.drawPixmap(Assets.findthis, 5, 245);
g.drawRect(165, 245, 150, 70, Color.rgb(world.colorWanted.r,
world.colorWanted.g,
world.colorWanted.b));
g.drawPixmap(Assets.lilyheadopen, 5, 325, 5, 5, 155, 155);
g.drawPixmap(Assets.rosieheadopen, 165, 325, 5, 5, 155, 155);
// Draws the grid
for (int i = 0; i < 12; i++) {
g.drawRect((
(world.colorGrid.squares.get(i).x-1)*80),
(world.colorGrid.squares.get(i).y-1)*80,
80,
80,
Color.rgb(world.colorGrid.squares.get(i).r, world.colorGrid.squares.get(i).g, world.colorGrid.squares.get(i).b));
// ************* DRAW TEXT NAMES HERE ********************
p.setTypeface(Typeface.SERIF);
p.setTextSize(100);
p.setColor(Color.WHITE);
p.setStyle(Style.FILL);
canvas.drawPaint(p);
canvas.drawText("RED",120, 120, p);
}
}
public void updateColorTouched(int x, int y){
for (int i = 1; i <= 12; i++) {
if (x == world.colorGrid.squares.get(i-1).x && y == world.colorGrid.squares.get(i-1).y){
world.colorTouched = world.colorGrid.squares.get(i-1);
break;
}
}
}
#Override
public void pause() {
}
#Override
public void resume() {
}
#Override
public void dispose() {
}
}
Rather than drawing to a Canvas object that doesn't get used anywhere, you'll probably want to extend the Graphics interface with a drawText(...) method. This would be analogue to all other draw calls that are defined in Graphics and implemented in AndroidGraphics.
To help you on your way:
Add to Graphics.java:
public void drawText(String text, float x, float y, Paint p);
Add to AndroidGraphics.java:
#Override public void drawText(String text, float x, float y, Paint p) {
paint.set(p);
canvas.drawText(text, x, y, paint);
}
If you need any of the other drawText(...) overloads that are available in Android's Canvas class, you can repeat the same steps as outlined above for those.
try this:
p.setTypeface(Typeface.SERIF);
p.setTextSize(100);
p.setColor(Color.WHITE);
p.setStyle(Style.FILL);
Bitmap bitmap = Bitmap.createBitmap(200,200,Bitmap.Config.ARGB_8888);
canvas = new Canvas(bitmap);
//canvas.drawPaint(p);
canvas.drawText("RED",120, 120, p);
g.drawPixmap(new AndroidPixmap(bitmap,PixmapFormat.ARG_8888),839,282);//your actual
//coordinates instead of 839,282 though
For my android game I use Libgdx and I detect the collision between Bob (Omino) and Plant (Pianta) with this code that works fine :
Assets.class
pianta = new Animation(0.5f,new TextureRegion(items, 160, 384, 64, 96),
new TextureRegion(items, 224, 384, 64, 96));
Pianta.class
public class Pianta extends GameObject {
public static final float PIANTA_WIDTH = 2;
public static final float PIANTA_HEIGHT = 3;
public static float stateTime;
public Pianta(float x, float y) {
super(x, y, PIANTA_WIDTH, PIANTA_HEIGHT);
stateTime = 0;
}
public void update(float deltaTime) {
stateTime += deltaTime;
}
}
World.class
Pianta pianta1_0 = new Pianta(x+10,2.2f);
piante.add(pianta1_0);
private void collisionPiante(){
int len = piante.size();
for(int i=0;i<len;i++){
if(OverlapTester.overlapRectangles(piante.get(i).bounds,omino.bounds)){
omino.ominoMorto();
}
}
}
WorldRender.class
private void renderPiante() {
TextureRegion keyFrame;
int len = world.piante.size();
for(int i = 0; i < len; i++) {
Pianta pianta = world.piante.get(i);
keyFrame = Assets.pianta.getKeyFrame(Pianta.stateTime, Animation.ANIMATION_LOOPING);
batcher.draw(keyFrame,pianta.position.x, pianta.position.y, 2, 3);
}
}
but if you watch the image 2 below, you can see that Bob hit but there isn't collision with stone (Pietra) !!
This is the code :
Assets.class
pietra1 = new TextureRegion(items,288,416,128,64);
Pietra.class
public class Pietra extends GameObject {
public static float PIETRA_WIDTH = 4;
public static float PIETRA_HEIGHT = 2;
public Pietra(float x, float y) {
super(x, y, PIETRA_WIDTH, PIETRA_HEIGHT);
}
}
World.class
Pietra pietra1_0 = new Pietra(x+25,2.2f);
pietre.add(pietra1_0);
private void collisionPietre(){
int len2 = pietre.size();
for(int l=0;l<len2;l++){
if(OverlapTester.overlapRectangles(pietre.get(l).bounds,omino.bounds)){
omino.ominoMorto();
}
}
}
WorldRender.class
private void renderPietre() {
int len = world.pietre.size();
for(int i = 0; i < len; i++) {
Pietra pietra = world.pietre.get(i);
batcher.draw(Assets.pietra1,pietra.position.x, pietra.position.y, 4, 2);
}
}
OverlapTester
public class OverlapTester {
public static boolean overlapRectangles (Rectangle r1, Rectangle r2) {
if (r1.x < r2.x + r2.width && r1.x + r1.width > r2.x && r1.y < r2.y + r2.height && r1.y + r1.height > r2.y)
return true;
else
return false;
}
Someone can tell me why the collision with the plant works fine and with stone Bob hit even if there is no collision? as you can see the code is the same, the only difference is that the plant is an animated object while the stone isn't.
Check your OverlapTester. This is how Libgdx does it in the Rectangle.java class:
/** #param rectangle the other {#link Rectangle}
* #return whether this rectangle overlaps the other rectangle. */
public boolean overlaps (Rectangle rectangle) {
return !(x > rectangle.x + rectangle.width || x + width < rectangle.x || y > rectangle.y + rectangle.height || y + height < rectangle.y);
}
If I understood right overlapRectangles checks the case if rectangle is totally inside. It is not probably thing you want.
LibGDX has special functionality for collision checking. Please, check http://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/math/Intersector.html
You may wish to replace your OverlapTester with the Rectangle's helper function contains. For instance:
World Class
if(OverlapTester.overlapRectangles(piante.get(i).bounds,omino.bounds)){
omino.ominoMorto();
}
Can be:
if (piante.get(i).bounds.contains(omino.bounds)) {
omino.ominoMorto();
}
Anyone that has used the AndroidPlot library tell me how would I go about drawing custom points on a graph. So far I'm using LineAndPointRenderer class and settings lines to transparent.
I would like to at least change the size of the dot but if possible have a custom image instead.
P.S someone with 1500 rep needs to create a "AndroidPlot" tag.
Solved the problem by creating my own renderer.
import android.graphics.*;
import com.androidplot.series.XYSeries;
import com.androidplot.exception.PlotRenderException;
import com.androidplot.util.ValPixConverter;
import com.androidplot.xy.LineAndPointFormatter;
import com.androidplot.xy.XYPlot;
import com.androidplot.xy.XYSeriesRenderer;
public class CustomPointRenderer<FormatterType extends LineAndPointFormatter> extends XYSeriesRenderer<FormatterType> {
private float circleWidth = 1;
public CustomPointRenderer(XYPlot plot) {
super(plot);
}
#Override
public void onRender(Canvas canvas, RectF plotArea) throws PlotRenderException {
for(XYSeries series : getPlot().getSeriesListForRenderer(this.getClass())) {
drawSeries(canvas, plotArea, series, getFormatter(series));
}
}
#Override
protected void doDrawLegendIcon(Canvas canvas, RectF rect, FormatterType formatter) {
// horizontal icon:
float centerY = rect.centerY();
float centerX = rect.centerX();
if(formatter.getFillPaint() != null) {
canvas.drawRect(rect, formatter.getFillPaint());
}
if(formatter.getLinePaint() != null) {
canvas.drawLine(rect.left, rect.bottom, rect.right, rect.top, formatter.getLinePaint());
}
if(formatter.getVertexPaint() != null) {
canvas.drawPoint(centerX, centerY, formatter.getVertexPaint());
}
}
private void drawSeries(Canvas canvas, RectF plotArea, XYSeries series, LineAndPointFormatter formatter) throws PlotRenderException {
PointF p = null;
XYPlot plot = getPlot();
int size = series.size();
for (int i = 0; i < size; i++) {
Number y = series.getY(i);
Number x = series.getX(i);
if (y != null && x != null) {
p = ValPixConverter.valToPix(x, y, plotArea,
plot.getCalculatedMinX(),
plot.getCalculatedMaxX(),
plot.getCalculatedMinY(),
plot.getCalculatedMaxY());
if (formatter.getVertexPaint() != null) {
boolean offScreen = p.x > plotArea.right || p.y > plotArea.bottom || p.x < plotArea.left || p.y < plotArea.top;
if(!offScreen)
canvas.drawCircle(p.x, p.y - circleWidth, circleWidth, formatter.getVertexPaint());
}
}
}
}
public void setWidth(float width){
circleWidth = width;
}
}