Android Open Source - wally Color Picker View






From Project

Back to project page wally.

License

The source code is released under:

Apache License

If you think the Android project wally listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright (C) 2010 Daniel Nilsson//  w w w  .j av a  2 s. c om
 *
 * 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.
 * 
 * 
 * 
 * 
 * Change Log:
 * 
 * 1.1
 * - Fixed buggy measure and layout code. You can now make the view any size you want.
 * - Optimization of the drawing using a bitmap cache, a lot faster!
 * - Support for hardware acceleration for all but the problematic
 *   part of the view will still be software rendered but much faster!
 *   See comment in drawSatValPanel() for more info.
 * - Support for declaring some variables in xml.
 */


package net.margaritov.preference.colorpicker.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ComposeShader;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.Shader.TileMode;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import com.musenkishi.wally.R;

import net.margaritov.preference.colorpicker.drawable.AlphaPatternDrawable;

/**
 * Displays a color picker to the user and allow them
 * to select a color. A slider for the alpha channel is
 * also available. Enable it by setting 
 * setAlphaSliderVisible(boolean) to true.
 * @author Daniel Nilsson
 */
public class ColorPickerView extends View{

  public interface OnColorChangedListener{
    public void onColorChanged(int newColor);    
  }
    
  private final static int  PANEL_SAT_VAL = 0;
  private final static int  PANEL_HUE = 1;
  private final static int  PANEL_ALPHA = 2;
  
  /**
   * The width in pixels of the border 
   * surrounding all color panels.
   */
  private final static float  BORDER_WIDTH_PX = 1;
  
  /**
   * The width in dp of the hue panel.
   */
  private float     HUE_PANEL_WIDTH = 30f;  
  /**
   * The height in dp of the alpha panel 
   */
  private float    ALPHA_PANEL_HEIGHT = 20f;
  /**
   * The distance in dp between the different
   * color panels.
   */
  private float     PANEL_SPACING = 10f;  
  /**
   * The radius in dp of the color palette tracker circle.
   */
  private float     PALETTE_CIRCLE_TRACKER_RADIUS = 5f;
  /**
   * The dp which the tracker of the hue or alpha panel
   * will extend outside of its bounds.
   */
  private float    RECTANGLE_TRACKER_OFFSET = 2f;
  
  
  private static float mDensity = 1f;
  
  private OnColorChangedListener  mListener;
  
  private Paint     mSatValPaint;
  private Paint    mSatValTrackerPaint;
  
  private Paint    mHuePaint;
  private Paint    mHueAlphaTrackerPaint;
  
  private Paint    mAlphaPaint;
  private Paint    mAlphaTextPaint;
  
  private Paint    mBorderPaint;
    
  private Shader    mValShader;
  private Shader    mSatShader;
  private Shader    mHueShader;
  private Shader    mAlphaShader;
  

  /*
   * We cache a bitmap of the sat/val panel which is expensive to draw each time.
   * We can reuse it when the user is sliding the circle picker as long as the hue isn't changed.
   */
  private BitmapCache    mSatValBackgroundCache;
  
  
  private int      mAlpha = 0xff;
  private float    mHue = 360f;
  private float     mSat = 0f;
  private float     mVal = 0f;
  
  private String    mAlphaSliderText = null;
  private int     mSliderTrackerColor = 0xFFBDBDBD;
  private int     mBorderColor = 0xFF6E6E6E;
  private boolean    mShowAlphaPanel = false;
  
  /*
   * To remember which panel that has the "focus" when 
   * processing hardware button data.
   */
  private int      mLastTouchedPanel = PANEL_SAT_VAL;
  
  /**
   * Offset from the edge we must have or else
   * the finger tracker will get clipped when
   * it is drawn outside of the view.
   */
  private int     mDrawingOffset;
  

  /*
   * Distance form the edges of the view 
   * of where we are allowed to draw.
   */  
  private RectF  mDrawingRect;
      
  private RectF  mSatValRect;
  private RectF   mHueRect;
  private RectF  mAlphaRect;
  
  private AlphaPatternDrawable mAlphaPattern;
  
  private Point  mStartTouchPoint = null;
  
  
  public ColorPickerView(Context context){
    this(context, null);
  }
  
