2

The code below is a custom view - it draws a circle, adds notches according to a scale and adds the scale text. This was derived from Mind The Robot's excellent tutorial on creating a vintage thermometer. http://mindtherobot.com/blog/272/android-custom-ui-making-a-vintage-thermometer/

This code works fine on devices running up to Jelly Bean 4.1.2 but breaks on 4.2. On 4.2 the numbers no longer get drawn around the circle but seem to be spread all over the screen.The code worked fine on a Nexus 7 until it got the 4.2 update so it can't be a device issue. I have tested it on a Nexus S running 4.1.2 and a Nexus 4 running 4.2 it works fine on the Nexus S but not on the Nexus 4.

Unfortunately as a new user I can't post screenshots, I'll try to describe it: The numbers display correctly for the first half of the dial, the rest of the numbers are scattered across the screen.

I have looked at the 4.2 change log but I can't see anything that would cause this to happen. I have looked for similar issues on-line but these all seem to be to do with hardware acceleration - I have tried various combinations of setting hardware acceleration in the manifest but nothing has any impact.

I'd really appreciate any input on what might be causing this to happen.

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.View;

public class AneroidView extends View {


    // drawing tools
    private RectF rimRect;

    private RectF faceRect;

    private Paint scalePaint;
    private RectF scaleRect;

    private Paint backgroundPaint; 
    // end drawing tools

    private Bitmap background; // holds the cached static part

    private int totalNotches = 130;
    private int incrementPerLargeNotch = 10;
    private int incrementPerSmallNotch = 1;
    private float degreesPerNotch = 360.0f / totalNotches;  

    private int scaleCenterValue = 1000; // the one in the top center (12 o'clock)
    private int scaleMinValue = 935;
    private int scaleMaxValue = 1065;


    public AneroidView(Context context) {
        super(context);
        init(context, null);
    }

    public AneroidView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public AneroidView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {

        rimRect = new RectF(0.1f, 0.1f, 0.9f, 0.9f);

        float rimSize = 0.02f;
        faceRect = new RectF();
        faceRect.set(rimRect.left + rimSize, rimRect.top + rimSize, 
                 rimRect.right - rimSize, rimRect.bottom - rimSize);        


        scalePaint = new Paint();
        scalePaint.setStyle(Paint.Style.STROKE);
        scalePaint.setColor(Color.rgb(49, 79, 79));
        scalePaint.setStrokeWidth(0.005f);
        scalePaint.setAntiAlias(true);

        scalePaint.setTextSize(0.045f);
        scalePaint.setTypeface(Typeface.SANS_SERIF);
        scalePaint.setTextScaleX(0.8f);
        scalePaint.setTextAlign(Paint.Align.CENTER);        

        // The scale rectangular is located .10 from the outer rim.
        float scalePosition = 0.10f;

        scaleRect = new RectF();
        scaleRect.set(faceRect.left + scalePosition, faceRect.top + scalePosition,
                      faceRect.right - scalePosition, faceRect.bottom - scalePosition);

            }

    private void drawScale(Canvas canvas) {
        // Draw a large notch every large increment, and a small
        // notch every small increment.

        canvas.drawOval(scaleRect, scalePaint);

        canvas.save(Canvas.MATRIX_SAVE_FLAG);
        for (int i = 0; i < totalNotches; ++i) {
            float y1 = scaleRect.top;
            float y2 = y1 - 0.015f;
            float y3 = y1 - 0.025f;

            int value = notchToValue(i);

            if (i % (incrementPerLargeNotch/incrementPerSmallNotch) == 0) {
                if (value >= scaleMinValue && value <= scaleMaxValue) {
                    // draw a nick
                    canvas.drawLine(0.5f, y1, 0.5f, y3, scalePaint);

                    String valueString = Integer.toString(value);
                    // Draw the text 0.15 away from y3 which is the long nick.
                    canvas.drawText(valueString, 0.5f, y3 - 0.015f, scalePaint);
                }
            }
            else{
                if (value >= scaleMinValue && value <= scaleMaxValue) {
                    // draw a nick
                    canvas.drawLine(0.5f, y1, 0.5f, y2, scalePaint);
                }
            }

            canvas.rotate(degreesPerNotch, 0.5f, 0.5f);
        }
        canvas.restore();       
    }

    private int notchToValue(int value) {
        int rawValue = ((value < totalNotches / 2) ? value : (value - totalNotches)) * incrementPerSmallNotch;
        int shiftedValue = rawValue + scaleCenterValue;
        return shiftedValue;
    }


    private void drawBackground(Canvas canvas) {
        if (background != null)         
            canvas.drawBitmap(background, 0, 0, backgroundPaint);
    }

    @Override
    protected void onDraw(Canvas canvas) {      

        drawBackground(canvas);

        float scale = (float) getWidth();       
        canvas.save(Canvas.MATRIX_SAVE_FLAG);
        canvas.scale(scale, scale); 

        canvas.restore();

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        regenerateBackground();
    }

    private void regenerateBackground() {
        // free the old bitmap
        if (background != null) {
            background.recycle();
        }

        background = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
        Canvas backgroundCanvas = new Canvas(background);
        float scale = (float) getWidth();       
        backgroundCanvas.scale(scale, scale);

        drawScale(backgroundCanvas);
    }
}
user675067
  • 23
  • 3
  • What have you figured out? Does it crash? Do you have `Log.d` sprinkled around the source and showing within the logcat? – t0mm13b Jan 18 '13 at 18:40
  • The answer is probably buried in the [change log.](http://developer.android.com/sdk/api_diff/17/changes.html) – Todd Sjolander Jan 18 '13 at 18:58
  • 1
    It doesn't crash, it runs without any errors. The problem is that the scale doesn't display correctly. I have been through the change log, I can't find anything that would have an effect; clearly I'm missing something in there, but I'm stumped. – user675067 Jan 18 '13 at 19:21
  • Hmm, that is a very peculiar effect - and as you say, having tested on 4.1.2 and 4.2 it seems to be an issue with the new OS version. I've also scanned the changelog and can't find anything that seems relevant to this problem. In the hope that someone else can help, here are some screenshots to illustrate the problem :) ![Dial display working correctly on 4.1.2](http://i.stack.imgur.com/mPs7X.png) ![Dial display doing crazy spiral stuff on 4.2](http://i.stack.imgur.com/rwVGo.png) – BasicPleasureModel Jan 22 '13 at 13:33

3 Answers3

4

Add scalePaint.setLinearText(true);

It will work better but text spacing may look bad.

See the threads below:

Android 4.2 on Nexus 7: canvas.drawText() not working correctly

Android 4.2.1 wrong character kerning (spacing)

Community
  • 1
  • 1
  • spot on - as you say it will now draw the scale around the circle, all be it with slightly dodgy text spacing - many thanks :) – user675067 Jan 30 '13 at 11:18
1

I managed to work around the problem by using scalePaint.setLinearText(true) to get around drawing the text characters in one place, setting textSize > 1.0f to get around the kerning problem, and then using canvas.scale(float, float) to get the font to the right size. It is ugly and a pain, but it works for me.

Kaleb
  • 1,855
  • 1
  • 18
  • 24
1

Here's another work around for the kerning issue. drawTextOnPath works so...

Replace this:
//canvas.drawText("Smushed text.", 0.5f, 0.7F, myTextPaint);

with this:

private Path strightPath; <br>
strightPath = new Path(); <br>
strightPath.moveTo(0.1f, 0.5f);<br>
strightPath.lineTo(0.9f, 0.5f); <br>
canvas.drawTextOnPath("This text is not smushed together.", strightPath, 0.0f, 0.2f, myTextPaint);