How to draw image below boxes on a Vaadin canvas? - java

I currently have a canvas where I will first draw an image before all the other canvas mouse-draw functions come in (to draw the boxes). I also have an undo function which will remove the last drawn box, clear the entire canvas and redraw all the remaining boxes back onto the canvas. However, after undoing, the image will somehow appear on top of the boxes and covering them, instead of below as it should be. I was able to fix this using z-index in HTML5 previously, but do not know how to do so using the Java way.
This is my Canvas.java (undo method is towards the end):
package com.vaadin.starter.beveragebuddy.ui.components;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.HasSize;
import com.vaadin.flow.component.HasStyle;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.dom.Element;
import com.vaadin.flow.dom.ElementFactory;
import com.vaadin.flow.shared.Registration;
import com.vaadin.starter.beveragebuddy.backend.MainLayout;
import elemental.json.JsonObject;
import java.util.ArrayList;
import java.util.List;
/**
* Canvas component that you can draw shapes and images on. It's a Java wrapper
* for the
* <a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API">HTML5
* canvas</a>.
* <p>
* Use {#link #getContext()} to get API for rendering shapes and images on the
* canvas.
* <p>
*/
#Tag("canvas")
#SuppressWarnings("serial")
public class Canvas extends Component implements HasStyle, HasSize {
private static CanvasRenderingContext2D context;
private Element element;
private boolean mouseSelect = false;
private boolean mouseIsDown = false;
private double endX;
private double endY;
public static int boxCount = 0;
public static boolean undoCalled = false;
public static ArrayList <BoundingBox> arrayBoxes = new ArrayList<BoundingBox>();
public static ArrayList <MousePosition> mousePosArray = new ArrayList<MousePosition>();
public static ArrayList <SelectBox> selectBoxes = new ArrayList<SelectBox>();
private List<Runnable> mouseMoveListeners = new ArrayList<>(0);
public static ArrayList<BoundingBox> getArrayBoxes() {
return arrayBoxes;
}
public static ArrayList<MousePosition> getMousePosArray() {
return mousePosArray;
}
public static void setMousePosArray(ArrayList<MousePosition> mousePosArray) {
Canvas.mousePosArray = mousePosArray;
}
/**
* Creates a new canvas component with the given size.
* <p>
* Use the API provided by {#link #getContext()} to render graphics on the
* canvas.
* <p>
* The width and height parameters will be used for the canvas' coordinate
* system. They will determine the size of the component in pixels, unless
* you explicitly set the component's size with {#link #setWidth(String)} or
* {#link #setHeight(String)}.
*
// * #param width
// * the width of the canvas
// * #param height
// * the height of the canvas
// */
public Registration addMouseMoveListener(Runnable listener) {
mouseMoveListeners.add(listener);
return () -> mouseMoveListeners.remove(listener);
}
public Canvas(int width, int height) {
context = new CanvasRenderingContext2D(this);
context.drawImage("https://freedesignfile.com/upload/2016/10/Red-Clouds-and-Prairie-Background.jpg", 0, 0);
element = getElement();
element.getStyle().set("border", "1px solid");
getElement().setAttribute("width", String.valueOf(width));
getElement().setAttribute("height", String.valueOf(height));
element.addEventListener("mousedown", event -> { // Retrieve Starting Position on MouseDown
Element boundingBoxResult = ElementFactory.createDiv();
element.appendChild(boundingBoxResult);
JsonObject evtData = event.getEventData();
double xBox = evtData.getNumber("event.x");
double yBox = evtData.getNumber("event.y");
boundingBoxResult.setAttribute("data-x", String.format("%f", xBox));
boundingBoxResult.setAttribute("data-y", String.format("%f", yBox));
BoundingBox newBox = new BoundingBox("","", xBox, yBox, 0.0, 0.0);
arrayBoxes.add(newBox);
SelectBox select = new SelectBox(xBox, 0.0, yBox, 0.0);
selectBoxes.add(0, select);
mouseIsDown=true;
mouseMoveListeners.forEach(Runnable::run);
}).addEventData("event.x").addEventData("event.y");
element.addEventListener("mouseup", event -> { // Draw Box on MouseUp
Element boundingBoxResult2 = ElementFactory.createDiv();
element.appendChild(boundingBoxResult2);
JsonObject evtData2 = event.getEventData();
endX = evtData2.getNumber("event.x");
endY = evtData2.getNumber("event.y");
boundingBoxResult2.setAttribute("end-x", String.format("%f", endX));
boundingBoxResult2.setAttribute("end-y", String.format("%f", endY));
// System.out.println(endX);
// System.out.println(endY);
double xcoordi = 0;
double ycoordi = 0;
double boxWidth = 0;
double boxHeight = 0;
// for (int i = 0; i < arrayBoxes.size(); i++) {
System.out.println(endX);
System.out.println(endY);
arrayBoxes.get(boxCount).setWidth(endX, arrayBoxes.get(boxCount).xcoordi);
arrayBoxes.get(boxCount).setHeight(endY, arrayBoxes.get(boxCount).ycoordi);
xcoordi = arrayBoxes.get(boxCount).getXcoordi();
ycoordi = arrayBoxes.get(boxCount).getYcoordi();
boxWidth = arrayBoxes.get(boxCount).getWidth();
boxHeight = arrayBoxes.get(boxCount).getHeight();
boxCount++;
mouseIsDown=false;
context.beginPath();
context.setStrokeStyle("green");
context.setLineWidth(2);
context.strokeRect(xcoordi, ycoordi, boxWidth, boxHeight);
context.stroke();
context.fill();
SelectBox select = new SelectBox(endX, 0.0, endY, 0.0);
selectBoxes.add(1, select);
// if (selectBoxes.get(1).getSelectEndX() == selectBoxes.get(0).getSelectStartX()){
// mouseSelect = true;
// context.beginPath();
// context.setStrokeStyle("yellow");
// context.setLineWidth(2);
// context.strokeRect(arrayBoxes.get(i).xcoordi, arrayBoxes.get(i).ycoordi, arrayBoxes.get(i).boxWidth, arrayBoxes.get(i).boxHeight);
// context.fill();
// }
System.out.println(arrayBoxes.toString());
//
// for (int i = 0; i < arrayBoxes.size(); i++){
// if(arrayBoxes.get(i).xcoordi)
// if (endX > arrayBoxes.get(i).xcoordi){
// if (endX < arrayBoxes.get(i).endY)
// }
// }
mouseMoveListeners.forEach(Runnable::run);
}).addEventData("event.x").addEventData("event.y");
element.addEventListener("mousemove", event -> { // Retrieve Mouse Position when Moving
JsonObject mousePos = event.getEventData();
double mouseX = mousePos.getNumber("event.x");
double mouseY = mousePos.getNumber("event.y");
MousePosition currentPos = new MousePosition(mouseX, mouseY);
mousePosArray.add(0, currentPos);
setMousePosArray(mousePosArray);
mouseMoveListeners.forEach(Runnable::run);
}).addEventData("event.x").addEventData("event.y");
}
public static void undoLast() {
undoCalled = true;
if (arrayBoxes.size() > 0) {
arrayBoxes.remove(arrayBoxes.size() - 1);
}
System.out.println(arrayBoxes.toString());
System.out.println(arrayBoxes.size());
context.clearRect(0, 0, 1580, 700);
context.drawImage("https://freedesignfile.com/upload/2016/10/Red-Clouds-and-Prairie-Background.jpg", 0, 0);
for (int i = 0; i < arrayBoxes.size(); i++){
context.beginPath();
context.setStrokeStyle("green");
context.setLineWidth(2);
context.strokeRect(arrayBoxes.get(i).xcoordi, arrayBoxes.get(i).ycoordi, arrayBoxes.get(i).boxWidth, arrayBoxes.get(i).boxHeight);
context.fill();
}
boxCount--;
System.out.println("Box Count: " + boxCount);
}
/**
* Gets the context for rendering shapes and images in the canvas.
* <p>
* It is a Java wrapper for the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D">same
* client-side API</a>.
*
* #return the 2D rendering context of this canvas
*/
public CanvasRenderingContext2D getContext() {
return context;
}
/**
* {#inheritDoc}
* <p>
* <b>NOTE:</b> Canvas has an internal coordinate system that it uses for
* drawing, and it uses the width and height provided in the constructor.
* This coordinate system is independent of the component's size. Changing
* the component's size with this method may scale/stretch the rendered
* graphics.
*/
#Override
public void setWidth(String width) {
HasSize.super.setWidth(width);
}
/**
* {#inheritDoc}
* <p>
* <b>NOTE:</b> Canvas has an internal coordinate system that it uses for
* drawing, and it uses the width and height provided in the constructor.
* This coordinate system is independent of the component's size. Changing
* the component's size with this method may scale/stretch the rendered
* graphics.
*/
#Override
public void setHeight(String height) {
HasSize.super.setHeight(height);
}
/**
* {#inheritDoc}
* <p>
* <b>NOTE:</b> Canvas has an internal coordinate system that it uses for
* drawing, and it uses the width and height provided in the constructor.
* This coordinate system is independent of the component's size. Changing
* the component's size with this method may scale/stretch the rendered
* graphics.
*/
#Override
public void setSizeFull() {
HasSize.super.setSizeFull();
}
public void addComponent(Label label) {
}
}
Any help is much appreciated, thank you!