  public ColorPickerView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }
  
  public ColorPickerView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init(attrs);
  }
    
  private void init(AttributeSet attrs) {
    //Load those if set in xml resource file.
    TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ColorPickerView);
    mShowAlphaPanel = a.getBoolean(R.styleable.ColorPickerView_alphaChannelVisible, false);
    mAlphaSliderText = a.getString(R.styleable.ColorPickerView_alphaChannelText);    
    mSliderTrackerColor = a.getColor(R.styleable.ColorPickerView_colorPickerSliderColor, 0xFFBDBDBD);
    mBorderColor = a.getColor(R.styleable.ColorPickerView_colorPickerBorderColor, 0xFF6E6E6E);
    a.recycle();
    
    
    mDensity = getContext().getResources().getDisplayMetrics().density;
    PALETTE_CIRCLE_TRACKER_RADIUS *= mDensity;    
    RECTANGLE_TRACKER_OFFSET *= mDensity;
    HUE_PANEL_WIDTH *= mDensity;
    ALPHA_PANEL_HEIGHT *= mDensity;
    PANEL_SPACING = PANEL_SPACING * mDensity;
    
    mDrawingOffset = calculateRequiredOffset();
    
    initPaintTools();
    
    //Needed for receiving trackball motion events.
    setFocusable(true);
    setFocusableInTouchMode(true);
  }
  
  private void initPaintTools(){
    
    mSatValPaint = new Paint();
    mSatValTrackerPaint = new Paint();
    mHuePaint = new Paint();
    mHueAlphaTrackerPaint = new Paint();
    mAlphaPaint = new Paint();
    mAlphaTextPaint = new Paint();
    mBorderPaint = new Paint();
    
    
    mSatValTrackerPaint.setStyle(Style.STROKE);
    mSatValTrackerPaint.setStrokeWidth(2f * mDensity);
    mSatValTrackerPaint.setAntiAlias(true);
    
    mHueAlphaTrackerPaint.setColor(mSliderTrackerColor);
    mHueAlphaTrackerPaint.setStyle(Style.STROKE);
    mHueAlphaTrackerPaint.setStrokeWidth(2f * mDensity);
    mHueAlphaTrackerPaint.setAntiAlias(true);
    
    mAlphaTextPaint.setColor(0xff1c1c1c);
    mAlphaTextPaint.setTextSize(14f * mDensity);
    mAlphaTextPaint.setAntiAlias(true);
    mAlphaTextPaint.setTextAlign(Align.CENTER);
    mAlphaTextPaint.setFakeBoldText(true);

  }
  
  private int calculateRequiredOffset(){    
    float offset = Math.max(PALETTE_CIRCLE_TRACKER_RADIUS, RECTANGLE_TRACKER_OFFSET);
    offset = Math.max(offset, BORDER_WIDTH_PX * mDensity);
    
    return (int) (offset * 1.5f);  
  }
  
  private int[] buildHueColorArray(){    
    int[] hue = new int[361];
    
    int count = 0;
    for(int i = hue.length -1; i >= 0; i--, count++){
      hue[count] = Color.HSVToColor(new float[]{i, 1f, 1f});
    }
    
    return hue;
  }
  
  
  @Override
  protected void onDraw(Canvas canvas) {    
    if(mDrawingRect.width() <= 0 || mDrawingRect.height() <= 0) {
      return;
    }
    
    drawSatValPanel(canvas);  
    drawHuePanel(canvas);
    drawAlphaPanel(canvas);
    
  }
  
  private void drawSatValPanel(Canvas canvas){
    /*
     * Draw time for this code without using bitmap cache and hardware acceleration was around 20ms.
     * Now with the bitmap cache and the ability to use hardware acceleration we are down at 1ms as long as the hue isn't changed.
     * If the hue is changed we the sat/val rectangle will be rendered in software and it takes around 10ms.
     * But since the rest of the view will be rendered in hardware the performance gain is big!
     */

    final RectF  rect = mSatValRect;
        
    if(BORDER_WIDTH_PX > 0){      
      mBorderPaint.setColor(mBorderColor);
      canvas.drawRect(mDrawingRect.left, mDrawingRect.top, rect.right + BORDER_WIDTH_PX, rect.bottom + BORDER_WIDTH_PX, mBorderPaint);    
    }
      
    if(mValShader == null) {
      //Black gradient has either not been created or the view has been resized.      
      mValShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, 
          0xffffffff, 0xff000000, TileMode.CLAMP);
    }
    
    
    //If the hue has changed we need to recreate the cache.
    if(mSatValBackgroundCache == null || mSatValBackgroundCache.value != mHue) {
      
      if(mSatValBackgroundCache == null) {
        mSatValBackgroundCache = new BitmapCache();
      }
          
      //We create our bitmap in the cache if it doesn't exist.
      if(mSatValBackgroundCache.bitmap == null) {
        mSatValBackgroundCache.bitmap = Bitmap.createBitmap((int)rect.width(), (int)rect.height(), Config.ARGB_8888);
      }
      
      //We create the canvas once so we can draw on our bitmap and the hold on to it.
      if(mSatValBackgroundCache.canvas == null) {
        mSatValBackgroundCache.canvas = new Canvas(mSatValBackgroundCache.bitmap);
      }
        
      int rgb = Color.HSVToColor(new float[]{mHue,1f,1f});
      
      mSatShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, 
          0xffffffff, rgb, TileMode.CLAMP);
      
      ComposeShader mShader = new ComposeShader(mValShader, mSatShader, PorterDuff.Mode.MULTIPLY);
      mSatValPaint.setShader(mShader);
      
      //Finally we draw on our canvas, the result will be stored in our bitmap which is already in the cache.
      //Since this is drawn on a canvas not rendered on screen it will automatically not be using the hardware acceleration.
      //And this was the code that wasn't supported by hardware acceleration which mean there is no need to turn it of anymore.
      //The rest of the view will still be hardware accelerated!!
      mSatValBackgroundCache.canvas.drawRect(0, 0, mSatValBackgroundCache.bitmap.getWidth(), mSatValBackgroundCache.bitmap.getHeight(), mSatValPaint);      
      
      //We set the hue value in our cache to which hue it was drawn with, 
      //then we know that if it hasn't changed we can reuse our cached bitmap.
      mSatValBackgroundCache.value = mHue;  
            
    }
    
    //We draw our bitmap from the cached, if the hue has changed
    //then it was just recreated otherwise the old one will be used.
    canvas.drawBitmap(mSatValBackgroundCache.bitmap, null, rect, null);
    

    Point p = satValToPoint(mSat, mVal);
      
    mSatValTrackerPaint.setColor(0xff000000);
    canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS - 1f * mDensity, mSatValTrackerPaint);
        
    mSatValTrackerPaint.setColor(0xffdddddd);
    canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS, mSatValTrackerPaint);
    
  }
  
  private void drawHuePanel(Canvas canvas){
    /*
     * Drawn with hw acceleration, very fast.
     */
        
    //long start = SystemClock.elapsedRealtime();
    
    final RectF rect = mHueRect;
    
    if(BORDER_WIDTH_PX > 0) {
      mBorderPaint.setColor(mBorderColor);
      canvas.drawRect(rect.left - BORDER_WIDTH_PX, 
          rect.top - BORDER_WIDTH_PX, 
          rect.right + BORDER_WIDTH_PX, 
          rect.bottom + BORDER_WIDTH_PX, 
          mBorderPaint);    
    }

    if (mHueShader == null) {
      //The hue shader has either not yet been created or the view has been resized.
      mHueShader = new LinearGradient(0, 0, 0, rect.height(), buildHueColorArray(), null, TileMode.CLAMP);
      mHuePaint.setShader(mHueShader);      
    }

    canvas.drawRect(rect, mHuePaint);
    
    float rectHeight = 4 * mDensity / 2;
        
    Point p = hueToPoint(mHue);
        
    RectF r = new RectF();
    r.left = rect.left - RECTANGLE_TRACKER_OFFSET;
    r.right = rect.right + RECTANGLE_TRACKER_OFFSET;
    r.top = p.y - rectHeight;
    r.bottom = p.y + rectHeight;
    
    
    canvas.drawRoundRect(r, 2, 2, mHueAlphaTrackerPaint);
    
    //Log.d("mColorPicker", "Draw Time Hue: " + (SystemClock.elapsedRealtime() - start) + "ms");
    
  }
  
  private void drawAlphaPanel(Canvas canvas) {
    /*
     * Will be drawn with hw acceleration, very fast.
     */
        
    if(!mShowAlphaPanel || mAlphaRect == null || mAlphaPattern == null) return;

    final RectF rect = mAlphaRect;
    
    if(BORDER_WIDTH_PX > 0){
      mBorderPaint.setColor(mBorderColor);
      canvas.drawRect(rect.left - BORDER_WIDTH_PX, 
          rect.top - BORDER_WIDTH_PX, 
          rect.right + BORDER_WIDTH_PX, 
          rect.bottom + BORDER_WIDTH_PX, 
          mBorderPaint);    
    }
    
    
    mAlphaPattern.draw(canvas);
    
    float[] hsv = new float[]{mHue,mSat,mVal};
    int color = Color.HSVToColor(hsv);
    int acolor = Color.HSVToColor(0, hsv);
    
    mAlphaShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, 
        color, acolor, TileMode.CLAMP);
    
    
    mAlphaPaint.setShader(mAlphaShader);
    
    canvas.drawRect(rect, mAlphaPaint);
    
    if(mAlphaSliderText != null && !mAlphaSliderText.equals("")){
      canvas.drawText(mAlphaSliderText, rect.centerX(), rect.centerY() + 4 * mDensity, mAlphaTextPaint);
    }
    
    float rectWidth = 4 * mDensity / 2;
    
    Point p = alphaToPoint(mAlpha);
        
    RectF r = new RectF();
    r.left = p.x - rectWidth;
    r.right = p.x + rectWidth;
    r.top = rect.top - RECTANGLE_TRACKER_OFFSET;
    r.bottom = rect.bottom + RECTANGLE_TRACKER_OFFSET;
    
    canvas.drawRoundRect(r, 2, 2, mHueAlphaTrackerPaint);
  }
  
  
  private Point hueToPoint(float hue){
    
    final RectF rect = mHueRect;
    final float height = rect.height();
    
    Point p = new Point();
      
    p.y = (int) (height - (hue * height / 360f) + rect.top);
    p.x = (int) rect.left;
    
    return p;    
  }
  
  private Point satValToPoint(float sat, float val){
    
    final RectF rect = mSatValRect;
    final float height = rect.height();
    final float width = rect.width();
    
    Point p = new Point();
    
    p.x = (int) (sat * width + rect.left);
    p.y = (int) ((1f - val) * height + rect.top);
    
    return p;
  }
  
  private Point alphaToPoint(int alpha){
    
    final RectF rect = mAlphaRect;
    final float width = rect.width();
    
    Point p = new Point();
    
    p.x = (int) (width - (alpha * width / 0xff) + rect.left);
    p.y = (int) rect.top;
    
    return p;
  
  }
  
  private float[] pointToSatVal(float x, float y){
  
    final RectF rect = mSatValRect;
    float[] result = new float[2];
    
    float width = rect.width();
    float height = rect.height();
    
    if (x < rect.left){
      x = 0f;
    }
    else if(x > rect.right){
      x = width;
    }
    else{
      x = x - rect.left;
    }
        
    if (y < rect.top){
      y = 0f;
    }
    else if(y > rect.bottom){
      y = height;
    }
    else{
      y = y - rect.top;
    }
    
      
    result[0] = 1.f / width * x;
    result[1] = 1.f - (1.f / height * y);
    
    return result;  
  }
  
  private float pointToHue(float y){    
    
    final RectF rect = mHueRect;
    
    float height = rect.height();
    
    if (y < rect.top){
      y = 0f;
    }
    else if(y > rect.bottom){
      y = height;
    }
    else{
      y = y - rect.top;
    }
    
    return 360f - (y * 360f / height);
  }
  
  private int pointToAlpha(int x){
    
    final RectF rect = mAlphaRect;
    final int width = (int) rect.width();
    
    if(x < rect.left){
      x = 0;
    }
    else if(x > rect.right){
      x = width;
    }
    else{
      x = x - (int)rect.left;
    }
    
    return 0xff - (x * 0xff / width);
    
  }
  
      
  @Override
  public boolean onTrackballEvent(MotionEvent event) {
    
    float x = event.getX();
    float y = event.getY();
    
    boolean update = false;
        
    if(event.getAction() == MotionEvent.ACTION_MOVE){
      
      switch(mLastTouchedPanel){
      
      case PANEL_SAT_VAL:
        
        float sat, val;
        
        sat = mSat + x/50f;
        val = mVal - y/50f; 
        
        if(sat < 0f){
          sat = 0f;
        }
        else if(sat > 1f){
          sat = 1f;
        }
        
        if(val < 0f){
          val = 0f;
        }
        else if(val > 1f){
          val = 1f;
        }
        
        mSat = sat;
        mVal = val;
        
        update = true;
        
        break;
        
      case PANEL_HUE:
        
        float hue = mHue - y * 10f;
        
        if(hue < 0f){
          hue = 0f;
        }
        else if(hue > 360f){
          hue = 360f;
        }
        
        mHue = hue;
            
        update = true;
        
        break;
        
      case PANEL_ALPHA:
        
        if(!mShowAlphaPanel || mAlphaRect == null){
          update = false;
        }
        else{
          
          int alpha = (int) (mAlpha - x*10);
          
          if(alpha < 0){
            alpha = 0;
          }
          else if(alpha > 0xff){
            alpha = 0xff;
          }
          
          mAlpha = alpha;
          
          
          update = true;
        }
        
        break;
      }
      
      
    }
    
    
    if(update){
      
      if(mListener != null){
        mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal}));
      }
      
      invalidate();
      return true;
    }
    
  
    return super.onTrackballEvent(event);
  }
    
  @Override
  public boolean onTouchEvent(MotionEvent event) {    
    boolean update = false;
        
    switch(event.getAction()){
    
    case MotionEvent.ACTION_DOWN:      
      mStartTouchPoint = new Point((int)event.getX(), (int)event.getY());
      update = moveTrackersIfNeeded(event);    
      break;
            
    case MotionEvent.ACTION_MOVE:      
      update = moveTrackersIfNeeded(event);
      break;      
    case MotionEvent.ACTION_UP:      
      mStartTouchPoint = null;
      update = moveTrackersIfNeeded(event);      
      break;  
    }
    
    if(update){      
      if(mListener != null){
        mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal}));
      }
      invalidate();
      return true;
    }
      
    return super.onTouchEvent(event);
  }
    
  private boolean moveTrackersIfNeeded(MotionEvent event){
    
    if(mStartTouchPoint == null) {
      return false;
    }
    
    boolean update = false;
    
    int startX = mStartTouchPoint.x;
    int startY = mStartTouchPoint.y;
    
    
    if(mHueRect.contains(startX, startY)){
      mLastTouchedPanel = PANEL_HUE;
      
      mHue = pointToHue(event.getY());
            
      update = true;
    }
    else if(mSatValRect.contains(startX, startY)){
              
      mLastTouchedPanel = PANEL_SAT_VAL;
      
      float[] result = pointToSatVal(event.getX(), event.getY());
      
      mSat = result[0];
      mVal = result[1];

      update = true;
    }
    else if(mAlphaRect != null && mAlphaRect.contains(startX, startY)){
      
      mLastTouchedPanel = PANEL_ALPHA;
      
      mAlpha = pointToAlpha((int)event.getX());
      
      update = true;
    }
    
    
    return update;
  }
  

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int finalWidth = 0;
    int finalHeight = 0;
    
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    
    int widthAllowed = MeasureSpec.getSize(widthMeasureSpec);
    int heightAllowed = MeasureSpec.getSize(heightMeasureSpec);
    
    
    //Log.d("color-picker-view", "widthMode: " + modeToString(widthMode) + " heightMode: " + modeToString(heightMode) + " widthAllowed: " + widthAllowed + " heightAllowed: " + heightAllowed);
        
    if(widthMode == MeasureSpec.EXACTLY || heightMode == MeasureSpec.EXACTLY) {
      //A exact value has been set in either direction, we need to stay within this size.
      
      if(widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY) {
        //The with has been specified exactly, we need to adopt the height to fit.
        int h = (int) (widthAllowed - PANEL_SPACING - HUE_PANEL_WIDTH);
        
        if(mShowAlphaPanel) {
          h += PANEL_SPACING + ALPHA_PANEL_HEIGHT;
        }
        
        if(h > heightAllowed) {
          //We can't fit the view in this container, set the size to whatever was allowed.
          finalHeight = heightAllowed;
        }
        else {
          finalHeight = h;
        }
        
        finalWidth = widthAllowed;      
        
      }
      else if(heightMode == MeasureSpec.EXACTLY && widthMode != MeasureSpec.EXACTLY) {
        //The height has been specified exactly, we need to stay within this height and adopt the width.
        
        int w = (int) (heightAllowed + PANEL_SPACING + HUE_PANEL_WIDTH);
        
        if(mShowAlphaPanel) {
          w -= PANEL_SPACING - ALPHA_PANEL_HEIGHT;
        }
        
        if(w > widthAllowed) {
          //we can't fit within this container, set the size to whatever was allowed.
          finalWidth = widthAllowed;
        }
        else {
          finalWidth = w;
        }
        
        finalHeight = heightAllowed;      
        
      }
      else {
        //If we get here the dev has set the width and height to exact sizes. For example match_parent or 300dp.
        //This will mean that the sat/val panel will not be square but it doesn't matter. It will work anyway.
        //In all other senarios our goal is to make that panel square.
        
        //We set the sizes to exactly what we were told.
        finalWidth = widthAllowed;
        finalHeight = heightAllowed;
      }
            
    }
    else {
      //If no exact size has been set we try to make our view as big as possible 
      //within the allowed space.
      
      //Calculate the needed with to layout the view based on the allowed height.
      int widthNeeded = (int) (heightAllowed + PANEL_SPACING + HUE_PANEL_WIDTH);
      //Calculate the needed height to layout the view based on the allowed width.
      int heightNeeded = (int) (widthAllowed - PANEL_SPACING - HUE_PANEL_WIDTH);
        
      if(mShowAlphaPanel) {
        widthNeeded -= (PANEL_SPACING + ALPHA_PANEL_HEIGHT);
        heightNeeded += PANEL_SPACING + ALPHA_PANEL_HEIGHT;
      }
        
        
      if(widthNeeded <= widthAllowed) {
        finalWidth = widthNeeded;
        finalHeight = heightAllowed;
      }
      else if(heightNeeded <= heightAllowed) {
        finalHeight = heightNeeded;
        finalWidth = widthAllowed;
      }
    }      
    
    //Log.d("mColorPicker", "Final Size: " + finalWidth + "x" + finalHeight);
    
    setMeasuredDimension(finalWidth, finalHeight);
  }

  private String modeToString(int mode) {
    switch(mode) {
    case MeasureSpec.AT_MOST:
      return "AT MOST";
    case MeasureSpec.EXACTLY:
      return "EXACTLY";
    case MeasureSpec.UNSPECIFIED:
      return "UNSPECIFIED";
    }
    
    return "ERROR";
  }
  
  private int getPreferredWidth(){    
    //Our preferred width and height is 200dp for the square sat / val rectangle.
    int width = (int)(200 * mDensity);
    
    return (int) (width + HUE_PANEL_WIDTH + PANEL_SPACING);  
  }
  
  private int getPreferredHeight(){  
    int height = (int)(200 * mDensity);
    
    if(mShowAlphaPanel){
      height += PANEL_SPACING + ALPHA_PANEL_HEIGHT;
    }
    return height;
  }
  
  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    
    mDrawingRect = new RectF();    
    mDrawingRect.left = mDrawingOffset + getPaddingLeft();
    mDrawingRect.right  = w - mDrawingOffset - getPaddingRight();
    mDrawingRect.top = mDrawingOffset + getPaddingTop();
    mDrawingRect.bottom = h - mDrawingOffset - getPaddingBottom();
    
    //The need to be recreated because they depend on the size of the view.
    mValShader = null;
    mSatShader = null;
    mHueShader = null;
    mAlphaShader = null;;
    
    setUpSatValRect();
    setUpHueRect();
    setUpAlphaRect();
  }
  
  private void setUpSatValRect(){
    //Calculate the size for the big color rectangle.
    final RectF  dRect = mDrawingRect;    
    
    float left = dRect.left + BORDER_WIDTH_PX;
    float top = dRect.top + BORDER_WIDTH_PX;
    float bottom = dRect.bottom - BORDER_WIDTH_PX;
    float right = dRect.right - BORDER_WIDTH_PX - PANEL_SPACING - HUE_PANEL_WIDTH;
    
    
    if(mShowAlphaPanel) {
      bottom -= (ALPHA_PANEL_HEIGHT + PANEL_SPACING);
    }
        
    mSatValRect = new RectF(left,top, right, bottom);
  }
  
  private void setUpHueRect(){
    //Calculate the size for the hue slider on the left.
    final RectF  dRect = mDrawingRect;    
    
    float left = dRect.right - HUE_PANEL_WIDTH + BORDER_WIDTH_PX;
    float top = dRect.top + BORDER_WIDTH_PX;
    float bottom = dRect.bottom - BORDER_WIDTH_PX - (mShowAlphaPanel ? (PANEL_SPACING + ALPHA_PANEL_HEIGHT) : 0);
    float right = dRect.right - BORDER_WIDTH_PX;
    
    mHueRect = new RectF(left, top, right, bottom);
  }

  private void setUpAlphaRect(){
    
    if(!mShowAlphaPanel) return;
    
    final RectF  dRect = mDrawingRect;    
    
    float left = dRect.left + BORDER_WIDTH_PX;
    float top = dRect.bottom - ALPHA_PANEL_HEIGHT + BORDER_WIDTH_PX;
    float bottom = dRect.bottom - BORDER_WIDTH_PX;
    float right = dRect.right - BORDER_WIDTH_PX;
    
    mAlphaRect = new RectF(left, top, right, bottom);  
    
  
    mAlphaPattern = new AlphaPatternDrawable((int) (5 * mDensity));
    mAlphaPattern.setBounds(Math.round(mAlphaRect.left), Math
        .round(mAlphaRect.top), Math.round(mAlphaRect.right), Math
        .round(mAlphaRect.bottom));
  }
  
  
  /**
   * Set a OnColorChangedListener to get notified when the color
   * selected by the user has changed.
   * @param listener
   */
  public void setOnColorChangedListener(OnColorChangedListener listener){
    mListener = listener;
  }
  
  /**
   * Get the current color this view is showing.
   * @return the current color.
   */
  public int getColor(){
    return Color.HSVToColor(mAlpha, new float[]{mHue,mSat,mVal});
  }
  
  /**
   * Set the color the view should show.
   * @param color The color that should be selected.
   */
  public void setColor(int color){
    setColor(color, false);
  }
  
  /**
   * Set the color this view should show.
   * @param color The color that should be selected.
   * @param callback If you want to get a callback to
   * your OnColorChangedListener.
   */
  public void setColor(int color, boolean callback){
  
    int alpha = Color.alpha(color);
    int red = Color.red(color);
    int blue = Color.blue(color);
    int green = Color.green(color);
    
    float[] hsv = new float[3];
    
    Color.RGBToHSV(red, green, blue, hsv);

    mAlpha = alpha;
    mHue = hsv[0];
    mSat = hsv[1];
    mVal = hsv[2];
    
    if(callback && mListener != null){      
      mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal}));        
    }
    
    invalidate();
  }
  
  /**
   * Get the drawing offset of the color picker view.
   * The drawing offset is the distance from the side of
   * a panel to the side of the view minus the padding.
   * Useful if you want to have your own panel below showing
   * the currently selected color and want to align it perfectly.
   * @return The offset in pixels.
   */
  public float getDrawingOffset(){
    return mDrawingOffset;
  }
  
  /**
   * Set if the user is allowed to adjust the alpha panel. Default is false.
   * If it is set to false no alpha will be set.
   * @param visible
   */
  public void setAlphaSliderVisible(boolean visible){
    
    if(mShowAlphaPanel != visible){
      mShowAlphaPanel = visible;
      
      /*
       * Reset all shader to force a recreation. 
       * Otherwise they will not look right after 
       * the size of the view has changed.
       */
      mValShader = null;
      mSatShader = null;
      mHueShader = null;
      mAlphaShader = null;;
      
      requestLayout();
    }
    
  }
  
  /**
   * Set the color of the tracker slider on the hue and alpha panel.
   * @param color
   */
  public void setSliderTrackerColor(int color){
    mSliderTrackerColor = color;
    mHueAlphaTrackerPaint.setColor(mSliderTrackerColor);    
    invalidate();
  }
  
  /**
   * Get color of the tracker slider on the hue and alpha panel.
   * @return
   */
  public int getSliderTrackerColor(){
    return mSliderTrackerColor;
  }
  
  /**
   * Set the color of the border surrounding all panels.
   * @param color
   */
  public void setBorderColor(int color){
    mBorderColor = color;
    invalidate();
  }
  
  /**
   * Get the color of the border surrounding all panels.
   */
  public int getBorderColor(){
    return mBorderColor;
  }
  
  /**
   * Set the text that should be shown in the 
   * alpha slider. Set to null to disable text.
   * @param res string resource id.
   */
  public void setAlphaSliderText(int res){    
    String text = getContext().getString(res);
    setAlphaSliderText(text);
  }
  
  /**
   * Set the text that should be shown in the 
   * alpha slider. Set to null to disable text.
   * @param text Text that should be shown.
   */
  public void setAlphaSliderText(String text){
    mAlphaSliderText = text;
    invalidate();
  }

  /**
   * Get the current value of the text
   * that will be shown in the alpha
   * slider.
   * @return
   */
  public String getAlphaSliderText(){
    return mAlphaSliderText;
  }

  private class BitmapCache {
    public Canvas  canvas;
    public Bitmap   bitmap;
    public float  value;
  }
}




