Created
July 8, 2016 03:36
-
-
Save un1tz3r0/598f1ed64eadc017d2930ad0fdc2ef11 to your computer and use it in GitHub Desktop.
A work in progress based on the API Examples LabelView.java custom view, which implements (partially) a custom control similar to SeekBar,
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2007 The Android Open Source Project | |
* | |
* 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 com.example.android.apis.view; | |
// Need the following import to get access to the app resources, since this | |
// class is in a sub-package. | |
import android.animation.*; | |
import android.content.*; | |
import android.content.res.*; | |
import android.graphics.*; | |
import android.graphics.drawable.*; | |
import android.graphics.drawable.shapes.*; | |
import android.util.*; | |
import android.view.*; | |
import android.view.GestureDetector.*; | |
import android.widget.*; | |
import com.example.android.apis.*; | |
import android.app.*; | |
import java.util.*; | |
import android.view.animation.*; | |
/** | |
TODO: Major cleanup, some refactoring. This working draft is a mess. | |
TODO: Add missing getters and setters, and make sure all setters that should call invalidate do so. | |
TODO: Add event hooks for subclasses to respond to various changes to the objects state. | |
TODO: Add mising styleable attribute definitions for exposed properties. | |
TODO: Implement a better more cohesive physics model and animations. | |
TODO: Finish handling of tickmarks and add textual labels and value feedback that follows the thumb. | |
Implement collapsing tickmark labels based on multiple priority levels and available real estate. | |
TODO: Implement themed appearance for default drawing style. | |
-- V.C. | |
*/ | |
/** -------------------------------------------- | |
in res/values/attrs.xml... | |
<resources> | |
... | |
<declare-styleable name="LabelView"> | |
<attr name="text" format="string" /> | |
<attr name="ticks" format="string" /> | |
<attr name="minVal" format="float" /> | |
<attr name="maxVal" format="float" /> | |
<attr name="textColor" format="color" /> | |
<attr name="barColor" format="color" /> | |
<attr name="tickColor" format="color" /> | |
<attr name="textSize" format="dimension" /> | |
<attr name="barThickness" format="dimension" /> | |
<attr name="barPadding" format="dimension" /> | |
<attr name="tickHeight" format="dimension" /> | |
... | |
</declare-styleable> | |
... | |
</resources> | |
-------------------------------------------- */ | |
/** | |
* Example of how to write a custom subclass of View. LabelView | |
* is used to draw simple text views. Note that it does not handle | |
* styled text or right-to-left writing systems. | |
* | |
*/ | |
public class LabelView extends View | |
{ | |
// these determine the rate at which the thumb is animated to follow the pointer | |
// during touch interaction... mDragAnimateDuration is how long the thumb will take | |
// to get from its current position to the pointer, which is restarted each time the | |
// gesturedetector reports motion as part of a scrolling gesture. mSnapAnimateDuration | |
// is how long it should take the thumb to animate jumping from its resting value to the | |
// initial touch event location (i.e. when tracking begins, within onDown() vs. onScroll().) | |
private int mDragAnimateDuration = 250; | |
private int mSnapAnimateDuration = 125; | |
private float mFlingVelocityDenominator = 4f; | |
/* | |
private float mScrollFriction = 0.5f; | |
private float mScrollMomentum = 0.2f; | |
private class Smoother | |
{ | |
int mMin, mMax, mValue, mFinal, mVel, mFriction; | |
}; | |
*/ | |
private Paint mTextPaint; | |
private String mText; | |
private int mAscent; | |
private ArraySet<Float> mTicks = new ArraySet<>(); | |
// the bounded value which is linked to the thumb position | |
private float mValue = 0.5f, mMinVal = 0f, mMaxVal = 1f; | |
private boolean mIsTracking = false; | |
// appearance and geometry of the bar along which the thumb moves | |
private Paint mBarPaint; | |
private float mBarPadding = 9; | |
private float mBarThickness = 3; | |
// the geometry of the ticks below the slider | |
private Paint mTickPaint; | |
private float mTickHeight = 12; | |
private float mTickPadding = -6; | |
private float mTickThickness = 2; | |
// these only have an effect if the default thumb drawable is in use | |
private int mDotActiveColor; | |
private int mDotColor; | |
// the current thumb drawable, and whether or not it is the default StateListDrawable of PathShape ShapeDrawables | |
private Drawable mDotDrawable; | |
private boolean mIsDefaultDotDrawable; | |
// all of the stuff we need for smoothly animated touch interaction for our thumb | |
private OnGestureListener mOnGestureListener; | |
private GestureDetector mGestureDetector; | |
private Scroller mScroller; | |
private ValueAnimator mScrollAnimator; | |
private ValueAnimator mGlowAnimator; | |
private float mThumbGlowRadius; | |
private float mThumbWidth; | |
private float mThumbHeight; | |
private int mThumbCornerRadius; | |
private long mGlowAnimateDuration; | |
/** | |
* Constructor. This version is only needed if you will be instantiating | |
* the object manually (not from a layout XML file). | |
* @param context | |
*/ | |
public LabelView(Context context) | |
{ | |
super(context); | |
initLabelView(); | |
} | |
/** | |
* 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 LabelView(Context context, AttributeSet attrs) | |
{ | |
super(context, attrs); | |
initLabelView(); | |
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LabelView); | |
CharSequence s = a.getString(R.styleable.LabelView_text); | |
if (s != null) | |
setText(s.toString()); | |
// Retrieve the color(s) to be used for this view and apply them. | |
// Note, if you only care about supporting a single color, that you | |
// can instead call a.getColor() and pass that to setTextColor(). | |
setTextColor(a.getColor(R.styleable.LabelView_textColor, getResources().getColor(android.R.color.primary_text_dark))); | |
int textSize = a.getDimensionPixelOffset(R.styleable.LabelView_textSize, 0); | |
if(textSize > 0) | |
setTextSize(textSize); | |
setBarColor(a.getColor(R.styleable.LabelView_barColor, getResources().getColor(android.R.color.secondary_text_dark))); | |
setBarThickness(a.getDimensionPixelOffset(R.styleable.LabelView_barThickness, 3)); | |
setBarPadding(a.getDimensionPixelOffset(R.styleable.LabelView_barPadding, 6)); | |
setTickColor(a.getColor(R.styleable.LabelView_tickColor, getResources().getColor(android.R.color.tertiary_text_dark))); | |
setTickPadding(a.getDimensionPixelOffset(R.styleable.LabelView_tickPadding, 3)); | |
setTickHeight(a.getDimensionPixelOffset(R.styleable.LabelView_tickHeight, 6)); | |
setTickThickness(a.getDimensionPixelOffset(R.styleable.LabelView_tickThickness, 2)); | |
setDotColor(a.getColor(R.styleable.LabelView_dotColor, getResources().getColor(android.R.color.holo_blue_dark))); | |
setDotActiveColor(a.getColor(R.styleable.LabelView_dotActiveColor, getResources().getColor(android.R.color.holo_blue_light))); | |
Drawable dotDrawable = a.getDrawable(R.styleable.LabelView_dotDrawable); | |
if(dotDrawable != null) | |
setDotDrawable(dotDrawable); | |
else | |
setDefaultDotDrawable(); | |
a.recycle(); | |
} | |
public void onStopMoving() | |
{ | |
// TODO: Implement this method | |
} | |
private final void initLabelView() | |
{ | |
mTextPaint = new Paint(); | |
mTextPaint.setAntiAlias(true); | |
mBarPaint = new Paint(); | |
mBarPaint.setAntiAlias(true); | |
mBarPaint.setStrokeCap(Paint.Cap.ROUND); | |
mBarPaint.setStrokeWidth(mBarThickness * getResources().getDisplayMetrics().density); | |
mBarPaint.setColor(android.R.color.tertiary_text_dark); | |
mTickPaint = new Paint(); | |
mTickPaint.setAntiAlias(true); | |
mTickPaint.setStrokeCap(Paint.Cap.ROUND); | |
mTickPaint.setStrokeWidth(mTickThickness * getResources().getDisplayMetrics().density); | |
mTickPaint.setColor(android.R.color.secondary_text_dark); | |
// Must manually scale the desired text size to match screen density | |
mTextPaint.setTextSize(16 * getResources().getDisplayMetrics().density); | |
mTextPaint.setColor(android.R.color.primary_text_dark); | |
setPadding(3, 3, 3, 3); | |
mDotColor = getResources().getColor(android.R.color.holo_blue_light); | |
mDotActiveColor = getResources().getColor(android.R.color.holo_blue_bright); | |
mThumbHeight = 17; | |
mThumbWidth = 12; | |
mThumbGlowRadius = 3; | |
mThumbCornerRadius = 2; | |
mGlowAnimateDuration = 125; | |
setDefaultDotDrawable(); | |
calcMetrics(); | |
// set up scroller and value animator for smooooove moooves | |
mScroller = new Scroller(getContext(), null, true); | |
mScrollAnimator = ValueAnimator.ofFloat(0f,1f); | |
mScroller.setFriction(0.1f); | |
mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { | |
@Override | |
public void onAnimationUpdate(ValueAnimator valueAnimator) { | |
mScroller.computeScrollOffset(); | |
mValue = getValueAtX(mScroller.getCurrX()); | |
postInvalidate(); | |
if (mScroller.isFinished()) { | |
mScrollAnimator.cancel(); | |
if(mIsTracking==false) | |
LabelView.this.onStopMoving(); | |
} | |
} | |
}); | |
// set up gesture detection for touch interaction | |
mOnGestureListener = new OnGestureListener() { | |
@Override | |
public boolean onDown(MotionEvent pos) | |
{ | |
mIsTracking = true; | |
mScroller.forceFinished(true); | |
mScroller.startScroll(Math.round(getXAtValue(getValue())), 0, Math.round(pos.getX() - getXAtValue(getValue())), 0, mSnapAnimateDuration); | |
mScrollAnimator.start(); | |
// TODO: Implement this method | |
//mGlowAnimator.cancel(); | |
//mGlowAnimator.setCurrentFraction(0f); | |
//mGlowAnimator.start(); | |
onStartTracking(); | |
return true; | |
} | |
@Override | |
public void onShowPress(MotionEvent p1) | |
{ | |
// TODO: Implement this method | |
} | |
@Override | |
public boolean onSingleTapUp(MotionEvent p1) | |
{ | |
// TODO: Implement this method | |
return false; | |
} | |
@Override | |
public boolean onScroll(MotionEvent first, MotionEvent last, float dx, float dy) | |
{ | |
//if(mWasFling)" | |
// return false; | |
if(mScroller.isFinished()) | |
{ | |
mScrollAnimator.cancel(); | |
mScroller.startScroll(Math.round(getXAtValue(getValue())), 0, Math.round(last.getX()-getXAtValue(getValue())), 0, mDragAnimateDuration); | |
mScrollAnimator.start(); | |
} | |
else | |
{ | |
mScrollAnimator.cancel(); | |
mScroller.extendDuration(mDragAnimateDuration); | |
mScroller.setFinalX(Math.round(last.getX())); | |
mScrollAnimator.start(); | |
} | |
return true; | |
} | |
@Override | |
public void onLongPress(MotionEvent p1) | |
{ | |
// TODO: Implement this method | |
} | |
@Override | |
public boolean onFling(MotionEvent p1, MotionEvent p2, float p3, float p4) | |
{ | |
//mWasFling = true; | |
// TODO: Implement this method | |
mScrollAnimator.cancel(); | |
mScroller.forceFinished(true); | |
mScroller.fling(Math.round(getXAtValue(getValue())), 0, Math.round(p3/mFlingVelocityDenominator + (1f-1f/mFlingVelocityDenominator)*mScroller.getCurrVelocity()*0f), 0, Math.round(mMetrics.bxl), Math.round(mMetrics.bxr), 0, 0); | |
mScrollAnimator.start(); | |
return true; | |
} | |
}; | |
mGestureDetector = new GestureDetector(getContext(), mOnGestureListener); | |
// stuff for animating glow effect while tracking thumb | |
// ... is already set up by setDefaultDotDrawable() | |
} | |
public static int[] stateSetOps(int[] in, int[] exclude, int[] include, boolean reverseOrder) | |
{ | |
// use an ArraySet internally to hold the resulting set while we iteratively build it up | |
ArraySet<Integer> set = new ArraySet<>(in.length + include.length); | |
// iterate over each element of in[] since the resulting set is always a subset or its identity | |
for(int i = 0; i < in.length; i++) | |
{ | |
boolean found = false; | |
// check if in[i] is last occurrence in in[] and short circuit if we find out otherwise | |
for(int k = i + 1; k < in.length && !found; k++) | |
if(in[i] == in[k]) | |
found = true; | |
// check if in[i] occurs in exclude[], and short circuit if found | |
for(int j = 0; j < exclude.length && !found; j ++) | |
if(in[i] == exclude[j]) | |
found = true; | |
// if both above searches fail to match anything then in[i] is a member of the resulting set | |
if(!found) | |
set.add(i); | |
} | |
for(int l = 0; l < include.length; l ++) | |
{ | |
boolean found = false; | |
// check if include[i] is last occurrence in include[] and short circuit if we find out otherwise | |
for(int k = l + 1; k < include.length && !found; k++) | |
if(include[l] == include[k]) | |
found = true; | |
if(found) | |
continue; | |
// we reach this point once for each unique element of include[]... | |
boolean found_in = false; | |
boolean found_exclude = false; | |
for (int m = 0; m < in.length && !found_in; m++) | |
if (include[l] == in[m]) | |
found_in = true; | |
// in the case of include-then-exclude (reverseOrder is true), if in[] contains include[l] then we never need to add it to result set, can skip searching for include[l] in exclude[] | |
// in the case of exclude-then-include (reverseOrder is false), if in[] does not contain include[l], then we always need to add it to result set, can skip searching for include[l] in exclude[] | |
if((reverseOrder && !found_in) || (!reverseOrder && found_in)) | |
// check if include[i] occurs in exclude[], and short circuit if found | |
for(int j = 0; j < exclude.length && !found_exclude; j ++) | |
if(include[l] == exclude[j]) | |
found_exclude = true; | |
// if include-then-exclude, include[l] is added to results if it isnt a member of either in[] or exclude[] (meaning it is neither already added to the results, nor to be excluded from them) | |
// if exclude-then-include, include[l] is added to results unless it is a member of in[] but not of exclude[] (meaning that it was not added because it is not a member of in[] or because it is a member of both in[] and exclude[]) | |
if((reverseOrder && (!found_in && !found_exclude)) || | |
(!reverseOrder && (!found_in || found_exclude))) | |
set.add(include[l]); | |
} | |
// allocate a java array and populate it with result set | |
int[] out = new int[set.size()]; | |
for(int i = 0; i < set.size(); i ++) | |
out[i] = set.valueAt(i); | |
// return the new array | |
return out; | |
} | |
@Override | |
public boolean onTouchEvent(MotionEvent event) | |
{ | |
if(event.getActionMasked() == MotionEvent.ACTION_DOWN) | |
{ | |
this.mDotDrawable.setState(stateSetOps(mDotDrawable.getState(), EMPTY_STATE_SET, PRESSED_STATE_SET, false)); | |
mGlowAnimator.cancel(); | |
//mGlowAnimator.setupEndValues(); | |
//mGlowAnimator.end(); | |
mGlowAnimator.start(); | |
mIsTracking = true; | |
} | |
boolean gestureHandled = mGestureDetector.onTouchEvent(event); | |
if(event.getActionMasked() == MotionEvent.ACTION_UP || | |
event.getActionMasked() == MotionEvent.ACTION_CANCEL) | |
{ | |
this.mDotDrawable.setState(stateSetOps(mDotDrawable.getState(), PRESSED_STATE_SET, EMPTY_STATE_SET, false)); | |
this.mGlowAnimator.cancel(); | |
this.mGlowAnimator.reverse(); | |
mIsTracking = false; | |
} | |
if(gestureHandled) | |
return true; | |
return super.onTouchEvent(event); | |
} | |
protected /*abstract*/ void onStartTracking() | |
{ | |
// mGlowAnimator.start(); | |
} | |
protected /*abstract*/ void onStopTracking() | |
{ | |
// TODO: Implement me or dont. Im like, "whateva, man". | |
} | |
private void setDefaultDotDrawable() | |
{ | |
Path dotPath = new Path(); | |
dotPath.moveTo(mThumbGlowRadius, mThumbGlowRadius); | |
dotPath.lineTo(mThumbGlowRadius+mThumbWidth, mThumbGlowRadius); | |
dotPath.lineTo(mThumbGlowRadius+mThumbWidth, mThumbGlowRadius+mThumbHeight-mThumbWidth/2f); | |
dotPath.lineTo(mThumbGlowRadius+mThumbWidth/2f, mThumbGlowRadius+mThumbHeight); | |
dotPath.lineTo(mThumbGlowRadius, mThumbGlowRadius+mThumbHeight-mThumbWidth/2f); | |
dotPath.close(); | |
PathShape dotShape = new PathShape(dotPath, Math.round(mThumbGlowRadius*2f+mThumbWidth), Math.round(mThumbGlowRadius*2f+mThumbHeight)); | |
ShapeDrawable dotInactiveDrawable = new ShapeDrawable(dotShape); | |
dotInactiveDrawable.setIntrinsicWidth(Math.round(mThumbWidth+mThumbGlowRadius*2f)); | |
dotInactiveDrawable.setIntrinsicHeight(Math.round(mThumbHeight +mThumbGlowRadius*2f )); | |
dotInactiveDrawable.getPaint().setAntiAlias(true); | |
dotInactiveDrawable.getPaint().setColor(mDotColor); | |
dotInactiveDrawable.getPaint().setStyle(Paint.Style.FILL); | |
dotInactiveDrawable.getPaint().setPathEffect(new CornerPathEffect(mThumbCornerRadius)); | |
ShapeDrawable dotActiveDrawable = new ShapeDrawable(dotShape); | |
dotActiveDrawable.setIntrinsicWidth(Math.round(mThumbWidth+mThumbGlowRadius*2f)); | |
dotActiveDrawable.setIntrinsicHeight(Math.round(mThumbHeight+mThumbGlowRadius*2f)); | |
dotActiveDrawable.getPaint().setAntiAlias(true); | |
dotActiveDrawable.getPaint().setColor(mDotActiveColor); | |
dotActiveDrawable.getPaint().setAlpha(0); | |
dotActiveDrawable.getPaint().setStyle(Paint.Style.FILL); | |
dotActiveDrawable.getPaint().setMaskFilter(new BlurMaskFilter(mThumbGlowRadius, BlurMaskFilter.Blur.SOLID)); | |
dotActiveDrawable.getPaint().setPathEffect(new CornerPathEffect(mThumbCornerRadius)); | |
LayerDrawable dotDrawable = new LayerDrawable(new Drawable[] { | |
dotInactiveDrawable, | |
dotActiveDrawable | |
}); | |
mGlowAnimator = ObjectAnimator.ofInt(dotActiveDrawable.getPaint(), "alpha", 0, 255); | |
mGlowAnimator.addUpdateListener(new ObjectAnimator.AnimatorUpdateListener() { | |
@Override | |
public void onAnimationUpdate(ValueAnimator p1) | |
{ | |
postInvalidate(); | |
} | |
}); | |
mGlowAnimator.setDuration(mGlowAnimateDuration); | |
mDotDrawable = dotDrawable; | |
mIsDefaultDotDrawable = true; | |
} | |
/** | |
* @see android.view.View#measure(int, int) | |
*/ | |
@Override | |
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) | |
{ | |
setMeasuredDimension(measureWidth(widthMeasureSpec), | |
measureHeight(heightMeasureSpec)); | |
} | |
/** | |
* 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 | |
result = specSize; | |
} | |
else | |
{ | |
// Measure the text | |
result = (int)( | |
// mTextPaint.measureText(mText) | |
+ getPaddingLeft() | |
+ getPaddingRight() + mMetrics.dw + mTickThickness * 2f + mBarThickness); | |
if (specMode == MeasureSpec.AT_MOST ) | |
{ | |
// Respect AT_MOST value if that was what is called for by measureSpec | |
result = Math.min(result, specSize); | |
} | |
} | |
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); | |
mAscent = (int) mTextPaint.ascent(); | |
if (specMode == MeasureSpec.EXACTLY) | |
{ | |
// We were told how big to be | |
result = specSize; | |
} | |
else | |
{ | |
calcMetrics(); | |
// Measure the text (beware: ascent is a negative number) | |
result = (int) (-mAscent + mTextPaint.descent()) + | |
+ getPaddingBottom() + (int)(mMetrics.tkyb + mBarPadding + mTickThickness/2f); | |
if (specMode == MeasureSpec.AT_MOST) | |
{ | |
// Respect AT_MOST value if that was what is called for by measureSpec | |
result = Math.min(result, specSize); | |
} | |
} | |
return result; | |
} | |
/** | |
* Render the text | |
* | |
* @see android.view.View#onDraw(android.graphics.Canvas) | |
*/ | |
private static class Metrics | |
{ | |
float dw, dh, // the width and height of the thumb drawable | |
dox, doy, // offset of thumb drawable centering it on the origin | |
bxl, bxr, // the x extents of the bar/thumb travel | |
by, // the y position of the bar/thumb origin | |
tkyt, tkyb; // the y extents of the tick marks | |
} | |
private final Metrics mMetrics = new Metrics(); | |
private void calcMetrics() | |
{ | |
// get size and origin of thumb drawable | |
mMetrics.dw = mDotDrawable.getIntrinsicWidth(); | |
mMetrics.dh = mDotDrawable.getIntrinsicHeight(); | |
mMetrics.dox = mMetrics.dw/-2f; | |
mMetrics.doy = mMetrics.dw/-2f; | |
// get horizontal extents and vertical position of the bar | |
mMetrics.bxl = getPaddingLeft() - mMetrics.dox; | |
mMetrics.bxr = getWidth() - (getPaddingRight() + mMetrics.dox + mMetrics.dw); | |
mMetrics.by = getPaddingTop() - mMetrics.doy;// + mBarThickness / 2f; | |
mMetrics.tkyt = mMetrics.by + mMetrics.doy - mThumbGlowRadius + mMetrics.dh + mTickPadding + mTickThickness / 2f; | |
mMetrics.tkyb = mMetrics.tkyt + mTickHeight; | |
} | |
@Override | |
protected void onDraw(Canvas canvas) | |
{ | |
super.onDraw(canvas); | |
float ta = mTextPaint.ascent(), | |
td = mTextPaint.descent(), | |
th = -ta + td; | |
// recalculate metrics, and then populate local copies | |
calcMetrics(); | |
float dw = mMetrics.dw, dh = mMetrics.dh, dox = mMetrics.dox, doy = mMetrics.doy, // get size and origin of thumb drawable | |
bxl = mMetrics.bxl, bxr = mMetrics.bxr, by = mMetrics.by, // get horizontal extents and vertical position of the bar | |
tkyt = mMetrics.tkyt, tkyb = mMetrics.tkyb; // and vertical extents of tick marks | |
ArraySet<Float> ticks = new ArraySet<>(); | |
ticks.addAll(mTicks); | |
ticks.add(mMinVal); | |
ticks.add(mMaxVal); | |
for (Float tick : ticks) | |
{ | |
float tickq = (tick - mMinVal) / (mMaxVal - mMinVal); | |
float tickx = tickq * (bxr - bxl) + bxl; | |
canvas.drawLine(tickx, tkyt, tickx, tkyb, mTickPaint); | |
} | |
canvas.drawLine(bxl, by, bxr, by, mBarPaint); | |
canvas.drawText(mText, getPaddingLeft(), canvas.getHeight() - getPaddingBottom() - td, mTextPaint); | |
float | |
dx = dox + bxl + (bxr - bxl) * ((mValue - mMinVal) / (mMaxVal - mMinVal)), | |
dy = by + doy; | |
mDotDrawable.setBounds(Math.round(dx), Math.round(dy), Math.round(dx + dw), Math.round(dy + dh)); | |
mDotDrawable.draw(canvas); | |
} | |
private Rect getThumbBox() | |
{ | |
// recalculate metrics, and then populate local copies | |
calcMetrics(); | |
float dw = mMetrics.dw, dh = mMetrics.dh, dox = mMetrics.dox, doy = mMetrics.doy, // get size and origin of thumb drawable | |
bxl = mMetrics.bxl, bxr = mMetrics.bxr, by = mMetrics.by; // get horizontal extents and vertical position of the bar | |
float | |
dx = dox + bxl + (bxr - bxl) * ((mValue - mMinVal) / (mMaxVal - mMinVal)), | |
dy = by + doy; | |
return new Rect(Math.round(dx), Math.round(dy), Math.round(dx+dw), Math.round(dy+dh)); | |
} | |
private Rect getBarBox() | |
{ | |
// recalculate metrics, and then populate local copies | |
calcMetrics(); | |
float dw = mMetrics.dw, dh = mMetrics.dh, dox = mMetrics.dox, doy = mMetrics.doy, // get size and origin of thumb drawable | |
bxl = mMetrics.bxl, bxr = mMetrics.bxr, by = mMetrics.by; | |
return new Rect(Math.round(bxl+dox), Math.round(by+doy), Math.round(bxr+dox+dw), Math.round(by+doy+dh)); | |
} | |
private float getValueAtX(float x) | |
{ | |
// recalculate metrics, and then populate local copies | |
calcMetrics(); | |
float bxl = mMetrics.bxl, bxr = mMetrics.bxr; | |
if(x < bxl) x = bxl; | |
if(x > bxr) x = bxr; | |
return getMinVal()+((x - bxl)/(bxr-bxl))*(getMaxVal()-getMinVal()); | |
} | |
private float getXAtValue(float v) | |
{ | |
// recalculate metrics, and then populate local copies | |
calcMetrics(); | |
float bxl = mMetrics.bxl, bxr = mMetrics.bxr; | |
float minv = getMinVal(), maxv = getMaxVal(); | |
if(v < minv) v = minv; | |
if(v > maxv) v = maxv; | |
return bxl + ((v - minv) / (maxv - minv)) * (bxr - bxl); | |
} | |
// ====================================== | |
public void setValue(float mValue) | |
{ | |
if(mValue < getMinVal()) mValue = getMinVal(); | |
if(mValue > getMaxVal()) mValue = getMaxVal(); | |
this.mValue = mValue; | |
invalidate(); | |
} | |
public float getValue() | |
{ | |
return mValue; | |
} | |
public void setMinVal(float mMinVal) | |
{ | |
this.mMinVal = mMinVal; | |
invalidate(); | |
} | |
public float getMinVal() | |
{ | |
return mMinVal; | |
} | |
public void setMaxVal(float mMaxVal) | |
{ | |
this.mMaxVal = mMaxVal; | |
invalidate(); | |
} | |
public float getMaxVal() | |
{ | |
return mMaxVal; | |
} | |
public void setDotColor(int color) | |
{ | |
// TODO: Reach inside that StateListDrawable and fuck up some Drawable getPaint().setColor() methods | |
this.mDotColor = color; | |
//setDotDefaultDrawable(); | |
if(mIsDefaultDotDrawable) | |
{ | |
this.setDefaultDotDrawable(); | |
invalidate(); | |
} | |
} | |
public void setDotActiveColor(int color) | |
{ | |
// TODO: Reach inside that StateListDrawable and fuck up some Drawable getPaint().setColor() methods | |
this.mDotActiveColor = color; | |
//setDotDefaultDrawable(); | |
if(mIsDefaultDotDrawable) | |
{ | |
this.setDefaultDotDrawable(); | |
invalidate(); | |
} | |
} | |
public void setDotDrawable(Drawable mDotDrawable) | |
{ | |
if(mDotDrawable != null) { | |
mIsDefaultDotDrawable = false; | |
this.mDotDrawable = mDotDrawable; | |
} | |
else { | |
mIsDefaultDotDrawable = true; | |
this.setDefaultDotDrawable(); | |
} | |
requestLayout(); | |
invalidate(); | |
} | |
public Drawable getDotDrawable() | |
{ | |
return mDotDrawable; | |
} | |
public void setTickThickness(float mTickThickness) | |
{ | |
this.mTickThickness = mTickThickness; | |
mTickPaint.setStrokeWidth(mTickThickness); | |
requestLayout(); | |
invalidate(); | |
} | |
public float getTickThickness() | |
{ | |
return mTickThickness; | |
} | |
public void setTickPadding(float mTickPadding) | |
{ | |
this.mTickPadding = mTickPadding; | |
requestLayout(); | |
invalidate(); | |
} | |
public float getTickPadding() | |
{ | |
return mTickPadding; | |
} | |
public void setBarPadding(float mBarPadding) | |
{ | |
this.mBarPadding = mBarPadding; | |
requestLayout(); | |
invalidate(); | |
} | |
public float getBarPadding() | |
{ | |
return mBarPadding; | |
} | |
public void setTickHeight(float mTickHeight) | |
{ | |
this.mTickHeight = mTickHeight; | |
requestLayout(); | |
invalidate(); | |
} | |
public float getTickHeight() | |
{ | |
return mTickHeight; | |
} | |
public void setBarThickness(float mBarThickness) | |
{ | |
this.mBarThickness = mBarThickness; | |
mBarPaint.setStrokeWidth(mBarThickness); | |
requestLayout(); | |
invalidate(); | |
} | |
public float getBarThickness() | |
{ | |
return mBarThickness; | |
} | |
public void setTicks(ArraySet<Float> mTicks) | |
{ | |
this.mTicks = mTicks; | |
requestLayout(); | |
invalidate(); | |
} | |
public ArraySet<Float> getTicks() | |
{ | |
return mTicks; | |
} | |
/** | |
* Sets the text to display in this label | |
* @param text The text to display. This will be drawn as one line. | |
*/ | |
public void setText(String text) | |
{ | |
mText = text; | |
requestLayout(); | |
invalidate(); | |
} | |
/** | |
* Sets the text size for this label | |
* @param size Font size | |
*/ | |
public void setTextSize(int size) | |
{ | |
// This text size has been pre-scaled by the getDimensionPixelOffset method | |
mTextPaint.setTextSize(size); | |
requestLayout(); | |
invalidate(); | |
} | |
/** | |
* Sets the text color for this label. | |
* @param color ARGB value for the text | |
*/ | |
public void setTextColor(int color) | |
{ | |
mTextPaint.setColor(color); | |
invalidate(); | |
} | |
public void setBarColor(int color) | |
{ | |
mBarPaint.setColor(color); | |
invalidate(); | |
} | |
public void setTickColor(int color) | |
{ | |
mTickPaint.setColor(color); | |
invalidate(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment