Canvas not displaying all drawn parts in Custom View? - android

I'm working on a custom view for an android application, similar to the Analog Gauge sample code available from Mind the Robot.
Running the code from listed site, I get see this on my screen:
(Motorola Droid, 2.2.3), (Emulator, 4.0.3)
(Xoom, 4.0.3)(Other phone, 4.0.3)
The hand is missing!
The drawing calls are being made (I can see them in logcat), but the canvas elements the calls draw are invisible.
It's not API level dependent, though; if I import it the right way into a project, it will hand will show up when I run it on the Xoom.
But, when I move the files to a different project folder (same source code, same layouts) it goes back to missing the dial.
What's going on? How could the same code be producing such different outcomes on different devices?

So, the key clue in my mystery seemed to be that it worked on the emulator, but not on the hardware devices.
Hardware Rendering
I did peruse the hardware rendering page on the Android Developer's website, but apparently not closely enough.
http://developer.android.com/guide/topics/graphics/hardware-accel.html
While it does mention that the API's are available beginning version 11, it does not say that Hardware Rendering is turned on for all applications by default, starting with API Level 14 (ICS).
What does this mean for us?
Almost everything is faster; except for the few things that don't work.
I managed to violate two of these, without realizing it:
Canvas.DrawTextOnPath()
Paint.setShadowLayer()
It's not mentioned in the API reference (or anywhere else I can find, and certainly not checked by Lint), but using any of the listed operations can do weird things.
In my case, Canvas.DrawTextOnPath() seemed to work just fine.
But when Android notice that the paint that I used on the hand had shadow layer set, it silently ignored it.
How do I know if my View is hardware accelerated?
From the documentation link above:
There are two different ways to check whether the application is hardware accelerated:
View.isHardwareAccelerated() returns true if the View is attached to a hardware accelerated window.
Canvas.isHardwareAccelerated() returns true if the Canvas is hardware accelerated
If you must do this check in your drawing code, use Canvas.isHardwareAccelerated() instead >of View.isHardwareAccelerated() when possible. When a view is attached to a hardware >accelerated window, it can still be drawn using a non-hardware accelerated Canvas. This >happens, for instance, when drawing a view into a bitmap for caching purposes.
In my case, the opposite appears to have occurred.
The custom view logs that it is not Hardware-accelerated; however, the canvas reports that it is hardware-accelerated.
Work Arounds and Fixings
The simplest fix is forcing the custom view to do software rendering. Per the documentation this can be accomplished by:
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Alternatively, you could remove the offending operations, and keep hardware rendering turned on.
Learn from my misfortune. Good luck, all.

I put it into init() and worked fine after that.
private void init() {
setLayerType(myView.LAYER_TYPE_SOFTWARE, null);
....
}

With myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); suggestion I can see hand. But I have still a problem: I see scale with only 0 written! As in the picture and two strage zeros out of the schema: (GALAXY NEXUS 4.2.1)
My drawScale() method is as in the example:
private void drawScale(Canvas canvas) {
canvas.drawOval(scaleRect, scalePaint);
canvas.save(Canvas.MATRIX_SAVE_FLAG);
for (int i = 0; i < totalNicks; ++i) {
float y1 = scaleRect.top;
float y2 = y1 - 0.020f;
canvas.drawLine(0.5f, y1, 0.5f, y2, scalePaint);
if ((i % 5) == 0) {
int value = nickToDegree(i);
if ((value >= minDegrees) && (value <= maxDegrees)) {
String valueString = Integer.toString(value);
canvas.drawText(valueString, 0.5f, y2 - 0.015f, scalePaint);
}
}
canvas.rotate(degreesPerNick, 0.5f, 0.5f);
}
canvas.restore();
}

in my case i made this:
AnalogView bar = (AnalogView) findViewById(R.id.AnalogBar);
bar.setLayerType(bar.LAYER_TYPE_SOFTWARE, null);
if (value_list.size()>0) bar.SetData(Double.parseDouble(value_list.get(value_list.size()-1)));
where SetData in AnalogView is
public void SetData(double data) {
setHandTarget((float)data);
invalidate();
}

On Galaxy S4 Android 4.4.2
TYPE_TEMPERATURE is deprecated
use
TYPE_AMBIENT_TEMPERATURE

For anyone having problems with text drawing on scale in the initialisation do this:
scalePaint.setLinearText(true);

Related

Unity Normal Maps don't work on Android device

I'm an experienced native iOS developer making my first foray into Android through Unity. I'm trying to set up a custom shader, but I'm having some trouble with the Normal maps. I've got them working perfectly in the Unity simulator on my computer, but when I build to an actual device (Samsung Galaxy S8+), the Normal maps don't work at all.
I'm using Mars as my test case. Here's the model running in the simulator on my computer:
And here's a screenshot from my device, running exactly the same code.
I've done a LOT of research, and apparently using Normal maps on Android with Unity is not an easy thing. There are a lot of people asking about it, but almost every answer I've found has said the trick is to override the texture import settings, and force it to be "Truecolor" which seems to be "RGBA 32 Bit" according to Unity's documentation. This hasn't helped me, though.
Another thread suggested reducing the Asino Level to zero, and another suggested turning off Mip Maps. I don't know what either of those are, but neither helped.
Here's my shader code, simplified but containing all references to Normal mapping:
void surf (Input IN, inout SurfaceOutputStandard o) {
half4 d = tex2D (_MainTex , IN.uv_MainTex);
half4 n = tex2D (_BumpMap , IN.uv_BumpMap);
o.Albedo = d.rgb;
o.Normal = UnpackNormal(n);
o.Metallic = 0.0;
o.Smoothness = 0.0;
}
I've seen some threads suggesting replacements for the "UnpackNormal()" function in the shader code, indicating that it might not be the thing to do on Android or mobile in general, but none of the suggested replacements have changed anything for better or worse: the normal maps continue to work in the simulator, but not on the device.
I've even tried making my own normal maps programmatically from a grayscale heightmap, to try to circumvent any import settings I may have done wrong. Here's the code I used, and again it works in the simulator but not on the device.
public Texture2D NormalMap(Texture2D source, float strength = 10.0f) {
Texture2D normalTexture;
float xLeft;
float xRight;
float yUp;
float yDown;
float yDelta;
float xDelta;
normalTexture = new Texture2D (source.width, source.height, TextureFormat.RGBA32, false, true);
for (int y=0; y<source.height; y++) {
for (int x=0; x<source.width; x++) {
xLeft = source.GetPixel (x - 1, y).grayscale * strength;
xRight = source.GetPixel (x + 1, y).grayscale * strength;
yUp = source.GetPixel (x, y - 1).grayscale * strength;
yDown = source.GetPixel (x, y + 1).grayscale * strength;
xDelta = ((xLeft - xRight) + 1) * 0.5f;
yDelta = ((yUp - yDown) + 1) * 0.5f;
normalTexture.SetPixel(x,y,new Color(xDelta,yDelta,1.0f,yDelta));
}
}
normalTexture.Apply();
return normalTexture;
}
Lastly, in the Build Settings, I've got the Platform set to Android and I've tried it using Texture Compression set to both "Don't Override" and "ETC (default)". The former was the original setting and the latter seemed to be Unity's suggestion both by the name and in the documentation.
I'm sure there's just some flag I haven't checked or some switch I haven't flipped, but I can't for the life of me figure out what I'm doing wrong here, or why there would be such a stubborn difference between the simulator and the device.
Can anyone help a Unity newbie out, and show me how these damn Normal maps are supposed to work on Android?
Check under:
Edit -> Project Settings -> Quality
Android is usually set to Fastest.

Path.offset() not working on every device

