3

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

Community
  • 1
  • 1
SilentKiller
  • 6,944
  • 6
  • 40
  • 75
  • Is the text **dynamic**? In other words, does it need to be plotted by your app *as text*, or could you just embedd the text into images that would be bundled with your app? If you did that, then the problem would only be about how to rotate three circle images based on user input, which seems easier. But, if the text needs to change, then obviously that wouldn't work. – Nate Dec 01 '12 at 07:07
  • @Nate the text are fixed stored as array.. when i rotate text angle fluctuate when it chacges from 360 to 0 on "TouchEvent.MOVE"... – SilentKiller Dec 01 '12 at 07:21
  • How are you supposed to use the trackball for this? Is each of the 3 circles a separate, focusable control? You set focus on a wheel, and then the trackball movement spins it? Maybe you can describe a bit more how this is supposed to be used ... I'm having a hard time understanding how you want this to work. – Nate Dec 05 '12 at 00:05
  • @Nate yes on up and down i'll give focus to circles and on right and left the circle will move.... – SilentKiller Dec 05 '12 at 04:14
  • Have you looked this Code.. http://code.google.com/p/radial-menu-widget/downloads/list – MKJParekh Dec 05 '12 at 07:15
  • @MKJParekh this is a dilar it does not rotate 360 degree whole... – SilentKiller Dec 05 '12 at 09:29
  • so? did I mention anywhere that this will solve your problem?.. I just seen something that from that you can get *inspiration* to make own custom view. I thought I was wrong. – MKJParekh Dec 05 '12 at 09:46

1 Answers1

7

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

Nate
  • 31,017
  • 13
  • 83
  • 207