I believe your issue is due to one or both of these issues
When drawing the image, you correctly wait for it to load before actually drawing it to the canvas (image.onload = ...). This means that your code might start loading the image, then it draws all the boxes, and then the image loads so that it is drawn on top.
You run the image drawing script on beforeClientResponse, which means it might get called after all the code for drawing the boxes is called.
The simplest solution, if you always want to have an image as the background, is to use two canvases on top of each other (with absolute positioning, for example). This way, you can always draw the images to the background canvas, and all boxes to the foreground canvas. A bonus of this is that you don't have to redraw the background even if you clear the foreground canvas.

Related

Create Rectangle class that is almost identical to the Circle class. Use Rect as the class name. JAVA, i think my error is coming from public boolean

Create a Rectangle class that is almost identical to the Circle class. Use Rect as the class name. The easiest way to write this class will be to copy all the code from Circle.java into Rect.java, then change the class name, instance variables, and statements. My rect code only opens a blank window when I use the shapemover. Please help me identify what I am doing wrong.
Here's the circle class code:
package lab08;
import java.awt.Graphics;
import java.awt.Color;
/**
* Circle objects represent a circle shape
* drawn to the screen at a particular position
* with some size and color.
*
* #author Peter Jensen, Donat Mouele
* #version Fall 2022
*/
public class Circle extends Shape
{
// Instance variables.
private int x, y;
private Color color;
private int radius;
/**
* Constructor - initializes the position, diameter, and
* color of this circle object.
*
* #param x
* the x coordinate of this object's position
*
* #param y
* the x coordinate of this object's position
*
* #param diameter
* the diameter of this circle
*
* #param color
* the color of this circle
*/
public Circle (int x, int y, int diameter, Color color)
{
this.x = x;
this.y = y;
this.color = color;
this.radius = diameter / 2;
}
/**
* Changes the position of this shape by
* the specified amount. Note that this does
* not set the position absolutely, the deltas
* specify how far to move the shape from its
* current position.
*
* #param deltaX
* how far to move the shape horizontally
*
* #param deltaY
* how far to move the shape vertically
*/
public void move (int deltaX, int deltaY)
{
x = x + deltaX;
y = y + deltaY;
}
/**
* Draws the circle at it's current position and color
* to the specified graphics object.
*
* #param g
* the graphics object (where to draw to)
*/
public void draw (Graphics g)
{
g.setColor (color);
g.fillOval (x, y, radius * 2, radius * 2);
}
/**
* Returns true if the coordinates are within the circle.
*
* #param targetX
* an x coordinate
*
* #param targetY
* a y coordinate
*
* #return
* true if the coordinates are within the shape
*/
public boolean isInside (int targetX, int targetY)
{
int cx = x + radius; // Calculate the center point
int cy = y + radius;
int deltaX = cx - targetX; // Calculate the deltas to the click
int deltaY = cy - targetY;
// Make sure the distance from the click to the center is less
// than the radius. (Notice how I avoid a square root.)
return (deltaX * deltaX + deltaY * deltaY) <= radius * radius;
}
}
Here's the Rectangle Class Code that I have
package lab08;
import java.awt.Graphics;
import java.awt.Color;
/**
* Circle objects represent a circle shape
* drawn to the screen at a particular position
* with some size and color.
*
* #author Peter Jensen, Donat Mouele
* #version Fall 2022
*/
public class Rect extends Shape
{
// Instance variables.
private int x, y;
private Color color;
private int height;
private int width;
/**
* Constructor - initializes the position, diameter, and
* color of this circle object.
*
* #param x
* the x coordinate of this object's position
*
* #param y
* the x coordinate of this object's position
*
* #param diameter
* the diameter of this circle
*
* #param color
* the color of this circle
*/
public Rect (int x, int y, int perimeter, Color color)
{
this.x = x;
this.y = y;
this.color = color;
this.height = perimeter / 2 - width;
this.width = perimeter/2-height;
}
/**
* Changes the position of this shape by
* the specified amount. Note that this does
* not set the position absolutely, the deltas
* specify how far to move the shape from its
* current position.
*
* #param deltaX
* how far to move the shape horizontally
*
* #param deltaY
* how far to move the shape vertically
*/
public void move (int deltaX, int deltaY)
{
x = x + deltaX;
y = y + deltaY;
}
/**
* Draws the circle at it's current position and color
* to the specified graphics object.
*
* #param g
* the graphics object (where to draw to)
*/
public void draw (Graphics g)
{
g.setColor (color);
g.fillOval (x, y, width, height);
}
/**
* Returns true if the coordinates are within the circle.
*
* #param targetX
* an x coordinate
*
* #param targetY
* a y coordinate
*
* #return
* true if the coordinates are within the shape
*/
public boolean isInside (int targetX, int targetY)
{
int cx = x + width; // Calculate the center point
int cy = y + height;
int deltaX = cx - targetX; // Calculate the deltas to the click
int deltaY = cy - targetY;
return targetX >= x &&
targetX < x + width &&
targetY >= y &&
targetY < y + height;
}
}
Here's the shape mover
package lab08;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/**
* This class represents and application/JPanel that
* draws a few shapes for the user and allows the
* user to drag the shapes around with the mouse.
*
* #author Peter Jensen
* #version Fall 2022
*/
public class ShapeMover extends JPanel implements MouseListener,
MouseMotionListener,
Runnable
{
// Instance variables.
Rect[] shapes;
int lastX, lastY;
Rect current;
/**
* Initialize this (our JPanel). Create the shapes, and
* register this object as a listener to mouse events
* produced by this object.
*/
public ShapeMover ()
{
// Make the shapes.
shapes = new Rect[]
{
new Rect (130, 40, 60, Color.RED),
new Rect (230, 40, 20, Color.BLUE),
new Rect (330, 40, 80, Color.GREEN.darker()),
new Rect (130, 140, 100, new Color (0.8f, 0.6f, 0.2f)),
new Rect ( 30, 140, 40, Color.YELLOW)
};
// Set the size of this panel.
Dimension d = new Dimension (600, 600);
this.setMinimumSize(d);
this.setPreferredSize(d);
this.setMaximumSize(d);
// Register this object as a listener to its own events.
this.addMouseListener (this);
this.addMouseMotionListener (this);
}
/**
* Draws the shapes at their current locations
* and colors.
*
* #param g
* the Graphics object to draw to
*/
public void paint (Graphics g)
{
// Clear the background to a nice light blue color.
g.setColor(new Color (0.8f, 0.8f, 1.0f));
g.fillRect(0, 0, this.getWidth(), this.getHeight());
// Draw all of the shapes.
for (Rect s : shapes)
s.draw(g);
}
/**
* This method is part of the MouseListener interface.
* Because we registered this application object as a listener
* to its own mouse events, this method will be automatically
* called whenever the mouse button is pressed down.
*
* In this method, we determine if the mouse click occurred
* in any of our shapes. If so, we record that shape object
* as the current shape. This has the effect of selecting
* a shape to drag around.
*
* #param e
* the mouse event
*/
public void mousePressed (MouseEvent e)
{
// Get the location of the mouse click within this window.
int x = e.getX ();
int y = e.getY ();
// Save it for later use.
lastX = x;
lastY = y;
// Determine if the mouse click is within any shape.
// If so, save the shape as the current shape.
for (Rect s : shapes)
if (s.isInside (x, y))
current = s;
}
/**
* This method is part of the MouseListener interface.
* Because we registered this applet object as a listener
* to its own mouse events, this method will be automatically
* called whenever the mouse button is let down.
*
* In this method, we mark the current shape as null. This
* has the effect of dropping whatever shape we are dragging
* around.
*
* #param e
* the mouse event
*/
public void mouseReleased (MouseEvent e)
{
current = null;
}
/**
* This method is part of the MouseMotionListener interface.
* Because we registered this applet object as a listener
* to its own mouse events, this method will be automatically
* called whenever the mouse is moved with the button pressed down.
*
* In this method, we adjust the position of the shape the user
* is dragging around.
*
* #param e
* the mouse event
*/
public void mouseDragged (MouseEvent e)
{
// Compute how far the mouse moved since the last event.
int x = e.getX ();
int y = e.getY ();
int deltaX = x - lastX;
int deltaY = y - lastY;
// Save the current mouse position.
lastX = x;
lastY = y;
// If the user is dragging around a shape, move it by
// the same amount that the mouse moved.
if (current != null)
{
current.move (deltaX, deltaY);
repaint ();
}
}
// Unused event methods (required by the interfaces).
public void mouseClicked (MouseEvent e) { }
public void mouseEntered (MouseEvent e) { }
public void mouseExited (MouseEvent e) { }
public void mouseMoved (MouseEvent e) { }
/* Above this point are the methods and variables that we use in the JPanel */
/* Below this point are the methods and variables that launch the application */
/* I violated separation of concerns. The JPanel and Application classes
* are merged, and 'main' is below. This works for simple code (like this
* lab), but it is not a good idea for larger projects.
*/
/**
* The application entry point.
*
* #param args unused
*/
public static void main (String[] args)
{
// Main runs in the 'main' execution thread, and the GUI
// needs to be built by the GUI execution thread.
// Ask the GUI thread to run our 'run' method (at some
// later time).
SwingUtilities.invokeLater(new ShapeMover());
// Done. Let the main thread of execution finish. All the
// remaining work will be done by the GUI thread.
}
/**
* Builds the GUI for this application. This method must
* only be called/executed by the GUI thread.
*/
public void run ()
{
JFrame f = new JFrame("Shape Mover 2021");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 'this' is our Runnable object, but it is also our JPanel.
f.setContentPane(this);
f.pack();
f.setLocationRelativeTo(null); // Centers window
f.setVisible(true);
}
}