I have some problems with android.graphics.path.
I am developing a game. I have some Paths. They don't change there sizes, shapes. I draw them onto a canvas. I move them in every game's frame. So I set an offset: dx
It works fine on many devices:
Nokia X Dual Sim (Android 4.4.4 Cyanogenmode)(API level 19)
Samsung Galaxy Duos (GT-S7562)(Android 4.0.4)(API level 15)
Sony X8 (Android 2.3.7 Cyanogenmode)(API level 10)
and on some others
But it not works well on some other devices:
Samsung Galaxy Ace II (Android 4.1.2)(API level 16)
Nokia X Dual Sim (Nokia X platform 1.2)(Android Studio shows: API level 16)
and on some others
I set the offset in my draw method:
path.offset(dX, 0);
Not working means: it not moves. The system draws it to the original position.
But when it works, it works descent (moves, and it is fast)
I also tried with Matrixes:
translateMatrix = new Matrix();
translateMatrix.setTranslate(dX, 0);
path.transform(translateMatrix);
The same happened.
I see something on developer site of Android:
http://developer.android.com/reference/android/graphics/Path.html
void offset(float dx, float dy)
Offset the path by (dx,dy), returning true on success.
??? It must have changed, that's why here is an error
It may in connection with the Hardware Accelerated mode:
https://groups.google.com/forum/#!topic/android-developers/HgGVSbSghpk
I support from Api level 9, but I turned Harware Accelerated mode. (It works form API level 14)
The problem also shows up when I turn it off.
I don't see why is it sometimes work over API level 14, and sometimes why not?
It says, hardware accelerated mode supports Path:
http://android-developers.blogspot.de/2011/03/android-30-hardware-acceleration.html
Do you know what is the problem with this? What should I do?
Something that mixes it up more:
This works on every device (but works slower - it lags):
Canvas temp = new Canvas(bitmap);
path.draw(temp);
originalCanvas.drawBitmap(bitmap, 0, 0, myPaint);
So
I make a temporary canvas
I set it on a bitmap
I draw on that canvas (it will be on the bitmap)
I draw the bitmap onto the original canvas
Why does it work here?
Please help me how should I make it work.
I made it work, but not with the offset
I made my own offset function:
it saves the last position, and calculates the new one with the
offset.
Then it creates a new Path without using operator new
calculateNewPoints();
wallpath.rewind();
wallpath.moveTo(newPosX, newPosY);
.
.
.
wallpath.close();
It performs well :)
On API level 16 on manifest add the following attribute to the application
<application android:hardwareAccelerated="false" ...>
Indeed there seems to be a bug related to Path.offset() on older Android devices.
I have purchased an 8 years old "Samsung Galaxy S3" phone with Android 4.1.2 to test my word game and was surprised to discover, that Paths in the app are not filled:
My workaround is shown below, for older Android devices I do not use Path.offset() anymore, but instead Canvas.translate() and then draw the not offsetted Path object:
public class LetterTile extends RectF {
// consider anything older then Android 6.0 Marshmallow as an old device
public static final boolean TOO_OLD =
(Build.VERSION.SDK_INT < Build.VERSION_CODES.M);
private final Path mPathFill = new Path();
// mPathFillMoved is same as mPathFill, but offset by left, top
// it is used on newer devices to paint gradient over all tiles
private final Path mPathFillMoved = new Path();
public void draw(Canvas canvas, Paint gradientPaint) {
if (!TOO_OLD) {
// the mPathFillMoved only works on newer Android devices
canvas.drawPath(mPathFillMoved, gradientPaint);
}
canvas.save();
canvas.translate(left, top);
if (TOO_OLD) {
// on older Android devices just draw mPathFilled in plain yellow
canvas.drawPath(mPathFill, gradientPaint);
}
canvas.restore();

how can I check if my device is capable to render Emoji images correctly?

I'm using Emoji unicode in a View. On most devices the images appear ok, but on one of my low-end device(android 2.3) they are rendered as little squares.
Can I check whether the device support emoji? So that I can publish my apk while won't show that ugly squares on some devices.
This is a late answer but I recently ran into a similar problem. I needed to filter through a List<String> and filter out emojis that couldn't be rendered on the device (i.e., if the device was old and didn't support rendering them).
What I ended up doing was using Paint to measure the text width.
Paint mPaint = new Paint();
private boolean emojiRenderable(String emoji) {
float width = mPaint.measureText(emoji);
if (width > 7) return true;
return false;
}
The width > 7 part is particularly hacky, I would expect the value to be 0.0 for non-renderable emoji, but across a few devices, I found that the value actually ranged around 3.0 to 6.0 for non-renderable, and 12.0 to 15.0 for renderable. Your results may vary so you might want to test that. I believe the font size also has an effect on the output of measureText() so keep that in mind.
Overall I am not sure if this is a great solution but it's the best that I've come up with so far.
please check the source code from Googles Mozc project.
The EmojiRenderableChecker class seems to work pretty well!
https://github.com/google/mozc/blob/master/src/android/src/com/google/android/inputmethod/japanese/emoji/EmojiRenderableChecker.java
it's like a compat version for Paint.hasGlypgh (added in Marshmallow).
https://developer.android.com/reference/android/graphics/Paint.html#hasGlyph(java.lang.String)
https://android.googlesource.com/platform/packages/inputmethods/LatinIME/+/master/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java#441
Inspired from the two methods found in the above file.
public static boolean canShowEmoji(String emoji) {
Paint paint = new Paint();
float tofuWidth = paint.measureText("\uFFFE");
float standardWidth = paint.measureText("\uD83D\uDC27");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return paint.hasGlyph(emoji);
} else {
float emojiWidth = paint.measureText(emoji);
return emojiWidth > tofuWidth && emojiWidth < standardWidth * 1.25;
// This assumes that a valid glyph for the cheese wedge must be greater than the width
// of the noncharacter.
}
}

