diff --git a/ruler-picker/src/main/java/com/kevalpatel2106/rulerpicker/RulerValuePicker.java b/ruler-picker/src/main/java/com/kevalpatel2106/rulerpicker/RulerValuePicker.java index bff5430..1bf3988 100644 --- a/ruler-picker/src/main/java/com/kevalpatel2106/rulerpicker/RulerValuePicker.java +++ b/ruler-picker/src/main/java/com/kevalpatel2106/rulerpicker/RulerValuePicker.java @@ -34,6 +34,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; +import android.widget.HorizontalScrollView; import android.widget.LinearLayout; @@ -192,9 +193,15 @@ private void init(@Nullable AttributeSet attributeSet) { if (a.hasValue(R.styleable.RulerValuePicker_min_value) || a.hasValue(R.styleable.RulerValuePicker_max_value)) { - setMinMaxValue(a.getInteger(R.styleable.RulerValuePicker_min_value, 0), + initializeMinMaxValue(a.getInteger(R.styleable.RulerValuePicker_min_value, 0), a.getInteger(R.styleable.RulerValuePicker_max_value, 100)); } + if (a.hasValue(R.styleable.RulerValuePicker_long_indicator_step)){ + int mLongIndicatorStep = a.getInteger(R.styleable.RulerValuePicker_long_indicator_step, 5); + if (mLongIndicatorStep < 1) + mLongIndicatorStep = 5; /*Fallback to default to prevent unexpected behaviour*/ + setLongIndicatorStep(mLongIndicatorStep); + } } finally { a.recycle(); } @@ -309,30 +316,70 @@ private void calculateNotchPath() { * will be selected. */ public void selectValue(final int value) { + //System.out.println("Save RulerValuePicker selectValue="+value+" called"); + this.value = value; + selectValueDelayed(0); + } + + /** + * Because selectValue can be called multiple times and each of them will cause a delayed selection - we rather keep the last value from the + * caller and concentrate on displaying only it not middle random calls. + */ + private int value; + + /** + * More precice method on returning the set value - usefull during rotation, etc. + * @return + */ + public int getValue(){ + return value; + } + /** + * Scroll the ruler to the given value. + * If value isn't reached yet - loop with delay 3 times till we manage to set the value ASAP. + //* @param value Value to select. Value must be between {@link #getMinValue()} and {@link #getMaxValue()}. + * If the value is less than {@link #getMinValue()}, {@link #getMinValue()} will be + * selected.If the value is greater than {@link #getMaxValue()}, {@link #getMaxValue()} + * will be selected. + * @param iteration - initial call should pass 0, the rest are done internally + */ + private void selectValueDelayed(/*final int value, */ final int iteration){ + int valuesToScroll; + if (value < mRulerView.getMinValue()) { + valuesToScroll = 0; + } else if (value > mRulerView.getMaxValue()) { + valuesToScroll = mRulerView.getMaxValue() - mRulerView.getMinValue(); + } else { + valuesToScroll = value - mRulerView.getMinValue(); + } + + mHorizontalScrollView.smoothScrollTo( + (valuesToScroll+1) * mRulerView.getIndicatorIntervalWidth(), 0);//extra 1 for spacing which we artificially added in case the labels may wanna get drawn + + //we have all the condition in postDelay, because smoothScrollTo is asynchronious and will take time to finish mHorizontalScrollView.postDelayed(new Runnable() { @Override public void run() { - int valuesToScroll; - if (value < mRulerView.getMinValue()) { - valuesToScroll = 0; - } else if (value > mRulerView.getMaxValue()) { - valuesToScroll = mRulerView.getMaxValue() - mRulerView.getMinValue(); - } else { - valuesToScroll = value - mRulerView.getMinValue(); + if (getCurrentValue() != value && //for the rest value selection + iteration < 3 || //loop protection with iteration from infinity + mHorizontalScrollView.getScrollX() == 0 //if minimum value is initially selected, but we actually have extra spaces (for the text) in front and back of the ruler, so it can't be 0. + ) { + selectValueDelayed(iteration + 1);//we rise iteration for loop protection } - - mHorizontalScrollView.smoothScrollTo( - valuesToScroll * mRulerView.getIndicatorIntervalWidth(), 0); } - }, 400); + }, 150);//maybe MhorizontalScrollView will be initialized faster than 400ms? } /** + * Not the best method to get current value as selection may be in progress + * but it is a good method to check weather we finally got our value selected + * and it is usefull during ruler initialization while smoothScrollTo will accept values + * but in reality - there will be no changes till it's fully initialized. * @return Get the current selected value. */ public int getCurrentValue() { int absoluteValue = mHorizontalScrollView.getScrollX() / mRulerView.getIndicatorIntervalWidth(); - int value = mRulerView.getMinValue() + absoluteValue; + int value = mRulerView.getMinValue() + absoluteValue - 1;//extra 1 for spacing which we artificially added in case the labels may wanna get drawn if (value > mRulerView.getMaxValue()) { return mRulerView.getMaxValue(); @@ -358,10 +405,21 @@ public void onScrollStopped() { private void makeOffsetCorrection(final int indicatorInterval) { int offsetValue = mHorizontalScrollView.getScrollX() % indicatorInterval; - if (offsetValue < indicatorInterval / 2) { + /* + Uncommenting the code in this function would make left / right boundaries strict on their values. + But eventually i've noticed it's nicer when you're able to scroll around without + these restrictions. + */ + //int maxScrollX = indicatorInterval * getMaxValue(); + if (offsetValue < indicatorInterval / 2 ){//&& + //mHorizontalScrollView.getScrollX() >= indicatorInterval && //this is our left padding of the indicator interval length + //mHorizontalScrollView.getScrollX() <= maxScrollX ){//this is our right edge of the padding mHorizontalScrollView.scrollBy(-offsetValue, 0); } else { - mHorizontalScrollView.scrollBy(indicatorInterval - offsetValue, 0); + //if (mHorizontalScrollView.getScrollX() <= maxScrollX)//if we're not on the right edge of the padding + mHorizontalScrollView.scrollBy(indicatorInterval - offsetValue, 0); + //else + //mHorizontalScrollView.scrollTo( maxScrollX, 0);//back to the right most max value } } @@ -370,6 +428,9 @@ public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SavedState ss = new SavedState(superState); ss.value = getCurrentValue(); + ss.minVal = getMinValue();//may change during runtime + ss.maxVal = getMaxValue();//may change during runtime + //System.out.println("Save RulerValuePicker onSave. "+getValue()+" or *"+getCurrentValue()+"* in ["+ss.minVal+"; "+ss.maxVal+"]"); return ss; } @@ -377,7 +438,10 @@ public Parcelable onSaveInstanceState() { public void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); + //System.out.println("Save RulerValuePicker onRestore I. "+value+" *"+getValue()+" / "+ getCurrentValue()+"* in ["+getMinValue()+"; "+getMaxValue()+"]"); + setMinMaxValue(ss.minVal, ss.maxVal); selectValue(ss.value); + //System.out.println("Save RulerValuePicker onRestore II. "+value+" *"+getValue()+" / "+ getCurrentValue()+"* in ["+getMinValue()+"; "+getMaxValue()+"]"); } //**********************************************************************************// @@ -535,6 +599,26 @@ public void setIndicatorWidth(final int widthPx) { mRulerView.setIndicatorWidth(widthPx); } + /** + * Set a step for a long indictor to be drawn. + * Every step a long indicator will be drawn. + * @param step - Step in decimal + * @see #getLongIndicatorStep() + * @see RulerView#mLongIndicatorStep + */ + public void setLongIndicatorStep(final int step){ + mRulerView.setLongIndicatorStep(step); + } + + /** + * @return Long indicator step + * @see #setLongIndicatorStep(int) + * @see RulerView#mLongIndicatorStep + */ + public int getLongIndicatorStep(){ + return mRulerView.getLongIndicatorStep(); + } + /** * Set the width of the indicator line in the ruler. * @@ -566,6 +650,11 @@ public int getMaxValue() { return mRulerView.getMaxValue(); } + /** + * If min / max values were set outside class + */ + private boolean isMinMaxSet = false; + /** * Set the maximum value to display on the ruler. This will decide the range of values and number * of indicators that ruler will draw. @@ -578,6 +667,22 @@ public int getMaxValue() { * @see #getMaxValue() */ public void setMinMaxValue(final int minValue, final int maxValue) { + //System.out.println("Save RulerValuePicker setMinMax("+minValue+", "+maxValue+");"); + int oldDiff = mRulerView.getMaxValue() - mRulerView.getMinValue(); + int curVal = getCurrentValue(); + mRulerView.setValueRange(minValue, maxValue); + if (isMinMaxSet && oldDiff != maxValue - minValue) {//it means we need to recalculate width + mRulerView.triggerOnMeasure(); + } + invalidate(); + if (curVal >= minValue && curVal <= maxValue) + selectValue(curVal);//we select same old value on change if we can + else + selectValue(minValue);//we select minimum value once we're out of edges + isMinMaxSet = true; + } + + private void initializeMinMaxValue(final int minValue, final int maxValue){ mRulerView.setValueRange(minValue, maxValue); invalidate(); selectValue(minValue); @@ -671,6 +776,8 @@ public SavedState[] newArray(int size) { }; private int value = 0; + private int minVal = 0; + private int maxVal = 0; SavedState(Parcelable superState) { super(superState); @@ -679,12 +786,18 @@ public SavedState[] newArray(int size) { private SavedState(Parcel in) { super(in); value = in.readInt(); + minVal = in.readInt(); + maxVal = in.readInt(); + //System.out.println("SaveState: savedState="+value+" in ["+minVal+"; "+maxVal+"]"); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeInt(value); + out.writeInt(minVal); + out.writeInt(maxVal); + //System.out.println("SaveState: writeToParcel="+value+" in ["+minVal+"; "+maxVal+"]"); } } } diff --git a/ruler-picker/src/main/java/com/kevalpatel2106/rulerpicker/RulerView.java b/ruler-picker/src/main/java/com/kevalpatel2106/rulerpicker/RulerView.java index aecc4f5..ebc9f9a 100644 --- a/ruler-picker/src/main/java/com/kevalpatel2106/rulerpicker/RulerView.java +++ b/ruler-picker/src/main/java/com/kevalpatel2106/rulerpicker/RulerView.java @@ -27,6 +27,7 @@ import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; +import android.widget.LinearLayout; /** * Created by Keval Patel on 28 Mar 2018. @@ -125,6 +126,11 @@ final class RulerView extends View { */ private int mShortIndicatorHeight = 0; + /** + * Allows to customise the step of the long indicator + */ + private int mLongIndicatorStep = 5 /* Default value */; + /** * Integer color of the text, that is displayed on the ruler. * @@ -216,7 +222,7 @@ private void parseAttr(@Nullable AttributeSet attributeSet) { if (a.hasValue(R.styleable.RulerView_indicator_interval)) { mIndicatorInterval = a.getDimensionPixelSize(R.styleable.RulerView_indicator_interval, - 4); + 14); } if (a.hasValue(R.styleable.RulerView_long_height_height_ratio)) { @@ -236,6 +242,11 @@ private void parseAttr(@Nullable AttributeSet attributeSet) { mMaxValue = a.getInteger(R.styleable.RulerView_max_value, 100); } setValueRange(mMinValue, mMaxValue); + if (a.hasValue(R.styleable.RulerValuePicker_long_indicator_step)){ + mLongIndicatorStep = a.getInteger(R.styleable.RulerValuePicker_long_indicator_step, 5); + if (mLongIndicatorStep < 1) + mLongIndicatorStep = 5; /*Fallback to default to prevent unexpected behaviour*/ + } } finally { a.recycle(); } @@ -264,9 +275,9 @@ private void refreshPaint() { @Override protected void onDraw(Canvas canvas) { //Iterate through all value - for (int value = 1; value < mMaxValue - mMinValue; value++) { + for (int value = 1; value <= mMaxValue - mMinValue; value++) { - if (value % 5 == 0) { + if (value % mLongIndicatorStep == 0) { drawLongIndicator(canvas, value); drawValueText(canvas, value); } else { @@ -274,11 +285,21 @@ protected void onDraw(Canvas canvas) { } } - //Draw the first indicator. - drawSmallIndicator(canvas, 0); + if (mLongIndicatorStep != 1) {//because 1 means that we want every step marked with labels + //Draw the first indicator. + drawSmallIndicator(canvas, 0); - //Draw the last indicator. - drawSmallIndicator(canvas, getWidth()); + //Draw the last indicator. + drawSmallIndicator(canvas, getWidth()); + }{ + //Draw the first indicator as long + drawLongIndicator(canvas, 0); + drawValueText(canvas, 0); + + //Draw the last indicator as long + drawLongIndicator(canvas, getWidth()); + drawValueText(canvas, getWidth()); + } super.onDraw(canvas); } @@ -286,13 +307,23 @@ protected void onDraw(Canvas canvas) { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //Measure dimensions mViewHeight = MeasureSpec.getSize(heightMeasureSpec); - int viewWidth = (mMaxValue - mMinValue - 1) * mIndicatorInterval; + + int viewWidth = (mMaxValue - mMinValue + 2) * mIndicatorInterval;//+2 are artificially added to have more space for first and last indicator drawValueText() updateIndicatorHeight(mLongIndicatorHeightRatio, mShortIndicatorHeightRatio); this.setMeasuredDimension(viewWidth, mViewHeight); } + /** + * Method that will trigger onMeasure to be recalled so that the view Width could be recalculated + */ + public void triggerOnMeasure(){ + LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) this.getLayoutParams(); + //params.width = mRulerView.calculateWidth();//would be usefull if onMeasure wouldn't get called + this.setLayoutParams(params); + } + /** * Calculate and update the height of the long and the short indicators based on new ratios. * @@ -314,9 +345,9 @@ private void updateIndicatorHeight(final float longIndicatorHeightRatio, */ private void drawSmallIndicator(@NonNull final Canvas canvas, final int value) { - canvas.drawLine(mIndicatorInterval * value, + canvas.drawLine(mIndicatorInterval * (value+1),//extra 1 for spacing which we artificially added in case the labels may wanna get drawn 0, - mIndicatorInterval * value, + mIndicatorInterval * (value+1),//extra 1 for spacing which we artificially added in case the labels may wanna get drawn mShortIndicatorHeight, mIndicatorPaint); } @@ -329,9 +360,9 @@ private void drawSmallIndicator(@NonNull final Canvas canvas, */ private void drawLongIndicator(@NonNull final Canvas canvas, final int value) { - canvas.drawLine(mIndicatorInterval * value, + canvas.drawLine(mIndicatorInterval * (value+1),//extra 1 for spacing which we artificially added in case the labels may wanna get drawn 0, - mIndicatorInterval * value, + mIndicatorInterval * (value+1),//extra 1 for spacing which we artificially added in case the labels may wanna get drawn mLongIndicatorHeight, mIndicatorPaint); } @@ -346,7 +377,7 @@ private void drawLongIndicator(@NonNull final Canvas canvas, private void drawValueText(@NonNull final Canvas canvas, final int value) { canvas.drawText(String.valueOf(value + mMinValue), - mIndicatorInterval * value, + mIndicatorInterval * (value+1), //extra 1 for spacing which we artificially added in case the labels may wanna get drawn mLongIndicatorHeight + mTextPaint.getTextSize(), mTextPaint); } @@ -432,6 +463,23 @@ void setIndicatorWidth(final int widthPx) { refreshPaint(); } + /** + * Set a step for a long indictor to be drawn. + * Every step a long indicator will be drawn. + * @param step - Step in decimal + */ + void setLongIndicatorStep(final int step){ + mLongIndicatorStep = step; + refreshPaint(); + } + + /** + * @return Long indicator step + * @see #setLongIndicatorStep(int) + */ + int getLongIndicatorStep(){ + return mLongIndicatorStep; + } /** * @return Get the minimum value displayed on the ruler. diff --git a/ruler-picker/src/main/res/values/attr.xml b/ruler-picker/src/main/res/values/attr.xml index 5acd22c..63e1ea4 100644 --- a/ruler-picker/src/main/res/values/attr.xml +++ b/ruler-picker/src/main/res/values/attr.xml @@ -26,6 +26,8 @@ + + @@ -41,6 +43,8 @@ + + @@ -58,6 +62,8 @@ + +