You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

390 lines
13 KiB

/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.levien.synthesizer.android.widgets.knob;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.SweepGradient;
import android.graphics.Typeface;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Shader.TileMode;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import com.levien.synthesizer.R;
import com.levien.synthesizer.core.model.SynthesizerInput;
import com.levien.synthesizer.core.model.composite.MultiChannelSynthesizer;
import com.levien.synthesizer.core.model.composite.Presets.Setting;
/**
* KnobView is a widget for setting a real value by turning a virtual "knob".
*/
public class KnobView extends View {
/** Basic constructor for an Android widget. */
public KnobView(Context context, AttributeSet attrs) {
super(context, attrs);
// Load the xml attributes.
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.KnobView);
knobValue_ = a.getFloat(R.styleable.KnobView_value, 0.5f);
min_ = a.getFloat(R.styleable.KnobView_min, 0.0f);
max_ = a.getFloat(R.styleable.KnobView_max, 1.0f);
// Set up the drawing structures.
knobPaint_ = new Paint();
knobPaint_.setAntiAlias(true);
knobPaint_.setColor(Color.WHITE);
rect_ = new Rect();
rectF_ = new RectF();
textRect_ = new Rect();
// The listener has to be set later.
listener_ = null;
setPadding(3, 3, 3, 3);
}
/**
* Touch event handler.
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN: {
// Just record the current finger position.
getDrawingRect(rect_);
previousX_ = event.getX() - rect_.centerX();
previousY_ = event.getY() - rect_.centerY();
currentTouchAngle_ = knobValue_ * 2 * Math.PI * 0.8 + (Math.PI / 5.0);
diffAngle_ = 0;
break;
}
case MotionEvent.ACTION_MOVE: {
getDrawingRect(rect_);
// Compare the previous angle of the finger position (relative to the center of the control)
// to the new angle, and update the value accordingly.
currentTouchAngle_ = knobValue_ * 2 * Math.PI * 0.8 + (Math.PI / 5.0);
currentX_ = event.getX() - rect_.centerX();
currentY_ = event.getY() - rect_.centerY();
diffAngle_ = Math.atan2(currentY_, currentX_) - Math.atan2(previousY_, previousX_);
if (diffAngle_ > Math.PI) {
diffAngle_ -= Math.PI * 2;
} else if (diffAngle_ < -Math.PI) {
diffAngle_ += Math.PI * 2;
}
currentTouchAngle_ += diffAngle_;
knobValue_ = currentTouchAngle_ / (2.0 * Math.PI);
if (knobValue_ < 0.1) knobValue_ = 0.1;
if (knobValue_ > 0.9) knobValue_ = 0.9;
knobValue_ -= 0.1;
knobValue_ /= 0.8;
previousX_ = currentX_;
previousY_ = currentY_;
// Notify listener and redraw.
if (listener_ != null) {
listener_.onKnobChanged(getValue());
}
invalidate();
break;
}
case MotionEvent.ACTION_UP: {
break;
}
}
return true;
}
/**
* Sets the listener to receive events when the knob's value changes.
*/
public void setKnobListener(KnobListener listener) {
listener_ = listener;
}
/**
* Sets the value for the knob when it is turned all the way counter-clockwise.
*/
public void setMin(double min) {
min_ = min;
invalidate();
}
/**
* Sets the value for the knob when it is turned all the way clockwise.
*/
public void setMax(double max) {
max_ = max;
invalidate();
}
/**
* Sets the current value of the knob.
*/
public void setValue(double value) {
if (value < min_) {
knobValue_ = 0.0;
} else if (value > max_) {
knobValue_ = 1.0;
} else {
knobValue_ = (value - min_) / (max_ - min_);
}
if (listener_ != null) {
listener_.onKnobChanged(value);
}
invalidate();
}
/**
* Returns the current value of the knob.
*/
public double getValue() {
return min_ + (knobValue_ * (max_ - min_));
}
/**
* Drawing handler.
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
getDrawingRect(rect_);
rectF_.set(rect_);
// Make it square.
if (rectF_.height() > rectF_.width()) {
float center = rectF_.centerY();
rectF_.top = center - rectF_.width() / 2;
rectF_.bottom = center + rectF_.width() / 2;
} else {
float center = rectF_.centerX();
rectF_.left = center - rectF_.height() / 2;
rectF_.right = center + rectF_.height() / 2;
}
// Draw outer white glow.
int[] radialGradientColors = {Color.WHITE, Color.WHITE, 0x00000000};
float[] radialGradientPositions = {0.0f, 0.87f, 1.0f};
radialGradient_ = new RadialGradient(rect_.exactCenterX(),
rect_.exactCenterY(),
Math.min(rect_.width(), rect_.height()) / 2,
radialGradientColors,
radialGradientPositions,
TileMode.CLAMP);
knobPaint_.setShader(radialGradient_);
canvas.drawCircle(rect_.exactCenterX(),
rect_.exactCenterY(),
Math.min(rect_.width(), rect_.height()) / 2,
knobPaint_);
// Draw outer gauge.
final int fullDark = Color.BLACK;
final int guageStartColor = 0xff202050;
final int guageEndColor = 0xff4040A0;
final int adjustedStartColor = Color.argb(
0xFF,
(int)(0.1875 * Color.red(guageStartColor) + (1.0 - 0.1875) * Color.red(guageEndColor)),
(int)(0.1875 * Color.green(guageStartColor) + (1.0 - 0.1875) * Color.green(guageEndColor)),
(int)(0.1875 * Color.blue(guageStartColor) + (1.0 - 0.1875) * Color.blue(guageEndColor)));
int[] sweepGradientColors = {
adjustedStartColor,
guageEndColor,
fullDark,
fullDark,
guageStartColor,
adjustedStartColor};
float[] sweepGradientPositions = { 0.0f, 0.16f, 0.16f, 0.35f, 0.35f, 1.0f };
sweepGradient_ = new SweepGradient(rect_.exactCenterX(),
rect_.exactCenterY(),
sweepGradientColors,
sweepGradientPositions);
knobPaint_.setShader(sweepGradient_);
canvas.drawCircle(rect_.exactCenterX(),
rect_.exactCenterY(),
Math.min(rect_.width(), rect_.height()) / 2.4f,
knobPaint_);
// Draw inner gauge.
knobPaint_.setShader(null);
knobPaint_.setStyle(Style.FILL);
knobPaint_.setColor(Color.BLACK);
canvas.drawCircle(rect_.exactCenterX(),
rect_.exactCenterY(),
Math.min(rect_.width(), rect_.height()) / 4,
knobPaint_);
// Draw inner white glow.
int[] innerRadialGradientColors = { 0x00000000, 0x00000000, Color.WHITE };
float[] innerRadialGradientPositions = { 0.0f, 0.6f, 1.0f };
innerRadialGradient_ = new RadialGradient(rect_.exactCenterX(),
rect_.exactCenterY(),
Math.min(rect_.width(), rect_.height()) / 4f,
innerRadialGradientColors,
innerRadialGradientPositions,
TileMode.CLAMP);
knobPaint_.setShader(innerRadialGradient_);
canvas.drawCircle(rect_.exactCenterX(),
rect_.exactCenterY(),
Math.min(rect_.width(),rect_.height()) / 4,
knobPaint_);
// Draw indicator.
knobPaint_.setShader(null);
knobPaint_.setColor(Color.WHITE);
knobPaint_.setStyle(Style.STROKE);
knobPaint_.setStrokeWidth(4.0f);
final float arcWidth = 15.0f;
canvas.drawArc(rectF_,
(float)(knobValue_ * 360 * 0.8 + 90 - arcWidth / 2 + 36),
(float)arcWidth,
false,
knobPaint_);
// Draw text.
String knobValueString = String.format("%.2f", getValue());
Typeface typeface = Typeface.create(knobPaint_.getTypeface(), Typeface.BOLD);
knobPaint_.setTypeface(typeface);
knobPaint_.setTextAlign(Align.CENTER);
knobPaint_.setTextSize(rectF_.width() / 8);
knobPaint_.setSubpixelText(true);
knobPaint_.setStyle(Style.FILL);
knobPaint_.getTextBounds(knobValueString, 0, knobValueString.length(), textRect_);
canvas.drawText(knobValueString,
rect_.centerX(),
rect_.centerY() + textRect_.height() / 2,
knobPaint_);
}
/**
* Controls how the knob is sized; it is square, and prefers to be 100x100 pixels.
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// Specify that 100 is preferred for both dimensions.
int width = 0;
int height = 0;
switch (widthMode) {
case MeasureSpec.EXACTLY:
width = widthSize;
break;
case MeasureSpec.AT_MOST:
width = widthSize;
break;
case MeasureSpec.UNSPECIFIED:
width = 100;
break;
}
switch (heightMode) {
case MeasureSpec.EXACTLY:
height = heightSize;
break;
case MeasureSpec.AT_MOST:
height = heightSize;
break;
case MeasureSpec.UNSPECIFIED:
height = 100;
break;
}
// Make it square.
if (width > height && widthMode != MeasureSpec.EXACTLY) {
width = height;
}
if (height > width && heightMode != MeasureSpec.EXACTLY) {
height = width;
}
setMeasuredDimension(width, height);
}
/**
* Connects knob to a SynthesizerInput.
* @input - The synthesizer input to connect to.
*/
public void bindTo(final SynthesizerInput input) {
setValue(input.getSynthesizerInputValue());
setKnobListener(new KnobListener() {
public void onKnobChanged(double newValue) {
input.setValue(newValue);
}
});
}
/**
* Connects knob to a SynthesizerInput.
* @synth - The synthesizer to connect to.
* @path - The setting to connect to.
* @return - True on success, false on failure.
*/
public boolean bindTo(final MultiChannelSynthesizer synth, int channel, Setting setting) {
SynthesizerInput input = synth.getChannel(channel).getSynthesizerInput(setting);
if (input != null) {
bindTo(input);
return true;
} else {
Log.e(getClass().getName(), "Unable to bind to setting " + setting.name() + ".");
return false;
}
}
// Knob's current value, ranges from 0 - 1.0.
private double knobValue_;
private double min_;
private double max_;
// Structures used in drawing that we don't want to reallocate every time we draw.
private Paint knobPaint_;
private Rect rect_;
private Rect textRect_;
private RectF rectF_;
private SweepGradient sweepGradient_;
private RadialGradient radialGradient_;
private RadialGradient innerRadialGradient_;
// Position of the finger relative to the knob.
private double previousX_ = 0;
private double previousY_ = 0;
private double currentX_ = 0;
private double currentY_ = 0;
private double currentTouchAngle_ = 0;
private double diffAngle_ = 0;
// Object listening for events when the knob's value changes.
private KnobListener listener_;
}