SilentKiller
SilentKiller

Reputation: 6942

How to Rotate Circle with text on Canvas in Blackberry

How to Rotate Circle with Text on TouchEvent or on TrackBallMoveEvent.

  1. How do I create this kind of circle?

    I had created a circle and rotated it also, but it always starts from 0 degrees.

  2. Is there any other option to create this kind of circle?

Each circle have different text and each of the circles can move independently.

enter image description here

Upvotes: 3

Views: 592

Answers (1)

Nate
Nate

Reputation: 31045

So, this is definitely not complete, but I think it's most of what you need.

Limitations/Assumptions

  1. I have so far only implemented touch handling, as I think that's more difficult. If I get time later, I'll come back and add trackball handling.
  2. I did not give the spinning discs any momentum. After the user's finger leaves the disc, it stops spinning.
  3. I'm not sure the focus transitions between discs are 100% right. You'll have to do some testing. They're mostly right, at least.
  4. When you mentioned Canvas in the title, I assumed that didn't mean you required this to utilize the J2ME Canvas. Writing BlackBerry apps with the RIM UI libraries is pretty much all I've done.

Solution

Essentially, I created a Field subclass to represent each disc. You create the field by passing in an array of labels, to be spaced around the perimeter, a radius, and a color. Hardcoded in each DiscField is an edge inset for the text, which kind of assumes a certain size difference between discs. You should probably make that more dynamic.

public class DiscField extends Field {

   /** Used to map Manager's TouchEvents into our coordinate system */
   private int _offset = 0;
   private int _radius;
   private int _fillColor;
   private double _currentRotation = 0.0;
   private double _lastTouchAngle = 0.0;
   private boolean _rotating = false;
   private String[] _labels;
   /** Text inset from outer disc edge */
   private static final int INSET = 30;  

   private DiscField() {      
   }

   public DiscField(String[] labels, int radius, int fillColor) {
      super(Field.FOCUSABLE);
      _labels = labels;
      _radius = radius;
      _fillColor = fillColor;
   }    

   protected void layout(int width, int height) {
      setExtent(Math.min(width, getPreferredWidth()), Math.min(height, getPreferredHeight()));
   }

   private void drawFilledCircle(Graphics g, int x, int y, int r) {
      // http://stackoverflow.com/a/1186851/119114
      g.fillEllipse(x, y, x + r, y, x, y + r, 0, 360);
   }

   private void drawCircle(Graphics g, int x, int y, int r) {
      g.drawEllipse(x, y, x + r, y, x, y + r, 0, 360);
   }

   protected void paint(Graphics graphics) {
      int oldColor = graphics.getColor();
      graphics.setColor(_fillColor);
      drawFilledCircle(graphics, _radius, _radius, _radius);
      graphics.setColor(Color.WHITE);
      drawCircle(graphics, _radius, _radius, _radius);

      // plot the text around the circle, inset by some 'padding' value
      int textColor = (_fillColor == Color.WHITE) ? Color.BLACK : Color.WHITE; 
      graphics.setColor(textColor);
      // equally space the labels around the disc
      double interval = (2.0 * Math.PI / _labels.length);
      for (int i = 0; i < _labels.length; i++) {
         // account for font size when plotting text
         int fontOffsetX = getFont().getAdvance(_labels[i]) / 2;
         int fontOffsetY = getFont().getHeight() / 2;
         int x = _radius + (int) ((_radius - INSET) * Math.cos(i * interval - _currentRotation)) - fontOffsetX;
         int y = _radius - (int) ((_radius - INSET) * Math.sin(i * interval - _currentRotation)) - fontOffsetY;
         graphics.drawText(_labels[i], x, y);
      }

      graphics.setColor(oldColor);
   }

   protected void drawFocus(Graphics graphics, boolean on) {
      if (on) {
         int oldColor = graphics.getColor();
         int oldAlpha = graphics.getGlobalAlpha();
         // just draw a white shine to indicate focus
         graphics.setColor(Color.WHITE);
         graphics.setGlobalAlpha(80);
         drawFilledCircle(graphics, _radius, _radius, _radius);
         // reset graphics context
         graphics.setColor(oldColor);
         graphics.setGlobalAlpha(oldAlpha);
      }
   }  

   protected void onUnfocus() {
      super.onUnfocus();
      _rotating = false;
   }