How can I use a JButton in a JPanel border?

I need to create a custom border for a JPanel that contains a JButton in the top left corner of the border (like the title in a titled border). Is there any way to do this in Java?
ComponentBorder might fit the bill. I successfully used it in the past, with minor tweaking, to make a TitledBorder with a CheckBox.
(copied from the website):
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
/**
* The ComponentBorder class allows you to place a real component in
* the space reserved for painting the Border of a component.
*
* This class takes advantage of the knowledge that all Swing components are
* also Containers. By default the layout manager is null, so we should be
* able to place a child component anywhere in the parent component. In order
* to prevent the child component from painting over top of the parent
* component a Border is added to the parent componet such that the insets of
* the Border will reserve space for the child component to be painted without
* affecting the parent component.
*/
public class ComponentBorder implements Border
{
public enum Edge
{
TOP,
LEFT,
BOTTOM,
RIGHT;
}
public static final float LEADING = 0.0f;
public static final float CENTER = 0.5f;
public static final float TRAILING = 1.0f;
private JComponent parent;
private JComponent component;
private Edge edge;
private float alignment;
private int gap = 5;
private boolean adjustInsets = true;
private Insets borderInsets = new Insets(0, 0, 0, 0);
/**
* Convenience constructor that uses the default edge (Edge.RIGHT) and
* alignment (CENTER).
*
* #param component the component to be added in the Border area
*/
public ComponentBorder(JComponent component)
{
this(component, Edge.RIGHT);
}
/**
* Convenience constructor that uses the default alignment (CENTER).
*
* #param component the component to be added in the Border area
* #param edge a valid Edge enum of TOP, LEFT, BOTTOM, RIGHT
*/
public ComponentBorder(JComponent component, Edge edge)
{
this(component, edge, CENTER);
}
/**
* Main constructor to create a ComponentBorder.
*
* #param component the component to be added in the Border area
* #param edge a valid Edge enum of TOP, LEFT, BOTTOM, RIGHT
* #param alignment the alignment of the component along the
* specified Edge. Must be in the range 0 - 1.0.
*/
public ComponentBorder(JComponent component, Edge edge, float alignment )
{
this.component = component;
component.setSize( component.getPreferredSize() );
component.setCursor(Cursor.getDefaultCursor());
setEdge( edge );
setAlignment( alignment );
}
public boolean isAdjustInsets()
{
return adjustInsets;
}
public void setAdjustInsets(boolean adjustInsets)
{
this.adjustInsets = adjustInsets;
}
/**
* Get the component alignment along the Border Edge
*
* #return the alignment
*/
public float getAlignment()
{
return alignment;
}
/**
* Set the component alignment along the Border Edge
*
* #param alignment a value in the range 0 - 1.0. Standard values would be
* CENTER (default), LEFT and RIGHT.
*/
public void setAlignment(float alignment)
{
this.alignment = alignment > 1.0f ? 1.0f : alignment < 0.0f ? 0.0f : alignment;
}
/**
* Get the Edge the component is positioned along
*
* #return the Edge
*/
public Edge getEdge()
{
return edge;
}
/**
* Set the Edge the component is positioned along
*
* #param edge the Edge the component is position on.
*/
public void setEdge(Edge edge)
{
this.edge = edge;
}
/**
* Get the gap between the border component and the parent component
*
* #return the gap in pixels.
*/
public int getGap()
{
return gap;
}
/**
* Set the gap between the border component and the parent component
*
* #param gap the gap in pixels (default is 5)
*/
public void setGap(int gap)
{
this.gap = gap;
}
//
// Implement the Border interface
//
public Insets getBorderInsets(Component c)
{
return borderInsets;
}
public boolean isBorderOpaque()
{
return false;
}
/**
* In this case a real component is to be painted. Setting the location
* of the component will cause it to be painted at that location.
*/
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height)
{
float x2 = (width - component.getWidth()) * component.getAlignmentX() + x;
float y2 = (height - component.getHeight()) * component.getAlignmentY() + y;
component.setLocation((int)x2, (int)y2);
}
/*
* Install this Border on the specified component by replacing the
* existing Border with a CompoundBorder containing the original Border
* and our ComponentBorder
*
* This method should only be invoked once all the properties of this
* class have been set. Installing the Border more than once will cause
* unpredictable results.
*/
public void install(JComponent parent)
{
this.parent = parent;
determineInsetsAndAlignment();
// Add this Border to the parent
Border current = parent.getBorder();
if (current == null)
{
parent.setBorder(this);
}
else
{
CompoundBorder compound = new CompoundBorder(current, this);
parent.setBorder(compound);
}
// Add component to the parent
parent.add(component);
}
/**
* The insets need to be determined so they are included in the preferred
* size of the component the Border is attached to.
*
* The alignment of the component is determined here so it doesn't need
* to be recalculated every time the Border is painted.
*/
private void determineInsetsAndAlignment()
{
borderInsets = new Insets(0, 0, 0, 0);
// The insets will only be updated for the edge the component will be
// diplayed on.
//
// The X, Y alignment of the component is controlled by both the edge
// and alignment parameters
if (edge == Edge.TOP)
{
borderInsets.top = component.getPreferredSize().height + gap;
component.setAlignmentX(alignment);
component.setAlignmentY(0.0f);
}
else if (edge == Edge.BOTTOM)
{
borderInsets.bottom = component.getPreferredSize().height + gap;
component.setAlignmentX(alignment);
component.setAlignmentY(1.0f);
}
else if (edge == Edge.LEFT)
{
borderInsets.left = component.getPreferredSize().width + gap;
component.setAlignmentX(0.0f);
component.setAlignmentY(alignment);
}
else if (edge == Edge.RIGHT)
{
borderInsets.right = component.getPreferredSize().width + gap;
component.setAlignmentX(1.0f);
component.setAlignmentY(alignment);
}
if (adjustInsets)
adjustBorderInsets();
}
/*
* The complimentary edges of the Border may need to be adjusted to allow
* the component to fit completely in the bounds of the parent component.
*/
private void adjustBorderInsets()
{
Insets parentInsets = parent.getInsets();
// May need to adust the height of the parent component to fit
// the component in the Border
if (edge == Edge.RIGHT || edge == Edge.LEFT)
{
int parentHeight = parent.getPreferredSize().height - parentInsets.top - parentInsets.bottom;
int diff = component.getHeight() - parentHeight;
if (diff > 0)
{
int topDiff = (int)(diff * alignment);
int bottomDiff = diff - topDiff;
borderInsets.top += topDiff;
borderInsets.bottom += bottomDiff;
}
}
// May need to adust the width of the parent component to fit
// the component in the Border
if (edge == Edge.TOP || edge == Edge.BOTTOM)
{
int parentWidth = parent.getPreferredSize().width - parentInsets.left - parentInsets.right;
int diff = component.getWidth() - parentWidth;
if (diff > 0)
{
int leftDiff = (int)(diff * alignment);
int rightDiff = diff - leftDiff;
borderInsets.left += leftDiff;
borderInsets.right += rightDiff;
}
}
}
}

