Android Api. This could be useful as a volume control.
This example demonstrates
- how to extend ImageView for custom views
- how to setup an appropriate listener to notify the Activity
- how to calculate the angle theta of a unit vector w.r.t. the origin in a 2D coordinate system using spherical coordinates
Save it in your res/drawable folder. I called it "jog.png".
Next, add some stuff to res/layout/main.xml:
<myprojects.android.RotaryKnobView android:id="@+id/jogView" android:layout_width="300px" android:layout_height="300px" android:layout_gravity="center" />
This is the java code for the rotary knob widget:
package myprojects.android; import android.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.ImageView; public class RotaryKnobView extends ImageView { private float angle = 0f; private float theta_old=0f; float width = 300; float height = 300; private RotaryKnobListener listener; public interface RotaryKnobListener { public void onKnobChanged(int arg); } public void setKnobListener(RotaryKnobListener l ) { listener = l; } public RotaryKnobView(Context context) { super(context); // TODO Auto-generated constructor stub } public RotaryKnobView(Context context, AttributeSet attrs) { super(context, attrs); initialize(); } public RotaryKnobView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initialize(); } private float getTheta(float x, float y) { float sx = x - (width / 2.0f); float sy = y - (height / 2.0f); float length = (float)Math.sqrt( sx*sx + sy*sy); float nx = sx / length; float ny = sy / length; float theta = (float)Math.atan2( ny, nx ); final float rad2deg = (float)(180.0/Math.PI); float theta2 = theta*rad2deg; return (theta2 < 0) ? theta2 + 360.0f : theta2; } public void initialize() { this.setImageResource(R.drawable.jog); setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub int action = event.getAction(); int actionCode = action & MotionEvent.ACTION_MASK; if (actionCode == MotionEvent.ACTION_POINTER_DOWN) { float x = event.getX(0); float y = event.getY(0); theta_old = getTheta(x, y); } else if (actionCode == MotionEvent.ACTION_MOVE) { invalidate(); float x = event.getX(0); float y = event.getY(0); float theta = getTheta(x,y); float delta_theta = theta - theta_old; theta_old = theta; int direction = (delta_theta > 0) ? 1 : -1; angle += 3*direction; notifyListener(direction); } return true; } }); } private void notifyListener(int arg) { if (null!=listener) listener.onKnobChanged(arg); } protected void onDraw(Canvas c) { c.rotate(angle,150,150); super.onDraw(c); } }
Note that instead of the actual angle delta the argument to onKnobChanged is +1, when the knob is rotated right, and -1 otherwise. You could change that of course. The size of the widget is a bit cluttered all over the place ... you should clean that.
Finally, we create a listener in the main activity:
/** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); RotaryKnobView jogView = (RotaryKnobView)findViewById(R.id.jogView); jogView.setKnobListener(new RotaryKnobView.RotaryKnobListener() { @Override public void onKnobChanged(int arg) { if (arg > 0) ; // rotate right else ; // rotate left }); }
That's it.

Awesome piece of code dude, Just what I was looking for. i'm itching to get it up and running now.
ReplyDeletethanks buddy!
ReplyDeletelet me know if you can get it to work.
You might also check out
http://mindtherobot.com/blog/534/android-ui-making-an-analog-rotary-knob/
- thomas
hi, yeah I tried checking out that link before but my browser gives me a malware warning on access. So I've been staying clear, maybe I@ll try from work tomorrow ;)
ReplyDeleteWhile I'm back here though, and now I've had a play with it, yeah I got it working, The only thing I changed is the "c.rotate(angle,150,150);" to "c.rotate(angle,(width/2),(height/2)); " makes it a bit neater when adjusting the dimensions, which I note have to be in px, other wise all kinds of fun start happening ;)
I have a question, I have a Radio player on my Sam Galaxy, and this has a rotary knob too, but their knob appears to focus under your finger constantly, I have to admit I would prefer this kind of control, have you seen it or would you know how to adapt your code to do this?
TR.
Yeah I noticed the malware warning. This is odd, but I ignored it and opened the site.
ReplyDeleteChanging angle,150,150 to the right thing is good of course.
If I got you right (I don't know the player on the Galaxy) it should be simple to adjust my code
to your needs: when the view is touched, you need to set the current angle to theta.
It might be enough to add
angle = theta_old;
behind the line
theta_old = getTheta(x, y);
in the POINTER_DOWN branch; maybe there's more work to do, that also depends on what you want to do. I kept it simple and just send a +1 if it turns right, -1 otherwise. You might need to adjust that too.
Thanks for getting back so quick. I did try your suggestions, but it makes the knob a big shaky. I wish I could work out your code to have a go myself, but it really is a little above my level of java and maths. Its working well at the moment anyway, I added a different job image which minimizes the misalignment. Your implementation of +1, -1 is perfect for my need so all is good.
ReplyDeletecheers
T.R.
hi, I worked out the finger tracking part. I noticed the angle and theta are 90deg off, so I added this bit of code, and all is well, of course I'm sure I could have just changed my png also!
ReplyDeleteangle = theta - 270;
anyway, cheers its working a treat.
T.R.
Thanks for the code man!
ReplyDeleteI am new to android programming and i want to make two knobs in the same activity. do you know how i can switch between the two ids ?
i get the same value for both knob.
just give them individual ids, like
ReplyDeleteandroid:id="@+id/jogView1"
...
android:id="@+id/jogView2"
in your code:
RotaryKnobView firstJogView = (RotaryKnobView)findViewById(R.id.jogView1);
...
RotaryKnobView secondJogView = (RotaryKnobView)findViewById(R.id.jogView2);
give each of them his listener and it should work.
thomas
First, thank you Thomas for sharing :) it means a lot.
ReplyDeleteI'm complete novice so can you please tell me how can I pass value (angle)from rotaryKnob class to a textView in main activity. I know, it's dumb, sorry :)
you could add the knob-class a public method getAngle:
ReplyDeletepublic float getAngle() { return angle; }
In the listener in your main activity, you can set the textview's text:
public void onKnobChanged(int arg) {
TextView tv = getResourceById(...);
tv.setText("angle: " + jogView.getAngle());
...
}
I guess this should work.
- thomas
Sir,... thank you :)
DeleteHello Thomas, is there a way to access MainActivity variables values from RotaryKnobView class? What ever I try all I get are original declared values.
ReplyDeleteI use this in RotaryKnobView
MainActivity myActiv = new MainActivity();
float currentAngle = myActiv.getCurrentAngle();
I'm looking for a way to reset knob to original or some predefined values. Sorry for newbie questions :)
hm, sorry but this makes no sense.
ReplyDeleteWhat might work, though I did not test it, would be to add a method to the KnobView-Class, something like
public void setAngle(float angle)
{
this.angle = angle;
invalidate();
}
-thomas
Nice work.
ReplyDeleteI've made a few modifications to support a dynamic size (in dp, or using fill_parent...) and to have the ability to listen to delta and angle. Code is available here : https://gist.github.com/4281823
Thx!
Yann