Android View.onDraw() always has a clean Canvas - android-canvas

I am trying to draw an animation. To do so I have extended View and overridden the onDraw() method. What I would expect is that each time onDraw() is called the canvas would be in the state that I left it in and I could choose to clear it or just draw over parts of it (This is how it worked when I used a SurfaceView) but each time the canvas comes back already cleared. Is there a way that I can not have it cleared? Or maybe save the previous state into a Bitmap so I can just draw that Bitmap and then draw over top of it?

I'm not sure if there is a way or not. But for my custom views I either redraw everything each time onDraw() is called, or draw to a bitmap and then draw the bitmap to the canvas (like you suggested in your question).
Here is how i do it
class A extends View {
private Canvas canvas;
private Bitmap bitmap;
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (bitmap != null) {
bitmap .recycle();
}
canvas= new Canvas();
bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
canvas.setBitmap(bitmap);
}
public void destroy() {
if (bitmap != null) {
bitmap.recycle();
}
}
public void onDraw(Canvas c) {
//draw onto the canvas if needed (maybe only the parts of animation that changed)
canvas.drawRect(0,0,10,10,paint);
//draw the bitmap to the real canvas c
c.drawBitmap(bitmap,
new Rect(0,0,bitmap.getWidth(),bitmap.getHeight()),
new Rect(0,0,bitmap.getWidth(),bitmap.getHeight()), null);
}
}

you should have a look here to see the difference between basic view and surfaceView. A surfaceView has a dedicated layer for drawing, which I suppose keeps track of what you drew before. Now if you really want to do it on a basic View, you could try to put each item you draw in an array, like the exemple of itemized overlay for the mapview.
It should work pretty much the same way

Your expectations do not jib w/ reality :) The canvas will not be the way you left it, but it blank instead. You could create an ArrayList of objects to be drawn (canvas.drawCircle(), canvas.drawBitmap() etc.), then iterate though the ArrayList in the OnDraw(). I am new to graphics programming but I have used this on a small scale. Maybe there is a much better way.

Related

Get bitmap of RelativeLayout having invisible TextView or ImageView

I am to create bitmap of a relative layout having a visible ImageView with two invisible TextViews and one invisible ImageView. But invisible views data was not shown in bitmap. If I set visible all those invisible views it is shown in bitmap, but not if hidden.
I am using below code -
private Bitmap getBitmap(View v) {
Bitmap bmp = null, b1 = null;
RelativeLayout targetView = (RelativeLayout) v;
targetView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
targetView.buildDrawingCache();
b1 = targetView.getDrawingCache();
bmp = b1.copy(Bitmap.Config.ARGB_8888, true);
targetView.destroyDrawingCache();
return bmp;
}
I also used below link but that also didn't give me expected result.
Getting bitmap from a view visible-invisible
I am really in a fix.
The drawing cache saves a bitmap of what's currently drawn on screen. Naturally, this does not include hidden views.
The key difference between the article you provided in the link, and your code is that in the article, the bitmap cache is constructed for the invisible view.
However, you have a visible parent, which contains invisible views. When you create the drawing cache of the parent, the invisible views are, of course, not rendered.
In order for you invisible views to appear, you need to draw the views yourself in a bitmap, then draw that bitmap inside the bitmap which contins the parent.
Code sample:
//these fields should be initialized before using
TextView invisibleTextView1;
TextView invisibleTextView2;
ImageView invisibleImageView;
private Bitmap getBitmap(View v) {
Bitmap bmp = null;
Bitmap b1 = null;
RelativeLayout targetView = (RelativeLayout) v;
targetView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
targetView.buildDrawingCache();
b1 = targetView.getDrawingCache();
bmp = b1.copy(Bitmap.Config.ARGB_8888, true);
targetView.destroyDrawingCache();
//create a canvas which will be used to draw the invisible views
//inside the bitmap returned from the drawing cache
Canvas fullCanvas = new Canvas(bmp);
//create a list of invisible views
List<View> invisibleViews = Arrays.asList(invisibleTextView1, invisibleImageView, invisibleTextView2);
//iterate over the invisible views list
for (View invisibleView : invisibleViews) {
//create a bitmap the size of the current invisible view
//in this bitmap the view will draw itself
Bitmap invisibleViewBitmap = Bitmap.createBitmap(invisibleView.getWidth(), invisibleView.getHeight(), Bitmap.Config.ARGB_8888);
//wrap the bitmap in a canvas. in this canvas the view will draw itself when calling "draw"
Canvas invisibleViewCanvas = new Canvas(invisibleViewBitmap);
//instruct the invisible view to draw itself in the canvas we created
invisibleView.draw(invisibleViewCanvas);
//the view drew itself in the invisibleViewCanvas, which in term modified the invisibleViewBitmap
//now, draw the invisibleViewBitmap in the fullCanvas, at the view's position
fullCanvas.drawBitmap(invisibleViewBitmap, invisibleView.getLeft(), invisibleView.getTop(), null);
//finally recycle the invisibleViewBitmap
invisibleViewBitmap.recycle();
}
return bmp;
}
Final mentions:
if your invisible views have visibility = View.GONE, you should layout(...) on each one, before calling draw(...) in the loop.
if the parent view of your invisible views does not occupy the whole screen, then your invisible views will not be drawn at the correct position, since getLeft() & getTop() return the left & top properties in pixels, relative to the parent location. If your invisible views are within a parent which only covers part of the screen, then use instead:
fullCanvas.drawBitmap(invisibleViewBitmap, invisibleView.getLeft() + parent.getLeft(), v.getTop() + v.getTop(), null);
Let me know if this works!

