Keyboard covers TextField - javafxports

As the center node of a gluon view I have a scrollpane which contains several textfields in a vbox. When one of these textfields becomes the focusowner and the keyboard shows up, the textfield doesn't get repositioned according to the layout of the keyboard, so it is left covered by the keyboard.
I tried putting
android:windowSoftInputMode="adjustResize"
in the AndroidManifest, but without any success.
As a workaround I translate the y-coordinates of the covered textfield to the visible area. When you press the android back button to hide the keyboard, the textfields position will be reset to its original state. The issue I'm getting here is that I don't get an event for the android back button, no matter where I add the listener:
view.addEventFilter(MobileEvent.BACK_BUTTON_PRESSED, evt -> eventFilter);
MobileApplication.getInstance().getGlassPane().addEventFilter(MobileEvent.BACK_BUTTON_PRESSED, evt -> eventFilter);
Is there any possibility to handle the positioning of a node under the keyboard, or to get a reference to the keyboard itself?

Only layers get the MobileEvent.BACK_BUTTON_PRESSED event. One solution is to go native and use the Android API.

This is the solution I could come up with so far:
public class PositionAdjuster {
public static void main(String[] args) { launch(args); }
private static final float SCALE = FXActivity.getInstance().getResources().getDisplayMetrics().density;
private Node nodeToAdjust;
private ObservableValue<Node> focusOwner;
private ViewGroup viewGroup;
private Rect currentBounds;
private DoubleProperty height;
private OnGlobalLayoutListener layoutListener;
public PositionAdjuster(Node nodeToAdjust, ObservableValue<Node> focusOwner) {
this.nodeToAdjust = nodeToAdjust;
this.focusOwner = focusOwner;
initLayoutListener();
}
private void initLayoutListener() {
double screenHeight = MobileApplication.getInstance().getScreenHeight();
height = new SimpleDoubleProperty(screenHeight);
height.addListener((ov, n, n1) -> onHeightChanged(n, n1));
layoutListener = () -> height.set(getCurrentHeigt());
viewGroup = FXActivity.getViewGroup();
viewGroup.getViewTreeObserver().addOnGlobalLayoutListener(layoutListener);
currentBounds = new Rect();
}
private float getCurrentHeigt() {
viewGroup.getRootView().getWindowVisibleDisplayFrame(currentBounds);
return currentBounds.height() / SCALE;
}
private void onHeightChanged(Number oldValue, Number newValue) {
double heightDelta = newValue.doubleValue() - oldValue.doubleValue();
if (heightDelta < 0) {
double maxY = getBoundsInScene(nodeToAdjust)).getMaxY();
double currentMaxY = heightDelta + maxY;
double result = currentMaxY- getBoundsInScene(focuseOwner.getValue()).getMaxY();
if (result < 0) {
nodeToAdjust.setTranslateY(result);
}
} else if (heightDelta > 0) {
nodeToAdjust.setTranslateY(0);
}
}
private Bounds getBoundsInScene(Node node) {
return node.localToScene(node.getBoundsInLocal());
}
public void removeListener() {
viewGroup.getViewTreeObserver().removeOnGlobalLayoutListener(layoutListener);
}
}
EDIT:
I think this is a more straightforward approach. The previous version was dependent on the maxY of noteToAdjust to be equal to the height of the screen, not taking into account e.g. the presence of a bottomBar. Now the maxY position of the focusedNode is validated against the visible screen height, and the difference is used to reposition its parent.
public AndroidPositionAdjuster(Node parent, ObservableValue<Node> focusOwner) {
this.parent = parent;
this.focusOwner = focusOwner;
initLayoutListener();
}
private void onHeightChanged(Number oldHeight, Number newHeight) {
double heightDelta = newHeight.doubleValue() - oldHeight.doubleValue();
if (heightDelta < 0) {
double maxY = newHeight.doubleValue();
double focusedNodeY = getBoundsInScene(focusOwner.getValue()).getMaxY();
double result = maxY - focusedNodeY;
if (result < 0) {
parent.setTranslateY(result);
}
} else if (heightDelta > 0) {
parent.setTranslateY(0);
}
}

