├── .gitignore
├── .idea
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── compiler.xml
├── gradle.xml
├── jarRepositories.xml
├── misc.xml
└── vcs.xml
├── .jitpack.yml
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── demo
│ │ └── simple
│ │ └── picker
│ │ ├── BaseActivity.kt
│ │ ├── DatePickerActivity.kt
│ │ ├── KTX.kt
│ │ ├── LogLinearLayoutManager.kt
│ │ ├── MainActivity.kt
│ │ └── TextPickerActivity.kt
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ └── ic_launcher_background.xml
│ ├── layout
│ ├── activity_date_picker.xml
│ ├── activity_main.xml
│ ├── activity_text_picker.xml
│ ├── dialog_setting.xml
│ ├── dialog_to_position.xml
│ ├── item_horizontal_picker.xml
│ └── item_vertical_picker.xml
│ ├── menu
│ └── menu_main.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── files
├── gif_date_time_picker.gif
├── gif_picker.gif
└── gif_text_picker.gif
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── picker_layoutmanager
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── me
│ │ └── simple
│ │ └── picker
│ │ ├── PickerItemDecoration.kt
│ │ ├── PickerLayoutManager.kt
│ │ ├── PickerRecyclerView.kt
│ │ ├── datepicker
│ │ ├── DatePickerView.kt
│ │ ├── DayPickerView.kt
│ │ ├── MonthPickerView.kt
│ │ └── YearPickerView.kt
│ │ ├── timepicker
│ │ ├── HourPickerView.kt
│ │ ├── MinutePickerView.kt
│ │ ├── SecondPickerView.kt
│ │ └── TimePickerView.kt
│ │ ├── utils
│ │ ├── CenterHelperLineItemDecoration.kt
│ │ └── PickerUtils.kt
│ │ └── widget
│ │ ├── TextPickerLinearLayout.kt
│ │ └── TextPickerView.kt
│ └── res
│ ├── layout
│ └── item_text_picker.xml
│ └── values
│ └── attrs.xml
└── settings.gradle
/.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/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | xmlns:android
18 |
19 | ^$
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | xmlns:.*
29 |
30 | ^$
31 |
32 |
33 | BY_NAME
34 |
35 |
36 |
37 |
38 |
39 |
40 | .*:id
41 |
42 | http://schemas.android.com/apk/res/android
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | .*:name
52 |
53 | http://schemas.android.com/apk/res/android
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | name
63 |
64 | ^$
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | style
74 |
75 | ^$
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | .*
85 |
86 | ^$
87 |
88 |
89 | BY_NAME
90 |
91 |
92 |
93 |
94 |
95 |
96 | .*
97 |
98 | http://schemas.android.com/apk/res/android
99 |
100 |
101 | ANDROID_ATTRIBUTE_ORDER
102 |
103 |
104 |
105 |
106 |
107 |
108 | .*
109 |
110 | .*
111 |
112 |
113 | BY_NAME
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.jitpack.yml:
--------------------------------------------------------------------------------
1 | jdk:
2 | - openjdk11
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Peng Chen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PickerLayoutManager
2 |
3 | [](https://jitpack.io/#simplepeng/PickerLayoutManager)
4 |
5 | 一个基于自定义LayoutManager的PickerView,扩展自由度极高。
6 |
7 | 
8 |
9 | ## 导入依赖
10 |
11 | ```groovy
12 | maven { url 'https://www.jitpack.io' }
13 | ```
14 |
15 | ```groovy
16 | implementation 'com.github.simplepeng:PickerLayoutManager:v1.0.4'
17 | ```
18 |
19 | ## 如何使用
20 |
21 | 直接将`PickerLayoutManager`设置给`RecyclerView`即可,`Adapter`完全自定义化。
22 |
23 | ```kotlin
24 | val pickerLayoutManager = PickerLayoutManager(PickerLayoutManager.VERTICAL)
25 | recyclerView.layoutManager = pickerLayoutManager
26 | recyclerView.adapter = Adapter()
27 | ```
28 |
29 | `PickerLayoutManager`支持的构造参数属性
30 |
31 | * orientation:摆放子View的方向,默认为VERTICAL
32 | * visibleCount:显示多少个子View,默认为3,切只支持设置奇数
33 | * isLoop:是否支持无限滚动,默认为false
34 | * scaleX:x轴缩放的比例,默认为1.0f
35 | * scaleY:y轴缩放的比例,默认为1.0f
36 | * alpha:未选中item的透明度,默认为1.0f
37 |
38 | ## 监听选中
39 |
40 | ```kotlin
41 | pickerLayoutManager.addOnItemSelectedListener { position ->
42 | toast(position.toString())
43 | }
44 | ```
45 |
46 | ## 监听选中和取消选中时itemView被填充的回调
47 |
48 | ```kotlin
49 | pickerLayoutManager.addOnItemFillListener(object : PickerLayoutManager.OnItemFillListener {
50 | override fun onItemSelected(itemView: View, position: Int) {
51 | val tvItem = itemView.findViewById(R.id.tv_item)
52 | tvItem.setTextColor(Color.RED)
53 | }
54 |
55 | override fun onItemUnSelected(itemView: View, position: Int) {
56 | val tvItem = itemView.findViewById(R.id.tv_item)
57 | tvItem.setTextColor(Color.BLUE)
58 | }
59 | })
60 | ```
61 |
62 | 这个方法超级有用,可以实现选中和取消选中itemView的自定义化,下面的几个扩展View都使用到了这个方法回调。
63 |
64 | ## 设置分割线
65 |
66 | 分割线基于`ItemDecoration`实现,给RecyclerView添加`PickerItemDecoration`即可。
67 |
68 | ```kotlin
69 | recyclerView.addItemDecoration(PickerItemDecoration())
70 | ```
71 |
72 | `PickerItemDecoration`支持`color`,`size`,`margin`等构造参数。
73 |
74 | ## 基于PickerLayoutManager实现的扩展View
75 |
76 | ### TextPickerView
77 |
78 | 
79 |
80 | 这个实现相对简单,就自定义了一个item layout为TextView的Adapter而已,再做了一点自定义属性的封装。
81 |
82 | #### 支持的属性和方法
83 |
84 | **`注意:`在调用自定义属性的方法后,必须重新调用`resetLayoutManager()`方法才会起作用。**
85 |
86 | | 属性 | 方法 | 注释 |
87 | | ------------------- | ------------- | ---- |
88 | | visibleCount | setVisibleCount | 显示多少个子View |
89 | | isLoop | setIsLoop | 是否支持无限滚动 |
90 | | scaleX | setItemScaleX | x轴缩放的比例 |
91 | | scaleY | setItemScaleY | y轴缩放的比例 |
92 | | alpha | setItemAlpha | 未选中item的透明度 |
93 | | dividerVisible | setDividerVisible | 分割线是否可见 |
94 | | dividerColor | setDividerColor | 分割线的颜色 |
95 | | dividerSize | setDividerSize | 分割线的大小 |
96 | | dividerMargin | setDividerMargin | 分割线的边距 |
97 | | selectedTextColor | setSelectedTextColor | 文字选中的颜色 |
98 | | unSelectedTextColor | setUnSelectedTextColor | 文字未选中的颜色 |
99 | | selectedTextSize | setSelectedTextSize | 文字选中的大小 |
100 | | unSelectedTextSize | setUnSelectedTextSize | 文字未必选中的大小 |
101 | | selectedIsBold | setSelectedIsBold | 文字选中是否加粗 |
102 | | ~~scrollToEnd~~ | scrollToEnd() | 是否滚动到底部 |
103 |
104 | #### 如何使用
105 |
106 | 在布局中添加TextPickerView
107 |
108 | ```xml
109 |
116 | ```
117 |
118 | 设置数据源
119 |
120 | ```kotlin
121 | val items = mutableListOf()
122 | for (index in 0 until 100) {
123 | items.add(index.toString())
124 | }
125 |
126 | textPickerView.setData(items)
127 | ```
128 |
129 | ### DataPickerView和TimePickerView
130 |
131 | 
132 |
133 | #### 支持的属性和方法
134 |
135 | 同上面的`TextPickerView`
136 |
137 | #### 如何使用
138 |
139 | ```xml
140 |
148 |
149 |
156 | ```
157 |
158 | #### 设置数据源
159 |
160 | ```kotlin
161 | //默认从1949-1-1到当前这天
162 | datePickerView.setDateInterval()
163 | //滚动到当前日期,不可与`scrollToEnd`同时使用
164 | datePickerView.scrollToCurrentDate()
165 | datePickerView.selectedTodayItem()
166 |
167 | //默认从0点到24点
168 | timerPickerView.setTimeInterval()
169 | ```
170 |
171 | #### 监听选中
172 |
173 | ```kotlin
174 | datePickerView.setOnDateSelectedListener { year, month, day ->
175 | tvDate.text = "$year-$month-$day"
176 | }
177 |
178 | timePickerView.setOnTimeSelectedListener { hour, minute ->
179 | tvTime.text = "$hour:$minute"
180 | }
181 | ```
182 |
183 | ## 版本迭代
184 |
185 | * v1.0.5:fix:`getScale`方法
186 | * v1.0.4:解决`visibleCount = 1`不能滑动的问题
187 | * v1.0.3:解决`TextPickerView`设置`isBold`频繁requestLayout的问题
188 | * v1.0.2:`DatePickerView`增加`scrollTodayItem`,`scrollEndItem`等方法,丰富api调用
189 | * v1.0.1:修复`itemCount=1`且`isLoop=true`闪退的bug,`DatePickerView`增加`scrollToCurrentDate`的方法。
190 | * v1.0.0:首次上传
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android'
4 | }
5 |
6 | android {
7 | compileSdk 30
8 |
9 | defaultConfig {
10 | applicationId "demo.simple.pickerlayoutmanager"
11 | minSdk 16
12 | targetSdk 30
13 | versionCode 1
14 | versionName "1.0"
15 |
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 |
18 | viewBinding {
19 | enabled = true
20 | }
21 | }
22 |
23 | buildTypes {
24 | release {
25 | minifyEnabled false
26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
27 | }
28 | }
29 |
30 | compileOptions {
31 | sourceCompatibility JavaVersion.VERSION_1_8
32 | targetCompatibility JavaVersion.VERSION_1_8
33 | }
34 |
35 | kotlinOptions {
36 | jvmTarget = '1.8'
37 | }
38 | }
39 |
40 | dependencies {
41 | implementation 'androidx.core:core-ktx:1.3.2'
42 | implementation 'androidx.appcompat:appcompat:1.2.0'
43 | implementation 'com.google.android.material:material:1.3.0'
44 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
45 | testImplementation 'junit:junit:4.+'
46 | androidTestImplementation 'androidx.test.ext:junit:1.1.2'
47 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
48 |
49 | // implementation project(path: ':picker_layoutmanager')
50 | implementation 'com.github.simplepeng:PickerLayoutManager:v1.0.5'
51 | }
52 |
--------------------------------------------------------------------------------
/app/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
22 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/java/demo/simple/picker/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package demo.simple.picker
2 |
3 | import android.widget.Toast
4 | import androidx.appcompat.app.AppCompatActivity
5 |
6 | open class BaseActivity : AppCompatActivity() {
7 |
8 |
9 | fun toast(text:String?){
10 | Toast.makeText(this,text,Toast.LENGTH_SHORT).show()
11 | }
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/demo/simple/picker/DatePickerActivity.kt:
--------------------------------------------------------------------------------
1 | package demo.simple.picker
2 |
3 | import android.graphics.Color
4 | import android.graphics.Typeface
5 | import android.os.Bundle
6 | import android.util.Log
7 | import android.view.View
8 | import android.widget.TextView
9 | import demo.simple.picker.databinding.ActivityDatePickerBinding
10 | import me.simple.picker.PickerLayoutManager
11 | import me.simple.picker.widget.TextPickerLinearLayout
12 | import java.text.SimpleDateFormat
13 | import java.util.*
14 |
15 | class DatePickerActivity : BaseActivity(), PickerLayoutManager.OnItemFillListener {
16 |
17 | private val binding by lazy { ActivityDatePickerBinding.inflate(this.layoutInflater) }
18 |
19 | val TAG = "DatePickerActivity"
20 | val dfDate = SimpleDateFormat("yyyy-MM-dd_HH:mm:ss", Locale.getDefault())
21 |
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 | setContentView(binding.root)
25 |
26 | // initPickerViewStyle(datePickerView)
27 | // initPickerViewStyle(timePickerView)
28 |
29 | initDatePicker()
30 | initTimePicker()
31 | initListener()
32 | }
33 |
34 | private fun initDatePicker() {
35 | binding.datePickerView.setOnDateSelectedListener { year, month, day ->
36 | Log.d(TAG, "date = $year-$month-$day")
37 | binding.tvDate.text = "$year-$month-$day"
38 | }
39 | binding.datePickerView.setOnDateSelectedListener { calendar ->
40 | val format = dfDate.format(calendar.time)
41 | Log.d(TAG, "calendar = $format")
42 | }
43 |
44 | // datePickerView.setDateInterval(
45 | // 1949, 1, 1,
46 | // 2030, 1, 1
47 | // )
48 | // datePickerView.selectedEndItem()
49 | binding.datePickerView.selectedTodayItem()
50 |
51 | binding.btnDatePickerScrollTo.setOnClickListener {
52 | val year = 2020
53 | val month = 2
54 | val day = 15
55 |
56 | val calendar = Calendar.getInstance().apply {
57 | set(year, month - 1, day)
58 | }
59 |
60 | binding.datePickerView.selectedTodayItem()
61 | // datePickerView.setSelectedItem(calendar)
62 | // datePickerView.setSelectedItem(year, month, day)
63 | }
64 |
65 | binding.btnSelectedEndItem.setOnClickListener {
66 | binding.datePickerView.selectedEndItem()
67 | }
68 | }
69 |
70 | private fun initTimePicker() {
71 | binding.timePickerView.setOnTimeSelectedListener { hour, minute ->
72 | binding.tvTime.text = "$hour:$minute"
73 | Log.d(TAG, "time = $hour:$minute")
74 | }
75 | binding.timePickerView.setOnTimeSelectedListener { calendar ->
76 | val format = dfDate.format(calendar.time)
77 | Log.d(TAG, "calendar = $format")
78 | }
79 | }
80 |
81 | private fun initListener() {
82 | binding.btnGetDate.setOnClickListener {
83 |
84 | val dateArr = binding.datePickerView.getYearMonthDay()
85 | val timeArr = binding.timePickerView.getTime()
86 |
87 | val year = dateArr[0]
88 | val month = dateArr[1]
89 | val day = dateArr[2]
90 |
91 | val hour = timeArr[0]
92 | val minute = timeArr[1]
93 |
94 | val date = "$year-$month-$day"
95 | val time = "$hour:$minute"
96 |
97 | binding.tvDate.text = date
98 | binding.tvTime.text = time
99 |
100 | toast("$date $time")
101 | }
102 | }
103 |
104 | private fun initPickerViewStyle(pickerView: TextPickerLinearLayout) {
105 | pickerView.run {
106 | setVisibleCount(5)
107 | setIsLoop(true)
108 | setItemScaleX(0.75f)
109 | setItemScaleY(0.75f)
110 | setItemAlpha(0.75f)
111 |
112 | setSelectedTextColor(Color.RED)
113 | setUnSelectedTextColor(Color.GREEN)
114 | setSelectedTextSize(16f.dp)
115 | setUnSelectedTextSize(12f.dp)
116 | setSelectedIsBold(true)
117 |
118 | setDividerVisible(true)
119 | setDividerSize(2f.dp)
120 | setDividerColor(Color.RED)
121 | setDividerMargin(10f.dp)
122 |
123 | resetLayoutManager()
124 | }
125 | pickerView.addOnItemFillListener(this)
126 | }
127 |
128 | override fun onItemSelected(child: View, position: Int) {
129 | val tv = child as TextView
130 | tv.setTextColor(Color.RED)
131 | tv.textSize = 15f
132 | tv.typeface = Typeface.DEFAULT_BOLD
133 | }
134 |
135 | override fun onItemUnSelected(child: View, position: Int) {
136 | val tv = child as TextView
137 | tv.setTextColor(Color.BLUE)
138 | tv.textSize = 11f
139 | tv.typeface = Typeface.DEFAULT
140 | }
141 | }
--------------------------------------------------------------------------------
/app/src/main/java/demo/simple/picker/KTX.kt:
--------------------------------------------------------------------------------
1 | package demo.simple.picker
2 |
3 | import android.content.res.Resources
4 |
5 | val Float.dp: Float
6 | get() = Resources.getSystem().displayMetrics.density * this + 0.5f
--------------------------------------------------------------------------------
/app/src/main/java/demo/simple/picker/LogLinearLayoutManager.kt:
--------------------------------------------------------------------------------
1 | package demo.simple.picker
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.util.Log
6 | import androidx.recyclerview.widget.LinearLayoutManager
7 | import androidx.recyclerview.widget.RecyclerView
8 |
9 | class LogLinearLayoutManager : LinearLayoutManager {
10 |
11 | private val TAG = "LogLinearLayoutManager"
12 |
13 | constructor(context: Context?) : super(context)
14 | constructor(context: Context?, orientation: Int, reverseLayout: Boolean) : super(
15 | context,
16 | orientation,
17 | reverseLayout
18 | )
19 |
20 | constructor(
21 | context: Context?,
22 | attrs: AttributeSet?,
23 | defStyleAttr: Int,
24 | defStyleRes: Int
25 | ) : super(context, attrs, defStyleAttr, defStyleRes)
26 |
27 | override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) {
28 | super.onLayoutChildren(recycler, state)
29 |
30 | Log.d(TAG, "onLayoutChildren")
31 | }
32 |
33 | override fun isAutoMeasureEnabled(): Boolean {
34 | // return false
35 | return super.isAutoMeasureEnabled()
36 | }
37 | }
--------------------------------------------------------------------------------
/app/src/main/java/demo/simple/picker/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package demo.simple.picker
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import android.view.*
7 | import android.widget.CheckBox
8 | import android.widget.EditText
9 | import android.widget.RadioGroup
10 | import android.widget.TextView
11 | import androidx.appcompat.app.AlertDialog
12 | import androidx.recyclerview.widget.RecyclerView
13 | import demo.simple.picker.databinding.ActivityMainBinding
14 | import me.simple.picker.PickerItemDecoration
15 | import me.simple.picker.PickerLayoutManager
16 |
17 | @SuppressLint("NotifyDataSetChanged")
18 | class MainActivity : BaseActivity() {
19 |
20 | private val binding by lazy { ActivityMainBinding.inflate(this.layoutInflater) }
21 |
22 | private val mItems = mutableListOf()
23 |
24 | override fun onCreate(savedInstanceState: Bundle?) {
25 | super.onCreate(savedInstanceState)
26 | setContentView(binding.root)
27 |
28 | // startActivity(Intent(this, DatePickerActivity::class.java))
29 | // startActivity(Intent(this, TextPickerActivity::class.java))
30 |
31 | for (i in 0..29) {
32 | mItems.add(i.toString())
33 | }
34 |
35 | initLinearPicker()
36 |
37 | binding.btnNotify.setOnClickListener {
38 | for (i in 0..10) {
39 | mItems.removeAt(mItems.size - 1)
40 | }
41 | binding.recyclerView.adapter?.notifyDataSetChanged()
42 | }
43 | }
44 |
45 | private fun initLinearPicker() {
46 | val pickerLayoutManager = PickerLayoutManager(
47 | PickerLayoutManager.VERTICAL, visibleCount = 7, isLoop = false,
48 | scaleX = 0.9f, scaleY = 0.9f
49 | )
50 | // val pickerLayoutManager = LinearLayoutManager(
51 | // this@MainActivity,
52 | // LinearLayoutManager.VERTICAL,
53 | // false
54 | // )
55 | setListener(pickerLayoutManager)
56 | binding.recyclerView.run {
57 | layoutManager = pickerLayoutManager
58 | adapter = PickerAdapter(pickerLayoutManager.orientation)
59 | }
60 | binding.recyclerView.addItemDecoration(PickerItemDecoration())
61 | }
62 |
63 | private fun setListener(pickerLayoutManager: PickerLayoutManager) {
64 | pickerLayoutManager.addOnItemSelectedListener { position ->
65 | toast(position.toString())
66 | }
67 | // pickerLayoutManager.addOnItemFillListener(object : PickerLayoutManager.OnItemFillListener {
68 | // override fun onItemSelected(itemView: View, position: Int) {
69 | // val tvItem = itemView.findViewById(R.id.tv_item)
70 | // tvItem.setTextColor(Color.RED)
71 | // }
72 | //
73 | // override fun onItemUnSelected(itemView: View, position: Int) {
74 | // val tvItem = itemView.findViewById(R.id.tv_item)
75 | // tvItem.setTextColor(Color.BLUE)
76 | // }
77 | // })
78 | }
79 |
80 | inner class PickerAdapter(private val orientation: Int) :
81 | RecyclerView.Adapter() {
82 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)
83 | : PickerViewHolder {
84 | val layoutId = if (orientation == PickerLayoutManager.HORIZONTAL) {
85 | R.layout.item_horizontal_picker
86 | } else {
87 | R.layout.item_vertical_picker
88 | }
89 | return PickerViewHolder(
90 | LayoutInflater.from(this@MainActivity)
91 | .inflate(layoutId, parent, false)
92 | )
93 | }
94 |
95 | override fun getItemCount(): Int {
96 | return mItems.size
97 | }
98 |
99 | override fun onBindViewHolder(holder: PickerViewHolder, position: Int) {
100 | holder.tvItem.text = mItems[position]
101 | }
102 |
103 | }
104 |
105 | inner class PickerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
106 | val tvItem = itemView.findViewById(R.id.tv_item)!!
107 | }
108 |
109 | override fun onCreateOptionsMenu(menu: Menu): Boolean {
110 | menuInflater.inflate(R.menu.menu_main, menu)
111 | return true
112 | }
113 |
114 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
115 | when (item.itemId) {
116 | // R.id.menu_setting -> {
117 | // showSettingDialog()
118 | // }
119 | R.id.menu_scroll_to -> {
120 | showToPositionDialog()
121 | }
122 | R.id.menu_date_picker -> {
123 | startActivity(Intent(this, DatePickerActivity::class.java))
124 | }
125 | R.id.menu_text_picker -> {
126 | startActivity(Intent(this, TextPickerActivity::class.java))
127 | }
128 | else -> {
129 | }
130 | }
131 | return true
132 | }
133 |
134 | private fun showSettingDialog() {
135 | val dialog = AlertDialog.Builder(this)
136 | .setView(R.layout.dialog_setting)
137 | .show()
138 |
139 | val rgOrientation = dialog.findViewById(R.id.rgOrientation)!!
140 | val etVisibleCount = dialog.findViewById(R.id.etVisibleCount)!!
141 | val cbIsLoop = dialog.findViewById(R.id.cbIsLoop)!!
142 | dialog.findViewById(R.id.btnOk)!!.setOnClickListener {
143 | dialog.dismiss()
144 |
145 | }
146 | }
147 |
148 | private fun showToPositionDialog() {
149 | val dialog = AlertDialog.Builder(this)
150 | .setView(R.layout.dialog_to_position)
151 | .show()
152 | val etPosition = dialog.findViewById(R.id.etToPosition)!!
153 |
154 | dialog.findViewById(R.id.btnToPosition)!!.setOnClickListener {
155 | dialog.dismiss()
156 | val position = etPosition.text.toString().toInt()
157 | binding.recyclerView.scrollToPosition(position)
158 | }
159 | dialog.findViewById(R.id.btnSmoothToPosition)!!.setOnClickListener {
160 | dialog.dismiss()
161 | val position = etPosition.text.toString().toInt()
162 | binding.recyclerView.smoothScrollToPosition(position)
163 | }
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/app/src/main/java/demo/simple/picker/TextPickerActivity.kt:
--------------------------------------------------------------------------------
1 | package demo.simple.picker
2 |
3 | import android.os.Bundle
4 | import demo.simple.picker.databinding.ActivityTextPickerBinding
5 |
6 | class TextPickerActivity : BaseActivity() {
7 |
8 | private val binding by lazy { ActivityTextPickerBinding.inflate(this.layoutInflater) }
9 |
10 | override fun onCreate(savedInstanceState: Bundle?) {
11 | super.onCreate(savedInstanceState)
12 | setContentView(binding.root)
13 |
14 | val items = mutableListOf()
15 | for (index in 0 until 100) {
16 | items.add(index.toString())
17 | }
18 |
19 | binding.textPickerView.setData(items)
20 |
21 | // textPickerView.run {
22 | // setVisibleCount(5)
23 | // setIsLoop(true)
24 | // setSelectedIsBold(true)
25 | // setSelectedTextColor(Color.RED)
26 | //
27 | // resetLayoutManager()
28 | // }
29 | }
30 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/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 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_date_picker.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
25 |
26 |
35 |
36 |
45 |
46 |
53 |
54 |
62 |
63 |
73 |
74 |
81 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
19 |
20 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
46 |
47 |
56 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_text_picker.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_setting.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
20 |
21 |
26 |
27 |
33 |
34 |
35 |
39 |
40 |
44 |
45 |
52 |
53 |
54 |
58 |
59 |
63 |
64 |
68 |
69 |
70 |
77 |
78 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_to_position.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
28 |
29 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_horizontal_picker.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_vertical_picker.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplepeng/PickerLayoutManager/bd82fc386cfd2f7131a209ea1843b5de754446ad/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplepeng/PickerLayoutManager/bd82fc386cfd2f7131a209ea1843b5de754446ad/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplepeng/PickerLayoutManager/bd82fc386cfd2f7131a209ea1843b5de754446ad/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplepeng/PickerLayoutManager/bd82fc386cfd2f7131a209ea1843b5de754446ad/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplepeng/PickerLayoutManager/bd82fc386cfd2f7131a209ea1843b5de754446ad/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplepeng/PickerLayoutManager/bd82fc386cfd2f7131a209ea1843b5de754446ad/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplepeng/PickerLayoutManager/bd82fc386cfd2f7131a209ea1843b5de754446ad/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplepeng/PickerLayoutManager/bd82fc386cfd2f7131a209ea1843b5de754446ad/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplepeng/PickerLayoutManager/bd82fc386cfd2f7131a209ea1843b5de754446ad/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplepeng/PickerLayoutManager/bd82fc386cfd2f7131a209ea1843b5de754446ad/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #6200EE
4 | #3700B3
5 | #03DAC5
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | PickerLayoutManager
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 | dependencies {
8 | classpath "com.android.tools.build:gradle:7.0.3"
9 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20"
10 |
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | task clean(type: Delete) {
17 | delete rootProject.buildDir
18 | }
19 |
--------------------------------------------------------------------------------
/files/gif_date_time_picker.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplepeng/PickerLayoutManager/bd82fc386cfd2f7131a209ea1843b5de754446ad/files/gif_date_time_picker.gif
--------------------------------------------------------------------------------
/files/gif_picker.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplepeng/PickerLayoutManager/bd82fc386cfd2f7131a209ea1843b5de754446ad/files/gif_picker.gif
--------------------------------------------------------------------------------
/files/gif_text_picker.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplepeng/PickerLayoutManager/bd82fc386cfd2f7131a209ea1843b5de754446ad/files/gif_text_picker.gif
--------------------------------------------------------------------------------
/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=-Xmx1536m
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
22 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplepeng/PickerLayoutManager/bd82fc386cfd2f7131a209ea1843b5de754446ad/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Nov 27 22:49:18 CST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/picker_layoutmanager/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/picker_layoutmanager/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | id 'maven-publish'
5 | }
6 |
7 | afterEvaluate {
8 | publishing {
9 | publications {
10 | release(MavenPublication) {
11 | from components.release
12 | }
13 | }
14 | }
15 | }
16 |
17 | android {
18 | compileSdk 31
19 |
20 | defaultConfig {
21 | minSdk 16
22 | targetSdk 31
23 | }
24 |
25 | compileOptions {
26 | sourceCompatibility JavaVersion.VERSION_1_8
27 | targetCompatibility JavaVersion.VERSION_1_8
28 | }
29 |
30 | kotlinOptions {
31 | jvmTarget = '1.8'
32 | }
33 |
34 | //指定不生成BuildConfig.java
35 | buildFeatures{
36 | buildConfig = false
37 | }
38 | }
39 |
40 | dependencies {
41 | api "androidx.recyclerview:recyclerview:1.2.1"
42 | }
43 |
--------------------------------------------------------------------------------
/picker_layoutmanager/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simplepeng/PickerLayoutManager/bd82fc386cfd2f7131a209ea1843b5de754446ad/picker_layoutmanager/consumer-rules.pro
--------------------------------------------------------------------------------
/picker_layoutmanager/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
22 |
--------------------------------------------------------------------------------
/picker_layoutmanager/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/picker_layoutmanager/src/main/java/me/simple/picker/PickerItemDecoration.kt:
--------------------------------------------------------------------------------
1 | package me.simple.picker
2 |
3 | import android.graphics.Canvas
4 | import android.graphics.Color
5 | import android.graphics.Paint
6 | import android.graphics.RectF
7 | import androidx.recyclerview.widget.RecyclerView
8 |
9 | open class PickerItemDecoration(
10 | private val color: Int = Color.LTGRAY,
11 | private val size: Float = 1.0f,
12 | private val margin: Float = 0f
13 | ) : RecyclerView.ItemDecoration() {
14 |
15 | private val mPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
16 | color = this@PickerItemDecoration.color
17 | style = Paint.Style.FILL
18 | }
19 | private val mRectF = RectF()
20 |
21 | override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
22 | super.onDrawOver(c, parent, state)
23 | if (parent.layoutManager == null || parent.layoutManager !is PickerLayoutManager)
24 | return
25 |
26 | calcDivider(c, parent)
27 | }
28 |
29 | private fun calcDivider(canvas: Canvas, parent: RecyclerView) {
30 | val lm = parent.layoutManager as PickerLayoutManager
31 | val itemSize = if (lm.orientation == PickerLayoutManager.HORIZONTAL) {
32 | parent.width / lm.visibleCount
33 | } else {
34 | parent.height / lm.visibleCount
35 | }
36 |
37 | val startDrawPosition = (lm.visibleCount - 1) / 2
38 | val endDrawPosition = startDrawPosition + 1
39 |
40 | drawDivider(canvas, itemSize, startDrawPosition, parent, lm)
41 | drawDivider(canvas, itemSize, endDrawPosition, parent, lm)
42 | }
43 |
44 | private fun drawDivider(
45 | canvas: Canvas,
46 | itemSize: Int,
47 | position: Int,
48 | parent: RecyclerView,
49 | lm: PickerLayoutManager
50 | ) {
51 | if (lm.orientation == PickerLayoutManager.HORIZONTAL) {
52 | val left = position * itemSize.toFloat() - size / 2
53 | val right = left + size
54 | mRectF.set(left, margin, right, parent.height - margin)
55 | canvas.drawRect(mRectF, mPaint)
56 | } else {
57 | val top = position * itemSize.toFloat() - size / 2
58 | val bottom = top + size
59 | mRectF.set(margin, top, parent.width - margin, bottom)
60 | canvas.drawRect(mRectF, mPaint)
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/picker_layoutmanager/src/main/java/me/simple/picker/PickerLayoutManager.kt:
--------------------------------------------------------------------------------
1 | package me.simple.picker
2 |
3 | import android.animation.Animator
4 | import android.animation.ValueAnimator
5 | import android.graphics.PointF
6 | import android.util.Log
7 | import android.view.View
8 | import android.view.animation.LinearInterpolator
9 | import androidx.annotation.FloatRange
10 | import androidx.recyclerview.widget.*
11 | import kotlin.math.abs
12 | import kotlin.math.max
13 | import kotlin.math.min
14 |
15 | typealias OnItemSelectedListener = (position: Int) -> Unit
16 |
17 | /**
18 | * @param orientation 摆放子View的方向
19 | * @param visibleCount 显示多少个子View
20 | * @param isLoop 是否支持无限滚动
21 | * @param scaleX x轴缩放的比例
22 | * @param scaleY y轴缩放的比例
23 | * @param alpha 未选中item的透明度
24 | */
25 | open class PickerLayoutManager @JvmOverloads constructor(
26 | var orientation: Int = ORIENTATION,
27 |
28 | var visibleCount: Int = VISIBLE_COUNT,
29 |
30 | var isLoop: Boolean = IS_LOOP,
31 |
32 | @FloatRange(from = 0.0, to = 1.0)
33 | var scaleX: Float = SCALE_X,
34 |
35 | @FloatRange(from = 0.0, to = 1.0)
36 | var scaleY: Float = SCALE_Y,
37 |
38 | @FloatRange(from = 0.0, to = 1.0)
39 | var alpha: Float = ALPHA
40 | ) : RecyclerView.LayoutManager(), RecyclerView.SmoothScroller.ScrollVectorProvider {
41 |
42 | companion object {
43 | const val HORIZONTAL = RecyclerView.HORIZONTAL
44 | const val VERTICAL = RecyclerView.VERTICAL
45 |
46 | private const val FILL_START = -1
47 | private const val FILL_END = 1
48 |
49 | private const val TAG = "PickerLayoutManager"
50 |
51 | var DEBUG = true
52 |
53 | private const val ORIENTATION = VERTICAL
54 | internal const val VISIBLE_COUNT = 3
55 | internal const val IS_LOOP = false
56 | internal const val SCALE_X = 1.0f
57 | internal const val SCALE_Y = 1.0f
58 | internal const val ALPHA = 1.0f
59 | }
60 |
61 | //将要填充的view的position
62 | private var mPendingFillPosition: Int = RecyclerView.NO_POSITION
63 |
64 | //保存下item的width和height‘’
65 | private var mItemWidth: Int = 0
66 | private var mItemHeight: Int = 0
67 |
68 | //将要滚到的position
69 | private var mPendingScrollToPosition: Int = RecyclerView.NO_POSITION
70 |
71 | //要回收的View先缓存起来
72 | private val mRecycleViews = hashSetOf()
73 |
74 | //直接搞个SnapHelper来findCenterView
75 | private val mSnapHelper = LinearSnapHelper()
76 |
77 | //选中中间item的监听器的集合
78 | private val mOnItemSelectedListener = mutableSetOf()
79 |
80 | //子view填充或滚动监听器的集合
81 | private val mOnItemFillListener = mutableSetOf()
82 |
83 | //Recyclerview内置的帮助类
84 | private val mOrientationHelper: OrientationHelper by lazy {
85 | if (orientation == HORIZONTAL) {
86 | OrientationHelper.createHorizontalHelper(this)
87 | } else {
88 | OrientationHelper.createVerticalHelper(this)
89 | }
90 | }
91 |
92 | init {
93 | if (visibleCount % 2 == 0) {
94 | throw IllegalArgumentException("visibleCount == $visibleCount 不能是偶数")
95 | }
96 | }
97 |
98 | override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams {
99 | return if (orientation == HORIZONTAL) {
100 | RecyclerView.LayoutParams(
101 | RecyclerView.LayoutParams.WRAP_CONTENT,
102 | RecyclerView.LayoutParams.MATCH_PARENT
103 | )
104 | } else {
105 | RecyclerView.LayoutParams(
106 | RecyclerView.LayoutParams.MATCH_PARENT,
107 | RecyclerView.LayoutParams.WRAP_CONTENT
108 | )
109 | }
110 | }
111 |
112 | override fun isAutoMeasureEnabled(): Boolean {
113 | return false
114 | }
115 |
116 | override fun onMeasure(
117 | recycler: RecyclerView.Recycler,
118 | state: RecyclerView.State,
119 | widthSpec: Int,
120 | heightSpec: Int
121 | ) {
122 | if (state.itemCount == 0) {
123 | super.onMeasure(recycler, state, widthSpec, heightSpec)
124 | return
125 | }
126 | if (state.isPreLayout) return
127 |
128 | //用第一个view计算宽高,这种方式可能不太好
129 | val itemView = recycler.getViewForPosition(0)
130 | addView(itemView)
131 | //这里不能用measureChild方法,具体看内部源码实现,内部getWidth默认为0
132 | // measureChildWithMargins(itemView, 0, 0)
133 | itemView.measure(widthSpec, heightSpec)
134 | mItemWidth = getDecoratedMeasuredWidth(itemView)
135 | mItemHeight = getDecoratedMeasuredHeight(itemView)
136 | logDebug("mItemWidth == $mItemWidth -- mItemHeight == $mItemHeight")
137 | detachAndScrapView(itemView, recycler)
138 |
139 | //设置宽高
140 | setWidthAndHeight(mItemWidth, mItemHeight)
141 | }
142 |
143 | private fun setWidthAndHeight(
144 | width: Int,
145 | height: Int
146 | ) {
147 | if (orientation == HORIZONTAL) {
148 | setMeasuredDimension(width * visibleCount, height)
149 | } else {
150 | setMeasuredDimension(width, height * visibleCount)
151 | }
152 | }
153 |
154 | // 软键盘的弹出和收起,scrollToPosition
155 | // 都会再次调用这个方法,自己要记录好偏移量
156 | override fun onLayoutChildren(
157 | recycler: RecyclerView.Recycler,
158 | state: RecyclerView.State
159 | ) {
160 | logDebug("onLayoutChildren")
161 | //如果itemCount==0了,直接移除全部view
162 | if (mPendingScrollToPosition != RecyclerView.NO_POSITION) {
163 | if (state.itemCount == 0) {
164 | removeAndRecycleAllViews(recycler)
165 | return
166 | }
167 | }
168 |
169 | //不支持预测动画,直接return
170 | if (state.isPreLayout) return
171 |
172 | logDebug("state.itemCount -- ${state.itemCount}")
173 |
174 | //计算当前开始的position
175 | mPendingFillPosition = 0
176 | val isScrollTo = mPendingScrollToPosition != RecyclerView.NO_POSITION
177 | if (isScrollTo) {
178 | mPendingFillPosition = mPendingScrollToPosition
179 | } else if (childCount != 0) {
180 | mPendingFillPosition = getSelectedPosition()
181 | }
182 | logDebug("mPendingFillPosition == $mPendingFillPosition")
183 |
184 | //解决当调用notifyDataChanges时itemCount变小
185 | //且getSelectedPosition>itemCount的bug
186 | if (mPendingFillPosition >= state.itemCount) {
187 | mPendingFillPosition = state.itemCount - 1
188 | }
189 |
190 | //暂时移除全部view,然后重新fill进来
191 | detachAndScrapAttachedViews(recycler)
192 |
193 | //开始就向下填充
194 | var anchor = getOffsetSpace()
195 | var fillDirection = FILL_END
196 | fillLayout(recycler, state, anchor, fillDirection)
197 |
198 | //如果是isLoop=true,或者是scrollTo或软键盘弹起,再向上填充
199 | //getAnchorView可能为null,先判断下childCount
200 | if (childCount != 0) {
201 | fillDirection = FILL_START
202 | mPendingFillPosition = getPendingFillPosition(fillDirection)
203 | anchor = getAnchor(fillDirection)
204 | fillLayout(recycler, state, anchor, fillDirection)
205 | }
206 |
207 | //scrollTo过来的要回调onItemSelected
208 | if (isScrollTo) {
209 | val centerPosition = getSelectedPosition()
210 | dispatchOnItemSelectedListener(centerPosition)
211 | }
212 |
213 | //变换children
214 | transformChildren()
215 | //分发Item Fill事件
216 | dispatchOnItemFillListener()
217 |
218 | //
219 | logDebug("width == $width -- height == $height")
220 | logChildCount(recycler)
221 | }
222 |
223 | override fun onItemsChanged(recyclerView: RecyclerView) {
224 | super.onItemsChanged(recyclerView)
225 | logDebug("onItemsChanged")
226 | }
227 |
228 | override fun onLayoutCompleted(state: RecyclerView.State?) {
229 | super.onLayoutCompleted(state)
230 | mPendingScrollToPosition = RecyclerView.NO_POSITION
231 | }
232 |
233 | override fun canScrollHorizontally(): Boolean {
234 | return orientation == HORIZONTAL
235 | }
236 |
237 | override fun canScrollVertically(): Boolean {
238 | return orientation == VERTICAL
239 | }
240 |
241 | override fun scrollHorizontallyBy(
242 | dx: Int,
243 | recycler: RecyclerView.Recycler,
244 | state: RecyclerView.State
245 | ): Int {
246 | if (orientation == VERTICAL) return 0
247 |
248 | return scrollBy(dx, recycler, state)
249 | }
250 |
251 | override fun scrollVerticallyBy(
252 | dy: Int,
253 | recycler: RecyclerView.Recycler,
254 | state: RecyclerView.State
255 | ): Int {
256 | if (orientation == HORIZONTAL) return 0
257 |
258 | return scrollBy(dy, recycler, state)
259 | }
260 |
261 | override fun scrollToPosition(position: Int) {
262 | if (childCount == 0) return
263 | checkToPosition(position)
264 |
265 | mPendingScrollToPosition = position
266 | requestLayout()
267 | }
268 |
269 | override fun smoothScrollToPosition(
270 | recyclerView: RecyclerView,
271 | state: RecyclerView.State,
272 | position: Int
273 | ) {
274 | if (childCount == 0) return
275 | checkToPosition(position)
276 |
277 | val toPosition = fixSmoothToPosition(position)
278 | val linearSmoothScroller = LinearSmoothScroller(recyclerView.context)
279 | linearSmoothScroller.targetPosition = toPosition
280 | startSmoothScroll(linearSmoothScroller)
281 | }
282 |
283 | override fun computeScrollVectorForPosition(targetPosition: Int): PointF? {
284 | if (childCount == 0) return null
285 |
286 | val firstChildPos = getPosition(mSnapHelper.findSnapView(this)!!)
287 | val direction = if (targetPosition < firstChildPos) -1 else 1
288 | return if (orientation == LinearLayoutManager.HORIZONTAL) {
289 | PointF(direction.toFloat(), 0f)
290 | } else {
291 | PointF(0f, direction.toFloat())
292 | }
293 | }
294 |
295 | override fun onScrollStateChanged(state: Int) {
296 | super.onScrollStateChanged(state)
297 | if (childCount == 0) return
298 | logDebug("onScrollStateChanged -- $state")
299 |
300 | if (state == RecyclerView.SCROLL_STATE_IDLE) {
301 | val centerView = getSelectedView() ?: return
302 | val centerPosition = getPosition(centerView)
303 | scrollToCenter(centerView, centerPosition)
304 | }
305 | }
306 |
307 | //------------------------------------------------------------------
308 | /**
309 | * 初始化摆放view
310 | */
311 | private fun fillLayout(
312 | recycler: RecyclerView.Recycler,
313 | state: RecyclerView.State,
314 | anchor: Int,
315 | fillDirection: Int
316 | ) {
317 | var innerAnchor = anchor
318 | var count = if (fillDirection == FILL_START) getOffsetCount() else getFixVisibleCount()
319 | while (count > 0 && hasMore(state)) {
320 | val child = nextView(recycler, fillDirection)
321 | if (fillDirection == FILL_START) {
322 | addView(child, 0)
323 | } else {
324 | addView(child)
325 | }
326 | measureChildWithMargins(child, 0, 0)
327 | layoutChunk(child, innerAnchor, fillDirection)
328 | if (fillDirection == FILL_START) {
329 | innerAnchor -= mOrientationHelper.getDecoratedMeasurement(child)
330 | } else {
331 | innerAnchor += mOrientationHelper.getDecoratedMeasurement(child)
332 | }
333 | count--
334 | }
335 | }
336 |
337 | /**
338 | * 获取偏移的item count
339 | * 例如:开始position == 0居中,就要偏移一个item count的距离
340 | */
341 | private fun getOffsetCount() = (visibleCount - 1) / 2
342 |
343 | /**
344 | * 获取真实可见的visible count
345 | * 例如:传入的visible count=3,但是在isLoop=false的情况下,
346 | * 开始只用填充2个item view进来就行了
347 | */
348 | private fun getFixVisibleCount(): Int {
349 | if (isLoop) return visibleCount
350 | return (visibleCount + 1) / 2
351 | }
352 |
353 | /**
354 | * 摆放item view
355 | */
356 | private fun layoutChunk(
357 | child: View,
358 | anchor: Int,
359 | fillDirection: Int
360 | ) {
361 | var left: Int = 0
362 | var top: Int = 0
363 | var right: Int = 0
364 | var bottom: Int = 0
365 | if (orientation == HORIZONTAL) {
366 | top = paddingTop
367 | bottom =
368 | paddingTop + mOrientationHelper.getDecoratedMeasurementInOther(child) - paddingBottom
369 | if (fillDirection == FILL_START) {
370 | right = anchor
371 | left = right - mOrientationHelper.getDecoratedMeasurement(child)
372 | } else {
373 | left = anchor
374 | right = left + mOrientationHelper.getDecoratedMeasurement(child)
375 | }
376 | } else {
377 | left = paddingLeft
378 | right = mOrientationHelper.getDecoratedMeasurementInOther(child) - paddingRight
379 | if (fillDirection == FILL_START) {
380 | bottom = anchor
381 | top = bottom - mOrientationHelper.getDecoratedMeasurement(child)
382 | } else {
383 | top = anchor
384 | bottom = anchor + mOrientationHelper.getDecoratedMeasurement(child)
385 | }
386 | }
387 |
388 | layoutDecoratedWithMargins(child, left, top, right, bottom)
389 | }
390 |
391 | /**
392 | * 滑动的统一处理事件
393 | */
394 | private fun scrollBy(
395 | delta: Int,
396 | recycler: RecyclerView.Recycler,
397 | state: RecyclerView.State
398 | ): Int {
399 | if (childCount == 0 || delta == 0) return 0
400 |
401 | //开始填充item view
402 | val consume = fillScroll(delta, recycler, state)
403 | //移动全部子view
404 | mOrientationHelper.offsetChildren(-consume)
405 | //回收屏幕外的view
406 | recycleChildren(delta, recycler)
407 |
408 | //变换children
409 | transformChildren()
410 | //分发事件
411 | dispatchOnItemFillListener()
412 |
413 | //输出当前屏幕全部的子view
414 | logChildCount(recycler)
415 | return consume
416 | }
417 |
418 | /**
419 | * 在滑动的时候填充view,
420 | * delta > 0 向右或向下移动
421 | * delta < 0 向左或向上移动
422 | */
423 | private fun fillScroll(
424 | delta: Int,
425 | recycler: RecyclerView.Recycler,
426 | state: RecyclerView.State
427 | ): Int {
428 |
429 | val absDelta = abs(delta)
430 | var remainSpace = abs(delta)
431 | logDebug("delta == $delta")
432 |
433 | val fillDirection = if (delta > 0) FILL_END else FILL_START
434 |
435 | //检查滚动距离是否可以填充下一个view
436 | if (canNotFillScroll(fillDirection, absDelta)) {
437 | return delta
438 | }
439 |
440 | //检查是否滚动到了顶部或者底部
441 | if (checkScrollToEdge(fillDirection, state)) {
442 | val fixLastScroll = getFixLastScroll(fillDirection)
443 | return if (fillDirection == FILL_START) {
444 | max(fixLastScroll, delta)
445 | } else {
446 | min(fixLastScroll, delta)
447 | }
448 | }
449 |
450 | //获取将要填充的view
451 | mPendingFillPosition = getPendingFillPosition(fillDirection)
452 |
453 | //
454 | while (remainSpace > 0 && hasMore(state)) {
455 | val anchor = getAnchor(fillDirection)
456 | val child = nextView(recycler, fillDirection)
457 | if (fillDirection == FILL_START) {
458 | addView(child, 0)
459 | } else {
460 | addView(child)
461 | }
462 | measureChildWithMargins(child, 0, 0)
463 | layoutChunk(child, anchor, fillDirection)
464 | remainSpace -= mOrientationHelper.getDecoratedMeasurement(child)
465 | }
466 |
467 | return delta
468 | }
469 |
470 | /**
471 | * 如果anchorView的(start或end)+delta还是没出现在屏幕内,
472 | * 就继续滚动,不填充view
473 | */
474 | private fun canNotFillScroll(fillDirection: Int, delta: Int): Boolean {
475 | val anchorView = getAnchorView(fillDirection)
476 | return if (fillDirection == FILL_START) {
477 | val start = mOrientationHelper.getDecoratedStart(anchorView)
478 | start + delta < mOrientationHelper.startAfterPadding
479 | } else {
480 | val end = mOrientationHelper.getDecoratedEnd(anchorView)
481 | end - delta > mOrientationHelper.endAfterPadding
482 | }
483 | }
484 |
485 | /**
486 | * 检查是否滚动到了底部或者顶部
487 | */
488 | private fun checkScrollToEdge(
489 | fillDirection: Int,
490 | state: RecyclerView.State
491 | ): Boolean {
492 | if (isLoop) return false
493 | val anchorPosition = getAnchorPosition(fillDirection)
494 | if (fillDirection == FILL_START && anchorPosition == 0) {
495 | return true
496 | }
497 | if (fillDirection == FILL_END && anchorPosition == state.itemCount - 1) {
498 | return true
499 | }
500 | return false
501 | // return anchorPosition == 0 || anchorPosition == (state.itemCount - 1)
502 | }
503 |
504 | private fun getFixLastScroll(fillDirection: Int): Int {
505 | val anchorView = getAnchorView(fillDirection)
506 | return if (fillDirection == FILL_START) {
507 | mOrientationHelper.getDecoratedStart(anchorView) - mOrientationHelper.startAfterPadding - getOffsetSpace()
508 | } else {
509 | mOrientationHelper.getDecoratedEnd(anchorView) - mOrientationHelper.endAfterPadding + getOffsetSpace()
510 | }
511 | }
512 |
513 | /**
514 | * 如果不是循环模式,将要填充的view的position不在合理范围内
515 | * 就返回false
516 | */
517 | private fun hasMore(state: RecyclerView.State): Boolean {
518 | if (isLoop) return true
519 |
520 | return mPendingFillPosition >= 0 && mPendingFillPosition < state.itemCount
521 | }
522 |
523 | /**
524 | * 获取锚点view,fill_end是最后一个,fill_start是第一个
525 | */
526 | private fun getAnchorView(fillDirection: Int): View {
527 | return if (fillDirection == FILL_START) {
528 | getChildAt(0)!!
529 | } else {
530 | getChildAt(childCount - 1)!!
531 | }
532 | }
533 |
534 | /**
535 | * 获取锚点view的position
536 | */
537 | private fun getAnchorPosition(fillDirection: Int): Int {
538 | return getPosition(getAnchorView(fillDirection))
539 | }
540 |
541 | /**
542 | * 获取要开始填充的锚点位置
543 | */
544 | private fun getAnchor(
545 | fillDirection: Int
546 | ): Int {
547 | val anchorView = getAnchorView(fillDirection)
548 | return if (fillDirection == FILL_START) {
549 | mOrientationHelper.getDecoratedStart(anchorView)
550 | } else {
551 | mOrientationHelper.getDecoratedEnd(anchorView)
552 | }
553 | }
554 |
555 | /**
556 | * 获取将要填充的view的position
557 | */
558 | private fun getPendingFillPosition(fillDirection: Int): Int {
559 | return getAnchorPosition(fillDirection) + fillDirection
560 | }
561 |
562 | /**
563 | * 获取下一个view,fill_start就-1,fill_end就是+1
564 | */
565 | private fun nextView(
566 | recycler: RecyclerView.Recycler,
567 | fillDirection: Int
568 | ): View {
569 | val child = getViewForPosition(recycler, mPendingFillPosition)
570 | mPendingFillPosition += fillDirection
571 | return child
572 | }
573 |
574 | /**
575 | * 回收在屏幕外的item view
576 | */
577 | private fun recycleChildren(
578 | delta: Int,
579 | recycler: RecyclerView.Recycler
580 | ) {
581 | if (delta > 0) {
582 | recycleStart()
583 | } else {
584 | recycleEnd()
585 | }
586 |
587 | //
588 | logRecycleChildren()
589 |
590 | //
591 | for (view in mRecycleViews) {
592 | removeAndRecycleView(view, recycler)
593 | }
594 | mRecycleViews.clear()
595 | }
596 |
597 | /**
598 | * 向右或向下移动时,就回收前面部分超出屏幕的子view
599 | */
600 | private fun recycleStart() {
601 | for (i in 0 until childCount) {
602 | val child = getChildAt(i)!!
603 | val end = mOrientationHelper.getDecoratedEnd(child)
604 | if (end < mOrientationHelper.startAfterPadding - getItemOffset()) {
605 | mRecycleViews.add(child)
606 | } else {
607 | break
608 | }
609 | }
610 | }
611 |
612 | /**
613 | * 向左或向上移动时,就回收后面部分超出屏幕的子view
614 | */
615 | private fun recycleEnd() {
616 | for (i in (childCount - 1) downTo 0) {
617 | val child = getChildAt(i)!!
618 | val start = mOrientationHelper.getDecoratedStart(child)
619 | if (start > mOrientationHelper.endAfterPadding + getItemOffset()) {
620 | mRecycleViews.add(child)
621 | } else {
622 | break
623 | }
624 | }
625 | }
626 |
627 | /**
628 | * 获取居中被选中的view
629 | */
630 | private fun getSelectedView(): View? {
631 | return mSnapHelper.findSnapView(this)
632 | }
633 |
634 | /**
635 | * 获取一个item占用的空间,横向为宽,竖向为高
636 | */
637 | private fun getItemSpace() = if (orientation == HORIZONTAL) {
638 | mItemWidth
639 | } else {
640 | mItemHeight
641 | }
642 |
643 | /**
644 | * 获取已经滚动过的偏移量,在软键盘弹出
645 | * 重新onLayoutChildren的时候有用
646 | * 这个暂时用不到了,因为加了自动居中,在LinearLayoutManager中
647 | * 还是用到了的
648 | */
649 | private fun getScrollOffset() {
650 |
651 | }
652 |
653 | /**
654 | * 增加一个偏移量让滚动顺滑点
655 | */
656 | private fun getItemOffset() = getItemSpace() / 2
657 |
658 | /**
659 | * 获取开始item距离开始位置的偏移量
660 | * 或者结束item距离尾端的偏移量
661 | */
662 | private fun getOffsetSpace(): Int {
663 | return getOffsetCount() * getItemSpace()
664 | }
665 |
666 | /**
667 | * 根据position获取一个item view
668 | */
669 | private fun getViewForPosition(
670 | recycler: RecyclerView.Recycler,
671 | position: Int
672 | ): View {
673 | //加入不是循环模式,且position不合法
674 | if (!isLoop && (position < 0 || position >= itemCount)) {
675 | throw IllegalArgumentException("position <0 or >= itemCount with !isLoop")
676 | }
677 |
678 | //假设itemCount==100
679 | //[0,99] -- 100=0,101=1,102=2
680 | if (isLoop && position > itemCount - 1) {
681 | return recycler.getViewForPosition(position % itemCount)
682 | }
683 |
684 | //[0,99] -- -1=99,-2=98,-3=97...-99=1,-100=0
685 | // -101=99(-1)
686 | if (isLoop && position < 0) {
687 | // return recycler.getViewForPosition(itemCount + (position % itemCount))
688 | return recycler.getViewForPosition(itemCount + position)
689 | }
690 |
691 | return recycler.getViewForPosition(position)
692 | }
693 |
694 | /**
695 | * 检查toPosition是否合法
696 | */
697 | private fun checkToPosition(position: Int) {
698 | if (position < 0 || position > itemCount - 1)
699 | throw IllegalArgumentException("position is $position,must be >= 0 and < itemCount,")
700 | }
701 |
702 | /**
703 | * 因为scrollTo是要居中,所以这里要fix一下
704 | */
705 | private fun fixSmoothToPosition(toPosition: Int): Int {
706 | val fixCount = getOffsetCount()
707 | val centerPosition = getSelectedPosition()
708 | return if (centerPosition < toPosition) toPosition + fixCount else toPosition - fixCount
709 | }
710 |
711 | /**
712 | * 分发回调OnItemSelectedListener
713 | */
714 | private fun dispatchOnItemSelectedListener(position: Int = getSelectedPosition()) {
715 | if (mOnItemSelectedListener.isEmpty() || position < 0) return
716 |
717 | for (listener in mOnItemSelectedListener) {
718 | listener.invoke(position)
719 | }
720 | }
721 |
722 | /**
723 | * 滚动到中间的item
724 | */
725 | private fun scrollToCenter(centerView: View, centerPosition: Int) {
726 | val destination =
727 | mOrientationHelper.totalSpace / 2 - mOrientationHelper.getDecoratedMeasurement(
728 | centerView
729 | ) / 2
730 | val distance = destination - mOrientationHelper.getDecoratedStart(centerView)
731 |
732 | //平滑动画的滚动到中心
733 | // smoothOffsetChildren(distance, centerPosition)
734 | //直接滚动到中心
735 | mOrientationHelper.offsetChildren(distance)
736 | dispatchOnItemSelectedListener(centerPosition)
737 | }
738 |
739 | /**
740 | * 加动画平滑的移动
741 | */
742 | private fun smoothOffsetChildren(amount: Int, centerPosition: Int) {
743 | var lastValue = amount
744 | val animator = ValueAnimator.ofInt(amount, 0).apply {
745 | interpolator = LinearInterpolator()
746 | duration = 300
747 | }
748 | animator.addUpdateListener {
749 | val value = it.animatedValue as Int
750 | mOrientationHelper.offsetChildren(lastValue - value)
751 | lastValue = value
752 | }
753 | animator.addListener(object : Animator.AnimatorListener {
754 | override fun onAnimationRepeat(animation: Animator?) {
755 | }
756 |
757 | override fun onAnimationEnd(animation: Animator?) {
758 |
759 | }
760 |
761 | override fun onAnimationCancel(animation: Animator?) {
762 | }
763 |
764 | override fun onAnimationStart(animation: Animator?) {
765 | dispatchOnItemSelectedListener(centerPosition)
766 | }
767 | })
768 | animator.start()
769 | }
770 |
771 | /**
772 | * 添加中心item选中的监听器
773 | */
774 | fun addOnItemSelectedListener(listener: OnItemSelectedListener) {
775 | mOnItemSelectedListener.add(listener)
776 | }
777 |
778 | /**
779 | * 删除中心item选中的监听器
780 | */
781 | fun removeOnItemSelectedListener(listener: OnItemSelectedListener) {
782 | mOnItemSelectedListener.remove(listener)
783 | }
784 |
785 | /**
786 | * 删除所有的监听器
787 | */
788 | fun removeAllOnItemSelectedListener() {
789 | mOnItemSelectedListener.clear()
790 | }
791 |
792 | /**
793 | * 获取被选中的position
794 | */
795 | fun getSelectedPosition(): Int {
796 | if (childCount == 0) return RecyclerView.NO_POSITION
797 | val centerView = getSelectedView() ?: return RecyclerView.NO_POSITION
798 | return getPosition(centerView)
799 | }
800 |
801 | /**
802 | * 变换子view,缩放或增加透明度
803 | */
804 | open fun transformChildren() {
805 | if (childCount == 0) return
806 |
807 | val centerView = getSelectedView() ?: return
808 | val centerPosition = getPosition(centerView)
809 |
810 | if (childCount == 0) return
811 | for (i in 0 until childCount) {
812 | val child = getChildAt(i)!!
813 | val position = getPosition(child)
814 | if (position == centerPosition) {
815 | child.scaleX = 1f
816 | child.scaleY = 1f
817 | child.alpha = 1.0f
818 | } else {
819 | val scaleX = getScale(this.scaleX, getIntervalCount(centerPosition, position))
820 | val scaleY = getScale(this.scaleY, getIntervalCount(centerPosition, position))
821 | child.scaleX = scaleX
822 | child.scaleY = scaleY
823 | child.alpha = this.alpha
824 | }
825 | }
826 | }
827 |
828 | private fun getScale(scale: Float, intervalCount: Int): Float {
829 | if (scale == 1.0f) return scale
830 | // return scale / intervalCount
831 | return 1 - (1 - scale) * intervalCount
832 | }
833 |
834 | /**
835 | * 获取两个position中间相差的item个数
836 | */
837 | private fun getIntervalCount(
838 | centerPosition: Int,
839 | position: Int
840 | ): Int {
841 | if (!isLoop)
842 | return abs(centerPosition - position)
843 |
844 | //例如:position=100,centerPosition=0这种情况
845 | if (position > centerPosition && position - centerPosition > visibleCount)
846 | return itemCount - position
847 |
848 | //例如:position=0,centerPosition=100这种情况
849 | if (position < centerPosition && centerPosition - position > visibleCount)
850 | return position + 1
851 |
852 | return abs(position - centerPosition)
853 | }
854 |
855 | /**
856 | * 分发OnItemFillListener事件
857 | */
858 | private fun dispatchOnItemFillListener() {
859 | if (childCount == 0 || mOnItemFillListener.isEmpty()) return
860 |
861 | val centerView = getSelectedView() ?: return
862 | val centerPosition = getPosition(centerView)
863 |
864 | for (i in 0 until childCount) {
865 | val child = getChildAt(i) ?: continue
866 | val position = getPosition(child)
867 |
868 | if (position == centerPosition) {
869 | onItemSelected(child, position)
870 | } else {
871 | onItemUnSelected(child, position)
872 | }
873 | }
874 | }
875 |
876 | /**
877 | * item选中回调
878 | */
879 | open fun onItemSelected(child: View, position: Int) {
880 | for (listener in mOnItemFillListener) {
881 | listener.onItemSelected(child, position)
882 | }
883 | }
884 |
885 | /**
886 | * item取消选中
887 | */
888 | open fun onItemUnSelected(child: View, position: Int) {
889 | for (listener in mOnItemFillListener) {
890 | listener.onItemUnSelected(child, position)
891 | }
892 | }
893 |
894 | /**
895 | * 当item填充或者滚动的时候回调
896 | */
897 | interface OnItemFillListener {
898 | fun onItemSelected(itemView: View, position: Int)
899 | fun onItemUnSelected(itemView: View, position: Int)
900 | }
901 |
902 | /**
903 | *
904 | */
905 | fun addOnItemFillListener(listener: OnItemFillListener) {
906 | mOnItemFillListener.add(listener)
907 | }
908 |
909 | /**
910 | *
911 | */
912 | fun removeOnItemFillListener(listener: OnItemFillListener) {
913 | mOnItemFillListener.remove(listener)
914 | }
915 |
916 | /**
917 | *
918 | */
919 | fun removeAllItemFillListener() {
920 | mOnItemFillListener.clear()
921 | }
922 |
923 | /**
924 | * 搞个Builder模式,构造函数难得写就用这个
925 | */
926 | class Builder {
927 | private var orientation = VERTICAL
928 | private var visibleCount = 3
929 | private var isLoop = false
930 | private var scaleX = 1.0f
931 | private var scaleY = 1.0f
932 | private var alpha = 1.0f
933 |
934 | fun setOrientation(orientation: Int) {
935 | this.orientation = orientation
936 | }
937 |
938 | fun setVisibleCount(visibleCount: Int) {
939 | this.visibleCount = visibleCount
940 | }
941 |
942 | fun setIsLoop(isLoop: Boolean) {
943 | this.isLoop = isLoop
944 | }
945 |
946 | fun setScaleX(@FloatRange(from = 0.0, to = 1.0) scaleX: Float) {
947 | this.scaleX = scaleX
948 | }
949 |
950 | fun setScaleY(@FloatRange(from = 0.0, to = 1.0) scaleY: Float) {
951 | this.scaleY = scaleY
952 | }
953 |
954 | fun setAlpha(@FloatRange(from = 0.0, to = 1.0) alpha: Float) {
955 | this.alpha = alpha
956 | }
957 |
958 | fun build() = PickerLayoutManager(orientation, visibleCount, isLoop, scaleX, scaleY, alpha)
959 | }
960 |
961 |
962 | // 用来测试的方法--------------------------------------------------
963 |
964 | private fun logDebug(msg: String) {
965 | if (!DEBUG) return
966 | Log.d(TAG, "${hashCode()} -- " + msg)
967 | }
968 |
969 | private fun logChildCount(recycler: RecyclerView.Recycler) {
970 | if (!DEBUG) return
971 | logDebug("childCount == $childCount -- scrapSize == ${recycler.scrapList.size}")
972 | logChildrenPosition()
973 | }
974 |
975 | private fun logChildrenPosition() {
976 | if (!DEBUG) return
977 |
978 | val builder = StringBuilder()
979 | for (i in 0 until childCount) {
980 | val child = getChildAt(i)
981 | builder.append(getPosition(child!!))
982 | builder.append(",")
983 | }
984 |
985 | logDebug("children == $builder")
986 | }
987 |
988 | //
989 | private fun logRecycleChildren() {
990 | if (!DEBUG) return
991 |
992 | val builder = StringBuilder()
993 | for (child in mRecycleViews) {
994 | builder.append(getPosition(child))
995 | builder.append(",")
996 | }
997 |
998 | if (builder.isEmpty()) return
999 | logDebug("recycle children == $builder")
1000 | }
1001 | }
--------------------------------------------------------------------------------
/picker_layoutmanager/src/main/java/me/simple/picker/PickerRecyclerView.kt:
--------------------------------------------------------------------------------
1 | package me.simple.picker
2 |
3 | import android.content.Context
4 | import android.graphics.Color
5 | import android.util.AttributeSet
6 | import androidx.annotation.ColorInt
7 | import androidx.annotation.Px
8 | import androidx.recyclerview.widget.DividerItemDecoration
9 | import androidx.recyclerview.widget.RecyclerView
10 |
11 | /**
12 | * 包装PickerLayoutManager的PickerRecyclerView
13 | */
14 | open class PickerRecyclerView @JvmOverloads constructor(
15 | context: Context,
16 | attrs: AttributeSet? = null,
17 | defStyleAttr: Int = 0
18 | ) : RecyclerView(context, attrs, defStyleAttr) {
19 |
20 | companion object {
21 | const val DIVIDER_VISIBLE = true
22 | const val DIVIDER_SIZE = 1.0f
23 | const val DIVIDER_COLOR = Color.LTGRAY
24 | const val DIVIDER_MARGIN = 0f
25 | }
26 |
27 | var mOrientation = PickerLayoutManager.VERTICAL
28 | var mVisibleCount = PickerLayoutManager.VISIBLE_COUNT
29 | var mIsLoop = PickerLayoutManager.IS_LOOP
30 | var mScaleX = PickerLayoutManager.SCALE_X
31 | var mScaleY = PickerLayoutManager.SCALE_Y
32 | var mAlpha = PickerLayoutManager.ALPHA
33 |
34 | var mDividerVisible = DIVIDER_VISIBLE
35 | var mDividerSize = DIVIDER_SIZE
36 | var mDividerColor = DIVIDER_COLOR
37 | var mDividerMargin = DIVIDER_MARGIN
38 |
39 | private var mDecor: PickerItemDecoration? = null
40 |
41 | init {
42 | initAttrs(attrs)
43 | resetLayoutManager(mOrientation, mVisibleCount, mIsLoop, mScaleX, mScaleY, mAlpha)
44 | }
45 |
46 | open fun initAttrs(attrs: AttributeSet?) {
47 | val typeA = context.obtainStyledAttributes(
48 | attrs,
49 | R.styleable.PickerRecyclerView
50 | )
51 |
52 | mOrientation = typeA.getInt(R.styleable.PickerRecyclerView_orientation, mOrientation)
53 | mVisibleCount = typeA.getInt(R.styleable.PickerRecyclerView_visibleCount, mVisibleCount)
54 | mIsLoop = typeA.getBoolean(R.styleable.PickerRecyclerView_isLoop, mIsLoop)
55 | mScaleX = typeA.getFloat(R.styleable.PickerRecyclerView_scaleX, mScaleX)
56 | mScaleY = typeA.getFloat(R.styleable.PickerRecyclerView_scaleY, mScaleY)
57 | mAlpha = typeA.getFloat(R.styleable.PickerRecyclerView_alpha, mAlpha)
58 |
59 | mDividerVisible =
60 | typeA.getBoolean(R.styleable.PickerRecyclerView_dividerVisible, mDividerVisible)
61 | mDividerSize =
62 | typeA.getDimension(R.styleable.PickerRecyclerView_dividerSize, mDividerSize)
63 | mDividerColor =
64 | typeA.getColor(R.styleable.PickerRecyclerView_dividerColor, mDividerColor)
65 | mDividerMargin =
66 | typeA.getDimension(R.styleable.PickerRecyclerView_dividerMargin, mDividerMargin)
67 |
68 | typeA.recycle()
69 | }
70 |
71 | /**
72 | * 重新设置LayoutManager
73 | */
74 | open fun resetLayoutManager(
75 | orientation: Int = mOrientation,
76 | visibleCount: Int = mVisibleCount,
77 | isLoop: Boolean = mIsLoop,
78 | scaleX: Float = mScaleX,
79 | scaleY: Float = mScaleY,
80 | alpha: Float = mAlpha
81 | ) {
82 | val lm = PickerLayoutManager(
83 | orientation,
84 | visibleCount,
85 | isLoop,
86 | scaleX,
87 | scaleY,
88 | alpha
89 | )
90 | resetLayoutManager(lm)
91 | }
92 |
93 | open fun resetLayoutManager(lm: PickerLayoutManager) {
94 | this.layoutManager = lm
95 | }
96 |
97 | override fun setLayoutManager(layout: LayoutManager?) {
98 | super.setLayoutManager(layout)
99 | initDivider()
100 | if (layout !is PickerLayoutManager) {
101 | throw IllegalArgumentException("LayoutManager only can use PickerLayoutManager")
102 | }
103 | }
104 |
105 | /**
106 | * 获取选中的那个item的position
107 | */
108 | open fun getSelectedPosition() = layoutManager.getSelectedPosition()
109 |
110 | /**
111 | *
112 | */
113 | override fun getLayoutManager(): PickerLayoutManager {
114 | return super.getLayoutManager() as PickerLayoutManager
115 | }
116 |
117 | //重新设置属性值
118 |
119 | open fun setOrientation(orientation: Int) {
120 | this.mOrientation = orientation
121 | }
122 |
123 | open fun setVisibleCount(count: Int) {
124 | this.mVisibleCount = count
125 | }
126 |
127 | open fun setIsLoop(isLoop: Boolean) {
128 | this.mIsLoop = isLoop
129 | }
130 |
131 | open fun setItemScaleX(scaleX: Float) {
132 | this.mScaleX = scaleX
133 | }
134 |
135 | open fun setItemScaleY(scaleY: Float) {
136 | this.mScaleY = scaleY
137 | }
138 |
139 | open fun setItemAlpha(alpha: Float) {
140 | this.mAlpha = alpha
141 | }
142 |
143 | open fun setDividerVisible(visible: Boolean) {
144 | this.mDividerVisible = visible
145 | }
146 |
147 | open fun setDividerSize(@Px size: Float) {
148 | this.mDividerSize = size
149 | }
150 |
151 | open fun setDividerColor(@ColorInt color: Int) {
152 | this.mDividerColor = color
153 | }
154 |
155 | open fun setDividerMargin(margin: Float) {
156 | this.mDividerMargin = margin
157 | }
158 |
159 | //设置分割线
160 | open fun initDivider() {
161 | removeDivider()
162 |
163 | if (!mDividerVisible) return
164 | mDecor = PickerItemDecoration(mDividerColor, mDividerSize, mDividerMargin)
165 | this.addItemDecoration(mDecor!!)
166 | }
167 |
168 | //删除分割线
169 | open fun removeDivider() {
170 | mDecor?.let { removeItemDecoration(it) }
171 | }
172 |
173 | /**
174 | *
175 | */
176 | fun addOnSelectedItemListener(listener: OnItemSelectedListener) {
177 | layoutManager.addOnItemSelectedListener(listener)
178 | }
179 |
180 | /**
181 | *
182 | */
183 | fun removeOnItemSelectedListener(listener: OnItemSelectedListener) {
184 | layoutManager.removeOnItemSelectedListener(listener)
185 | }
186 |
187 | /**
188 | * 删除所有的监听器
189 | */
190 | fun removeAllOnItemSelectedListener() {
191 | layoutManager.removeAllOnItemSelectedListener()
192 | }
193 |
194 | /**
195 | *
196 | */
197 | fun addOnItemFillListener(listener: PickerLayoutManager.OnItemFillListener) {
198 | layoutManager.addOnItemFillListener(listener)
199 | }
200 |
201 | /**
202 | *
203 | */
204 | fun removeOnItemFillListener(listener: PickerLayoutManager.OnItemFillListener) {
205 | layoutManager.removeOnItemFillListener(listener)
206 | }
207 |
208 | /**
209 | *
210 | */
211 | fun removeAllItemFillListener() {
212 | layoutManager.removeAllItemFillListener()
213 | }
214 |
215 | /**
216 | * 滚动到最后一个item
217 | */
218 | fun scrollToEnd() {
219 | if (adapter == null) return
220 | this.post {
221 | this.scrollToPosition(adapter!!.itemCount - 1)
222 | }
223 | }
224 |
225 | /**
226 | * 平滑的滚动到最后一个item
227 | */
228 | fun smoothScrollToEnd() {
229 | if (adapter == null) return
230 | this.post {
231 | this.scrollToPosition(adapter!!.itemCount - 1)
232 | }
233 | }
234 | }
--------------------------------------------------------------------------------
/picker_layoutmanager/src/main/java/me/simple/picker/datepicker/DatePickerView.kt:
--------------------------------------------------------------------------------
1 | package me.simple.picker.datepicker
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import me.simple.picker.OnItemSelectedListener
6 | import me.simple.picker.widget.TextPickerLinearLayout
7 | import me.simple.picker.utils.PickerUtils
8 | import java.util.*
9 | import kotlin.IllegalArgumentException
10 |
11 | /**
12 | * 日期类型的PickerView
13 | */
14 | open class DatePickerView @JvmOverloads constructor(
15 | context: Context,
16 | attrs: AttributeSet? = null,
17 | defStyleAttr: Int = 0
18 | ) : TextPickerLinearLayout(context, attrs, defStyleAttr) {
19 |
20 | val yearPickerView = YearPickerView(context)
21 | val monthPickerView = MonthPickerView(context)
22 | val dayPickerView = DayPickerView(context)
23 |
24 | private var mSelectedListener1: ((
25 | year: String,
26 | month: String,
27 | day: String
28 | ) -> Unit)? = null
29 |
30 | private var mSelectedListener2: ((calendar: Calendar) -> Unit)? = null
31 |
32 | private var mStartYear: Int = PickerUtils.START_YEAR
33 | private var mStartMonth: Int = PickerUtils.START_YEAR
34 | private var mStartDay: Int = PickerUtils.START_YEAR
35 |
36 | private var mEndYear: Int = PickerUtils.getCurrentYear()
37 | private var mEndMonth: Int = PickerUtils.getCurrentMonth()
38 | private var mEndDay: Int = PickerUtils.getCurrentDay()
39 |
40 | private val mYearOnSelectedItemListener: OnItemSelectedListener = { _ ->
41 | val year = yearPickerView.getYear()
42 | when (year) {
43 | mStartYear -> {
44 | monthPickerView.setMonthInterval(mStartMonth)
45 | }
46 | mEndYear -> {
47 | monthPickerView.setMonthInterval(endMonth = mEndMonth)
48 | }
49 | else -> {
50 | monthPickerView.setMonthInterval()
51 | }
52 | }
53 |
54 | monthPickerView.post {
55 | val month = monthPickerView.getMonth()
56 | setDayInterval(year, month)
57 |
58 | dispatchOnItemSelected()
59 | }
60 | }
61 |
62 | private val mMonthOnSelectedItemListener: OnItemSelectedListener = { _ ->
63 | val year = yearPickerView.getYear()
64 | val month = monthPickerView.getMonth()
65 |
66 | setDayInterval(year, month)
67 |
68 | dispatchOnItemSelected()
69 | }
70 |
71 | private val mDayOnSelectedItemListener: OnItemSelectedListener = { _ ->
72 | dispatchOnItemSelected()
73 | }
74 |
75 | init {
76 | weightSum = 3f
77 |
78 | addViewInLayout(yearPickerView, 0, generateDefaultLayoutParams(), true)
79 | addViewInLayout(monthPickerView, 1, generateDefaultLayoutParams(), true)
80 | addViewInLayout(dayPickerView, 2, generateDefaultLayoutParams(), true)
81 | requestLayout()
82 |
83 | resetLayoutManager()
84 |
85 | setDateInterval()
86 | }
87 |
88 | override fun resetLayoutManager() {
89 | super.resetLayoutManager()
90 | setListener()
91 | }
92 |
93 | /**
94 | * 设置年月日item的监听
95 | */
96 | private fun setListener() {
97 | yearPickerView.addOnSelectedItemListener(mYearOnSelectedItemListener)
98 |
99 | monthPickerView.addOnSelectedItemListener(mMonthOnSelectedItemListener)
100 |
101 | dayPickerView.addOnSelectedItemListener(mDayOnSelectedItemListener)
102 | }
103 |
104 | /**
105 | * 设置日期-天的区间
106 | */
107 | private fun setDayInterval(
108 | year: Int,
109 | month: Int
110 | ) {
111 | if (year == mStartYear && month == mStartMonth) {
112 | val endDay = PickerUtils.getDayCountInMonth(year, month)
113 | dayPickerView.setDayInterval(mStartDay, endDay)
114 | } else if (year == mEndYear && month == mEndMonth) {
115 | dayPickerView.setDayInterval(endDay = mEndDay)
116 | } else {
117 | dayPickerView.setDayIntervalByMonth(year, month)
118 | }
119 | }
120 |
121 | /**
122 | *
123 | */
124 | private val mDispatchOnItemSelectedRun = Runnable {
125 | val year = yearPickerView.getYearStr()
126 | val month = monthPickerView.getMonthStr()
127 | val day = dayPickerView.getDayStr()
128 |
129 | mSelectedListener1?.invoke(year, month, day)
130 |
131 | mSelectedListener2?.let {
132 | val calendar = Calendar.getInstance()
133 | calendar.set(
134 | year.toInt(), month.toInt() - 1, day.toInt(),
135 | 0, 0, 0
136 | )
137 | it.invoke(calendar)
138 | }
139 | }
140 |
141 | /**
142 | * 分发item选中事件
143 | */
144 | private fun dispatchOnItemSelected() {
145 | this.post(mDispatchOnItemSelectedRun)
146 | }
147 |
148 | /**
149 | * 设置日期时间的区间
150 | */
151 | fun setDateInterval(
152 | start: Calendar = PickerUtils.getStartCalendar(),
153 | end: Calendar = PickerUtils.getCurrentCalendar()
154 | ) {
155 | setDateInterval(
156 | PickerUtils.getYear(start), PickerUtils.getMonth(start), PickerUtils.getDay(start),
157 | PickerUtils.getYear(end), PickerUtils.getMonth(end), PickerUtils.getDay(end)
158 | )
159 | }
160 |
161 | /**
162 | * 设置日期时间的区间
163 | */
164 | fun setDateInterval(
165 | startYear: Int = PickerUtils.START_YEAR,
166 | startMonth: Int = PickerUtils.START_MONTH,
167 | startDay: Int = PickerUtils.START_DAY,
168 |
169 | endYear: Int = PickerUtils.getCurrentYear(),
170 | endMonth: Int = PickerUtils.getCurrentMonth(),
171 | endDay: Int = PickerUtils.getCurrentDay()
172 | ) {
173 | this.mStartYear = startYear
174 | this.mStartMonth = startMonth
175 | this.mStartDay = startDay
176 |
177 | this.mEndYear = endYear
178 | this.mEndMonth = endMonth
179 | this.mEndDay = endDay
180 |
181 | yearPickerView.setYearInterval(startYear, endYear)
182 | }
183 |
184 | /**
185 | * 选中结束时间
186 | */
187 | override fun scrollToEnd() {
188 | selectedEndItem()
189 | }
190 |
191 | /**
192 | * 选中结束时间
193 | */
194 | fun selectedEndItem() {
195 | yearPickerView.post {
196 | yearPickerView.selectedEndItem()
197 | monthPickerView.post {
198 | monthPickerView.selectedEndItem()
199 | dayPickerView.post {
200 | dayPickerView.selectedEndItem()
201 | }
202 | }
203 | }
204 | }
205 |
206 | /**
207 | * 选中当前时间的那个item
208 | */
209 | @Deprecated("方法名不合理", ReplaceWith("selectedTodayItem"))
210 | fun scrollToCurrentDate() {
211 | selectedTodayItem()
212 | }
213 |
214 | /**
215 | * 选中今天的那个item
216 | */
217 | fun selectedTodayItem() {
218 | val currentCalendar = PickerUtils.getCurrentCalendar()
219 | selectedItem(currentCalendar)
220 | }
221 |
222 | /**
223 | * 选中某一个item
224 | */
225 | fun selectedItem(date: Date) {
226 | val calendar = Calendar.getInstance()
227 | calendar.time = date
228 | selectedItem(calendar)
229 | }
230 |
231 | /**
232 | * 选中某一个item
233 | */
234 | fun selectedItem(calendar: Calendar) {
235 | val year = PickerUtils.getYear(calendar)
236 | val month = PickerUtils.getMonth(calendar)
237 | val day = PickerUtils.getDay(calendar)
238 | selectedItem(year, month, day)
239 | }
240 |
241 | /**
242 | * 选中某一个item
243 | */
244 | fun selectedItem(
245 | year: Int,
246 | month: Int,
247 | day: Int
248 | ) {
249 | if (year < mStartYear || year > mEndYear) {
250 | throw IllegalArgumentException("year must be >= $mStartYear and <= $mEndYear")
251 | }
252 |
253 | if (year == mStartYear && month < mStartMonth) {
254 | throw IllegalArgumentException("month must be >= $mStartMonth")
255 | }
256 | if (year == mEndYear && month > mEndMonth) {
257 | throw IllegalArgumentException("month must be <= $mEndMonth")
258 | }
259 |
260 | if (year == mStartYear && month == mStartMonth && day < mStartDay) {
261 | throw IllegalArgumentException("day must be >= $mStartDay")
262 | }
263 | if (year == mEndYear && month == mEndMonth && day > mEndDay) {
264 | throw IllegalArgumentException("day must be <= $mEndDay")
265 | }
266 |
267 | yearPickerView.post {
268 | yearPickerView.selectedItem(year)
269 | monthPickerView.post {
270 | monthPickerView.selectedItem(month)
271 | dayPickerView.post {
272 | dayPickerView.selectedItem(day)
273 | }
274 | }
275 | }
276 | }
277 |
278 | /**
279 | * 获取当前选中时间的Calendar
280 | */
281 | fun getCalendar(): Calendar = Calendar.getInstance().apply {
282 | set(
283 | yearPickerView.getYear(), monthPickerView.getMonth() - 1, dayPickerView.getDay(),
284 | 0, 0, 0
285 | )
286 | }
287 |
288 | /**
289 | * 获取当前选中时间的Date
290 | */
291 | fun getDate(): Date = getCalendar().time
292 |
293 | /**
294 | * 获取年月日的字符串数组
295 | */
296 | fun getYearMonthDay() = arrayOf(
297 | yearPickerView.getYearStr(),
298 | monthPickerView.getMonthStr(),
299 | dayPickerView.getDayStr()
300 | )
301 |
302 | /**
303 | * 日期选中的监听
304 | */
305 | fun setOnDateSelectedListener(onSelected: (year: String, month: String, day: String) -> Unit) {
306 | this.mSelectedListener1 = onSelected
307 | }
308 |
309 | /**
310 | * 日期选中的监听
311 | */
312 | fun setOnDateSelectedListener(onSelected: (calendar: Calendar) -> Unit) {
313 | this.mSelectedListener2 = onSelected
314 | }
315 | }
--------------------------------------------------------------------------------
/picker_layoutmanager/src/main/java/me/simple/picker/datepicker/DayPickerView.kt:
--------------------------------------------------------------------------------
1 | package me.simple.picker.datepicker
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import me.simple.picker.utils.PickerUtils
6 | import me.simple.picker.widget.TextPickerView
7 |
8 | open class DayPickerView @JvmOverloads constructor(
9 | context: Context,
10 | attrs: AttributeSet? = null,
11 | defStyleAttr: Int = 0
12 | ) : TextPickerView(context, attrs, defStyleAttr) {
13 |
14 | init {
15 | setDayInterval()
16 | }
17 |
18 | fun setDayInterval(
19 | startDay: Int = 1,
20 | endDay: Int = 31
21 | ) {
22 | mItems.clear()
23 | for (day in startDay..endDay) {
24 | mItems.add(PickerUtils.formatTwoChars(day))
25 | }
26 | adapter?.notifyDataSetChanged()
27 | }
28 |
29 | fun setDayIntervalByMonth(
30 | year: Int,
31 | month: Int,
32 | startDay: Int = 1
33 | ) {
34 | setDayInterval(startDay, endDay = PickerUtils.getDayCountInMonth(year, month))
35 | }
36 |
37 | fun getDayStr() = getSelectedItem()
38 |
39 | fun getDay() = getDayStr().toInt()
40 |
41 | fun selectedItem(number: Int) {
42 | selectedItem(PickerUtils.formatTwoChars(number))
43 | }
44 | }
--------------------------------------------------------------------------------
/picker_layoutmanager/src/main/java/me/simple/picker/datepicker/MonthPickerView.kt:
--------------------------------------------------------------------------------
1 | package me.simple.picker.datepicker
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import me.simple.picker.utils.PickerUtils
6 | import me.simple.picker.widget.TextPickerView
7 |
8 | open class MonthPickerView @JvmOverloads constructor(
9 | context: Context,
10 | attrs: AttributeSet? = null,
11 | defStyleAttr: Int = 0
12 | ) : TextPickerView(context, attrs, defStyleAttr) {
13 |
14 | init {
15 | setMonthInterval()
16 | }
17 |
18 | fun setMonthInterval(
19 | startMonth: Int = 1,
20 | endMonth: Int = 12
21 | ) {
22 | mItems.clear()
23 | for (month in startMonth..endMonth) {
24 | mItems.add(PickerUtils.formatTwoChars(month))
25 | }
26 | adapter?.notifyDataSetChanged()
27 | }
28 |
29 | fun getMonthStr() = getSelectedItem()
30 |
31 | fun getMonth() = getSelectedItem().toInt()
32 |
33 | fun selectedItem(number: Int) {
34 | selectedItem(PickerUtils.formatTwoChars(number))
35 | }
36 | }
--------------------------------------------------------------------------------
/picker_layoutmanager/src/main/java/me/simple/picker/datepicker/YearPickerView.kt:
--------------------------------------------------------------------------------
1 | package me.simple.picker.datepicker
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import me.simple.picker.utils.PickerUtils
6 | import me.simple.picker.widget.TextPickerView
7 |
8 | open class YearPickerView @JvmOverloads constructor(
9 | context: Context,
10 | attrs: AttributeSet? = null,
11 | defStyleAttr: Int = 0
12 | ) : TextPickerView(context, attrs, defStyleAttr) {
13 |
14 | init {
15 | setYearInterval()
16 | }
17 |
18 | fun setYearInterval(
19 | startYear: Int = PickerUtils.START_YEAR,
20 | endYear: Int = PickerUtils.getCurrentYear()
21 | ) {
22 | mItems.clear()
23 | for (year in startYear..endYear) {
24 | mItems.add(year.toString())
25 | }
26 | adapter?.notifyDataSetChanged()
27 | }
28 |
29 | fun getYear(position: Int): Int {
30 | return mItems[position].toInt()
31 | }
32 |
33 | fun getYear(): Int {
34 | return getYearStr().toInt()
35 | }
36 |
37 | fun getYearStr(): String {
38 | return getSelectedItem()
39 | }
40 |
41 | fun selectedItem(number: Int) {
42 | selectedItem(number.toString())
43 | }
44 | }
--------------------------------------------------------------------------------
/picker_layoutmanager/src/main/java/me/simple/picker/timepicker/HourPickerView.kt:
--------------------------------------------------------------------------------
1 | package me.simple.picker.timepicker
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import me.simple.picker.utils.PickerUtils
6 | import me.simple.picker.widget.TextPickerView
7 |
8 | open class HourPickerView @JvmOverloads constructor(
9 | context: Context,
10 | attrs: AttributeSet? = null,
11 | defStyleAttr: Int = 0
12 | ) : TextPickerView(context, attrs, defStyleAttr) {
13 |
14 | init {
15 | setHourInterval()
16 | }
17 |
18 | @SuppressWarnings
19 | fun setHourInterval(
20 | start: Int = PickerUtils.START_HOUR,
21 | end: Int = PickerUtils.END_HOUR
22 | ) {
23 | mItems.clear()
24 | for (hour in start..end) {
25 | mItems.add(PickerUtils.formatTwoChars(hour))
26 | }
27 | adapter?.notifyDataSetChanged()
28 | }
29 |
30 | fun getHourStr() = getSelectedItem()
31 |
32 | fun getHour() = getHourStr().toInt()
33 | }
--------------------------------------------------------------------------------
/picker_layoutmanager/src/main/java/me/simple/picker/timepicker/MinutePickerView.kt:
--------------------------------------------------------------------------------
1 | package me.simple.picker.timepicker
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import me.simple.picker.utils.PickerUtils
6 | import me.simple.picker.widget.TextPickerView
7 |
8 | open class MinutePickerView @JvmOverloads constructor(
9 | context: Context,
10 | attrs: AttributeSet? = null,
11 | defStyleAttr: Int = 0
12 | ) : TextPickerView(context, attrs, defStyleAttr) {
13 |
14 | init {
15 | setMinuteInterval()
16 | }
17 |
18 | fun setMinuteInterval(
19 | start: Int = PickerUtils.START_MINUTE,
20 | end: Int = PickerUtils.END_MINUTE
21 | ) {
22 | mItems.clear()
23 | for (minute in start..end) {
24 | mItems.add(PickerUtils.formatTwoChars(minute))
25 | }
26 | adapter!!.notifyDataSetChanged()
27 | }
28 |
29 | fun getMinuteStr() = getSelectedItem()
30 |
31 | fun getMinute() = getMinuteStr().toInt()
32 | }
--------------------------------------------------------------------------------
/picker_layoutmanager/src/main/java/me/simple/picker/timepicker/SecondPickerView.kt:
--------------------------------------------------------------------------------
1 | package me.simple.picker.timepicker
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import me.simple.picker.utils.PickerUtils
6 | import me.simple.picker.widget.TextPickerView
7 |
8 | open class SecondPickerView @JvmOverloads constructor(
9 | context: Context,
10 | attrs: AttributeSet? = null,
11 | defStyleAttr: Int = 0
12 | ) : TextPickerView(context, attrs, defStyleAttr) {
13 |
14 | init {
15 | setMinuteInterval()
16 | }
17 |
18 | fun setMinuteInterval(start: Int = 0, end: Int = 60) {
19 | mItems.clear()
20 | for (minute in start until end) {
21 | mItems.add(PickerUtils.formatTwoChars(minute))
22 | }
23 | adapter?.notifyDataSetChanged()
24 | }
25 |
26 | fun getSecond() = getSelectedItem()
27 | }
--------------------------------------------------------------------------------
/picker_layoutmanager/src/main/java/me/simple/picker/timepicker/TimePickerView.kt:
--------------------------------------------------------------------------------
1 | package me.simple.picker.timepicker
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import me.simple.picker.OnItemSelectedListener
6 | import me.simple.picker.widget.TextPickerLinearLayout
7 | import me.simple.picker.utils.PickerUtils
8 | import java.util.*
9 |
10 | open class TimePickerView @JvmOverloads constructor(
11 | context: Context,
12 | attrs: AttributeSet? = null,
13 | defStyleAttr: Int = 0
14 | ) : TextPickerLinearLayout(context, attrs, defStyleAttr) {
15 |
16 | private val hourPickerView = HourPickerView(context)
17 |
18 | private val minutePickerView = MinutePickerView(context)
19 |
20 | private var mOnTimeSelectedListener1: ((hour: String, minute: String) -> Unit)? = null
21 | private var mOnTimeSelectedListener2: ((calendar: Calendar) -> Unit)? = null
22 |
23 | private var mStartHour: Int = PickerUtils.START_HOUR
24 | private var mStartMinute: Int = PickerUtils.START_MINUTE
25 |
26 | private var mEndHour: Int = PickerUtils.END_HOUR
27 | private var mEndMinute: Int = PickerUtils.END_MINUTE
28 |
29 | private val mHourOnSelectedItemListener: OnItemSelectedListener = {
30 | val hour = hourPickerView.getHour()
31 |
32 | when (hour) {
33 | mStartHour -> {
34 | minutePickerView.setMinuteInterval(start = mStartMinute)
35 | }
36 | mEndHour -> {
37 | minutePickerView.setMinuteInterval(end = mEndMinute)
38 | }
39 | else -> {
40 | minutePickerView.setMinuteInterval()
41 | }
42 | }
43 |
44 | dispatchOnSelectedItem()
45 | }
46 |
47 | private val mMinuteOnSelectedItemListener: OnItemSelectedListener = {
48 | dispatchOnSelectedItem()
49 | }
50 |
51 | init {
52 | weightSum = 2f
53 |
54 | addViewInLayout(hourPickerView, 0, generateDefaultLayoutParams(), true)
55 | addViewInLayout(minutePickerView, 1, generateDefaultLayoutParams(), true)
56 | requestLayout()
57 |
58 | resetLayoutManager()
59 |
60 | setTimeInterval()
61 | }
62 |
63 | override fun resetLayoutManager() {
64 | super.resetLayoutManager()
65 | setListener()
66 | }
67 |
68 | private fun setListener() {
69 | hourPickerView.addOnSelectedItemListener(mHourOnSelectedItemListener)
70 |
71 | minutePickerView.addOnSelectedItemListener(mMinuteOnSelectedItemListener)
72 | }
73 |
74 | @SuppressWarnings
75 | fun setTimeInterval(
76 | startHour: Int = PickerUtils.START_HOUR,
77 | startMinute: Int = PickerUtils.START_MINUTE,
78 | endHour: Int = PickerUtils.END_HOUR,
79 | endMinute: Int = PickerUtils.END_MINUTE
80 | ) {
81 | this.mStartHour = startHour
82 | this.mStartMinute = startMinute
83 | this.mEndHour = endHour
84 | this.mEndMinute = endMinute
85 |
86 | hourPickerView.setHourInterval(startHour, endHour)
87 | minutePickerView.setMinuteInterval(startMinute)
88 | }
89 |
90 | private val mDispatchOnSelectedItemRun = Runnable {
91 | val hour = hourPickerView.getHourStr()
92 | val minute = minutePickerView.getMinuteStr()
93 | mOnTimeSelectedListener1?.invoke(hour, minute)
94 |
95 | mOnTimeSelectedListener2?.let {
96 | val calendar = Calendar.getInstance()
97 | calendar.set(Calendar.HOUR_OF_DAY, hour.toInt())
98 | calendar.set(Calendar.MINUTE, minute.toInt())
99 | calendar.set(Calendar.SECOND, 0)
100 | it.invoke(calendar)
101 | }
102 | }
103 |
104 | /**
105 | *
106 | */
107 | private fun dispatchOnSelectedItem() {
108 | this.post(mDispatchOnSelectedItemRun)
109 | }
110 |
111 | /**
112 | * 获取时间的字符串数组
113 | */
114 | fun getTime() = arrayOf(
115 | hourPickerView.getHourStr(),
116 | minutePickerView.getMinuteStr()
117 | )
118 |
119 | /**
120 | * 设置时间选中的监听
121 | */
122 | fun setOnTimeSelectedListener(onSelected: (hour: String, minute: String) -> Unit) {
123 | this.mOnTimeSelectedListener1 = onSelected
124 | }
125 |
126 | /**
127 | * 设置时间选中的监听
128 | */
129 | fun setOnTimeSelectedListener(onSelected: (calendar: Calendar) -> Unit) {
130 | this.mOnTimeSelectedListener2 = onSelected
131 | }
132 | }
--------------------------------------------------------------------------------
/picker_layoutmanager/src/main/java/me/simple/picker/utils/CenterHelperLineItemDecoration.kt:
--------------------------------------------------------------------------------
1 | package me.simple.picker.utils
2 |
3 | import android.graphics.Canvas
4 | import android.graphics.Color
5 | import android.graphics.Paint
6 | import androidx.recyclerview.widget.RecyclerView
7 | import me.simple.picker.PickerLayoutManager
8 |
9 | open class CenterHelperLineItemDecoration : RecyclerView.ItemDecoration() {
10 |
11 | private val mPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
12 | color = Color.RED
13 | style = Paint.Style.FILL
14 | }
15 |
16 | override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
17 | super.onDrawOver(canvas, parent, state)
18 | if (parent.layoutManager == null || parent.layoutManager !is PickerLayoutManager) return
19 |
20 | drawCenterLine(canvas, parent)
21 | }
22 |
23 | private fun drawCenterLine(canvas: Canvas, parent: RecyclerView) {
24 | val width = parent.width.toFloat()
25 | val height = parent.height.toFloat()
26 | val lm = parent.layoutManager as PickerLayoutManager
27 |
28 | if (lm.orientation == PickerLayoutManager.HORIZONTAL) {
29 | canvas.drawLine(width / 2, 0f, width / 2, height, mPaint)
30 | } else {
31 | canvas.drawLine(0f, height / 2f, width, height / 2f, mPaint)
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/picker_layoutmanager/src/main/java/me/simple/picker/utils/PickerUtils.kt:
--------------------------------------------------------------------------------
1 | package me.simple.picker.utils
2 |
3 | import android.graphics.Color
4 | import java.util.*
5 |
6 | object PickerUtils {
7 |
8 | const val START_YEAR = 1949
9 | const val START_MONTH = 1
10 | const val START_DAY = 1
11 |
12 | const val START_HOUR = 0
13 | const val END_HOUR = 23
14 | const val START_MINUTE = 0
15 | const val END_MINUTE = 59
16 |
17 | const val SELECTED_TEXT_COLOR = Color.BLACK
18 | const val UNSELECTED_TEXT_COLOR = Color.LTGRAY
19 |
20 | const val SELECTED_TEXT_SIZE = 14f
21 | const val UNSELECTED_TEXT_SIZE = 14f
22 |
23 | const val SELECTED_IS_BOLD = false
24 |
25 | /**
26 | * 格式化不足2位的月份或日期
27 | */
28 | fun formatTwoChars(text: Int) = String.format("%02d", text)
29 |
30 | /**
31 | * 有31天的月数组
32 | */
33 | private val bigMonthSet = hashSetOf(1, 3, 5, 7, 8, 10, 12)
34 |
35 | /**
36 | * 获取这个月最多有多少天
37 | */
38 | fun getDayCountInMonth(year: Int, month: Int): Int {
39 | if (bigMonthSet.contains(month)) return 31
40 |
41 | if (month == 2) {
42 | return if (year % 4 == 0) 29 else 28
43 | }
44 |
45 | return 30
46 | }
47 |
48 | /**
49 | * 获取开始的日期
50 | * 1949-1-1
51 | */
52 | fun getStartCalendar(): Calendar = Calendar.getInstance().apply {
53 | set(
54 | START_YEAR,
55 | START_MONTH - 1,
56 | START_DAY
57 | )
58 | }
59 |
60 | /**
61 | * 获取结束的日期,就是当天
62 | */
63 | fun getCurrentCalendar(): Calendar = Calendar.getInstance()
64 |
65 | fun getCurrentYear() = getYear(getCurrentCalendar())
66 | fun getCurrentMonth() = getMonth(getCurrentCalendar())
67 | fun getCurrentDay() = getDay(getCurrentCalendar())
68 |
69 | fun getYear(calendar: Calendar) = calendar.get(Calendar.YEAR)
70 | fun getMonth(calendar: Calendar) = calendar.get(Calendar.MONTH) + 1
71 | fun getDay(calendar: Calendar) = calendar.get(Calendar.DATE)
72 | }
--------------------------------------------------------------------------------
/picker_layoutmanager/src/main/java/me/simple/picker/widget/TextPickerLinearLayout.kt:
--------------------------------------------------------------------------------
1 | package me.simple.picker.widget
2 |
3 | import android.content.Context
4 | import android.graphics.Color
5 | import android.util.AttributeSet
6 | import android.util.TypedValue
7 | import android.widget.LinearLayout
8 | import androidx.annotation.ColorInt
9 | import androidx.annotation.Px
10 | import me.simple.picker.PickerItemDecoration
11 | import me.simple.picker.PickerLayoutManager
12 | import me.simple.picker.PickerRecyclerView
13 | import me.simple.picker.R
14 | import me.simple.picker.utils.PickerUtils
15 |
16 | /**
17 | * PickerView的包装器
18 | */
19 | open class TextPickerLinearLayout @JvmOverloads constructor(
20 | context: Context,
21 | attrs: AttributeSet? = null,
22 | defStyleAttr: Int = 0
23 | ) : LinearLayout(context, attrs, defStyleAttr) {
24 |
25 | var mVisibleCount = PickerLayoutManager.VISIBLE_COUNT
26 | var mIsLoop = PickerLayoutManager.IS_LOOP
27 | var mScaleX = PickerLayoutManager.SCALE_X
28 | var mScaleY = PickerLayoutManager.SCALE_Y
29 | var mAlpha = PickerLayoutManager.ALPHA
30 |
31 | var mDividerVisible = PickerRecyclerView.DIVIDER_VISIBLE
32 | var mDividerSize = PickerRecyclerView.DIVIDER_SIZE
33 | var mDividerColor = PickerRecyclerView.DIVIDER_COLOR
34 | var mDividerMargin = PickerRecyclerView.DIVIDER_MARGIN
35 |
36 | var mSelectedTextColor = PickerUtils.SELECTED_TEXT_COLOR
37 | var mUnSelectedTextColor = PickerUtils.UNSELECTED_TEXT_COLOR
38 |
39 | var mSelectedTextSize = TypedValue.applyDimension(
40 | TypedValue.COMPLEX_UNIT_DIP,
41 | PickerUtils.SELECTED_TEXT_SIZE,
42 | resources.displayMetrics
43 | )
44 | var mUnSelectedTextSize = TypedValue.applyDimension(
45 | TypedValue.COMPLEX_UNIT_DIP,
46 | PickerUtils.UNSELECTED_TEXT_SIZE,
47 | resources.displayMetrics
48 | )
49 |
50 | var mSelectedIsBold = PickerUtils.SELECTED_IS_BOLD
51 |
52 | init {
53 | orientation = HORIZONTAL
54 | initAttrs(attrs)
55 | }
56 |
57 | open fun initAttrs(attrs: AttributeSet? = null) {
58 | val typeA = context.obtainStyledAttributes(
59 | attrs,
60 | R.styleable.TextPickerLinearLayout
61 | )
62 |
63 | mVisibleCount = typeA.getInt(
64 | R.styleable.TextPickerLinearLayout_visibleCount,
65 | PickerLayoutManager.VISIBLE_COUNT
66 | )
67 | mIsLoop =
68 | typeA.getBoolean(R.styleable.TextPickerLinearLayout_isLoop, PickerLayoutManager.IS_LOOP)
69 | mScaleX =
70 | typeA.getFloat(R.styleable.TextPickerLinearLayout_scaleX, PickerLayoutManager.SCALE_X)
71 | mScaleY =
72 | typeA.getFloat(R.styleable.TextPickerLinearLayout_scaleY, PickerLayoutManager.SCALE_Y)
73 | mAlpha = typeA.getFloat(R.styleable.TextPickerLinearLayout_alpha, PickerLayoutManager.ALPHA)
74 |
75 | mDividerVisible =
76 | typeA.getBoolean(
77 | R.styleable.TextPickerLinearLayout_dividerVisible,
78 | PickerRecyclerView.DIVIDER_VISIBLE
79 | )
80 | mDividerSize =
81 | typeA.getDimension(
82 | R.styleable.TextPickerLinearLayout_dividerSize,
83 | PickerRecyclerView.DIVIDER_SIZE
84 | )
85 | mDividerColor =
86 | typeA.getColor(
87 | R.styleable.TextPickerLinearLayout_dividerColor,
88 | PickerRecyclerView.DIVIDER_COLOR
89 | )
90 | mDividerMargin =
91 | typeA.getDimension(
92 | R.styleable.TextPickerLinearLayout_dividerMargin,
93 | PickerRecyclerView.DIVIDER_MARGIN
94 | )
95 |
96 | mSelectedTextColor =
97 | typeA.getColor(
98 | R.styleable.TextPickerLinearLayout_selectedTextColor,
99 | PickerUtils.SELECTED_TEXT_COLOR
100 | )
101 | mUnSelectedTextColor =
102 | typeA.getColor(
103 | R.styleable.TextPickerLinearLayout_unSelectedTextColor,
104 | PickerUtils.UNSELECTED_TEXT_COLOR
105 | )
106 | mSelectedTextSize =
107 | typeA.getDimension(
108 | R.styleable.TextPickerLinearLayout_selectedTextSize,
109 | mSelectedTextSize
110 | )
111 | mUnSelectedTextSize =
112 | typeA.getDimension(
113 | R.styleable.TextPickerLinearLayout_unSelectedTextSize,
114 | mUnSelectedTextSize
115 | )
116 | mSelectedIsBold =
117 | typeA.getBoolean(R.styleable.TextPickerLinearLayout_selectedIsBold, mSelectedIsBold)
118 |
119 | typeA.recycle()
120 | }
121 |
122 | override fun generateDefaultLayoutParams(): LayoutParams {
123 | val lp = LayoutParams(0, LayoutParams.WRAP_CONTENT)
124 | lp.weight = 1f
125 | return lp
126 | }
127 |
128 | private fun getTextPickerViews(): HashSet {
129 | val views = hashSetOf()
130 | for (index in 0 until childCount) {
131 | val child = getChildAt(index)
132 | if (child is TextPickerView) {
133 | views.add(child)
134 | }
135 | }
136 | return views
137 | }
138 |
139 | /**
140 | * 设置监听
141 | */
142 | fun addOnItemFillListener(listener: PickerLayoutManager.OnItemFillListener) {
143 | getTextPickerViews().forEach {
144 | it.layoutManager.addOnItemFillListener(listener)
145 | }
146 | }
147 |
148 | /**
149 | * 删除监听
150 | */
151 | fun removeOnItemFillListener(listener: PickerLayoutManager.OnItemFillListener) {
152 | getTextPickerViews().forEach {
153 | it.layoutManager.removeOnItemFillListener(listener)
154 | }
155 | }
156 |
157 | /**
158 | * 滚动到底部
159 | */
160 | open fun scrollToEnd() {
161 | for (view in getTextPickerViews()) {
162 | view.scrollToEnd()
163 | }
164 | }
165 |
166 | //重新设置属性值-------------------------------------------
167 |
168 | fun setVisibleCount(count: Int) {
169 | this.mVisibleCount = count
170 | }
171 |
172 | fun setIsLoop(isLoop: Boolean) {
173 | this.mIsLoop = isLoop
174 | }
175 |
176 | fun setItemScaleX(scaleX: Float) {
177 | this.mScaleX = scaleX
178 | }
179 |
180 | fun setItemScaleY(scaleY: Float) {
181 | this.mScaleY = scaleY
182 | }
183 |
184 | fun setItemAlpha(alpha: Float) {
185 | this.mAlpha = alpha
186 | }
187 |
188 |
189 | fun setSelectedTextColor(textColor: Int) {
190 | this.mSelectedTextColor = textColor
191 | }
192 |
193 | fun setUnSelectedTextColor(textColor: Int) {
194 | this.mUnSelectedTextColor = textColor
195 | }
196 |
197 | fun setSelectedTextSize(textSize: Float) {
198 | this.mSelectedTextSize = textSize
199 | }
200 |
201 | fun setUnSelectedTextSize(textSize: Float) {
202 | this.mUnSelectedTextSize = textSize
203 | }
204 |
205 | fun setSelectedIsBold(bold: Boolean) {
206 | this.mSelectedIsBold = bold
207 | }
208 |
209 |
210 | fun setDividerVisible(visible: Boolean) {
211 | this.mDividerVisible = visible
212 | }
213 |
214 | fun setDividerSize(@Px size: Float) {
215 | this.mDividerSize = size
216 | }
217 |
218 | fun setDividerColor(@ColorInt color: Int) {
219 | this.mDividerColor = color
220 | }
221 |
222 | fun setDividerMargin(margin: Float) {
223 | this.mDividerMargin = margin
224 | }
225 |
226 | /**
227 | * 在设置完属性后,必须调用这个方法
228 | */
229 | open fun resetLayoutManager() {
230 | for (view in getTextPickerViews()) {
231 |
232 | view.setSelectedTextColor(mSelectedTextColor)
233 | view.setUnSelectedTextColor(mUnSelectedTextColor)
234 |
235 | view.setSelectedTextSize(mSelectedTextSize)
236 | view.setUnSelectedTextSize(mUnSelectedTextSize)
237 |
238 | view.setSelectedIsBold(mSelectedIsBold)
239 |
240 | view.setDividerVisible(mDividerVisible)
241 | view.setDividerColor(mDividerColor)
242 | view.setDividerSize(mDividerSize)
243 | view.setDividerMargin(mDividerMargin)
244 |
245 | val lm = PickerLayoutManager(
246 | PickerLayoutManager.VERTICAL,
247 | mVisibleCount,
248 | mIsLoop,
249 | mScaleX,
250 | mScaleY,
251 | mAlpha
252 | )
253 | view.resetLayoutManager(lm)
254 | }
255 | }
256 | }
--------------------------------------------------------------------------------
/picker_layoutmanager/src/main/java/me/simple/picker/widget/TextPickerView.kt:
--------------------------------------------------------------------------------
1 | package me.simple.picker.widget
2 |
3 | import android.content.Context
4 | import android.graphics.Typeface
5 | import android.util.AttributeSet
6 | import android.util.TypedValue
7 | import android.view.LayoutInflater
8 | import android.view.View
9 | import android.view.ViewGroup
10 | import android.widget.TextView
11 | import androidx.annotation.ColorInt
12 | import androidx.annotation.Px
13 | import androidx.recyclerview.widget.RecyclerView
14 | import me.simple.picker.PickerLayoutManager
15 | import me.simple.picker.PickerRecyclerView
16 | import me.simple.picker.R
17 | import me.simple.picker.utils.PickerUtils
18 |
19 | /**
20 | * 文本类型的PickerView
21 | */
22 | open class TextPickerView @JvmOverloads constructor(
23 | context: Context,
24 | attrs: AttributeSet? = null,
25 | defStyleAttr: Int = 0
26 | ) : PickerRecyclerView(context, attrs, defStyleAttr),
27 | PickerLayoutManager.OnItemFillListener {
28 |
29 | val mItems = mutableListOf()
30 |
31 | private var mSelectedTextColor = PickerUtils.SELECTED_TEXT_COLOR
32 | private var mUnSelectedTextColor = PickerUtils.UNSELECTED_TEXT_COLOR
33 |
34 | private var mSelectedTextSize = TypedValue.applyDimension(
35 | TypedValue.COMPLEX_UNIT_DIP,
36 | PickerUtils.SELECTED_TEXT_SIZE,
37 | resources.displayMetrics
38 | )
39 | private var mUnSelectedTextSize = TypedValue.applyDimension(
40 | TypedValue.COMPLEX_UNIT_DIP,
41 | PickerUtils.UNSELECTED_TEXT_SIZE,
42 | resources.displayMetrics
43 | )
44 |
45 | private var mSelectedIsBold = PickerUtils.SELECTED_IS_BOLD
46 |
47 | init {
48 | initAttrs(attrs)
49 |
50 | mOrientation = PickerLayoutManager.VERTICAL
51 | overScrollMode = View.OVER_SCROLL_NEVER
52 | adapter = TextPickerAdapter()
53 |
54 | resetLayoutManager()
55 | }
56 |
57 | override fun initAttrs(attrs: AttributeSet?) {
58 | super.initAttrs(attrs)
59 |
60 | val typeA = context.obtainStyledAttributes(
61 | attrs,
62 | R.styleable.TextPickerView
63 | )
64 |
65 | mSelectedTextColor =
66 | typeA.getColor(R.styleable.TextPickerView_selectedTextColor, mSelectedTextColor)
67 | mUnSelectedTextColor =
68 | typeA.getColor(R.styleable.TextPickerView_unSelectedTextColor, mUnSelectedTextColor)
69 |
70 | mSelectedTextSize =
71 | typeA.getDimension(R.styleable.TextPickerView_selectedTextSize, mSelectedTextSize)
72 | mUnSelectedTextSize =
73 | typeA.getDimension(R.styleable.TextPickerView_unSelectedTextSize, mUnSelectedTextSize)
74 |
75 | mSelectedIsBold =
76 | typeA.getBoolean(R.styleable.TextPickerView_selectedIsBold, mSelectedIsBold)
77 |
78 | typeA.recycle()
79 | }
80 |
81 | /**
82 | * 设置数据源
83 | */
84 | fun setData(data: List) {
85 | mItems.clear()
86 | mItems.addAll(data)
87 | adapter!!.notifyDataSetChanged()
88 | }
89 |
90 | /**
91 | * 获取数据源
92 | */
93 | fun getData() = mItems
94 |
95 | //重新设置属性值-------------------------------------------
96 |
97 | /**
98 | * 设置选中时文字的颜色
99 | */
100 | fun setSelectedTextColor(@ColorInt textColor: Int) {
101 | this.mSelectedTextColor = textColor
102 | }
103 |
104 | /**
105 | * 设置未选中时文字的颜色
106 | */
107 | fun setUnSelectedTextColor(@ColorInt textColor: Int) {
108 | this.mUnSelectedTextColor = textColor
109 | }
110 |
111 | /**
112 | * 设置选中时文字的大小
113 | */
114 | fun setSelectedTextSize(@Px textSize: Float) {
115 | this.mSelectedTextSize = textSize
116 | }
117 |
118 | /**
119 | * 设置未选中时文字的大小
120 | */
121 | fun setUnSelectedTextSize(@Px textSize: Float) {
122 | this.mUnSelectedTextSize = textSize
123 | }
124 |
125 | /**
126 | * 设置选中时文字是否加粗
127 | */
128 | fun setSelectedIsBold(bold: Boolean) {
129 | this.mSelectedIsBold = bold
130 | }
131 |
132 | /**
133 | * 获取选中那个item的文本
134 | */
135 | fun getSelectedItem(): String {
136 | val selectedPosition = getSelectedPosition()
137 | if (selectedPosition == -1) return ""
138 | return mItems[getSelectedPosition()]
139 | }
140 |
141 | /**
142 | * 选中某个item
143 | */
144 | open fun selectedItem(item: String) {
145 | this.post {
146 | val position = mItems.indexOf(item)
147 | scrollToPosition(position)
148 | }
149 | }
150 |
151 | /**
152 | * 选中最后一个item
153 | */
154 | open fun selectedEndItem() {
155 | if (adapter == null) return
156 | this.post {
157 | scrollToPosition(adapter!!.itemCount - 1)
158 | }
159 | }
160 |
161 | override fun setLayoutManager(layout: LayoutManager?) {
162 | super.setLayoutManager(layout)
163 |
164 | layoutManager.removeAllItemFillListener()
165 | layoutManager.addOnItemFillListener(this)
166 | }
167 |
168 | /**
169 | *
170 | */
171 | inner class TextPickerAdapter :
172 | RecyclerView.Adapter() {
173 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)
174 | : TextPickerViewHolder {
175 | val inflater = LayoutInflater.from(parent.context)
176 | return TextPickerViewHolder(inflater.inflate(R.layout.item_text_picker, parent, false))
177 | }
178 |
179 | override fun getItemCount(): Int {
180 | return mItems.size
181 | }
182 |
183 | override fun onBindViewHolder(holder: TextPickerViewHolder, position: Int) {
184 | holder.bindItem(position)
185 | }
186 | }
187 |
188 | /**
189 | *
190 | */
191 | inner class TextPickerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
192 |
193 | private val textView = itemView as TextView
194 |
195 | fun bindItem(position: Int) {
196 | val item = mItems[position]
197 | textView.text = item
198 | }
199 | }
200 |
201 | override fun onItemSelected(itemView: View, position: Int) {
202 | val isItemSelected = itemView.tag as? Boolean
203 | if (isItemSelected == null || !isItemSelected) {
204 | val tv = itemView as TextView
205 | tv.setTextColor(mSelectedTextColor)
206 | tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, mSelectedTextSize)
207 | if (mSelectedIsBold) {
208 | tv.typeface = Typeface.DEFAULT_BOLD
209 | }
210 | itemView.tag = true
211 | }
212 | }
213 |
214 | override fun onItemUnSelected(itemView: View, position: Int) {
215 | val isItemSelected = itemView.tag as? Boolean
216 | if (isItemSelected == null || isItemSelected) {
217 | val tv = itemView as TextView
218 | tv.setTextColor(mUnSelectedTextColor)
219 | tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, mUnSelectedTextSize)
220 | if (mSelectedIsBold) {
221 | tv.typeface = Typeface.DEFAULT
222 | }
223 | itemView.tag = false
224 | }
225 | }
226 | }
--------------------------------------------------------------------------------
/picker_layoutmanager/src/main/res/layout/item_text_picker.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/picker_layoutmanager/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | dependencyResolutionManagement {
2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
3 | repositories {
4 | google()
5 | mavenCentral()
6 | jcenter() // Warning: this repository is going to shut down soon
7 | maven { url 'https://jitpack.io' }
8 | }
9 | }
10 | rootProject.name='PickerLayoutManager'
11 | include ':app'
12 | include ':picker_layoutmanager'
13 |
--------------------------------------------------------------------------------