layout.xml :
<LinearLayout
android:id="@+id/search_editor_tab_max_hoa_fees_row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/search_editor_tab_row_margin"
android:background="?android:attr/selectableItemBackground"
android:orientation="vertical"
android:visibility="visible">
<LinearLayout
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/search_editor_tab_row_margin"
android:orientation="horizontal">
<TextView
android:id="@+id/max_hoa_fees_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:text="@string/max_hoa_fees"
android:textColor="@color/filter_header"
android:textStyle="bold"
android:textSize="@dimen/filter_text_size" />
<com.androidlib.util.InfoButton
android:layout_toRightOf="@id/hide_pending_contingent_listings_title"
android:padding="@dimen/base_line_grid_size_standard"
android:id="@+id/max_hoa_fees_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_hoa_info" />
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/search_editor_tab_row_margin"
android:layout_marginTop="@dimen/search_editor_tab_row_margin"
android:layout_marginLeft="@dimen/search_editor_tab_row_margin">
<com.searcheditor.view.FancyWidthExtendableRadioView
android:layout_marginRight="@dimen/three_d_tour_shadow_size"
android:id="@+id/search_editor_tab_max_hoa_fees_options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:ballColor="@color/primary"
app:selectedTextColor="@color/white"
app:circleRadius="@dimen/search_editor_fancy_radio_circle_radius"
app:textColor="@color/filter_description"
app:textSize="@dimen/default_text_size"
android:overScrollMode="always"
app:values="@array/fancy_radio_max_hoa_fees_options"
android:layout_centerVertical="true"/>
<ImageView
android:layout_marginRight="@dimen/three_d_tour_shadow_size"
android:id="@+id/hoa_fee_arrow_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_arrow_down_black"
android:layout_centerVertical="true"
android:layout_alignRight="@+id/search_editor_tab_max_hoa_fees_options"
android:layout_alignEnd="@+id/search_editor_tab_max_hoa_fees_options" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/max_hoa_fees_editor_row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/search_editor_tab_row_margin"
android:layout_marginRight="@dimen/search_editor_tab_row_margin"
android:layout_marginBottom="@dimen/search_editor_tab_row_margin"
android:background="?android:attr/selectableItemBackground"
android:visibility="gone">
<TextView
android:id="@+id/editor_max_hoa_fees_dollar_sign"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:text="$"
android:textColor="@color/filter_header"
android:textSize="@dimen/default_text_size"/>
<!-- HOA input number should allow max 6 digits with comma separator -->
<EditText
android:id="@+id/enter_max_hoa_fees_editor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/editor_max_hoa_fees_dollar_sign"
android:hint="@string/max_hoa_fees_label"
android:inputType="number"
android:maxLength="6"
android:paddingLeft="4dp"
android:imeOptions="actionDone"
android:textColorHint="@color/filter_description"
android:textSize="@dimen/default_text_size"/>
</RelativeLayout>
<RelativeLayout
android:id="@+id/search_editor_tab_show_homes_hoa_not_known_row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:layout_marginBottom="@dimen/search_editor_tab_row_margin"
android:visibility="gone">
<TextView
android:id="@+id/show_homes_hoa_not_known_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="@dimen/search_editor_tab_row_margin"
android:text="@string/homes_hoa_not_known"
android:textColor="@color/filter_description" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/show_homes_hoa_not_known_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_marginRight="@dimen/search_editor_tab_row_margin"
android:layout_marginLeft="4dp"
android:checked="false"
android:theme="@style/SwitchButton" />
</RelativeLayout>
<View
android:id="@+id/search_editor_tab_max_hoa_fees_separator"
style="@style/separator_line" />
</LinearLayout>
FancyWidthExtendableRadioView.java:
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
/** * This is a fancy radio view wherein we can select individual items */public class FancyWidthExtendableRadioView extends FancyRadioView {
private int mDrawnTextLeftPosition[] , mDrawnTextRightPosition[];
private ImageView mArrowImage;
private int mArrowMargin = 14;
private final int mMarginDivider = 6;
public FancyWidthExtendableRadioView(Context context, AttributeSet attrs) {
super(context, attrs);
mDrawnTextLeftPosition = new int[mValues.length];
mDrawnTextRightPosition = new int[mValues.length];
}
/** * Handles drawing of the circle / range highlight. * * @param canvas Canvas */ @Override protected void initSelectionDrawing(Canvas canvas) {
if (isLargeText()) {
// For even number of selections / touches (other than 'Any' option), // the last selection would be set. // Draw the round cornered rectangle to show the range. drawRoundCorneredRectangle(canvas);
} else {
// Draw circle when there is no range selected super.drawCircle(canvas);
}
}
@Override protected void handleTextColor(Canvas canvas, int pos, float textPosition) {
super.handleTextColor(canvas, pos, textPosition);
}
@Override protected void onDraw(Canvas canvas) {
// Handle drawing of the selection initSelectionDrawing(canvas);
// Draw text and with right color for (int i = 0; i < mValues.length; i++) {
drawTextWithColor(canvas, i);
}
}
private void drawTextWithColor(Canvas canvas, int i) {
// Draw text String value = mValues[i].toString();
float textPosition;
//From Second element we are moving items to left side because Third element has large text and that should fit inside screen. //so we are making space adjustments here, We are not moving first element to left side because it should be align leftside with its all above circles... if(i > 0) {
textPosition = (getButtonCenterX(i)/ mMarginDivider) + mTextWidths[i];
} else {
textPosition = getButtonCenterX(i) - mTextWidths[i] / 2;
}
mDrawnTextLeftPosition[i] = (int)(textPosition);
mDrawnTextRightPosition[i] = (int)(textPosition+mTextWidths[i]);
drawTextInCanvas(canvas, value, textPosition, mTextPaint , i);
// Handle text color. handleTextColor(canvas, i, textPosition);
}
private void drawTextInCanvas(Canvas canvas, CharSequence mValue, float textPosition, Paint p, int index) {
canvas.drawText(
String.valueOf(mValue), textPosition, getHeight() / 2.0f - (p.descent() + p.ascent()) / 2, p);
//Arrow image need to draw beside MaxHOA label, we have to get the last label right position and place beside accordingly... if (index == 2) {
mArrowImage.setX(mDrawnTextRightPosition[2] + mArrowMargin);
}
}
@Override public boolean onTouch(View v, MotionEvent event) {
if (mAnimator != null) return true;
if (event.getAction() == MotionEvent.ACTION_UP) {
// Find the closest position that has been clicked. int closest = 0;
for (int i = 0; i < mDrawnTextLeftPosition.length; i++) {
if ((mDrawnTextLeftPosition[i] <= event.getX()) && (event.getX() <= mDrawnTextRightPosition[i])) {
closest = i;
// Handle animation of the selection and set the last selection handleAnimationAndUpdateSelection(closest);
// Update values invokeValueChangedListener();
invalidate();
break;
}
}
}
return true;
}
/** * @param newSelection closest index value that user intended to touch */ @Override protected void handleAnimationAndUpdateSelection(int newSelection) {
if (mRuntimeDrawValues == null) return;
// Increment the tap count. mRuntimeDrawValues.tapCount++;
animateSelectionChange(newSelection, newSelection);
mRuntimeDrawValues.tapCount = 0;
setDrawValues(newSelection, newSelection);
}
private void setDrawValues(int last, int current) {
mRuntimeDrawValues.lastSelection = last;
mRuntimeDrawValues.selection = current;
}
/** * Updates the values when new radio button values are selected */ @Override protected void invokeValueChangedListener() {
if (mOnValueChangedListener != null) {
// Updates the values based on if its a range selection or not. if (isLargeText()) {
mOnValueChangedListener.valueUpdated(this,
// Since user can select a 'lastSelection' such that either its less than or // more than the currently selected value. That's why Math.min(). mRuntimeDrawValues.selection,
mRuntimeDrawValues.selection,
String.valueOf(mValues[mRuntimeDrawValues.selection]));
} else {
super.invokeValueChangedListener();
}
}
}
/** * This method draws a round cornered rectangle selection like: * _______ * (_______) * <p>
* This is to display when a range is selected for the radio buttons. * * @param canvas Canvas */ private void drawRoundCorneredRectangle(Canvas canvas) {
// Create a rectangle with the 4 corner . // Set an offset value in pixels to draw rounded rectangle on canvas float left=0 , right=0;
int margin = 25;
//Third element has big length text, We need to handle that.. if(mRuntimeDrawValues.selection > 0) {
left = ((getButtonCenterX(mRuntimeDrawValues.selection)/ mMarginDivider) + mTextWidths[mRuntimeDrawValues.selection]) - margin ;
//Here last HOAFee label in canvas has arrow icon on its right and the circle need to cover that ..So right side we need to have more margin for circle if(mRuntimeDrawValues.selection == 2)
right = left + mTextWidths[mRuntimeDrawValues.selection] + (3 * margin + mArrowMargin) ;
else right = left + mTextWidths[mRuntimeDrawValues.selection] + (2 * margin) ;
}
float top = ((getHeight() / 2f) + getCircleRadius()) - 4 ;
float bottom = (-1 * (getHeight() / 2f) + getCircleRadius()) + 4 ;
RectF rect = new RectF(
left, // left top, // top right, // right bottom // bottom );
// Finally, draw the rounded corners rectangle object on the canvas canvas.drawRoundRect(
rect, // rect getCircleRadius(), // rx getCircleRadius(), // ry mBallPaint // Paint );
}
/** * Checks whether the current selection is a range or just single value. * See onTouch in super class, to see when lastSelection is set. * * @return bool */ protected boolean isLargeText() {
return mRuntimeDrawValues != null // If there is only one selection and last selection is -1 (default), then no range. && mRuntimeDrawValues.lastSelection != NO_SELECTION // If last selection is for some reason same as current, then its not a range && mRuntimeDrawValues.selection != 0;
}
/** * Sets the current selections Text in a specified index */ public void setSelectionText(String strSelection) {
// Need to update the Third label in the canvas mValues[2] = strSelection ;
// Refresh the canvas with updated text this.invalidate();
}
public void drawSelectionInCanvas(int selectionItem){
// Handle animation of the selection and set the last selection handleAnimationAndUpdateSelection(selectionItem);
// Refresh the canvas with updated text this.invalidate();
}
public void setImage(ImageView mArrowImage) {
this.mArrowImage = mArrowImage;
}
}
FancyRadioView.java :
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
public class FancyRadioView extends View implements View.OnTouchListener {
static final int NO_SELECTION = -1;
//Paints and Colors Paint mTextPaint;
Paint mTextLargePaint;
Paint mTextSmallPaint;
Paint mSelectedTextPaint;
Paint mSelectedTextLargePaint;
Paint mSelectedTextSmallPaint;
Paint mBallPaint;
int mTextColor = Color.BLACK;
int mSelectedColor = Color.WHITE;
int mBallColor = Color.RED;
CharSequence[] mValues; //The options, provided by custom:mValues float[] mTextWidths; //Measurements for the text
float mDensityMultiplier; //Pixel to DP Scale float mScaledDensityMultiplier;
float mTextSizeDP = 12;
float mTextLargeSizeDP = 14;
float mTextSmallSizeDP = 11;
float mPaddingDP = 48;
float mCircleRadiusDP = 23;
float mStrokeWidth = 1f;
boolean mStroked = false;
ValueAnimator mAnimator; //Null when no animation, and the animation when there is
RuntimeDrawValues mRuntimeDrawValues = new RuntimeDrawValues();
OnValueChangedListener mOnValueChangedListener;
//We serialize this up to know the current state of the view public static class RuntimeDrawValues implements Serializable {
// Denotes current selection / touch. int selection;
// Denotes previous selection. // In case of range selection, this is set to the last value that was selected in the range . int lastSelection = NO_SELECTION;
int tapCount;
}
public FancyRadioView(Context context, AttributeSet attrs) {
super(context, attrs);
calculateDensity();
initAttributes(attrs);
initPaint();
measureOptions();
setOnTouchListener(this);
}
// Use a stroked rather than a filled circle or range. public void setStroked(boolean isStroked) {
mStroked = isStroked;
if (mBallPaint != null) {
mBallPaint.setStyle(Paint.Style.STROKE);
mBallPaint.setStrokeWidth(getStrokeWidth());
}
// If the ball is stroked, make the selected color to be the same as the stroke color. // If the A/B test declares the stroke mode to be a winner, set this // value in the XML app:selectedTextColor if (isStroked) {
mSelectedColor = mBallColor;
mSelectedTextPaint.setColor(mSelectedColor);
mSelectedTextLargePaint.setColor(mSelectedColor);
mSelectedTextSmallPaint.setColor(mSelectedColor);
}
}
float getStrokeWidth() {
return mStrokeWidth * mDensityMultiplier;
}
private void calculateDensity() {
DisplayMetrics dm = new DisplayMetrics();
if (isInEditMode()) {
//This uses reflection to get the display metrics from the BridgeContext which isn't //available to our code Context ctx = getContext();
Class c = ctx.getClass();
try {
dm = (DisplayMetrics) c.getMethod("getMetrics").invoke(ctx);
mDensityMultiplier = dm.density;
mScaledDensityMultiplier = dm.scaledDensity;
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
} else {
dm = Resources.getSystem().getDisplayMetrics();
mDensityMultiplier = dm.density;
mScaledDensityMultiplier = dm.scaledDensity;
}
}
//Requires density lookup private void initPaint() {
mTextPaint = new Paint();
mTextPaint.setTextSize(mDensityMultiplier * mTextSizeDP);
mTextPaint.setColor(mTextColor);
mTextPaint.setAntiAlias(true);
mTextLargePaint = new Paint();
mTextLargePaint.setTextSize(mDensityMultiplier * mTextLargeSizeDP);
mTextLargePaint.setColor(mTextColor);
mTextLargePaint.setAntiAlias(true);
mTextSmallPaint = new Paint();
mTextSmallPaint.setTextSize(mDensityMultiplier * mTextSmallSizeDP);
mTextSmallPaint.setColor(mTextColor);
mTextSmallPaint.setAntiAlias(true);
mSelectedTextPaint = new Paint();
mSelectedTextPaint.setTextSize(mDensityMultiplier * mTextSizeDP);
mSelectedTextPaint.setColor(mSelectedColor);
mSelectedTextPaint.setAntiAlias(true);
mSelectedTextLargePaint = new Paint();
mSelectedTextLargePaint.setTextSize(mDensityMultiplier * mTextLargeSizeDP);
mSelectedTextLargePaint.setColor(mSelectedColor);
mSelectedTextLargePaint.setAntiAlias(true);
mSelectedTextSmallPaint = new Paint();
mSelectedTextSmallPaint.setTextSize(mDensityMultiplier * mTextSmallSizeDP);
mSelectedTextSmallPaint.setColor(mSelectedColor);
mSelectedTextSmallPaint.setAntiAlias(true);
mBallPaint = new Paint();
mBallPaint.setColor(mBallColor);
mBallPaint.setAntiAlias(true);
}
// Measure the PX width of the options // Requires paint to be init'd already private void measureOptions() {
mTextWidths = new float[mValues.length];
for (int i = 0; i < mValues.length; i++) {
mTextWidths[i] = mTextPaint.measureText(mValues[i], 0, mValues[i].length());
}
}
//Apply the custom attributes to the view. // Requires density to be looked up already private void initAttributes(AttributeSet attrs) {
TypedArray a = getContext().getTheme().obtainStyledAttributes(
attrs,
R.styleable.fancy_radio_view_attribs,
0, 0);
//Mandatory values array (Buggy in editor, only returns the first element?) mValues = a.getTextArray(R.styleable.fancy_radio_view_attribs_values);
Preconditions.checkNotNull(mValues,
"Need to provide mValues attribute point to a" +
"string array resource on your fancy radio view");
//Size Attributes mCircleRadiusDP = a.getDimensionPixelSize(R.styleable.fancy_radio_view_attribs_circleRadius,
(int) (mCircleRadiusDP * mDensityMultiplier)) / mDensityMultiplier;
mPaddingDP = a.getDimensionPixelSize(R.styleable.fancy_radio_view_attribs_padding,
(int) (mPaddingDP * mDensityMultiplier)) / mDensityMultiplier;
mTextSizeDP = a.getDimensionPixelSize(R.styleable.fancy_radio_view_attribs_textSize,
(int) (mTextSizeDP * mDensityMultiplier)) / mDensityMultiplier;
//Color Attribs mBallColor = a.getColor(R.styleable.fancy_radio_view_attribs_ballColor, mBallColor);
mTextColor = a.getColor(R.styleable.fancy_radio_view_attribs_textColor, mTextColor);
mSelectedColor = a.getColor(R.styleable.fancy_radio_view_attribs_selectedTextColor, mSelectedColor);
a.recycle();
}
public void setOnValueChangedListener(OnValueChangedListener mOnValueChangedListener) {
this.mOnValueChangedListener = mOnValueChangedListener;
}
/** * Sets the current selections index value * * @param selection Current Selection */ public void setSelection(int selection) {
mRuntimeDrawValues.selection = selection;
mRuntimeDrawValues.lastSelection = NO_SELECTION;
if (selection == 0) mRuntimeDrawValues.tapCount = 0;
postInvalidate();
}
/** * Sets the current selections index value, and makes a valueUpdated callback. Useful for autotest * * @param selection Current Selection */ public void setSelectionWithCallback(int selection) {
setSelection(selection);
invokeValueChangedListener();
}
/** * Gets the current selections index value * * @return the currently selected index */ public int getSelection() {
return mRuntimeDrawValues.selection;
}
/** * Gets the current selections Text as a String * * @return the currently selected text */ public String getSelectionText() {
return String.valueOf(mValues[mRuntimeDrawValues.selection]);
}
@Override protected void onDraw(Canvas canvas) {
// Handle drawing of the selection initSelectionDrawing(canvas);
// Draw text and with right color for (int i = 0; i < mValues.length; i++) {
drawOptionText(canvas, i);
}
}
/** * Handles drawing of the circle / range highlight. * * @param canvas Canvas */ protected void initSelectionDrawing(Canvas canvas) {
drawCircle(canvas);
}
protected void drawCircle(Canvas canvas) {
if (mAnimator == null) {
drawCircle(canvas, getButtonCenterX(mRuntimeDrawValues.selection));
} else {
Float val = (Float) mAnimator.getAnimatedValue();
drawCircle(canvas, val);
}
}
private void drawOptionText(Canvas canvas, int i) {
// Draw text String value = mValues[i].toString();
if (value.contains("\n")) {
String[] texts = value.split("\n");
for (int m = 0; m < texts.length; m++) {
Paint paint = m == 0 ? mTextLargePaint : mTextSmallPaint;
Paint selectedPaint = m == 0 ? mSelectedTextLargePaint : mSelectedTextSmallPaint;
//We calculate the center and then offset by half the width to the left (center on button) float textPosition = getButtonCenterX(i) - paint.measureText(texts[m], 0, texts[m].length()) / 2;
//padding top and bottom float unpaddedHeight = (float) (getHeight() * 0.70);
float textPositionY = (getHeight() - unpaddedHeight) / 2 + unpaddedHeight * (m + 1) / (texts.length + 1);
drawText(canvas, texts[m], textPosition, textPositionY, paint);
// Handle text color. handleTextColor(canvas, i, texts[m], textPosition, textPositionY, selectedPaint);
}
} else {
//We calculate the center and then offset by half the width to the left (center on button) float textPosition = getButtonCenterX(i) - mTextWidths[i] / 2;
drawText(canvas, value, textPosition, mTextPaint);
// Handle text color. handleTextColor(canvas, i, textPosition);
}
}
/** * Handle text color for the current position in question for which text is being drawn. * * @param canvas canvas * @param i index of the value in the array which needs to be drawn * @param textPosition exact position where the text needs to be draw */ protected void handleTextColor(Canvas canvas, int i, float textPosition) {
handleTextColor(canvas, i, mValues[i], textPosition, getHeight() / 2.0f, mSelectedTextPaint);
}
protected void handleTextColor(Canvas canvas, int i, CharSequence text, float textPosition, float textPositionY, Paint selectedPaint) {
// If current selection, then make it light. if (mRuntimeDrawValues.selection == i) {
handleTextFadeIn(canvas, text, textPosition, textPositionY, selectedPaint);
} else if (mRuntimeDrawValues.lastSelection == i) {
handleTextFadeOut(canvas, text, textPosition, textPositionY, selectedPaint);
}
}
private void drawText(Canvas canvas, CharSequence mValue, float textPosition, Paint p) {
canvas.drawText(
String.valueOf(mValue), textPosition, getHeight() / 2.0f - (p.descent() + p.ascent()) / 2, p);
}
private void drawText(Canvas canvas, CharSequence mValue, float textPosition, float textPositionY, Paint p) {
canvas.drawText(
String.valueOf(mValue), textPosition, textPositionY - (p.descent() + p.ascent()) / 2, p);
}
private void handleTextFadeOut(Canvas canvas, CharSequence text, float textPosition, float textPositionY, Paint selectedPaint) {
if (mAnimator != null) {
float f = mAnimator.getAnimatedFraction();
selectedPaint.setAlpha((int) ((1f - f) * 255));
drawText(canvas, text, textPosition, textPositionY, selectedPaint);
}
}
/** * Draws the text fading to white * * @param canvas canvas * @param i index of the value in the array which needs to be drawn * @param textPosition exact position where the text needs to be draw */ protected void handleTextFadeIn(Canvas canvas, int i, float textPosition) {
handleTextFadeIn(canvas, mValues[i], textPosition, getHeight() / 2.0f, mSelectedTextPaint);
}
protected void handleTextFadeIn(Canvas canvas, CharSequence text, float textPosition, float textPositionY, Paint selectedPaint) {
//If we are drawing the selection field we want to fade with the animation float f = mAnimator != null ? mAnimator.getAnimatedFraction() : 1;
selectedPaint.setAlpha((int) (f * 255));
drawText(canvas, text, textPosition, textPositionY, selectedPaint);
}
/** * We measure this view, this is called by the system. * <p>
* we call setMeasuredDimensions at the end that fits the component as appropriate, inflates * to the view, or uses specific sizes */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int desiredWidth = (int) (getButtonCenterX(mValues.length - 1) + getCircleRadius());
int desiredHeight = (int) (getCircleRadius() * 2);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
width = Math.max(desiredWidth, widthSize);
} else {
width = desiredWidth;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
height = Math.max(desiredHeight, heightSize);
} else {
height = desiredHeight;
}
//MUST CALL THIS setMeasuredDimension(width, height);
}
private void drawCircle(Canvas canvas, float center_pos) {
if (mStroked) {
canvas.drawCircle(center_pos, getHeight() / 2f,
getCircleRadius() - getStrokeWidth(), mBallPaint);
} else {
canvas.drawCircle(center_pos, getHeight() / 2f,
getCircleRadius(), mBallPaint);
}
}
protected float getCircleRadius() {
return mCircleRadiusDP * mScaledDensityMultiplier;
}
protected float getButtonCenterX(int selection) {
/* * We want the left edge of the leftmost circle to touch the end edge of the view, * and the same for the right side */ float circleRadius = getCircleRadius();
int separationWidth = (int) ((getWidth() - (circleRadius * 2)) /
((float) (mValues.length - 1)));
return separationWidth * selection + circleRadius;
}
@Override public boolean onTouch(View v, MotionEvent event) {
if (mAnimator != null) return true;
if (event.getAction() == MotionEvent.ACTION_UP) {
// Find the closest position that has been clicked. int closest = 0;
int min_dist = 1000000;
for (int i = 0; i < mValues.length; i++) {
int distance = (int) Math.abs(getButtonCenterX(i) - event.getX());
if (distance < min_dist) {
min_dist = distance;
closest = i;
}
}
// Handle animation of the selection and set the last selection handleAnimationAndUpdateSelection(closest);
// Update values invokeValueChangedListener();
invalidate();
}
return true;
}
/** * Handles animating the new change based on touch. * * @param closest closes index value that user intended to touch */ protected void handleAnimationAndUpdateSelection(int closest) {
animateSelectionChange(mRuntimeDrawValues.selection, closest);
// Set the last selection to the current one mRuntimeDrawValues.lastSelection = mRuntimeDrawValues.selection;
// Set current selection mRuntimeDrawValues.selection = closest;
}
/** * Perform animation * * @param old from position * @param newSelection to position */ protected void animateSelectionChange(final int old, final int newSelection) {
mAnimator = new ValueAnimator();
mAnimator.setDuration(300);
mAnimator.setFloatValues(getButtonCenterX(old), getButtonCenterX(newSelection));
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override public void onAnimationUpdate(ValueAnimator animation) {
invalidate();
}
});
mAnimator.addListener(new Animator.AnimatorListener() {
@Override public void onAnimationStart(Animator animation) {
}
@Override public void onAnimationEnd(Animator animation) {
mAnimator = null;
}
@Override public void onAnimationCancel(Animator animation) {
}
@Override public void onAnimationRepeat(Animator animation) {
}
});
mAnimator.start();
}
/** * Send value selected updates to model /server. */ protected void invokeValueChangedListener() {
if (mOnValueChangedListener != null) {
mOnValueChangedListener.valueUpdated(this,
mRuntimeDrawValues.selection,
null,
String.valueOf(mValues[mRuntimeDrawValues.selection]));
}
}
public interface OnValueChangedListener {
void valueUpdated(FancyRadioView radioView, int startRange, Integer endRange, String selectionText);
}
@Override protected Parcelable onSaveInstanceState() {
Bundle b = new Bundle();
b.putParcelable("parent", super.onSaveInstanceState());
b.putSerializable("state", mRuntimeDrawValues);
return b;
}
@Override protected void onRestoreInstanceState(Parcelable state) {
Bundle b = (Bundle) state;
mRuntimeDrawValues = (RuntimeDrawValues) b.getSerializable("state");
super.onRestoreInstanceState(b.getParcelable("parent"));
invalidate();
}
}
MainActivity.java :
private FancyWidthExtendableRadioView mMaxHoaFees;
private static String HOA_MODE_MAX = "HOA_MODE_MAX";
private EditText mMaxHoaFeesEditor;
mMaxHoaFees = viewGroup.findViewById(R.id.search_editor_tab_max_hoa_fees_options);
FancyRadioView.OnValueChangedListener mValueChangedListener = new FancyValueChangedListener();
mMaxHoaFees.setOnValueChangedListener(mValueChangedListener);
private class FancyValueChangedListener implements FancyRadioView.OnValueChangedListener {
@Override public void valueUpdated(FancyRadioView radioView, int startRange, Integer endRange, String selectionText) {
//AB test for reorder filter action HashMap<String, String> params = new HashMap<>();
if (radioView == mMaxHoaFees) {
if (startRange == 0) {
//HOA option "Any" resetHOAToAny(false);
} else if (startRange == 1) {
//HOA option "No HOA" hideSoftKeyboard();
mMaxHoaFeesEditorRow.setVisibility(View.GONE);
mHoaFeeArrowIcon.setBackgroundResource(R.drawable.ic_arrow_down_black);
Settings.setHoaFilterMode(getContext() , HOA_MODE_NONE);
resetHoaFees();
mSearchEditorSelections.noHoaFee = true;
mShowHomesHoaNotKnownRow.setVisibility(View.GONE);
if (mMaxHoaFeesEditor.getText().toString().length() > 0) {
// Condition to display price on top of HOAFee label when user entered price and click on Any/NOHoa label mMaxHoaFeesOnOffFlag = true;
mMaxHoaFees.setSelectionText("Max $" + mMaxHoaFeesEditor.getText().toString() + "/month");
}
} else if (startRange == 2) {
//HOA option "Enter Max Hoa fees if (showHomeWithoutHoaInfoCheckBox.isChecked()) {
mSearchEditorSelections.hoaFeeOptional = hoaMaxFee;
mSearchEditorSelections.hoaMaxFee = null;
} else {
mSearchEditorSelections.hoaFeeOptional = null;
mSearchEditorSelections.hoaMaxFee = hoaMaxFee;
}
Settings.setHoaFilterMode(getContext() , HOA_MODE_MAX);
mSearchEditorSelections.noHoaFee = null;
mMaxHoaFeesEditorRow.setVisibility(View.VISIBLE);
mShowHomesHoaNotKnownRow.setVisibility(View.VISIBLE);
mMaxHoaFeesEditor.requestFocus();
InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY);
//while user click on MaxHoa button then we need to update arrow up/down.. mHoaFeeArrowIcon.setBackgroundResource(R.drawable.ic_arrow_up_red);
maxHoaFeesInputHandle();
}
}
}
}
Output :