package com.ijoomer.customviews;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import com.ijoomer.src.R;
import java.util.List;
import java.util.Vector;
public class RangeSeekBar extends View {
private static final String TAG = "RangeSeekBar";
public static final int HORIZONTAL = 0;
public static final int VERTICAL = 1;
private static final int DEFAULT_THUMBS = 2;
private static final float DEFAULT_STEP = 5.0f;
private List<RangeSeekBarListener> listeners;
private List<Thumb> thumbs;
private float thumbWidth;
private float thumbHeight;
private float thumbHalf;
private float pixelRangeMin;
private float pixelRangeMax;
private int orientation;
private boolean limitThumbRange;
private int viewWidth;
private int viewHeight;
public float scaleRangeMin;
public float scaleRangeMax;
private float scaleStep;
private Drawable track;
private Drawable range;
private Drawable thumb;
private boolean firstRun;
private void init(Context context) {
orientation = HORIZONTAL;
limitThumbRange = true;
scaleRangeMin = 0;
scaleRangeMax = 100;
scaleStep = DEFAULT_STEP;
viewWidth = 0;
viewHeight = 0;
thumbWidth = 50;
thumbHeight = 100;
thumbs = new Vector<Thumb>();
listeners = new Vector<RangeSeekBarListener>();
this.setFocusable(true);
this.setFocusableInTouchMode(true);
firstRun = true;
}
public RangeSeekBar(Context context) {
super(context);
init(context);
}
/**
* Construct object, initializing with any attributes we understand from a
* layout file. These attributes are defined in
* SDK/assets/res/any/classes.xml.
*
* @see android.view.View#View(android.content.Context, android.util.AttributeSet)
*/
public RangeSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
// Obtain our styled custom attributes from xml
TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.RangeSeekBar);
String s = a.getString(R.styleable.RangeSeekBar_orientation);
if(s != null)
orientation = s.toLowerCase().contains("vertical") ? VERTICAL : HORIZONTAL;
limitThumbRange = a.getBoolean(R.styleable.RangeSeekBar_limitThumbRange, true);
scaleRangeMin = a.getFloat(R.styleable.RangeSeekBar_scaleMin, 0);
scaleRangeMax = a.getFloat(R.styleable.RangeSeekBar_scaleMax, 100);
scaleStep = Math.abs(a.getFloat(R.styleable.RangeSeekBar_scaleStep, DEFAULT_STEP));
thumb = a.getDrawable(R.styleable.RangeSeekBar_thumb);
range = a.getDrawable(R.styleable.RangeSeekBar_range);
track = a.getDrawable(R.styleable.RangeSeekBar_track);
// Register desired amount of thumbs
int noThumbs = a.getInt(R.styleable.RangeSeekBar_thumbs, DEFAULT_THUMBS);
thumbWidth = a.getDimension(R.styleable.RangeSeekBar_thumbWidth, 50);
thumbHeight = a.getDimension(R.styleable.RangeSeekBar_thumbHeight, 100);
for(int i = 0; i < noThumbs; i++) {
Thumb th = new Thumb();
thumbs.add(th);
}
a.recycle();
}
/**
* {@inheritDoc}
* @see android.view.View#measure(int, int)
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
viewWidth = measureWidth(widthMeasureSpec);
viewHeight = measureHeight(heightMeasureSpec);
setMeasuredDimension(viewWidth,viewHeight);
//
thumbHalf = (orientation == VERTICAL) ? (thumbHeight/2) : (thumbWidth/2);
pixelRangeMin = 0 + thumbHalf;
pixelRangeMax = (orientation == VERTICAL) ? viewHeight : viewWidth;
pixelRangeMax -= thumbHalf;
if(firstRun) {
distributeThumbsEvenly();
// Fire listener callback
if(listeners != null && listeners.size() > 0) {
for(RangeSeekBarListener l : listeners) {
l.onCreate(currentThumb,getThumbValue(currentThumb));
}
}
firstRun = false;
}
}
/**
* Draw
*
* @see android.view.View#onDraw(android.graphics.Canvas)
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas); // 1. Make sure parent view get to draw it's components
drawGutter(canvas); // 2. Draw slider gutter
drawRange(canvas); // 3. Draw range in gutter
drawThumbs(canvas); // 4. Draw thumbs
}
private int currentThumb = 0;
private float lowLimit = pixelRangeMin;
private float highLimit = pixelRangeMax;
/**
* {@inheritDoc}
*/
@Override
public boolean onTouchEvent (MotionEvent event) {
if(!thumbs.isEmpty()) {
//boolean testFocus = this.requestFocus();
//Log.d(TAG,"Focus: "+testFocus);
float coordinate = (orientation == VERTICAL) ? event.getY() : event.getX();
// Find thumb closest to event coordinate on screen touch
if(event.getAction() == MotionEvent.ACTION_DOWN) {
currentThumb = getClosestThumb(coordinate);
Log.d(TAG,"Closest "+currentThumb);
lowLimit = getLowerThumbRangeLimit(currentThumb);
highLimit = getHigherThumbRangeLimit(currentThumb);
}
if(event.getAction() == MotionEvent.ACTION_CANCEL || event.getAction() == MotionEvent.ACTION_UP) {
//
}
// Update thumb position
// Make sure we stay in our tracks's bounds or limited by other thumbs
if(coordinate < lowLimit) {
if(lowLimit == highLimit && currentThumb >= thumbs.size()-1) {
currentThumb = getUnstuckFrom(currentThumb);
setThumbPos(currentThumb,coordinate);
lowLimit = getLowerThumbRangeLimit(currentThumb);
highLimit = getHigherThumbRangeLimit(currentThumb);
} else
setThumbPos(currentThumb,lowLimit);
//Log.d(TAG,"Setting low "+low);
} else if(coordinate > highLimit) {
setThumbPos(currentThumb,highLimit);
//Log.d(TAG,"Setting high "+high);
} else {
coordinate = asStep(coordinate);
setThumbPos(currentThumb,coordinate);
//Log.d(TAG,"Setting coordinate "+coordinate);
}
// Fire listener callbacks
if(listeners != null && listeners.size() > 0) {
for(RangeSeekBarListener l : listeners) {
l.onSeek(currentThumb,getThumbValue(currentThumb));
}
}
// Tell the view we want a complete redraw
//invalidate();
// Tell the system we've handled this event
return true;
}
return false;
}
private int getUnstuckFrom(int index) {
int unstuck = 0;
float lastVal = thumbs.get(index).val;
for(int i = index-1; i >= 0; i--) {
Thumb th = thumbs.get(i);
if(th.val != lastVal)
return i+1;
}
return unstuck;
}
private float asStep(float pixelValue) {
return stepScaleToPixel(pixelToStep(pixelValue));
}
private float pixelToScale(float pixelValue) {
float pixelRange = (pixelRangeMax - pixelRangeMin);
float scaleRange = (scaleRangeMax - scaleRangeMin);
float scaleValue = (((pixelValue - pixelRangeMin) * scaleRange) / pixelRange) + scaleRangeMin;
return scaleValue;
}
private float scaleToPixel(float scaleValue) {
float pixelRange = (pixelRangeMax - pixelRangeMin);
float scaleRange = (scaleRangeMax - scaleRangeMin);
float pixelValue = (((scaleValue - scaleRangeMin) * pixelRange) / scaleRange) + pixelRangeMin;
return pixelValue;
}
private float pixelToStep(float pixelValue) {
float stepScaleMin = 0;
float stepScaleMax = (float) Math.floor((scaleRangeMax-scaleRangeMin)/scaleStep);
float pixelRange = (pixelRangeMax - pixelRangeMin);
float stepScaleRange = (stepScaleMax - stepScaleMin);
float stepScaleValue = (((pixelValue - pixelRangeMin) * stepScaleRange) / pixelRange) + stepScaleMin;
//Log.d(TAG,"scaleVal: "+scaleValue+" smin: "+scaleMin+" smax: "+scaleMax);
return Math.round(stepScaleValue);
}
private float stepScaleToPixel(float stepScaleValue) {
float stepScaleMin = 0;
float stepScaleMax = (float) Math.floor((scaleRangeMax-scaleRangeMin)/scaleStep);
float pixelRange = (pixelRangeMax - pixelRangeMin);
float stepScaleRange = (stepScaleMax - stepScaleMin);
float pixelValue = (((stepScaleValue - stepScaleMin) * pixelRange) / stepScaleRange) + pixelRangeMin;
//Log.d(TAG,"pixelVal: "+pixelValue+" smin: "+scaleMin+" smax: "+scaleMax);
return pixelValue;
}
private void calculateThumbValue(int index) {
if(index < thumbs.size() && !thumbs.isEmpty()) {
Thumb th = thumbs.get(index);
th.val = pixelToScale(th.pos);
}
}
private void calculateThumbPos(int index) {
if(index < thumbs.size() && !thumbs.isEmpty()) {
Thumb th = thumbs.get(index);
th.pos = scaleToPixel(th.val);
}
}
private float getLowerThumbRangeLimit(int index) {
float limit = pixelRangeMin;
if(limitThumbRange && index < thumbs.size() && !thumbs.isEmpty()) {
Thumb th = thumbs.get(index);
for(int i = 0; i < thumbs.size(); i++) {
if(i < index) {
Thumb tht = thumbs.get(i);
if(tht.pos <= th.pos && tht.pos > limit) {
limit = tht.pos;
//Log.d(TAG,"New low limit: "+limit+" i:"+i+" index: "+index);
}
}
}
}
return limit;
}
private float getHigherThumbRangeLimit(int index) {
float limit = pixelRangeMax;
if(limitThumbRange && index < thumbs.size() && !thumbs.isEmpty()) {
Thumb th = thumbs.get(index);
for(int i = 0; i < thumbs.size(); i++) {
if(i > index) {
Thumb tht = thumbs.get(i);
if(tht.pos >= th.pos && tht.pos < limit) {
limit = tht.pos;
//Log.d(TAG,"New high limit: "+limit+" i:"+i+" index: "+index);
}
}
}
}
return limit;
}
public void distributeThumbsEvenly() {
if(!thumbs.isEmpty()) {
int noThumbs = thumbs.size();
float even = pixelRangeMax/noThumbs;
float lastPos = even/2;
for(int i = 0; i < thumbs.size(); i++) {
setThumbPos(i, asStep(lastPos));
//Log.d(TAG,"lp: "+lastPos);
lastPos += even;
}
}
}
public float getThumbValue(int index) {
return thumbs.get(index).val;
}
public void setThumbValue(int index, float value) {
thumbs.get(index).val = value;
calculateThumbPos(index);
// Tell the view we want a complete redraw
invalidate();
}
private void setThumbPos(int index, float pos) {
thumbs.get(index).pos = pos;
calculateThumbValue(index);
// Tell the view we want a complete redraw
invalidate();
}
private int getClosestThumb(float coordinate) {
int closest = 0;
if(!thumbs.isEmpty()) {
float shortestDistance = pixelRangeMax+thumbHalf+((orientation == VERTICAL) ? (getPaddingTop()+getPaddingBottom()) : (getPaddingLeft() + getPaddingRight()));
// Oldschool for-loop to have access to index
for(int i = 0; i < thumbs.size(); i++) {
// Find thumb closest to x coordinate
float tcoordinate = thumbs.get(i).pos;
float distance = Math.abs(coordinate-tcoordinate);
if(distance <= shortestDistance) {
shortestDistance = distance;
closest = i;
//Log.d(TAG,"shDist: "+shortestDistance+" thumb i: "+closest);
}
}
}
return closest;
}
private void drawGutter(Canvas canvas) {
if(track != null) {
//Log.d(TAG,"gutterbg: "+gutterBackground.toString());
Rect area1 = new Rect();
area1.left = 0 + getPaddingLeft();
area1.top = 0 + getPaddingTop();
area1.right = getMeasuredWidth() - getPaddingRight();
area1.bottom = getMeasuredHeight() - getPaddingBottom();
track.setBounds(area1);
track.draw(canvas);
}
}
/*
RectF area = new RectF();
area.left = 0 + getPaddingLeft() + minPos;
area.top = 0 + getPaddingTop();
area.right = getMeasuredWidth() - getPaddingRight() + maxPos;
area.bottom = getMeasuredHeight() - getPaddingBottom();
Paint p = new Paint();
p.setAntiAlias(true);
p.setColor(gutterColor);
canvas.drawRoundRect(area, 7.5f, 7.5f, p);
*/
private void drawRange(Canvas canvas) {
if(!thumbs.isEmpty()) {
Thumb thLow = thumbs.get(getClosestThumb(0));
Thumb thHigh = thumbs.get(getClosestThumb(pixelRangeMax));
// If we only have 1 thumb - choose to draw from 0 in scale
if(thumbs.size() == 1)
thLow = new Thumb();
//Log.d(TAG,"l: "+thLow.pos+" h: "+thHigh.pos);
if(range != null) {
Rect area1 = new Rect();
if(orientation == VERTICAL) {
area1.left = 0 + getPaddingLeft();
area1.top = (int) thLow.pos;
area1.right = getMeasuredWidth() - getPaddingRight();
area1.bottom = (int) thHigh.pos;
} else {
area1.left = (int) thLow.pos;
area1.top = 0 + getPaddingTop();
area1.right = (int) thHigh.pos;
area1.bottom = getMeasuredHeight() - getPaddingBottom();
}
range.setBounds(area1);
range.draw(canvas);
}
}
}
private void drawThumbs(Canvas canvas) {
if(!thumbs.isEmpty()) {
//Paint p = new Paint();
for(Thumb th : thumbs) {
Rect area1 = new Rect();
//Log.d(TAG,""+th.pos);
if(orientation == VERTICAL) {
area1.left = 0 + getPaddingLeft();
area1.top = (int) ((th.pos - thumbHalf) + getPaddingTop());
area1.right = getMeasuredWidth() - getPaddingRight();
area1.bottom = (int) ((th.pos + thumbHalf) - getPaddingBottom());
//Log.d(TAG,"th: "+th.pos);
} else {
area1.left = (int) ((th.pos - thumbHalf) + getPaddingLeft());
area1.top = 0 + getPaddingTop();
area1.right = (int) ((th.pos + thumbHalf) - getPaddingRight());
area1.bottom = getMeasuredHeight() - getPaddingBottom();
//Log.d(TAG,"th: "+area1.toString());
}
thumb.setBounds(area1);
thumb.draw(canvas);
}
}
}
/**
* Determines the width of this view
* @param measureSpec A measureSpec packed into an int
* @return The width of the view, honoring constraints from measureSpec
*/
private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
//Log.d(TAG,"measureWidth() EXACTLY");
result = specSize;
} else {
// Measure
//Log.d(TAG,"measureWidth() not EXACTLY");
result = specSize + getPaddingLeft() + getPaddingRight();
if (specMode == MeasureSpec.AT_MOST) {
// Respect AT_MOST value if that was what is called for by measureSpec
//Log.d(TAG,"measureWidth() AT_MOST");
result = Math.min(result, specSize);
// Add our thumbWidth to the equation if we're vertical
if(orientation == VERTICAL) {
int h = (int) (thumbWidth+ getPaddingLeft() + getPaddingRight());
result = Math.min(result, h);
}
}
}
return result;
}
/**
* Determines the height of this view
* @param measureSpec A measureSpec packed into an int
* @return The height of the view, honoring constraints from measureSpec
*/
private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
//Log.d(TAG,"measureHeight() EXACTLY");
result = specSize;
} else {
// Measure
//Log.d(TAG,"measureHeight() not EXACTLY");
result = specSize + getPaddingTop() + getPaddingBottom();
if (specMode == MeasureSpec.AT_MOST) {
// Respect AT_MOST value if that was what is called for by measureSpec
//Log.d(TAG,"measureHeight() AT_MOST");
result = Math.min(result, specSize);
// Add our thumbHeight to the equation if we're horizontal
if(orientation == HORIZONTAL) {
int h = (int) (thumbHeight+ getPaddingTop() + getPaddingBottom());
result = Math.min(result, h);
}
}
}
return result;
}
public class Thumb {
public float val;
public float pos;
public Thumb() {
val = 0;
pos = 0;
}
}
public interface RangeSeekBarListener {
public void onCreate(int index, float value);
public void onSeek(int index, float value);
}
public void setListener(RangeSeekBarListener listener) {
listeners.add(listener);
}
public int getOrientation() {
return orientation;
}
public void setOrientation(int orientation) {
this.orientation = orientation;
}
public float getThumbWidth() {
return thumbWidth;
}
public void setThumbWidth(float thumbWidth) {
this.thumbWidth = thumbWidth;
}
public float getThumbHeight() {
return thumbHeight;
}
public void setThumbHeight(float thumbHeight) {
this.thumbHeight = thumbHeight;
}
public boolean isLimitThumbRange() {
return limitThumbRange;
}
public void setLimitThumbRange(boolean limitThumbRange) {
this.limitThumbRange = limitThumbRange;
}
public float getScaleRangeMin() {
return scaleRangeMin;
}
public void setScaleRangeMin(float scaleRangeMin) {
this.scaleRangeMin = scaleRangeMin;
}
public float getScaleRangeMax() {
return scaleRangeMax;
}
public void setScaleRangeMax(float scaleRangeMax) {
this.scaleRangeMax = scaleRangeMax;
}
public float getScaleStep() {
return scaleStep;
}
public void setScaleStep(float scaleStep) {
this.scaleStep = scaleStep;
}
public Drawable getTrack() {
return track;
}
public void setTrack(Drawable track) {
this.track = track;
}
public Drawable getRange() {
return range;
}
public void setRange(Drawable range) {
this.range = range;
}
public Drawable getThumb() {
return thumb;
}
public void setThumb(Drawable thumb) {
this.thumb = thumb;
}
public void initThumbs(int noThumbs)
{
for(int i = 0; i < noThumbs; i++) {
Thumb th = new Thumb();
thumbs.add(th);
}
}
}