Related

Android - How to make google Maps display a polyline that animates sequenctial flashing dots

I am searching for a way to animate the dots between two markers on a google map in android device.
So what i want in the end is the following line between the two images:
and it would be used like this typical google polyline implementation:
lets say there is a point A and a Point B. if im directing the user to point B, then the line animates to from point A to point B so the user knows to walk in this direction.
to achieve this i thought i could get the points out of the polyLine and remove them and add them back
rapidily. so lets say i had 5 points in the polyLine, i would remove position 1 , then put it back, then remove position 2, and put it back, to simulate this animation.
but it does not work . once hte polyline is set it seems i cannot alter it. you have any suggestions ?
val dotPattern = Arrays.asList(Dot(), Gap(convertDpToPixel(7).toFloat()))
val polyLineOptions: PolylineOptions = PolylineOptions()
.add(usersLocation)
.add(users_destination)
.pattern(dotPattern)
.width(convertDpToPixel(6).toFloat())
dottedPolyLine = googleMap.addPolyline(polyLineOptions)
dottedPolyLine?.points?.removeAt(1) // here as a test if my idea i try removing a point but it looks like a point here means current location or destination so there always 2. i thought a point would be one of the dots.
You can use MapView-based custom view View Canvas animationlike in this answer:
This approach requires
MapView-based
custom
view,
that implements:
drawing over the MapView canvas;
customizing line styles (circles instead of a simple line);
binding path to Lat/Lon coordinates of map
performing animation.
Drawing over the MapView needs to override dispatchDraw().
Customizing line styles needs
setPathEffect()
method of
Paint
class that allows to create create path for "circle stamp" (in
pixels), which will repeated every "advance" (in pixels too),
something like that:
mCircleStampPath = new Path(); mCircleStampPath.addCircle(0,0,
CIRCLE_RADIUS, Path.Direction.CCW); mCircleStampPath.close();
For binding path on screen to Lat/Lon coordinates
Projection.toScreenLocation()
needed, that requires
GoogleMap
object so custom view should implements OnMapReadyCallback for
receive it. For continuous animation
postInvalidateDelayed()
can be used.
but not draw path directly from point A to point B, but from point A to point C that animated from A to B. To get current position of point C you can use SphericalUtil.interpolate() from Google Maps Android API Utility Library. Something like that:
public class EnhancedMapView extends MapView implements OnMapReadyCallback {
private static final float CIRCLE_RADIUS = 10;
private static final float CIRCLE_ADVANCE = 3.5f * CIRCLE_RADIUS; // spacing between each circle stamp
private static final int FRAMES_PER_SECOND = 30;
private static final int ANIMATION_DURATION = 10000;
private OnMapReadyCallback mMapReadyCallback;
private GoogleMap mGoogleMap;
private LatLng mPointA;
private LatLng mPointB;
private LatLng mPointC;
private float mCirclePhase = 0; // amount to offset before the first circle is stamped
private Path mCircleStampPath;
private Paint mPaintLine;
private final Path mPathFromAtoC = new Path();
private long mStartTime;
private long mElapsedTime;
public EnhancedMapView(#NonNull Context context) {
super(context);
init();
}
public EnhancedMapView(#NonNull Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public EnhancedMapView(#NonNull Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public EnhancedMapView(#NonNull Context context, #Nullable GoogleMapOptions options) {
super(context, options);
init();
}
#Override
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
canvas.save();
drawLineFomAtoB(canvas);
canvas.restore();
// perform one shot animation
mElapsedTime = System.currentTimeMillis() - mStartTime;
if (mElapsedTime < ANIMATION_DURATION) {
postInvalidateDelayed(1000 / FRAMES_PER_SECOND);
}
}
private void drawLineFomAtoB(Canvas canvas) {
if (mGoogleMap == null || mPointA == null || mPointB == null) {
return;
}
// interpolate current position
mPointC = SphericalUtil.interpolate(mPointA, mPointB, (float) mElapsedTime / (float)ANIMATION_DURATION);
final Projection mapProjection = mGoogleMap.getProjection();
final Point pointA = mapProjection.toScreenLocation(mPointA);
final Point pointC = mapProjection.toScreenLocation(mPointC);
mPathFromAtoC.rewind();
mPathFromAtoC.moveTo(pointC.x, pointC.y);
mPathFromAtoC.lineTo(pointA.x, pointA.y);
// change phase for circles shift
mCirclePhase = (mCirclePhase < CIRCLE_ADVANCE)
? mCirclePhase + 1.0f
: 0;
mPaintLine.setPathEffect(new PathDashPathEffect(mCircleStampPath, CIRCLE_ADVANCE, mCirclePhase, PathDashPathEffect.Style.ROTATE));
canvas.drawPath(mPathFromAtoC, mPaintLine);
}
private void init() {
setWillNotDraw(false);
mCircleStampPath = new Path();
mCircleStampPath.addCircle(0,0, CIRCLE_RADIUS, Path.Direction.CCW);
mCircleStampPath.close();
mPaintLine = new Paint();
mPaintLine.setColor(Color.BLACK);
mPaintLine.setStrokeWidth(1);
mPaintLine.setStyle(Paint.Style.STROKE);
mPaintLine.setPathEffect(new PathDashPathEffect(mCircleStampPath, CIRCLE_ADVANCE, mCirclePhase, PathDashPathEffect.Style.ROTATE));
// start animation
mStartTime = System.currentTimeMillis();
postInvalidate();
}
#Override
public void getMapAsync(OnMapReadyCallback callback) {
mMapReadyCallback = callback;
super.getMapAsync(this);
}
#Override
public void onMapReady(GoogleMap googleMap) {
mGoogleMap = googleMap;
mGoogleMap.setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener() {
#Override
public void onCameraMove() {
invalidate();
}
});
if (mMapReadyCallback != null) {
mMapReadyCallback.onMapReady(googleMap);
}
}
public void setPoints(LatLng pointA, LatLng pointB) {
mPointA = pointA;
mPointB = pointB;
}
}
NB! This is just idea, not full tested code.