RSyntaxTextArea not showing line numbers

I'm using RSyntaxTextArea for a minimized IDE I'm working on, Everything seems to be really working smoothly except for the line numbering, which I couldn't really make it show:
RSyntaxTextArea textArea = new RSyntaxTextArea(20, 60);
textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_C);
textArea.setCodeFoldingEnabled(true);
textArea.setAntiAliasingEnabled(true);
RTextScrollPane sp = new RTextScrollPane(textArea);
sp.setLineNumbersEnabled(true);
sp.setFoldIndicatorEnabled(true);
if ( sp.getLineNumbersEnabled() )
{
System.out.println("Enabled"); // it prints the line but it's not showing
}
contentPane.add(/*textEditorScrollPane*/ textArea, BorderLayout.CENTER);
I can't figure out why it's not showing the line numbers..
It's not showing the scrollbars either, right? Assuming that contentPane is where you want your components, you need to add the RTextScrollPane instance to the contentPane, not the RSyntaxTextArea instance. The Gutter, which displays line numbers, is a part of the RTextScrollPane - an extended JScrollPane.
If you don't add a scroll pane to your GUI, it will not be shown, nor will you be able to scroll around. :P
So try the following:
contentPane.add(sp, BorderLayout.CENTER);
Alternatively, you can use the following class:
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.MatteBorder;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.*;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
/**
* This class will display line numbers for a related text component. The text
* component must use the same line height for each line. TextLineNumber
* supports wrapped lines and will highlight the line number of the current
* line in the text component.
* <p>
* This class was designed to be used as a component added to the row header
* of a JScrollPane.
*/
public class TextLineNumber extends JPanel
implements CaretListener, DocumentListener, PropertyChangeListener
{
public final static float LEFT = 0.0f;
public final static float CENTER = 0.5f;
public final static float RIGHT = 1.0f;
private final static Border OUTER = new MatteBorder(0, 0, 0, 2, Color.GRAY);
private final static int HEIGHT = Integer.MAX_VALUE - 1000000;
// Text component this TextTextLineNumber component is in sync with
private JTextComponent component;
// Properties that can be changed
private boolean updateFont;
private int borderGap;
private Color currentLineForeground;
private float digitAlignment;
private int minimumDisplayDigits;
// Keep history information to reduce the number of times the component
// needs to be repainted
private int lastDigits;
private int lastHeight;
private int lastLine;
private HashMap<String, FontMetrics> fonts;
/**
* Create a line number component for a text component. This minimum
* display width will be based on 3 digits.
*
* #param component the related text component
*/
public TextLineNumber(JTextComponent component)
{
this(component, 3);
}
/**
* Create a line number component for a text component.
*
* #param component the related text component
* #param minimumDisplayDigits the number of digits used to calculate
* the minimum width of the component
*/
public TextLineNumber(JTextComponent component, int minimumDisplayDigits)
{
this.component = component;
setFont(component.getFont());
setBorderGap(5);
setCurrentLineForeground(Color.RED);
setDigitAlignment(RIGHT);
setMinimumDisplayDigits(minimumDisplayDigits);
component.getDocument().addDocumentListener(this);
component.addCaretListener(this);
component.addPropertyChangeListener("font", this);
}
/**
* Gets the update font property
*
* #return the update font property
*/
public boolean getUpdateFont()
{
return updateFont;
}
/**
* Set the update font property. Indicates whether this Font should be
* updated automatically when the Font of the related text component
* is changed.
*
* #param updateFont when true update the Font and repaint the line
* numbers, otherwise just repaint the line numbers.
*/
public void setUpdateFont(boolean updateFont)
{
this.updateFont = updateFont;
}
/**
* Gets the border gap
*
* #return the border gap in pixels
*/
public int getBorderGap()
{
return borderGap;
}
/**
* The border gap is used in calculating the left and right insets of the
* border. Default value is 5.
*
* #param borderGap the gap in pixels
*/
public void setBorderGap(int borderGap)
{
this.borderGap = borderGap;
Border inner = new EmptyBorder(0, borderGap, 0, borderGap);
setBorder(new CompoundBorder(OUTER, inner));
lastDigits = 0;
setPreferredWidth();
}
/**
* Gets the current line rendering Color
*
* #return the Color used to render the current line number
*/
public Color getCurrentLineForeground()
{
return currentLineForeground == null ? getForeground() : currentLineForeground;
}
/**
* The Color used to render the current line digits. Default is Coolor.RED.
*
* #param currentLineForeground the Color used to render the current line
*/
public void setCurrentLineForeground(Color currentLineForeground)
{
this.currentLineForeground = currentLineForeground;
}
/**
* Gets the digit alignment
*
* #return the alignment of the painted digits
*/
public float getDigitAlignment()
{
return digitAlignment;
}
/**
* Specify the horizontal alignment of the digits within the component.
* Common values would be:
* <ul>
* <li>TextLineNumber.LEFT
* <li>TextLineNumber.CENTER
* <li>TextLineNumber.RIGHT (default)
* </ul>
*
* #param currentLineForeground the Color used to render the current line
*/
public void setDigitAlignment(float digitAlignment)
{
this.digitAlignment =
digitAlignment > 1.0f ? 1.0f : digitAlignment < 0.0f ? -1.0f : digitAlignment;
}
/**
* Gets the minimum display digits
*
* #return the minimum display digits
*/
public int getMinimumDisplayDigits()
{
return minimumDisplayDigits;
}
/**
* Specify the mimimum number of digits used to calculate the preferred
* width of the component. Default is 3.
*
* #param minimumDisplayDigits the number digits used in the preferred
* width calculation
*/
public void setMinimumDisplayDigits(int minimumDisplayDigits)
{
this.minimumDisplayDigits = minimumDisplayDigits;
setPreferredWidth();
}
/**
* Calculate the width needed to display the maximum line number
*/
private void setPreferredWidth()
{
Element root = component.getDocument().getDefaultRootElement();
int lines = root.getElementCount();
int digits = Math.max(String.valueOf(lines).length(), minimumDisplayDigits);
// Update sizes when number of digits in the line number changes
if (lastDigits != digits)
{
lastDigits = digits;
FontMetrics fontMetrics = getFontMetrics(getFont());
int width = fontMetrics.charWidth('0') * digits;
Insets insets = getInsets();
int preferredWidth = insets.left + insets.right + width;
Dimension d = getPreferredSize();
d.setSize(preferredWidth, HEIGHT);
setPreferredSize(d);
setSize(d);
}
}
/**
* Draw the line numbers
*/
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
// Determine the width of the space available to draw the line number
FontMetrics fontMetrics = component.getFontMetrics(component.getFont());
Insets insets = getInsets();
int availableWidth = getSize().width - insets.left - insets.right;
// Determine the rows to draw within the clipped bounds.
Rectangle clip = g.getClipBounds();
int rowStartOffset = component.viewToModel(new Point(0, clip.y));
int endOffset = component.viewToModel(new Point(0, clip.y + clip.height));
while (rowStartOffset <= endOffset)
{
try
{
if (isCurrentLine(rowStartOffset))
{
g.setColor(getCurrentLineForeground());
} else
{
g.setColor(getForeground());
}
// Get the line number as a string and then determine the
// "X" and "Y" offsets for drawing the string.
String lineNumber = getTextLineNumber(rowStartOffset);
int stringWidth = fontMetrics.stringWidth(lineNumber);
int x = getOffsetX(availableWidth, stringWidth) + insets.left;
int y = getOffsetY(rowStartOffset, fontMetrics);
g.drawString(lineNumber, x, y);
// Move to the next row
rowStartOffset = Utilities.getRowEnd(component, rowStartOffset) + 1;
} catch (Exception e)
{
break;
}
}
}
/*
* We need to know if the caret is currently positioned on the line we
* are about to paint so the line number can be highlighted.
*/
private boolean isCurrentLine(int rowStartOffset)
{
int caretPosition = component.getCaretPosition();
Element root = component.getDocument().getDefaultRootElement();
if (root.getElementIndex(rowStartOffset) == root.getElementIndex(caretPosition))
{
return true;
} else
{
return false;
}
}
/*
* Get the line number to be drawn. The empty string will be returned
* when a line of text has wrapped.
*/
protected String getTextLineNumber(int rowStartOffset)
{
Element root = component.getDocument().getDefaultRootElement();
int index = root.getElementIndex(rowStartOffset);
Element line = root.getElement(index);
if (line.getStartOffset() == rowStartOffset)
{
return String.valueOf(index + 1);
} else
{
return "";
}
}
/*
* Determine the X offset to properly align the line number when drawn
*/
private int getOffsetX(int availableWidth, int stringWidth)
{
return (int) ((availableWidth - stringWidth) * digitAlignment);
}
/*
* Determine the Y offset for the current row
*/
private int getOffsetY(int rowStartOffset, FontMetrics fontMetrics)
throws BadLocationException
{
// Get the bounding rectangle of the row
Rectangle r = component.modelToView(rowStartOffset);
int lineHeight = fontMetrics.getHeight();
int y = r.y + r.height;
int descent = 0;
// The text needs to be positioned above the bottom of the bounding
// rectangle based on the descent of the font(s) contained on the row.
if (r.height == lineHeight) // default font is being used
{
descent = fontMetrics.getDescent();
} else // We need to check all the attributes for font changes
{
if (fonts == null)
{
fonts = new HashMap<>();
}
Element root = component.getDocument().getDefaultRootElement();
int index = root.getElementIndex(rowStartOffset);
Element line = root.getElement(index);
for (int i = 0; i < line.getElementCount(); i++)
{
Element child = line.getElement(i);
AttributeSet as = child.getAttributes();
String fontFamily = (String) as.getAttribute(StyleConstants.FontFamily);
Integer fontSize = (Integer) as.getAttribute(StyleConstants.FontSize);
String key = fontFamily + fontSize;
FontMetrics fm = fonts.get(key);
if (fm == null)
{
Font font = new Font(fontFamily, Font.PLAIN, fontSize);
fm = component.getFontMetrics(font);
fonts.put(key, fm);
}
descent = Math.max(descent, fm.getDescent());
}
}
return y - descent;
}
//
// Implement CaretListener interface
//
#Override
public void caretUpdate(CaretEvent e)
{
// Get the line the caret is positioned on
int caretPosition = component.getCaretPosition();
Element root = component.getDocument().getDefaultRootElement();
int currentLine = root.getElementIndex(caretPosition);
// Need to repaint so the correct line number can be highlighted
if (lastLine != currentLine)
{
repaint();
lastLine = currentLine;
}
}
//
// Implement DocumentListener interface
//
#Override
public void changedUpdate(DocumentEvent e)
{
documentChanged();
}
#Override
public void insertUpdate(DocumentEvent e)
{
documentChanged();
}
#Override
public void removeUpdate(DocumentEvent e)
{
documentChanged();
}
/*
* A document change may affect the number of displayed lines of text.
* Therefore the lines numbers will also change.
*/
private void documentChanged()
{
// View of the component has not been updated at the time
// the DocumentEvent is fired
SwingUtilities.invokeLater(() ->
{
try
{
int endPos = component.getDocument().getLength();
Rectangle rect = component.modelToView(endPos);
if (rect != null && rect.y != lastHeight)
{
setPreferredWidth();
repaint();
lastHeight = rect.y;
}
} catch (BadLocationException ex)
{ /* nothing to do */ }
});
}
//
// Implement PropertyChangeListener interface
//
#Override
public void propertyChange(PropertyChangeEvent evt)
{
if (evt.getNewValue() instanceof Font)
{
if (updateFont)
{
Font newFont = (Font) evt.getNewValue();
setFont(newFont);
lastDigits = 0;
setPreferredWidth();
} else
{
repaint();
}
}
}
}
Then line numbers can be added like this:
TextLineNumber textLineNumber = new TextLineNumber(sourceCodeArea);
sourceCodeAreaScrollPane.setRowHeaderView(textLineNumber);