Android Canvas drawText not working

I made a custom view using canvas's drawText method. Somehow none of the text is showing on any of the Jelly Bean devices. It works fine for ICS and below.
Does anyone know if anything has changed from API 15 to 16 for this method or any related methods?
Edit Code: (from the draw method where canvas is supplied as a parameter)
paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(3);
paint.setColor(context.getResources().getColor(R.color.plot_background));
canvas.drawRect(new Rect(0,0,getWidth(),getHeight()), paint);
paint.setColor(color_text);
paint.setTextSize(getScaled(18.5f));
paint.setTextAlign(Align.CENTER);
canvas.drawText(title, (graphwidth / 2) + horstart, border/2+15, paint);
I know the line is been executed and the coordinates are correct because the same code works on the older platforms.
Thanks Eric. Figured out the error. I scale everything in the app base on canvas.getDensity(). getDensity() at the moment the draw function is ALWAYS 0 for jelly bean devices for some reason. But it does return the correct value for anything between 1.6 -> 4.0.3
I didn't post the code for that (which is my fault) is because I didn't suspect getDensity() to be the problem since it never did in the last two years while the app is in the market.
The workaround was to modify the getScaled function.
public float getScaled(Canvas canvas,float in){
return in * ( canvas.getDensity()==0 ? 1 : canvas.getDensity()/ 160.0f);
}
The documentation does say that DENSITY_NONE could be returned but I think what might have happened is that in Jelly Bean does the scaling for you since if I just multiply it by 1, it works as a charm on the two different density device that I just tested on.
(P.S. Can anyone familiar the internals of Android OS correct me if I am wrong or confirm it? )

Android ACTION_MOVE Threshold

