| /* |
| * 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; |
| } |
| } |