   protected boolean touchEvent(TouchEvent event) {
      switch (event.getEvent()) {
      case TouchEvent.MOVE: {
         setFocus();         
         // Get the touch location, within this Field
         int x = event.getX(1) - _offset - _radius;
         int y = event.getY(1) - _offset - _radius;
         if (x * x + y * y <= _radius * _radius) {
            double angle = MathUtilities.atan2(y, x);
            if (_rotating) {
               // _lastTouchAngle only valid if _rotating
               _currentRotation += angle - _lastTouchAngle;
               // force a redraw (paint) with the new rotation angle
               invalidate();
            } else {
               _rotating = true;
            }         
            _lastTouchAngle = angle;

            return true;
         }
      }
      case TouchEvent.UNCLICK:
      case TouchEvent.UP: {
         _rotating = false;
         return true;      
      }  
      case TouchEvent.DOWN: {
         setFocus();                 
         int x = event.getX(1) - _offset - _radius;
         int y = event.getY(1) - _offset - _radius;
         if (x * x + y * y <= _radius * _radius) {
            _lastTouchAngle = MathUtilities.atan2(y, x);
            _rotating = true;
            return true;
         }
      }
      default:
         break;
      }           
      return super.touchEvent(event);
   }

   protected boolean trackwheelRoll(int arg0, int arg1, int arg2) {
      return super.trackwheelRoll(arg0, arg1, arg2);
      // TODO!
   }

   public int getPreferredHeight() {
      return getPreferredWidth();
   }

   public int getPreferredWidth() {
      return 2 * _radius;
   }

   public String[] getLabels() {
      return _labels;
   }

   public void setLabels(String[] labels) {
      this._labels = labels;
   }

   public int getRadius() {
      return _radius;
   }

   public void setRadius(int radius) {
      this._radius = radius;
   }

   public double getCurrentAngle() {
      return _currentRotation;
   }

   public void setCurrentAngle(double angle) {
      this._currentRotation = angle;
   }

   public int getOffset() {
      return _offset;
   }

   public void setOffset(int offset) {
      this._offset = offset;
   }
}

Containing all the DiscField objects is the DiscManager. It aligns the child DiscFields in sublayout(), and handles proper delegation of touch events ... since the fields overlap, and a touch within a DiscFields extent that does not also fall within its radius (i.e. the corners) should be handled by a larger disc.

   /** 
    * A DiscManager is a container for DiscFields and manages proper delegation
    * of touch event handling.
    */
   private class DiscManager extends Manager {

      private int _maxRadius = 0;

      public DiscManager(long style){
         super(style);

         DiscField outerDisc = new DiscField(new String[] { "1", "2", "3", "4", "5", "6" }, 
               180, Color.BLUE);
         _maxRadius = outerDisc.getRadius();
         DiscField middleDisc = new DiscField(new String[] { "1", "2", "3", "4", "5" }, 
               120, Color.GRAY);
         middleDisc.setOffset(_maxRadius - middleDisc.getRadius());
         DiscField innerDisc = new DiscField(new String[] { "1", "2", "3", "4" }, 
               60, Color.RED);
         innerDisc.setOffset(_maxRadius - innerDisc.getRadius());

         // order matters here:
         add(outerDisc);
         add(middleDisc);
         add(innerDisc);
      }

      protected void sublayout(int width, int height) {
         setExtent(2 * _maxRadius, 2 * _maxRadius);

         // each disc needs to have the same x,y center to be concentric
         for (int i = 0; i < getFieldCount(); i++) {
            if (getField(i) instanceof DiscField) {
               DiscField disc = (DiscField) getField(i);
               int xCenter = _maxRadius - disc.getRadius();
               int yCenter = _maxRadius - disc.getRadius();
               setPositionChild(disc, xCenter, yCenter);
               layoutChild(disc, 2 * _maxRadius, 2 * _maxRadius);
            }
         }
      }

      protected boolean touchEvent(TouchEvent event) {
         int eventCode = event.getEvent();
         // Get the touch location, within this Manager
         int x = event.getX(1);
         int y = event.getY(1);

         if ((x >= 0) && (y >= 0) && (x < getWidth()) && (y < getHeight())) {
            int field = getFieldAtLocation(x, y);
            if (field >= 0) {
               DiscField df = null;
               for (int i = 0; i < getFieldCount(); i++) {
                  if (getField(field) instanceof DiscField) {
                     int r = ((DiscField)getField(field)).getRadius();
                     // (_maxRadius, _maxRadius) is the center of all discs
                     if ((x - _maxRadius) * (x - _maxRadius) + (y - _maxRadius) * (y - _maxRadius) <= r * r) {
                        df = (DiscField)getField(field);
                     } else {
                        // touch was not within this disc's radius, so the one slightly bigger
                        // should be passed this touch event                        
                        break;
                     }
                  }
               }
               // Let event propagate to child field
               return (df != null) ? df.touchEvent(event) : super.touchEvent(event);
            } else {
               if (eventCode == TouchEvent.DOWN) {                    
                  setFocus();
               }
               // Consume the event
               return true;
            }
         }
         // Event wasn't for us, let superclass handle in default manner
         return super.touchEvent(event);
      }
   }

Finally, a screen to use them:

public class DiscScreen extends MainScreen {

   public DiscScreen() {
      super(MainScreen.VERTICAL_SCROLL | MainScreen.VERTICAL_SCROLLBAR);

      add(new DiscManager(Field.USE_ALL_WIDTH));
   }
}

Results

enter image description here

Upvotes: 7

Related Questions