2D Graphics Android

/* 
 * Copyright (C) 2008 OpenIntents.org
 *
 * 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 org.openintents.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Shader;
import android.graphics.SweepGradient;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
 * Interface for notifications of position change of slider.
 * 
 * @author Peli
 */
 interface OnColorChangedListener {
  /**
   * This method is called when the user changed the color.
   * 
   * This works in touch mode, by dragging the along the 
   * color circle with the finger.
   */
  void onColorChanged(View view, int newColor);
  
  /**
   * This method is called when the user clicks the center button.
   * 
   * @param colorcircle
   * @param newColor
   */
  void onColorPicked(View view, int newColor);
}
/**
 * ColorCircle.
 * 
 * @author Peli, based on API demo code.
 * 
 */
public class ColorCircle extends View {
    private float center_radius;
    private final static float CENTER_RADIUS_SCALE = 0.4f;
    
    private Paint mPaint;
    private Paint mCenterPaint;
    private int[] mColors;
    private OnColorChangedListener mListener;
  /**
   * Constructor. This version is only needed for instantiating the object
   * manually (not from a layout XML file).
   * 
   * @param context
   */
  public ColorCircle(Context context) {
    super(context);
    init();
  }
  /**
   * Construct object, initializing with any attributes we understand from a
   * layout file.
   * 
   * These attributes are defined in res/values/attrs.xml .
   * 
   * @see android.view.View#View(android.content.Context,
   *      android.util.AttributeSet, java.util.Map)
   */
  public ColorCircle(Context context, AttributeSet attrs) {
    super(context, attrs);
    // TODO what happens with inflateParams
    init();
  }
  /**
   * Initializes variables.
   */
  void init() {
        mColors = new int[] {
            0xFFFF0000, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF, 0xFF00FF00,
            0xFFFFFF00, 0xFFFF0000
        };
        Shader s = new SweepGradient(0, 0, mColors, null);
        
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setShader(s);
        mPaint.setStyle(Paint.Style.STROKE);
        
        mCenterPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mCenterPaint.setStrokeWidth(5);
  }
    
    private boolean mTrackingCenter;
    private boolean mHighlightCenter;
    @Override 
    protected void onDraw(Canvas canvas) {
    float outer_radius = Math.min(getWidth(), getHeight())/2;
     float touch_feedback_ring = center_radius + 2*mCenterPaint.getStrokeWidth();
        float r = (outer_radius + touch_feedback_ring) / 2;
        
        canvas.translate(getWidth()/2, getHeight()/2);
        
        mPaint.setStrokeWidth(outer_radius - touch_feedback_ring);
        
        // This is the main "color ring"
        canvas.drawCircle(0, 0, r, mPaint);       
        // This is the center "activation button" circle
        canvas.drawCircle(0, 0, center_radius, mCenterPaint);
        
        if (mTrackingCenter) {
            int c = mCenterPaint.getColor();
            mCenterPaint.setStyle(Paint.Style.STROKE);
            
            if (mHighlightCenter) {
                mCenterPaint.setAlpha(0xFF);
            } else {
                mCenterPaint.setAlpha(0x80);
            }
            
            // The skinny ring around the center to indicate that it is being pressed
            canvas.drawCircle(0, 0,
                center_radius + mCenterPaint.getStrokeWidth(),
                              mCenterPaint);
            
            mCenterPaint.setStyle(Paint.Style.FILL);
            mCenterPaint.setColor(c);
        }
    }
    
  /**
   * @see android.view.View#measure(int, int)
   */
  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
    int max_width = MeasureSpec.getSize(widthMeasureSpec);
    int max_height = MeasureSpec.getSize(heightMeasureSpec);
    int size = Math.min(max_width, max_height);
    this.center_radius = CENTER_RADIUS_SCALE * size/2;
    
    setMeasuredDimension(size, size);
  }
    
  public void setColor(int color) {
        mCenterPaint.setColor(color);
        invalidate();
  }
  public int getColor() {
    return mCenterPaint.getColor();
  }
  
  public void setOnColorChangedListener(
      OnColorChangedListener colorListener) {
    mListener = colorListener;
  }
    
    private int ave(int s, int d, float p) {
        return s + java.lang.Math.round(p * (d - s));
    }
    
    private int interpColor(int colors[], float unit) {
        if (unit <= 0) {
            return colors[0];
        }
        if (unit >= 1) {
            return colors[colors.length - 1];
        }
        
        float p = unit * (colors.length - 1);
        int i = (int)p;
        p -= i;
        // now p is just the fractional part [0...1) and i is the index
        int c0 = colors[i];
        int c1 = colors[i+1];
        int a = ave(Color.alpha(c0), Color.alpha(c1), p);
        int r = ave(Color.red(c0), Color.red(c1), p);
        int g = ave(Color.green(c0), Color.green(c1), p);
        int b = ave(Color.blue(c0), Color.blue(c1), p);
        
        return Color.argb(a, r, g, b);
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX() - getWidth()/2;
        float y = event.getY() - getHeight()/2;
        boolean inCenter = PointF.length(x, y) <= center_radius;
        
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mTrackingCenter = inCenter;
                if (inCenter) {
                    mHighlightCenter = true;
                    invalidate();
                    break;
                }
            case MotionEvent.ACTION_MOVE:
                if (mTrackingCenter) {
                    if (mHighlightCenter != inCenter) {
                        mHighlightCenter = inCenter;
                        invalidate();
                    }
                } else {
                    float angle = (float)java.lang.Math.atan2(y, x);
                    // need to turn angle [-PI ... PI] into unit [0....1]
                    float unit = angle/(2*(float) Math.PI);
                    if (unit < 0) {
                        unit += 1;
                    }
                    int newcolor = interpColor(mColors, unit);
                    mCenterPaint.setColor(newcolor);
                  if (mListener != null) {
                    mListener.onColorChanged(this, newcolor);
                  }
                    invalidate();
                }
                break;
            case MotionEvent.ACTION_UP:
                if (mTrackingCenter) {
                    if (inCenter) {
                      if (mListener != null) {
                        mListener.onColorPicked(this, mCenterPaint.getColor());
                      }
                    }
                    mTrackingCenter = false;    // so we draw w/o halo
                    invalidate();
                }
                break;
        }
        return true;
    }
}