Javafx titledpane animation speed - animation

Is there a way to set the animation speed of a titledpane? I couldn't find anything.
In fact there are two issues.
First:
The animation of the expanding is faster than the expanding of the content itself. You see that the circle is slightly slower than the bar from the second titledpane is moving down.
Second:
How to change the speed of both of them. I need them at the same speed, because it looks weird.
Here is a small example for testing purposes:
package test;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class TestClass extends Application{
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
VBox vb = new VBox();
{
TitledPane tp = new TitledPane();
System.out.println(tp.getContextMenu());
tp.setContent(new Circle(100));
tp.setText("asfadf");
tp.expandedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
System.out.println("expand " + newValue);
}
});
vb.getChildren().add(tp);
}
vb.getChildren().add(new Line(0, 0, 100, 0));
{
TitledPane tp = new TitledPane();
tp.setContent(new Circle(100));
tp.setText("asfadf");
tp.expandedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
System.out.println("expand " + newValue);
}
});
vb.getChildren().add(tp);
}
vb.setStyle("-fx-background-color: gray");
Scene scene = new Scene(vb,500,500);
primaryStage.setScene(scene);
primaryStage.show();
}
}

Short answer: There's no API to change the duration.
However, there are two ways to achieve it anyway:
Alternative #1: Reflection
The duration is defined in the static final field com.sun.javafx.scene.control.TitledPaneSkin.TRANSITION_DURATION. Using reflection, you can change its value but this is really bad. Not only because that's a dirty hack, but also because TitledPaneSkin is Oracle internal API that is subject to change anyway. Also, this does not fix the issue with the different speeds:
private static void setTitledPaneDuration(Duration duration) throws NoSuchFieldException, IllegalAccessException {
Field durationField = TitledPaneSkin.class.getField("TRANSITION_DURATION");
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(durationField, durationField.getModifiers() & ~Modifier.FINAL);
durationField.setAccessible(true);
durationField.set(TitledPaneSkin.class, duration);
}
Alternative #2: Set your own skin
To be on the safe side, you could create and use your own skin (start by copying the existing one) using titledPane.setSkin(). This way you can also fix the different speed, which is basically caused by linear vs. ease interpolation - but that's quite some work.

Just disable the animation with something like:
TitledPane pane = new TitledPane();
pane.animatedProperty().set(false);
and expanding will be as fast as possible.

Related

Infinite Cycle View Pager 3D Item -- How to make NewActivity, when click one card

I want to implement a special conversion that is popular on the net, namely "Infinite Cycle View Pager - GithubAndroid Library Dev Light"
Here is the link:Infinite Cycle View Pager
I think you know this, you can present cards that can be rotated in 3D, very design.
I would like a menu system from this, if you click on the card, I would like to open a NewActivity window. It would basically be a video presentation.
But the code below is "myAdapter.java", I transformed it, and in the setOnClickListener - Intent section
so as you can see, it doesn't indicate a syntax error, it translates, it starts, but when I click on the card, the program exits with a program stop.
What do you think could be wrong?
import android.content.Context;
import android.content.Intent;
import android.provider.ContactsContract;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.viewpager.widget.PagerAdapter;
import java.util.List;
public class MyAdopter extends PagerAdapter {
List<Integer> images;
Context context;
LayoutInflater layoutInflater;
public MyAdopter(List<Integer> images, Context context) {
this.images = images;
this.context = context;
layoutInflater = LayoutInflater.from(context);
}
#Override
public int getCount() {
return images.size();
}
#Override
public boolean isViewFromObject(#NonNull View view, #NonNull Object object) {
return view.equals(object);
}
#Override
public void destroyItem(#NonNull ViewGroup container, int position, #NonNull Object object) {
container.removeView((View)object);
}
#NonNull
#Override
public Object instantiateItem(#NonNull ViewGroup container, final int position) {
View view= layoutInflater.inflate(R.layout.card_item,container,false);
ImageView imageView =(ImageView)view.findViewById(R.id.imageview);
imageView.setImageResource(images.get(position));
container.addView(view);
view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(context, zoompresentationvideo.class);
context.startActivity(intent);
//Toast.makeText(context,"Clicked image show presentation " +position,Toast.LENGTH_LONG ).show();
}
});
return view;
}
}
Thank you in advance for your help.

