I'm trying to test some custom scrolling behavior that changes the location of a view based on how far down the user has scrolled. I discovered that even though the view will scroll in Robolectric 2.2, the height of the ListView (and everything else) is 0.
I read about the shadow objects and that we're supposed to call visible() when starting up the Activity so that it'll be drawn, and that's done. The default layout for a ListActivity is just a ListView with match_parent for height and width.
What's missing? Why doesn't the list have height?
#Test
public void testListActivity() {
final ListActivity activity = Robolectric.buildActivity(ListActivity.class).create().start().resume().visible().get();
ListView listView = activity.getListView();
// Set up an adapter with items to scroll.
final ArrayList<String> messages = new ArrayList<>();
final int numberOfChildren = 100;
for (int i = 0; i < numberOfChildren; i++) {
messages.add("test");
}
final ArrayAdapter<String> listAdapter = new ArrayAdapter<>(activity, android.R.layout.simple_dropdown_item_1line, messages);
listView.setAdapter(listAdapter);
Assert.assertEquals(listView.getCount(), numberOfChildren);
Assert.assertTrue(listView.getHeight() > 0); // This fails.
}
Call populateItems() on the shadow of your listview after setting the adapter:
...
listView.setAdapter(listAdapter);
ShadowListView shadowListView = Robolectric.shadowOf(listView);
shadowListView.populateItems();
Assert.assertTrue(listView.getHeight() > 0);
...
Related
I'm stuck on full scaling for my JavaFX application. I'm in the process of making a full screen feature for the application and I'm running into issues on trying to get the aspect ratio and positioning right without manually editing the values.
With the way I've been trying, the values butcher the game's start screen making the positioning change making the designs of the game offset from the center of the application. I can understand the reasoning behind it with the way I set it up. My problem is wondering how to scale the start screen and keep it's original position without having to manually edit the values.
What I thought of was trying to input the value and having it scale according to that value then putting the result in the position of objects X and Y.
if (fullscreen) {
WIDTH = (Enter aspect ratio here) * 1.5;
HEIGHT = (Enter aspect ratio here) * 1.5;
} else {
WIDTH = 990;
HEIGHT = 525;
}
with Obvious flaws this butchers the start screen.
My solution was to make a double() that you just enter the value of the application WIDTH/HEIGHT then entering the amount you want to divide by (since I couldn't come up with exact cords, I grabbed the WIDTH and divided by specific value for it to align in the center) following with a boolean to state whether it's full screened or not. Though my only issue with this theory is that it'll only work with 1920x1080 monitors so I'd assume I would have to manually enter all types of aspect ratios to make it fit otherwise the start screen would be butchered.
I've seen a way of scaling here:
JavaFX fullscreen - resizing elements based upon screen size
Though I'm not sure how to correctly implement it.
public static boolean fullscreen = false;
public static double WIDTH = 990;
public static double HEIGHT = 525;
public static Pane pane = new Pane();
public static void StartScreen() {
pane.setPrefSize(WIDTH, (HEIGHT - 25)); // the 25 is for the text field/input.
pane.setStyle("-fx-background-color: transparent;");
Group sGroup = new Group();
Image i = new Image("file:start/So7AA.png");
ImageView outer = new ImageView(i);
// outer.setX(Ce.WIDTH/4.75); //4.75 // The functioning code for the snippit
// outer.setY(-10); //-10
outer.setX(Ce.WIDTH/position(3.60, fullscreen)); //4.75 // The non functioning code.
outer.setY(position(-1, Ce.fullscreen)); //-10
outer.setFitWidth(550);
outer.setFitHeight(550);
outer.setOpacity(.3);
GaussianBlur gBlur = new GaussianBlur();
gBlur.setRadius(50);
ImageView seal = new ImageView(i);
// seal.setX(Ce.WIDTH/3.83); //247.5 - 3.83
// seal.setY(39); //39
seal.setX(Ce.WIDTH/position(3.83, fullscreen)); //247.5 - 3.83
seal.setY(position(32, Ce.fullscreen)); //39
seal.setFitWidth(450);
seal.setFitHeight(450);
ImageView sealBlur = new ImageView(i);
// sealBlur.setX(Ce.WIDTH/3.83); //247.5 - 3.83
// sealBlur.setY(39); //39
sealBlur.setX(Ce.WIDTH/position(3.83, fullscreen)); //247.5 - 3.83
sealBlur.setY(position(32, Ce.fullscreen));
sealBlur.setFitWidth(450);
sealBlur.setFitHeight(450);
sealBlur.setEffect(gBlur);
}
For getting the values of the WIDTH and HEIGHT:
public static double getWidth(double W, boolean fs) {
if (fs) {
return WIDTH = Screen.getPrimary().getBounds().getMaxX();
} else {
return WIDTH = W;
}
}
public static double getHeight(double H, boolean fs) {
if (fs) {
return HEIGHT = Screen.getPrimary().getBounds().getMaxY();
} else {
return HEIGHT = H;
}
}
I know there's a way around this, I'm just not sure how to pull it off.
I'm not sure exactly what the requirements are here, but it looks like you have three images, which you want centered, and you want them all scaled by the same amount so that one of the images fills the available space in its container. (Then, you just need to make sure its container grows to fill all the space, and you can call stage.setFullScreen(true) or stage.setMaximized(true) as needed.)
You can do this with a pretty simple custom pane that manages the layout in the layoutChildren() method:
public class ImagePane extends Region {
private final Image image1;
private final ImageView imageView1;
private final Image image2;
private final ImageView imageView2;
private final Image image3;
private final ImageView imageView3;
public ImagePane(Image image1, Image image2, Image image3) {
this.image1 = image1;
this.image2 = image2;
this.image3 = image3;
imageView1 = new ImageView(image1);
imageView2 = new ImageView(image2);
imageView3 = new ImageView(image3);
getChildren().addAll(imageView1, imageView2, imageView3);
}
#Override
protected void layoutChildren() {
double xScale = getWidth() / image1.getWidth();
double yScale = getHeight() / image1.getHeight();
double scale = Math.min(xScale, yScale);
for (ImageView view : List.of(imageView1, imageView2, imageView3) {
scaleAndCenter(view, scale);
}
}
private void scaleAndCenter(ImageView view, scale) {
double w = scale * view.getImage().getWidth();
double h = scale * view.getImage().getHeight();
view.setFitWidth(w);
view.setFitHeight(h);
view.relocate((getWidth()-w) / 2, (getHeight()-h) / 2);
}
}
The rest of your layout looks something like:
Label label = new Label("Type in 'start'.\nType in 'options' for options.\n(Demo)");
TextField textField = new TextField();
ImagePane imagePane = new ImagePane(new Image(...), new Image(...), new Image(...));
AnchorPane anchor = new AnchorPane(imagePane, label);
AnchorPane.setTopAnchor(imagePane, 0.0);
AnchorPane.setRightAnchor(imagePane, 0.0);
AnchorPane.setBottomAnchor(imagePane, 0.0);
AnchorPane.setLeftAnchor(imagePane, 0.0);
AnchorPane.setTopAnchor(label, 5.0);
AnchorPane.setLeftAnchor(label, 5.0);
BorderPane root = new BorderPane();
root.setCenter(anchor);
root.setBottom(textField);
Now everything should just respond to whatever size is assigned to the root pane, so setting full screen mode should "just work".
I have a FlowPane with equally-sized rectangular children and I have this problem with the space at the right end of the flowpane, when the space is not enough to fit another column of children, I want it to be equally divided between the other columns.
An example of what I want to achieve is how the file explorer in windows behaves, see the gif bellow for reference
The default FlowPane behaviour doesn't look like this, it leaves the remaining width that can't fit a new child at the end of the region, as shown in the gif bellow
And I failed to find any API or documentation to help me achieve my goal, I thought of adding a listener on the width property and adjusting the hGap property accordingly, something like
[ flowPaneWidth - (sum of the widths of the children in one column) ] / (column count - 1)
But again, I have no idea how to figure out the column count, so any help would be appreciated.
Here is a MRE if anyone wants to try their ideas :
public class FPAutoSpace extends Application {
#Override
public void start(Stage ps) throws Exception {
FlowPane root = new FlowPane(10, 10);
root.setPadding(new Insets(10));
for(int i = 0; i < 20; i++) {
root.getChildren().add(new Rectangle(100, 100, Color.GRAY));
}
ps.setScene(new Scene(root, 600, 600));
ps.show();
}
}
After a bit of thinking, I tried to implement the idea mentioned in the question :
adding a listener on the width property and adjusting the hGap property accordingly
but I added the listener on the needsLayout property instead, as follows :
public class FPAutoSpace extends Application {
private double nodeWidth = 100;
#Override
public void start(Stage ps) throws Exception {
FlowPane root = new FlowPane();
root.setVgap(10);
for (int i = 0; i < 20; i++) {
root.getChildren().add(new Rectangle(nodeWidth, nodeWidth, Color.GRAY));
}
root.needsLayoutProperty().addListener((obs, ov, nv) -> {
int colCount = (int) (root.getWidth() / nodeWidth);
//added 4 pixels because it gets glitchy otherwise
double occupiedWidth = nodeWidth * colCount + 4;
double hGap = (root.getWidth() - occupiedWidth) / (colCount - 1);
root.setHgap(hGap);
});
StackPane preRoot = new StackPane(root);
preRoot.setPadding(new Insets(10));
preRoot.setAlignment(Pos.TOP_LEFT);
ps.setScene(new Scene(preRoot, 600, 600));
ps.show();
}
}
Not sure how this will hold up in a multi-hundred node FlowPane but it worked for the MRE
Let me know if you think there are better ways to do it.
I am using a controlsfx GridView in a JavaFX application. It shows a scrollbar when needed, but I can't find any way to determine where the scrollbar is positioned at, nor update it. I need to be able to do things like respond to a "go to the top" command from the user and scroll up; or scroll to keep the selected thumbnail visible as the user uses arrow keys to navigate through the grid. But I don't see how to get access to the current scroll position, nor manipulate it, as you can with a ScrollPane.
For example, here is a sample application that creates a GridView with 100 generated images in it. I add a listener to the "onScrollProperty", but it is never called. I also have no idea how I would cause it to scroll to a certain scroll position (0..1):
import java.awt.image.BufferedImage;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import org.controlsfx.control.GridView;
import org.controlsfx.control.cell.ImageGridCell;
// Demo class to illustrate the slowdown problem without worrying about thumbnail generation or fetching.
public class GridViewDemo extends Application {
private static final int CELL_SIZE = 200;
public static void main(String[] args) {
launch(args);
}
public void start(Stage primaryStage) {
// Create a Scene with a ScrollPane that contains a TilePane.
GridView<Image> gridView = new GridView<>();
gridView.setCellFactory(gridView1 -> new ImageGridCell());
gridView.setCellWidth(CELL_SIZE);
gridView.setCellHeight(CELL_SIZE);
gridView.setHorizontalCellSpacing(10);
gridView.setVerticalCellSpacing(10);
addImagesToGrid(gridView);
gridView.onScrollProperty().addListener((observable, oldValue, newValue) -> {
// Never called
System.out.println("Scrolled...");
});
primaryStage.setScene(new Scene(gridView, 1000, 600));
primaryStage.setOnCloseRequest(x -> {
Platform.exit();
System.exit(0);
});
primaryStage.show();
}
private void addImagesToGrid(GridView<Image> gridView) {
for (int i = 0; i < 100; i++) {
final Image image = createFakeImage(i, CELL_SIZE);
gridView.getItems().add(image);
}
}
// Create an image with a bunch of rectangles in it just to have something to display.
private static Image createFakeImage(int imageIndex, int size) {
BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
for (int i = 1; i < size; i ++) {
g.setColor(new Color(i * imageIndex % 256, i * 2 * (imageIndex + 40) % 256, i * 3 * (imageIndex + 60) % 256));
g.drawRect(i, i, size - i * 2, size - i * 2);
}
return SwingFXUtils.toFXImage(image, null);
}
}
The needed maven include is:
<dependency>
<groupId>org.controlsfx</groupId>
<artifactId>controlsfx</artifactId>
<version>8.0.6_20</version>
</dependency>
Here is a screen shot of what it looks like.
Maybe to late for the questioner. But after i stumbled across the same problem, after hours of investigation, hopefully useful to others...
I add a listener to the "onScrollProperty", but it is never called.
This "works as intended". :-/ See https://bugs.openjdk.java.net/browse/JDK-8096847.
You have to use "addEventFilter()". See example below.
To set the scroll position is a pain. You have to get the underlying "VirtualFlow" object of the GridView. VirtualFlow contains methods to set the scroll position to specific rows. It's strange that GridView seems to have no API for this common use case. :-(
A "prove of concept" example how to set scroll position, for a GridView with images:
/**
* Register a scroll event and a key event listener to gridView.
* After that navigate with "arrow up" and "arrow down" the grid.
* #param gridView
*/
private void addScrollAndKeyhandler(final GridView<Image> gridView) {
// example for scroll listener
gridView.addEventFilter(ScrollEvent.ANY, e-> System.out.println("*** scroll event fired ***"));
// add UP and DOWN arrow key listener, to set scroll position
gridView.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.UP) oneRowUp(gridView);
if (e.getCode() == KeyCode.DOWN) oneRowDown(gridView);
});
}
int selectedRow = 0; // current "selected" GridView row.
/**
* Scrolls the GridView one row up.
* #param gridView
*/
private void oneRowUp(final GridView<Image> gridView) {
// get the underlying VirtualFlow object
VirtualFlow<?> flow = (VirtualFlow<?>) ((GridViewSkin<?>) gridView.getSkin()).getChildren().get(0);
if (flow.getCellCount() == 0) return; // check that rows exist
if (--selectedRow < 0) selectedRow = 0;
if (selectedRow >= flow.cellCountProperty().get()) selectedRow = flow.getCellCount() - 1;
System.out.println("*** KeyEvent oneRowUp " + selectedRow + " ***");
flow.scrollTo(selectedRow);
}
/**
* Scrolls the GridView one row down.
* #param gridView
*/
private void oneRowDown(final GridView<Image> gridView) {
// get the underlying VirtualFlow object
VirtualFlow<?> flow = (VirtualFlow<?>) ((GridViewSkin<?>) gridView.getSkin()).getChildren().get(0);
if (flow.getCellCount() == 0) return; // check that rows exist
if (++selectedRow >= flow.cellCountProperty().get()) selectedRow = flow.getCellCount() - 1;
System.out.println("*** KeyEvent oneRowDown " + selectedRow + " ***");
flow.scrollTo(selectedRow);
}
(Tested with javafx 15.0.1 and controlsfx 11.0.3)
A few days ago I started studying JavaFX, and came across the desire to perform 2 experiments. Firstly, I would like to know if it is possible to put an animated background behind an user interface. I've succeeded in creating an animated background, and now I'm having great difficulties to position some controls in the middle of my interface.
I'd like to introduce you 2 pictures of my program. The first demonstrates the undesirable result that I'm getting:
I believe this is my nodes tree:
This is the code of my application:
public class AnimatedBackground extends Application
{
// #########################################################################################################
// MAIN
// #########################################################################################################
public static void main(String[] args)
{
Application.launch(args);
}
// #########################################################################################################
// INSTÂNCIAS
// #########################################################################################################
private Group root;
private Group grp_hexagons;
private Rectangle rect_background;
private Scene cenario;
// UI
private VBox lay_box_controls;
private Label lab_test;
private TextArea texA_test;
private Button bot_test;
// #########################################################################################################
// INÍCIO FX
// #########################################################################################################
#Override public void start(Stage stage) throws Exception
{
this.confFX();
cenario = new Scene(this.root , 640 , 480);
this.rect_background.widthProperty().bind(this.cenario.widthProperty());
this.rect_background.heightProperty().bind(this.cenario.heightProperty());
stage.setScene(cenario);
stage.setTitle("Meu programa JavaFX - R.D.S.");
stage.show();
}
protected void confFX()
{
this.root = new Group();
this.grp_hexagons = new Group();
// Initiate the circles and all animation stuff.
for(int cont = 0 ; cont < 15 ; cont++)
{
Circle circle = new Circle();
circle.setFill(Color.WHITE);
circle.setEffect(new GaussianBlur(Math.random() * 8 + 2));
circle.setOpacity(Math.random());
circle.setRadius(20);
this.grp_hexagons.getChildren().add(circle);
double randScale = (Math.random() * 4) + 1;
KeyValue kValueX = new KeyValue(circle.scaleXProperty() , randScale);
KeyValue kValueY = new KeyValue(circle.scaleYProperty() , randScale);
KeyFrame kFrame = new KeyFrame(Duration.millis(5000 + (Math.random() * 5000)) , kValueX , kValueY);
Timeline linhaT = new Timeline();
linhaT.getKeyFrames().add(kFrame);
linhaT.setAutoReverse(true);
linhaT.setCycleCount(Animation.INDEFINITE);
linhaT.play();
}
this.rect_background = new Rectangle();
this.root.getChildren().add(this.rect_background);
this.root.getChildren().add(this.grp_hexagons);
// UI
this.lay_box_controls = new VBox();
this.lay_box_controls.setSpacing(20);
this.lay_box_controls.setAlignment(Pos.CENTER);
this.bot_test = new Button("CHANGE POSITIONS");
this.bot_test.setAlignment(Pos.CENTER);
this.bot_test.setOnAction(new EventHandler<ActionEvent>()
{
#Override public void handle(ActionEvent e)
{
for(Node hexagono : grp_hexagons.getChildren())
{
hexagono.setTranslateX(Math.random() * cenario.getWidth());
hexagono.setTranslateY(Math.random() * cenario.getHeight());
}
}
});
this.texA_test = new TextArea();
this.texA_test.setText("This is just a test.");
this.lab_test = new Label("This is just a label.");
this.lab_test.setTextFill(Color.WHITE);
this.lab_test.setFont(new Font(32));
this.lay_box_controls.getChildren().add(this.lab_test);
this.lay_box_controls.getChildren().add(this.texA_test);
this.lay_box_controls.getChildren().add(this.bot_test);
this.root.getChildren().add(this.lay_box_controls);
}
}
I've tried to make the use of a StackPane as the root of my scene graph, but also found an undesired result. Despite the controls have stayed in the center of the window, the circles begin to move in as they grow and shrink, making it appear that everything is weird.
The second thing I would like to know is if it is possible to customize the controls so they perform some animation when some event happens. Although we can change the appearance of controls using CSS, it's harder to create something complex. For example, when a control changes its appearance due to a change of state, the transition state change is not made in an animated way, but in an abrupt and static way. Is there a way to animate, for example, a button between its states? This would be done using the JavaFX API? Or would that be using CSS? Or would not be possible in any way?
Thank you for your attention.
after much struggle, I and some users of the Oracle community could resolve this issue. I see no need to repeat here all the resolution made by us, so I'll post the link so you can access the solution of the problem. I hope this benefits us all. Thanks for your attention anyway.
https://community.oracle.com/thread/2620500
I am trying to create a grid that fills the width of a ContentPanel. The grid should have two columns of equal size that span the entire width of the grid. Resizing the browser window should update the grid size accordingly. I would expect the code below to accomplish this, but the grid does not grow on browser resize and there is a ~15px gap between the second column and the right edge of the grid.
Any ideas?
public class MyGrid extends ContentPanel {
#Override
protected void onRender(Element parent, int index) {
super.onRender(parent, index);
setLayout(new FillLayout());
ColumnConfig c1 = new ColumnConfig("value","value", 50);
ColumnConfig c2 = new ColumnConfig("value1","value1", 50);
ListStore<ModelData> store = new ListStore<ModelData>();
for (int i=0; i<10; i++) {
BaseModelData data = new BaseModelData();
data.set("value", "value");
data.set("value1", "value1");
store.add(data);
}
Grid<ModelData> grid = new Grid<ModelData>(store, new ColumnModel(Arrays.asList(new ColumnConfig[] {c1, c2})));
grid.setAutoHeight(true);
grid.setAutoWidth(true);
grid.getView().setAutoFill(true);
add(grid);
}
}
The cause of the extra space on the right of the table is grid.setAutoHeight(). Remove this and it will be gone. Getting the table to resize with the browser seems to require the use of a Viewport (I dont understand why the ContentPanel would grow without the grid in the code above). This code accomplishes what I was trying to do:
public class MyGrid extends Viewport {
#Override
protected void onRender(Element parent, int index) {
super.onRender(parent, index);
setLayout(new FitLayout());
ColumnConfig c1 = new ColumnConfig("value","value", 50);
ColumnConfig c2 = new ColumnConfig("value1","value1", 50);
ListStore<ModelData> store = new ListStore<ModelData>();
for (int i=0; i<10; i++) {
BaseModelData data = new BaseModelData();
data.set("value", "value");
data.set("value1", "value1");
store.add(data);
}
final Grid<ModelData> grid = new Grid<ModelData>(store, new ColumnModel(Arrays.asList(new ColumnConfig[] {c1, c2})));
grid.getView().setAutoFill(true);
ContentPanel panel = new ContentPanel();
panel.setLayout(new FitLayout());
panel.add(grid);
add(panel);
}
}
Have you tried this?
grid.getView().setForceFit(true);
Grid will fit its container. Also you really should use other layout for container to make inner elements fit container (FitLayout, BorderLayout, RowLayout are acceptable)