How can I convert mouse pointer coordinates to a MapPoint using Gluon Maps 1.0.1?

Using Gluon Mobile 4 and Gluon Maps 1.0.1, I am displaying a map with a layer showing foot steps. When the users double clicks the mouse button, a new foot step is shown. This works great, but I currently need a workaround to convert from pointer coordinates (where the user clicked) to MapPoints (needed for the layer).
Here is how the mouse click is obtained:
public MainView(String name) {
super(name);
MapView mapView = new MapView();
mapView.setZoom(18f);
mapView.setCenter(NUREMBERG);
layer = new FootStepsLayer();
mapView.addLayer(layer);
setCenter(mapView);
layer.addPoint(NUREMBERG);
setOnMouseClicked(event -> {
if ((event.getButton() == MouseButton.PRIMARY)
&& (event.getClickCount() == 2)) {
double x = event.getX();
double y = event.getY();
layer.addPoint(x, y);
}
});
}
Currently my layer implementation looks like this:
public class FootStepsLayer extends MapLayer {
private static final Image FOOTSTEPS
= new Image(FootStepsLayer.class.getResourceAsStream("/footsteps.png"),
32, 32, true, true);
private final ObservableList<Pair<MapPoint, Node>> points
= FXCollections.observableArrayList();
public void addPoint(MapPoint mapPoint) {
Node node = new ImageView(FOOTSTEPS);
Pair<MapPoint, Node> pair = new Pair<>(mapPoint, node);
points.add(pair);
getChildren().add(node);
markDirty();
}
public void addPoint(double x, double y) {
Bounds bounds = baseMap.getParent().getLayoutBounds();
baseMap.moveX(x - bounds.getWidth() / 2);
baseMap.moveY(y - bounds.getHeight() / 2);
addPoint(new MapPoint(baseMap.centerLat().get(),
baseMap.centerLon().get()));
}
#Override
protected void layoutLayer() {
// Warning: suggested conversion to functional style crashed app on BlueStacks
for (Pair<MapPoint, Node> element : points) {
MapPoint mapPoint = element.getKey();
Node node = element.getValue();
Point2D point = baseMap.getMapPoint(mapPoint.getLatitude(), mapPoint.getLongitude());
node.setVisible(true);
node.setTranslateX(point.getX());
node.setTranslateY(point.getY());
}
}
}
My workaround is in public void addPoint(double x, double y): I am calling moveX() and moveY(), because after that I can query centerLat() and centerLong(). This is not ideal because the map moves and the new foot step becomes the center of the map. What I want is the map position to remain unchanged.
If I have not overlooked it, there seems to be no API for converting mouse coordinates to geo locations. As answered in question create a polyline in gluon mapLayer, the BaseMap class has two getMapPoint methods, but I have found none the other way round. But there must be a way to do it. ;-)
If you have a look at BaseMap, there is already one method that does precisely what you are looking for, but only for the center of the map: calculateCenterCoords.
Based on it, you could add your own method to BaseMap, where the sceneX and sceneY coordinates are taken into account instead:
public MapPoint getMapPosition(double sceneX, double sceneY) {
double x = sceneX - this.getTranslateX();
double y = sceneY - this.getTranslateY();
double z = zoom.get();
double latrad = Math.PI - (2 * Math.PI * y) / (Math.pow(2, z) * 256);
double mlat = Math.toDegrees(Math.atan(Math.sinh(latrad)));
double mlon = x / (256 * Math.pow(2, z)) * 360 - 180;
return new MapPoint(mlat, mlon);
}
Then you can expose this method in MapView:
public MapPoint getMapPosition(double sceneX, double sceneY) {
return baseMap.getMapPosition(sceneX, sceneY);
}
So you can use it on your map:
mapView.setOnMouseClicked(e -> {
MapPoint mapPosition = mapView.getMapPosition(e.getSceneX(), e.getSceneY());
System.out.println("mapPosition: " + mapPosition.getLatitude()+ ", " + mapPosition.getLongitude());
});
This method should be part of Maps, so feel free to create a feature request or even a pull request.