Node back to Position after TranslateTransition in JavaFX (shake TextField)

After a wrong insert i want to shake a Textfield.
For that i have code a static shake animation
public static void shake(Node node) {
TranslateTransition tt = new TranslateTransition(Duration.millis(50), node);
tt.setByX(10f);
tt.setCycleCount(2);
tt.setAutoReverse(true);
tt.playFromStart();
}
This animation is called in a ChangeListener when the input is wrong.
This works fine but if the user type wrong characters very fast, the TextField is moving to the right.
Is there a way to do a repositioning? Or is there a better way to do this?
Don't create a new transition every time you want to shake the field, otherwise the field will shake while it is already shaking, the outcome of which would be difficult to predict but probably pretty undesirable.
The other thing you need to do is to setFromX(0) for the translate transition. This is actually pretty important because what happens with a translate transition is that, when it stops, the translateX value for the node remains at whatever it was when the transition stopped.
When you invoke playFromStart multiple times while the transition is playing, the transition will be stopped again and then started from the beginning. If you don't have a fromX, then the beginning will be wherever the translateX value last ended up at (which might not be what you want at all and, after shaking, the item might start moving to seemingly random positions on the screen). However, if you have a fromX, then the beginning translateX value will always start from an untranslated position.
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.*;
import javafx.scene.control.TextField;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ShakenNotStirred extends Application {
#Override
public void start(Stage stage) throws Exception {
TextField field = new TextField();
Shaker shaker = new Shaker(field);
field.textProperty().addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
try {
Integer.parseInt(newValue);
} catch (NumberFormatException e) {
shaker.shake();
}
}
});
StackPane layout = new StackPane(field);
layout.setPadding(new Insets(20));
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
class Shaker {
private TranslateTransition tt;
public Shaker(Node node) {
tt = new TranslateTransition(Duration.millis(50), node);
tt.setFromX(0f);
tt.setByX(10f);
tt.setCycleCount(2);
tt.setAutoReverse(true);
}
public void shake() {
tt.playFromStart();
}
}
}
Same way i thinking.
Thanks jewelsea!
The solution for me is to make the TranslateTransition static and use it inside the static method like this:
private static TranslateTransition tt;
public static TranslateTransition shake(Node node) {
if (tt == null || tt.getNode() != node)
{
tt = new TranslateTransition(Duration.millis(50), node);
}
tt.setByX(10f);
tt.setCycleCount(2);
tt.setAutoReverse(true);
if (tt.getStatus() == Status.STOPPED)
{
tt.playFromStart();
}
return tt;
}
In this way, a shake will only perform when the previous is stopped. And the transition only changed if it is NULL or another Node.

JFrame won't load the images I want it to

