blob: 179023e0282dafcf3a9eb4644baa23ea42dd0de4 [file] [log] [blame]
/*
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of The Linux Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.android.gallery3d.ui;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.lang.Math;
import com.android.gallery3d.R;
public class Knob extends FrameLayout {
private static final int STROKE_WIDTH = 6;
private static final float TEXT_SIZE = 0.20f;
private static final float TEXT_PADDING = 0.31f;
private static final float LABEL_PADDING = 0.05f;
private static final float LABEL_SIZE = 0.09f;
private static final float LABEL_WIDTH = 0.80f;
private static final float INDICATOR_RADIUS = 0.38f;
public interface OnKnobChangeListener {
void onValueChanged(Knob knob, int value, boolean fromUser);
boolean onSwitchChanged(Knob knob, boolean on);
}
private OnKnobChangeListener mOnKnobChangeListener = null;
private float mProgress = 0.0f;
private int mMax = 100;
private boolean mOn = false;
private boolean mEnabled = false;
private int mHighlightColor;
private int mLowlightColor;
private int mDisabledColor;
private final Paint mPaint;
private final TextView mLabelTV;
private final TextView mProgressTV;
private final ImageView mKnobOn;
private final ImageView mKnobOff;
private float mLastX;
private float mLastY;
private boolean mMoved;
private int mWidth = 0;
private int mIndicatorWidth = 0;
private RectF mRectF;
public Knob(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Knob, 0, 0);
String label;
int foreground;
try {
label = a.getString(R.styleable.Knob_label);
foreground = a.getResourceId(R.styleable.Knob_foreground, R.drawable.knob);
} finally {
a.recycle();
}
LayoutInflater li = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
li.inflate(R.layout.knob, this, true);
Resources res = getResources();
mHighlightColor = res.getColor(R.color.highlight);
mLowlightColor = res.getColor(R.color.lowlight);
mDisabledColor = res.getColor(R.color.disabled_knob);
((ImageView) findViewById(R.id.knob_foreground)).setImageResource(foreground);
mLabelTV = (TextView) findViewById(R.id.knob_label);
mLabelTV.setText(label);
mProgressTV = (TextView) findViewById(R.id.knob_value);
mKnobOn = (ImageView) findViewById(R.id.knob_toggle_on);
mKnobOff = (ImageView) findViewById(R.id.knob_toggle_off);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(mHighlightColor);
mPaint.setStrokeWidth(STROKE_WIDTH);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStyle(Paint.Style.STROKE);
setWillNotDraw(false);
}
public Knob(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Knob(Context context) {
this(context, null);
}
public void setOnKnobChangeListener(OnKnobChangeListener l) {
mOnKnobChangeListener = l;
}
public void setValue(int value) {
if (mMax != 0) {
setProgress(((float) value) / mMax);
}
}
public int getValue() {
return (int) (mProgress * mMax);
}
public void setProgress(float progress) {
setProgress(progress, false);
}
private void setProgressText(boolean on) {
if (on) {
mProgressTV.setText((int) (mProgress * 100) + "%");
} else {
mProgressTV.setText("--%");
}
}
private void setProgress(float progress, boolean fromUser) {
if (progress > 1.0f) {
progress = 1.0f;
}
if (progress < 0.0f) {
progress = 0.0f;
}
mProgress = progress;
setProgressText(mOn && mEnabled);
invalidate();
if (mOnKnobChangeListener != null) {
mOnKnobChangeListener.onValueChanged(this, (int) (progress * mMax), fromUser);
}
}
public void setMax(int max) {
mMax = max;
}
public float getProgress() {
return mProgress;
}
private void drawIndicator() {
float r = mWidth * INDICATOR_RADIUS;
ImageView view = mOn ? mKnobOn : mKnobOff;
view.setTranslationX((float) Math.sin(mProgress * 2 * Math.PI) * r - mIndicatorWidth / 2);
view.setTranslationY((float) -Math.cos(mProgress * 2 * Math.PI) * r - mIndicatorWidth / 2);
}
@Override
public void setEnabled(boolean enabled) {
mEnabled = enabled;
setOn(enabled);
}
public void setOn(boolean on) {
if (on != mOn) {
mOn = on;
}
on = on && mEnabled;
mLabelTV.setTextColor(on ? mHighlightColor : mDisabledColor);
mProgressTV.setTextColor(on ? mHighlightColor : mDisabledColor);
setProgressText(on);
mPaint.setColor(on ? mHighlightColor : mDisabledColor);
mKnobOn.setVisibility(on ? View.VISIBLE : View.GONE);
mKnobOff.setVisibility(on ? View.GONE : View.VISIBLE);
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawIndicator();
if (mOn && mEnabled) {
canvas.drawArc(mRectF, -90, mProgress * 360, false, mPaint);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldW, int oldH) {
int size = w > h ? h : w;
mWidth = size;
mIndicatorWidth = mKnobOn.getWidth();
int diff;
if (w > h) {
diff = (w - h) / 2;
mRectF = new RectF(STROKE_WIDTH + diff, STROKE_WIDTH,
w - STROKE_WIDTH - diff, h - STROKE_WIDTH);
} else {
diff = (h - w) / 2;
mRectF = new RectF(STROKE_WIDTH, STROKE_WIDTH + diff,
w - STROKE_WIDTH, h - STROKE_WIDTH - diff);
}
mProgressTV.setTextSize(TypedValue.COMPLEX_UNIT_PX, size * TEXT_SIZE);
mProgressTV.setPadding(0, (int) (size * TEXT_PADDING), 0, 0);
mProgressTV.setVisibility(View.VISIBLE);
mLabelTV.setTextSize(TypedValue.COMPLEX_UNIT_PX, size * LABEL_SIZE);
mLabelTV.setPadding(0, (int) (size * LABEL_PADDING), 0, 0);
mLabelTV.setLayoutParams(new LinearLayout.LayoutParams((int) (w * LABEL_WIDTH),
LayoutParams.WRAP_CONTENT));
mLabelTV.setVisibility(View.VISIBLE);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (mOn) {
mLastX = event.getX();
mLastY = event.getY();
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
case MotionEvent.ACTION_MOVE:
if (mOn) {
float x = event.getX();
float y = event.getY();
float center = mWidth / 2;
if (mMoved || (x - center) * (x - center) + (y - center) * (y - center)
> center * center / 4) {
float delta = getDelta(x, y);
setProgress(mProgress + delta / 360, true);
mMoved = true;
}
mLastX = x;
mLastY = y;
}
break;
case MotionEvent.ACTION_UP:
if (!mMoved) {
if (mOnKnobChangeListener == null
|| mOnKnobChangeListener.onSwitchChanged(this, !mOn)) {
if (mEnabled) {
setOn(!mOn);
invalidate();
}
}
}
mMoved = false;
break;
default:
break;
}
return true;
}
private float getDelta(float x, float y) {
float angle = angle(x, y);
float oldAngle = angle(mLastX, mLastY);
float delta = angle - oldAngle;
if (delta >= 180.0f) {
delta = -oldAngle;
} else if (delta <= -180.0f) {
delta = 360 - oldAngle;
}
return delta;
}
private float angle(float x, float y) {
float center = mWidth / 2.0f;
x -= center;
y -= center;
if (x == 0.0f) {
if (y > 0.0f) {
return 180.0f;
} else {
return 0.0f;
}
}
float angle = (float) (Math.atan(y / x) / Math.PI * 180.0);
if (x > 0.0f) {
angle += 90;
} else {
angle += 270;
}
return angle;
}
}