How to get smooth animation with KeyPress event in javaFX?

I am trying to implement LEFT RIGHT movement of a rectangle shape in JavaFX. Below is my code:
public void start(Stage primaryStage) throws Exception {
AnchorPane ancPane = new AnchorPane();
final Rectangle rect = new Rectangle();
rect.setHeight(50);
rect.setWidth(50);
ancPane.getChildren().add(rect);
Scene scene = new Scene(ancPane, 400, 200, Color.GREEN);
primaryStage.setScene(scene);
primaryStage.show();
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent keyEvent) {
System.out.println("hello");
if(keyEvent.getCode().toString() == "RIGHT"){
System.out.println("Move Right");
TranslateTransition translateTransitionRight = new TranslateTransition();
translateTransitionRight.setDuration(Duration.millis(200));
translateTransitionRight.setNode(rect);
translateTransitionRight.setFromX(rect.getTranslateX());
translateTransitionRight.setToX(rect.getTranslateX()+30);
translateTransitionRight.play();
}
if(keyEvent.getCode().toString() == "LEFT"){
System.out.println("Move Left");
TranslateTransition translateTransitionRight = new TranslateTransition();
translateTransitionRight.setDuration(Duration.millis(200));
translateTransitionRight.setNode(rect);
translateTransitionRight.setFromX(rect.getTranslateX());
translateTransitionRight.setToX(rect.getTranslateX()-30);
translateTransitionRight.play();
}
}
});
}
Here when I press either LEFT/RIGHT key continuously (i.e. I don't released the key, I hold it for some times) the rectangle moves but not continuously. It pauses for a small fraction of time just after animation started. After the pause the animation continues smoothly.
How can I get rid of this pausing of animation with KeyEvents?
I would use an AnimationTimer for moving the rectangle, and just update a property representing the velocity on key pressed or key released:
final Rectangle rect = ... ;
final double rectangleSpeed = 100 ; // pixels per second
final double minX = 0 ;
final double maxX = 800 ; // whatever the max value should be.. can use a property and bind to scene width if needed...
final DoubleProperty rectangleVelocity = new SimpleDoubleProperty();
final LongProperty lastUpdateTime = new SimpleLongProperty();
final AnimationTimer rectangleAnimation = new AnimationTimer() {
#Override
public void handle(long timestamp) {
if (lastUpdateTime.get() > 0) {
final double elapsedSeconds = (timestamp - lastUpdateTime.get()) / 1_000_000_000.0 ;
final double deltaX = elapsedSeconds * rectangleVelocity.get();
final double oldX = rect.getTranslateX();
final double newX = Math.max(minX, Math.min(maxX, oldX + deltaX));
rect.setTranslateX(newX);
}
lastUpdateTime.set(timestamp);
}
};
rectangleAnimation.start();
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getCode()==KeyCode.RIGHT) { // don't use toString here!!!
rectangleVelocity.set(rectangleSpeed);
} else if (event.getCode() == KeyCode.LEFT) {
rectangleVelocity.set(-rectangleSpeed);
}
}
});
scene.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getCode() == KeyCode.RIGHT || event.getCode() == KeyCode.LEFT) {
rectangleVelocity.set(0);
}
}
});
UPDATE:
The AnimationTimer executes its handle method once each time a frame is rendered by the JavaFX mechanism. The long passed into the handle method is a timestamp of the render frame, in nanoseconds.
The way this works is that we keep track of the last update time. The handle(...) method computes the elapsed time since the last update, multiplies it by the rectangle's velocity, and updates the translateX of the rectangle by that amount. The AnimationTimer is always running, but initially the velocity is set to zero so the rectangle doesn't move.
The keyPressed handler simply changes the velocity: to a positive value if moving right and a negative value if moving left. The keyReleased handler sets the velocity back to zero.