Please be patient with me.. I'm very new to Java.
I have two separate JFrames and the first loads the background I want but when I dispose the first JFrame and load the second one it loads with the background from the first.
j1.java
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.ImageIcon;
public class j1 extends JFrame implements KeyListener {
public bg1 img;
public bg2 img2;
public j1() {
lvl1();
}
private JFrame lvl1() {
this.img=new bg1();
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
setTitle("lvl1");
setResizable(false);
setSize(600, 600);
setMinimumSize(new Dimension(600, 600));
setDefaultCloseOperation(EXIT_ON_CLOSE);
getContentPane().add(img);
pack();
setVisible(true);
return(this);
}
private JFrame lvl2() {
this.img2=new bg2();
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
setTitle("lvl2");
setResizable(false);
setSize(600, 600);
setMinimumSize(new Dimension(600, 600));
setDefaultCloseOperation(EXIT_ON_CLOSE);
getContentPane().add(img2);
pack();
setVisible(true);
return(this);
}
public void keyPressed(KeyEvent e) { }
public void keyReleased(KeyEvent e) {
if(e.getKeyCode()== KeyEvent.VK_RIGHT) {
lvl1().dispose();
lvl2();
}
}
public void keyTyped(KeyEvent e) { }
public static void main(String[] args) {
new j1();
}
}
bg1.java
import java.awt.Graphics;
import javax.swing.JComponent;
import java.awt.image.BufferedImage;
import java.io.*;
import javax.imageio.ImageIO;
public class bg1 extends JComponent {
public BufferedImage person;
public BufferedImage background;
public bg1() {
loadImages2();
}
public void loadImages2() {
try {
String personn = "Images/person.gif";
person = ImageIO.read(new File(personn));
String backgroundd = "Images/background2.jpg";
background = ImageIO.read(new File(backgroundd));
} catch (IOException ex) {
ex.printStackTrace();
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(background, 0, 0, this);
g.drawImage(person, 100, 100, this);
}
public static void main(String[] args) {
new bg1();
}
}
bg2.java is very similar to bg1.java but it has different names for the images and voids.
So you kind of have a series of problems.
First, this is one of the dangers of re-using a frame this way, basically, you never actually remove bg1 from the frame, you just keep adding new instances of the bg2. This means that bg1 is still visible and valid on the frame...
Second, you're calling lvl1() AGAIN before you call lvl2, which is making a new instance of bg1 and adding that to the window and then disposing of it (which does NOT dispose of the components) and then you add a new instance of lvl2 to the frame and the whole thing is just one big mess.
Instead, you should simply be using a CardLayout which will allow you to switch between the individual views more elegantly. See How to Use CardLayout for moer details.
You should also have a look at How to Use Key Bindings instead of using KeyListener
As general rule of thumb, you should avoid overriding JFrame, this has a nasty habit of just confusing the whole thing. Simple create a new instance of a JFrame when you need it and add you components directly to it. Before anyone takes that the wrong way, you'll also want to have a look at The Use of Multiple JFrames, Good/Bad Practice?

JavaFX - Set Slider value after dragging mouse button

I'm writing music player and I don't know how to code slider dragging handler to set value after user frees mouse button. When I write simple MouseDragged method dragging brings non estetic "rewinding" sound because mediaplayer changes value every time slider moves. While playing slider automatic changes value by mediaplayer listener to synchronize with track duration. This is what I got so far.
ChangeListener<Duration> timeListener = new ChangeListener<Duration>() {
#Override
public void changed(
ObservableValue<? extends Duration> observableValue,
Duration duration,
Duration current) {
durSlider
.setValue(current
.toSeconds());
}
};
durSlider.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
mediaPlayer.seek(Duration.seconds(durSlider.getValue()));
}
});
The valueChanging property of the slider indicates if the slider is in the process of being changed. It is an observable property, so you can attach a listener directly to it, and respond when the value stops changing:
durSlider.valueChangingProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> obs, Boolean wasChanging, Boolean isNowChanging) {
if (! isNowChanging) {
mediaPlayer.seek(Duration.seconds(durSlider.getValue()));
}
}
});
This won't change the position of the player if the user clicks on the "track" on the slider, or uses the keyboard to move it. For that, you can register a listener with the value property. You need to be careful here, because the value is also going to change via your time listener. In theory, the time listener should set the value of the slider, and then that should cause an attempt to set the current time of the player to the exact value it already has (which would result in a no-op). However, rounding errors will likely result in a lot of small adjustments, causing the "static" you are observing. To fix this, only move the media player if the change is more than some small minimum amount:
private static double MIN_CHANGE = 0.5 ; //seconds
// ...
durSlider.valueProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> obs, Number oldValue, Number newValue) {
if (! durSlider.isValueChanging()) {
double currentTime = mediaPlayer.getCurrentTime().toSeconds();
double sliderTime = newValue.doubleValue();
if (Math.abs(currentTime - sliderTime) > 0.5) {
mediaPlayer.seek(newValue.doubleValue());
}
}
}
});
Finally, you don't want your time listener to move the slider if the user is trying to drag it:
ChangeListener<Duration> timeListener = new ChangeListener<Duration>() {
#Override
public void changed(
ObservableValue<? extends Duration> observableValue,
Duration duration,
Duration current) {
if (! durSlider.isValueChanging()) {
durSlider.setValue(current.toSeconds());
}
}
};
Here's a complete example (using lambdas for brevity):
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
import javafx.stage.Stage;
import javafx.util.Duration;
public class VideoPlayerTest extends Application {
private static final String MEDIA_URL =
"http://download.oracle.com/otndocs/products/javafx/oow2010-2.flv";
private static final double MIN_CHANGE = 0.5 ;
#Override
public void start(Stage primaryStage) {
MediaPlayer player = new MediaPlayer(new Media(MEDIA_URL));
MediaView mediaView = new MediaView(player);
Slider slider = new Slider();
player.totalDurationProperty().addListener((obs, oldDuration, newDuration) -> slider.setMax(newDuration.toSeconds()));
BorderPane root = new BorderPane(mediaView, null, null, slider, null);
slider.valueChangingProperty().addListener((obs, wasChanging, isChanging) -> {
if (! isChanging) {
player.seek(Duration.seconds(slider.getValue()));
}
});
slider.valueProperty().addListener((obs, oldValue, newValue) -> {
if (! slider.isValueChanging()) {
double currentTime = player.getCurrentTime().toSeconds();
if (Math.abs(currentTime - newValue.doubleValue()) > MIN_CHANGE) {
player.seek(Duration.seconds(newValue.doubleValue()));
}
}
});
player.currentTimeProperty().addListener((obs, oldTime, newTime) -> {
if (! slider.isValueChanging()) {
slider.setValue(newTime.toSeconds());
}
});
Scene scene = new Scene(root, 540, 280);
primaryStage.setScene(scene);
primaryStage.show();
player.play();
}
public static void main(String[] args) {
launch(args);
}
}