Java Source Code List

com.musenkishi.wally.activities.ImageDetailsActivity.java
com.musenkishi.wally.activities.MainActivity.java
com.musenkishi.wally.adapters.RecyclerImagesAdapter.java
com.musenkishi.wally.adapters.RecyclerSavedImagesAdapter.java
com.musenkishi.wally.adapters.SmartFragmentPagerAdapter.java
com.musenkishi.wally.adapters.SmartFragmentStatePagerAdapter.java
com.musenkishi.wally.anim.BaseItemAnimator.java
com.musenkishi.wally.anim.ScaleInOutItemAnimator.java
com.musenkishi.wally.anim.interpolator.EaseInOutBezierInterpolator.java
com.musenkishi.wally.anim.interpolator.FastOutLinearInInterpolator.java
com.musenkishi.wally.anim.interpolator.FastOutSlowInInterpolator.java
com.musenkishi.wally.anim.interpolator.LinearOutSlowInInterpolator.java
com.musenkishi.wally.base.BaseActivity.java
com.musenkishi.wally.base.BaseFragment.java
com.musenkishi.wally.base.GridFragment.java
com.musenkishi.wally.base.WallyApplication.java
com.musenkishi.wally.dataprovider.DataProvider.java
com.musenkishi.wally.dataprovider.FileManager.java
com.musenkishi.wally.dataprovider.NetworkDataProvider.java
com.musenkishi.wally.dataprovider.SharedPreferencesDataProvider.java
com.musenkishi.wally.dataprovider.models.DataProviderError.java
com.musenkishi.wally.dataprovider.models.SaveImageRequest.java
com.musenkishi.wally.dataprovider.okhttp.OkHttpStreamFetcher.java
com.musenkishi.wally.dataprovider.okhttp.OkHttpUrlLoader.java
com.musenkishi.wally.dataprovider.util.Parser.java
com.musenkishi.wally.fragments.CustomResolutionDialogFragment.java
com.musenkishi.wally.fragments.FilterDialogFragment.java
com.musenkishi.wally.fragments.ImageZoomFragment.java
com.musenkishi.wally.fragments.LatestFragment.java
com.musenkishi.wally.fragments.MaterialDialogFragment.java
com.musenkishi.wally.fragments.RandomImagesFragment.java
com.musenkishi.wally.fragments.SavedImagesFragment.java
com.musenkishi.wally.fragments.SearchFragment.java
com.musenkishi.wally.fragments.ToplistFragment.java
com.musenkishi.wally.models.Author.java
com.musenkishi.wally.models.ExceptionReporter.java
com.musenkishi.wally.models.Filter.java
com.musenkishi.wally.models.ImagePage.java
com.musenkishi.wally.models.Image.java
com.musenkishi.wally.models.ListFilterGroup.java
com.musenkishi.wally.models.Rating.java
com.musenkishi.wally.models.SavedImageData.java
com.musenkishi.wally.models.Size.java
com.musenkishi.wally.models.Tag.java
com.musenkishi.wally.models.filters.FilterAspectRatioKeys.java
com.musenkishi.wally.models.filters.FilterBoardsKeys.java
com.musenkishi.wally.models.filters.FilterBoards.java
com.musenkishi.wally.models.filters.FilterGroup.java
com.musenkishi.wally.models.filters.FilterGroupsStructure.java
com.musenkishi.wally.models.filters.FilterPurityKeys.java
com.musenkishi.wally.models.filters.FilterPurity.java
com.musenkishi.wally.models.filters.FilterResOptKeys.java
com.musenkishi.wally.models.filters.FilterResOpt.java
com.musenkishi.wally.models.filters.FilterResolutionKeys.java
com.musenkishi.wally.models.filters.FilterTimeSpanKeys.java
com.musenkishi.wally.muzei.WallyArtSource.java
com.musenkishi.wally.notification.NotificationProvider.java
com.musenkishi.wally.observers.FileChangeReceiver.java
com.musenkishi.wally.observers.FiltersChangeReceiver.java
com.musenkishi.wally.util.Blur.java
com.musenkishi.wally.util.PaletteLoader.java
com.musenkishi.wally.util.PaletteRequest.java
com.musenkishi.wally.util.SparseBooleanArrayParcelable.java
com.musenkishi.wally.util.TextClickableSpan.java
com.musenkishi.wally.util.TextLinkBuilder.java
com.musenkishi.wally.util.TypefaceSpan.java
com.musenkishi.wally.views.AutoGridView.java
com.musenkishi.wally.views.GridRecyclerView.java
com.musenkishi.wally.views.ObservableScrollView.java
com.musenkishi.wally.views.TabBarView.java
com.musenkishi.wally.views.TabView.java
com.musenkishi.wally.views.swipeclearlayout.SwipeClearLayout.java
net.margaritov.preference.colorpicker.dialog.ColorPickerDialogFragment.java
net.margaritov.preference.colorpicker.drawable.AlphaPatternDrawable.java
net.margaritov.preference.colorpicker.preference.ColorPickerPreference.java
net.margaritov.preference.colorpicker.view.ColorPanelView.java
net.margaritov.preference.colorpicker.view.ColorPickerView.java
nl.codesoup.cubicbezier.CubicBezierInterpolator.java