according to this code how java know where to start

i am trying to figure out how Java know where to start according to this Graphics code when i run this code it showing ball moving from top to down but i can't understand why from top and why from this place and i know that java use math.random() to set the value but how it set the x and y
while when i trying to put any random number by my self it gives number that is bigger than the width it self (i am taking exactly about pos.x and pos.y)
this the first class
package movingball;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
/**
*
* #author isslam
*/
public class Ball {
private final int RADIUS = 10;
private final Point pos;
private final Color ballColor = Color.red;
private final int CHANGE = 3;
private final int height,
width;
public Ball(int frameWidth, int frameHight) {
width = frameWidth;
hight = frameHeight;
pos = new Point();
pos.x = (int)(Math.random() * (width - RADIUS)) + RADIUS;
pos.y = (int)(Math.random() * (height / 2 - RADIUS)) + RADIUS;
}
public void paint(Graphics g) {
g.setColor(ballColor);
g.fillOval(pos.x - RADIUS, pos.y - RADIUS, 2 * RADIUS, 2 * RADIUS);
}
public void move() {
if (pos.y < height - RADIUS) {
pos.translate(0, CHANGE);
}
}
}
this is the second class
package movingball;
import java.awt. * ;
import javax.swing. * ;
/**
*
* #author isslam
*/
public class ClassOfMoving extends JFrame {
protected final int FRAME_WIDTH = 240;
protected final int FRAME_HIGHT = 320;
private final Ball myBall = new Ball(FRAME_WIDTH, FRAME_HEIGHT);
public ClassOfMoving(String title) {
super(title);
setSize(FRAME_WIDTH, FRAME_HEIGHT);
setDefaultCloseOperation(ClassOfMoving.EXIT_ON_CLOSE);
}
public void paint(Graphics g) {
super.paint(g);
myBall.paint(g);
}
public void move() {
while (true) {
myBall.move();
repaint();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
System.exit(0);
}
}
}
}
the main class
package movingball;
/**
*
* #author isslam
*/
public class MovingBall {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
ClassOfMoving is = new ClassOfMoving("isslam");
is.setVisible(true);
is.move();
}
I'm not entirely sure what the problem is, You seem a bit confused.
This line does the actual moving.
pos.translate(0, CHANGE);
The Ball position pos is translated with CHANGE pixels in the y-dimension. CHANGE is defined as 3 somewhere else. Top-Left position is normally (0,0) on a computer screen, so the pos.y will increase with 3 and the Ball will move downwards by three pixels*.
Math.random() returns a number between 0 and 1. The following line positions the ball on a random position between 0+r and width-r.
pos.x = (int)(Math.random() * (width - RADIUS)) + RADIUS;
If you want to specify a specific position, set pos.x directly instead of replacing Math.random().
The y-position is defined as the top half half of the window because the max value is defined as height/2.
pos.y = (int)(Math.random() * (height / 2- RADIUS)) + RADIUS;
*:The coordinate system of a computer display is flipped upside down compared to the conventional coordinate system where positive is up. The reason is perhaps to be found in how old cathode ray tubes paints (from top left) and also how line numbers increase downwards on line printers, and also later in character based display systems.
Independently these two classes when compiled and executed in an app won't do anything as it doesnt have either main method or static block. but if you are using it in conjuction with any framework , jars or any additional code, that might be helping execute it by means of creating their objects. Hope it answers.