Update UI from an AsyncTaskLoader

I've converted my AsyncTask to an AsyncTaskLoader (mostly to deal with configuration changes). I have a TextView I am using as a progress status and was using onProgressUpdate in the AsyncTask to update it. It doesn't look like AsyncTaskLoader has an equivalent, so during loadInBackground (in the AsyncTaskLoader) I'm using this:
getActivity().runOnUiThread(new Runnable() {
public void run() {
((TextView)getActivity().findViewById(R.id.status)).setText("Updating...");
}
});
I am using this in a Fragment, which is why I'm using getActivity(). This work pretty well, except when a configuration change happens, like changing the screen orientation. My AsyncTaskLoader keeps running (which is why I'm using an AsyncTaskLoader), but the runOnUiThread seems to get skipped.
Not sure why it's being skipped or if this is the best way to update the UI from an AsyncTaskLoader.
UPDATE:
I ended up reverting back to an AsyncTask as it seems better suited for UI updates. Wish they could merge what works with an AsyncTask with an AsyncTaskLoader.
It's actually possible. You essentially need to subclass the AsyncTaskloader and implement a publishMessage() method, which will use a Handler to deliver the progress message to any class that implements the ProgressListener (or whatever you want to call it) interface.
Download this for an example: http://www.2shared.com/file/VW68yhZ1/SampleTaskProgressDialogFragme.html (message me if it goes offline) - this was based of http://habrahabr.ru/post/131560/
Emm... you shouldn't be doing this.
because how an anonymous class access parent class Method or Field is by storing an invisible reference to the parent class.
for example you have a Activity:
public class MyActivity
extends Activity
{
public void someFunction() { /* do some work over here */ }
public void someOtherFunction() {
Runnable r = new Runnable() {
#Override
public void run() {
while (true)
someFunction();
}
};
new Thread(r).start(); // use it, for example here just make a thread to run it.
}
}
the compiler will actually generate something like this:
private static class AnonymousRunnable {
private MyActivity parent;
public AnonymousRunnable(MyActivity parent) {
this.parent = parent;
}
#Override
public void run() {
while (true)
parent.someFunction();
}
}
So, when your parent Activity destroys (due to configuration change, for example), and your anonymous class still exists, the whole activity cannot be gc-ed. (because someone still hold a reference.)
THAT BECOMES A MEMORY LEAK AND MAKE YOUR APP GO LIMBO!!!
If it was me, I would implement the "onProgressUpdate()" for loaders like this:
public class MyLoader extends AsyncTaskLoader<Something> {
private Observable mObservable = new Observable();
synchronized void addObserver(Observer observer) {
mObservable.addObserver(observer);
}
synchronized void deleteObserver(Observer observer) {
mObservable.deleteObserver(observer);
}
#Override
public void loadInBackground(CancellationSignal signal)
{
for (int i = 0;i < 100;++i)
mObservable.notifyObservers(new Integer(i));
}
}
And in your Activity class
public class MyActivity extends Activity {
private Observer mObserver = new Observer() {
#Override
public void update(Observable observable, Object data) {
final Integer progress = (Integer) data;
mTextView.post(new Runnable() {
mTextView.setText(data.toString()); // update your progress....
});
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreated(savedInstanceState);
MyLoader loader = (MyLoader) getLoaderManager().initLoader(0, null, this);
loader.addObserver(mObserver);
}
#Override
public void onDestroy() {
MyLoader loader = (MyLoader) getLoaderManager().getLoader(0);
if (loader != null)
loader.deleteObserver(mObserver);
super.onDestroy();
}
}
remember to deleteObserver() during onDestroy() is important, this way the loader don't hold a reference to your activity forever. (the loader will probably be held alive during your Application lifecycle...)
Answering my own question, but from what I can tell, AsyncTaskLoader isn't the best to use if you need to update the UI.
In the class in which you implement LoaderManager.LoaderCallback (presumably your Activity), there is an onLoadFinished() method which you must override. This is what is returned when the AsyncTaskLoader has finished loading.
The best method is to use LiveData, 100% Working
Step 1: Add lifecycle dependency or use androidx artifacts as yes during project creation
implementation "androidx.lifecycle:lifecycle-livedata:2.1.0"
Step 2: Create the loader class as follow, in loader create in public method to set the livedata that can be observed from activity or fragment. see the setLiveCount method in my loader class.
package com.androidcodeshop.asynctaskloaderdemo;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.MutableLiveData;
import androidx.loader.content.AsyncTaskLoader;
import java.util.ArrayList;
public class ContactLoader extends AsyncTaskLoader<ArrayList<String>> {
private MutableLiveData<Integer> countLive = new MutableLiveData<>();
synchronized public void setLiveCount(MutableLiveData<Integer> observer) {
countLive = (observer);
}
public ContactLoader(#NonNull Context context) {
super(context);
}
#Nullable
#Override
public ArrayList<String> loadInBackground() {
return loadNamesFromDB();
}
private ArrayList<String> loadNamesFromDB() {
ArrayList<String> names = new ArrayList<>();
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
names.add("Name" + i);
countLive.postValue(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return names;
}
#Override
protected void onStartLoading() {
super.onStartLoading();
forceLoad(); // forcing the loading operation everytime it starts loading
}
}
Step 3: Set the live data from activity and observe the change as follows
package com.androidcodeshop.asynctaskloaderdemo;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.MutableLiveData;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity implements
LoaderManager.LoaderCallbacks<ArrayList> {
private ContactAdapter mAdapter;
private ArrayList<String> mNames;
private MutableLiveData<Integer> countLiveData;
private static final String TAG = "MainActivity";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNames = new ArrayList<>();
mAdapter = new ContactAdapter(this, mNames);
RecyclerView mRecyclerView = findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setAdapter(mAdapter);
countLiveData = new MutableLiveData<>();
countLiveData.observe(this, new androidx.lifecycle.Observer<Integer>() {
#Override
public void onChanged(Integer integer) {
Log.d(TAG, "onChanged: " + integer);
Toast.makeText(MainActivity.this, "" +
integer,Toast.LENGTH_SHORT).show();
}
});
// initialize the loader in onCreate of activity
getSupportLoaderManager().initLoader(0, null, this);
// it's deprecated the best way is to use viewmodel and livedata while loading data
}
#NonNull
#Override
public Loader onCreateLoader(int id, #Nullable Bundle args) {
ContactLoader loader = new ContactLoader(this);
loader.setLiveCount(countLiveData);
return loader;
}
#Override
public void onLoadFinished(#NonNull Loader<ArrayList> load, ArrayList data) {
mNames.clear();
mNames.addAll(data);
mAdapter.notifyDataSetChanged();
}
#Override
public void onLoaderReset(#NonNull Loader loader) {
mNames.clear();
}
#Override
protected void onDestroy() {
super.onDestroy();
}
}
Hope this will help you :) happy coding

Resources