├── sample ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── font │ │ │ └── ubuntu_mono_bold.ttf │ │ ├── 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 │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ └── styles.xml │ │ ├── layout │ │ │ ├── activity_root.xml │ │ │ ├── fragment_dashboard.xml │ │ │ ├── fragment_team_picker.xml │ │ │ ├── fragment_rating_picker.xml │ │ │ ├── fragment_clothing_size_picker.xml │ │ │ ├── item_view_dashboard.xml │ │ │ ├── fragment_date_picker.xml │ │ │ ├── fragment_time_picker.xml │ │ │ ├── fragment_person_info_picker.xml │ │ │ └── fragment_movie_filtering_picker.xml │ │ ├── drawable │ │ │ ├── team_picker_divider.xml │ │ │ ├── clothing_size_picker_divider.xml │ │ │ └── ic_launcher_background.xml │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ └── navigation │ │ │ └── root_graph.xml │ │ ├── java │ │ └── com │ │ │ └── paulrybitskyi │ │ │ └── valuepicker │ │ │ └── sample │ │ │ ├── clothingsizepicker │ │ │ ├── model │ │ │ │ └── ClothingSize.kt │ │ │ └── ClothingSizePickerFragment.kt │ │ │ ├── root │ │ │ └── RootActivity.kt │ │ │ ├── moviefilteringpicker │ │ │ ├── model │ │ │ │ ├── StreamingService.kt │ │ │ │ └── Genre.kt │ │ │ └── MovieFilteringPickerFragment.kt │ │ │ ├── valueeffects │ │ │ ├── CompositeValueEffect.kt │ │ │ ├── ValueEffectsExtensions.kt │ │ │ └── RotationValueEffect.kt │ │ │ ├── teampicker │ │ │ ├── model │ │ │ │ └── Team.kt │ │ │ └── TeamPickerFragment.kt │ │ │ ├── ratingpicker │ │ │ ├── model │ │ │ │ └── Rating.kt │ │ │ └── RatingPickerFragment.kt │ │ │ ├── datepicker │ │ │ ├── model │ │ │ │ └── Month.kt │ │ │ └── DatePickerFragment.kt │ │ │ ├── dashboard │ │ │ ├── DashboardFragment.kt │ │ │ ├── DashboardRecyclerViewAdapter.kt │ │ │ └── DashboardDestination.kt │ │ │ ├── BaseFragment.kt │ │ │ ├── timepicker │ │ │ └── TimePickerFragment.kt │ │ │ └── personinfopicker │ │ │ └── PersonInfoPickerFragment.kt │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle.kts ├── value-picker ├── .gitignore ├── consumer-rules.pro ├── proguard-rules.pro ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ └── attrs.xml │ │ └── drawable │ │ │ └── value_picker_divider_drawable.xml │ │ └── java │ │ └── com │ │ └── paulrybitskyi │ │ └── valuepicker │ │ ├── model │ │ ├── PickerItem.kt │ │ ├── ValueItemConfig.kt │ │ ├── Item.kt │ │ ├── Orientation.kt │ │ ├── DefaultValues.kt │ │ └── Size.kt │ │ ├── scrollerhelpers │ │ ├── ScrollerHelper.kt │ │ ├── ScrollerHelperFactory.kt │ │ └── concrete │ │ │ ├── RegularScrollerHelper.kt │ │ │ └── InfiniteScrollerHelper.kt │ │ ├── utils │ │ └── TypedArrayExtensions.kt │ │ ├── valueeffects │ │ ├── concrete │ │ │ ├── NoValueEffect.kt │ │ │ └── FadingValueEffect.kt │ │ └── ValueEffect.kt │ │ ├── layoutmanager │ │ ├── ValuePickerSmoothScroller.kt │ │ └── ValuePickerLayoutManager.kt │ │ ├── decorators │ │ ├── ValuePickerItemDecorator.kt │ │ ├── ValuePickerItemDecoratorFactory.kt │ │ └── concrete │ │ │ ├── HorizontalValuePickerItemDecorator.kt │ │ │ └── VerticalValuePickerItemDecorator.kt │ │ └── ValuePickerRecyclerViewAdapter.kt └── build.gradle.kts ├── value-picker-lint ├── .gitignore ├── src │ └── main │ │ └── java │ │ └── com │ │ └── paulrybitskyi │ │ └── valuepicker │ │ └── lint │ │ ├── DetectorCommon.kt │ │ ├── ValuePickerIssueRegistry.kt │ │ └── NumberPickerUsageDetector.kt └── build.gradle.kts ├── media └── demo_thumbnail.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle.kts ├── .gitignore ├── .editorconfig ├── hooks └── pre-push ├── .github └── workflows │ ├── release.yml │ └── build.yml ├── gradle.properties ├── gradlew.bat ├── publishing.gradle.kts ├── gradlew ├── README.md └── LICENSE /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /value-picker/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /value-picker/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /value-picker-lint/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /media/demo_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mars885/value-picker/HEAD/media/demo_thumbnail.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mars885/value-picker/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /sample/src/main/res/font/ubuntu_mono_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mars885/value-picker/HEAD/sample/src/main/res/font/ubuntu_mono_bold.ttf -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "ValuePicker" 2 | 3 | include(":sample") 4 | include(":value-picker") 5 | include(":value-picker-lint") 6 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mars885/value-picker/HEAD/sample/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mars885/value-picker/HEAD/sample/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mars885/value-picker/HEAD/sample/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mars885/value-picker/HEAD/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mars885/value-picker/HEAD/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mars885/value-picker/HEAD/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mars885/value-picker/HEAD/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mars885/value-picker/HEAD/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mars885/value-picker/HEAD/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mars885/value-picker/HEAD/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Aug 15 16:01:08 EEST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | /.idea 16 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | indent_size = unset 4 | indent_style = space 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | max_line_length = 120 8 | 9 | [*.yml] 10 | indent_size = 2 11 | 12 | [*.{kt,kts}] 13 | ktlint_standard_annotation = disabled 14 | ktlint_standard_no-empty-first-line-in-class-body = disabled 15 | ktlint_standard_blank-line-before-declaration = disabled 16 | ktlint_standard_multiline-expression-wrapping = disabled 17 | ktlint_standard_string-template-indent = disabled 18 | ktlint_standard_spacing-between-declarations-with-annotations = disabled 19 | ktlint_standard_function-signature = disabled 20 | ktlint_standard_class-signature = disabled 21 | -------------------------------------------------------------------------------- /hooks/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Running Git Pre-Push Hook" 4 | 5 | ###################################### 6 | 7 | echo "Running Android Lint" 8 | ./gradlew lint 9 | RESULT_LINT=$? 10 | if [ $RESULT_LINT -ne 0 ] 11 | then 12 | exit 1 13 | fi 14 | 15 | ###################################### 16 | 17 | echo "Running Detekt" 18 | ./gradlew detekt --continue 19 | RESULT_DETEKT=$? 20 | if [ $RESULT_DETEKT -ne 0 ] 21 | then 22 | exit 1 23 | fi 24 | 25 | ###################################### 26 | 27 | echo "Running Ktlint" 28 | ./gradlew ktlintCheck --continue 29 | RESULT_KTLINT=$? 30 | if [ $RESULT_KTLINT -ne 0 ] 31 | then 32 | exit 1 33 | fi 34 | 35 | ###################################### 36 | 37 | echo "Static Analysis Checks Completed Successfully" 38 | -------------------------------------------------------------------------------- /sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /value-picker/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /value-picker/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | #000000 20 | #000000 21 | 22 | 23 | -------------------------------------------------------------------------------- /sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | ValuePicker 20 | 21 | sans-serif-medium 22 | 23 | 24 | -------------------------------------------------------------------------------- /sample/src/main/java/com/paulrybitskyi/valuepicker/sample/clothingsizepicker/model/ClothingSize.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.sample.clothingsizepicker.model 18 | 19 | internal enum class ClothingSize { 20 | XS, 21 | S, 22 | M, 23 | L, 24 | XL, 25 | XXL, 26 | } 27 | -------------------------------------------------------------------------------- /sample/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | #2d2b39 20 | #22202b 21 | #7986cb 22 | #95949a 23 | 24 | 25 | -------------------------------------------------------------------------------- /sample/src/main/java/com/paulrybitskyi/valuepicker/sample/root/RootActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.sample.root 18 | 19 | import androidx.appcompat.app.AppCompatActivity 20 | import com.paulrybitskyi.valuepicker.sample.R 21 | 22 | internal class RootActivity : AppCompatActivity(R.layout.activity_root) 23 | -------------------------------------------------------------------------------- /sample/src/main/java/com/paulrybitskyi/valuepicker/sample/moviefilteringpicker/model/StreamingService.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.sample.moviefilteringpicker.model 18 | 19 | internal enum class StreamingService(val title: String) { 20 | NETFLIX("Netflix"), 21 | PRIME("Prime"), 22 | HULU("Hulu"), 23 | HBO_MAX("HBO"), 24 | DISNEY("Disney"), 25 | APPLE_TV("Apple"), 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | env: 9 | JDK_JAVA_DISTRIBUTION: 'temurin' 10 | JDK_JAVA_VERSION: '17' 11 | 12 | jobs: 13 | release: 14 | name: Create a GitHub Release 15 | runs-on: [ubuntu-latest] 16 | permissions: 17 | contents: write 18 | 19 | steps: 20 | - name: Checkout Repo 21 | uses: actions/checkout@v4 22 | 23 | - name: Set up JDK 24 | uses: actions/setup-java@v4 25 | with: 26 | distribution: ${{ env.JDK_JAVA_DISTRIBUTION }} 27 | java-version: ${{ env.JDK_JAVA_VERSION }} 28 | 29 | - name: Set up Gradle 30 | uses: gradle/actions/setup-gradle@v4 31 | 32 | - name: Build Debug APK 33 | run: ./gradlew assembleDebug 34 | 35 | - name: Create GitHub Release 36 | uses: softprops/action-gh-release@v2 37 | with: 38 | name: Release ${{ github.ref }} 39 | draft: true 40 | prerelease: false 41 | files: sample/build/outputs/apk/debug/sample-debug.apk 42 | -------------------------------------------------------------------------------- /value-picker/src/main/java/com/paulrybitskyi/valuepicker/model/PickerItem.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.model 18 | 19 | /** 20 | * A simple representation of the items inside the value picker. 21 | * 22 | * see [Item] 23 | */ 24 | class PickerItem @JvmOverloads constructor( 25 | override val id: Int, 26 | override val title: String, 27 | override val payload: Any? = null, 28 | ) : Item 29 | -------------------------------------------------------------------------------- /value-picker/src/main/java/com/paulrybitskyi/valuepicker/scrollerhelpers/ScrollerHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.scrollerhelpers 18 | 19 | internal interface ScrollerHelper { 20 | 21 | val adapterItemCount: Int 22 | 23 | var dataSetItemCount: Int 24 | 25 | fun calculateAdapterPosition(dataSetPosition: Int): Int 26 | 27 | fun calculateDataSetPosition(adapterPosition: Int): Int 28 | } 29 | -------------------------------------------------------------------------------- /value-picker/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 80dp 20 | 50dp 21 | 10dp 22 | 16sp 23 | 2dp 24 | 25 | 26 | -------------------------------------------------------------------------------- /sample/src/main/java/com/paulrybitskyi/valuepicker/sample/moviefilteringpicker/model/Genre.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.sample.moviefilteringpicker.model 18 | 19 | internal enum class Genre(val title: String) { 20 | ACTION("Action"), 21 | HORROR("Horror"), 22 | THRILLER("Thriller"), 23 | SCI_FI("Sci-Fi"), 24 | CRIME("Crime"), 25 | COMEDY("Comedy"), 26 | DRAMA("Drama"), 27 | WESTERN("Western"), 28 | } 29 | -------------------------------------------------------------------------------- /value-picker/src/main/java/com/paulrybitskyi/valuepicker/utils/TypedArrayExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @file:JvmName("TypedArrayUtils") 18 | 19 | package com.paulrybitskyi.valuepicker.utils 20 | 21 | import android.content.res.TypedArray 22 | import androidx.annotation.StyleableRes 23 | 24 | internal fun TypedArray.getColor(@StyleableRes id: Int, default: Int?): Int? { 25 | return getColor(id, -1) 26 | .takeIf { it != -1 } 27 | ?: default 28 | } 29 | -------------------------------------------------------------------------------- /value-picker/src/main/res/drawable/value_picker_divider_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /value-picker-lint/src/main/java/com/paulrybitskyi/valuepicker/lint/DetectorCommon.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.lint 18 | 19 | internal const val NUMBER_PICKER_SIMPLE_NAME = "NumberPicker" 20 | internal const val NUMBER_PICKER_CANONICAL_NAME = "android.widget.NumberPicker" 21 | internal const val NUMBER_PICKER_USAGE_BRIEF_DESC = "Using 'NumberPicker' instead of 'ValuePickerView'" 22 | 23 | internal const val VALUE_PICKER_VIEW_CANONICAL_NAME = "com.paulrybitskyi.valuepicker.ValuePickerView" 24 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_root.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 28 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official -------------------------------------------------------------------------------- /sample/src/main/res/drawable/team_picker_divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /value-picker/src/main/java/com/paulrybitskyi/valuepicker/model/ValueItemConfig.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @file:JvmName("ValueItemConfigUtils") 18 | 19 | package com.paulrybitskyi.valuepicker.model 20 | 21 | import android.graphics.Typeface 22 | 23 | internal data class ValueItemConfig( 24 | val size: Size, 25 | val textColor: Int, 26 | val textSize: Float, 27 | val textTypeface: Typeface, 28 | ) 29 | 30 | @JvmField 31 | internal val VALUE_ITEM_CONFIG_STUB = ValueItemConfig( 32 | size = sizeOf(width = 0, height = 0), 33 | textColor = 0, 34 | textSize = 0f, 35 | textTypeface = Typeface.SANS_SERIF, 36 | ) 37 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/clothing_size_picker_divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 22 | 23 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /value-picker/src/main/java/com/paulrybitskyi/valuepicker/valueeffects/concrete/NoValueEffect.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.valueeffects.concrete 18 | 19 | import android.view.View 20 | import androidx.recyclerview.widget.RecyclerView 21 | import com.paulrybitskyi.valuepicker.model.Orientation 22 | import com.paulrybitskyi.valuepicker.valueeffects.ValueEffect 23 | 24 | /** 25 | * An implementation of the value effect that does nothing. 26 | * 27 | * see [ValueEffect] 28 | */ 29 | class NoValueEffect : ValueEffect { 30 | 31 | override fun applyEffect(child: View, recyclerView: RecyclerView, orientation: Orientation) { 32 | // Stub 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /value-picker/src/main/java/com/paulrybitskyi/valuepicker/model/Item.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.model 18 | 19 | import com.paulrybitskyi.valuepicker.ValuePickerView 20 | 21 | /** 22 | * An abstraction for representing an item inside the value picker. 23 | * 24 | * @see [ValuePickerView] 25 | * @see [ValuePickerView.items] 26 | */ 27 | interface Item { 28 | 29 | /** 30 | * The unique ID of the item. 31 | */ 32 | val id: Int 33 | 34 | /** 35 | * The text to be displayed. 36 | */ 37 | val title: String 38 | 39 | /** 40 | * The optional payload to attach to each of the items. 41 | */ 42 | val payload: Any? 43 | } 44 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_dashboard.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 30 | -------------------------------------------------------------------------------- /value-picker/src/main/java/com/paulrybitskyi/valuepicker/layoutmanager/ValuePickerSmoothScroller.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @file:JvmName("ValuePickerSmoothScrollerUtils") 18 | 19 | package com.paulrybitskyi.valuepicker.layoutmanager 20 | 21 | import android.content.Context 22 | import android.util.DisplayMetrics 23 | import androidx.recyclerview.widget.LinearSmoothScroller 24 | 25 | private const val MILLISECONDS_PER_INCH = 150f 26 | 27 | internal class ValuePickerSmoothScroller(context: Context) : LinearSmoothScroller(context) { 28 | 29 | override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float { 30 | return (MILLISECONDS_PER_INCH / displayMetrics.densityDpi) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /value-picker/src/main/java/com/paulrybitskyi/valuepicker/scrollerhelpers/ScrollerHelperFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.scrollerhelpers 18 | 19 | import com.paulrybitskyi.valuepicker.scrollerhelpers.concrete.InfiniteScrollerHelper 20 | import com.paulrybitskyi.valuepicker.scrollerhelpers.concrete.RegularScrollerHelper 21 | 22 | internal object ScrollerHelperFactory { 23 | 24 | @JvmStatic 25 | fun create(isInfinite: Boolean, dataSetItemCount: Int): ScrollerHelper { 26 | return if (isInfinite) { 27 | InfiniteScrollerHelper(dataSetItemCount) 28 | } else { 29 | RegularScrollerHelper(dataSetItemCount) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /value-picker/src/main/java/com/paulrybitskyi/valuepicker/scrollerhelpers/concrete/RegularScrollerHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.scrollerhelpers.concrete 18 | 19 | import com.paulrybitskyi.valuepicker.scrollerhelpers.ScrollerHelper 20 | 21 | internal class RegularScrollerHelper(override var dataSetItemCount: Int) : ScrollerHelper { 22 | 23 | override val adapterItemCount: Int 24 | get() = dataSetItemCount 25 | 26 | override fun calculateAdapterPosition(dataSetPosition: Int): Int { 27 | return dataSetPosition 28 | } 29 | 30 | override fun calculateDataSetPosition(adapterPosition: Int): Int { 31 | return adapterPosition 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sample/src/main/java/com/paulrybitskyi/valuepicker/sample/valueeffects/CompositeValueEffect.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.sample.valueeffects 18 | 19 | import android.view.View 20 | import androidx.recyclerview.widget.RecyclerView 21 | import com.paulrybitskyi.valuepicker.model.Orientation 22 | import com.paulrybitskyi.valuepicker.valueeffects.ValueEffect 23 | 24 | internal class CompositeValueEffect( 25 | private val itemEffects: List, 26 | ) : ValueEffect { 27 | 28 | override fun applyEffect(child: View, recyclerView: RecyclerView, orientation: Orientation) { 29 | for (itemEffect in itemEffects) { 30 | itemEffect.applyEffect(child, recyclerView, orientation) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /value-picker-lint/src/main/java/com/paulrybitskyi/valuepicker/lint/ValuePickerIssueRegistry.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.lint 18 | 19 | import com.android.tools.lint.client.api.IssueRegistry 20 | import com.android.tools.lint.client.api.Vendor 21 | import com.android.tools.lint.detector.api.CURRENT_API 22 | import com.google.auto.service.AutoService 23 | 24 | private val VENDOR = Vendor( 25 | vendorName = "ValuePicker Android Library", 26 | feedbackUrl = "https://github.com/mars885/value-picker/issues/new", 27 | ) 28 | 29 | @AutoService(IssueRegistry::class) 30 | internal class ValuePickerIssueRegistry : IssueRegistry() { 31 | override val issues = listOf(NumberPickerUsageDetector.ISSUE) 32 | override val api = CURRENT_API 33 | override val vendor = VENDOR 34 | } 35 | -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 26 | 27 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /value-picker/src/main/java/com/paulrybitskyi/valuepicker/decorators/ValuePickerItemDecorator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.decorators 18 | 19 | import android.graphics.Canvas 20 | import android.graphics.drawable.Drawable 21 | import androidx.recyclerview.widget.RecyclerView 22 | import com.paulrybitskyi.valuepicker.model.ValueItemConfig 23 | 24 | internal abstract class ValuePickerItemDecorator( 25 | protected val maxVisibleItems: Int, 26 | protected val dividerDrawable: Drawable, 27 | protected val valueItemConfigProvider: () -> ValueItemConfig, 28 | ) : RecyclerView.ItemDecoration() { 29 | 30 | override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { 31 | super.onDraw(canvas, parent, state) 32 | 33 | drawDividers(canvas, parent) 34 | } 35 | 36 | abstract fun drawDividers(canvas: Canvas, parent: RecyclerView) 37 | } 38 | -------------------------------------------------------------------------------- /sample/src/main/java/com/paulrybitskyi/valuepicker/sample/valueeffects/ValueEffectsExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.sample.valueeffects 18 | 19 | import android.view.View 20 | import androidx.recyclerview.widget.RecyclerView 21 | import com.paulrybitskyi.valuepicker.model.Orientation 22 | 23 | internal fun RecyclerView.calculateCenter(orientation: Orientation): Float { 24 | return (getDimension(orientation) / 2f) 25 | } 26 | 27 | internal fun RecyclerView.getDimension(orientation: Orientation): Int { 28 | return when (orientation) { 29 | Orientation.VERTICAL -> height 30 | Orientation.HORIZONTAL -> width 31 | } 32 | } 33 | 34 | internal fun View.calculateChildCenter(orientation: Orientation): Int { 35 | return when (orientation) { 36 | Orientation.VERTICAL -> ((height / 2) + top) 37 | Orientation.HORIZONTAL -> ((width / 2) + left) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /value-picker/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /value-picker-lint/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 2 | 3 | /* 4 | * Copyright 2021 Paul Rybitskyi, oss@paulrybitskyi.com 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | plugins { 20 | `java-library` 21 | kotlin() 22 | kotlinKapt() 23 | lint() 24 | } 25 | 26 | java.sourceCompatibility = appConfig.javaCompatibilityVersion 27 | java.targetCompatibility = appConfig.javaCompatibilityVersion 28 | 29 | kotlin { 30 | compilerOptions { 31 | jvmTarget = JvmTarget.fromTarget(appConfig.kotlinCompatibilityVersion.toString()) 32 | } 33 | } 34 | 35 | dependencies { 36 | compileOnly(deps.lintApi) 37 | 38 | // See this for why stdLib is needed: 39 | // https://pspdfkit.com/blog/2020/how-updating-to-kotlin-14-broke-our-linter-rules/ 40 | compileOnly(deps.stdLib) 41 | 42 | compileOnly(deps.autoService) 43 | kapt(deps.autoService) 44 | 45 | testImplementation(deps.jUnit) 46 | testImplementation(deps.lintApi) 47 | testImplementation(deps.lintTests) 48 | } 49 | -------------------------------------------------------------------------------- /value-picker/src/main/java/com/paulrybitskyi/valuepicker/scrollerhelpers/concrete/InfiniteScrollerHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.scrollerhelpers.concrete 18 | 19 | import com.paulrybitskyi.valuepicker.scrollerhelpers.ScrollerHelper 20 | 21 | internal class InfiniteScrollerHelper(override var dataSetItemCount: Int) : ScrollerHelper { 22 | 23 | override val adapterItemCount: Int 24 | get() = (if (dataSetItemCount == 0) 0 else Integer.MAX_VALUE) 25 | 26 | override fun calculateAdapterPosition(dataSetPosition: Int): Int { 27 | val halfAdapterItemCount = (adapterItemCount / 2) 28 | val baseAdapterPosition = ((halfAdapterItemCount / dataSetItemCount) * dataSetItemCount) 29 | 30 | return (baseAdapterPosition + dataSetPosition) 31 | } 32 | 33 | override fun calculateDataSetPosition(adapterPosition: Int): Int { 34 | return (adapterPosition % dataSetItemCount) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /sample/src/main/java/com/paulrybitskyi/valuepicker/sample/teampicker/model/Team.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.sample.teampicker.model 18 | 19 | internal enum class Team(val longName: String) { 20 | BOSTON_CELTICS(longName = "Boston Celtics"), 21 | MINNESOTA_TIMBERWOLVES(longName = "Minnesota Timberwolves"), 22 | OKLAHOMA_CITY_THUNDER(longName = "Oklahoma City Thunder"), 23 | HOUSTON_ROCKETS(longName = "Houston Rockets"), 24 | ORLANDO_MAGIC(longName = "Orlando Magic"), 25 | CHICAGO_BULLS(longName = "Chicago Bulls"), 26 | TORONTO_RAPTORS(longName = "Toronto Raptors"), 27 | NEW_YORK_KNICKS(longName = "New York Knics"), 28 | INDIANA_PACERS(longName = "Indiana Pacers"), 29 | MIAMI_HEAT(longName = "Miami Heat"), 30 | GOLDEN_STATE_WARRIOS(longName = "Golden State Warriors"), 31 | LOS_ANGELES_LAKERS(longName = "Los Angeles Lakers"), 32 | SAN_ANTONIO_SPURS(longName = "San Antonio Spurs"), 33 | } 34 | -------------------------------------------------------------------------------- /sample/src/main/java/com/paulrybitskyi/valuepicker/sample/ratingpicker/model/Rating.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.sample.ratingpicker.model 18 | 19 | internal enum class Rating( 20 | val number: Int, 21 | val title: String, 22 | ) { 23 | ONE( 24 | number = 1, 25 | title = "One", 26 | ), 27 | TWO( 28 | number = 2, 29 | title = "Two", 30 | ), 31 | THREE( 32 | number = 3, 33 | title = "Three", 34 | ), 35 | FOUR( 36 | number = 4, 37 | title = "Four", 38 | ), 39 | FIVE( 40 | number = 5, 41 | title = "Five", 42 | ), 43 | SIX( 44 | number = 6, 45 | title = "Six", 46 | ), 47 | SEVEN( 48 | number = 7, 49 | title = "Seven", 50 | ), 51 | EIGHT( 52 | number = 8, 53 | title = "Eight", 54 | ), 55 | NINE( 56 | number = 9, 57 | title = "Nine", 58 | ), 59 | TEN( 60 | number = 10, 61 | title = "Ten", 62 | ), 63 | } 64 | -------------------------------------------------------------------------------- /value-picker/src/main/java/com/paulrybitskyi/valuepicker/valueeffects/ValueEffect.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.valueeffects 18 | 19 | import android.view.View 20 | import androidx.recyclerview.widget.RecyclerView 21 | import com.paulrybitskyi.valuepicker.model.Orientation 22 | import com.paulrybitskyi.valuepicker.valueeffects.concrete.FadingValueEffect 23 | import com.paulrybitskyi.valuepicker.valueeffects.concrete.NoValueEffect 24 | 25 | /** 26 | * An interface responsible for applying cool visual effects to 27 | * child views of the value picker. 28 | */ 29 | interface ValueEffect { 30 | 31 | /** 32 | * Applies visual effect to child views of the value picker. 33 | * 34 | * @param child The child view of the value picker 35 | * @param recyclerView The recycler view of the value picker 36 | * @param orientation The current orientation 37 | * 38 | * see [FadingValueEffect] 39 | * see [NoValueEffect] 40 | */ 41 | fun applyEffect(child: View, recyclerView: RecyclerView, orientation: Orientation) 42 | } 43 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /value-picker/src/main/java/com/paulrybitskyi/valuepicker/model/Orientation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @file:JvmName("OrientationUtils") 18 | 19 | package com.paulrybitskyi.valuepicker.model 20 | 21 | import androidx.recyclerview.widget.RecyclerView 22 | import com.paulrybitskyi.valuepicker.ValuePickerView 23 | 24 | /** 25 | * An orientation of the value picker. 26 | * 27 | * @see [ValuePickerView] 28 | * @see [ValuePickerView.orientation] 29 | */ 30 | enum class Orientation(internal val id: Int) { 31 | 32 | VERTICAL(id = 1), 33 | HORIZONTAL(id = 2), 34 | ; 35 | 36 | companion object { 37 | 38 | @JvmName("forId") 39 | @JvmStatic 40 | internal fun Int.asOrientation(): Orientation { 41 | return values().find { it.id == this } 42 | ?: throw IllegalArgumentException("Could not find the orientation for the specified ID: $this.") 43 | } 44 | } 45 | } 46 | 47 | internal val Orientation.isVertical: Boolean 48 | get() = (this == Orientation.VERTICAL) 49 | 50 | internal val Orientation.isHorizontal: Boolean 51 | get() = (this == Orientation.HORIZONTAL) 52 | 53 | internal val Orientation.rvOrientation: Int 54 | get() = when (this) { 55 | Orientation.VERTICAL -> RecyclerView.VERTICAL 56 | Orientation.HORIZONTAL -> RecyclerView.HORIZONTAL 57 | } 58 | -------------------------------------------------------------------------------- /sample/src/main/java/com/paulrybitskyi/valuepicker/sample/datepicker/model/Month.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.sample.datepicker.model 18 | 19 | internal enum class Month( 20 | val longName: String, 21 | val shortName: String, 22 | ) { 23 | JANUARY( 24 | longName = "January", 25 | shortName = "Jan", 26 | ), 27 | FEBRUARY( 28 | longName = "February", 29 | shortName = "Feb", 30 | ), 31 | MARCH( 32 | longName = "March", 33 | shortName = "Mar", 34 | ), 35 | APRIL( 36 | longName = "April", 37 | shortName = "Apr", 38 | ), 39 | MAY( 40 | longName = "May", 41 | shortName = "May", 42 | ), 43 | JUNE( 44 | longName = "June", 45 | shortName = "Jun", 46 | ), 47 | JULY( 48 | longName = "July", 49 | shortName = "Jul", 50 | ), 51 | AUGUST( 52 | longName = "August", 53 | shortName = "Aug", 54 | ), 55 | SEPTEMBER( 56 | longName = "September", 57 | shortName = "Sept", 58 | ), 59 | OCTOBER( 60 | longName = "October", 61 | shortName = "Oct", 62 | ), 63 | NOVEMBER( 64 | longName = "November", 65 | shortName = "Nov", 66 | ), 67 | DECEMBER( 68 | longName = "December", 69 | shortName = "Dec", 70 | ), 71 | } 72 | -------------------------------------------------------------------------------- /sample/src/main/java/com/paulrybitskyi/valuepicker/sample/dashboard/DashboardFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.sample.dashboard 18 | 19 | import androidx.navigation.fragment.findNavController 20 | import com.paulrybitskyi.commons.utils.viewBinding 21 | import com.paulrybitskyi.valuepicker.sample.BaseFragment 22 | import com.paulrybitskyi.valuepicker.sample.R 23 | import com.paulrybitskyi.valuepicker.sample.databinding.FragmentDashboardBinding 24 | 25 | internal class DashboardFragment : BaseFragment< 26 | FragmentDashboardBinding, 27 | >(R.layout.fragment_dashboard) { 28 | 29 | override val viewBinding by viewBinding(FragmentDashboardBinding::bind) 30 | 31 | override fun onInit() { 32 | super.onInit() 33 | 34 | initRecyclerView() 35 | } 36 | 37 | private fun initRecyclerView() = with(viewBinding.recyclerView) { 38 | adapter = initAdapter() 39 | } 40 | 41 | private fun initAdapter(): DashboardRecyclerViewAdapter { 42 | return DashboardRecyclerViewAdapter( 43 | context = requireContext(), 44 | items = DashboardDestination.entries.toList(), 45 | onItemClickListener = ::navigateToDestination, 46 | ) 47 | } 48 | 49 | private fun navigateToDestination(destination: DashboardDestination) { 50 | findNavController().navigate(destination.destinationId) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /value-picker/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | androidLibrary() 19 | kotlinAndroid() 20 | } 21 | 22 | android { 23 | compileSdk = appConfig.compileSdkVersion 24 | 25 | defaultConfig { 26 | namespace = "com.paulrybitskyi.valuepicker" 27 | minSdk = appConfig.minSdkVersion 28 | 29 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 30 | } 31 | 32 | buildTypes { 33 | getByName("release") { 34 | isMinifyEnabled = false 35 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 36 | } 37 | } 38 | 39 | compileOptions { 40 | sourceCompatibility = appConfig.javaCompatibilityVersion 41 | targetCompatibility = appConfig.javaCompatibilityVersion 42 | } 43 | 44 | kotlinOptions { 45 | jvmTarget = appConfig.kotlinCompatibilityVersion.toString() 46 | } 47 | } 48 | 49 | dependencies { 50 | api(deps.recyclerView) 51 | 52 | implementation(deps.appCompat) 53 | implementation(deps.coreKtx) 54 | implementation(deps.commonsCore) 55 | implementation(deps.commonsKtx) 56 | implementation(deps.commonsRecyclerView) 57 | 58 | lintPublish(project(deps.local.valuePickerLint)) 59 | 60 | testImplementation(deps.jUnit) 61 | androidTestImplementation(deps.jUnitExt) 62 | } 63 | 64 | apply(from = "../publishing.gradle.kts") 65 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | JDK_JAVA_DISTRIBUTION: 'temurin' 11 | JDK_JAVA_VERSION: '17' 12 | 13 | jobs: 14 | static-analysis: 15 | name: Static Analysis 16 | runs-on: [ubuntu-latest] 17 | if: ${{ !contains(github.event.head_commit.message, '[ci skip]') }} 18 | 19 | steps: 20 | - name: Checkout Repo 21 | uses: actions/checkout@v4 22 | 23 | - name: Set up JDK 24 | uses: actions/setup-java@v4 25 | with: 26 | distribution: ${{ env.JDK_JAVA_DISTRIBUTION }} 27 | java-version: ${{ env.JDK_JAVA_VERSION }} 28 | 29 | - name: Set up Gradle 30 | uses: gradle/actions/setup-gradle@v4 31 | 32 | - name: Run Static Analysis Tools 33 | run: ./gradlew lint detekt ktlintCheck --continue 34 | 35 | test: 36 | name: Unit Tests 37 | runs-on: [ ubuntu-latest ] 38 | if: ${{ !contains(github.event.head_commit.message, '[ci skip]') }} 39 | 40 | steps: 41 | - name: Checkout Repo 42 | uses: actions/checkout@v4 43 | 44 | - name: Set up JDK 45 | uses: actions/setup-java@v4 46 | with: 47 | distribution: ${{ env.JDK_JAVA_DISTRIBUTION }} 48 | java-version: ${{ env.JDK_JAVA_VERSION }} 49 | 50 | - name: Set up Gradle 51 | uses: gradle/actions/setup-gradle@v4 52 | 53 | - name: Run Unit Tests 54 | run: ./gradlew test 55 | 56 | assemble: 57 | name: App Assembling 58 | runs-on: [ubuntu-latest] 59 | if: ${{ !contains(github.event.head_commit.message, '[ci skip]') }} 60 | 61 | steps: 62 | - name: Checkout Repo 63 | uses: actions/checkout@v4 64 | 65 | - name: Set up JDK 66 | uses: actions/setup-java@v4 67 | with: 68 | distribution: ${{ env.JDK_JAVA_DISTRIBUTION }} 69 | java-version: ${{ env.JDK_JAVA_VERSION }} 70 | 71 | - name: Set up Gradle 72 | uses: gradle/actions/setup-gradle@v4 73 | 74 | - name: Build Debug APK 75 | run: ./gradlew assembleDebug 76 | -------------------------------------------------------------------------------- /value-picker/src/main/java/com/paulrybitskyi/valuepicker/decorators/ValuePickerItemDecoratorFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.decorators 18 | 19 | import android.graphics.drawable.Drawable 20 | import com.paulrybitskyi.valuepicker.decorators.concrete.HorizontalValuePickerItemDecorator 21 | import com.paulrybitskyi.valuepicker.decorators.concrete.VerticalValuePickerItemDecorator 22 | import com.paulrybitskyi.valuepicker.model.ValueItemConfig 23 | 24 | internal object ValuePickerItemDecoratorFactory { 25 | 26 | @JvmStatic 27 | fun createVerticalDecorator( 28 | maxVisibleItems: Int, 29 | dividerDrawable: Drawable, 30 | valueItemConfigProvider: () -> ValueItemConfig, 31 | ): ValuePickerItemDecorator { 32 | return VerticalValuePickerItemDecorator( 33 | maxVisibleItems = maxVisibleItems, 34 | dividerDrawable = dividerDrawable, 35 | valueItemConfigProvider = valueItemConfigProvider, 36 | ) 37 | } 38 | 39 | @JvmStatic 40 | fun createHorizontalDecorator( 41 | maxVisibleItems: Int, 42 | dividerDrawable: Drawable, 43 | valueItemConfigProvider: () -> ValueItemConfig, 44 | ): ValuePickerItemDecorator { 45 | return HorizontalValuePickerItemDecorator( 46 | maxVisibleItems = maxVisibleItems, 47 | dividerDrawable = dividerDrawable, 48 | valueItemConfigProvider = valueItemConfigProvider, 49 | ) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /value-picker/src/main/java/com/paulrybitskyi/valuepicker/model/DefaultValues.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @file:JvmName("DefaultValuesUtils") 18 | 19 | package com.paulrybitskyi.valuepicker.model 20 | 21 | import android.content.Context 22 | import android.graphics.Typeface 23 | import com.paulrybitskyi.commons.ktx.getCompatColor 24 | import com.paulrybitskyi.commons.ktx.getDimension 25 | import com.paulrybitskyi.commons.ktx.getDimensionPixelSize 26 | import com.paulrybitskyi.valuepicker.R 27 | 28 | internal class DefaultValues( 29 | val valueItemTextColor: Int, 30 | val valueItemMinWidth: Int, 31 | val valueItemMinHeight: Int, 32 | val valueItemPadding: Int, 33 | val valueItemTextSize: Float, 34 | val valueItemTextTypeface: Typeface, 35 | ) 36 | 37 | internal val DefaultValues.valueItemSize: Size 38 | get() = sizeOf( 39 | width = valueItemMinWidth, 40 | height = valueItemMinHeight, 41 | ) 42 | 43 | internal fun initDefaultValues(context: Context): DefaultValues { 44 | return DefaultValues( 45 | valueItemTextColor = context.getCompatColor(R.color.default_value_item_text_color), 46 | valueItemMinWidth = context.getDimensionPixelSize(R.dimen.default_value_item_min_width), 47 | valueItemMinHeight = context.getDimensionPixelSize(R.dimen.default_value_item_min_height), 48 | valueItemPadding = context.getDimensionPixelSize(R.dimen.default_value_item_padding), 49 | valueItemTextSize = context.getDimension(R.dimen.default_value_item_text_size), 50 | valueItemTextTypeface = Typeface.SANS_SERIF, 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_team_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 26 | 27 | 34 | 35 | 42 | 43 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_rating_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 26 | 27 | 34 | 35 | 42 | 43 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /sample/src/main/java/com/paulrybitskyi/valuepicker/sample/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.sample 18 | 19 | import android.os.Bundle 20 | import android.view.LayoutInflater 21 | import android.view.View 22 | import android.view.ViewGroup 23 | import androidx.annotation.CallSuper 24 | import androidx.annotation.LayoutRes 25 | import androidx.fragment.app.Fragment 26 | import androidx.viewbinding.ViewBinding 27 | 28 | internal abstract class BaseFragment< 29 | VB : ViewBinding, 30 | >(@LayoutRes contentLayoutId: Int) : Fragment(contentLayoutId) { 31 | 32 | private var isViewCreated = false 33 | 34 | protected abstract val viewBinding: VB 35 | 36 | final override fun onCreateView( 37 | inflater: LayoutInflater, 38 | container: ViewGroup?, 39 | savedInstanceState: Bundle?, 40 | ): View? { 41 | // Prevent the view from recreation until onDestroy is called 42 | return if (isViewCreated) { 43 | viewBinding.root 44 | } else { 45 | super.onCreateView(inflater, container, savedInstanceState) 46 | } 47 | } 48 | 49 | final override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 50 | super.onViewCreated(view, savedInstanceState) 51 | 52 | val wasViewCreated = isViewCreated 53 | isViewCreated = true 54 | 55 | if (!wasViewCreated) { 56 | onInit() 57 | } 58 | } 59 | 60 | @CallSuper 61 | protected open fun onInit() { 62 | // Stub 63 | } 64 | 65 | override fun onDestroy() { 66 | super.onDestroy() 67 | 68 | isViewCreated = false 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_clothing_size_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 26 | 27 | 34 | 35 | 42 | 43 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /sample/src/main/res/navigation/root_graph.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 24 | 25 | 28 | 29 | 32 | 33 | 36 | 37 | 40 | 41 | 44 | 45 | 48 | 49 | 52 | 53 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /sample/src/main/java/com/paulrybitskyi/valuepicker/sample/valueeffects/RotationValueEffect.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.sample.valueeffects 18 | 19 | import android.view.View 20 | import androidx.recyclerview.widget.RecyclerView 21 | import com.paulrybitskyi.valuepicker.model.Orientation 22 | import com.paulrybitskyi.valuepicker.valueeffects.ValueEffect 23 | 24 | private const val PROPERTY_ROTATION_X_START_ANGLE = 90f 25 | private const val PROPERTY_ROTATION_Y_START_ANGLE = -90f 26 | 27 | internal class RotationValueEffect( 28 | private val property: Property, 29 | ) : ValueEffect { 30 | 31 | override fun applyEffect(child: View, recyclerView: RecyclerView, orientation: Orientation) { 32 | val rvCenter = recyclerView.calculateCenter(orientation) 33 | val childCenter = child.calculateChildCenter(orientation) 34 | val rvDistanceFrmChildCenter = (rvCenter - childCenter) 35 | val rotation = calculateChildRotation( 36 | rvDistanceFrmChildCenter, 37 | recyclerView.getDimension(orientation), 38 | ) 39 | 40 | when (property) { 41 | Property.ROTATION_X -> child.rotationX = rotation 42 | Property.ROTATION_Y -> child.rotationY = rotation 43 | } 44 | } 45 | 46 | private fun calculateChildRotation(rvDistanceFrmChildCenter: Float, rvDimension: Int): Float { 47 | return (property.starAngle * (rvDistanceFrmChildCenter / (rvDimension / 2f))) 48 | } 49 | 50 | private val Property.starAngle: Float 51 | get() = when (this) { 52 | Property.ROTATION_X -> PROPERTY_ROTATION_X_START_ANGLE 53 | Property.ROTATION_Y -> PROPERTY_ROTATION_Y_START_ANGLE 54 | } 55 | 56 | internal enum class Property { 57 | ROTATION_X, 58 | ROTATION_Y, 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /sample/src/main/java/com/paulrybitskyi/valuepicker/sample/dashboard/DashboardRecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.sample.dashboard 18 | 19 | import android.content.Context 20 | import android.view.LayoutInflater 21 | import android.view.View 22 | import android.view.ViewGroup 23 | import android.widget.TextView 24 | import androidx.recyclerview.widget.RecyclerView 25 | import com.paulrybitskyi.valuepicker.sample.R 26 | import com.paulrybitskyi.valuepicker.sample.dashboard.DashboardRecyclerViewAdapter.ViewHolder 27 | 28 | internal class DashboardRecyclerViewAdapter( 29 | context: Context, 30 | private val items: List, 31 | private val onItemClickListener: (DashboardDestination) -> Unit, 32 | ) : RecyclerView.Adapter() { 33 | 34 | private val layoutInflater = LayoutInflater.from(context) 35 | 36 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 37 | return ViewHolder( 38 | layoutInflater.inflate( 39 | R.layout.item_view_dashboard, 40 | parent, 41 | false, 42 | ), 43 | ) 44 | } 45 | 46 | override fun onBindViewHolder(holder: ViewHolder, position: Int) = with(holder) { 47 | val item = items[position] 48 | 49 | title.text = item.title 50 | description.text = item.description 51 | 52 | itemView.setOnClickListener { 53 | onItemClickListener(item) 54 | } 55 | } 56 | 57 | override fun getItemCount(): Int { 58 | return items.size 59 | } 60 | 61 | class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 62 | 63 | internal val title = itemView.findViewById(R.id.titleTv) 64 | internal val description = itemView.findViewById(R.id.descriptionTv) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /sample/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | androidApplication() 19 | kotlinAndroid() 20 | } 21 | 22 | android { 23 | compileSdk = appConfig.compileSdkVersion 24 | 25 | defaultConfig { 26 | applicationId = appConfig.applicationId 27 | namespace = appConfig.applicationId 28 | minSdk = appConfig.minSdkVersion 29 | targetSdk = appConfig.targetSdkVersion 30 | 31 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 32 | } 33 | 34 | buildTypes { 35 | getByName("release") { 36 | isMinifyEnabled = false 37 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 38 | } 39 | } 40 | 41 | buildFeatures { 42 | viewBinding = true 43 | } 44 | 45 | compileOptions { 46 | sourceCompatibility = appConfig.javaCompatibilityVersion 47 | targetCompatibility = appConfig.javaCompatibilityVersion 48 | } 49 | 50 | kotlinOptions { 51 | jvmTarget = appConfig.kotlinCompatibilityVersion.toString() 52 | } 53 | } 54 | 55 | dependencies { 56 | implementation(project(deps.local.valuePicker)) 57 | 58 | implementation(deps.appCompat) 59 | implementation(deps.navFragmentKtx) 60 | implementation(deps.navUiKtx) 61 | implementation(deps.constraintLayout) 62 | implementation(deps.commonsCore) 63 | implementation(deps.commonsKtx) 64 | 65 | testImplementation(deps.jUnit) 66 | androidTestImplementation(deps.jUnitExt) 67 | } 68 | 69 | val installGitHook by tasks.registering(Copy::class) { 70 | from(File(rootProject.rootDir, "hooks/pre-push")) 71 | into(File(rootProject.rootDir, ".git/hooks/")) 72 | // https://github.com/gradle/kotlin-dsl-samples/issues/1412 73 | fileMode = 0b111101101 // -rwxr-xr-x 74 | } 75 | 76 | tasks.getByPath(":sample:preBuild").dependsOn(installGitHook) 77 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /value-picker/src/main/java/com/paulrybitskyi/valuepicker/valueeffects/concrete/FadingValueEffect.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.valueeffects.concrete 18 | 19 | import android.view.View 20 | import androidx.recyclerview.widget.RecyclerView 21 | import com.paulrybitskyi.valuepicker.model.Orientation 22 | import com.paulrybitskyi.valuepicker.valueeffects.ValueEffect 23 | import kotlin.math.abs 24 | 25 | /** 26 | * An implementation of the value effect that modifies child views' 27 | * alpha property to create a fading effect. 28 | * 29 | * see [ValueEffect] 30 | */ 31 | class FadingValueEffect : ValueEffect { 32 | 33 | override fun applyEffect(child: View, recyclerView: RecyclerView, orientation: Orientation) { 34 | val rvCenter = recyclerView.calculateCenter(orientation) 35 | val childCenter = child.calculateChildCenter(orientation) 36 | val rvDistanceFrmChildCenter = abs(rvCenter - childCenter) 37 | val alpha = calculateChildAlpha(rvDistanceFrmChildCenter, recyclerView.getDimension(orientation)) 38 | 39 | child.alpha = alpha 40 | } 41 | 42 | private fun RecyclerView.calculateCenter(orientation: Orientation): Float { 43 | return (getDimension(orientation) / 2f) 44 | } 45 | 46 | private fun RecyclerView.getDimension(orientation: Orientation): Int { 47 | return when (orientation) { 48 | Orientation.VERTICAL -> height 49 | Orientation.HORIZONTAL -> width 50 | } 51 | } 52 | 53 | private fun View.calculateChildCenter(orientation: Orientation): Int { 54 | return when (orientation) { 55 | Orientation.VERTICAL -> ((height / 2) + top) 56 | Orientation.HORIZONTAL -> ((width / 2) + left) 57 | } 58 | } 59 | 60 | private fun calculateChildAlpha(rvDistanceFrmChildCenter: Float, rvDimension: Int): Float { 61 | return (1 - (rvDistanceFrmChildCenter / (rvDimension / 2f))) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /sample/src/main/java/com/paulrybitskyi/valuepicker/sample/dashboard/DashboardDestination.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.sample.dashboard 18 | 19 | import androidx.annotation.IdRes 20 | import com.paulrybitskyi.valuepicker.sample.R 21 | 22 | internal enum class DashboardDestination( 23 | val title: String, 24 | val description: String, 25 | @IdRes val destinationId: Int, 26 | ) { 27 | TIME_PICKER( 28 | title = "Time Picker", 29 | description = "Three vertical pickers for picking time.", 30 | destinationId = R.id.timePickerFragment, 31 | ), 32 | DATE_PICKER( 33 | title = "Date Picker", 34 | description = "Three vertical pickers with infinite scroll enabled for picking date.", 35 | destinationId = R.id.datePickerFragment, 36 | ), 37 | TEAM_PICKER( 38 | title = "Team Picker", 39 | description = "A vertical picker with a fixed item size, custom divider, " + 40 | "and text font for picking the NBA team.", 41 | destinationId = R.id.teamPickerFragment, 42 | ), 43 | MOVIE_FILTERING_PICKER( 44 | title = "Movie Filtering Picker", 45 | description = "Three vertical pickers each with a custom effect for picking a movie.", 46 | destinationId = R.id.movieFilteringPicker, 47 | ), 48 | RATING_PICKER( 49 | title = "Rating Picker", 50 | description = "A horizontal picker for picking a rating.", 51 | destinationId = R.id.ratingPickerFragment, 52 | ), 53 | CLOTHING_SIZE_PICKER( 54 | title = "Clothing Size Picker", 55 | description = "A horizontal picker with a fixed item size, custom divider, " + 56 | "and infinite scroll enabled for picking the clothing size.", 57 | destinationId = R.id.clothingSizePickerFragment, 58 | ), 59 | PERSON_INFO_PICKER( 60 | title = "Person Info Picker", 61 | description = "Three horizontal pickers each with a custom effect for picking person's information.", 62 | destinationId = R.id.personInfoPickerFragment, 63 | ), 64 | } 65 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/item_view_dashboard.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 27 | 28 | 40 | 41 | 53 | 54 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /sample/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 15dp 20 | 18sp 21 | 30dp 22 | 20dp 23 | 15sp 24 | 20dp 25 | 20dp 26 | 27 | 15dp 28 | 15sp 29 | 2dp 30 | 15dp 31 | 13sp 32 | 2dp 33 | 1dp 34 | 35 | 16sp 36 | 14sp 37 | 14sp 38 | 200dp 39 | 50dp 40 | 3dp 41 | 4dp 42 | 4dp 43 | 2dp 44 | 16sp 45 | 16sp 46 | 16sp 47 | 90dp 48 | 60dp 49 | 2dp 50 | 2dp 51 | 16sp 52 | 90dp 53 | 50dp 54 | 55 | 56 | -------------------------------------------------------------------------------- /value-picker/src/main/java/com/paulrybitskyi/valuepicker/decorators/concrete/HorizontalValuePickerItemDecorator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.decorators.concrete 18 | 19 | import android.graphics.Canvas 20 | import android.graphics.drawable.Drawable 21 | import androidx.recyclerview.widget.RecyclerView 22 | import com.paulrybitskyi.commons.ktx.applyBounds 23 | import com.paulrybitskyi.valuepicker.decorators.ValuePickerItemDecorator 24 | import com.paulrybitskyi.valuepicker.model.ValueItemConfig 25 | import kotlin.math.ceil 26 | import kotlin.math.floor 27 | 28 | internal class HorizontalValuePickerItemDecorator( 29 | maxVisibleItems: Int, 30 | dividerDrawable: Drawable, 31 | valueItemConfigProvider: () -> ValueItemConfig, 32 | ) : ValuePickerItemDecorator(maxVisibleItems, dividerDrawable, valueItemConfigProvider) { 33 | 34 | override fun drawDividers(canvas: Canvas, parent: RecyclerView) = with(canvas) { 35 | drawLeftDivider(parent) 36 | drawRightDivider(parent) 37 | } 38 | 39 | private fun Canvas.drawLeftDivider(parent: RecyclerView) { 40 | val config = valueItemConfigProvider() 41 | val indexOfValueBeforeCenter = floor(maxVisibleItems / 2f).toInt() 42 | val drawableLeftBound = (config.size.width * indexOfValueBeforeCenter) 43 | val drawableRightBound = (drawableLeftBound + dividerDrawable.intrinsicWidth) 44 | 45 | dividerDrawable.applyBounds( 46 | left = drawableLeftBound, 47 | top = 0, 48 | right = drawableRightBound, 49 | bottom = parent.height, 50 | ) 51 | dividerDrawable.draw(this) 52 | } 53 | 54 | private fun Canvas.drawRightDivider(parent: RecyclerView) { 55 | val config = valueItemConfigProvider() 56 | val indexOfCenterValue = ceil(maxVisibleItems / 2f).toInt() 57 | val drawableRightBound = (config.size.width * indexOfCenterValue) 58 | val drawableLeftBound = (drawableRightBound - dividerDrawable.intrinsicWidth) 59 | 60 | dividerDrawable.applyBounds( 61 | left = drawableLeftBound, 62 | top = 0, 63 | right = drawableRightBound, 64 | bottom = parent.height, 65 | ) 66 | dividerDrawable.draw(this) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /value-picker/src/main/java/com/paulrybitskyi/valuepicker/decorators/concrete/VerticalValuePickerItemDecorator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.decorators.concrete 18 | 19 | import android.graphics.Canvas 20 | import android.graphics.drawable.Drawable 21 | import androidx.recyclerview.widget.RecyclerView 22 | import com.paulrybitskyi.commons.ktx.applyBounds 23 | import com.paulrybitskyi.valuepicker.decorators.ValuePickerItemDecorator 24 | import com.paulrybitskyi.valuepicker.model.ValueItemConfig 25 | import kotlin.math.ceil 26 | import kotlin.math.floor 27 | 28 | internal class VerticalValuePickerItemDecorator( 29 | maxVisibleItems: Int, 30 | dividerDrawable: Drawable, 31 | valueItemConfigProvider: () -> ValueItemConfig, 32 | ) : ValuePickerItemDecorator(maxVisibleItems, dividerDrawable, valueItemConfigProvider) { 33 | 34 | override fun drawDividers(canvas: Canvas, parent: RecyclerView) = with(canvas) { 35 | drawTopDivider(parent) 36 | drawBottomDivider(parent) 37 | } 38 | 39 | private fun Canvas.drawTopDivider(parent: RecyclerView) { 40 | val config = valueItemConfigProvider() 41 | val indexOfValueBeforeCenter = floor(maxVisibleItems / 2f).toInt() 42 | val drawableTopBound = (config.size.height * indexOfValueBeforeCenter) 43 | val drawableBottomBound = (drawableTopBound + dividerDrawable.intrinsicHeight) 44 | 45 | dividerDrawable.applyBounds( 46 | left = 0, 47 | top = drawableTopBound, 48 | right = parent.width, 49 | bottom = drawableBottomBound, 50 | ) 51 | dividerDrawable.draw(this) 52 | } 53 | 54 | private fun Canvas.drawBottomDivider(parent: RecyclerView) { 55 | val config = valueItemConfigProvider() 56 | val indexOfCenterValue = ceil(maxVisibleItems / 2f).toInt() 57 | val drawableBottomBound = (config.size.height * indexOfCenterValue) 58 | val drawableTopBound = (drawableBottomBound - dividerDrawable.intrinsicHeight) 59 | 60 | dividerDrawable.applyBounds( 61 | left = 0, 62 | top = drawableTopBound, 63 | right = parent.width, 64 | bottom = drawableBottomBound, 65 | ) 66 | dividerDrawable.draw(this) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /sample/src/main/java/com/paulrybitskyi/valuepicker/sample/ratingpicker/RatingPickerFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.sample.ratingpicker 18 | 19 | import android.graphics.Typeface 20 | import com.paulrybitskyi.commons.ktx.getColor 21 | import com.paulrybitskyi.commons.ktx.getDimension 22 | import com.paulrybitskyi.commons.utils.viewBinding 23 | import com.paulrybitskyi.valuepicker.ValuePickerView 24 | import com.paulrybitskyi.valuepicker.model.Item 25 | import com.paulrybitskyi.valuepicker.model.Orientation 26 | import com.paulrybitskyi.valuepicker.model.PickerItem 27 | import com.paulrybitskyi.valuepicker.sample.BaseFragment 28 | import com.paulrybitskyi.valuepicker.sample.R 29 | import com.paulrybitskyi.valuepicker.sample.databinding.FragmentRatingPickerBinding 30 | import com.paulrybitskyi.valuepicker.sample.ratingpicker.model.Rating 31 | 32 | @Suppress("MagicNumber") 33 | internal class RatingPickerFragment : BaseFragment< 34 | FragmentRatingPickerBinding, 35 | >(R.layout.fragment_rating_picker) { 36 | 37 | override val viewBinding by viewBinding(FragmentRatingPickerBinding::bind) 38 | 39 | override fun onInit() { 40 | super.onInit() 41 | 42 | initRatingPicker() 43 | } 44 | 45 | private fun initRatingPicker() = with(viewBinding.ratingPicker) { 46 | areDividersEnabled = true 47 | isInfiniteScrollEnabled = false 48 | maxVisibleItems = 3 49 | textSize = getDimension(R.dimen.rating_picker_text_size) 50 | textColor = getColor(R.color.colorAccent) 51 | dividerColor = getColor(R.color.colorAccent) 52 | textTypeface = Typeface.DEFAULT_BOLD 53 | orientation = Orientation.HORIZONTAL 54 | onItemSelectedListener = ValuePickerView.OnItemSelectedListener { 55 | viewBinding.ratingTv.text = (it.payload as Rating).title 56 | } 57 | 58 | val ratingPickerItems = generateRatingPickerItems() 59 | items = ratingPickerItems 60 | setSelectedItem(ratingPickerItems[2]) 61 | } 62 | 63 | private fun generateRatingPickerItems(): List { 64 | return Rating.entries.map { 65 | PickerItem( 66 | id = it.ordinal, 67 | title = it.number.toString(), 68 | payload = it, 69 | ) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /value-picker/src/main/java/com/paulrybitskyi/valuepicker/model/Size.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @file:JvmName("SizeUtils") 18 | 19 | package com.paulrybitskyi.valuepicker.model 20 | 21 | import androidx.annotation.Px 22 | 23 | private const val DIMENSION_NOT_SET = -1 24 | 25 | /** 26 | * A size of the item inside the value picker. 27 | */ 28 | class Size internal constructor( 29 | val width: Int, 30 | val height: Int, 31 | ) { 32 | 33 | companion object { 34 | 35 | /** 36 | * Creates an item size with a specified fixed width. Height will be 37 | * calculated based on the maximum height of the item text. 38 | * 39 | * @param width The width of the item in pixels 40 | * 41 | * @return The size with a fixed width and default height 42 | */ 43 | @JvmStatic 44 | fun withFixedWidth(@Px width: Int): Size { 45 | return Size( 46 | width = width, 47 | height = DIMENSION_NOT_SET, 48 | ) 49 | } 50 | 51 | /** 52 | * Creates an item size with a specified fixed height. Width will be 53 | * calculated based on the maximum width of the item text. 54 | * 55 | * @param height The height of the item in pixels 56 | * 57 | * @return The size with a fixed height and default width 58 | */ 59 | @JvmStatic 60 | fun withFixedHeight(@Px height: Int): Size { 61 | return Size( 62 | width = DIMENSION_NOT_SET, 63 | height = height, 64 | ) 65 | } 66 | 67 | /** 68 | * Creates an item size with a specified fixed width and height. 69 | * 70 | * @param width The width of the item in pixels 71 | * @param height The height of the item in pixels 72 | * 73 | * @return The size with a fixed width and height 74 | */ 75 | @JvmStatic 76 | fun withFixedSize(@Px width: Int, @Px height: Int): Size { 77 | return Size( 78 | width = width, 79 | height = height, 80 | ) 81 | } 82 | } 83 | 84 | internal val hasWidth: Boolean 85 | get() = (width != DIMENSION_NOT_SET) 86 | 87 | internal val hasHeight: Boolean 88 | get() = (height != DIMENSION_NOT_SET) 89 | 90 | internal val hasBothDimensions: Boolean 91 | get() = (hasWidth && hasHeight) 92 | } 93 | 94 | internal fun sizeOf(width: Int, height: Int): Size { 95 | return Size(width, height) 96 | } 97 | -------------------------------------------------------------------------------- /sample/src/main/java/com/paulrybitskyi/valuepicker/sample/teampicker/TeamPickerFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.sample.teampicker 18 | 19 | import android.graphics.Typeface 20 | import androidx.core.content.res.ResourcesCompat 21 | import com.paulrybitskyi.commons.ktx.getColor 22 | import com.paulrybitskyi.commons.ktx.getDimension 23 | import com.paulrybitskyi.commons.ktx.getDimensionPixelSize 24 | import com.paulrybitskyi.commons.ktx.getDrawable 25 | import com.paulrybitskyi.commons.utils.viewBinding 26 | import com.paulrybitskyi.valuepicker.ValuePickerView 27 | import com.paulrybitskyi.valuepicker.model.Item 28 | import com.paulrybitskyi.valuepicker.model.Orientation 29 | import com.paulrybitskyi.valuepicker.model.PickerItem 30 | import com.paulrybitskyi.valuepicker.model.Size 31 | import com.paulrybitskyi.valuepicker.sample.BaseFragment 32 | import com.paulrybitskyi.valuepicker.sample.R 33 | import com.paulrybitskyi.valuepicker.sample.databinding.FragmentTeamPickerBinding 34 | import com.paulrybitskyi.valuepicker.sample.teampicker.model.Team 35 | 36 | @Suppress("MagicNumber") 37 | internal class TeamPickerFragment : BaseFragment< 38 | FragmentTeamPickerBinding, 39 | >(R.layout.fragment_team_picker) { 40 | 41 | override val viewBinding by viewBinding(FragmentTeamPickerBinding::bind) 42 | 43 | override fun onInit() { 44 | super.onInit() 45 | 46 | initTeamPicker() 47 | } 48 | 49 | private fun initTeamPicker() = with(viewBinding.teamPicker) { 50 | areDividersEnabled = true 51 | isInfiniteScrollEnabled = false 52 | maxVisibleItems = 5 53 | textSize = getDimension(R.dimen.team_picker_text_size) 54 | textColor = getColor(R.color.colorAccent) 55 | textTypeface = (ResourcesCompat.getFont(context, R.font.ubuntu_mono_bold) ?: Typeface.SANS_SERIF) 56 | dividerDrawable = getDrawable(R.drawable.team_picker_divider) 57 | fixedItemSize = Size.withFixedSize( 58 | width = getDimensionPixelSize(R.dimen.team_picker_item_width), 59 | height = getDimensionPixelSize(R.dimen.team_picker_item_height), 60 | ) 61 | orientation = Orientation.VERTICAL 62 | onItemSelectedListener = ValuePickerView.OnItemSelectedListener { 63 | viewBinding.teamTv.text = (it.payload as Team).longName 64 | } 65 | 66 | val teamPickerItems = generateTeamPickerItems() 67 | items = teamPickerItems 68 | setSelectedItem(teamPickerItems[2]) 69 | } 70 | 71 | private fun generateTeamPickerItems(): List { 72 | return Team.entries.map { 73 | PickerItem( 74 | id = it.ordinal, 75 | title = it.longName, 76 | payload = it, 77 | ) 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /sample/src/main/java/com/paulrybitskyi/valuepicker/sample/clothingsizepicker/ClothingSizePickerFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.sample.clothingsizepicker 18 | 19 | import android.graphics.Typeface 20 | import com.paulrybitskyi.commons.ktx.getColor 21 | import com.paulrybitskyi.commons.ktx.getDimension 22 | import com.paulrybitskyi.commons.ktx.getDimensionPixelSize 23 | import com.paulrybitskyi.commons.ktx.getDrawable 24 | import com.paulrybitskyi.commons.utils.viewBinding 25 | import com.paulrybitskyi.valuepicker.ValuePickerView 26 | import com.paulrybitskyi.valuepicker.model.Item 27 | import com.paulrybitskyi.valuepicker.model.Orientation 28 | import com.paulrybitskyi.valuepicker.model.PickerItem 29 | import com.paulrybitskyi.valuepicker.model.Size 30 | import com.paulrybitskyi.valuepicker.sample.BaseFragment 31 | import com.paulrybitskyi.valuepicker.sample.R 32 | import com.paulrybitskyi.valuepicker.sample.clothingsizepicker.model.ClothingSize 33 | import com.paulrybitskyi.valuepicker.sample.databinding.FragmentClothingSizePickerBinding 34 | 35 | @Suppress("MagicNumber") 36 | internal class ClothingSizePickerFragment : BaseFragment< 37 | FragmentClothingSizePickerBinding, 38 | >(R.layout.fragment_clothing_size_picker) { 39 | 40 | override val viewBinding by viewBinding(FragmentClothingSizePickerBinding::bind) 41 | 42 | override fun onInit() { 43 | super.onInit() 44 | 45 | initClothingSizePicker() 46 | } 47 | 48 | private fun initClothingSizePicker() = with(viewBinding.clothingSizePicker) { 49 | areDividersEnabled = true 50 | isInfiniteScrollEnabled = true 51 | maxVisibleItems = 3 52 | textSize = getDimension(R.dimen.clothing_size_picker_text_size) 53 | textColor = getColor(R.color.colorAccent) 54 | textTypeface = Typeface.DEFAULT_BOLD 55 | dividerDrawable = getDrawable(R.drawable.clothing_size_picker_divider) 56 | fixedItemSize = Size.withFixedSize( 57 | width = getDimensionPixelSize(R.dimen.clothing_size_picker_item_width), 58 | height = getDimensionPixelSize(R.dimen.clothing_size_picker_item_height), 59 | ) 60 | orientation = Orientation.HORIZONTAL 61 | onItemSelectedListener = ValuePickerView.OnItemSelectedListener { 62 | viewBinding.clothingSizeTv.text = it.title 63 | } 64 | 65 | val clothingSizePickerItems = generateClothingSizePickerItems() 66 | items = clothingSizePickerItems 67 | setSelectedItem(clothingSizePickerItems[2]) 68 | } 69 | 70 | private fun generateClothingSizePickerItems(): List { 71 | return ClothingSize.entries.map { 72 | PickerItem( 73 | id = it.ordinal, 74 | title = it.name, 75 | ) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /value-picker/src/main/java/com/paulrybitskyi/valuepicker/ValuePickerRecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker 18 | 19 | import android.content.Context 20 | import android.view.Gravity 21 | import android.view.View 22 | import android.view.ViewGroup 23 | import android.widget.TextView 24 | import androidx.appcompat.widget.AppCompatTextView 25 | import androidx.recyclerview.widget.RecyclerView 26 | import com.paulrybitskyi.commons.ktx.views.setTextSizeInPx 27 | import com.paulrybitskyi.commons.utils.observeChanges 28 | import com.paulrybitskyi.valuepicker.ValuePickerRecyclerViewAdapter.ViewHolder 29 | import com.paulrybitskyi.valuepicker.model.Item 30 | import com.paulrybitskyi.valuepicker.model.ValueItemConfig 31 | import com.paulrybitskyi.valuepicker.scrollerhelpers.ScrollerHelper 32 | 33 | internal class ValuePickerRecyclerViewAdapter( 34 | items: List, 35 | var valueItemConfig: ValueItemConfig, 36 | var scrollerHelper: ScrollerHelper, 37 | ) : RecyclerView.Adapter() { 38 | 39 | var items by observeChanges(items) { _, newItems -> 40 | scrollerHelper.dataSetItemCount = newItems.size 41 | notifyDataSetChanged() 42 | } 43 | 44 | var onItemClickListener: ((View) -> Unit)? = null 45 | 46 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 47 | return ViewHolder(createTextView(parent.context)) 48 | } 49 | 50 | private fun createTextView(context: Context): TextView { 51 | return AppCompatTextView(context).apply { 52 | layoutParams = RecyclerView.LayoutParams( 53 | valueItemConfig.size.width, 54 | valueItemConfig.size.height, 55 | ) 56 | gravity = Gravity.CENTER 57 | setTextColor(valueItemConfig.textColor) 58 | setTextSizeInPx(valueItemConfig.textSize) 59 | typeface = valueItemConfig.textTypeface 60 | } 61 | } 62 | 63 | override fun onBindViewHolder(holder: ViewHolder, adapterPosition: Int) = with(holder.valueTv) { 64 | text = items[scrollerHelper.calculateDataSetPosition(adapterPosition)].title 65 | setOnClickListener(onItemClickListener) 66 | } 67 | 68 | override fun getItemCount(): Int { 69 | return scrollerHelper.adapterItemCount 70 | } 71 | 72 | fun getItemAdapterPosition(item: Item): Int? { 73 | return items.indexOfFirst { it.id == item.id } 74 | .takeIf { it != -1 } 75 | ?.let { dataSetPosition -> 76 | scrollerHelper.calculateAdapterPosition(dataSetPosition) 77 | } 78 | } 79 | 80 | fun getItem(adapterPosition: Int): Item? { 81 | return items.getOrNull(scrollerHelper.calculateDataSetPosition(adapterPosition)) 82 | } 83 | 84 | class ViewHolder(val valueTv: TextView) : RecyclerView.ViewHolder(valueTv) 85 | } 86 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_date_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 26 | 27 | 34 | 35 | 43 | 44 | 53 | 54 | 61 | 62 | 72 | 73 | 84 | 85 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_time_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 26 | 27 | 34 | 35 | 43 | 44 | 53 | 54 | 61 | 62 | 72 | 73 | 84 | 85 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_person_info_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 26 | 27 | 34 | 35 | 43 | 44 | 53 | 54 | 61 | 62 | 72 | 73 | 84 | 85 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_movie_filtering_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 26 | 27 | 34 | 35 | 43 | 44 | 53 | 54 | 61 | 62 | 72 | 73 | 84 | 85 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /publishing.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply(plugin = PLUGIN_MAVEN_PUBLISH) 18 | apply(plugin = PLUGIN_SIGNING) 19 | apply(plugin = PLUGIN_DOKKA) 20 | 21 | project.group = publishingConfig.artifactGroupId 22 | project.version = publishingConfig.artifactVersion 23 | 24 | val sourcesJar by tasks.registering(Jar::class) { 25 | archiveClassifier.set("sources") 26 | 27 | if (project.plugins.hasPlugin(PLUGIN_ANDROID_LIBRARY)) { 28 | val libExt = checkNotNull(project.extensions.findByType(com.android.build.gradle.LibraryExtension::class.java)) 29 | val libMainSourceSet = libExt.sourceSets.getByName("main") 30 | 31 | from(libMainSourceSet.java.srcDirs) 32 | } else { 33 | val sourceSetExt = checkNotNull(project.extensions.findByType(SourceSetContainer::class.java)) 34 | val mainSourceSet = sourceSetExt.getByName("main") 35 | 36 | from(mainSourceSet.java.srcDirs) 37 | } 38 | } 39 | 40 | val javadocJar by tasks.registering(Jar::class) { 41 | archiveClassifier.set("javadoc") 42 | 43 | val dokkaJavadocTask = tasks.getByName("dokkaJavadoc") 44 | 45 | from(dokkaJavadocTask) 46 | dependsOn(dokkaJavadocTask) 47 | } 48 | 49 | afterEvaluate { 50 | configure { 51 | publications { 52 | create(publishingConfig.mavenPublicationName) { 53 | groupId = publishingConfig.artifactGroupId 54 | artifactId = publishingConfig.artifactName 55 | version = publishingConfig.artifactVersion 56 | 57 | if (project.plugins.hasPlugin(PLUGIN_ANDROID_LIBRARY)) { 58 | from(components["release"]) 59 | } else { 60 | from(components["java"]) 61 | } 62 | 63 | artifact(sourcesJar.get()) 64 | artifact(javadocJar.get()) 65 | 66 | pom { 67 | name.set(publishingConfig.artifactName) 68 | description.set(publishingConfig.artifactDescription) 69 | url.set(publishingConfig.artifactWebsite) 70 | 71 | licenses { 72 | license { 73 | name.set(publishingConfig.licenseName) 74 | url.set(publishingConfig.licenseUrl) 75 | } 76 | } 77 | 78 | developers { 79 | developer { 80 | id.set(publishingConfig.developerId) 81 | name.set(publishingConfig.developerName) 82 | email.set(publishingConfig.developerEmail) 83 | } 84 | } 85 | 86 | scm { 87 | connection.set(publishingConfig.gitUrl) 88 | developerConnection.set(publishingConfig.gitUrl) 89 | url.set(publishingConfig.siteUrl) 90 | } 91 | } 92 | } 93 | } 94 | 95 | repositories { 96 | maven { 97 | name = publishingConfig.hostRepoName 98 | url = uri(publishingConfig.hostRepoUrl) 99 | 100 | credentials { 101 | username = property("SONATYPE_NEXUS_USERNAME", System.getenv("SONATYPE_NEXUS_USERNAME")) 102 | password = property("SONATYPE_NEXUS_PASSWORD", System.getenv("SONATYPE_NEXUS_PASSWORD")) 103 | } 104 | } 105 | } 106 | } 107 | 108 | configure { 109 | val pubExt = checkNotNull(extensions.findByType(PublishingExtension::class.java)) 110 | val publication = pubExt.publications[publishingConfig.mavenPublicationName] 111 | 112 | sign(publication) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /value-picker/src/main/java/com/paulrybitskyi/valuepicker/layoutmanager/ValuePickerLayoutManager.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.layoutmanager 18 | 19 | import android.view.View 20 | import androidx.recyclerview.widget.LinearLayoutManager 21 | import androidx.recyclerview.widget.RecyclerView 22 | import com.paulrybitskyi.valuepicker.model.Orientation 23 | import com.paulrybitskyi.valuepicker.model.rvOrientation 24 | import com.paulrybitskyi.valuepicker.valueeffects.ValueEffect 25 | import kotlin.math.abs 26 | 27 | internal class ValuePickerLayoutManager( 28 | private val recyclerView: RecyclerView, 29 | private val orientation: Orientation, 30 | private val valueItemEffect: ValueEffect, 31 | ) : LinearLayoutManager( 32 | recyclerView.context, 33 | orientation.rvOrientation, 34 | false, 35 | ) { 36 | 37 | private val dimension: Int 38 | get() = when (orientation) { 39 | Orientation.HORIZONTAL -> width 40 | Orientation.VERTICAL -> height 41 | } 42 | 43 | var onViewSelectedListener: ((View) -> Unit)? = null 44 | 45 | override fun onLayoutChildren( 46 | recycler: RecyclerView.Recycler, 47 | state: RecyclerView.State, 48 | ) { 49 | super.onLayoutChildren(recycler, state) 50 | 51 | applyEffectToChildren() 52 | } 53 | 54 | private fun applyEffectToChildren() { 55 | for (i in 0 until childCount) { 56 | getChildAt(i)?.also { child -> 57 | valueItemEffect.applyEffect(child, recyclerView, orientation) 58 | } 59 | } 60 | } 61 | 62 | override fun scrollVerticallyBy( 63 | dy: Int, 64 | recycler: RecyclerView.Recycler, 65 | state: RecyclerView.State, 66 | ): Int { 67 | return super.scrollVerticallyBy(dy, recycler, state) 68 | .also { applyEffectToChildren() } 69 | } 70 | 71 | override fun scrollHorizontallyBy( 72 | dx: Int, 73 | recycler: RecyclerView.Recycler, 74 | state: RecyclerView.State, 75 | ): Int { 76 | return super.scrollHorizontallyBy(dx, recycler, state) 77 | .also { applyEffectToChildren() } 78 | } 79 | 80 | override fun onScrollStateChanged(state: Int) { 81 | reportViewSelectionEvent(state) 82 | } 83 | 84 | private fun reportViewSelectionEvent(state: Int) { 85 | if (state != RecyclerView.SCROLL_STATE_IDLE) return 86 | 87 | val recyclerViewCenter = calculateRecyclerViewCenter() 88 | var minDistance = dimension 89 | var selectedChild: View? = null 90 | 91 | for (i in 0 until childCount) { 92 | val child = (getChildAt(i) ?: continue) 93 | val childCenter = calculateChildCenter(child) 94 | val childDistanceFromRvCenter = abs(childCenter - recyclerViewCenter) 95 | 96 | if (childDistanceFromRvCenter < minDistance) { 97 | minDistance = childDistanceFromRvCenter 98 | selectedChild = child 99 | } 100 | } 101 | 102 | selectedChild?.let { onViewSelectedListener?.invoke(it) } 103 | } 104 | 105 | private fun calculateRecyclerViewCenter(): Int { 106 | return (dimension / 2) 107 | } 108 | 109 | private fun calculateChildCenter(child: View): Int { 110 | return when (orientation) { 111 | Orientation.VERTICAL -> ((child.height / 2) + child.top) 112 | Orientation.HORIZONTAL -> ((child.width / 2) + child.left) 113 | } 114 | } 115 | 116 | override fun smoothScrollToPosition( 117 | recyclerView: RecyclerView, 118 | state: RecyclerView.State, 119 | position: Int, 120 | ) { 121 | attachCustomSmoothScroller(recyclerView, position) 122 | } 123 | 124 | private fun attachCustomSmoothScroller( 125 | recyclerView: RecyclerView, 126 | position: Int, 127 | ) { 128 | ValuePickerSmoothScroller(recyclerView.context) 129 | .apply { targetPosition = position } 130 | .also(::startSmoothScroll) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /sample/src/main/java/com/paulrybitskyi/valuepicker/sample/datepicker/DatePickerFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.sample.datepicker 18 | 19 | import android.graphics.Typeface 20 | import com.paulrybitskyi.commons.ktx.getColor 21 | import com.paulrybitskyi.commons.ktx.getDimension 22 | import com.paulrybitskyi.commons.utils.viewBinding 23 | import com.paulrybitskyi.valuepicker.ValuePickerView 24 | import com.paulrybitskyi.valuepicker.model.Item 25 | import com.paulrybitskyi.valuepicker.model.Orientation 26 | import com.paulrybitskyi.valuepicker.model.PickerItem 27 | import com.paulrybitskyi.valuepicker.sample.BaseFragment 28 | import com.paulrybitskyi.valuepicker.sample.R 29 | import com.paulrybitskyi.valuepicker.sample.databinding.FragmentDatePickerBinding 30 | import com.paulrybitskyi.valuepicker.sample.datepicker.model.Month 31 | 32 | @Suppress("MagicNumber") 33 | internal class DatePickerFragment : BaseFragment< 34 | FragmentDatePickerBinding, 35 | >(R.layout.fragment_date_picker) { 36 | 37 | override val viewBinding by viewBinding(FragmentDatePickerBinding::bind) 38 | 39 | override fun onInit() { 40 | super.onInit() 41 | 42 | initPickers() 43 | } 44 | 45 | private fun initPickers() { 46 | initMonthPicker() 47 | initDayPicker() 48 | initYearPicker() 49 | } 50 | 51 | private fun ValuePickerView.initDatePicker() { 52 | areDividersEnabled = true 53 | isInfiniteScrollEnabled = true 54 | maxVisibleItems = 5 55 | textSize = getDimension(R.dimen.date_picker_text_size) 56 | textColor = getColor(R.color.colorAccent) 57 | dividerColor = getColor(R.color.colorAccent) 58 | textTypeface = Typeface.SANS_SERIF 59 | orientation = Orientation.VERTICAL 60 | } 61 | 62 | private fun initMonthPicker() = with(viewBinding.monthPicker) { 63 | initDatePicker() 64 | onItemSelectedListener = ValuePickerView.OnItemSelectedListener { 65 | viewBinding.monthTv.text = "Month: ${(it.payload as Month).shortName}" 66 | } 67 | 68 | val monthPickerItems = generateMonthPickerItems(longMonthNames = true) 69 | items = monthPickerItems 70 | setSelectedItem(monthPickerItems[7]) 71 | } 72 | 73 | private fun generateMonthPickerItems(longMonthNames: Boolean): List { 74 | return Month.entries.map { 75 | PickerItem( 76 | id = it.ordinal, 77 | title = if (longMonthNames) it.longName else it.shortName, 78 | payload = it, 79 | ) 80 | } 81 | } 82 | 83 | private fun initDayPicker() = with(viewBinding.dayPicker) { 84 | initDatePicker() 85 | onItemSelectedListener = ValuePickerView.OnItemSelectedListener { 86 | viewBinding.dayTv.text = "Day: ${it.title}" 87 | } 88 | 89 | val dayPickerItems = generateDayPickerItems() 90 | items = dayPickerItems 91 | setSelectedItem(dayPickerItems[8]) 92 | } 93 | 94 | private fun generateDayPickerItems(): List { 95 | return buildList { 96 | for (day in 1..31) { 97 | add( 98 | PickerItem( 99 | id = day, 100 | title = if (day < 10) "0$day" else day.toString(), 101 | ), 102 | ) 103 | } 104 | } 105 | } 106 | 107 | private fun initYearPicker() = with(viewBinding.yearPicker) { 108 | initDatePicker() 109 | onItemSelectedListener = ValuePickerView.OnItemSelectedListener { 110 | viewBinding.yearTv.text = "Year: ${it.title}" 111 | } 112 | 113 | val yearPickerItems = generateYearPickerItems() 114 | items = yearPickerItems 115 | setSelectedItem(yearPickerItems[0]) 116 | } 117 | 118 | private fun generateYearPickerItems(): List { 119 | return buildList { 120 | for (year in 2020..2030) { 121 | add( 122 | PickerItem( 123 | id = year, 124 | title = year.toString(), 125 | ), 126 | ) 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /sample/src/main/java/com/paulrybitskyi/valuepicker/sample/timepicker/TimePickerFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.sample.timepicker 18 | 19 | import android.graphics.Typeface 20 | import com.paulrybitskyi.commons.ktx.getColor 21 | import com.paulrybitskyi.commons.ktx.getDimension 22 | import com.paulrybitskyi.commons.utils.viewBinding 23 | import com.paulrybitskyi.valuepicker.ValuePickerView 24 | import com.paulrybitskyi.valuepicker.model.Item 25 | import com.paulrybitskyi.valuepicker.model.Orientation 26 | import com.paulrybitskyi.valuepicker.model.PickerItem 27 | import com.paulrybitskyi.valuepicker.sample.BaseFragment 28 | import com.paulrybitskyi.valuepicker.sample.R 29 | import com.paulrybitskyi.valuepicker.sample.databinding.FragmentTimePickerBinding 30 | 31 | @Suppress("MagicNumber") 32 | internal class TimePickerFragment : BaseFragment< 33 | FragmentTimePickerBinding, 34 | >(R.layout.fragment_time_picker) { 35 | 36 | override val viewBinding by viewBinding(FragmentTimePickerBinding::bind) 37 | 38 | override fun onInit() { 39 | super.onInit() 40 | 41 | initPickers() 42 | } 43 | 44 | private fun initPickers() { 45 | initHourPicker() 46 | initMinutePicker() 47 | initPeriodPicker() 48 | } 49 | 50 | private fun ValuePickerView.initPicker() { 51 | areDividersEnabled = true 52 | isInfiniteScrollEnabled = false 53 | maxVisibleItems = 3 54 | textColor = getColor(R.color.colorAccent) 55 | dividerColor = getColor(R.color.colorAccent) 56 | textSize = getDimension(R.dimen.time_picker_text_size) 57 | textTypeface = Typeface.DEFAULT_BOLD 58 | orientation = Orientation.VERTICAL 59 | } 60 | 61 | private fun initHourPicker() = with(viewBinding.hourPicker) { 62 | initPicker() 63 | onItemSelectedListener = ValuePickerView.OnItemSelectedListener { 64 | viewBinding.hourTv.text = "Hour: ${it.title}" 65 | } 66 | 67 | val hourPickerItems = generateHourPickerItems() 68 | items = hourPickerItems 69 | setSelectedItem(hourPickerItems[10]) 70 | } 71 | 72 | private fun generateHourPickerItems(): List { 73 | return buildList { 74 | for (hour in 1..12) { 75 | add( 76 | PickerItem( 77 | id = hour, 78 | title = hour.toString(), 79 | ), 80 | ) 81 | } 82 | } 83 | } 84 | 85 | private fun initMinutePicker() = with(viewBinding.minutePicker) { 86 | initPicker() 87 | onItemSelectedListener = ValuePickerView.OnItemSelectedListener { 88 | viewBinding.minuteTv.text = "Minute: ${it.title}" 89 | } 90 | 91 | val minutePickerItems = generateMinutePickerItems() 92 | items = minutePickerItems 93 | setSelectedItem(minutePickerItems[30]) 94 | } 95 | 96 | private fun generateMinutePickerItems(): List { 97 | return buildList { 98 | for (minute in 0..59) { 99 | add( 100 | PickerItem( 101 | id = minute, 102 | title = if (minute < 10) "0$minute" else minute.toString(), 103 | ), 104 | ) 105 | } 106 | } 107 | } 108 | 109 | private fun initPeriodPicker() = with(viewBinding.periodPicker) { 110 | initPicker() 111 | onItemSelectedListener = ValuePickerView.OnItemSelectedListener { 112 | viewBinding.periodTv.text = "Period: ${it.title}" 113 | } 114 | 115 | val periodPickerItems = generatePeriodPickerItems() 116 | items = periodPickerItems 117 | setSelectedItem(periodPickerItems[1]) 118 | } 119 | 120 | private fun generatePeriodPickerItems(): List { 121 | return buildList { 122 | add( 123 | PickerItem( 124 | id = 1, 125 | title = "AM", 126 | ), 127 | ) 128 | add( 129 | PickerItem( 130 | id = 2, 131 | title = "PM", 132 | ), 133 | ) 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /value-picker-lint/src/main/java/com/paulrybitskyi/valuepicker/lint/NumberPickerUsageDetector.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.lint 18 | 19 | import com.android.resources.ResourceFolderType 20 | import com.android.tools.lint.detector.api.Category 21 | import com.android.tools.lint.detector.api.Context 22 | import com.android.tools.lint.detector.api.Detector 23 | import com.android.tools.lint.detector.api.Implementation 24 | import com.android.tools.lint.detector.api.Issue 25 | import com.android.tools.lint.detector.api.JavaContext 26 | import com.android.tools.lint.detector.api.LintFix 27 | import com.android.tools.lint.detector.api.Scope 28 | import com.android.tools.lint.detector.api.Severity 29 | import com.android.tools.lint.detector.api.SourceCodeScanner 30 | import com.android.tools.lint.detector.api.XmlContext 31 | import com.android.tools.lint.detector.api.XmlScanner 32 | import com.intellij.psi.PsiMethod 33 | import org.jetbrains.uast.UCallExpression 34 | import org.jetbrains.uast.getQualifiedName 35 | import org.w3c.dom.Element 36 | import java.util.EnumSet 37 | 38 | internal class NumberPickerUsageDetector : Detector(), XmlScanner, SourceCodeScanner { 39 | 40 | companion object { 41 | 42 | @JvmField 43 | val ISSUE = Issue.create( 44 | id = "NumberPickerUsage", 45 | briefDescription = NUMBER_PICKER_USAGE_BRIEF_DESC, 46 | explanation = """ 47 | Since `ValuePickerView` is included in the project, it is likely 48 | that **it** should be used instead of `NumberPicker`. 49 | """, 50 | category = Category.CORRECTNESS, 51 | priority = 5, 52 | severity = Severity.WARNING, 53 | implementation = Implementation( 54 | NumberPickerUsageDetector::class.java, 55 | EnumSet.of(Scope.RESOURCE_FILE, Scope.JAVA_FILE), 56 | Scope.RESOURCE_FILE_SCOPE, 57 | Scope.JAVA_FILE_SCOPE, 58 | ), 59 | ) 60 | } 61 | 62 | override fun appliesTo(folderType: ResourceFolderType): Boolean { 63 | return (folderType == ResourceFolderType.LAYOUT) 64 | } 65 | 66 | override fun run(context: Context) { 67 | // The infrastructure should never call this method on an xml detector since 68 | // it will run the various visitors instead 69 | 70 | assert(false) 71 | } 72 | 73 | override fun getApplicableElements(): Collection { 74 | return listOf(NUMBER_PICKER_SIMPLE_NAME, NUMBER_PICKER_CANONICAL_NAME) 75 | } 76 | 77 | // The below handles usages in XML Layout Files 78 | 79 | override fun visitElement(context: XmlContext, element: Element) { 80 | context.report( 81 | issue = ISSUE, 82 | location = context.getElementLocation(element), 83 | message = NUMBER_PICKER_USAGE_BRIEF_DESC, 84 | quickfixData = computeQuickFixForXmlUsage(element), 85 | ) 86 | } 87 | 88 | private fun computeQuickFixForXmlUsage(element: Element): LintFix { 89 | return fix() 90 | .replace().text(element.tagName) 91 | .with(VALUE_PICKER_VIEW_CANONICAL_NAME) 92 | .build() 93 | } 94 | 95 | // The below handles usages in Java/Kotlin Source Files 96 | 97 | override fun getApplicableConstructorTypes(): List { 98 | return listOf(NUMBER_PICKER_CANONICAL_NAME) 99 | } 100 | 101 | override fun visitConstructor( 102 | context: JavaContext, 103 | node: UCallExpression, 104 | constructor: PsiMethod, 105 | ) { 106 | context.report( 107 | issue = ISSUE, 108 | location = context.getLocation(node), 109 | message = NUMBER_PICKER_USAGE_BRIEF_DESC, 110 | quickfixData = computeQuickFixForSourceUsage(node), 111 | ) 112 | } 113 | 114 | private fun computeQuickFixForSourceUsage(node: UCallExpression): LintFix? { 115 | // ValuePickerView does not support 4 arg constructor 116 | @Suppress("MagicNumber") 117 | if (node.valueArgumentCount == 4) return null 118 | 119 | val textToReplace = if (node.asSourceString().contains(NUMBER_PICKER_CANONICAL_NAME)) { 120 | node.classReference.getQualifiedName() 121 | } else { 122 | node.classReference!!.resolvedName 123 | } 124 | 125 | return fix() 126 | .replace().text(textToReplace) 127 | .with(VALUE_PICKER_VIEW_CANONICAL_NAME) 128 | .shortenNames() 129 | .reformat(true) 130 | .build() 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /sample/src/main/java/com/paulrybitskyi/valuepicker/sample/moviefilteringpicker/MovieFilteringPickerFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.sample.moviefilteringpicker 18 | 19 | import android.graphics.Typeface 20 | import com.paulrybitskyi.commons.ktx.getColor 21 | import com.paulrybitskyi.commons.ktx.getDimension 22 | import com.paulrybitskyi.commons.utils.viewBinding 23 | import com.paulrybitskyi.valuepicker.ValuePickerView 24 | import com.paulrybitskyi.valuepicker.model.Item 25 | import com.paulrybitskyi.valuepicker.model.Orientation 26 | import com.paulrybitskyi.valuepicker.model.PickerItem 27 | import com.paulrybitskyi.valuepicker.sample.BaseFragment 28 | import com.paulrybitskyi.valuepicker.sample.R 29 | import com.paulrybitskyi.valuepicker.sample.databinding.FragmentMovieFilteringPickerBinding 30 | import com.paulrybitskyi.valuepicker.sample.moviefilteringpicker.model.Genre 31 | import com.paulrybitskyi.valuepicker.sample.moviefilteringpicker.model.StreamingService 32 | import com.paulrybitskyi.valuepicker.sample.valueeffects.CompositeValueEffect 33 | import com.paulrybitskyi.valuepicker.sample.valueeffects.RotationValueEffect 34 | import com.paulrybitskyi.valuepicker.valueeffects.concrete.FadingValueEffect 35 | 36 | @Suppress("MagicNumber") 37 | internal class MovieFilteringPickerFragment : BaseFragment< 38 | FragmentMovieFilteringPickerBinding, 39 | >(R.layout.fragment_movie_filtering_picker) { 40 | 41 | override val viewBinding by viewBinding(FragmentMovieFilteringPickerBinding::bind) 42 | 43 | override fun onInit() { 44 | super.onInit() 45 | 46 | initPickers() 47 | } 48 | 49 | private fun initPickers() { 50 | initGenrePicker() 51 | initYearPicker() 52 | initServicePicker() 53 | } 54 | 55 | private fun ValuePickerView.initMovieFilteringPicker() { 56 | areDividersEnabled = true 57 | isInfiniteScrollEnabled = true 58 | maxVisibleItems = 5 59 | textSize = getDimension(R.dimen.movie_filtering_picker_text_size) 60 | textColor = getColor(R.color.colorAccent) 61 | dividerColor = getColor(R.color.colorAccent) 62 | textTypeface = Typeface.SANS_SERIF 63 | orientation = Orientation.VERTICAL 64 | } 65 | 66 | private fun initGenrePicker() = with(viewBinding.genrePicker) { 67 | initMovieFilteringPicker() 68 | valueEffect = RotationValueEffect(RotationValueEffect.Property.ROTATION_X) 69 | onItemSelectedListener = ValuePickerView.OnItemSelectedListener { 70 | viewBinding.genreTv.text = "Genre: ${it.title}" 71 | } 72 | 73 | val genrePickerItems = generateGenrePickerItems() 74 | items = genrePickerItems 75 | setSelectedItem(genrePickerItems[3]) 76 | } 77 | 78 | private fun generateGenrePickerItems(): List { 79 | return Genre.entries.map { 80 | PickerItem( 81 | id = it.ordinal, 82 | title = it.title, 83 | payload = it, 84 | ) 85 | } 86 | } 87 | 88 | private fun initYearPicker() = with(viewBinding.yearPicker) { 89 | initMovieFilteringPicker() 90 | valueEffect = RotationValueEffect(RotationValueEffect.Property.ROTATION_Y) 91 | onItemSelectedListener = ValuePickerView.OnItemSelectedListener { 92 | viewBinding.yearTv.text = "Year: ${it.title}" 93 | } 94 | 95 | val yearPickerItems = generateYearPickerItems() 96 | items = yearPickerItems 97 | setSelectedItem(yearPickerItems.last()) 98 | } 99 | 100 | private fun generateYearPickerItems(): List { 101 | return buildList { 102 | for (year in 1930..2020) { 103 | add( 104 | PickerItem( 105 | id = year, 106 | title = year.toString(), 107 | ), 108 | ) 109 | } 110 | } 111 | } 112 | 113 | private fun initServicePicker() = with(viewBinding.servicePicker) { 114 | initMovieFilteringPicker() 115 | valueEffect = CompositeValueEffect( 116 | listOf( 117 | FadingValueEffect(), 118 | RotationValueEffect(RotationValueEffect.Property.ROTATION_X), 119 | ), 120 | ) 121 | onItemSelectedListener = ValuePickerView.OnItemSelectedListener { 122 | viewBinding.serviceTv.text = "Service: ${it.title}" 123 | } 124 | 125 | val servicePickerItems = generateServicePickerItems() 126 | items = servicePickerItems 127 | setSelectedItem(servicePickerItems[0]) 128 | } 129 | 130 | private fun generateServicePickerItems(): List { 131 | return StreamingService.entries.map { 132 | PickerItem( 133 | id = it.ordinal, 134 | title = it.title, 135 | payload = it, 136 | ) 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /sample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 27 | 28 | 37 | 38 | 45 | 46 | 55 | 56 | 65 | 66 | 76 | 77 | 86 | 87 | 96 | 97 | 106 | 107 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /sample/src/main/java/com/paulrybitskyi/valuepicker/sample/personinfopicker/PersonInfoPickerFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Paul Rybitskyi, oss@paulrybitskyi.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.paulrybitskyi.valuepicker.sample.personinfopicker 18 | 19 | import android.graphics.Typeface 20 | import com.paulrybitskyi.commons.ktx.getColor 21 | import com.paulrybitskyi.commons.ktx.getDimension 22 | import com.paulrybitskyi.commons.ktx.getDimensionPixelSize 23 | import com.paulrybitskyi.commons.utils.viewBinding 24 | import com.paulrybitskyi.valuepicker.ValuePickerView 25 | import com.paulrybitskyi.valuepicker.model.Item 26 | import com.paulrybitskyi.valuepicker.model.Orientation 27 | import com.paulrybitskyi.valuepicker.model.PickerItem 28 | import com.paulrybitskyi.valuepicker.model.Size 29 | import com.paulrybitskyi.valuepicker.sample.BaseFragment 30 | import com.paulrybitskyi.valuepicker.sample.R 31 | import com.paulrybitskyi.valuepicker.sample.databinding.FragmentPersonInfoPickerBinding 32 | import com.paulrybitskyi.valuepicker.sample.valueeffects.CompositeValueEffect 33 | import com.paulrybitskyi.valuepicker.sample.valueeffects.RotationValueEffect 34 | import com.paulrybitskyi.valuepicker.valueeffects.concrete.FadingValueEffect 35 | import com.paulrybitskyi.valuepicker.valueeffects.concrete.NoValueEffect 36 | 37 | @Suppress("MagicNumber") 38 | internal class PersonInfoPickerFragment : BaseFragment< 39 | FragmentPersonInfoPickerBinding, 40 | >(R.layout.fragment_person_info_picker) { 41 | 42 | override val viewBinding by viewBinding(FragmentPersonInfoPickerBinding::bind) 43 | 44 | override fun onInit() { 45 | super.onInit() 46 | 47 | initPickers() 48 | } 49 | 50 | private fun initPickers() { 51 | initAgePicker() 52 | initHeightPicker() 53 | initWeightPicker() 54 | } 55 | 56 | private fun ValuePickerView.initPersonInfoPicker() { 57 | areDividersEnabled = true 58 | isInfiniteScrollEnabled = true 59 | maxVisibleItems = 3 60 | textSize = getDimension(R.dimen.person_info_picker_text_size) 61 | textColor = getColor(R.color.colorAccent) 62 | dividerColor = getColor(R.color.colorAccent) 63 | textTypeface = Typeface.DEFAULT_BOLD 64 | fixedItemSize = Size.withFixedSize( 65 | width = getDimensionPixelSize(R.dimen.person_info_picker_item_width), 66 | height = getDimensionPixelSize(R.dimen.person_info_picker_item_height), 67 | ) 68 | orientation = Orientation.HORIZONTAL 69 | } 70 | 71 | private fun initAgePicker() = with(viewBinding.agePicker) { 72 | initPersonInfoPicker() 73 | valueEffect = RotationValueEffect(RotationValueEffect.Property.ROTATION_Y) 74 | onItemSelectedListener = ValuePickerView.OnItemSelectedListener { 75 | viewBinding.ageTv.text = "Age: ${it.title}" 76 | } 77 | 78 | val agePickerItems = generateAgePickerItems() 79 | items = agePickerItems 80 | setSelectedItem(agePickerItems[19]) 81 | } 82 | 83 | private fun generateAgePickerItems(): List { 84 | return buildList { 85 | for (age in 1..100) { 86 | add( 87 | PickerItem( 88 | id = age, 89 | title = age.toString(), 90 | ), 91 | ) 92 | } 93 | } 94 | } 95 | 96 | private fun initHeightPicker() = with(viewBinding.heightPicker) { 97 | initPersonInfoPicker() 98 | valueEffect = NoValueEffect() 99 | onItemSelectedListener = ValuePickerView.OnItemSelectedListener { 100 | viewBinding.heightTv.text = "Height: ${it.title}" 101 | } 102 | 103 | val heightPickerItems = generateHeightPickerItems() 104 | items = heightPickerItems 105 | setSelectedItem(heightPickerItems[130]) 106 | } 107 | 108 | private fun generateHeightPickerItems(): List { 109 | return buildList { 110 | for (cm in 50..250) { 111 | add( 112 | PickerItem( 113 | id = cm, 114 | title = "$cm cm", 115 | ), 116 | ) 117 | } 118 | } 119 | } 120 | 121 | private fun initWeightPicker() = with(viewBinding.weightPicker) { 122 | initPersonInfoPicker() 123 | valueEffect = CompositeValueEffect( 124 | listOf( 125 | FadingValueEffect(), 126 | RotationValueEffect(RotationValueEffect.Property.ROTATION_Y), 127 | ), 128 | ) 129 | onItemSelectedListener = ValuePickerView.OnItemSelectedListener { 130 | viewBinding.weightTv.text = "Weight: ${it.title}" 131 | } 132 | 133 | val weightPickerItems = generateWeightPickerItems() 134 | items = weightPickerItems 135 | setSelectedItem(weightPickerItems[60]) 136 | } 137 | 138 | private fun generateWeightPickerItems(): List { 139 | return buildList { 140 | for (kg in 10..150) { 141 | add( 142 | PickerItem( 143 | id = kg, 144 | title = "$kg kg", 145 | ), 146 | ) 147 | } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ValuePicker 2 | An Android library that provides a simple and customizable ValuePicker. 3 | 4 | ![](https://img.shields.io/badge/API-21%2B-orange.svg?style=flat) 5 | [![Platform](https://img.shields.io/badge/platform-Android-green.svg)](http://developer.android.com/index.html) 6 | [![Download](https://img.shields.io/maven-central/v/com.paulrybitskyi.valuepicker/valuepicker.svg?label=Download)](https://search.maven.org/search?q=com.paulrybitskyi.valuepicker) 7 | [![Build](https://github.com/mars885/value-picker/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/mars885/value-picker/actions) 8 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-ValuePicker-brightgreen.svg?style=flat)](https://android-arsenal.com/details/1/8212) 9 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 10 | 11 | ## Contents 12 | 13 | * [Demo](#demo-youtube) 14 | * [Installation](#installation) 15 | * [Usage](#usage) 16 | * [Advanced Usage](#advanced-usage) 17 | * [License](#license) 18 | 19 | ## Demo (YouTube) 20 | 21 | 22 | 23 | 24 | 25 | ## Installation 26 | 27 | 1. Make sure that you've added the `mavenCentral()` repository to your top-level `build.gradle` file. 28 | 29 | ````groovy 30 | buildscript { 31 | //... 32 | repositories { 33 | //... 34 | mavenCentral() 35 | } 36 | //... 37 | } 38 | ```` 39 | 40 | 2. Add the library dependency to your module-level `build.gradle` file. 41 | 42 | ````groovy 43 | dependencies { 44 | //... 45 | implementation "com.paulrybitskyi.valuepicker:valuepicker:1.0.3" 46 | //... 47 | } 48 | ```` 49 | 50 | ## Usage 51 | Basic usage of the ValuePickerView involves two steps - declaring a widget inside the XML file of your choice and configuring it in one of the Kotlin/Java classes. 52 | 53 | Let's see how we can do that by following the steps listed above: 54 | 55 | 1. Declaring a widget inside the XML file. 56 | 57 |
XML (click to expand) 58 |

