I am trying to print svg pictures on my xamarin forms app.
I am using SkiaSharp.
I implemented a component using a Bindable property to get the byte[] of the svg and I draw it in the component.
public static readonly BindableProperty ImageProperty = BindableProperty.Create(nameof(Image), typeof(byte[]), typeof(SvgCanvas));
public byte[] Image
{
get => (byte[])GetValue(ImageProperty);
set
{
if (Image == value)
{
return;
}
SetValue(ImageProperty, value);
CanvasView.InvalidateSurface();
}
}
private void OnPainting(object sender, SKPaintSurfaceEventArgs args)
{
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
int width = args.Info.Width;
int height = args.Info.Height;
// clear the surface
canvas.Clear(SKColors.White);
// the page is not visible yet
if (Image == null)
{
return;
}
SKSvg svg = new SKSvg(new SKSize(width, height));
using (MemoryStream ms = new MemoryStream(Image))
{
svg.Load(ms);
}
// draw the svg
// canvas.DrawPicture(svg.Picture, ref matrix);
canvas.DrawPicture(svg.Picture);
}
It looks like the Picture is loaded but only a black square is displayed on my view...
My component is called like this in my page: (Picture is a byte[])
<controls:SvgCanvas Image="{Binding Picture}" />
Did somebody had the same issue? Maybe an encoding problem?
I am kind of lost..
Related
I'm trying to code a simple maze with a character that moves using a Joystick (touchpad). I used a Fit viewport to maintain the aspect ration of my maze while keeping it at the center of the screen;
However, I find that that the joystick is also added beside the maze. sample of what it looks like currently
I want the joystick to be of the far bottom-left of the screen. I tried doing this using a different viewport for UI elements (including the joystick), but then the whole maze just gets distorted.
Here is part of my BaseScreen class, an extension I made to reuse in other games as well:
public abstract class BaseScreen implements Screen, InputProcessor {
public BaseScreen(){
gameWorldHeight = 1024;
gameWorldWidth = 1024;
cam = new OrthographicCamera(gameWorldWidth, gameWorldHeight);
port = new FitViewport(cam.viewportWidth, cam.viewportHeight, cam);
port.apply();
cam.update();
mainStage = new Stage(port);
uiStage = new Stage(port);
uiTable = new Table();
uiTable.setFillParent(true);
uiStage.addActor(uiTable);
initialize();
}
public abstract void initialize();
public void render(float dt) {
mainStage.getCamera().update();
uiStage.getCamera().update();
uiStage.act(dt);
mainStage.act(dt);
update(dt);
mainStage.draw();
uiStage.draw();
}
public abstract void update(float dt);
public void resize(int width, int height) {
mainStage.getViewport().update(width,height, true);
uiStage.getViewport().update(width,height,false);
}
}
Here is also a part of my gameScreen class, where I actually code all the core mechanics of the game
public class gameScreen extends BaseScreen {
#Override
public void initialize() {
//..
uiTable.pad(10);
uiTable.add().expandX().expandY();
uiTable.row();
uiTable.add(ball.touchpad).left();
uiTable.add().expandX();
}
}
and in case you were wondering, here is what ball.touchpad is
public class Ball extends BaseActor {
public Touchpad touchpad;
public Touchpad.TouchpadStyle touchpadStyle;
public Skin touchpadSkin;
public Drawable touchBackground;
public Drawable touchKnob;
public Ball (float x, float y, Stage s) {
//..
//Create a touchpad skin
touchpadSkin = new Skin();
//Set background image
touchpadSkin.add("touchBackground", new Texture("touchBackground.png"));
//Set knob image
touchpadSkin.add("touchKnob", new Texture("touchKnob.png"));
//Create TouchPad Style
touchpadStyle = new Touchpad.TouchpadStyle();
//Create Drawable's from TouchPad skin
touchBackground = touchpadSkin.getDrawable("touchBackground");
touchKnob = touchpadSkin.getDrawable("touchKnob");
//Apply the Drawables to the TouchPad Style
touchpadStyle.background = touchBackground;
touchpadStyle.knob = touchKnob;
//Create new TouchPad with the created style
touchpad = new Touchpad(10, touchpadStyle);
//setBounds(x,y,width,height)
touchpad.setBounds(15, 15, 200, 200);
}
So how can i add this touchpad on the bottom left of the screen?
Depending on the screen size (and device idiom?) the width of the master page varies: On phones it is about 80 % of the screen width, while on tablets it seems to be a constant dimension like 320 dp.
Does anybody know a general formula for this value? I'd like to use it for laying out some elements during construction time, when the Width property isn't set, yet.
Edit:
I know how to get the current screen size. But how does the width of the presented master page of Xamarin.Form's master-detail page relate to it? It doesn't cover the whole screen, but fills a different fraction of it depending on the device.
You could request the device's actual screen width, height, and scale factor.
(From https://github.com/mattregul/Xamarin_GetDeviceScreensize)
iOS AppDelegate.cs
[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
// Store off the device sizes, so we can access them within Xamarin Forms
App.DisplayScreenWidth = (double)UIScreen.MainScreen.Bounds.Width;
App.DisplayScreenHeight = (double)UIScreen.MainScreen.Bounds.Height;
App.DisplayScaleFactor = (double)UIScreen.MainScreen.Scale;
LoadApplication(new App());
return base.FinishedLaunching(app, options);
}
}
Android MainActivity.cs
[Activity(Label = "Xamarin_GetDeviceScreensize.Droid", Icon = "#drawable/icon", Theme = "#style/MyTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
// Store off the device sizes, so we can access them within Xamarin Forms
App.DisplayScreenWidth = (double)Resources.DisplayMetrics.WidthPixels / (double)Resources.DisplayMetrics.Density; // Width = WidthPixels / Density
App.DisplayScreenHeight = (double)Resources.DisplayMetrics.HeightPixels / (double)Resources.DisplayMetrics.Density; // Height = HeightPixels / Density
App.DisplayScaleFactor = (double)Resources.DisplayMetrics.Density;
global::Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication(new App());
}
}
Xamarin Forms App.cs
public class App : Application
{
public static double DisplayScreenWidth;
public static double DisplayScreenHeight;
public static double DisplayScaleFactor;
public App()
{
string ScreenDetails = Device.OS.ToString() + " Device Screen Size:\n" +
$"Width: {DisplayScreenWidth}\n" +
$"Height: {DisplayScreenHeight}\n" +
$"Scale Factor: {DisplayScaleFactor}";
// The root page of your application
var content = new ContentPage
{
Title = "Xamarin_GetDeviceScreensize",
Content = new StackLayout
{
VerticalOptions = LayoutOptions.Center,
Children = {
new Label {
HorizontalTextAlignment = TextAlignment.Center,
FontSize = Device.GetNamedSize (NamedSize.Large, typeof(Label)),
Text = ScreenDetails
}
}
}
};
MainPage = new NavigationPage(content);
}
}
So I have a TextArea and as the user pastes paragraphs into it, or just writes in it, I want it to expand vertically to reveal all the available text. I.e. not to use a scrollbar in the text field itself... much like what happens on many web pages. Many users, myself included, don't like to be forced to edit in a small window. Exactly how Facebook status updates box works.
I've tried
myTextArea.autoSize()
wrapped in an
myTextArea.textProperty().addListener(new ChangeListener()....);
but that doesn't work. I think it's happy autosizing to its current size.
The left, right & top anchors are set to it's parent AnchorPane. I've tried it with the bottom attached and not attached. Ideally I'd like to grow the anchor pane as the textarea grows.
I don't mind reading the TextProperty and calculating a trigger size which I set myself... but this seems a hacky approach IF there is already a best practise. The number of properties and sub objects of javafx is sufficiently daunting that it seems like a good point to ask the question here, rather than trying to figure out how many pixels the font/paragraphs etc are taking up.
Update:
So I thought maybe I was overthinking it, and all I needed to do was to switch the scrollbars off and the rest would happen. Alas, looking for available fields and methods for "scroll", "vertical", "vbar" comes up with nothing I can use. ScrollTopProperty looks like it's for something else.
The problem; the height of textArea is wanted to be grown or shrunk while its text is changing by either user's typing or copy-pasting. Here is another approach:
public class TextAreaDemo extends Application {
private Text textHolder = new Text();
private double oldHeight = 0;
#Override
public void start(Stage primaryStage) {
final TextArea textArea = new TextArea();
textArea.setPrefSize(200, 40);
textArea.setWrapText(true);
textHolder.textProperty().bind(textArea.textProperty());
textHolder.layoutBoundsProperty().addListener(new ChangeListener<Bounds>() {
#Override
public void changed(ObservableValue<? extends Bounds> observable, Bounds oldValue, Bounds newValue) {
if (oldHeight != newValue.getHeight()) {
System.out.println("newValue = " + newValue.getHeight());
oldHeight = newValue.getHeight();
textArea.setPrefHeight(textHolder.getLayoutBounds().getHeight() + 20); // +20 is for paddings
}
}
});
Group root = new Group(textArea);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
// See the explanation below of the following line.
// textHolder.setWrappingWidth(textArea.getWidth() - 10); // -10 for left-right padding. Exact value can be obtained from caspian.css
}
public static void main(String[] args) {
launch(args);
}
}
But it has a drawback; the textarea's height is changing only if there are line breaks (ie Enter keys) between multiple lines, if the user types long enough the text gets wrapped to multiple line but the height is not changing.
To workaround this drawback I added this line
textHolder.setWrappingWidth(textArea.getWidth() - 10);
after primaryStage.show();. It works well for long typings where user does not linebreaks. However this generates another problem. This problem occurs when the user is deleting the text by hitting "backspace". The problem occurs exactly when the textHolder height is changed and where the textArea's height is set to new value. IMO it maybe a bug, didn't observe deeper.
In both case the copy-pasting is handling properly.
Awaiting a better, i use this hacky solution.
lookup the vertical scrollbar of the textarea.
make it transparent
listen to its visible property
when the scrollbar become visible i add a row to the textarea.
The code:
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.TextArea;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class GrowGrowTextArea extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
AnchorPane root = new AnchorPane();
root.setStyle("-fx-padding:20;-fx-background-color:dodgerblue;");
final TextArea textArea = new TextArea();
AnchorPane.setTopAnchor(textArea, 10.0);
AnchorPane.setLeftAnchor(textArea, 10.0);
AnchorPane.setRightAnchor(textArea, 10.0);
root.getChildren().add(textArea);
primaryStage.setScene(new Scene(root, 400, 300));
primaryStage.show();
ScrollBar scrollBar = lookupVerticalScrollBar(textArea);
scrollBar.setOpacity(0.0);
scrollBar.visibleProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> source,
Boolean wasVisible,
Boolean isVisible) {
if (isVisible) {
textArea.setPrefRowCount(textArea.getPrefRowCount() + 1);
textArea.requestLayout();
}
}
});
}
private ScrollBar lookupVerticalScrollBar(Node node) {
if (node instanceof ScrollBar && ((ScrollBar)node).getOrientation() == Orientation.VERTICAL) {
return (ScrollBar) node;
}
if (node instanceof Parent) {
ObservableList<Node> children = ((Parent) node).getChildrenUnmodifiable();
for (Node child : children) {
ScrollBar scrollBar = lookupVerticalScrollBar(child);
if (scrollBar != null) {
return scrollBar;
}
}
}
return null;
}
}
I had a similar problem with creating expanding TextArea. I was creating TextArea that looks like TextField and expand vertically every time when there is no more space in line.
I have tested all solutions that I could find on this topic on stack and other sources available. I found few good solutions but neither was good enough.
After many hours of fighting, I figured out this approach.
I extended TextArea class, override layoutChildren() method and add a listener on text height.
#Override
protected void layoutChildren() {
super.layoutChildren();
setWrapText(true);
addListenerToTextHeight();
}
private void addListenerToTextHeight() {
ScrollPane scrollPane = (ScrollPane) lookup(".scroll-pane");
scrollPane.setHbarPolicy(ScrollBarPolicy.NEVER);
scrollPane.setVbarPolicy(ScrollBarPolicy.NEVER);
StackPane viewport = (StackPane) scrollPane.lookup(".viewport");
Region content = (Region) viewport.lookup(".content");
Text text = (Text) content.lookup(".text");
text.textProperty().addListener(textHeightListener(text));
}
private InvalidationListener textHeightListener(Text text) {
return (property) -> {
// + 1 for little margin
double textHeight = text.getBoundsInLocal().getHeight() + 1;
//To prevent that our TextArena will be smaller than our TextField
//I used DEFAULT_HEIGHT = 18.0
if (textHeight < DEFAULT_HEIGHT) {
textHeight = DEFAULT_HEIGHT;
}
setMinHeight(textHeight);
setPrefHeight(textHeight);
setMaxHeight(textHeight);
};
}
I used some of the code found in the previous answers.
The growTextAreaIfNecessary method will increase the height of textArea until the scrollbar is not visible (limited to 20 lines in this example).
The problem with this approach is that the window needs to be redrawn several times until the perfect height is found.
private ScrollBar lookupVerticalScrollBar(Node node) {
if (node instanceof ScrollBar && ((ScrollBar) node).getOrientation() == Orientation.VERTICAL) {
return (ScrollBar) node;
}
if (node instanceof Parent) {
ObservableList<Node> children = ((Parent) node).getChildrenUnmodifiable();
for (Node child : children) {
ScrollBar scrollBar = lookupVerticalScrollBar(child);
if (scrollBar != null) {
return scrollBar;
}
}
}
return null;
}
private void growTextAreaIfNecessary(TextArea textArea) {
Platform.runLater(() -> {
ScrollBar lookupVerticalScrollBar = lookupVerticalScrollBar(textArea);
int prefRowCount = textArea.getPrefRowCount();
if (lookupVerticalScrollBar.isVisible() && prefRowCount < 20) {
textArea.setPrefRowCount(prefRowCount + 1);
System.out.println("increasing height to: " + (prefRowCount + 1));
growTextAreaIfNecessary(textArea);
}
});
}
I have tried many hacks, most of them had jitters while typing, this to me was the perfect result:
textArea.textProperty().addListener((obs,old,niu)->{
Text t = new Text(old+niu);
t.setFont(textArea.getFont());
StackPane pane = new StackPane(t);
pane.layout();
double height = t.getLayoutBounds().getHeight();
double padding = 20 ;
textArea.setMinHeight(height+padding);
});
I'm changing the height and the width of my CustomView which extends Android View in runtime like this:
#Override
public void onGlobalLayout() {
if(!initialized) {
int containerHeight = instance.getHeight();
int containerWidth = instance.getWidth();
myView.getLayoutParams().height = (int) (containerHeight * HEIGHT_RATIO);
myView.getLayoutParams().width = (int) (containerWidth * WIDTH_RATIO);
instance.getViewTreeObserver().removeGlobalOnLayoutListener(this);
initialized = true;
}
}
this code is in the container view Constructor.
In addition my CustomView onMeasure() is as follows:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// maximum width we should use
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
}
This is the result:
Where the width and height I specified are at the same size of the green rectangle.
My Question is: why does the actual size of my custom view (red rectangle) is not at the same size as I gave as input in the LayoutParams ?
not sure what object you're working with for your view, but you might try something like this:
CustomViewget.Window().setLayout(370, 480); //Controls width and height
My problem was in the drawing of the CustomView. In onDraw(), I took the width and height of the canvas instead of the View itself.
The sizes might not be calculated properly at the time of calling, so you may need to place your code in a handler and run it on post so that it gets done after everything else on the UI thread is done. Try the following code.
Handler handler = new Handler();
handler.post(new Runnable() {
public void run() {
//check sizes and update sizes here
});
I have the following shape XML:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:a="http://schemas.android.com/apk/res/android"
a:shape="ring"
a:innerRadiusRatio="3"
a:thicknessRatio="8"
a:useLevel="false">
<!-- some other stuff goes here -->
</gradient>
</shape>
I would like to use code instead to create this shape, since some things need to be calculated on the fly before I do it, so static pre-defined layout doesn't cut it.
I'm new to Android and can't quite figure out how XML translates to code, and there's no RingShape class inheriting from Shape.
In addition to answering just this question, if there's a guide somewhere that details relation between XML and Java code and how XML gets processed in order to end up on the screen I would appreciate a link too. Thanks.
Reuben already pointed out most the most useful observations, so I'll just focus on the implementation side of the story. There's multiple approaches using reflection that'll probably give you what you're looking for.
First one is to (ab)use the private GradientDrawable constructor that takes a GradientState reference. Unfortunately the latter is a final subclass with package visibility, so you can't easily get access to it. In order to use it, you would need to dive further in using reflection or mimic its functionality into your own code.
Second approach is to use reflection to get the private member variable mGradientState, which fortunately has a getter in the form of getConstantState(). This'll give you the ConstantState, which at runtime is really a GradientState and hence we can use reflection to access its members and change them at runtime.
In order to support above statements, here's a somewhat basic implementation to create a ring-shaped drawable from code:
RingDrawable.java
public class RingDrawable extends GradientDrawable {
private Class<?> mGradientState;
public RingDrawable() {
this(Orientation.TOP_BOTTOM, null);
}
public RingDrawable(int innerRadius, int thickness, float innerRadiusRatio, float thicknessRatio) {
this(Orientation.TOP_BOTTOM, null, innerRadius, thickness, innerRadiusRatio, thicknessRatio);
}
public RingDrawable(GradientDrawable.Orientation orientation, int[] colors) {
super(orientation, colors);
setShape(RING);
}
public RingDrawable(GradientDrawable.Orientation orientation, int[] colors, int innerRadius, int thickness, float innerRadiusRatio, float thicknessRatio) {
this(orientation, colors);
try {
setInnerRadius(innerRadius);
setThickness(thickness);
setInnerRadiusRatio(innerRadiusRatio);
setThicknessRatio(thicknessRatio);
} catch (Exception e) {
// fail silently - change to your own liking
e.printStackTrace();
}
}
public void setInnerRadius(int radius) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
if (mGradientState == null) mGradientState = resolveGradientState();
Field innerRadius = resolveField(mGradientState, "mInnerRadius");
innerRadius.setInt(getConstantState(), radius);
}
public void setThickness(int thicknessValue) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
if (mGradientState == null) mGradientState = resolveGradientState();
Field thickness = resolveField(mGradientState, "mThickness");
thickness.setInt(getConstantState(), thicknessValue);
}
public void setInnerRadiusRatio(float ratio) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
if (mGradientState == null) mGradientState = resolveGradientState();
Field innerRadiusRatio = resolveField(mGradientState, "mInnerRadiusRatio");
innerRadiusRatio.setFloat(getConstantState(), ratio);
}
public void setThicknessRatio(float ratio) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
if (mGradientState == null) mGradientState = resolveGradientState();
Field thicknessRatio = resolveField(mGradientState, "mThicknessRatio");
thicknessRatio.setFloat(getConstantState(), ratio);
}
private Class<?> resolveGradientState() {
Class<?>[] classes = GradientDrawable.class.getDeclaredClasses();
for (Class<?> singleClass : classes) {
if (singleClass.getSimpleName().equals("GradientState")) return singleClass;
}
throw new RuntimeException("GradientState could not be found in current GradientDrawable implementation");
}
private Field resolveField(Class<?> source, String fieldName) throws SecurityException, NoSuchFieldException {
Field field = source.getDeclaredField(fieldName);
field.setAccessible(true);
return field;
}
}
Above can be used as follows to create a RingDrawable from code and display it in a standard ImageView.
ImageView target = (ImageView) findViewById(R.id.imageview);
RingDrawable ring = new RingDrawable(10, 20, 0, 0);
ring.setColor(Color.BLUE);
target.setImageDrawable(ring);
This will show a simple, opaque blue ring in the ImageView (10 units inner radius, 20 units thick). You'll need to make sure to not set the ImageView's width and height to wrap_content, unless you add ring.setSize(width, height) to above code in order for it to show up.
Hope this helps you out in any way.
Ring and other shapes are GradientDrawables.
If you look at the source code for GradientDrawable, you'll see it looks like certain properties (like innerRadius) can only be defined through XML... they are not exposed through accessor methods. The relevant state is also unhelpfully private to the class, so subclassing is no help either.
You can do something like this:
private ShapeDrawable newRingShapeDrawable(int color) {
ShapeDrawable drawable = new ShapeDrawable(new OvalShape());
drawable.getPaint().setColor(color);
drawable.getPaint().setStrokeWidth(2);
drawable.getPaint().setStyle(Paint.Style.STROKE);
return drawable;
}
It is possible to do it from code:
int r = dipToPixels(DEFAULT_CORNER_RADIUS_DIP); // this can be used to make it circle
float[] outerR = new float[]{r, r, r, r, r, r, r, r};
int border = dipToPixels(2); // border of circle
RectF rect = new RectF(border, border, border, border);
RoundRectShape rr = new RoundRectShape(outerR, rect, outerR);// must checkout this constructor
ShapeDrawable drawable = new ShapeDrawable(rr);
drawable.getPaint().setColor(badgeColor);// change color of border
// use drawble now
For me it works as follow: (also for Android version > lollipop)
ImageView target = (ImageView) findViewById(R.id.imageview);
GradientDrawable shapeRing = new GradientDrawable();
shapeRing.setShape(GradientDrawable.OVAL);
shapeRing.setColor(centerColor); // transparent
shapeRing.setStroke(stroke, strokeColor);
shapeRing.setSize(width, width);
target.setImageDrawable(ring);