Android convert Layout to Bitmap and save it - performance

I'm trying to convert a RelativeLayout to Bitmap, and save it
But every time I run my code, I'm getting java.lang.IllegalArgumentException: width and height must be > 0, I think I'm getting this because the layout wasn't displayed.
Is there a way to convert a RelativeLayout which has a TextView to Bitmap and save it?
My codes are:
Activity code
///////// Activity
////////Activity
TextView text;
public bita(View view) {
text= (TextView) findViewById(R.id.text);
text.setText("aaatest");
}
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
text.setText("clicked");
}

To convert a view, first you need to measure the view, then assign a size for it.

Related

Xamarin Forms WKWebView size doesn't shrink

I have a custom WkWebView where I set the height of the view to the size of the html content.
This works great when I initialize it, but the problem comes when I change the source of the WkWebView to a shorter html.
I've already encountered this problem in the android version of my app and I fixed that by setting the HeightRequest to 0 before EvaluateJavaScriptAsync. In that way the view will always get bigger.
I tried the same thing with iOS but it keeps the highest content I had .
So for example :
If I set the source of the WkWebView to a html file that is 200px height and change it to a html file that is 1000px, it works fine and I can see all the content. BUT, if I try to go back to my 200px html file, I get a 800px blank space underneath and keep the 1000px height.
The goal is to always have the height of the WKWebView to adapt to the height of its content.
Here is the current version of the custom render
namespace MyNamespace.iOS.CustomControl
{
public class AutoHeightWebViewRenderer : WkWebViewRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
try
{
base.OnElementChanged(e);
if (NativeView != null)
{
var webView = (WKWebView)NativeView;
NavigationDelegate = new ExtendedWKWebViewDelegate(this);
}
}
catch (Exception ex)
{
//Log the Exception
}
}
}
class ExtendedWKWebViewDelegate : WKNavigationDelegate
{
AutoHeightWebViewRenderer webViewRenderer;
public ExtendedWKWebViewDelegate(AutoHeightWebViewRenderer _webViewRenderer = null)
{
webViewRenderer = _webViewRenderer ?? new AutoHeightWebViewRenderer();
}
public override async void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
try
{
var _webView = webViewRenderer.Element as AutoHeightWebView;
if (_webView != null)
{
_webView.HeightRequest = 0d;
await Task.Delay(300);
var result = await _webView.EvaluateJavaScriptAsync("(function(){return document.body.scrollHeight;})()");
_webView.HeightRequest = Convert.ToDouble(result);
}
}
catch (Exception ex)
{
//Log the Exception
}
}
}
}
EDIT 1 :
To be clearer, I'm able to change the height of the webview, but I dont know the height because it always returned the height of the largest html displayed so far. Nomatter if I use _webView.EvaluateJavaScriptAsync("(function(){return document.body.scrollHeight;})()") or webView.ScrollView.ContentSize.Height.
EDIT 2 :
Here a little sample to help understand the problem. I got two buttons and my custom webview (initialize with a 40 HeightRequest empty html).
The first button set the Source of the webview to a 70px long HTML. The second one set the Source to a 280px long HTML.
In this example, I click on the first button than the second one and finaly back on the first button again. You see the webview getting bigger on the first 2 clicks. But then then webview should get shrunk when I choose back the first html (passing from 280px to 70px) but it keeps the 280px long.
First button (70px long)
Second button (280px long)
Back to the first button (should be 70px long instead of 280px).
The problem occured on both simulator and iOS device.
You can change the Frame of webView to change the height.
public override async void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
try
{
var _webView = webViewRenderer.Element as WebView;
if (_webView != null)
{
webView.Frame = new CGRect(webView.Frame.Location, new CGSize(webView.Frame.Size.Width, 0));
var a = webView.ScrollView.ContentSize.Height;
var b = await _webView.EvaluateJavaScriptAsync("(function(){return document.body.scrollHeight;})()");
Console.WriteLine(a);
Console.WriteLine(b);
CGRect tempRect = webView.Frame;
// for test
webView.Frame = new CGRect(tempRect.Location.X, tempRect.Location.Y, tempRect.Size.Width, float.Parse(b));
}
}
catch (Exception ex)
{
//Log the Exception
}
}
I had the same problem and the CustomNavigationDelegate would also only show the same large size, which was the Device Display size.
I found that I had set this on the init part of the XAML and code behind, which somehow overrides the later content-based calculation.
See my fix here: https://stackoverflow.com/a/62409859/3443564

JavaFX change the image in an imageView