How to make large 2d tilemap easier to load in Unity

I am creating a small game in the Unity game engine, and the map for the game is generated from a 2d tilemap. The tilemap contains so many tiles, though, is is very hard for a device like a phone to render them all, so the frame rate drops. The map is completely static in that the only moving thing in the game is a main character sprite and the camera following it. The map itself has no moving objects, it is very simple, there must be a way to render only the needed sections of it or perhaps just render the map in once. All I have discovered from researching the topic is that perhaps a good way to do it is buy using the Unity mesh class to turn the tilemap into a mesh. I could not figure out how to do this with a 2d tilemap, and I could not see how it would benefit the render time anyways, but if anyone could point me in the right direction for rendering large 2d tilemaps that would be fantastic. Thanks.
Tile system:
To make the tile map work I put every individual tile as a prefab in my prefab folder, with the attributes changed for 2d box colliders and scaled size. I attribute each individual prefab of the tile to a certain color on the RGB scale, and then import a png file that has the corresponding colors of the prefabs where I want them like this:
I then wrote a script which will place each prefab where its associated color is. It would look like this for one tile:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Map : MonoBehaviour {
private int levelWidth;
private int levelHeight;
public Transform block13;
private Color[] tileColors;
public Color block13Color;
public Texture2D levelTexture;
public PlayerMobility playerMobility;
// Use this for initialization
void Start () {
levelWidth = levelTexture.width;
levelHeight = levelTexture.height;
loadLevel ();
}
// Update is called once per frame
void Update () {
}
void loadLevel(){
tileColors = new Color[levelWidth * levelHeight];
tileColors = levelTexture.GetPixels ();
for (int y = 0; y < levelHeight; y++) {
for (int x = 0; x < levelWidth; x++) {
// if (tileColors [x + y * levelWidth] == block13Color) {
// Instantiate(block13, new Vector3(x, y), Quaternion.identity);
// }
//
}
}
}
}
This results in a map that looks like this when used with all the code (I took out all the code for the other prefabs to save space)
You can instantiate tiles that are in range of the camera and destroy tiles that are not. There are several ways to do this. But first make sure that what's consuming your resources is in fact the large number of tiles, not something else.
One way is to create an empty parent gameObject to every tile (right click in "Hierarchy" > Create Empty"
then attach a script to this parent. This script has a reference to the camera (tell me if you need help with that) and calculates the distance between it and the camera and instantiates the tile if the distance is less than a value, otherwise destroys the instance (if it's there).
It has to do this in the Update function to check for the distances every frame, or you can use "Coroutines" to do less checks (more efficient).
Another way is to attach a script to the camera that has an array with instances of all tiles and checks on their distances from the camera the same way. You can do this if you only have exactly one large tilemap because it would be hard to re-use this script if you have more than a large tilemap.
Also you can calculate the distance between the tile and the character sprite instead of the camera. Pick whichever is more convenient.
After doing the above and you still get frame-drops you can zoom-in the camera to include less tiles in its range but you'd have to recalculate the distances then.

Creating a flexible UI contianer for an image and a label

I thought this would be like pretty simple task to do, but now I have tried for hours and cant figure out how to get around this.
I have a list of friends which should be displayed in a scrollable list. Each friend have a profile image and a name associated to him, so each item in the list should display the image and the name.
The problem is that I cant figure out how to make a flexible container that contains both the image and the name label. I want to be able to change the width and height dynamically so that the image and the text will scale and move accordingly.
I am using Unity 5 and Unity UI.
I want to achieve the following for the container:
The width and height of the container should be flexible
The image is a child of the container and should be left aligned, the height should fill the container height and should keep its aspect ratio.
The name label is a child of the contianer and should be left aligned to the image with 15 px left padding. The width of the text should fill the rest of the space in the container.
Hope this is illustrated well in the following attached image:
I asked the same question here on Unity Answers, but no answers so far. Is it really possible that such a simple task is not doable in Unity UI without using code?
Thanks a lot for your time!
Looks like can be achieved with layout components.
The image is a child of the container and should be left aligned, the height should fill the container height and should keep its aspect ratio.
For this try to add Aspect Ratio Fitter Component with Aspect mode - Width Controls Height
The name label is a child of the container and should be left aligned to the image with 15 px left padding. The width of the text should fill the rest of the space in the container.
For this you can simply anchor and stretch your label to the container size and use BestFit option on the Text component
We never found a way to do this without code. I am very unsatisfied that such a simple task cannot be done in the current UI system.
We did create the following layout script that does the trick (tanks to Angry Ant for helping us out). The script is attached to the text label:
using UnityEngine;
using UnityEngine.EventSystems;
[RequireComponent (typeof (RectTransform))]
public class IndentByHeightFitter : UIBehaviour, UnityEngine.UI.ILayoutSelfController
{
public enum Edge
{
Left,
Right
}
[SerializeField] Edge m_Edge = Edge.Left;
[SerializeField] float border;
public virtual void SetLayoutHorizontal ()
{
UpdateRect ();
}
public virtual void SetLayoutVertical() {}
#if UNITY_EDITOR
protected override void OnValidate ()
{
UpdateRect ();
}
#endif
protected override void OnRectTransformDimensionsChange ()
{
UpdateRect ();
}
Vector2 GetParentSize ()
{
RectTransform parent = transform.parent as RectTransform;
return parent == null ? Vector2.zero : parent.rect.size;
}
RectTransform.Edge IndentEdgeToRectEdge (Edge edge)
{
return edge == Edge.Left ? RectTransform.Edge.Left : RectTransform.Edge.Right;
}
void UpdateRect ()
{
RectTransform rect = (RectTransform)transform;
Vector2 parentSize = GetParentSize ();
rect.SetInsetAndSizeFromParentEdge (IndentEdgeToRectEdge (m_Edge), parentSize.y + border, parentSize.x - parentSize.y);
}
}

Resizing desktop application with ligdx issues

I'm trying to make an app using libgdx. When I first launch it on my desktop, my map print well. But if I resize the window, the map is no longer completely in the screen.
But if I change the size on the beginning, the map print well. So there might be an issue with my resizing function.
In my GameScreen class I've got :
public void resize(int width, int height)
{
renderer.setSize(width, height);
}
And my renderer got this :
public void setSize (int w, int h)
{
ppuX = (float)w / (float)world.getWidth();
ppuY = (float)h / (float)world.getHeight();
}
For example, if I reduce my window, the map is too small for the window. If I extend it, the map doesn't in it.
If you have a fixed viewport, which is the case most of the time. You don't need to do anything in your resize method. The camera will fill the window to draw everything even if you resize it at runtime.
I, myself, never put anything on resize.

Drawing control with transparent background

I've been trying to display an image which has a transparent border as the background to a control.
Unfortunately, the transparent area creates a hole in the parent form as follows:
In the above image, the form has a red background which I'd hoped to see behind my control in the transparent areas.
The code I used is as follows:
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
if (this.Image != null)
{
Graphics g = Graphics.FromImage(this.Image);
ImageAttributes attr = new ImageAttributes();
//set the transparency based on the top left pixel
attr.SetColorKey((this.Image as Bitmap).GetPixel(0, 0), (this.Image as Bitmap).GetPixel(0, 0));
//draw the image using the image attributes.
Rectangle dstRect = new Rectangle(0, 0, this.Image.Width, this.Image.Height);
e.Graphics.DrawImage(this.Image, dstRect, 0, 0, this.Image.Width, this.Image.Height,
GraphicsUnit.Pixel, attr);
}
else
{
base.OnPaint(e);
}
}
protected override void OnPaintBackground(System.Windows.Forms.PaintEventArgs e)
{
//base.OnPaintBackground(e);
}
This class is inherited from a PictureBox because I needed a control which implements OnMouseMove and OnMouseUp Events.
I've been researching most of the day without success testing out different ideas but unfortunately most only work on the full framework and not .Net CF.
Any ideas would be much appreciated.
Ah the joys of CF transparency. I could go on and on about it (and have in my blog and the Project Resistance code I did ages ago).
The gist is this. The child control has to paint it's areas, but first it has to call back up to it's parent (the Form in your case) and tell it to redraw it's background image everywhere except in the child's clipping region and then draw itself on top of that. If that sounds a bit confusing it's because it is.
For example, if you look at Project Resistance, a View (which is just a Control) draws a resistor and bands. It lies in a Form that has an image background, and that background needs to "show through" the transparent areas of the resistor:
So in the drawing code of the resistor it does this:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
try
{
RECT rect = new RECT(this.Bounds);
// draw the blank
Infrastructure.GraphicTools.DrawTransparentBitmap(e.Graphics, m_blankImage, Bounds,
new Rectangle(0, 0, m_blankImage.Width, m_blankImage.Height));
if (m_bandsImage != null)
{
// draw the bands
Infrastructure.GraphicTools.DrawTransparentBitmap(e.Graphics, m_bandsImage, Bounds,
new Rectangle(0, 0, m_bandsImage.Width, m_bandsImage.Height));
}
}
finally
{
}
if (!Controller.TouchMode)
{
// TODO: draw in the selection arrow
// Controller.SelectedBand
}
}
Which is simple enough. The key is that it calls to it's base OnPaint, which does this:
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
// this assumes we're in a workspace, on MainForm (the whole Parent.Parent thing)
IBackgroundPaintProvider bgPaintProvider = Parent.Parent as IBackgroundPaintProvider;
if (bgPaintProvider != null)
{
Rectangle rcPaint = e.ClipRectangle;
// use the parent, since it's the workspace position in the Form we want,
// not our position in the workspace
rcPaint.Offset(Parent.Left, Parent.Top);
bgPaintProvider.PaintBackground(e.Graphics, e.ClipRectangle, rcPaint);
}
}
You can see it's calling PaintBackground of the containing Form (it's Parent.Parent in this case becuse the Control is actually in a container called a Workspace - you wouldn't need to walk up twice in your case). That draws in the background image in the area you're currently seeing as the "hole"
public void PaintBackground(Graphics g, Rectangle targetRect, Rectangle sourceRect)
{
g.DrawImage(m_bmBuffer, targetRect, sourceRect, GraphicsUnit.Pixel);
}