diff --git a/DateTimePickerLibrary/build.gradle b/DateTimePickerLibrary/build.gradle index 5b6150a..778e23d 100644 --- a/DateTimePickerLibrary/build.gradle +++ b/DateTimePickerLibrary/build.gradle @@ -1,12 +1,12 @@ apply plugin: 'com.android.library' android { - compileSdkVersion 23 - buildToolsVersion "23.0.3" + compileSdkVersion 24 + buildToolsVersion "24.0.0" defaultConfig { minSdkVersion 16 - targetSdkVersion 23 + targetSdkVersion 24 } compileOptions { @@ -16,6 +16,7 @@ android { } dependencies { - compile 'com.android.support:support-v4:23.3.0' - compile 'com.android.support:appcompat-v7:23.3.0' + compile 'com.android.support:support-v4:24.0.0@aar' + compile 'com.android.support:appcompat-v7:24.0.0@aar' + compile 'com.android.support:support-annotations:24.0.0' } diff --git a/DateTimePickerLibrary/src/main/java/io/doist/datetimepicker/time/RadialTimePickerView.java b/DateTimePickerLibrary/src/main/java/io/doist/datetimepicker/time/RadialTimePickerView.java index de715ba..b554adb 100644 --- a/DateTimePickerLibrary/src/main/java/io/doist/datetimepicker/time/RadialTimePickerView.java +++ b/DateTimePickerLibrary/src/main/java/io/doist/datetimepicker/time/RadialTimePickerView.java @@ -44,6 +44,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.TextView; +import java.text.DateFormatSymbols; import java.util.ArrayList; import java.util.Calendar; import java.util.List; @@ -181,6 +182,32 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { private boolean mInputEnabled = true; + // NOT a real index for the purpose of what's showing. + // See TimePickerClockDelegate.AMPM_INDEX. + private static final int AMPM_INDEX = 2; + // Also NOT a real index, just used for keyboard mode. + // See TimePickerClockDelegate.ENABLE_PICKER_INDEX. + private static final int ENABLE_PICKER_INDEX = 3; + + private float mAmPmCircleRadiusMultiplier; + + private final Paint mAmPmPaint = new Paint(); + private int mSelectedAlpha; + private int mSelectedColor; + private int mUnselectedColor; + private int mAmPmTextColor; + private int mTouchedColor; + + private String mAmText; + private String mPmText; + + private boolean mDrawValuesReady = false; + private int mAmPmCircleRadius; + private int mAmXCenter; + private int mPmXCenter; + private int mAmPmYCenter; + private int mAmOrPmPressed = -1; + public interface OnValueSelectedListener { void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance); } @@ -338,46 +365,39 @@ public RadialTimePickerView(Context context, AttributeSet attrs, int defStyleAtt mPaintCenter.setAntiAlias(true); mPaintCenter.setTextAlign(Paint.Align.CENTER); + int selectorColor = a.getColor(R.styleable.TimePicker_numbersSelectorColor, + res.getColor(R.color.timepicker_default_selector_color_material)); + mPaintSelector[HOURS][SELECTOR_CIRCLE] = new Paint(); mPaintSelector[HOURS][SELECTOR_CIRCLE].setAntiAlias(true); - mColorSelector[HOURS][SELECTOR_CIRCLE] = a.getColor( - R.styleable.TimePicker_numbersSelectorColor, - R.color.timepicker_default_selector_color_material); + mColorSelector[HOURS][SELECTOR_CIRCLE] = selectorColor; mPaintSelector[HOURS][SELECTOR_DOT] = new Paint(); mPaintSelector[HOURS][SELECTOR_DOT].setAntiAlias(true); - mColorSelector[HOURS][SELECTOR_DOT] = a.getColor( - R.styleable.TimePicker_numbersSelectorColor, - R.color.timepicker_default_selector_color_material); + mColorSelector[HOURS][SELECTOR_DOT] = selectorColor; mPaintSelector[HOURS][SELECTOR_LINE] = new Paint(); mPaintSelector[HOURS][SELECTOR_LINE].setAntiAlias(true); mPaintSelector[HOURS][SELECTOR_LINE].setStrokeWidth(2); - mColorSelector[HOURS][SELECTOR_LINE] = a.getColor( - R.styleable.TimePicker_numbersSelectorColor, - R.color.timepicker_default_selector_color_material); + mColorSelector[HOURS][SELECTOR_LINE] = selectorColor; mPaintSelector[MINUTES][SELECTOR_CIRCLE] = new Paint(); mPaintSelector[MINUTES][SELECTOR_CIRCLE].setAntiAlias(true); - mColorSelector[MINUTES][SELECTOR_CIRCLE] = a.getColor( - R.styleable.TimePicker_numbersSelectorColor, - R.color.timepicker_default_selector_color_material); + mColorSelector[MINUTES][SELECTOR_CIRCLE] = selectorColor; mPaintSelector[MINUTES][SELECTOR_DOT] = new Paint(); mPaintSelector[MINUTES][SELECTOR_DOT].setAntiAlias(true); - mColorSelector[MINUTES][SELECTOR_DOT] = a.getColor( - R.styleable.TimePicker_numbersSelectorColor, - R.color.timepicker_default_selector_color_material); + mColorSelector[MINUTES][SELECTOR_DOT] = selectorColor; mPaintSelector[MINUTES][SELECTOR_LINE] = new Paint(); mPaintSelector[MINUTES][SELECTOR_LINE].setAntiAlias(true); mPaintSelector[MINUTES][SELECTOR_LINE].setStrokeWidth(2); - mColorSelector[MINUTES][SELECTOR_LINE] = a.getColor( - R.styleable.TimePicker_numbersSelectorColor, - R.color.timepicker_default_selector_color_material); + mColorSelector[MINUTES][SELECTOR_LINE] = selectorColor; - mPaintBackground.setColor(a.getColor(R.styleable.TimePicker_numbersBackgroundColor, - res.getColor(R.color.timepicker_default_numbers_background_color_material))); + int bgColor = a.getColor(R.styleable.TimePicker_numbersBackgroundColor, + res.getColor(R.color.timepicker_default_numbers_background_color_material)); + + mPaintBackground.setColor(bgColor); mPaintBackground.setAntiAlias(true); if (DEBUG) { @@ -414,6 +434,22 @@ public RadialTimePickerView(Context context, AttributeSet attrs, int defStyleAtt mSelectionRadiusMultiplier = Float.parseFloat( res.getString(R.string.timepicker_selection_radius_multiplier)); + mAmPmCircleRadiusMultiplier = Float.parseFloat( + res.getString(R.string.timepicker_ampm_circle_radius_multiplier)); + + mAmPmPaint.setTypeface(mTypeface); + mAmPmPaint.setAntiAlias(true); + mAmPmPaint.setTextAlign(Paint.Align.CENTER); + + mSelectedColor = a.getColor(R.styleable.TimePicker_amPmSelectedBackgroundColor, selectorColor); + mTouchedColor = a.getColor(R.styleable.TimePicker_amPmSelectedBackgroundColor, selectorColor); + mUnselectedColor = a.getColor(R.styleable.TimePicker_amPmBackgroundColor, bgColor); + mAmPmTextColor = a.getColor(R.styleable.TimePicker_amPmTextColor, numbersTextColor); + + String[] amPmTexts = new DateFormatSymbols().getAmPmStrings(); + mAmText = amPmTexts[0]; + mPmText = amPmTexts[1]; + a.recycle(); setOnTouchListener(this); @@ -689,6 +725,8 @@ private void initData() { mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE); mAlphaSelector[MINUTES][SELECTOR_LINE].setValue( mShowHours ? ALPHA_TRANSPARENT : ALPHA_SELECTOR); + + mSelectedAlpha = ALPHA_SELECTOR; } @Override @@ -760,6 +798,10 @@ public void onDraw(Canvas canvas) { drawCenter(canvas); + if (!mIs24HourMode) { + drawAmPmCircle(canvas); + } + if (DEBUG) { drawDebug(canvas); } @@ -767,6 +809,68 @@ public void onDraw(Canvas canvas) { canvas.restore(); } + private void drawAmPmCircle(Canvas canvas) { + int viewWidth = getWidth(); + if (viewWidth == 0) { + return; + } + + if (!mDrawValuesReady) { + int layoutXCenter = getWidth() / 2; + int layoutYCenter = getHeight() / 2; + int circleRadius = + (int) (Math.min(layoutXCenter, layoutYCenter) * mCircleRadiusMultiplier[HOURS]); + mAmPmCircleRadius = (int) (circleRadius * mAmPmCircleRadiusMultiplier); + layoutYCenter += mAmPmCircleRadius * 0.75; + int textSize = mAmPmCircleRadius * 3 / 4; + mAmPmPaint.setTextSize(textSize); + + // Line up the vertical center of the AM/PM circles with the bottom of the main circle. + mAmPmYCenter = layoutYCenter - mAmPmCircleRadius + circleRadius; + // Line up the horizontal edges of the AM/PM circles with the horizontal edges + // of the main circle. + mAmXCenter = layoutXCenter - circleRadius + mAmPmCircleRadius / 2; + mPmXCenter = layoutXCenter + circleRadius - mAmPmCircleRadius / 2; + + mDrawValuesReady = true; + } + + // We'll need to draw either a lighter blue (for selection), a darker blue (for touching) + // or white (for not selected). + int amColor = mUnselectedColor; + int amAlpha = Color.alpha(mUnselectedColor); + int pmColor = mUnselectedColor; + int pmAlpha = Color.alpha(mUnselectedColor); + if (mAmOrPm == AM) { + amColor = mSelectedColor; + amAlpha = mSelectedAlpha; + } else if (mAmOrPm == PM) { + pmColor = mSelectedColor; + pmAlpha = mSelectedAlpha; + } + if (mAmOrPmPressed == AM) { + amColor = mTouchedColor; + amAlpha = Color.alpha(mTouchedColor); + } else if (mAmOrPmPressed == PM) { + pmColor = mTouchedColor; + pmAlpha = Color.alpha(mTouchedColor); + } + + // Draw the two circles. + mAmPmPaint.setColor(amColor); + mAmPmPaint.setAlpha(amAlpha); + canvas.drawCircle(mAmXCenter, mAmPmYCenter, mAmPmCircleRadius, mAmPmPaint); + mAmPmPaint.setColor(pmColor); + mAmPmPaint.setAlpha(pmAlpha); + canvas.drawCircle(mPmXCenter, mAmPmYCenter, mAmPmCircleRadius, mAmPmPaint); + + // Draw the AM/PM texts on top. + mAmPmPaint.setColor(mAmPmTextColor); + int textYCenter = mAmPmYCenter - (int) (mAmPmPaint.descent() + mAmPmPaint.ascent()) / 2; + canvas.drawText(mAmText, mAmXCenter, textYCenter, mAmPmPaint); + canvas.drawText(mPmText, mPmXCenter, textYCenter, mAmPmPaint); + } + private void drawCircleBackground(Canvas canvas) { canvas.drawCircle(mXCenter, mYCenter, mCircleRadius[HOURS], mPaintBackground); } @@ -1198,32 +1302,75 @@ private int getDegreesFromXY(float x, float y) { @Override public boolean onTouch(View v, MotionEvent event) { - if (!mInputEnabled) { - return true; - } final int action = event.getActionMasked(); - if (action == MotionEvent.ACTION_MOVE - || action == MotionEvent.ACTION_UP - || action == MotionEvent.ACTION_DOWN) { - boolean forceSelection = false; - boolean autoAdvance = false; + boolean forceSelection = false; + boolean autoAdvance = false; - if (action == MotionEvent.ACTION_DOWN) { + float eventX = event.getX(); + float eventY = event.getY(); + + int isTouchingAmOrPm = !mIs24HourMode ? getIsTouchingAmOrPm(eventX, eventY) : -1; + + switch (action) { + case MotionEvent.ACTION_DOWN: { + if (!mInputEnabled) { + return true; + } // This is a new event stream, reset whether the value changed. mChangedDuringTouch = false; - } else if (action == MotionEvent.ACTION_UP) { - autoAdvance = true; - // If we saw a down/up pair without the value changing, assume - // this is a single-tap selection and force a change. - if (!mChangedDuringTouch) { - forceSelection = true; + if (isTouchingAmOrPm == AM || isTouchingAmOrPm == PM) { + mAmOrPmPressed = isTouchingAmOrPm; + invalidate(); + } else { + mChangedDuringTouch |= handleTouchInput( + event.getX(), event.getY(), forceSelection, autoAdvance); } + break; } + case MotionEvent.ACTION_MOVE: { + if (!mInputEnabled) { + return true; + } + if (isTouchingAmOrPm == AM || isTouchingAmOrPm == PM) { + mAmOrPmPressed = isTouchingAmOrPm; + invalidate(); + } else { + mAmOrPmPressed = -1; + invalidate(); + mChangedDuringTouch |= handleTouchInput( + event.getX(), event.getY(), forceSelection, autoAdvance); + } + break; + } + case MotionEvent.ACTION_UP: { + if (!mInputEnabled) { + mListener.onValueSelected(ENABLE_PICKER_INDEX, 1, false); + return true; + } + if (isTouchingAmOrPm == AM || isTouchingAmOrPm == PM) { + mAmOrPmPressed = -1; + invalidate(); - mChangedDuringTouch |= handleTouchInput( - event.getX(), event.getY(), forceSelection, autoAdvance); + if (isTouchingAmOrPm != mAmOrPm) { + setAmOrPm(isTouchingAmOrPm); + mListener.onValueSelected(AMPM_INDEX, isTouchingAmOrPm, false); + } + } else { + autoAdvance = true; + + // If we saw a down/up pair without the value changing, assume + // this is a single-tap selection and force a change. + if (!mChangedDuringTouch) { + forceSelection = true; + } + mChangedDuringTouch |= handleTouchInput( + event.getX(), event.getY(), forceSelection, autoAdvance); + } + break; + } + default: } return true; @@ -1293,6 +1440,29 @@ public void setInputEnabled(boolean inputEnabled) { invalidate(); } + private int getIsTouchingAmOrPm(float xCoord, float yCoord) { + if (!mDrawValuesReady) { + return -1; + } + + int squaredYDistance = (int) ((yCoord - mAmPmYCenter) * (yCoord - mAmPmYCenter)); + + int distanceToAmCenter = + (int) Math.sqrt((xCoord - mAmXCenter) * (xCoord - mAmXCenter) + squaredYDistance); + if (distanceToAmCenter <= mAmPmCircleRadius) { + return AM; + } + + int distanceToPmCenter = + (int) Math.sqrt((xCoord - mPmXCenter) * (xCoord - mPmXCenter) + squaredYDistance); + if (distanceToPmCenter <= mAmPmCircleRadius) { + return PM; + } + + // Neither was close enough. + return -1; + } + private class RadialPickerTouchHelper extends ExploreByTouchHelper { private final Rect mTempRect = new Rect(); diff --git a/DateTimePickerLibrary/src/main/res/values/strings.xml b/DateTimePickerLibrary/src/main/res/values/strings.xml index 9fcc135..1ee637c 100644 --- a/DateTimePickerLibrary/src/main/res/values/strings.xml +++ b/DateTimePickerLibrary/src/main/res/values/strings.xml @@ -52,4 +52,7 @@ sans-serif + + + 0.22 diff --git a/DateTimePickerSamples/build.gradle b/DateTimePickerSamples/build.gradle index 69e137c..5fa50ac 100644 --- a/DateTimePickerSamples/build.gradle +++ b/DateTimePickerSamples/build.gradle @@ -1,15 +1,16 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 23 - buildToolsVersion "22.0.3" + compileSdkVersion 24 + buildToolsVersion "24.0.0" defaultConfig { applicationId "io.doist.datetimepicker.sample" minSdkVersion 16 - targetSdkVersion 23 + targetSdkVersion 24 versionCode 1 versionName "1.0" + vectorDrawables.useSupportLibrary = true } buildTypes { release { @@ -23,5 +24,7 @@ dependencies { compile project(':DateTimePickerLibrary') compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:appcompat-v7:22.0.0' + compile 'com.android.support:appcompat-v7:24.0.0@aar' + compile 'com.android.support:support-vector-drawable:24.0.0@aar' + compile 'com.android.support:animated-vector-drawable:24.0.0@aar' } diff --git a/build.gradle b/build.gradle index d3ff69d..e220f0b 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.1.0' + classpath 'com.android.tools.build:gradle:2.1.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0c71e76..4c74aab 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-all.zip