Basically I have a method to load an Image from database into an imageView and a second method to change the image I'm sucessfully running both methods without getting an exception but after the setImage in changeImage() method what do I need to update and how (scene,stage) is it possible at all. I know that there is no method like repaint() in swing in javafx, so how do I approach this ?
public class MainMenuController implements Initializable {
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
private AnchorPane stck1;
#FXML
private AnchorPane openSecondWindow(ActionEvent event) throws Exception {
GUIController ctrl = new GUIController();
Stage stage = new Stage();
setStck1((AnchorPane) FXMLLoader.load(InteractiveFictionGame2.class.getResource("GUI.fxml")));
ImageView img_1 = new ImageView(ctrl.loadImg().getImage());
img_1.setPreserveRatio(true);
img_1.setSmooth(true);
img_1.setCache(true);
getStck1().getChildren().add(img_1);
Scene scene = new Scene(getStck1());
stage.setTitle("Interactive Fiction Game");
stage.setScene(scene);
stage.setFullScreen(true);
// stage.sizeToScene();
stage.show();
return getStck1();
}
public class GUIController implements Initializable {
#FXML
private TabPane tb1;
/**
* Initializes the controller class.
*
* #param url
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
#FXML
private ImageView img_1;
public ImageView loadImg() {
try {
con = DriverManager.getConnection(host, unm, pswrd);
stmnt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
rs = stmnt.executeQuery(SQL);
rs.next();
fis = rs.getBinaryStream(4);
imgt = javax.imageio.ImageIO.read(fis);
Image newImg = SwingFXUtils.toFXImage(imgt, null);
img_1 = new ImageView();
img_1.setImage(newImg);
rs.close();
stmnt.close();
con.close();
} catch (Exception e) {
System.out.println("Not working");
}
return img_1;
}
public void changeImage() {
..
fis = rs.getBinaryStream(1);
imgt = javax.imageio.ImageIO.read(fis);
Image newImg = SwingFXUtils.toFXImage(imgt, null);
img_1.setImage(newImg);
...
} catch (Exception e) {
System.out.println("Not working");
}
return img_1;
}
Your Issue
If you have a member node in your controller which you inject using #FXML, you should never create a new object instance using a new constructor and assign that new object to your existing reference. Instead just use the object which FXML created for you.
You have:
#FXML
private ImageView img_1;
That's fine.
Then in loadImg, you have:
img_1 = new ImageView();
img_1.setImage(newImg);
That is bad.
You already have an ImageView which the FXMLLoader created for you when you loaded your FXML document. The FXML Loader then assigned that ImageView to your img_1 reference because you used an #FXML annotation.
How to Fix it
So all you need to do is to stop creating new ImageViews and only write:
img_1.setImage(newImg);
And you are done.
Why it works
The Image property of ImageView is an observable property. The JavaFX system observes the Image property for any changes and if it changes, automatically updates the image displayed on the screen for the ImageView. You don't need to perform any repaint call (there is no such repaint routine to call in any case).
Background Reading
If you want to understand the JavaFX scene graph architecture better, read the Oracle tutorial on it:
Working with the JavaFX Scene Graph.
Some Tips
You can create a JavaFX image directly from an InputStream, you don't need to use ImageIO and SwingFXUtils for this task.
You can use a Task to communicate with a database and your application may be more responsive.
It is probably simpler to read the image from a file or over http rather than from a database.
Disclaimer
Besides the issue pointed out here, there may be other errors in code you have not provided which may prevent you from getting your application to work as you wish.
Java I graduate here:
In my JavaFX term project I had to update an imageView object upon a setOnAction Event (clicking a button). This allowed the program user to click through a series of pictures.
The following worked great:
First create your Image and ImageView instances:
Image imageObject = new Image();
ImageView imageViewObject = new ImageView();
Then down in the code a button event causes the (next) image to be assigned and updated as follows:
btn.setOnAction(e -> {
imageIndex++;
imageFilename = imageNamesArray.get(imageIndex);
imageObject = new Image(imageFilename);
imageViewObject.setImage(imageObject);
}
Note: The filename(s) in my project are jpg file (names) saved as String elements in an ArrayList(). The button click also increments the array index to the next jpg filename (and path or URL) and the new image would appear.
So as in the aforementioned answer you only create one ImageViewObject but you do reassign a new image to the image object "imageObject" each time.

JavaFX 2.2: Hooking Slider Drag n Drop Events

I am trying to catch the events on the JavaFX Slider especially the one which indicates that the drag stopped and was released. At first I used the valueProperty with mock-up code like this
slider.valueProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> ov, Number oldValue, Number newValue) {
log.fine(newValue.toString());
}
});
but with this it update too often. So I searched within SceneBuilder and the API and found some interessting like
slider.setOnMouseDragReleased(new EventHandler<MouseDragEvent>() {
#Override
public void handle(MouseDragEvent event) {
System.out.println("setOnMouseDragReleased");
}
});
but they never get fired. There only some like setOnMouseReleased I get some output, but this for example count for the whole Node like the labels etc.
So my question is, which is the correct hook to know the value is not changing anymore (if possible after release of the mouse like drag'n'drop gesture) and maybe with a small example to see its interfaces working.
Add a change listener to the slider's valueChangingProperty to know when the slider's value is changing, and take whatever action you want on the value change.
The sample below will log the slider's value when it starts to change and again when it finishes changing.
import javafx.application.Application;
import javafx.beans.value.*;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class SliderChangeLog extends Application {
private final ListView<String> startLog = new ListView<>();
private final ListView<String> endLog = new ListView<>();
#Override public void start(Stage stage) throws Exception {
Pane logsPane = createLogsPane();
Slider slider = createMonitoredSlider();
VBox layout = new VBox(10);
layout.setAlignment(Pos.CENTER);
layout.setPadding(new Insets(10));
layout.getChildren().setAll(
slider,
logsPane
);
VBox.setVgrow(logsPane, Priority.ALWAYS);
stage.setTitle("Slider Value Change Logger");
stage.setScene(new Scene(layout));
stage.show();
}
private Slider createMonitoredSlider() {
final Slider slider = new Slider(0, 1, 0.5);
slider.setMajorTickUnit(0.5);
slider.setMinorTickCount(0);
slider.setShowTickMarks(true);
slider.setShowTickLabels(true);
slider.setMinHeight(Slider.USE_PREF_SIZE);
slider.valueChangingProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(
ObservableValue<? extends Boolean> observableValue,
Boolean wasChanging,
Boolean changing) {
String valueString = String.format("%1$.3f", slider.getValue());
if (changing) {
startLog.getItems().add(
valueString
);
} else {
endLog.getItems().add(
valueString
);
}
}
});
return slider;
}
private HBox createLogsPane() {
HBox logs = new HBox(10);
logs.getChildren().addAll(
createLabeledLog("Start", startLog),
createLabeledLog("End", endLog)
);
return logs;
}
public Pane createLabeledLog(String logName, ListView<String> log) {
Label label = new Label(logName);
label.setLabelFor(log);
VBox logPane = new VBox(5);
logPane.getChildren().setAll(
label,
log
);
logPane.setAlignment(Pos.TOP_LEFT);
return logPane;
}
public static void main(String[] args) { launch(args); }
}
There could be times when you want to know when the user is moving the slider versus the slider value changing due to a binding to a property. One example is a slider that is used on a media player view to show the media timeline. The slider not only displays the time but also allows the user to fast forward or rewind. The slider is bound to the media player's current time which fires the change value on the slider. If the user moves the slider, you may want to detect the drag so as to stop the media player, have the media player seek to the new time and resume playing. Unfortunately the only drag event that seems to fire on the slider is the setOnDragDetected event. So I used the following two methods to check for a slider drag.
slider.setOnDragDetected(new EventHandler<Event>() {
#Override
public void handle(Event event) {
currentPlayer.pause();
isDragged=true;
}
});
slider.setOnMouseReleased(new EventHandler<Event>() {
#Override
public void handle(Event event) {
if(isDragged){
currentPlayer.seek(Duration.seconds((double) slider.getValue()));
currentPlayer.play();
isDragged=false;
}
}
});
jewelsea's answer was very helpful for setting me on the right track, however if "snapToTicks" is on, undesired behavior results. The "end" value as captured by jewelsea's listener is before the snap takes place, and the post-snap value is never captured.
My solution sets a listener on value but uses valueChanging as a sentinel. Something like:
slider.valueProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(
ObservableValue<? extends Number> observableValue,
Number previous,
Number now) {
if (!slider.isValueChanging()
|| now.doubleValue() == slider.getMax()
|| now.doubleValue() == slider.getMin()) {
// This only fires when we're done
// or when the slider is dragged to its max/min.
}
}
});
I found that checking for the max and min value was necessary to catch the corner case where the user drags the slider all the way past its left or right bounds before letting go of the mouse. For some reason, that doesn't fire an event like I'd expect, so this seems like an okay work-around.
Note: Unlike jewelsea, I'm ignoring the starting value for the sake of simplicity.
Note 2: I'm actually using ScalaFX 2, so I'm not sure if this Java translation compiles as-written.

Javafx textfield resize to text length?

Hello guys I am building a chat server where I use a textfield on the screen to type in the chat message that the user writes, the idea is that it works like a bubble over a persons head when he types a message.
my question is in order to not make a textbox that is too large or too small is there a way to make the textbox resize (trim if you will) so it adjust to the text written in the textfield?
P.S. I'm using JavaFx scenebuilder to do all of this.
You can use computeTextWidth method in the com.sun.javafx.scene.control.skin.Utils. the method is used in javafx.scene.control.Label class to calculate the minimum width for label content.
I solved my problem as below:
field.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> ob, String o,
String n) {
// expand the textfield
field.setPrefWidth(TextUtils.computeTextWidth(field.getFont(),
field.getText(), 0.0D) + 10);
}
});
I have added a listener to textProperty, and with every text change i change the prefWidth of textfield.
Note: as long as the Utils.computeTextWidth() is not public, I have copied the source code to a new class (TextUtils).
Here is the full source code:
package me.jone30rw.fxcontrol;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextBoundsType;
public class TextUtils {
static final Text helper;
static final double DEFAULT_WRAPPING_WIDTH;
static final double DEFAULT_LINE_SPACING;
static final String DEFAULT_TEXT;
static final TextBoundsType DEFAULT_BOUNDS_TYPE;
static {
helper = new Text();
DEFAULT_WRAPPING_WIDTH = helper.getWrappingWidth();
DEFAULT_LINE_SPACING = helper.getLineSpacing();
DEFAULT_TEXT = helper.getText();
DEFAULT_BOUNDS_TYPE = helper.getBoundsType();
}
public static double computeTextWidth(Font font, String text, double help0) {
// Toolkit.getToolkit().getFontLoader().computeStringWidth(field.getText(),
// field.getFont());
helper.setText(text);
helper.setFont(font);
helper.setWrappingWidth(0.0D);
helper.setLineSpacing(0.0D);
double d = Math.min(helper.prefWidth(-1.0D), help0);
helper.setWrappingWidth((int) Math.ceil(d));
d = Math.ceil(helper.getLayoutBounds().getWidth());
helper.setWrappingWidth(DEFAULT_WRAPPING_WIDTH);
helper.setLineSpacing(DEFAULT_LINE_SPACING);
helper.setText(DEFAULT_TEXT);
return d;
}
}
In JavaFX 8, there is a solution for that, here is the code:
TextField tf = new TextField();
// Set Max and Min Width to PREF_SIZE so that the TextField is always PREF
tf.setMinWidth(Region.USE_PREF_SIZE);
tf.setMaxWidth(Region.USE_PREF_SIZE);
tf.textProperty().addListener((ov, prevText, currText) -> {
// Do this in a Platform.runLater because of Textfield has no padding at first time and so on
Platform.runLater(() -> {
Text text = new Text(currText);
text.setFont(tf.getFont()); // Set the same font, so the size is the same
double width = text.getLayoutBounds().getWidth() // This big is the Text in the TextField
+ tf.getPadding().getLeft() + tf.getPadding().getRight() // Add the padding of the TextField
+ 2d; // Add some spacing
tf.setPrefWidth(width); // Set the width
tf.positionCaret(tf.getCaretPosition()); // If you remove this line, it flashes a little bit
});
});
tf.setText("Hello World!");
In JavaFX 2.2 this code works with little limitations. You can't set the Font(so if you do not use the std-font, you must set it manually).
You can't get the padding from a TextField(so if you know the padding, write it hardcoded).
Happy Coding,
Kalasch
Since JavaFX 8, this is by far the simplest:
textField.prefColumnCountProperty().bind(textField.textProperty().length());
It is time to do some coding behind the scenes(builder) :).
The following code chunk is not a neat solution but better than none. :)
// define width limits
textField.setMinWidth(50);
textField.setPrefWidth(50);
textField.setMaxWidth(400);
// add listner
textField.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
textField.setPrefWidth(textField.getText().length() * 7); // why 7? Totally trial number.
}
});
No font dependent magic required if you use setPrefColumnCount
tf.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> ob, String o, String n) {
tf.setPrefColumnCount(tf.getText().length() +1);
}
});
The best / easiest way to do this is to use JavaFX's "USE_COMPUTED_SIZE" option. You can either define it in the FXML, or programatically like this:
TextField textField = new TextField("hello");
textField.setPrefWidth(Control.USE_COMPUTED_SIZE);

Java - drawing many images with Graphics.drawImage() and 2-screen buffer strategy distorts and cuts images

I am using a loop to invoke double buffering painting. This, together with overriding my only Panel's repaint method, is designed to pass complete control of repaint to my loop and only render when it necessary (i.e. some change was made in the GUI).
This is my rendering routine:
Log.write("renderer painting");
setNeedsRendering(false);
Graphics g = frame.getBufferStrategy().getDrawGraphics();
g.setFont(font);
g.setColor(Color.BLACK);
g.fillRect(0, 0, window.getWidth(),window.getHeight());
if(frame != null)
window.paint(g);
g.dispose();
frame.getBufferStrategy().show();
As you can see, it is pretty standard. I get the grpahics object from the buffer strategy (initialized to 2), make it all black and pass it to the paint method of my "window" object.
After window is done using the graphics object, I dispose of it and invoke show on the buffer strategy to display the contents of the virtual buffer.
It is important to note that window passes the graphics object to many other children components the populate the window and each one, in turn, uses the same instance of the graphics object to draw something onto the screen: text, shapes, or images.
My problem begins to show when the system is running and a large image is rendered. The image appears to be cut into seveeal pieces and drawn again and again (3-4 times) with different offsets inside of where the image is supposed to be rendered. See my attached images:
This is the original image:
alt text http://img109.imageshack.us/img109/8308/controller.png
This is what I get:
alt text http://img258.imageshack.us/img258/3248/probv.png
Note that in the second picture, I am rendering shapes over the picture - these are always at the correct position.
Any idea why this is happening?
If I save the image to file, as it is in memory, right before the call to g.drawImage(...) it is identical to the original.
Uh, you are using Swing ?
Normally Swing automatically renders the image, you can't switch it off. The repaint()
method is out of bounds because Swing has a very complicated rendering routine due to
method compatibility for AWT widgets and several optimizations, inclusive drawing only
when necessary !
If you want to use the High-Speed Drawing API, you use a component with a BufferStrategy
like JFrame and Window, use
setIgnoreRepaint(false);
to switch off Swing rendering, set up a drawing loop and paint the content itself.
Or you can use JOGL for OpenGL rendering. The method you are using seems completely
at odds with correct Java2D usage.
Here the correct use:
public final class FastDraw extends JFrame {
private static final transient double NANO = 1.0e-9;
private BufferStrategy bs;
private BufferedImage frontImg;
private BufferedImage backImg;
private int PIC_WIDTH,
PIC_HEIGHT;
private Timer timer;
public FastDraw() {
timer = new Timer(true);
JMenu menu = new JMenu("Dummy");
menu.add(new JMenuItem("Display me !"));
menu.add(new JMenuItem("Display me, too !"));
JMenuBar menuBar = new JMenuBar();
menuBar.add(menu);
setJMenuBar(menuBar);
setIgnoreRepaint(true);
setVisible(true);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent evt) {
super.windowClosing(evt);
timer.cancel();
dispose();
System.exit(0);
}
});
try {
backImg = javax.imageio.ImageIO.read(new File("MyView"));
frontImg = javax.imageio.ImageIO.read(new File("MyView"));
}
catch (IOException e) {
System.out.println(e.getMessage());
}
PIC_WIDTH = backImg.getWidth();
PIC_HEIGHT = backImg.getHeight();
setSize(PIC_WIDTH, PIC_HEIGHT);
createBufferStrategy(1); // Double buffering
bs = getBufferStrategy();
timer.schedule(new Drawer(),0,20);
}
public static void main(String[] args) {
new FastDraw();
}
private class Drawer extends TimerTask {
private VolatileImage img;
private int count = 0;
private double time = 0;
public void run() {
long begin = System.nanoTime();
Graphics2D g = (Graphics2D) bs.getDrawGraphics();
GraphicsConfiguration gc = g.getDeviceConfiguration();
if (img == null)
img = gc.createCompatibleVolatileImage(PIC_WIDTH, PIC_HEIGHT);
Graphics2D g2 = img.createGraphics();
// Zeichenschleife
do {
int valStatus = img.validate(gc);
if (valStatus == VolatileImage.IMAGE_OK)
g2.drawImage(backImg,0,0,null);
else {
g.drawImage(frontImg, 0, 0, null);
}
// volatile image is ready
g.drawImage(img,0,50,null);
bs.show();
} while (img.contentsLost());
time = NANO*(System.nanoTime()-begin);
count++;
if (count % 100 == 0)
System.out.println(1.0/time);
}
}

Resources