I'm relatively new to Android programming, and I need a control that holds text and scrolls automatically. Now, I know about the "marquee" in the TextView control and it works fine for what it's intended, but that approach has two problems.
1) I need the text to scroll regardless of its length, i.e. if the text is only "Hello", and the control is set to match parents width, it needs to scroll.
2) The control needs to respond to user scroll - by flicking/dragging it left/right, the text should also scroll.
And naturally, when the text is "gone" to the left side, it should reappear on the right side and continue scrolling. For now, it should be a single line text.
Does anything like that exist, and if not, what would be the best approach guidelines to implementing it?
I ended up extending the default TextView, and pulling out Marquee class from TextView source. From there it's easy to modify the Marquee class so that it starts/stops when needed, and no longer requires the TextView to be selected (if that requirement is necessary).
To implement slide by gesture, the base class implements OnGestureListener and in onScroll(...) I update the offset in Marquee class, so when the View is drawn the next time it applies the new scroll offset.
And finally, to actually scroll by the amount needed, in the constructor I set the custom scroller, and in onDraw apply the scroll.
The important parts of code:
public class MarqueeTextView extends TextView implements OnGestureListener {
private GestureDetector gestureDetector;
private Marquee marquee;
private Scroller scroller;
// constructor
public MarqueeTextView(Context context, AttributeSet attrs) {
this.marquee = new Marquee(this);
this.scroller = new Scroller(context);
this.setScroller(scroller);
gestureDetector = new GestureDetector(getContext(), this);
// when enabled, longpress disables further movement tracking
gestureDetector.setIsLongpressEnabled(false);
}
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
marquee.addToScroll(distanceX);
return false;
}
// onDraw
protected void onDraw(Canvas canvas) {
scroller.setFinalX((int) marquee.mScroll);
super.onDraw(canvas);
}
// Marquee handler
private static final class Marquee extends Handler {
// mostly the same as original
// ...
float mScroll;
public void addToScroll(float amount) {
mScroll += amount;
// detect if needs to start over
}
}
}
Related
I am creating a simple event-driven GUI for a video game I am making with LibGDX. It only needs to support buttons (rectangular) with a single act() function called when they are clicked. I would appreciate some advice on structuring because the solution I've thought of so far seems to be far from ideal.
My current implementation involves all buttons extending a Button class. Each button has a Rectangle with its bounds and an abstract act() method.
Each game screen (e.g. main menu, character select, pause menu, the in-game screen) has a HashMap of Buttons. When clicked, the game screen iterates through everything in the HashMap, and calls act() on any button that was clicked.
The problem I'm having is that Buttons have to have their act() overridden from their superclass in order to perform their action, and that the Buttons aren't a member of the Screen class which contains all the game code. I am subclassing Button for each button in the game. My main menu alone has a ButtonPlay, ButtonMapDesigner, ButtonMute, ButtonQuit, etc. This is going to get messy fast, but I can't think of any better way to do it while keeping a separate act() method for each button.
Since my mute button isn't a part of the main menu screen and can't access game logic, it's act() is nothing more than mainMenuScreen.mute();. So effectively, for every button in my game, I have to create a class class that does nothing more than <currentGameScreen>.doThisAction();, since the code to actually do stuff must be in the game screen class.
I considered having a big if/then to check the coordinates of each click and call the appropriate action if necessary. For example,
if (clickWithinTheseCoords)
beginGame();
else if(clickWithinTheseOtherCoords)
muteGame();
...
However, I need to be able to add/remove buttons on the fly. When a unit is clicked from the game screen, a button to move it needs to appear, and then disappear when the unit is actually moved. With a HashMap, I can just map.add("buttonMove", new ButtonMove()) and map.remove("buttonMove") in the code called when a unit is clicked or moved. With the if/else method, I won't need a separate class for every button, but I would need to keep track of whether each clickable area tested is visible and clickable by the user at this point in the game, which seems like an even bigger headache that what I have right now.
I would provide a runnable to all the buttons which u will run in the act method. To give u a simple example.
private final Map<String, Button> buttons = new HashMap<>();
public void initialiseSomeExampleButtons() {
buttons.put("changeScreenBytton", new Button(new Runnable() {
#Override
public void run() {
//Put a change screen action here.
}
}));
buttons.put("muteButton", new Button(new Runnable() {
#Override
public void run() {
//Do a mute Action here
}
}));
}
public class Button {
//Your other stuff like rectangle
private final Runnable runnable;
protected Button(Runnable runnable) {
this.runnable = runnable;
}
public void act() {
runnable.run();
}
}
You keep track of your buttons via the map and just need to pass a runnable action to every button in the constructor. I intentionally skipped some code so that you can try yourself. If you have any questions, let me know.
Sneh's response reminded me of a fairly major oversight - instead of having to create a separate class for every button, I could use anonymous inner classes whenever I created a button, specifying its coordinates and act() method every time. I explored lambda syntax as a possible shorter method to do this, but ran into limitations with it. I ended up with a flexible solution, but ended up reducing it a bit further to fit my needs. Both ways are presented below.
Each game screen in my game is subclassed from a MyScreen class, which extends LibGDX's Screen but adds universal features like updating the viewport on resize, having a HashMap of Buttons, etc. I added to the MyScreen class a buttonPressed() method, which takes in as its one parameter an enum. I have ButtonValues enum which contains all the possible buttons (such as MAINMENU_PLAY, MAINMENU_MAPDESIGNER, etc.). In each game screen, buttonPressed() is overriden and a switch is used to perform the correct action:
public void buttonPressed(ButtonValues b) {
switch(b) {
case MAINMENU_PLAY:
beginGame();
case MAINMENU_MAPDESIGNER:
switchToMapDesigner();
}
}
The other solution has the button store a lambda expression so that it can perform actions on its own, instead of requiring buttonPressed() to act as an intermediary that performs the correct action based on what button was pressed.
To add a button, it is created with its coordinates and type (enum), and added to the HashMap of buttons:
Button b = new Button(this,
new Rectangle(300 - t.getRegionWidth() / 2, 1.9f * 60, t.getRegionWidth(), t.getRegionHeight()),
tex, ButtonValues.MAINMENU_PLAY);
buttons.put("buttonPlay", b);
To remove it, just buttons.remove("buttonPlay"). and it'll disappear from the screen and be forgotten by the game.
The arguments are the game screen which owns it (so the button can call buttonPressed() on the game screen), a Rectangle with its coordinates, its texture (used to draw it), and its enum value.
And here's the Button class:
public class Button {
public Rectangle r;
public TextureRegion image;
private MyScreen screen;
private ButtonValues b;
public Button(MyScreen screen, Rectangle r, TextureRegion image, ButtonValues b) {
this.screen = screen;
this.r = r;
this.image = image;
this.b = b;
}
public void act() {
screen.buttonPressed(b);
}
public boolean isClicked(float x, float y) {
return x > r.x && y > r.y && x < r.x + r.width && y < r.y + r.height;
}
}
isClicked() just takes in an (x, y) and checks whether that point is contained within the button. On mouse click, I iterate through all the buttons and call act() if a button isClicked.
The second way I did it was similar, but with a lambda expression instead of the ButtonValues enum. The Button class is similar, but with these changes (it's a lot simpler than it sounds):
The field ButtonValues b is replaced with Runnable r, and this is removed from the constructor. Added is a setAction() method which takes in a Runnable and sets r to the Runnable passed to it. The act() method is just r.run(). Example:
public class Button {
[Rectangle, Texture, Screen]
Runnable r;
public Button(screen, rectangle, texture) {...}
public void setAction(Runnable r) { this.r = r; }
public void act() { r.run(); }
}
To create a button, I do the following:
Button b = new Button(this,
new Rectangle(300 - t.getRegionWidth() / 2, 1.9f * 60, t.getRegionWidth(), t.getRegionHeight()),
tex);
b.setAction(() -> b.screen.doSomething());
buttons.put("buttonPlay", b);
First, a button is created with its containing game screen class, its bounding box, and its texture. Then, in the second command, I set its action - in this case, b.screen.doSomething();. This can't be passed to the constructor, because b and b.screen don't exist at that point. setAction() takes a Runnable and sets it as that Button's Runnable that is called when act() is called. However, Runnables can be created with lambda syntax, so you don't need to create an anonymous Runnable class and can just pass in the function it performs.
This method allows much more flexibility, but with one caveat. The screen field in Button holds a MyScreen, the base screen class from which all of my game screens are extended. The Button's function can only use methods that are part of the MyScreen class (which is why I made buttonPressed() in MyScreen and then realized I could just scrap the lambda expressions completely). The obvious solution is to cast the screen field, but for me it wasn't worth the extra code when I could just use the buttonPressed() method.
If I had a beginGame() method in my MainMenuScreen class (which extends MyScreen), the lambda expression passed to the button would need to involve a cast to MainMenuScreen:
b.setAction(() -> ((MainMenuScreen) b.screen).beginGame());
Unfortunately, even wildcard syntax doesn't help here.
And finally, for completeness, the code in the game loop to operate the buttons:
public abstract class MyScreen implements Screen {
protected HashMap<String, Button> buttons; // initialize this in the constructor
// this is called in every game screen's game loop
protected void handleInput() {
if (Gdx.input.justTouched()) {
Vector2 touchCoords = new Vector2(Gdx.input.getX(), Gdx.input.getY());
g.viewport.unproject(touchCoords);
for (HashMap.Entry<String, Button> b : buttons.entrySet()) {
if (b.getValue().isClicked(touchCoords.x, touchCoords.y))
b.getValue().act();
}
}
}
}
And to draw them, located in a helper class:
public void drawButtons(HashMap<String, Button> buttons) {
for (HashMap.Entry<String, Button> b : buttons.entrySet()) {
sb.draw(b.getValue().image, b.getValue().r.x, b.getValue().r.y);
}
}
I am new to libGDX. I am trying to create a custom button by extending com.badlogic.gdx.scenes.scene2d.ui.Button.
I want all the button related logic in this class. But I am not getting how to make the click work. I read many tutorials regarding adding Event Listeners but nothing is working.
public class RestartButton extends Button {
public RestartButton(ButtonStyle style) {
super(style);
}
#Override
public void draw(SpriteBatch batch, float parentAlpha) {
batch.draw(TextureProvider.getInstance().getRestart(), 175, 100);
}
}
And i am trying to add my button in the screen(i.e in show method) like this
RestartButton restartButton;
restartButton=new RestartButton(new ButtonStyle());
Stage stage;
stage.addActor(restartButton);
I am able to see my button on the screen. Now what i want to do is add some code which gets invoked when button is clicked or touched. Can someone please help ?
restartButton = new RestartButton(new ButtonStyle());
button.addListener(new ClickListener() {
#Override
public void clicked(InputEvent event, float x, float y) {
System.out.println("Restart clicked!");
}
});
stage.addActor(restartButton);
It does not work because you need to setBounds for your Button. If you wanted to draw the button in the position (175, 100) you could just create a Button directly from Button Class and call
button.setBounds(x, y, width, height);
Then adding the listener will work because now your button will actually have a position and an area in the stage. If you still need to extend the button class for your own reasons, you can set the bounds in the extended class ditectly or you can pass another argument in your RestartButton class. Similar to:
public RestartButton(ButtonStyle style, Vector2 position, Vector2 size) {
super(style);
this.setBounds(position.x, position.y, size.x, size.y);
}
Then the button will automatically be drawn to the position you want without the need of overriding draw method. add the listener by using this.addListener(yourListener);
Hope it helps.
I need some help to make an activity.
I have stored coordinates from shapes and name of shapes in a database. These are grouped into rooms.
In the activity I need the top of the screen 8 TextViews (with colors and textst) in two rows. If I click on a textView, accordingly the shapes for the room need to be drawn on the screen under the textViews.
If I click on a shape, I need to get back the information, on which shape I made a click.
I guess you could create a custom view:
public class CustomView extends View { ... }
and then you simply override appropriate methods like:
#Override
public boolean onTouchEvent(MotionEvent ev) { /* check for hitting shapes here */ }
#Override
protected void onDraw(Canvas canvas) { /* draw anything you want here */ }
Use the search engine of your choice to find out more. This looks like what you need
I may seem ignorant but I have not found much information about how to go about my problem developing my BlackBerry application.
Basically I have constructed a UI that contains a few image buttons. What I want to do is when I click on one of the buttons, I want an image at the center of the screen to switch seemlessly to another image.
I am currently not using any threads though from what I've gathered I need to?
Or do I simply have to push a new screen in my button listener and recall the class's constructor and rebuild the page with different images?
Sorry if this is unclear, I would really appreciate some basic info on how to do this or a link that explains page refreshing in detail for blackberry!
Thanks alot
EDIT : Okay here is a very small part of my code :
This is basically the last thing I have tried in the listeners, I dont know how to change the image (flagField) when I press one of the two buttons! The only thing that worked for me was pushScreen(new Csps("americanflag")); every time I would press on a button, but the performance was bad and I was left with a stack of screens, not ideal.
I have been trying to do this all day long hehe... sigh , here goes:
public class CSP extends UiApplication
{
public static void main(String[] args)
{
CSP theApp = new CSP();
theApp.enterEventDispatcher();
}
public CSP()
{
CSPS csps = new CSPS();
pushScreen(csps);
}
}
class CSPS extends MainScreen implements FieldChangeListener
{
int width = Display.getWidth();
int height = Display.getHeight();
ButtonField backButton;
ImageButtonField canadianFlag;
ImageButtonField americanFlag;
Bitmap changeableFlag;
String currentFlag ="canada_flag.png";
BitmapField flagField;
canadianFlag = construct("canada_flag.png", 0.18);
americanFlag = construct("us.gif", 0.18);
canadianFlag.setChangeListener(this);
americanFlag.setChangeListener(this);
add(flagField);
add(canadianFlag);
add(americanFlag);
//LISTENERS
public void fieldChanged(Field field, int context){
if(field == canadianFlag){
setCurrentFlagResource("canada_flag.png");
BitmapField newFlagField = populateFlagField(currentFlag, 0.3);
replace(flagField, newFlagField);
this.invalidate();
this.doPaint();
this.updateDisplay();
}
else if(field == americanFlag){
setCurrentFlagResource("american-flag.gif");
BitmapField newFlagField = populateFlagField(currentFlag, 0.3);
replace(flagField, newFlagField);
this.invalidate();
this.doPaint();
this.updateDisplay();
}
the setCurrentFlagRessource method sets the flagField attribute to the appropriate BitmapField
There is a method called setBitmap() in the BitmapField.
In the listeners you should simply call flagField.setBitmap(newbitmap). You can also use the method setImage(). There is no need to call invalidate(), updateUI etc.
If you get any IllegalStateException while using this method, simply get a lock on the Applicaton Event lock object or invokeLater() this code.
You will only need to use threads if you are getting your images from some place that might block (like a web server). You should only need to update the images, though you may need to call invalidate() on your button or screen. What would really help is some information on what you are doing. You didn't even tell us what class you're using to display the image, if you've extended it or not, etc.
I don't know what you are doing in your code; But The way of refresh the screen is not like that;
So, I change Your CSPS screen which you are trying to refresh the screen; See the below code...
class CSPS extends MainScreen implements FieldChangeListener
{
int width = Display.getWidth();
int height = Display.getHeight();
ButtonField backButton;
//ImageButtonField canadianFlag;
//ImageButtonField americanFlag;
Bitmap changeableFlag;
String currentFlag ="canada_flag.png";
BitmapField flagField;
public CSPS()
{
createGUI();
}
public void createGUI()
{
canadianFlag = construct("canada_flag.png", 0.18);
americanFlag = construct("us.gif", 0.18);
canadianFlag.setChangeListener(this);
americanFlag.setChangeListener(this);
add(flagField);
add(canadianFlag);
add(americanFlag);
//LISTENERS
}
public void fieldChanged(Field field, int context){
if(field == canadianFlag)
{
setCurrentFlagResource("canada_flag.png");
BitmapField newFlagField = populateFlagField(currentFlag, 0.3);
replace(flagField, newFlagField);
deleteAll();
createGUI();
invalidate();
}
else if(field == americanFlag)
{
setCurrentFlagResource("american-flag.gif");
BitmapField newFlagField = populateFlagField(currentFlag, 0.3);
replace(flagField, newFlagField);
deleteAll();
createGUI();
invalidate();
}
}
}
Construct a createGUI() method, in that write the code that what you have to design for that screen. Not only this screen; What ever you are creating do like this;
and
deleteAll();
createGUI();
invalidate();
For refresh the screen do the above three lines; delete all the fields and call again the createGUI() method; and at last invalidate();
I'm developing an application for android, and i have a big old GridView that's centered vertically in the screen, with all the cells visible.
What i want, is a function by which all cells are zoomed in by a factor of 3, letting the user scroll around in the gridview instead.
Sort of how Wordfeud does it if you're familiar with the game.
I've searched the webs and haven't found a satisfactory solution, i managed to find a way to alter the layout params in my adapter for the grid tiles, but it only stretches them vertically and not horizontally, also the whole app becomes slow and unresponsive until you've scrolled through the list and let the Grid reload all the views or whatever.
Any help at all is appreciated.
In the end, i used a custom view with a Canvas instead - much easier than trying to bend GridView into something it's not supposed to be.
I know it's been a while since you asked, but I had a similar problem that it took me a long time to solve, so I'm hoping someone else may stumble on this. I ended up extending the GridLayout class and hooking up an instance of
private class ScaleGestureListener
extends ScaleGestureDetector.SimpleOnScaleGestureListener
in the constructor:
scaleDetector = new ScaleGestureDetector(context, new ScaleGestureListener());
since it's a view group, override the dispatch… instead of the on… methods:
#Override
public boolean dispatchTouchEvent(MotionEvent ev){
scaleDetector.onTouchEvent(ev);
super.dispatchTouchEvent(ev);
return true;
}
#Override
public void dispatchDraw(Canvas canvas) {
String disp = String.valueOf(mScaleFactor);
Log.v(TAG, "dispatch draw " + disp);
canvas.save();
// i have some other stuff in here specific to my app
canvas.scale(mScaleFactor, mScaleFactor);
super.dispatchDraw(canvas);
canvas.restore();
}