6

I have custom view called ArrowView.

When this view is part of a layout which is part of an ArrayAdapter everything is fine. (It is repainted as the underlying data changes.)

When I use that same layout as part of another Adapter the ArrowView doesn't repaint.

I have debugged it, and found that onDraw is called, with different values for rot, when I expect it to be - it is just that the screen is not updating.

Do I need to alter my ArrowView so that it repaints properly?

public class ArrowView extends View {

    private Path path;
    public final Paint paint;
    public final Paint circlePaint;
    private float rot = 30;
    private float length = 0.5f;
    private float width = 1.0f;
    public final static int SIZE = 96;
    private float size = SIZE;
    private float xOff;
    private float yOff;

    public ArrowView(Context context, AttributeSet attrs) {
        super(context, attrs);

        path = new Path();
        path.moveTo( 0.0f,  -0.5f);
        path.lineTo( 0.5f,   0.0f);
        path.lineTo( 0.25f,  0.0f);
        path.lineTo( 0.25f,  0.5f);
        path.lineTo(-0.25f,  0.5f);
        path.lineTo(-0.25f,  0.0f);
        path.lineTo(-0.5f,   0.0f);
        path.close();

        paint = new Paint();
        paint.setARGB(150, 0, 150, 0);

        circlePaint = new Paint();
        circlePaint.setARGB(50, 150, 150, 150);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.save();
        canvas.translate(xOff, yOff);
        canvas.scale(size, size);
        canvas.save();
        canvas.translate(0.5f, 0.5f);
        canvas.drawCircle(-0.0f, -0.0f, 0.5f, circlePaint);
        canvas.save();
        canvas.rotate(rot, 0.0f, 0.0f);
        canvas.save();
        canvas.scale(width, length);
        canvas.drawPath(path, paint);
        canvas.restore();
        canvas.restore();
        canvas.restore();
        canvas.restore();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        //Get the width measurement
        int widthSize = getMeasurement(widthMeasureSpec,
                SIZE);

        //Get the height measurement
        int heightSize = getMeasurement(heightMeasureSpec,
                SIZE);

        size = min(widthSize, heightSize) * 0.9f;
        xOff = 0.0f;
        yOff = 0.05f * size;

        //MUST call this to store the measurements
        setMeasuredDimension(widthSize, heightSize);
    }

    public static int getMeasurement(int measureSpec, int contentSize) {
        int specMode = View.MeasureSpec.getMode(measureSpec);
        int specSize = View.MeasureSpec.getSize(measureSpec);
        int resultSize = 0;
        switch (specMode) {
            case View.MeasureSpec.UNSPECIFIED:
                //Big as we want to be
                resultSize = contentSize;
                break;
            case View.MeasureSpec.AT_MOST:
                //Big as we want to be, up to the spec
                resultSize = min(contentSize, specSize);
                break;
            case View.MeasureSpec.EXACTLY:
                //Must be the spec size
                resultSize = specSize;
                break;
        }

        return resultSize;
    }

    public void setLength(float length) {
        this.length = length;
        invalidate();
    }

    public float getLength() {
        return length;
    }

    public void setWidth(float width) {
        this.width = width;
        invalidate();
    }

    public void setRot(float rot) {
        this.rot = rot;
        invalidate();
    }

}
fadedbee
  • 42,671
  • 44
  • 178
  • 308
  • Have you tried to invalidate your view in your adapter? – Groco Oct 14 '15 at 15:24
  • 1
    Try to call notifydatasetchanged() on array adapter and do call invalidate() method, If it's non UI call postInvalidate() on Arrowview. – Madhukar Hebbar Oct 19 '15 at 07:18
  • 1
    @MadhukarHebbar Thanks, postInvalidate() did the trick. If you write an answer, I will award the 50 point bounty. – fadedbee Oct 20 '15 at 19:53
  • 1
    @chrisdew just be careful that postInvalidate() have some issues when called from other threads. using invalidate() and runOnUiThread is often preferred – Luca S. Oct 20 '15 at 19:59
  • @LucaS. I'm calling `postInvalidate()` from the UI thread, so I should be safe... `invalidate()` didn't work for me. – fadedbee Oct 21 '15 at 13:17

2 Answers2

5

I also faced the same problem when i want to update an view from the non-ui thread and fragment; invalidate() method not updating the view. So as per the sdk reference

http://developer.android.com/intl/es/reference/android/view/View.html postInvalidate() will solve the issue.

This can be done in two ways or more!!!.

Calling runOnUi thread in setter method

runOnUiThread(new Runnable() {
    public void run() {
     this.length = length;
    this.invalidate();
    }
});

Or Just call postInvalidate() in setter method.

Madhukar Hebbar
  • 3,113
  • 5
  • 41
  • 69
1

if onDraw() is called but the view doesn't update itself, the cause may be that it is called on the wrong thread.

In your adapter be sure to handle your view from a UI Thread

 getActivity().runOnUiThread(new Runnable() {
    @Override
    public void run() {
    mAdapter.updateRot(newRot);
 });

 public void updateRot(double newRot){
     arrowView.setRot(newRot);
     notifyDataSetChanged();
 }

from the adapter use notifyDataSetChanged() to refresh the view

notifyDataSetChanged(): Notifies the attached observers that the underlying data has been changed and any View reflecting the data set should refresh itself.

Luca S.
  • 1,132
  • 2
  • 16
  • 31