how to overlay image with multiline text(text will be in center of the canvas )

I am developing photography apps in that I overlay an image with text.
Here is my code:
Bitmap mBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.themes11);
// create a mutable bitmap with the same size as the background image's size
bmOverlay = Bitmap.createBitmap(mBitmap.getWidth(),
mBitmap.getHeight(), Bitmap.Config.ARGB_4444);
// create a canvas on which to draw
Canvas canvas = new Canvas(bmOverlay);
TextPaint paint = new TextPaint();
paint.setColor(Color.RED);
paint.setTextSize(40);
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
// if the background image is defined in main.xml, omit this line
canvas.drawBitmap(mBitmap, 0, 0, null);
// draw the text and the point
canvas.drawPoint(50, 100, paint);
// canvas.drawText(InstaTextActivity.CurrentWord, 300, 200, paint);
StaticLayout layout = new StaticLayout(InstaTextActivity.CurrentWord,
paint, display.getHeight(),
android.text.Layout.Alignment.ALIGN_NORMAL, (float) 1.0,
(float) 0.0, true);
canvas.translate(width / 5, height / 5);
layout.draw(canvas);
imageview_img.setImageBitmap(bmOverlay);
In this code I overlay the text on screen width/2 and height/2 it will display on top the image but I want the text to be center-aligned. Also when I write a large text it will align form center to right.
Have a look at the images to see how I want it:
The Image Background:
And the result I want:
Use the below methods to measure the height and width of the text.
Then when drawing the text on canvas
left = width/2 - textWidth/2
top = height/2 - textHeight/2
But if you need a multiple line text for long texts, it will be a bit tricky.
/**
* Method to get the height of the paint
*
* #param brush The TextPaint used to paint the text
* #param text The text which needs to be measured
* #return height of the text
*/
public static int measureTextHeight(Paint brush, String text) {
Rect result = new Rect();
// Measure the text rectangle to get the height
brush.getTextBounds(text, 0, text.length(), result);
return result.height();
}
/**
* Method to get the width of the paint
*
* #param brush The TextPaint used to paint the text
* #param text The text which needs to be measured
* #return width of the text
*/
public static int measureTextWidth(Paint brush, String text) {
Rect result = new Rect();
// Measure the text rectangle to get the height
brush.getTextBounds(text, 0, text.length(), result);
return result.width();
}
i using this. working for me.
public Bitmap drawMultilineTextToBitmap(Context gContext,
int gResId,
String gText) {
// prepare canvas
Resources resources = gContext.getResources();
float scale = resources.getDisplayMetrics().density;
Bitmap bitmap = BitmapFactory.decodeResource(resources, gResId);
android.graphics.Bitmap.Config bitmapConfig = bitmap.getConfig();
// set default bitmap config if none
if(bitmapConfig == null) {
bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888;
}
// resource bitmaps are imutable,
// so we need to convert it to mutable one
bitmap = bitmap.copy(bitmapConfig, true);
Canvas canvas = new Canvas(bitmap);
// new antialiased Paint
TextPaint paint=new TextPaint(Paint.ANTI_ALIAS_FLAG);
// text color - #3D3D3D
paint.setColor(Color.rgb(61, 61, 61));
// text size in pixels
paint.setTextSize((int) (14 * scale));
// text shadow
paint.setShadowLayer(1f, 0f, 1f, Color.WHITE);
// set text width to canvas width minus 16dp padding
int textWidth = canvas.getWidth() - (int) (16 * scale);
// init StaticLayout for text
StaticLayout textLayout = new StaticLayout(
gText, paint, textWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
// get height of multiline text
int textHeight = textLayout.getHeight();
// get position of text's top left corner
float x = (bitmap.getWidth() - textWidth)/2;
float y = (bitmap.getHeight() - textHeight)/2;
// draw text to the Canvas center
canvas.save();
canvas.translate(x, y);
textLayout.draw(canvas);
canvas.restore();
return bitmap;
}
source : http://www.skoumal.net/en/android-drawing-multiline-text-on-bitmap/
This is not a very refined approach(Not optimized for memory usage) but does the job. You will need to modify the code in order to split at the string keeping words intact. Hope this helps.
package org.edu.abhi;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
/**
* #author Abhishek_Nandi
*
*/
public class DummyUserSpace extends View {
/***************************************************************************
* Calls superclass constructor. This will call {#link #init(Context)}
*
* #param context
* The Context the view is running in, through which it can
* access the current theme, resources, etc.
**************************************************************************/
public DummyUserSpace(Context context) {
super(context);
init(context);
}
/***************************************************************************
* Calls superclass constructor. This will call {#link #init(Context)}
*
* #param context
* The Context the view is running in, through which it can
* access the current theme, resources, etc.
* #param attrs
* The attributes of the XML tag that is inflating the view.
* #param defStyle
* The default style to apply to this view. If 0, no style will
* be applied (beyond what is included in the theme). This may
* either be an attribute resource, whose value will be retrieved
* from the current theme, or an explicit style resource.
**************************************************************************/
public DummyUserSpace(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
/***************************************************************************
* Calls superclass constructor. This will call {#link #init(Context)}
*
* #param context
* The Context the view is running in, through which it can
* access the current theme, resources, etc.
* #param attrs
* The attributes of the XML tag that is inflating the view.
**************************************************************************/
public DummyUserSpace(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
/**
* Width of the View
*/
int viewWidth = 0;
/**
* Height of the View
*/
int viewHeight = 0;
/**
* {#link Paint} which is used to draw {#link #text}
*/
Paint textPaint;
/**
* The text which needs to be drawn
*/
String text = "can be spared the burden of having to perform manual memory management. In some languages, memory for the creation of objects is implicitly allocated on the stack, or explicitly allocated and deallocated from the heap. In the latter case the responsibility of managing memory resides with the programmer. If the program does not deallocate an object, a memory leak occurs. If the program attempts to access or deallocate memory that has already been deallocated, the result is undefined and difficult to predict, and the program is likely to become unstable and/or crash. This can be partially remedied by the use of smart pointers, but these add overhead and complexity. Note that garbage collection does not prevent \"logical\" memory leaks, i.e. those where the memory is still referenced but never used.. o have as few implementation dependencies as possible. It is intended to let application developers \"write once, run anywhere\" (WORA), meaning that code that runs on one platform does not need to be recompiled to run on another. Java is as of 2012 one of the most popular programming languages in use, particularly for client-server web applications, with a reported 10 million users";
/**
* Common constructor routine
*
* #param context
* The Context the view is running in. It is used to get the
* density of the device
*/
private void init(Context context) {
textPaint = new Paint();
textPaint.setColor(Color.WHITE);
textPaint.setTextSize(40);
textPaint.setStrokeWidth(6.0f);
WindowManager manager = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
manager.getDefaultDisplay().getMetrics(outMetrics);
padding *= outMetrics.density;
lineSpacing *= outMetrics.density;
}
/**
* Split the string and compute the {#link #startY}, {#link #textHeight}
* which is required for drawing the view
*/
private void prepareForDrawing() {
viewWidth = getWidth();
viewHeight = getHeight();
if (viewHeight == 0) {
return;
}
textHeight = Utility.measureTextHeight(textPaint, text);
if (0 == textHeight) {
return;
}
maxLines = (viewHeight - 2 * padding) / (textHeight + lineSpacing);
prepareMultiLineText(textPaint, text);
int noOfLines = splittedText.size();
int section = noOfLines / 2;
int center = viewHeight / 2;
if (noOfLines % 2 == 0) {
center -= lineSpacing / 2;
} else {
center -= textHeight / 2;
}
startY = center - (section - 1) * (textHeight + lineSpacing)
+ lineSpacing;
}
/**
* The starting position from where the view will start drawing
*/
private int startY = 0;
/**
* Height of the text
*/
private int textHeight = 0;
/**
* Maximum lines this view can hold
*/
private int maxLines = 1;
/**
* Padding from the view border
*/
private int padding = 20;
/**
* Spacing between each line
*/
private int lineSpacing = 10;
private final String loadingText = "Loading..";
/*
* (non-Javadoc)
*
* #see android.view.View#onDraw(android.graphics.Canvas)
*/
#Override
protected void onDraw(Canvas canvas) {
if (!computationComplete) {
new DrawComputations().execute();
canvas.drawText(
loadingText,
getWidth() / 2
- Utility.measureTextWidth(textPaint, loadingText)
/ 2,
getHeight() / 2
- Utility.measureTextHeight(textPaint, loadingText)
/ 2, textPaint);
} else {
for(int count = 0; count<splittedText.size() ; count++){
String trimmed = splittedText.get(count);
//System.out.println(trimmed);
canvas.drawText(
trimmed,
viewWidth / 2
- Utility.measureTextWidth(textPaint, trimmed)
/ 2, startY + (count - 1)
* (textHeight + lineSpacing), textPaint);
}
}
}
/**
* Denotes the computation has completed and the view is ready to be drawn
*/
boolean computationComplete = false;
/**
* This {#link AsyncTask} performs the computation in background and updates
* the view when computation is finished and the view is ready to be drawn
*
* #author Abhishek_nandi
* #version 1.0
*/
class DrawComputations extends AsyncTask<Void, Void, Void> {
/*
* (non-Javadoc)
*
* #see android.os.AsyncTask#doInBackground(Params[])
*/
#Override
protected Void doInBackground(Void... params) {
prepareForDrawing();
computationComplete = true;
return null;
}
/*
* (non-Javadoc)
*
* #see android.os.AsyncTask#onPostExecute(java.lang.Object)
*/
#Override
protected void onPostExecute(Void result) {
invalidate();
}
}
/**
* Collection which holds the multi-line text
*/
private List<String> splittedText = new ArrayList<String>();
/**
* This method is responsible for stripping {#link #text} into individual
* lines in order to form the multi line text. This method can be used as a
* utility.
*
* #param paint
* The {#link Paint} which is used to draw the text
* #param str
* The string which needs to be stripped
*/
private void prepareMultiLineText(Paint paint, String str) {
if (str == null || str.trim().length() == 0)
return;
str = str.trim();
String result = str;
int boxWidth = viewWidth;
try {
float textWidth = paint.measureText(str) + 2 * padding;
result = str;
while (textWidth > boxWidth) {
if (result.length() == 0)
break;//keeping the entire word intact//if(result.lastIndexOf(" ")!=-1){result = result.substring(0, result.lastIndexOf(" "))}else
result = result.substring(0, result.length() - 1);
textWidth = paint.measureText(result) + 2 * padding;
}
result = result.trim();
boolean exceeded = false;
if (splittedText.size() == maxLines) {
exceeded = true;
result = result.substring(0, result.length()-2).concat("..");
}
splittedText.add(result);
if (!exceeded && result.length() != str.length()) {
prepareMultiLineText(textPaint, str.substring(result.length()));
}
} catch (Exception e) {
Log.e("CustomView", "prepareMultiLineText", e);
}
}
} // END of class
// END of file
You should use StaticLayout for this. It measures and draws multiline text, handling line wrapping, line spacing, alignment etc.
StaticLayout layout = new StaticLayout("your long text", textPaint,
pixelsToFitWidthTo, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, true);
int boxHeight = layout.getHeight(); // measure
The text can even be HTML formatted, just wrap your text in Html.fromHtml():
Html.fromHtml("your long <b>and bold</b> text");

Categories