59 | 60 | ````xml 61 | 62 | 68 | 69 | 70 | 71 | 85 | 86 | 87 | ```` 88 |

89 | 90 | 2. Configuring the widget in one of the Kotlin/Java classes. 91 | 92 |
Kotlin (click to expand) 93 |

94 | 95 | ````kotlin 96 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 97 | super.onViewCreated(view, savedInstanceState) 98 | 99 | //... 100 | 101 | with(valuePickerView) { 102 | onItemSelectedListener = ValuePickerView.OnItemSelectedListener { item -> 103 | // Do something with item 104 | } 105 | 106 | val pickerItems = getPickerItems() 107 | 108 | items = pickerItems 109 | setSelectedItem(pickerItems[2]) 110 | } 111 | } 112 | 113 | 114 | private fun getPickerItems(): List { 115 | return buildList { 116 | for(number in 1..100) { 117 | add( 118 | PickerItem( 119 | id = number, 120 | title = number.toString() 121 | ) 122 | ) 123 | } 124 | } 125 | } 126 | ```` 127 | 128 |

129 | 130 |
Java (click to expand) 131 |

132 | 133 | ````java 134 | @Override 135 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 136 | super.onViewCreated(view, savedInstanceState); 137 | 138 | ValuePickerView valuePickerView = view.findViewById(R.id.valuePickerView); 139 | valuePickerView.setOnItemSelectedListener((item) -> { 140 | // Do something with item 141 | }); 142 | 143 | final ArrayList pickerItems = getPickerItems(); 144 | 145 | valuePickerView.setItems(getPickerItems()); 146 | valuePickerView.setSelectedItem(pickerItems.get(2)); 147 | } 148 | 149 | 150 | private ArrayList getPickerItems() { 151 | final ArrayList pickerItems = new ArrayList<>(100); 152 | 153 | for(int i = 1; i <= 100; i++) { 154 | pickerItems.add( 155 | new PickerItem( 156 | i, 157 | String.valueOf(i) 158 | ) 159 | ); 160 | } 161 | 162 | return pickerItems; 163 | } 164 | ```` 165 | 166 |

167 | 168 | ## Advanced Usage 169 | 170 | See the [Sample app](https://github.com/mars885/value-picker/tree/master/sample/src/main/java/com/paulrybitskyi/valuepicker/sample). 171 | 172 | ## License 173 | 174 | ValuePicker is licensed under the [Apache 2.0 License](LICENSE). 175 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | --------------------------------------------------------------------------------