I'm writing an app that involves writing on the screen using one's finger, or eventually a stylus. I have that part working. On ACTION_DOWN, starts drawing; on ACTION_MOVE, adds line segments; on ACTION_UP, finishes line.
The problem is that after ACTION_DOWN, apparently the pointer needs to move more than 10 pixels away from where it started (basically a 20x20 box around the starting point) in order to begin sending ACTION_MOVE events. After leaving the box, the move events are all quite accurate. (I figured out the 10 pixel thing by testing it.) Since this is meant to be used for writing or drawing, 10 pixels is a fairly significant loss: depending on how small you're trying to write, you can lose the first letter or two. I haven't been able to find anything about it - only a couple posts on a forum or two, like http://android.modaco.com/topic/339694-touch-input-problem-not-detecting-very-small-movements/page_pid_1701028#entry1701028. It seems to be present on some devices or systems and not others. No ideas as to how to get rid of it when you have it, though.
I'm using a Galaxy Tab 10.1, with Android 3.1. I've tried several different things to try to get rid of it: I've tried setting the event's coords to something else to see if I could trick it into thinking the cursor was in a different place; I tried re-dispatching the event with the coords changed (my handler reacted to the new points, but still didn't respond to movements in the 10-pixel radius.) I've searched through source code for any references to the effect, and found none (though I think it's from a different version of Android - code for 3.1 isn't released yet, is it?) I've searched for methods of querying the current state of the pointers, so I could just have a timer catch the changes until the pointer crossed the threshold. Couldn't find any way of getting pointer coords without a corresponding movement event. Nothing worked. Does anybody know anything about this, or have any ideas or work-arounds? Thank you.
-- Update: Drag and drop events show the same threshold.
I agree in part with the post by #passsy but come to a different conclusion. Firstly as mentioned, the mTouchSlop is the value that we are interested in and is exposed via ViewConfiguration.get(context).getScaledTouchSlop();
If you check the Android source for the ViewConfiguraton, the default value for TOUCH_SLOP is 8dip, but the comments mention that this value is a fallback only, and the actual value is defined when the Android OS for that specific device is built. (it may be more or less than this value. It appears to hold true for the Galaxy Tab devices)
More specific to the code sample, the mTouchSlop value is read from the ViewConfiguration when the View is initialised, but the value is only accessed in the onTouchEvent method. If you extend View and override this method (without calling super) then the behaviour of mTouchSlop in the View class is no longer relevant.
More telling (to us) was that when changing the Android settings to overlay touch events on the screen, a touch with a small drag does not register as a motion event, highlighted by the fact that the crosshairs from the Android OS do not move. From this our conclusion is that the minimal drag distance is being enforced at the OS level and your application will never be aware of drag events smaller than the TOUCH_SLOP value. You should also be aware that TOUCH_SLOP should not be used directly and the API deprecates the getTouchSlop method and recommends getScaledTouchSlop which takes the device screen size and pixel density into account. A side effect of this is that the actual minimum stroke length as perceived on different devices may vary. eg on a Galaxy Tab 2.0 7.0" it feels like we are able to draw shorter minimum strokes using the same code base than when running on a Galaxy Tab 2.0 10.1"
You should also be aware that (if you find a way to alter this value), this value determines how Android systems distinguish between taps and strokes. That is if you tap the screen but your finger moves slightly while performing the tap, it will be interpreted as a tap if it moved less than TOUCH_SLOP, but as a stroke if it moved more than TOUCH_SLOP. Therefore setting TOUCH_SLOP to a smaller value will increase the chance that a tap will be interpreted as a stroke.
Our own conclusion is that this minimum distance is not something that can be changed in practice and is something we need to live with.
The problem is on Line 6549 in class View https://github.com/android/platform_frameworks_base/blob/master/core/java/android/view/View.java
if (!pointInView(x, y, mTouchSlop)) {...}
/**
* Utility method to determine whether the given point, in local coordinates,
* is inside the view, where the area of the view is expanded by the slop factor.
* This method is called while processing touch-move events to determine if the event
* is still within the view.
*/
private boolean pointInView(float localX, float localY, float slop) {
return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
localY < ((mBottom - mTop) + slop);
}
mTouchSlop is set in the constructor
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
You can extend View and set mTouchSlop to zero. I don't see an other way to set mTouchSlop. There is no function like getApplicationContext.setScaledTouchSlop(int n).
Extend View Class.
Override pointInView method without "#Override" annotaion and set touchSlop = 0:
public boolean pointInView(float localX, float localY, float slop) {
slop = 0;
return localX >= -slop && localY >= -slop && localX < (getWidth() + slop) &&
localY < (getBottom() + slop);
}

Categories

Resources