├── .gitignore
├── .idea
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── gradle.xml
├── markdown-navigator.xml
├── markdown-navigator
│ └── profiles_settings.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── androidchils
│ │ └── odometerlibrary
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ ├── Lato-Black.ttf
│ │ ├── Lato-BlackItalic.ttf
│ │ ├── Lato-Bold.ttf
│ │ ├── Lato-BoldItalic.ttf
│ │ ├── Lato-Hairline.ttf
│ │ ├── Lato-HairlineItalic.ttf
│ │ ├── Lato-Italic.ttf
│ │ ├── Lato-Light.ttf
│ │ ├── Lato-LightItalic.ttf
│ │ ├── Lato-Regular.ttf
│ │ ├── Screenshot 2.png
│ │ ├── Screenshot 3.png
│ │ └── screenshot 1.png
│ ├── java
│ │ └── com
│ │ │ └── androidchils
│ │ │ └── odometerlibrary
│ │ │ └── MainActivity.java
│ └── res
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── androidchils
│ └── odometerlibrary
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── odometer
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── androidchils
│ │ └── odometer
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ ├── Lato-Black.ttf
│ │ ├── Lato-BlackItalic.ttf
│ │ ├── Lato-Bold.ttf
│ │ ├── Lato-BoldItalic.ttf
│ │ ├── Lato-Hairline.ttf
│ │ ├── Lato-HairlineItalic.ttf
│ │ ├── Lato-Italic.ttf
│ │ ├── Lato-Light.ttf
│ │ ├── Lato-LightItalic.ttf
│ │ └── Lato-Regular.ttf
│ ├── java
│ │ └── com
│ │ │ └── androidchils
│ │ │ └── odometer
│ │ │ ├── NumberPicker.java
│ │ │ └── Odometer.java
│ └── res
│ │ ├── drawable
│ │ ├── gradient.xml
│ │ └── np_numberpicker_selection_divider.9.png
│ │ ├── layout
│ │ ├── number_picker.xml
│ │ └── number_picker_selector_wheel.xml
│ │ └── values
│ │ ├── attrs.xml
│ │ ├── color.xml
│ │ └── strings.xml
│ └── test
│ └── java
│ └── com
│ └── androidchils
│ └── odometer
│ └── ExampleUnitTest.java
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
17 |
22 |
27 |
4 | Example is available in app module.
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
1069 | * Note: If you have provided alternative values for the values this 1070 | * formatter is never invoked. 1071 | *
1072 | * 1073 | * @param formatter The formatter object. If formatter isnull
,
1074 | * {@link String#valueOf(int)} will be used.
1075 | *@see #setDisplayedValues(String[])
1076 | */
1077 | public void setFormatter(NumberPicker.Formatter formatter) {
1078 | if (formatter == mFormatter) {
1079 | return;
1080 | }
1081 | mFormatter = formatter;
1082 | initializeSelectorWheelIndices();
1083 | updateInputTextView();
1084 | }
1085 |
1086 | /**
1087 | * Set the current value for the number picker.
1088 | *
1089 | * If the argument is less than the {@link NumberPicker#getMinValue()} and
1090 | * {@link NumberPicker#getWrapSelectorWheel()} is false
the
1091 | * current value is set to the {@link NumberPicker#getMinValue()} value.
1092 | *
1094 | * If the argument is less than the {@link NumberPicker#getMinValue()} and
1095 | * {@link NumberPicker#getWrapSelectorWheel()} is true
the
1096 | * current value is set to the {@link NumberPicker#getMaxValue()} value.
1097 | *
1099 | * If the argument is less than the {@link NumberPicker#getMaxValue()} and
1100 | * {@link NumberPicker#getWrapSelectorWheel()} is false
the
1101 | * current value is set to the {@link NumberPicker#getMaxValue()} value.
1102 | *
1104 | * If the argument is less than the {@link NumberPicker#getMaxValue()} and
1105 | * {@link NumberPicker#getWrapSelectorWheel()} is true
the
1106 | * current value is set to the {@link NumberPicker#getMinValue()} value.
1107 | *
1178 | * By default if the range (max - min) is more than the number of items shown 1179 | * on the selector wheel the selector wheel wrapping is enabled. 1180 | *
1181 | *1182 | * Note: If the number of items, i.e. the range ( 1183 | * {@link #getMaxValue()} - {@link #getMinValue()}) is less than 1184 | * the number of items shown on the selector wheel, the selector wheel will 1185 | * not wrap. Hence, in such a case calling this method is a NOP. 1186 | *
1187 | * 1188 | * @param wrapSelectorWheel Whether to wrap. 1189 | */ 1190 | public void setWrapSelectorWheel(boolean wrapSelectorWheel) { 1191 | final boolean wrappingAllowed = (mMaxValue - mMinValue) >= mSelectorIndices.length; 1192 | if ((!wrapSelectorWheel || wrappingAllowed) && wrapSelectorWheel != mWrapSelectorWheel) { 1193 | mWrapSelectorWheel = wrapSelectorWheel; 1194 | } 1195 | } 1196 | 1197 | /** 1198 | * Sets the speed at which the numbers be incremented and decremented when 1199 | * the up and down buttons are long pressed respectively. 1200 | *1201 | * The default value is 300 ms. 1202 | *
1203 | * 1204 | * @param intervalMillis The speed (in milliseconds) at which the numbers 1205 | * will be incremented and decremented. 1206 | */ 1207 | public void setOnLongPressUpdateInterval(long intervalMillis) { 1208 | mLongPressUpdateInterval = intervalMillis; 1209 | } 1210 | 1211 | /** 1212 | * Returns the value of the picker. 1213 | * 1214 | * @return The value. 1215 | */ 1216 | public int getValue() { 1217 | return mValue; 1218 | } 1219 | 1220 | /** 1221 | * Returns the min value of the picker. 1222 | * 1223 | * @return The min value 1224 | */ 1225 | public int getMinValue() { 1226 | return mMinValue; 1227 | } 1228 | 1229 | /** 1230 | * Sets the min value of the picker. 1231 | * 1232 | * @param minValue The min value inclusive. 1233 | * 1234 | * Note: The length of the displayed values array 1235 | * set via {@link #setDisplayedValues(String[])} must be equal to the 1236 | * range of selectable numbers which is equal to 1237 | * {@link #getMaxValue()} - {@link #getMinValue()} + 1. 1238 | */ 1239 | public void setMinValue(int minValue) { 1240 | // if (minValue < 0) { 1241 | // throw new IllegalArgumentException("minValue must be >= 0"); 1242 | // } 1243 | mMinValue = minValue; 1244 | if (mMinValue > mValue) { 1245 | mValue = mMinValue; 1246 | } 1247 | boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length; 1248 | setWrapSelectorWheel(wrapSelectorWheel); 1249 | initializeSelectorWheelIndices(); 1250 | updateInputTextView(); 1251 | tryComputeMaxWidth(); 1252 | invalidate(); 1253 | } 1254 | 1255 | /** 1256 | * Returns the max value of the picker. 1257 | * 1258 | * @return The max value. 1259 | */ 1260 | public int getMaxValue() { 1261 | return mMaxValue; 1262 | } 1263 | 1264 | /** 1265 | * Sets the max value of the picker. 1266 | * 1267 | * @param maxValue The max value inclusive. 1268 | * 1269 | * Note: The length of the displayed values array 1270 | * set via {@link #setDisplayedValues(String[])} must be equal to the 1271 | * range of selectable numbers which is equal to 1272 | * {@link #getMaxValue()} - {@link #getMinValue()} + 1. 1273 | */ 1274 | public void setMaxValue(int maxValue) { 1275 | if (maxValue < 0) { 1276 | throw new IllegalArgumentException("maxValue must be >= 0"); 1277 | } 1278 | mMaxValue = maxValue; 1279 | if (mMaxValue < mValue) { 1280 | mValue = mMaxValue; 1281 | } 1282 | 1283 | boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length; 1284 | setWrapSelectorWheel(wrapSelectorWheel); 1285 | initializeSelectorWheelIndices(); 1286 | updateInputTextView(); 1287 | tryComputeMaxWidth(); 1288 | invalidate(); 1289 | } 1290 | 1291 | /** 1292 | * Gets the values to be displayed instead of string values. 1293 | * 1294 | * @return The displayed values. 1295 | */ 1296 | public String[] getDisplayedValues() { 1297 | return mDisplayedValues; 1298 | } 1299 | 1300 | /** 1301 | * Sets the values to be displayed. 1302 | * 1303 | * @param displayedValues The displayed values. 1304 | * 1305 | * Note: The length of the displayed values array 1306 | * must be equal to the range of selectable numbers which is equal to 1307 | * {@link #getMaxValue()} - {@link #getMinValue()} + 1. 1308 | */ 1309 | public void setDisplayedValues(String[] displayedValues) { 1310 | if (mDisplayedValues == displayedValues) { 1311 | return; 1312 | } 1313 | mDisplayedValues = displayedValues; 1314 | if (mDisplayedValues != null) { 1315 | // Allow text entry rather than strictly numeric entry. 1316 | mSelectedText.setRawInputType(InputType.TYPE_CLASS_TEXT 1317 | | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); 1318 | } else { 1319 | mSelectedText.setRawInputType(InputType.TYPE_CLASS_NUMBER); 1320 | } 1321 | updateInputTextView(); 1322 | initializeSelectorWheelIndices(); 1323 | tryComputeMaxWidth(); 1324 | } 1325 | 1326 | @Override 1327 | protected float getTopFadingEdgeStrength() { 1328 | return isHorizontalMode() ? 0: FADING_EDGE_STRENGTH; 1329 | } 1330 | 1331 | @Override 1332 | protected float getBottomFadingEdgeStrength() { 1333 | return isHorizontalMode() ? 0: FADING_EDGE_STRENGTH; 1334 | } 1335 | 1336 | @Override 1337 | protected float getLeftFadingEdgeStrength() { 1338 | return isHorizontalMode() ? FADING_EDGE_STRENGTH : 0; 1339 | } 1340 | 1341 | @Override 1342 | protected float getRightFadingEdgeStrength() { 1343 | return isHorizontalMode() ? FADING_EDGE_STRENGTH : 0; 1344 | } 1345 | 1346 | @Override 1347 | protected void onDetachedFromWindow() { 1348 | super.onDetachedFromWindow(); 1349 | removeAllCallbacks(); 1350 | } 1351 | 1352 | @Override 1353 | protected void onDraw(Canvas canvas) { 1354 | float x, y; 1355 | if (isHorizontalMode()) { 1356 | x = mCurrentScrollOffset; 1357 | y = mSelectedText.getBaseline() + mSelectedText.getTop(); 1358 | } else { 1359 | x = (getRight() - getLeft()) / 2; 1360 | y = mCurrentScrollOffset; 1361 | } 1362 | 1363 | // draw the selector wheel 1364 | int[] selectorIndices = mSelectorIndices; 1365 | for (int i = 0; i < selectorIndices.length; i++) { 1366 | if (i == mWheelMiddleItemIndex) { 1367 | mSelectorWheelPaint.setColor(mSelectedTextColor); 1368 | } else { 1369 | mSelectorWheelPaint.setColor(mTextColor); 1370 | } 1371 | 1372 | int selectorIndex = selectorIndices[i]; 1373 | String scrollSelectorValue = mSelectorIndexToStringCache.get(selectorIndex); 1374 | // Do not draw the middle item if input is visible since the input 1375 | // is shown only if the wheel is static and it covers the middle 1376 | // item. Otherwise, if the user starts editing the text via the 1377 | // IME he may see a dimmed version of the old value intermixed 1378 | // with the new one. 1379 | if (i != mWheelMiddleItemIndex || mSelectedText.getVisibility() != VISIBLE) { 1380 | canvas.drawText(scrollSelectorValue, x, y, mSelectorWheelPaint); 1381 | } 1382 | 1383 | if (isHorizontalMode()) { 1384 | x += mSelectorElementSize; 1385 | } else { 1386 | y += mSelectorElementSize; 1387 | } 1388 | } 1389 | 1390 | // draw the selection dividers 1391 | if (mSelectionDivider != null) { 1392 | if (isHorizontalMode()) { 1393 | // draw the left divider 1394 | int leftOfLeftDivider = mLeftOfSelectionDividerLeft; 1395 | int rightOfLeftDivider = leftOfLeftDivider + mSelectionDividerThickness; 1396 | mSelectionDivider.setBounds(leftOfLeftDivider, 0, rightOfLeftDivider, getBottom()); 1397 | mSelectionDivider.draw(canvas); 1398 | 1399 | // draw the right divider 1400 | int rightOfRightDivider = mRightOfSelectionDividerRight; 1401 | int leftOfRightDivider = rightOfRightDivider - mSelectionDividerThickness; 1402 | mSelectionDivider.setBounds(leftOfRightDivider, 0, rightOfRightDivider, getBottom()); 1403 | mSelectionDivider.draw(canvas); 1404 | } else { 1405 | // draw the top divider 1406 | int topOfTopDivider = mTopSelectionDividerTop; 1407 | int bottomOfTopDivider = topOfTopDivider + mSelectionDividerThickness; 1408 | mSelectionDivider.setBounds(0, topOfTopDivider, getRight(), bottomOfTopDivider); 1409 | mSelectionDivider.draw(canvas); 1410 | 1411 | // draw the bottom divider 1412 | int bottomOfBottomDivider = mBottomSelectionDividerBottom; 1413 | int topOfBottomDivider = bottomOfBottomDivider - mSelectionDividerThickness; 1414 | mSelectionDivider.setBounds(0, topOfBottomDivider, getRight(), bottomOfBottomDivider); 1415 | mSelectionDivider.draw(canvas); 1416 | } 1417 | } 1418 | } 1419 | 1420 | @Override 1421 | public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 1422 | super.onInitializeAccessibilityEvent(event); 1423 | event.setClassName(NumberPicker.class.getName()); 1424 | event.setScrollable(true); 1425 | final int scroll = (mMinValue + mValue) * mSelectorElementSize; 1426 | final int maxScroll = (mMaxValue - mMinValue) * mSelectorElementSize; 1427 | if (isHorizontalMode()) { 1428 | event.setScrollX(scroll); 1429 | event.setMaxScrollX(maxScroll); 1430 | } else { 1431 | event.setScrollY(scroll); 1432 | event.setMaxScrollY(maxScroll); 1433 | } 1434 | } 1435 | 1436 | /** 1437 | * Makes a measure spec that tries greedily to use the max value. 1438 | * 1439 | * @param measureSpec The measure spec. 1440 | * @param maxSize The max value for the size. 1441 | * @return A measure spec greedily imposing the max size. 1442 | */ 1443 | private int makeMeasureSpec(int measureSpec, int maxSize) { 1444 | if (maxSize == SIZE_UNSPECIFIED) { 1445 | return measureSpec; 1446 | } 1447 | final int size = MeasureSpec.getSize(measureSpec); 1448 | final int mode = MeasureSpec.getMode(measureSpec); 1449 | switch (mode) { 1450 | case MeasureSpec.EXACTLY: 1451 | return measureSpec; 1452 | case MeasureSpec.AT_MOST: 1453 | return MeasureSpec.makeMeasureSpec(Math.min(size, maxSize), MeasureSpec.EXACTLY); 1454 | case MeasureSpec.UNSPECIFIED: 1455 | return MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.EXACTLY); 1456 | default: 1457 | throw new IllegalArgumentException("Unknown measure mode: " + mode); 1458 | } 1459 | } 1460 | 1461 | /** 1462 | * Utility to reconcile a desired size and state, with constraints imposed 1463 | * by a MeasureSpec. Tries to respect the min size, unless a different size 1464 | * is imposed by the constraints. 1465 | * 1466 | * @param minSize The minimal desired size. 1467 | * @param measuredSize The currently measured size. 1468 | * @param measureSpec The current measure spec. 1469 | * @return The resolved size and state. 1470 | */ 1471 | private int resolveSizeAndStateRespectingMinSize(int minSize, int measuredSize, int measureSpec) { 1472 | if (minSize != SIZE_UNSPECIFIED) { 1473 | final int desiredWidth = Math.max(minSize, measuredSize); 1474 | return resolveSizeAndState(desiredWidth, measureSpec, 0); 1475 | } else { 1476 | return measuredSize; 1477 | } 1478 | } 1479 | 1480 | /** 1481 | * Utility to reconcile a desired size and state, with constraints imposed 1482 | * by a MeasureSpec. Will take the desired size, unless a different size 1483 | * is imposed by the constraints. The returned value is a compound integer, 1484 | * with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and 1485 | * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the resulting 1486 | * size is smaller than the size the view wants to be. 1487 | * 1488 | * @param size How big the view wants to be 1489 | * @param measureSpec Constraints imposed by the parent 1490 | * @return Size information bit mask as defined by 1491 | * {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}. 1492 | */ 1493 | public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { 1494 | int result = size; 1495 | int specMode = MeasureSpec.getMode(measureSpec); 1496 | int specSize = MeasureSpec.getSize(measureSpec); 1497 | switch (specMode) { 1498 | case MeasureSpec.UNSPECIFIED: 1499 | result = size; 1500 | break; 1501 | case MeasureSpec.AT_MOST: 1502 | if (specSize < size) { 1503 | result = specSize | MEASURED_STATE_TOO_SMALL; 1504 | } else { 1505 | result = size; 1506 | } 1507 | break; 1508 | case MeasureSpec.EXACTLY: 1509 | result = specSize; 1510 | break; 1511 | } 1512 | return result | (childMeasuredState&MEASURED_STATE_MASK); 1513 | } 1514 | 1515 | /** 1516 | * Resets the selector indices and clear the cached string representation of 1517 | * these indices. 1518 | */ 1519 | private void initializeSelectorWheelIndices() { 1520 | mSelectorIndexToStringCache.clear(); 1521 | int[] selectorIndices = mSelectorIndices; 1522 | int current = getValue(); 1523 | for (int i = 0; i < mSelectorIndices.length; i++) { 1524 | int selectorIndex = current + (i - mWheelMiddleItemIndex); 1525 | if (mWrapSelectorWheel) { 1526 | selectorIndex = getWrappedSelectorIndex(selectorIndex); 1527 | } 1528 | selectorIndices[i] = selectorIndex; 1529 | ensureCachedScrollSelectorValue(selectorIndices[i]); 1530 | } 1531 | } 1532 | 1533 | /** 1534 | * Sets the current value of this NumberPicker. 1535 | * 1536 | * @param current The new value of the NumberPicker. 1537 | * @param notifyChange Whether to notify if the current value changed. 1538 | */ 1539 | private void setValueInternal(int current, boolean notifyChange) { 1540 | if (mValue == current) { 1541 | return; 1542 | } 1543 | // Wrap around the values if we go past the start or end 1544 | if (mWrapSelectorWheel) { 1545 | current = getWrappedSelectorIndex(current); 1546 | } else { 1547 | current = Math.max(current, mMinValue); 1548 | current = Math.min(current, mMaxValue); 1549 | } 1550 | int previous = mValue; 1551 | mValue = current; 1552 | updateInputTextView(); 1553 | if (notifyChange) { 1554 | notifyChange(previous, current); 1555 | } 1556 | initializeSelectorWheelIndices(); 1557 | invalidate(); 1558 | } 1559 | 1560 | /** 1561 | * Changes the current value by one which is increment or 1562 | * decrement based on the passes argument. 1563 | * decrement the current value. 1564 | * 1565 | * @param increment True to increment, false to decrement. 1566 | */ 1567 | private void changeValueByOne(boolean increment) { 1568 | mSelectedText.setVisibility(View.INVISIBLE); 1569 | if (!moveToFinalScrollerPosition(mFlingScroller)) { 1570 | moveToFinalScrollerPosition(mAdjustScroller); 1571 | } 1572 | if (isHorizontalMode()) { 1573 | mPreviousScrollerX = 0; 1574 | if (increment) { 1575 | mFlingScroller.startScroll(0, 0, -mSelectorElementSize, 0, SNAP_SCROLL_DURATION); 1576 | } else { 1577 | mFlingScroller.startScroll(0, 0, mSelectorElementSize, 0, SNAP_SCROLL_DURATION); 1578 | } 1579 | } else { 1580 | mPreviousScrollerY = 0; 1581 | if (increment) { 1582 | mFlingScroller.startScroll(0, 0, 0, -mSelectorElementSize, SNAP_SCROLL_DURATION); 1583 | } else { 1584 | mFlingScroller.startScroll(0, 0, 0, mSelectorElementSize, SNAP_SCROLL_DURATION); 1585 | } 1586 | } 1587 | invalidate(); 1588 | } 1589 | 1590 | private void initializeSelectorWheel() { 1591 | initializeSelectorWheelIndices(); 1592 | int[] selectorIndices = mSelectorIndices; 1593 | int totalTextSize = selectorIndices.length * (int) mTextSize; 1594 | float textGapCount = selectorIndices.length; 1595 | int editTextTextPosition; 1596 | if (isHorizontalMode()) { 1597 | float totalTextGapWidth = (getRight() - getLeft()) - totalTextSize; 1598 | mSelectorTextGapWidth = (int) (totalTextGapWidth / textGapCount + 0.5f); 1599 | mSelectorElementSize = (int) mTextSize + mSelectorTextGapWidth; 1600 | // Ensure that the middle item is positioned the same as the text in mSelectedText 1601 | editTextTextPosition = mSelectedText.getRight() / 2; 1602 | } else { 1603 | float totalTextGapHeight = (getBottom() - getTop()) - totalTextSize; 1604 | mSelectorTextGapHeight = (int) (totalTextGapHeight / textGapCount + 0.5f); 1605 | mSelectorElementSize = (int) mTextSize + mSelectorTextGapHeight; 1606 | // Ensure that the middle item is positioned the same as the text in mSelectedText 1607 | editTextTextPosition = mSelectedText.getBaseline() + mSelectedText.getTop(); 1608 | } 1609 | mInitialScrollOffset = editTextTextPosition - (mSelectorElementSize * mWheelMiddleItemIndex); 1610 | mCurrentScrollOffset = mInitialScrollOffset; 1611 | updateInputTextView(); 1612 | } 1613 | 1614 | private void initializeFadingEdges() { 1615 | if (isHorizontalMode()) { 1616 | setHorizontalFadingEdgeEnabled(true); 1617 | setFadingEdgeLength((getRight() - getLeft() - (int) mTextSize) / 2); 1618 | } else { 1619 | setVerticalFadingEdgeEnabled(true); 1620 | setFadingEdgeLength((getBottom() - getTop() - (int) mTextSize) / 2); 1621 | } 1622 | } 1623 | 1624 | /** 1625 | * Callback invoked upon completion of a givenscroller
.
1626 | */
1627 | private void onScrollerFinished(Scroller scroller) {
1628 | if (scroller == mFlingScroller) {
1629 | if (!ensureScrollWheelAdjusted()) {
1630 | updateInputTextView();
1631 | }
1632 | onScrollStateChange(NumberPicker.OnScrollListener.SCROLL_STATE_IDLE);
1633 | } else if (mScrollState != NumberPicker.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
1634 | updateInputTextView();
1635 | }
1636 | }
1637 |
1638 | /**
1639 | * Handles transition to a given scrollState
1640 | */
1641 | private void onScrollStateChange(int scrollState) {
1642 | if (mScrollState == scrollState) {
1643 | return;
1644 | }
1645 | mScrollState = scrollState;
1646 | if (mOnScrollListener != null) {
1647 | mOnScrollListener.onScrollStateChange(this, scrollState);
1648 | }
1649 | }
1650 |
1651 | /**
1652 | * Flings the selector with the given velocity
.
1653 | */
1654 | private void fling(int velocity) {
1655 | if (isHorizontalMode()) {
1656 | mPreviousScrollerX = 0;
1657 | if (velocity > 0) {
1658 | mFlingScroller.fling(0, 0, velocity, 0, 0, Integer.MAX_VALUE, 0, 0);
1659 | } else {
1660 | mFlingScroller.fling(Integer.MAX_VALUE, 0, velocity, 0, 0, Integer.MAX_VALUE, 0, 0);
1661 | }
1662 | } else {
1663 | mPreviousScrollerY = 0;
1664 | if (velocity > 0) {
1665 | mFlingScroller.fling(0, 0, 0, velocity, 0, 0, 0, Integer.MAX_VALUE);
1666 | } else {
1667 | mFlingScroller.fling(0, Integer.MAX_VALUE, 0, velocity, 0, 0, 0, Integer.MAX_VALUE);
1668 | }
1669 | }
1670 |
1671 | invalidate();
1672 | }
1673 |
1674 | /**
1675 | * @return The wrapped index selectorIndex
value.
1676 | */
1677 | private int getWrappedSelectorIndex(int selectorIndex) {
1678 | if (selectorIndex > mMaxValue) {
1679 | return mMinValue + (selectorIndex - mMaxValue) % (mMaxValue - mMinValue) - 1;
1680 | } else if (selectorIndex < mMinValue) {
1681 | return mMaxValue - (mMinValue - selectorIndex) % (mMaxValue - mMinValue) + 1;
1682 | }
1683 | return selectorIndex;
1684 | }
1685 |
1686 | /**
1687 | * Increments the selectorIndices
whose string representations
1688 | * will be displayed in the selector.
1689 | */
1690 | private void incrementSelectorIndices(int[] selectorIndices) {
1691 | for (int i = 0; i < selectorIndices.length - 1; i++) {
1692 | selectorIndices[i] = selectorIndices[i + 1];
1693 | }
1694 | int nextScrollSelectorIndex = selectorIndices[selectorIndices.length - 2] + 1;
1695 | if (mWrapSelectorWheel && nextScrollSelectorIndex > mMaxValue) {
1696 | nextScrollSelectorIndex = mMinValue;
1697 | }
1698 | selectorIndices[selectorIndices.length - 1] = nextScrollSelectorIndex;
1699 | ensureCachedScrollSelectorValue(nextScrollSelectorIndex);
1700 | }
1701 |
1702 | /**
1703 | * Decrements the selectorIndices
whose string representations
1704 | * will be displayed in the selector.
1705 | */
1706 | private void decrementSelectorIndices(int[] selectorIndices) {
1707 | for (int i = selectorIndices.length - 1; i > 0; i--) {
1708 | selectorIndices[i] = selectorIndices[i - 1];
1709 | }
1710 | int nextScrollSelectorIndex = selectorIndices[1] - 1;
1711 | if (mWrapSelectorWheel && nextScrollSelectorIndex < mMinValue) {
1712 | nextScrollSelectorIndex = mMaxValue;
1713 | }
1714 | selectorIndices[0] = nextScrollSelectorIndex;
1715 | ensureCachedScrollSelectorValue(nextScrollSelectorIndex);
1716 | }
1717 |
1718 | /**
1719 | * Ensures we have a cached string representation of the given
1720 | * selectorIndex
to avoid multiple instantiations of the same string.
1721 | */
1722 | private void ensureCachedScrollSelectorValue(int selectorIndex) {
1723 | SparseArrayvalue
.
1817 | */
1818 | private int getSelectedPos(String value) {
1819 | if (mDisplayedValues == null) {
1820 | try {
1821 | return Integer.parseInt(value);
1822 | } catch (NumberFormatException e) {
1823 | // Ignore as if it's not a number we don't care
1824 | }
1825 | } else {
1826 | for (int i = 0; i < mDisplayedValues.length; i++) {
1827 | // Don't force the user to type in jan when ja will do
1828 | value = value.toLowerCase();
1829 | if (mDisplayedValues[i].toLowerCase().startsWith(value)) {
1830 | return mMinValue + i;
1831 | }
1832 | }
1833 |
1834 | /*
1835 | * The user might have typed in a number into the month field i.e.
1836 | * 10 instead of OCT so support that too.
1837 | */
1838 | try {
1839 | return Integer.parseInt(value);
1840 | } catch (NumberFormatException e) {
1841 | // Ignore as if it's not a number we don't care
1842 | }
1843 | }
1844 | return mMinValue;
1845 | }
1846 |
1847 | /**
1848 | * Posts an {@link NumberPicker.SetSelectionCommand} from the given selectionStart
1849 | *
to selectionEnd
.
1850 | */
1851 | private void postSetSelectionCommand(int selectionStart, int selectionEnd) {
1852 | if (mSetSelectionCommand == null) {
1853 | mSetSelectionCommand = new SetSelectionCommand();
1854 | } else {
1855 | removeCallbacks(mSetSelectionCommand);
1856 | }
1857 | mSetSelectionCommand.mSelectionStart = selectionStart;
1858 | mSetSelectionCommand.mSelectionEnd = selectionEnd;
1859 | post(mSetSelectionCommand);
1860 | }
1861 |
1862 | /**
1863 | * The numbers accepted by the input text's {@link LayoutInflater.Filter}
1864 | */
1865 | private static final char[] DIGIT_CHARACTERS = new char[] {
1866 | // Latin digits are the common case
1867 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1868 | // Arabic-Indic
1869 | '\u0660', '\u0661', '\u0662', '\u0663', '\u0664',
1870 | '\u0665', '\u0666', '\u0667', '\u0668', '\u0669',
1871 | // Extended Arabic-Indic
1872 | '\u06f0', '\u06f1', '\u06f2', '\u06f3', '\u06f4',
1873 | '\u06f5', '\u06f6', '\u06f7', '\u06f8', '\u06f9',
1874 | // Negative
1875 | '-'
1876 | };
1877 |
1878 | /**
1879 | * Filter for accepting only valid indices or prefixes of the string
1880 | * representation of valid indices.
1881 | */
1882 | class InputTextFilter extends NumberKeyListener {
1883 |
1884 | // XXX This doesn't allow for range limits when controlled by a soft input method!
1885 | public int getInputType() {
1886 | return InputType.TYPE_CLASS_TEXT;
1887 | }
1888 |
1889 | @Override
1890 | protected char[] getAcceptedChars() {
1891 | return DIGIT_CHARACTERS;
1892 | }
1893 |
1894 | @Override
1895 | public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
1896 | if (mDisplayedValues == null) {
1897 | CharSequence filtered = super.filter(source, start, end, dest, dstart, dend);
1898 | if (filtered == null) {
1899 | filtered = source.subSequence(start, end);
1900 | }
1901 |
1902 | String result = String.valueOf(dest.subSequence(0, dstart)) + filtered
1903 | + dest.subSequence(dend, dest.length());
1904 |
1905 | if ("".equals(result)) {
1906 | return result;
1907 | }
1908 | int val = getSelectedPos(result);
1909 |
1910 | /*
1911 | * Ensure the user can't type in a value greater than the max
1912 | * allowed. We have to allow less than min as the user might
1913 | * want to delete some numbers and then type a new number.
1914 | */
1915 | if (val > mMaxValue) {
1916 | return "";
1917 | } else {
1918 | return filtered;
1919 | }
1920 | } else {
1921 | CharSequence filtered = String.valueOf(source.subSequence(start, end));
1922 | if (TextUtils.isEmpty(filtered)) {
1923 | return "";
1924 | }
1925 | String result = String.valueOf(dest.subSequence(0, dstart)) + filtered
1926 | + dest.subSequence(dend, dest.length());
1927 | String str = String.valueOf(result).toLowerCase();
1928 | for (String val : mDisplayedValues) {
1929 | String valLowerCase = val.toLowerCase();
1930 | if (valLowerCase.startsWith(str)) {
1931 | postSetSelectionCommand(result.length(), val.length());
1932 | return val.subSequence(dstart, val.length());
1933 | }
1934 | }
1935 | return "";
1936 | }
1937 | }
1938 | }
1939 |
1940 | /**
1941 | * Ensures that the scroll wheel is adjusted i.e. there is no offset and the
1942 | * middle element is in the middle of the widget.
1943 | *
1944 | * @return Whether an adjustment has been made.
1945 | */
1946 | private boolean ensureScrollWheelAdjusted() {
1947 | // adjust to the closest value
1948 | int delta = mInitialScrollOffset - mCurrentScrollOffset;
1949 | if (delta != 0) {
1950 | if (Math.abs(delta) > mSelectorElementSize / 2) {
1951 | delta += (delta > 0) ? -mSelectorElementSize : mSelectorElementSize;
1952 | }
1953 | if (isHorizontalMode()) {
1954 | mPreviousScrollerX = 0;
1955 | mAdjustScroller.startScroll(0, 0, delta, 0, SELECTOR_ADJUSTMENT_DURATION_MILLIS);
1956 | } else {
1957 | mPreviousScrollerY = 0;
1958 | mAdjustScroller.startScroll(0, 0, 0, delta, SELECTOR_ADJUSTMENT_DURATION_MILLIS);
1959 | }
1960 | invalidate();
1961 | return true;
1962 | }
1963 | return false;
1964 | }
1965 |
1966 | /**
1967 | * Command for setting the input text selection.
1968 | */
1969 | class SetSelectionCommand implements Runnable {
1970 | private int mSelectionStart;
1971 |
1972 | private int mSelectionEnd;
1973 |
1974 | public void run() {
1975 | mSelectedText.setSelection(mSelectionStart, mSelectionEnd);
1976 | }
1977 | }
1978 |
1979 | /**
1980 | * Command for changing the current value from a long press by one.
1981 | */
1982 | class ChangeCurrentByOneFromLongPressCommand implements Runnable {
1983 | private boolean mIncrement;
1984 |
1985 | private void setStep(boolean increment) {
1986 | mIncrement = increment;
1987 | }
1988 |
1989 | @Override
1990 | public void run() {
1991 | changeValueByOne(mIncrement);
1992 | postDelayed(this, mLongPressUpdateInterval);
1993 | }
1994 | }
1995 |
1996 | private String formatNumberWithLocale(int value) {
1997 | return String.format(Locale.getDefault(), "%d", value);
1998 | }
1999 |
2000 | private void setWidthAndHeight() {
2001 | if (isHorizontalMode()) {
2002 | mMinHeight = SIZE_UNSPECIFIED;
2003 | mMaxHeight = (int) dpToPx(DEFAULT_MIN_WIDTH);
2004 | mMinWidth = (int) dpToPx(DEFAULT_MAX_HEIGHT);
2005 | mMaxWidth = SIZE_UNSPECIFIED;
2006 | } else {
2007 | mMinHeight = SIZE_UNSPECIFIED;
2008 | mMaxHeight = (int) dpToPx(DEFAULT_MAX_HEIGHT);
2009 | mMinWidth = (int) dpToPx(DEFAULT_MIN_WIDTH);
2010 | mMaxWidth = SIZE_UNSPECIFIED;
2011 | }
2012 | }
2013 |
2014 | public void setDividerColor(@ColorInt int color) {
2015 | mSelectionDividerColor = color;
2016 | mSelectionDivider = new ColorDrawable(color);
2017 | }
2018 |
2019 | public void setDividerColorResource(@ColorRes int colorId) {
2020 | setDividerColor(ContextCompat.getColor(mContext, colorId));
2021 | }
2022 |
2023 | public void setDividerDistance(int distance) {
2024 | mSelectionDividersDistance = (int) dpToPx(distance);
2025 | }
2026 |
2027 | public void setDividerThickness(int thickness) {
2028 | mSelectionDividerThickness = (int) dpToPx(thickness);
2029 | }
2030 |
2031 | public void setOrientation(@NumberPicker.Orientation int orientation) {
2032 | mOrientation = orientation;
2033 | setWidthAndHeight();
2034 | }
2035 |
2036 | public void setWheelItemCount(final int count) {
2037 | mWheelItemCount = count;
2038 | mWheelMiddleItemIndex = mWheelItemCount / 2;
2039 | mSelectorIndices = new int[mWheelItemCount];
2040 | }
2041 |
2042 | public void setFormatter(final String formatter) {
2043 | if (TextUtils.isEmpty(formatter)) {
2044 | return;
2045 | }
2046 |
2047 | setFormatter(stringToFormatter(formatter));
2048 | }
2049 |
2050 | public void setFormatter(@StringRes int stringId) {
2051 | setFormatter(getResources().getString(stringId));
2052 | }
2053 |
2054 | public void setSelectedTextColor(@ColorInt int color) {
2055 | mSelectedTextColor = color;
2056 | mSelectedText.setTextColor(mSelectedTextColor);
2057 | }
2058 |
2059 | public void setSelectedTextColorResource(@ColorRes int colorId) {
2060 | setSelectedTextColor(ContextCompat.getColor(mContext, colorId));
2061 | }
2062 |
2063 | public void setTextColor(@ColorInt int color) {
2064 | mTextColor = color;
2065 | mSelectorWheelPaint.setColor(mTextColor);
2066 | }
2067 |
2068 | public void setTextColorResource(@ColorRes int colorId) {
2069 | setTextColor(ContextCompat.getColor(mContext, colorId));
2070 | }
2071 |
2072 | public void setTextSize(float textSize) {
2073 | mTextSize = textSize;
2074 | mSelectedText.setTextSize(pxToSp(mTextSize));
2075 | mSelectorWheelPaint.setTextSize(mTextSize);
2076 | }
2077 |
2078 | public void setTextSize(@DimenRes int dimenId) {
2079 | setTextSize(getResources().getDimension(dimenId));
2080 | }
2081 |
2082 | public void setTypeface(Typeface typeface) {
2083 | mTypeface = typeface;
2084 | if (mTypeface != null) {
2085 | mSelectedText.setTypeface(mTypeface);
2086 | mSelectorWheelPaint.setTypeface(mTypeface);
2087 | } else {
2088 | mSelectedText.setTypeface(Typeface.MONOSPACE);
2089 | mSelectorWheelPaint.setTypeface(Typeface.MONOSPACE);
2090 | }
2091 | }
2092 |
2093 | public void setTypeface(String string, int style) {
2094 | if (TextUtils.isEmpty(string)) {
2095 | return;
2096 | }
2097 | setTypeface(Typeface.create(string, style));
2098 | }
2099 |
2100 | public void setTypeface(String string) {
2101 | setTypeface(string, Typeface.NORMAL);
2102 | }
2103 |
2104 | public void setTypeface(@StringRes int stringId, int style) {
2105 | setTypeface(getResources().getString(stringId), style);
2106 | }
2107 |
2108 | public void setTypeface(@StringRes int stringId) {
2109 | setTypeface(stringId, Typeface.NORMAL);
2110 | }
2111 |
2112 | private NumberPicker.Formatter stringToFormatter(final String formatter) {
2113 | if (TextUtils.isEmpty(formatter)) {
2114 | return null;
2115 | }
2116 |
2117 | return new NumberPicker.Formatter() {
2118 | @Override
2119 | public String format(int i) {
2120 | return String.format(Locale.getDefault(), formatter, i);
2121 | }
2122 | };
2123 | }
2124 |
2125 | private float dpToPx(float dp) {
2126 | return dp * getResources().getDisplayMetrics().density;
2127 | }
2128 |
2129 | private float pxToDp(float px) {
2130 | return px / getResources().getDisplayMetrics().density;
2131 | }
2132 |
2133 | private float spToPx(float sp) {
2134 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
2135 | }
2136 |
2137 | private float pxToSp(float px) {
2138 | return px / getResources().getDisplayMetrics().scaledDensity;
2139 | }
2140 |
2141 | public boolean isHorizontalMode() {
2142 | return mOrientation == HORIZONTAL;
2143 | }
2144 |
2145 | public int getDividerColor() {
2146 | return mSelectionDividerColor;
2147 | }
2148 |
2149 | public float getDividerDistance() {
2150 | return pxToDp(mSelectionDividersDistance);
2151 | }
2152 |
2153 | public float getDividerThickness() {
2154 | return pxToDp(mSelectionDividerThickness);
2155 | }
2156 |
2157 | public int getOrientation() {
2158 | return mOrientation;
2159 | }
2160 |
2161 | public int getWheelItemCount() {
2162 | return mWheelItemCount;
2163 | }
2164 |
2165 | public NumberPicker.Formatter getFormatter() {
2166 | return mFormatter;
2167 | }
2168 |
2169 | public int getSelectedTextColor() {
2170 | return mSelectedTextColor;
2171 | }
2172 |
2173 | public int getTextColor() {
2174 | return mTextColor;
2175 | }
2176 |
2177 | public float getTextSize() {
2178 | return spToPx(mTextSize);
2179 | }
2180 |
2181 | public Typeface getTypeface() {
2182 | return mTypeface;
2183 | }
2184 |
2185 | }
2186 |
2187 |
--------------------------------------------------------------------------------
/odometer/src/main/java/com/androidchils/odometer/Odometer.java:
--------------------------------------------------------------------------------
1 | package com.androidchils.odometer;
2 |
3 | import android.content.Context;
4 | import android.content.res.Resources;
5 | import android.content.res.TypedArray;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.Typeface;
9 | import android.graphics.drawable.ColorDrawable;
10 | import android.graphics.drawable.GradientDrawable;
11 | import android.support.annotation.Nullable;
12 | import android.support.v4.content.ContextCompat;
13 | import android.text.TextUtils;
14 | import android.util.AttributeSet;
15 | import android.util.Log;
16 | import android.util.TypedValue;
17 | import android.view.Gravity;
18 | import android.view.LayoutInflater;
19 | import android.view.View;
20 | import android.widget.EditText;
21 | import android.widget.LinearLayout;
22 | import android.widget.NumberPicker;
23 | import android.widget.TextView;
24 |
25 | import java.lang.reflect.Field;
26 |
27 | /**
28 | * Created by chiragpatel on 15-05-2017.
29 | */
30 |
31 | public class Odometer extends LinearLayout {
32 |
33 | private LinearLayout llParent;
34 | private int slot;
35 | private int odo_text_color;
36 | private float textSize;
37 | private String read, fontName;
38 | private int odo_edge_color;
39 | private int odo_center_color;
40 |
41 | public Odometer(Context context, Builder builder) {
42 | super(context);
43 | initViews(context, builder);
44 | }
45 |
46 | public Odometer(Context context, @Nullable AttributeSet attrs) {
47 | super(context, attrs);
48 | initViews(context, attrs);
49 | }
50 |
51 | public Odometer(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
52 | super(context, attrs, defStyleAttr);
53 | initViews(context, attrs);
54 | }
55 |
56 | private void initViews(Context context, Builder builder) {
57 | setOrientation(HORIZONTAL);
58 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
59 | inflater.inflate(R.layout.number_picker, this, true);
60 |
61 | llParent = (LinearLayout) findViewById(R.id.llParent);
62 |
63 | createNumberPicker(context, builder);
64 | }
65 |
66 | private void initViews(Context context, AttributeSet attrs) {
67 |
68 | setOrientation(HORIZONTAL);
69 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
70 | inflater.inflate(R.layout.number_picker, this, true);
71 |
72 | llParent = (LinearLayout) findViewById(R.id.llParent);
73 |
74 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Odometer);
75 |
76 | try {
77 | fontName = typedArray.getString(R.styleable.Odometer_np_font);
78 |
79 | textSize = typedArray.getDimension(R.styleable.Odometer_np_textSize, spToPx(18f));
80 |
81 | odo_text_color = typedArray.getColor(R.styleable.Odometer_np_textColor, ContextCompat.getColor(context, R.color.white));
82 |
83 | slot = typedArray.getInt(R.styleable.Odometer_np_slots, 6);
84 | read = typedArray.getString(R.styleable.Odometer_np_reading);
85 |
86 | odo_edge_color = typedArray.getResourceId(R.styleable.Odometer_np_edgeColor, R.color.white);
87 | odo_center_color = typedArray.getResourceId(R.styleable.Odometer_np_centerColor, R.color.black);
88 |
89 | } finally {
90 | typedArray.recycle();
91 | }
92 |
93 | if (TextUtils.isEmpty(fontName)) {
94 | fontName = "Lato-Regular.ttf";
95 | }
96 |
97 |
98 | if (TextUtils.isEmpty(read)) {
99 | read = "000000";
100 | }
101 |
102 |
103 | if (TextUtils.isEmpty(read) || read.length() != slot) {
104 | TextView textView = new TextView(context);
105 | LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
106 | lp.gravity = Gravity.CENTER;
107 | textView.setLayoutParams(lp);
108 | textView.setTextColor(ContextCompat.getColor(context, R.color.black));
109 | textView.setText("Invalid Values");
110 | llParent.addView(textView);
111 |
112 | } else {
113 | createDynamicNumberPicker(context);
114 | }
115 |
116 | }
117 |
118 | private GradientDrawable makeGradientDrawable(GradientDrawable.Orientation orientation,
119 | int startColor, int centerColor, int endColor) {
120 | int[] colors = new int[]{startColor, centerColor, endColor};
121 | GradientDrawable gd = new GradientDrawable(orientation, colors);
122 | gd.setCornerRadius(8);
123 | gd.setGradientRadius(90);
124 | return gd;
125 | }
126 |
127 | private float spToPx(float sp) {
128 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
129 | }
130 |
131 | private void createDynamicNumberPicker(Context context) {
132 |
133 | for (int i = 1; i <= slot; i++) {
134 | NumberPicker numberPicker = new NumberPicker(context);
135 | LayoutParams lp = new LayoutParams(90, LayoutParams.WRAP_CONTENT);
136 | lp.setMargins(2, 0, 2, 0);
137 | lp.gravity = Gravity.CENTER;
138 | numberPicker.setLayoutParams(lp);
139 |
140 | setDividerColor(numberPicker, Color.TRANSPARENT);
141 |
142 | setNumberPickerTextColor(numberPicker, odo_text_color, fontName, textSize);
143 |
144 | if (odo_edge_color!=0 && odo_center_color!=0) {
145 | numberPicker.setBackgroundDrawable(makeGradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM,
146 | ContextCompat.getColor(context, odo_edge_color),
147 | ContextCompat.getColor(context, odo_center_color),
148 | ContextCompat.getColor(context, odo_edge_color)));
149 | }
150 |
151 | numberPicker.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS);
152 | numberPicker.setId(i - 1);
153 | numberPicker.setMinValue(0);
154 | numberPicker.setMaxValue(9);
155 |
156 | numberPicker.setWrapSelectorWheel(true);
157 |
158 | int read_val = Character.getNumericValue(read.charAt(i - 1));
159 | numberPicker.setValue(read_val);
160 |
161 | llParent.addView(numberPicker);
162 | }
163 |
164 | }
165 |
166 |
167 | private void createNumberPicker(Context context, Builder builder) {
168 |
169 | for (int i = 1; i <= builder.slot; i++) {
170 | NumberPicker numberPicker = new NumberPicker(context);
171 | LayoutParams lp = new LayoutParams(90, LayoutParams.WRAP_CONTENT);
172 | lp.setMargins(2, 0, 2, 0);
173 | lp.gravity = Gravity.CENTER;
174 | numberPicker.setLayoutParams(lp);
175 |
176 | setDividerColor(numberPicker, Color.TRANSPARENT);
177 |
178 | setNumberPickerTextColor(numberPicker, builder.odo_text_color, builder.font, spToPx(builder.textSize));
179 |
180 | numberPicker.setBackgroundDrawable(makeGradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM,
181 | builder.odo_edge_color, builder.odo_center_color, builder.odo_edge_color));
182 |
183 | numberPicker.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS);
184 | numberPicker.setId(i - 1);
185 | numberPicker.setMinValue(0);
186 | numberPicker.setMaxValue(9);
187 |
188 | numberPicker.setWrapSelectorWheel(true);
189 |
190 | int read_val = Character.getNumericValue(builder.reading.charAt(i - 1));
191 | numberPicker.setValue(read_val);
192 |
193 | llParent.addView(numberPicker);
194 | Log.e("add", "add" + i);
195 | }
196 |
197 | }
198 |
199 | public void setNumberPickerTextColor(NumberPicker numberPicker, int color, String fontName, float textSize) {
200 | final int count = numberPicker.getChildCount();
201 | for (int i = 0; i < count; i++) {
202 | View child = numberPicker.getChildAt(i);
203 | if (child instanceof EditText) {
204 | try {
205 | Field selectorWheelPaintField = numberPicker.getClass().getDeclaredField("mSelectorWheelPaint");
206 | selectorWheelPaintField.setAccessible(true);
207 | ((Paint) selectorWheelPaintField.get(numberPicker)).setColor(color);
208 |
209 | if (!TextUtils.isEmpty(fontName))
210 | ((Paint) selectorWheelPaintField.get(numberPicker)).setTypeface(Typeface.createFromAsset(getResources().getAssets(), fontName));
211 |
212 | ((Paint) selectorWheelPaintField.get(numberPicker)).setTextSize(textSize);
213 |
214 | ((EditText) child).setTextColor(color);
215 |
216 | if (!TextUtils.isEmpty(fontName))
217 | ((EditText) child).setTypeface(Typeface.createFromAsset(getResources().getAssets(), fontName));
218 |
219 | ((EditText) child).setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
220 |
221 | numberPicker.invalidate();
222 | } catch (NoSuchFieldException e) {
223 | Log.w("NumberPickerTextColor", e);
224 | } catch (IllegalAccessException e) {
225 | Log.w("NumberPickerTextColor", e);
226 | } catch (IllegalArgumentException e) {
227 | Log.w("NumberPickerTextColor", e);
228 | }
229 | }
230 | }
231 | }
232 |
233 | public void setDividerColor(NumberPicker picker, int color) {
234 |
235 | Field[] pickerFields = NumberPicker.class.getDeclaredFields();
236 | for (Field pf : pickerFields) {
237 | if (pf.getName().equals("mSelectionDivider")) {
238 | pf.setAccessible(true);
239 | try {
240 | ColorDrawable colorDrawable = new ColorDrawable(color);
241 | pf.set(picker, colorDrawable);
242 | } catch (IllegalArgumentException e) {
243 | e.printStackTrace();
244 | } catch (Resources.NotFoundException e) {
245 | e.printStackTrace();
246 | } catch (IllegalAccessException e) {
247 | e.printStackTrace();
248 | }
249 | break;
250 | }
251 | }
252 | }
253 |
254 |
255 | public String getFinalOdometerValue() {
256 | StringBuilder stringBuilder = new StringBuilder();
257 | for (int i = 0; i < llParent.getChildCount(); i++) {
258 |
259 | NumberPicker localNumberPicker = (NumberPicker) llParent.getChildAt(i);
260 | localNumberPicker.getValue();
261 |
262 | stringBuilder.append(localNumberPicker.getValue());
263 | stringBuilder.append(" ");
264 | }
265 | return stringBuilder.toString();
266 | }
267 |
268 |
269 | public static class Builder {
270 | // default values
271 | private Context context;
272 | private String font = "";
273 | private float textSize = 0;
274 | private int odo_text_color = 0;
275 | private int odo_edge_color = 0;
276 | private int odo_center_color = 0;
277 | private int slot;
278 | private String reading;
279 |
280 |
281 | public Builder(Context context) {
282 | this.context = context;
283 | odo_text_color = ContextCompat.getColor(context, R.color.white);
284 | odo_edge_color = ContextCompat.getColor(context, R.color.startColor);
285 | odo_center_color = ContextCompat.getColor(context, R.color.centerColor);
286 | textSize = spToPx(14);
287 | slot = 4;
288 | reading = "0000";
289 | }
290 |
291 | public Builder background(int odo_edge_color, int odo_center_color) {
292 | this.odo_edge_color = odo_edge_color;
293 | this.odo_center_color = odo_center_color;
294 | return this;
295 | }
296 |
297 |
298 | public Builder textColor(int odo_text_color) {
299 | this.odo_text_color = odo_text_color;
300 | return this;
301 | }
302 |
303 |
304 | public Builder font(String font) {
305 | this.font = font;
306 | return this;
307 | }
308 |
309 | public Builder textSize(float textSize) {
310 | this.textSize = textSize;
311 | return this;
312 | }
313 |
314 | public Builder slot(int slot) {
315 | this.slot = slot;
316 | return this;
317 | }
318 |
319 | public Builder reading(String reading) {
320 | this.reading = reading;
321 | return this;
322 | }
323 |
324 |
325 | public Odometer build() {
326 | return new Odometer(context, this);
327 | }
328 |
329 |
330 | private float spToPx(float sp) {
331 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, context.getResources().getDisplayMetrics());
332 | }
333 |
334 | }
335 |
336 |
337 | }
338 |
--------------------------------------------------------------------------------
/odometer/src/main/res/drawable/gradient.xml:
--------------------------------------------------------------------------------
1 |
2 |