My AsyncTask does not update UI smoothly (animation)

I want to make a TextView appear little by little, like animation. The problem is, the animation is not smooth. It gets stuck for a little while sometimes and then resumes. Sometimes even worse, it goes back... I mean, the TextView gets bigger and bigger but suddenly gets smaller then bigger again. Could anyone help me?
private class UnfoldTask extends AsyncTask<Integer, Integer, Integer> {
View view;
public UnfoldTask(View v) {
this.view = v;
ViewGroup.LayoutParams pa = view.getLayoutParams();
pa.height = 0;
view.setLayoutParams(pa);
}
#Override
protected Integer doInBackground(Integer... maxHeight) {
ViewGroup.LayoutParams pa = view.getLayoutParams();
while (pa.height < maxHeight[0]) {
pa.height += (int) (24 * getResources().getDisplayMetrics().density + 0.5f);
sleep(100);
publishProgress(pa.height);
}
return maxHeight[0];
}
private void sleep(int i) {
try {
Thread.sleep(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
#Override
protected void onProgressUpdate(Integer... values) {
ViewGroup.LayoutParams pa = view.getLayoutParams();
pa.height = values[0];
view.setLayoutParams(pa);
}
#Override
protected void onPostExecute(Integer result) {
ViewGroup.LayoutParams pa = view.getLayoutParams();
pa.height = result;
view.setLayoutParams(pa);
}
}
You should be using a scale animation for this. Here's an example:
ScaleAnimation animation = new ScaleAnimation(1, 2, 1, 2, centerX, centerY); // Scales from normal size (1) to double size (2). centerX/Y is the center of your text view. Change this to set the pivot point of your animation.
animation.setDuration(1000);
myTextView.startAnimation(animation);
You can use droidQuery to simplify this:
//this will set the height of myView to 0px.
$.with(myView).height(0);
//when you are ready to animate to height (in pixels):
$.with(myView).animate("{height:" + height + "}", new AnimationOptions());
Check the documentation if you want to get fancy - such as adding duration, and event callbacks. If you are still noticing non-smooth animation, consider adding the application attribute to your AndroidManifest:
android:hardwareAccelerated="true"

SlimDX Handling Window Resizing

I'm trying to handle the program window being resized, and the (I think inefficient) code I've flung together below seems to do the trick.
Is there a better way to do this, preferably one that does not create a stutter when resizing the window and which does not constantly use 12-17% of a CPU? I also suspect MessagePump.Run may somehow run before form.Resize finishes setting up the device again, and throw an error.
Thanks!
using System;
using System.Drawing;
using System.Windows.Forms;
using SlimDX;
using SlimDX.Direct3D9;
using SlimDX.Windows;
namespace SlimDX_1
{
struct Vertex
{
public Vector4 Position;
public int Color;
}
static class Program
{
private static VertexBuffer vertices;
private static Device device;
private static RenderForm form;
private static PresentParameters present;
private static VertexDeclaration vertexDecl;
private static VertexElement[] vertexElems;
private static bool wasMinimized = false;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
form = new RenderForm("Tutorial 1: Basic Window");
init();
form.Resize += (o, e) =>
{
if (form.WindowState == FormWindowState.Minimized)
{
foreach (var item in ObjectTable.Objects)
{
item.Dispose();
}
wasMinimized = true;
}
else
{
foreach (var item in ObjectTable.Objects)
{
item.Dispose();
}
init();
device.SetRenderState(RenderState.FillMode, FillMode.Wireframe);
device.SetRenderState(RenderState.CullMode, Cull.None);
present.BackBufferHeight = form.ClientSize.Height;
present.BackBufferWidth = form.ClientSize.Width;
device.Reset(present);
}
};
MessagePump.Run(form, () =>
{
if (form.WindowState == FormWindowState.Minimized)
{
return;
}
device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.Black, 1.0f, 0);
device.BeginScene();
device.SetStreamSource(0, vertices, 0, 20); // 20 is the size of each vertex
device.VertexDeclaration = vertexDecl;
device.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);
device.EndScene();
device.Present();
});
foreach (var item in ObjectTable.Objects)
{
item.Dispose();
}
}
private static void init()
{
present = new PresentParameters();
//present.EnableAutoDepthStencil = false;
//present.BackBufferCount = 1;
//present.SwapEffect = SwapEffect.Discard;
present.Windowed = true;
present.BackBufferHeight = form.ClientSize.Height;
present.BackBufferWidth = form.ClientSize.Width;
//present.BackBufferFormat = Format.Unknown;
device = new Device(new Direct3D(), 0, DeviceType.Hardware, form.Handle, CreateFlags.HardwareVertexProcessing, present);
vertices = new VertexBuffer(device, 3 * 20, Usage.WriteOnly, VertexFormat.None, Pool.Managed);
vertices.Lock(0, 0, LockFlags.None).WriteRange(new Vertex[]
{
new Vertex() { Color = Color.Red.ToArgb(), Position = new Vector4(400.0f, 100.0f, 0.5f, 1.0f) },
new Vertex() { Color = Color.Blue.ToArgb(), Position = new Vector4(650.0f, 500.0f, 0.5f, 1.0f) },
new Vertex() { Color = Color.Green.ToArgb(), Position = new Vector4(150.0f, 500.0f, 0.5f, 1.0f) }
});
vertices.Unlock();
// specifies the layout of the vertexes
vertexElems = new VertexElement[]
{
new VertexElement(0, 0, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.PositionTransformed, 0),
new VertexElement(0, 16, DeclarationType.Color, DeclarationMethod.Default, DeclarationUsage.Color, 0),
VertexElement.VertexDeclarationEnd
};
vertexDecl = new VertexDeclaration(device, vertexElems);
}
}
}
You're going way above and beyond what you need to do when the window is resized. You're releasing every single DirectX object you've created, including the graphics device, and then recreating everything. This is going to take a comparatively long time, which is why you're seeing performance issues.
In fact, none of your objects need to be released. Simply call the Reset() function on the device to recreate the backbuffer to match the new window size. Check out some of the native Direct3D9 tutorials on window resizing to see how in general how the process works.

Resources