├── .gitattributes ├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── dictionaries │ └── wangchenlong.xml ├── encodings.xml ├── gradle.xml ├── libraries │ ├── animated_vector_drawable_25_1_0.xml │ ├── appcompat_v7_25_1_0.xml │ ├── espresso_core_2_2_2.xml │ ├── espresso_idling_resource_2_2_2.xml │ ├── exposed_instrumentation_api_publish_0_5.xml │ ├── hamcrest_core_1_3.xml │ ├── hamcrest_integration_1_3.xml │ ├── hamcrest_library_1_3.xml │ ├── javawriter_2_1_1.xml │ ├── javax_annotation_api_1_2.xml │ ├── javax_inject_1.xml │ ├── jsr305_2_0_1.xml │ ├── junit_4_12.xml │ ├── rules_0_5.xml │ ├── runner_0_5.xml │ ├── support_annotations_25_1_0.xml │ ├── support_compat_25_1_0.xml │ ├── support_core_ui_25_1_0.xml │ ├── support_core_utils_25_1_0.xml │ ├── support_fragment_25_1_0.xml │ ├── support_media_compat_25_1_0.xml │ ├── support_v4_25_1_0.xml │ └── support_vector_drawable_25_1_0.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml ├── vcs.xml └── workspace.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── org │ │ └── wangchenlong │ │ └── datescroller │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── org │ │ │ └── wangchenlong │ │ │ └── datescroller │ │ │ ├── MainActivity.java │ │ │ └── widget │ │ │ ├── DateScrollerDialog.java │ │ │ ├── TimeWheel.java │ │ │ ├── adapters │ │ │ ├── AbstractWheelAdapter.java │ │ │ ├── AbstractWheelTextAdapter.java │ │ │ ├── NumericWheelAdapter.java │ │ │ └── WheelViewAdapter.java │ │ │ ├── config │ │ │ ├── DefaultConfig.java │ │ │ └── ScrollerConfig.java │ │ │ ├── data │ │ │ ├── Type.java │ │ │ ├── WheelCalendar.java │ │ │ └── source │ │ │ │ ├── TimeDataSource.java │ │ │ │ └── TimeRepository.java │ │ │ ├── listener │ │ │ └── OnDateSetListener.java │ │ │ ├── utils │ │ │ ├── DateConstants.java │ │ │ └── Utils.java │ │ │ └── wheel │ │ │ ├── ItemsRange.java │ │ │ ├── OnWheelChangedListener.java │ │ │ ├── OnWheelClickedListener.java │ │ │ ├── OnWheelScrollListener.java │ │ │ ├── WheelRecycle.java │ │ │ ├── WheelScroller.java │ │ │ └── WheelView.java │ └── res │ │ ├── anim │ │ ├── slide_in_bottom.xml │ │ └── slide_out_bottom.xml │ │ ├── drawable │ │ ├── date_wheel_bg.xml │ │ ├── timepicker_divider_line.xml │ │ └── timepicker_sel_text_item.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── time_scroller_toolbar.xml │ │ ├── timepicker_layout.xml │ │ ├── timepicker_line.xml │ │ └── tv_item.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── org │ └── wangchenlong │ └── datescroller │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── 效果图.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.apk 2 | *.ap_ 3 | 4 | # Files for the Dalvik VM 5 | *.dex 6 | 7 | # Java class files 8 | *.class 9 | 10 | # Generated files 11 | bin/ 12 | gen/ 13 | out/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Log Files 26 | *.log 27 | 28 | # Android Studio Navigation editor temp files 29 | .navigation/ 30 | 31 | # Android Studio captures folder 32 | captures/ 33 | 34 | # Intellij 35 | *.iml 36 | 37 | # Keystore files 38 | *.jks -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/dictionaries/wangchenlong.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dataset 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/libraries/animated_vector_drawable_25_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/appcompat_v7_25_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/espresso_core_2_2_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/espresso_idling_resource_2_2_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/exposed_instrumentation_api_publish_0_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/hamcrest_core_1_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/hamcrest_integration_1_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/libraries/hamcrest_library_1_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/javawriter_2_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/libraries/javax_annotation_api_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/libraries/javax_inject_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/jsr305_2_0_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/libraries/junit_4_12.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/rules_0_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/runner_0_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/support_annotations_25_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/support_compat_25_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/support_core_ui_25_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/support_core_utils_25_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/support_fragment_25_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/support_media_compat_25_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/support_v4_25_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/libraries/support_vector_drawable_25_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 26 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | Android 46 | 47 | 48 | Android Lint 49 | 50 | 51 | Java 52 | 53 | 54 | Java language level migration aidsJava 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 76 | 77 | 78 | 79 | 80 | 1.8 81 | 82 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DateRangePicker 2 | 自定义的时间双滚轮控件,可以选择时间范围,结束时间会自动调整到开始时间之后。 3 | 4 | ## 效果图 5 | 6 | ![](https://raw.githubusercontent.com/huangssh/DateRangePicker/master/效果图.png) 7 | 8 | ## 参考 9 | [**探索日期滚轮控件的源码**](https://juejin.im/post/5858a8f3b123db00658b1191) \ 10 | [**DateScroller**](https://github.com/SpikeKing/DateScroller) 11 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | defaultConfig { 7 | applicationId "org.wangchenlong.datescroller" 8 | minSdkVersion 16 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:25.1.0' 28 | testCompile 'junit:junit:4.12' 29 | } 30 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/wangchenlong/Installations/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/org/wangchenlong/datescroller/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package org.wangchenlong.datescroller; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("org.wangchenlong.datescroller", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/MainActivity.java: -------------------------------------------------------------------------------- 1 | package org.wangchenlong.datescroller; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.view.View; 6 | import android.widget.TextView; 7 | 8 | import org.wangchenlong.datescroller.widget.DateScrollerDialog; 9 | import org.wangchenlong.datescroller.widget.data.Type; 10 | import org.wangchenlong.datescroller.widget.listener.OnDateSetListener; 11 | 12 | import java.text.SimpleDateFormat; 13 | import java.util.Date; 14 | import java.util.Locale; 15 | 16 | public class MainActivity extends AppCompatActivity { 17 | private static final long HUNDRED_YEARS = 100L * 365 * 1000 * 60 * 60 * 24L; // 100年 18 | 19 | private TextView mTvTime; 20 | private SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH); 21 | private long mLastTime = System.currentTimeMillis(); // 上次设置的开始时间 22 | private long mLastFinishTime = System.currentTimeMillis(); // 上次设置的结束时间 23 | 24 | // 数据的回调 25 | private OnDateSetListener mOnDateSetListener = new OnDateSetListener() { 26 | @Override public void onDateSet(DateScrollerDialog timePickerView, long milliseconds, long milliFinishseconds) { 27 | mLastTime = milliseconds; 28 | mLastFinishTime = milliFinishseconds; 29 | String text = getDateToString(milliseconds); 30 | String text2 = getDateToString(milliFinishseconds); 31 | mTvTime.setText(text + "-" + text2); 32 | } 33 | }; 34 | 35 | @Override 36 | protected void onCreate(Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | setContentView(R.layout.activity_main); 39 | initView(); // 初始化View 40 | } 41 | 42 | /** 43 | * 显示日期 44 | * 45 | * @param view 视图 46 | */ 47 | public void showDate(View view) { 48 | // 选择日期 49 | DateScrollerDialog dialog = new DateScrollerDialog.Builder() 50 | .setType(Type.YEAR_MONTH_DAY) 51 | .setTitleStringId("请选择日期") 52 | .setMinMilliseconds(System.currentTimeMillis() - HUNDRED_YEARS) 53 | .setMaxMilliseconds(System.currentTimeMillis() + HUNDRED_YEARS) 54 | // 上次设置的开始时间,结束时间 55 | .setCurMilliseconds(mLastTime, mLastFinishTime) 56 | .setCallback(mOnDateSetListener) 57 | .build(); 58 | 59 | if (dialog != null) { 60 | if (!dialog.isAdded()) { 61 | dialog.show(getSupportFragmentManager(), "year_month_day"); 62 | } 63 | } 64 | } 65 | 66 | // 初始化视图 67 | private void initView() { 68 | mTvTime = (TextView) findViewById(R.id.tv_time); 69 | } 70 | 71 | public String getDateToString(long time) { 72 | Date d = new Date(time); 73 | return sf.format(d); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/widget/DateScrollerDialog.java: -------------------------------------------------------------------------------- 1 | package org.wangchenlong.datescroller.widget; 2 | 3 | import android.app.Dialog; 4 | import android.os.Bundle; 5 | import android.support.annotation.ColorRes; 6 | import android.support.annotation.NonNull; 7 | import android.support.annotation.Nullable; 8 | import android.support.v4.app.DialogFragment; 9 | import android.view.Gravity; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.view.Window; 14 | import android.view.WindowManager; 15 | import android.widget.TextView; 16 | 17 | import org.wangchenlong.datescroller.R; 18 | import org.wangchenlong.datescroller.widget.config.ScrollerConfig; 19 | import org.wangchenlong.datescroller.widget.data.Type; 20 | import org.wangchenlong.datescroller.widget.data.WheelCalendar; 21 | import org.wangchenlong.datescroller.widget.listener.OnDateSetListener; 22 | 23 | import java.util.Calendar; 24 | 25 | /** 26 | * 时间选择框, 设置若干参数 27 | * 28 | * @author C.L.Wang 29 | */ 30 | public class DateScrollerDialog extends DialogFragment implements View.OnClickListener { 31 | private ScrollerConfig mScrollerConfig; 32 | private TimeWheel mTimeWheel; 33 | private long mCurrentMilliseconds; 34 | private long mCurrentFinishMilliseconds; 35 | 36 | // 实例化参数, 传入数据 37 | private static DateScrollerDialog newInstance(ScrollerConfig scrollerConfig) { 38 | DateScrollerDialog dateScrollerDialog = new DateScrollerDialog(); 39 | dateScrollerDialog.initialize(scrollerConfig); 40 | return dateScrollerDialog; 41 | } 42 | 43 | @Override 44 | public void onCreate(@Nullable Bundle savedInstanceState) { 45 | super.onCreate(savedInstanceState); 46 | Window window = getActivity().getWindow(); 47 | // 隐藏软键盘 48 | window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); 49 | } 50 | 51 | @Override 52 | public void onResume() { 53 | super.onResume(); 54 | 55 | // Dialog的位置置底 56 | Window window = getDialog().getWindow(); 57 | if (window != null) { 58 | window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 59 | window.setGravity(Gravity.BOTTOM); 60 | } 61 | } 62 | 63 | /** 64 | * 初始化参数, 来源{@link #newInstance(ScrollerConfig)} 65 | * 66 | * @param scrollerConfig 滚动参数 67 | */ 68 | private void initialize(ScrollerConfig scrollerConfig) { 69 | mScrollerConfig = scrollerConfig; 70 | } 71 | 72 | @NonNull @Override 73 | public Dialog onCreateDialog(Bundle savedInstanceState) { 74 | Dialog dialog = new Dialog(getActivity(), R.style.Dialog_NoTitle); 75 | dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); 76 | dialog.setCancelable(true); // 后退键取消 77 | dialog.setCanceledOnTouchOutside(true); // 点击外面被取消 78 | dialog.setContentView(initView()); // 设置View 79 | return dialog; 80 | } 81 | 82 | /** 83 | * 初始化视图 84 | * 85 | * @return 当前视图 86 | */ 87 | private View initView() { 88 | LayoutInflater inflater = LayoutInflater.from(getContext()); 89 | final ViewGroup nullParent = null; 90 | View view = inflater.inflate(R.layout.timepicker_layout, nullParent); 91 | 92 | TextView cancel = (TextView) view.findViewById(R.id.tv_cancel); 93 | cancel.setOnClickListener(this); // 设置取消按钮 94 | TextView sure = (TextView) view.findViewById(R.id.tv_sure); 95 | sure.setOnClickListener(this); // 设置确认按钮 96 | TextView title = (TextView) view.findViewById(R.id.tv_title); 97 | 98 | // 设置顶部栏 99 | View toolbar = view.findViewById(R.id.toolbar); // 头部视图 100 | toolbar.setBackgroundResource(mScrollerConfig.mToolbarBkgColor); 101 | title.setText(mScrollerConfig.mTitleString); // 设置文字 102 | cancel.setText(mScrollerConfig.mCancelString); 103 | sure.setText(mScrollerConfig.mSureString); 104 | 105 | mTimeWheel = new TimeWheel(view, mScrollerConfig); // 设置滚动参数 106 | return view; 107 | } 108 | 109 | @Override 110 | public void onClick(View v) { 111 | int i = v.getId(); 112 | if (i == R.id.tv_cancel) { 113 | dismiss(); // 取消 114 | } else if (i == R.id.tv_sure) { 115 | onSureClicked(); 116 | } 117 | } 118 | 119 | /** 120 | * 返回当前的选择秒数 121 | * 122 | * @return 当前秒数 123 | */ 124 | public long getCurrentMilliseconds() { 125 | if (mCurrentMilliseconds == 0) 126 | return System.currentTimeMillis(); 127 | 128 | return mCurrentMilliseconds; 129 | } 130 | 131 | /** 132 | * 确认按钮, 回调秒数 133 | */ 134 | private void onSureClicked() { 135 | Calendar calendar = Calendar.getInstance(); 136 | calendar.clear(); 137 | 138 | calendar.set(Calendar.YEAR, mTimeWheel.getCurrentYear()); 139 | calendar.set(Calendar.MONTH, mTimeWheel.getCurrentMonth() - 1); 140 | calendar.set(Calendar.DAY_OF_MONTH, mTimeWheel.getCurrentDay()); 141 | calendar.set(Calendar.HOUR_OF_DAY, mTimeWheel.getCurrentHour()); 142 | calendar.set(Calendar.MINUTE, mTimeWheel.getCurrentMinute()); 143 | 144 | mCurrentMilliseconds = calendar.getTimeInMillis(); 145 | 146 | Calendar calendar2 = Calendar.getInstance(); 147 | calendar2.clear(); 148 | 149 | calendar2.set(Calendar.YEAR, mTimeWheel.getCurrentFinishYear()); 150 | calendar2.set(Calendar.MONTH, mTimeWheel.getCurrentFinishMonth() - 1); 151 | calendar2.set(Calendar.DAY_OF_MONTH, mTimeWheel.getCurrentFinishDay()); 152 | 153 | mCurrentFinishMilliseconds = calendar2.getTimeInMillis(); 154 | 155 | if (mScrollerConfig.mCallback != null) { 156 | mScrollerConfig.mCallback.onDateSet(this, mCurrentMilliseconds, mCurrentFinishMilliseconds); 157 | } 158 | dismiss(); 159 | } 160 | 161 | @SuppressWarnings("unused") 162 | public static class Builder { 163 | ScrollerConfig mScrollerConfig; 164 | 165 | public Builder() { 166 | mScrollerConfig = new ScrollerConfig(); 167 | } 168 | 169 | public Builder setType(Type type) { 170 | mScrollerConfig.mType = type; 171 | return this; 172 | } 173 | 174 | public Builder setThemeColor(@ColorRes int color) { 175 | mScrollerConfig.mToolbarBkgColor = color; 176 | return this; 177 | } 178 | 179 | public Builder setCancelStringId(String left) { 180 | mScrollerConfig.mCancelString = left; 181 | return this; 182 | } 183 | 184 | public Builder setSureStringId(String right) { 185 | mScrollerConfig.mSureString = right; 186 | return this; 187 | } 188 | 189 | public Builder setTitleStringId(String title) { 190 | mScrollerConfig.mTitleString = title; 191 | return this; 192 | } 193 | 194 | public Builder setToolBarTextColor(int color) { 195 | mScrollerConfig.mToolBarTVColor = color; 196 | return this; 197 | } 198 | 199 | public Builder setWheelItemTextNormalColor(int color) { 200 | mScrollerConfig.mWheelTVNormalColor = color; 201 | return this; 202 | } 203 | 204 | public Builder setWheelItemTextSelectorColor(int color) { 205 | mScrollerConfig.mWheelTVSelectorColor = color; 206 | return this; 207 | } 208 | 209 | public Builder setWheelItemTextSize(int size) { 210 | mScrollerConfig.mWheelTVSize = size; 211 | return this; 212 | } 213 | 214 | public Builder setCyclic(boolean cyclic) { 215 | mScrollerConfig.cyclic = cyclic; 216 | return this; 217 | } 218 | 219 | public Builder setMinMilliseconds(long milliseconds) { 220 | mScrollerConfig.mMinCalendar = new WheelCalendar(milliseconds); 221 | return this; 222 | } 223 | 224 | public Builder setMaxMilliseconds(long milliseconds) { 225 | mScrollerConfig.mMaxCalendar = new WheelCalendar(milliseconds); 226 | return this; 227 | } 228 | 229 | public Builder setCurMilliseconds(long milliseconds, long milliFinishSeconds) { 230 | mScrollerConfig.mCurCalendar = new WheelCalendar(milliseconds); 231 | mScrollerConfig.mCurFinishCalendar = new WheelCalendar(milliFinishSeconds); 232 | return this; 233 | } 234 | 235 | public Builder setYearText(String year) { 236 | mScrollerConfig.mYear = year; 237 | return this; 238 | } 239 | 240 | public Builder setMonthText(String month) { 241 | mScrollerConfig.mMonth = month; 242 | return this; 243 | } 244 | 245 | public Builder setDayText(String day) { 246 | mScrollerConfig.mDay = day; 247 | return this; 248 | } 249 | 250 | public Builder setHourText(String hour) { 251 | mScrollerConfig.mHour = hour; 252 | return this; 253 | } 254 | 255 | public Builder setMinuteText(String minute) { 256 | mScrollerConfig.mMinute = minute; 257 | return this; 258 | } 259 | 260 | public Builder setCallback(OnDateSetListener listener) { 261 | mScrollerConfig.mCallback = listener; 262 | return this; 263 | } 264 | 265 | public DateScrollerDialog build() { 266 | return newInstance(mScrollerConfig); 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/widget/TimeWheel.java: -------------------------------------------------------------------------------- 1 | package org.wangchenlong.datescroller.widget; 2 | 3 | import android.content.Context; 4 | import android.view.View; 5 | 6 | import org.wangchenlong.datescroller.R; 7 | import org.wangchenlong.datescroller.widget.adapters.NumericWheelAdapter; 8 | import org.wangchenlong.datescroller.widget.config.ScrollerConfig; 9 | import org.wangchenlong.datescroller.widget.data.source.TimeRepository; 10 | import org.wangchenlong.datescroller.widget.utils.DateConstants; 11 | import org.wangchenlong.datescroller.widget.utils.Utils; 12 | import org.wangchenlong.datescroller.widget.wheel.OnWheelChangedListener; 13 | import org.wangchenlong.datescroller.widget.wheel.WheelView; 14 | 15 | import java.util.Calendar; 16 | 17 | /** 18 | * 时间滚轮, 用于控制时间数据 19 | */ 20 | class TimeWheel { 21 | private Context mContext; 22 | 23 | private WheelView mYearView, mMonthView, mDayView, mHourView, mMinuteView 24 | , mFinishYearView, mFinishMonthView, mFinishDayView; // 滚动视图 25 | private NumericWheelAdapter mYearAdapter, mMonthAdapter, mDayAdapter, mHourAdapter, mMinuteAdapter; 26 | 27 | private ScrollerConfig mScrollerConfig; 28 | private TimeRepository mRepository; 29 | 30 | private OnWheelChangedListener mYearListener = new OnWheelChangedListener() { 31 | @Override 32 | public void onChanged(WheelView wheel, int oldValue, int newValue) { 33 | updateMonths(); 34 | 35 | // 如果开始年份大于结束年份 36 | if (mYearView.getCurrentItem() > mFinishYearView.getCurrentItem()){ 37 | // 则结束年份马上更新到相同年份 38 | mFinishYearView.setCurrentItem(mYearView.getCurrentItem()); 39 | 40 | // 当前月份大于结束月份 41 | if (mMonthView.getCurrentItem() > mFinishMonthView.getCurrentItem()){ 42 | // 结束月份设置为开始月份 43 | mFinishMonthView.setCurrentItem(mMonthView.getCurrentItem()); 44 | } 45 | } 46 | } 47 | }; 48 | 49 | private OnWheelChangedListener mFinishYearListener = new OnWheelChangedListener() { 50 | @Override 51 | public void onChanged(WheelView wheel, int oldValue, int newValue) { 52 | updateFinishMonths(); 53 | 54 | // 如果结束年份小于结束年份 55 | if (mFinishYearView.getCurrentItem() < mYearView.getCurrentItem()){ 56 | // 则开始年份马上更新到相同年份 57 | mYearView.setCurrentItem(mFinishYearView.getCurrentItem()); 58 | 59 | // 当前结束月份大于开始月份 60 | if (mFinishMonthView.getCurrentItem() < mMonthView.getCurrentItem()){ 61 | // 开始月份设置为结束月份 62 | mMonthView.setCurrentItem(mFinishMonthView.getCurrentItem()); 63 | 64 | // 开始日大于结束日 65 | if (mDayView.getCurrentItem() > mFinishDayView.getCurrentItem()){ 66 | // 结束日设置为开始日 67 | mFinishDayView.setCurrentItem(mDayView.getCurrentItem()); 68 | } 69 | } 70 | } 71 | } 72 | }; 73 | private OnWheelChangedListener mMonthListener = new OnWheelChangedListener() { 74 | @Override 75 | public void onChanged(WheelView wheel, int oldValue, int newValue) { 76 | updateDays(); 77 | 78 | // 开始月份大于结束月份 79 | if (mMonthView.getCurrentItem() > mFinishMonthView.getCurrentItem() && mYearView.getCurrentItem() == mFinishYearView.getCurrentItem()){ 80 | // 结束月份设置为开始月份 81 | mFinishMonthView.setCurrentItem(mMonthView.getCurrentItem()); 82 | } 83 | } 84 | }; 85 | private OnWheelChangedListener mFinishMonthListener = new OnWheelChangedListener() { 86 | @Override 87 | public void onChanged(WheelView wheel, int oldValue, int newValue) { 88 | updateFinishDays(); 89 | 90 | // 当前结束月份大于开始月份 91 | if (mFinishMonthView.getCurrentItem() < mMonthView.getCurrentItem() && mYearView.getCurrentItem() == mFinishYearView.getCurrentItem()){ 92 | // 开始月份设置为结束月份 93 | mMonthView.setCurrentItem(mFinishMonthView.getCurrentItem()); 94 | } 95 | } 96 | }; 97 | 98 | // 开始日监听 99 | private OnWheelChangedListener mDayListener = new OnWheelChangedListener() { 100 | @Override 101 | public void onChanged(WheelView wheel, int oldValue, int newValue) { 102 | // updateHours(); 103 | 104 | // 开始日大于结束日 105 | if (mDayView.getCurrentItem() > mFinishDayView.getCurrentItem() && mFinishMonthView.getCurrentItem() == mMonthView.getCurrentItem()){ 106 | // 结束日设置为开始日 107 | mFinishDayView.setCurrentItem(mDayView.getCurrentItem()); 108 | } 109 | } 110 | }; 111 | 112 | // 结束日监听 113 | private OnWheelChangedListener mFinishDayListener = new OnWheelChangedListener() { 114 | @Override 115 | public void onChanged(WheelView wheel, int oldValue, int newValue) { 116 | // 开始日大于结束日 117 | if (mFinishDayView.getCurrentItem() < mDayView.getCurrentItem() && mFinishMonthView.getCurrentItem() == mMonthView.getCurrentItem()){ 118 | // 结束日设置为开始日 119 | mDayView.setCurrentItem(mFinishDayView.getCurrentItem()); 120 | } 121 | } 122 | }; 123 | 124 | private OnWheelChangedListener mHourListener = new OnWheelChangedListener() { 125 | @Override 126 | public void onChanged(WheelView wheel, int oldValue, int newValue) { 127 | updateMinutes(); 128 | } 129 | }; 130 | 131 | /** 132 | * 设置视图与参数 133 | * 134 | * @param view 视图 135 | * @param scrollerConfig 滚动参数 136 | */ 137 | TimeWheel(View view, ScrollerConfig scrollerConfig) { 138 | mScrollerConfig = scrollerConfig; 139 | mRepository = new TimeRepository(scrollerConfig); 140 | mContext = view.getContext(); 141 | initialize(view); 142 | } 143 | 144 | /** 145 | * 初始化与设置视图 146 | * 147 | * @param view 视图 148 | */ 149 | private void initialize(View view) { 150 | initView(view); // 初始化视图 151 | 152 | // 初始化年份和结束年 153 | initYearView(); 154 | initMonthView(); 155 | initDayView(); 156 | initHourView(); 157 | initMinuteView(); 158 | 159 | // 初始化结束月 160 | initFinishMonthView(); 161 | // 初始化结束日 162 | initFinishDayView(); 163 | } 164 | 165 | /** 166 | * 初始化视图 167 | * 168 | * @param view 视图 169 | */ 170 | private void initView(View view) { 171 | mYearView = (WheelView) view.findViewById(R.id.year); 172 | mMonthView = (WheelView) view.findViewById(R.id.month); 173 | mDayView = (WheelView) view.findViewById(R.id.day); 174 | mHourView = (WheelView) view.findViewById(R.id.hour); 175 | mMinuteView = (WheelView) view.findViewById(R.id.minute); 176 | mFinishYearView = (WheelView) view.findViewById(R.id.finish_year); 177 | mFinishMonthView = (WheelView)view.findViewById(R.id.finish_month); 178 | mFinishDayView= (WheelView)view.findViewById(R.id.finish_day); 179 | 180 | // 根据类型, 设置隐藏位置 181 | switch (mScrollerConfig.mType) { 182 | case ALL: 183 | break; 184 | case YEAR_MONTH_DAY: 185 | Utils.hideViews(mHourView, mMinuteView); 186 | break; 187 | case YEAR_MONTH: 188 | Utils.hideViews(mDayView, mHourView, mMinuteView); 189 | break; 190 | case MONTH_DAY_HOUR_MIN: 191 | Utils.hideViews(mYearView); 192 | break; 193 | case HOURS_MINS: 194 | Utils.hideViews(mYearView, mMonthView, mDayView); 195 | break; 196 | case YEAR: 197 | Utils.hideViews(mMonthView, mDayView, mHourView, mMinuteView); 198 | break; 199 | } 200 | 201 | mYearView.addChangingListener(mYearListener); 202 | mYearView.addChangingListener(mMonthListener); 203 | mYearView.addChangingListener(mDayListener); 204 | mYearView.addChangingListener(mHourListener); 205 | 206 | mFinishYearView.addChangingListener(mFinishYearListener); 207 | mFinishYearView.addChangingListener(mMonthListener); 208 | mFinishYearView.addChangingListener(mDayListener); 209 | mFinishYearView.addChangingListener(mHourListener); 210 | 211 | mMonthView.addChangingListener(mMonthListener); 212 | mMonthView.addChangingListener(mDayListener); 213 | mMonthView.addChangingListener(mHourListener); 214 | 215 | mFinishMonthView.addChangingListener(mFinishMonthListener); 216 | 217 | mDayView.addChangingListener(mDayListener); 218 | mDayView.addChangingListener(mHourListener); 219 | 220 | mFinishDayView.addChangingListener(mDayListener); 221 | 222 | mHourView.addChangingListener(mHourListener); 223 | } 224 | 225 | /** 226 | * 初始化年视图, 年是最高级 227 | */ 228 | private void initYearView() { 229 | int minYear = mRepository.getMinYear(); 230 | int maxYear = mRepository.getMaxYear(); 231 | 232 | mYearAdapter = new NumericWheelAdapter(mContext, minYear, maxYear, 233 | DateConstants.FORMAT, mScrollerConfig.mYear); 234 | mYearAdapter.setConfig(mScrollerConfig); 235 | 236 | mYearView.setViewAdapter(mYearAdapter); 237 | mFinishYearView.setViewAdapter(mYearAdapter); 238 | 239 | mYearView.setCurrentItem(mRepository.getDefaultCalendar().year - minYear); 240 | mFinishYearView.setCurrentItem(mRepository.getDefaultFinishCalendar().year - minYear); 241 | } 242 | 243 | /** 244 | * 初始化月视图 245 | */ 246 | private void initMonthView() { 247 | updateMonths(); 248 | int curYear = getCurrentYear(); 249 | int minMonth = mRepository.getMinMonth(curYear); 250 | mMonthView.setCurrentItem(mRepository.getDefaultCalendar().month - minMonth); 251 | } 252 | 253 | /** 254 | * 初始化结束月视图 255 | */ 256 | private void initFinishMonthView() { 257 | updateFinishMonths(); 258 | int curYear = getCurrentFinishYear(); 259 | int minMonth = mRepository.getMinMonth(curYear); 260 | mFinishMonthView.setCurrentItem(mRepository.getDefaultFinishCalendar().month - minMonth); 261 | } 262 | 263 | /** 264 | * 初始化日视图 265 | */ 266 | private void initDayView() { 267 | updateDays(); 268 | int curYear = getCurrentYear(); 269 | int curMonth = getCurrentMonth(); 270 | 271 | int minDay = mRepository.getMinDay(curYear, curMonth); 272 | mDayView.setCurrentItem(mRepository.getDefaultCalendar().day - minDay); 273 | } 274 | 275 | /** 276 | * 初始化结束日视图 277 | */ 278 | private void initFinishDayView() { 279 | updateFinishDays(); 280 | int curYear = getCurrentFinishYear(); 281 | int curMonth = getCurrentFinishMonth(); 282 | 283 | int minDay = mRepository.getMinDay(curYear, curMonth); 284 | mFinishDayView.setCurrentItem(mRepository.getDefaultFinishCalendar().day - minDay); 285 | } 286 | 287 | /** 288 | * 初始化小时视图 289 | */ 290 | private void initHourView() { 291 | updateHours(); 292 | int curYear = getCurrentYear(); 293 | int curMonth = getCurrentMonth(); 294 | int curDay = getCurrentDay(); 295 | 296 | int minHour = mRepository.getMinHour(curYear, curMonth, curDay); 297 | mHourView.setCurrentItem(mRepository.getDefaultCalendar().hour - minHour); 298 | } 299 | 300 | /** 301 | * 初始化分钟视图 302 | */ 303 | private void initMinuteView() { 304 | updateMinutes(); 305 | int curYear = getCurrentYear(); 306 | int curMonth = getCurrentMonth(); 307 | int curDay = getCurrentDay(); 308 | int curHour = getCurrentHour(); 309 | int minMinute = mRepository.getMinMinute(curYear, curMonth, curDay, curHour); 310 | 311 | mMinuteView.setCurrentItem(mRepository.getDefaultCalendar().minute - minMinute); 312 | } 313 | 314 | /** 315 | * 更新月份 316 | */ 317 | private void updateMonths() { 318 | if (mMonthView.getVisibility() == View.GONE) 319 | return; 320 | 321 | int curYear = getCurrentYear(); 322 | int minMonth = mRepository.getMinMonth(curYear); 323 | int maxMonth = mRepository.getMaxMonth(curYear); 324 | mMonthAdapter = new NumericWheelAdapter(mContext, minMonth, maxMonth, 325 | DateConstants.FORMAT, mScrollerConfig.mMonth); 326 | mMonthAdapter.setConfig(mScrollerConfig); 327 | mMonthView.setViewAdapter(mMonthAdapter); 328 | mMonthView.setCyclic(mScrollerConfig.cyclic); 329 | 330 | // 当滚动数量不足时, 需要避免循环 331 | if (maxMonth - minMonth < mScrollerConfig.mMaxLines) { 332 | mMonthView.setCyclic(false); 333 | } 334 | 335 | if (mRepository.isMinYear(curYear)) { 336 | mMonthView.setCurrentItem(0, false); 337 | } 338 | } 339 | 340 | /** 341 | * 更新结束月份 342 | */ 343 | private void updateFinishMonths() { 344 | if (mFinishMonthView.getVisibility() == View.GONE) 345 | return; 346 | 347 | int curYear = getCurrentFinishYear(); 348 | int minMonth = mRepository.getMinMonth(curYear); 349 | int maxMonth = mRepository.getMaxMonth(curYear); 350 | mMonthAdapter = new NumericWheelAdapter(mContext, minMonth, maxMonth, 351 | DateConstants.FORMAT, mScrollerConfig.mMonth); 352 | mMonthAdapter.setConfig(mScrollerConfig); 353 | mFinishMonthView.setViewAdapter(mMonthAdapter); 354 | mFinishMonthView.setCyclic(mScrollerConfig.cyclic); 355 | 356 | // 当滚动数量不足时, 需要避免循环 357 | if (maxMonth - minMonth < mScrollerConfig.mMaxLines) { 358 | mFinishMonthView.setCyclic(false); 359 | } 360 | 361 | if (mRepository.isMinYear(curYear)) { 362 | mFinishMonthView.setCurrentItem(0, false); 363 | } 364 | } 365 | 366 | /** 367 | * 更新日 368 | */ 369 | private void updateDays() { 370 | if (mDayView.getVisibility() == View.GONE) 371 | return; 372 | 373 | int curYear = getCurrentYear(); 374 | int curMonth = getCurrentMonth(); 375 | 376 | Calendar calendar = Calendar.getInstance(); 377 | calendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR) + mYearView.getCurrentItem()); 378 | calendar.set(Calendar.MONTH, curMonth); 379 | 380 | int maxDay = mRepository.getMaxDay(curYear, curMonth); 381 | int minDay = mRepository.getMinDay(curYear, curMonth); 382 | mDayAdapter = new NumericWheelAdapter(mContext, minDay, maxDay, DateConstants.FORMAT, mScrollerConfig.mDay); 383 | mDayAdapter.setConfig(mScrollerConfig); 384 | mDayView.setViewAdapter(mDayAdapter); 385 | mDayView.setCyclic(mScrollerConfig.cyclic); // 是否循环 386 | 387 | // 当滚动数量不足时, 需要避免循环 388 | if (maxDay - minDay < mScrollerConfig.mMaxLines) { 389 | mDayView.setCyclic(false); 390 | } 391 | 392 | if (mRepository.isMinMonth(curYear, curMonth)) { 393 | mDayView.setCurrentItem(0, true); 394 | } 395 | 396 | int dayCount = mDayAdapter.getItemsCount(); 397 | if (mDayView.getCurrentItem() >= dayCount) { 398 | mDayView.setCurrentItem(dayCount - 1, true); 399 | } 400 | } 401 | 402 | /** 403 | * 更新结束日 404 | */ 405 | private void updateFinishDays() { 406 | if (mFinishDayView.getVisibility() == View.GONE) 407 | return; 408 | 409 | int curYear = getCurrentFinishYear(); 410 | int curMonth = getCurrentFinishMonth(); 411 | 412 | Calendar calendar = Calendar.getInstance(); 413 | calendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR) + mFinishDayView.getCurrentItem()); 414 | calendar.set(Calendar.MONTH, curMonth); 415 | 416 | int maxDay = mRepository.getMaxDay(curYear, curMonth); 417 | int minDay = mRepository.getMinDay(curYear, curMonth); 418 | mDayAdapter = new NumericWheelAdapter(mContext, minDay, maxDay, DateConstants.FORMAT, mScrollerConfig.mDay); 419 | mDayAdapter.setConfig(mScrollerConfig); 420 | mFinishDayView.setViewAdapter(mDayAdapter); 421 | mFinishDayView.setCyclic(mScrollerConfig.cyclic); // 是否循环 422 | 423 | // 当滚动数量不足时, 需要避免循环 424 | if (maxDay - minDay < mScrollerConfig.mMaxLines) { 425 | mFinishDayView.setCyclic(false); 426 | } 427 | 428 | if (mRepository.isMinMonth(curYear, curMonth)) { 429 | mFinishDayView.setCurrentItem(0, true); 430 | } 431 | 432 | int dayCount = mDayAdapter.getItemsCount(); 433 | if (mFinishDayView.getCurrentItem() >= dayCount) { 434 | mFinishDayView.setCurrentItem(dayCount - 1, true); 435 | } 436 | } 437 | 438 | /** 439 | * 更新小时 440 | */ 441 | private void updateHours() { 442 | if (mHourView.getVisibility() == View.GONE) 443 | return; 444 | 445 | int curYear = getCurrentYear(); 446 | int curMonth = getCurrentMonth(); 447 | int curDay = getCurrentDay(); 448 | 449 | int minHour = mRepository.getMinHour(curYear, curMonth, curDay); 450 | int maxHour = mRepository.getMaxHour(curYear, curMonth, curDay); 451 | 452 | mHourAdapter = new NumericWheelAdapter(mContext, minHour, maxHour, DateConstants.FORMAT, mScrollerConfig.mHour); 453 | mHourAdapter.setConfig(mScrollerConfig); 454 | mHourView.setViewAdapter(mHourAdapter); 455 | mHourView.setCyclic(mScrollerConfig.cyclic); 456 | 457 | // 当滚动数量不足时, 需要避免循环 458 | if (maxHour - minHour < mScrollerConfig.mMaxLines) { 459 | mHourView.setCyclic(false); 460 | } 461 | 462 | if (mRepository.isMinDay(curYear, curMonth, curDay)) 463 | mHourView.setCurrentItem(0, false); 464 | } 465 | 466 | /** 467 | * 更新分钟 468 | */ 469 | private void updateMinutes() { 470 | if (mMinuteView.getVisibility() == View.GONE) 471 | return; 472 | 473 | int curYear = getCurrentYear(); 474 | int curMonth = getCurrentMonth(); 475 | int curDay = getCurrentDay(); 476 | int curHour = getCurrentHour(); 477 | 478 | int minMinute = mRepository.getMinMinute(curYear, curMonth, curDay, curHour); 479 | int maxMinute = mRepository.getMaxMinute(curYear, curMonth, curDay, curHour); 480 | 481 | mMinuteAdapter = new NumericWheelAdapter(mContext, minMinute, maxMinute, DateConstants.FORMAT, mScrollerConfig.mMinute); 482 | mMinuteAdapter.setConfig(mScrollerConfig); 483 | mMinuteView.setViewAdapter(mMinuteAdapter); 484 | mMinuteView.setCyclic(mScrollerConfig.cyclic); 485 | 486 | // 当滚动数量不足时, 需要避免循环 487 | if (maxMinute - minMinute < mScrollerConfig.mMaxLines) { 488 | mMinuteView.setCyclic(false); 489 | } 490 | 491 | if (mRepository.isMinHour(curYear, curMonth, curDay, curHour)) 492 | mMinuteView.setCurrentItem(0, false); 493 | } 494 | 495 | int getCurrentYear() { 496 | return mYearView.getCurrentItem() + mRepository.getMinYear(); 497 | } 498 | 499 | int getCurrentFinishYear() { 500 | return mFinishYearView.getCurrentItem() + mRepository.getMinYear(); 501 | } 502 | 503 | int getCurrentMonth() { 504 | int curYear = getCurrentYear(); 505 | return mMonthView.getCurrentItem() + +mRepository.getMinMonth(curYear); 506 | } 507 | 508 | int getCurrentFinishMonth() { 509 | int curYear = getCurrentFinishYear(); 510 | return mFinishMonthView.getCurrentItem() + +mRepository.getMinMonth(curYear); 511 | } 512 | 513 | int getCurrentDay() { 514 | int curYear = getCurrentYear(); 515 | int curMonth = getCurrentMonth(); 516 | return mDayView.getCurrentItem() + mRepository.getMinDay(curYear, curMonth); 517 | } 518 | 519 | int getCurrentFinishDay() { 520 | int curYear = getCurrentFinishYear(); 521 | int curMonth = getCurrentFinishMonth(); 522 | return mFinishDayView.getCurrentItem() + mRepository.getMinDay(curYear, curMonth); 523 | } 524 | 525 | int getCurrentHour() { 526 | int curYear = getCurrentYear(); 527 | int curMonth = getCurrentMonth(); 528 | int curDay = getCurrentDay(); 529 | return mHourView.getCurrentItem() + mRepository.getMinHour(curYear, curMonth, curDay); 530 | } 531 | 532 | int getCurrentMinute() { 533 | int curYear = getCurrentYear(); 534 | int curMonth = getCurrentMonth(); 535 | int curDay = getCurrentDay(); 536 | int curHour = getCurrentHour(); 537 | 538 | return mMinuteView.getCurrentItem() + mRepository.getMinMinute(curYear, curMonth, curDay, curHour); 539 | } 540 | } 541 | -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/widget/adapters/AbstractWheelAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Yuri Kanivets 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.wangchenlong.datescroller.widget.adapters; 18 | 19 | import android.database.DataSetObserver; 20 | import android.view.View; 21 | import android.view.ViewGroup; 22 | 23 | import java.util.LinkedList; 24 | import java.util.List; 25 | 26 | /** 27 | * 抽象滚轮适配器 28 | */ 29 | abstract class AbstractWheelAdapter implements WheelViewAdapter { 30 | private List mDatasetObservers; // 数据集观察者 31 | 32 | @Override 33 | public View getEmptyItem(View convertView, ViewGroup parent) { 34 | return null; 35 | } 36 | 37 | @Override 38 | public void registerDataSetObserver(DataSetObserver observer) { 39 | if (mDatasetObservers == null) { 40 | mDatasetObservers = new LinkedList<>(); 41 | } 42 | mDatasetObservers.add(observer); 43 | } 44 | 45 | @Override 46 | public void unregisterDataSetObserver(DataSetObserver observer) { 47 | if (mDatasetObservers != null) { 48 | mDatasetObservers.remove(observer); 49 | } 50 | } 51 | 52 | /** 53 | * 通知观察者数据改变 54 | */ 55 | protected void notifyDataChangedEvent() { 56 | if (mDatasetObservers != null) { 57 | for (DataSetObserver observer : mDatasetObservers) { 58 | observer.onChanged(); 59 | } 60 | } 61 | } 62 | 63 | /** 64 | * 通知观察者无效数据 65 | */ 66 | protected void notifyDataInvalidatedEvent() { 67 | if (mDatasetObservers != null) { 68 | for (DataSetObserver observer : mDatasetObservers) { 69 | observer.onInvalidated(); 70 | } 71 | } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/widget/adapters/AbstractWheelTextAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Yuri Kanivets 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.wangchenlong.datescroller.widget.adapters; 17 | 18 | import android.content.Context; 19 | import android.util.Log; 20 | import android.view.Gravity; 21 | import android.view.LayoutInflater; 22 | import android.view.View; 23 | import android.view.ViewGroup; 24 | import android.widget.TextView; 25 | 26 | import org.wangchenlong.datescroller.R; 27 | import org.wangchenlong.datescroller.widget.config.ScrollerConfig; 28 | import org.wangchenlong.datescroller.widget.utils.Utils; 29 | 30 | 31 | /** 32 | * Abstract wheel adapter provides common functionality for adapters. 33 | */ 34 | public abstract class AbstractWheelTextAdapter extends AbstractWheelAdapter { 35 | 36 | /** 37 | * Text view resource. Used as a default view for adapter. 38 | */ 39 | public static final int TEXT_VIEW_ITEM_RESOURCE = -1; 40 | 41 | /** 42 | * Default text color 43 | */ 44 | public static final int LABEL_COLOR = 0xFF700070; 45 | /** 46 | * Default text size 47 | */ 48 | public static final int DEFAULT_TEXT_SIZE = 24; 49 | /** 50 | * No resource constant. 51 | */ 52 | protected static final int NO_RESOURCE = 0; 53 | // Current context 54 | protected Context context; 55 | // Layout inflater 56 | protected LayoutInflater inflater; 57 | // Items resources 58 | protected int itemResourceId; 59 | protected int itemTextResourceId; 60 | // Empty items resources 61 | protected int emptyItemResourceId; 62 | 63 | private int padding = 0; 64 | 65 | private ScrollerConfig mScrollerConfig; 66 | 67 | /** 68 | * Constructor 69 | * 70 | * @param context the current context 71 | */ 72 | protected AbstractWheelTextAdapter(Context context) { 73 | this(context, TEXT_VIEW_ITEM_RESOURCE); 74 | } 75 | 76 | /** 77 | * Constructor 78 | * 79 | * @param context the current context 80 | * @param itemResource the resource ID for a layout file containing a TextView to use when instantiating items views 81 | */ 82 | protected AbstractWheelTextAdapter(Context context, int itemResource) { 83 | this(context, itemResource, NO_RESOURCE); 84 | } 85 | 86 | /** 87 | * Constructor 88 | * 89 | * @param context the current context 90 | * @param itemResource the resource ID for a layout file containing a TextView to use when instantiating items views 91 | * @param itemTextResource the resource ID for a text view in the item layout 92 | */ 93 | protected AbstractWheelTextAdapter(Context context, int itemResource, int itemTextResource) { 94 | this.context = context; 95 | itemResourceId = itemResource; 96 | itemTextResourceId = itemTextResource; 97 | padding = context.getResources().getDimensionPixelSize(R.dimen.textview_default_padding); 98 | 99 | inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 100 | } 101 | 102 | /** 103 | * Gets resource Id for items views 104 | * 105 | * @return the item resource Id 106 | */ 107 | public int getItemResource() { 108 | return itemResourceId; 109 | } 110 | 111 | /** 112 | * Sets resource Id for items views 113 | * 114 | * @param itemResourceId the resource Id to set 115 | */ 116 | public void setItemResource(int itemResourceId) { 117 | this.itemResourceId = itemResourceId; 118 | } 119 | 120 | /** 121 | * Gets resource Id for text view in item layout 122 | * 123 | * @return the item text resource Id 124 | */ 125 | public int getItemTextResource() { 126 | return itemTextResourceId; 127 | } 128 | 129 | /** 130 | * Sets resource Id for text view in item layout 131 | * 132 | * @param itemTextResourceId the item text resource Id to set 133 | */ 134 | public void setItemTextResource(int itemTextResourceId) { 135 | this.itemTextResourceId = itemTextResourceId; 136 | } 137 | 138 | /** 139 | * Gets resource Id for empty items views 140 | * 141 | * @return the empty item resource Id 142 | */ 143 | public int getEmptyItemResource() { 144 | return emptyItemResourceId; 145 | } 146 | 147 | /** 148 | * Sets resource Id for empty items views 149 | * 150 | * @param emptyItemResourceId the empty item resource Id to set 151 | */ 152 | public void setEmptyItemResource(int emptyItemResourceId) { 153 | this.emptyItemResourceId = emptyItemResourceId; 154 | } 155 | 156 | 157 | /** 158 | * Returns text for specified item 159 | * 160 | * @param index the item index 161 | * @return the text of specified items 162 | */ 163 | protected abstract CharSequence getItemText(int index); 164 | 165 | @Override 166 | public View getItem(int index, View convertView, ViewGroup parent) { 167 | if (index >= 0 && index < getItemsCount()) { 168 | if (convertView == null) { 169 | convertView = getView(itemResourceId, parent); 170 | } 171 | TextView textView = getTextView(convertView, itemTextResourceId); 172 | if (textView != null) { 173 | CharSequence text = getItemText(index); 174 | if (text == null) { 175 | text = ""; 176 | } 177 | textView.setText(text); 178 | if (itemResourceId == TEXT_VIEW_ITEM_RESOURCE) { 179 | configureTextView(textView); 180 | } 181 | } 182 | return convertView; 183 | } 184 | return null; 185 | } 186 | 187 | @Override 188 | public View getEmptyItem(View convertView, ViewGroup parent) { 189 | if (convertView == null) { 190 | convertView = getView(emptyItemResourceId, parent); 191 | } 192 | if (emptyItemResourceId == TEXT_VIEW_ITEM_RESOURCE && convertView instanceof TextView) { 193 | configureTextView((TextView) convertView); 194 | } 195 | 196 | return convertView; 197 | } 198 | 199 | 200 | /** 201 | * Configures text view. Is called for the TEXT_VIEW_ITEM_RESOURCE views. 202 | * 203 | * @param view the text view to be configured 204 | */ 205 | protected void configureTextView(TextView view) { 206 | if (mScrollerConfig == null) 207 | mScrollerConfig = new ScrollerConfig(); 208 | view.setTextColor(mScrollerConfig.mWheelTVNormalColor); 209 | 210 | view.setGravity(Gravity.CENTER); 211 | view.setPadding(0, padding, 0, padding); 212 | view.setTextSize(mScrollerConfig.mWheelTVSize); 213 | view.setLines(1); 214 | // view.setTypeface(Typeface.SANS_SERIF, Typeface.BOLD); 215 | } 216 | 217 | /** 218 | * Loads a text view from view 219 | * 220 | * @param view the text view or layout containing it 221 | * @param textResource the text resource Id in layout 222 | * @return the loaded text view 223 | */ 224 | private TextView getTextView(View view, int textResource) { 225 | TextView text = null; 226 | try { 227 | if (textResource == NO_RESOURCE && view instanceof TextView) { 228 | text = (TextView) view; 229 | } else if (textResource != NO_RESOURCE) { 230 | text = (TextView) view.findViewById(textResource); 231 | } 232 | } catch (ClassCastException e) { 233 | Log.e("AbstractWheelAdapter", "You must supply a resource ID for a TextView"); 234 | throw new IllegalStateException( 235 | "AbstractWheelAdapter requires the resource ID to be a TextView", e); 236 | } 237 | 238 | return text; 239 | } 240 | 241 | /** 242 | * Loads view from resources 243 | * 244 | * @param resource the resource Id 245 | * @return the loaded view or null if resource is not set 246 | */ 247 | private View getView(int resource, ViewGroup parent) { 248 | switch (resource) { 249 | case NO_RESOURCE: 250 | return null; 251 | case TEXT_VIEW_ITEM_RESOURCE: 252 | return new TextView(context); 253 | default: 254 | return inflater.inflate(resource, parent, false); 255 | } 256 | } 257 | 258 | @Override 259 | public ScrollerConfig getConfig() { 260 | if (mScrollerConfig == null) 261 | mScrollerConfig = new ScrollerConfig(); 262 | return mScrollerConfig; 263 | } 264 | 265 | @Override 266 | public void setConfig(ScrollerConfig config) { 267 | mScrollerConfig = config; 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/widget/adapters/NumericWheelAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 Yuri Kanivets 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.wangchenlong.datescroller.widget.adapters; 18 | 19 | import android.content.Context; 20 | import android.text.TextUtils; 21 | 22 | import org.wangchenlong.datescroller.R; 23 | 24 | /** 25 | * Numeric Wheel adapter. 26 | */ 27 | public class NumericWheelAdapter extends AbstractWheelTextAdapter { 28 | 29 | private static final int DEFAULT_MAX_VALUE = 9; // 默认的数字最大值 30 | private static final int DEFAULT_MIN_VALUE = 0; // 默认的数字最小值 31 | 32 | // Values 33 | private int minValue; 34 | private int maxValue; 35 | 36 | private String format; // format 37 | private String unit; //unit 38 | 39 | /** 40 | * 构造器 41 | * 42 | * @param context 当前上下文 43 | */ 44 | public NumericWheelAdapter(Context context) { 45 | this(context, DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE); 46 | } 47 | 48 | 49 | /** 50 | * 构造器 51 | * 52 | * @param context 上下文 53 | * @param minValue 最小值 54 | * @param maxValue 最大值 55 | */ 56 | public NumericWheelAdapter(Context context, int minValue, int maxValue) { 57 | this(context, minValue, maxValue, null); 58 | } 59 | 60 | /** 61 | * 构造器 62 | * 63 | * @param context 上下文 64 | * @param minValue 最小值 65 | * @param maxValue 最大值 66 | * @param format 格式 67 | */ 68 | public NumericWheelAdapter(Context context, int minValue, int maxValue, String format) { 69 | this(context, minValue, maxValue, format, null); 70 | } 71 | 72 | /** 73 | * 构造器 74 | * 75 | * @param context 上下文 76 | * @param minValue 最小值 77 | * @param maxValue 最大值 78 | * @param format 格式化 79 | * @param unit 滚轮单位 80 | */ 81 | public NumericWheelAdapter(Context context, int minValue, int maxValue, String format, String unit) { 82 | super(context); 83 | this.minValue = minValue; 84 | this.maxValue = maxValue; 85 | this.format = format; 86 | this.unit = unit; 87 | this.itemResourceId = R.layout.tv_item; // 默认样式 88 | } 89 | 90 | @Override 91 | public CharSequence getItemText(int index) { 92 | if (index >= 0 && index < getItemsCount()) { 93 | int value = minValue + index; 94 | String text = !TextUtils.isEmpty(format) ? String.format(format, value) : Integer.toString(value); 95 | text = TextUtils.isEmpty(unit) ? text : text + unit; 96 | 97 | return text; 98 | } 99 | return null; 100 | } 101 | 102 | @Override 103 | public int getItemsCount() { 104 | return maxValue - minValue + 1; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/widget/adapters/WheelViewAdapter.java: -------------------------------------------------------------------------------- 1 | package org.wangchenlong.datescroller.widget.adapters; 2 | 3 | import android.database.DataSetObserver; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | 7 | import org.wangchenlong.datescroller.widget.config.ScrollerConfig; 8 | 9 | 10 | /** 11 | * 滚轮列表视图的适配器接口 12 | */ 13 | public interface WheelViewAdapter { 14 | /** 15 | * 获取滚轮项目的数量 16 | * 17 | * @return 滚轮项目数量 18 | */ 19 | int getItemsCount(); 20 | 21 | /** 22 | * 根据索引获取展示项目的视图. 23 | * 24 | * @param index 索引 25 | * @param convertView 复用视图 26 | * @param parent 依附的父视图 27 | * @return 当前视图 28 | */ 29 | View getItem(int index, View convertView, ViewGroup parent); 30 | 31 | /** 32 | * 获取一个空的滚轮视图, 在开始或者结尾 33 | * 34 | * @param convertView 复用视图 35 | * @param parent 依附的父视图 36 | * @return 空视图 37 | */ 38 | View getEmptyItem(View convertView, ViewGroup parent); 39 | 40 | /** 41 | * 注册数据集的观察者, 供适配器使用 42 | * 43 | * @param observer 被注册观察者 44 | */ 45 | void registerDataSetObserver(DataSetObserver observer); 46 | 47 | /** 48 | * 取消已注册数据集的观察者 49 | * 50 | * @param observer 被取消的观察者 51 | */ 52 | void unregisterDataSetObserver(DataSetObserver observer); 53 | 54 | /** 55 | * 获取滚轮的配置 56 | * 57 | * @return 配置 58 | */ 59 | ScrollerConfig getConfig(); 60 | 61 | /** 62 | * 设置滚轮的配置 63 | * 64 | * @param config 配置 65 | */ 66 | void setConfig(ScrollerConfig config); 67 | 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/widget/config/DefaultConfig.java: -------------------------------------------------------------------------------- 1 | package org.wangchenlong.datescroller.widget.config; 2 | 3 | 4 | import org.wangchenlong.datescroller.R; 5 | import org.wangchenlong.datescroller.widget.data.Type; 6 | 7 | /** 8 | * 默认配置, 类型, 颜色, 文字 9 | * 10 | * @author C.L. Wang 11 | */ 12 | public class DefaultConfig { 13 | public static final Type TYPE = Type.ALL; // 全部类型 14 | 15 | public static final int TOOLBAR_BKG_COLOR = R.color.toolbar_bg; // 背景颜色 16 | public static final int ITEM_SELECTOR_LINE = R.color.item_selector_line; // 选择线颜色 17 | public static final int ITEM_SELECTOR_RECT = R.color.item_selector_rect; // 选择框颜色 18 | 19 | 20 | public static final int TOOLBAR_TV_COLOR = 0xFFFFFFFF; 21 | public static final int TV_NORMAL_COLOR = R.color.timepicker_dialog_bg; // 默认文字颜色 22 | public static final int TV_SELECTOR_COLOR = R.color.toolbar_text_cancel; // 选中文字颜色 23 | 24 | public static final int TV_SIZE = 12; // 尺寸 25 | 26 | public static final boolean CYCLIC = true; // 循环 27 | 28 | public static final int MAX_LINE = 5; // 最大行数, 依据控件样式 29 | 30 | // 默认文案 31 | public static String CANCEL = "取消"; 32 | public static String SURE = "确定"; 33 | public static String TITLE = "请选择日期"; 34 | public static String YEAR = "年"; 35 | public static String MONTH = "月"; 36 | public static String DAY = "日"; 37 | public static String HOUR = "时"; 38 | public static String MINUTE = "分"; 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/widget/config/ScrollerConfig.java: -------------------------------------------------------------------------------- 1 | package org.wangchenlong.datescroller.widget.config; 2 | 3 | 4 | import android.support.annotation.ColorRes; 5 | 6 | import org.wangchenlong.datescroller.widget.data.Type; 7 | import org.wangchenlong.datescroller.widget.data.WheelCalendar; 8 | import org.wangchenlong.datescroller.widget.listener.OnDateSetListener; 9 | 10 | import static org.wangchenlong.datescroller.widget.config.DefaultConfig.MAX_LINE; 11 | 12 | /** 13 | * 滚动配置 14 | */ 15 | public class ScrollerConfig { 16 | public Type mType = DefaultConfig.TYPE; 17 | @ColorRes public int mToolbarBkgColor = DefaultConfig.TOOLBAR_BKG_COLOR; // 背景颜色 18 | @ColorRes public int mItemSelectorLine = DefaultConfig.ITEM_SELECTOR_LINE; // 选中线颜色 19 | @ColorRes public int mItemSelectorRect = DefaultConfig.ITEM_SELECTOR_RECT; // 选中框颜色 20 | 21 | public String mCancelString = DefaultConfig.CANCEL; // 取消 22 | public String mSureString = DefaultConfig.SURE; // 确认 23 | public String mTitleString = DefaultConfig.TITLE; // 标题 24 | public int mToolBarTVColor = DefaultConfig.TOOLBAR_TV_COLOR; // ToolBar的颜色 25 | 26 | public int mWheelTVNormalColor = DefaultConfig.TV_NORMAL_COLOR; // 滚轮默认颜色 27 | public int mWheelTVSelectorColor = DefaultConfig.TV_SELECTOR_COLOR; // 滚轮选中颜色 28 | public int mWheelTVSize = DefaultConfig.TV_SIZE; // 文字默认大小 29 | public boolean cyclic = DefaultConfig.CYCLIC; // 是否循环 30 | 31 | public String mYear = DefaultConfig.YEAR; // 年单位 32 | public String mMonth = DefaultConfig.MONTH; // 月单位 33 | public String mDay = DefaultConfig.DAY; // 日单位 34 | public String mHour = DefaultConfig.HOUR; // 小时单位 35 | public String mMinute = DefaultConfig.MINUTE; // 分钟单位 36 | 37 | public WheelCalendar mMinCalendar = new WheelCalendar(0); // 最小日期 38 | public WheelCalendar mMaxCalendar = new WheelCalendar(0); // 最大日期 39 | public WheelCalendar mCurCalendar = new WheelCalendar(System.currentTimeMillis()); // 当前日期 40 | public WheelCalendar mCurFinishCalendar = new WheelCalendar(System.currentTimeMillis()); // 当前结束日期 41 | 42 | public OnDateSetListener mCallback; // 回调 43 | 44 | public int mMaxLines = MAX_LINE; // 最大行数, 依据控件样式 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/widget/data/Type.java: -------------------------------------------------------------------------------- 1 | package org.wangchenlong.datescroller.widget.data; 2 | 3 | public enum Type { 4 | // 五种选择模式,年月日时分,年月日,时分,月日时分,年月 5 | ALL, 6 | YEAR_MONTH_DAY, 7 | HOURS_MINS, 8 | MONTH_DAY_HOUR_MIN, 9 | YEAR_MONTH, 10 | YEAR 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/widget/data/WheelCalendar.java: -------------------------------------------------------------------------------- 1 | package org.wangchenlong.datescroller.widget.data; 2 | 3 | import java.util.Calendar; 4 | 5 | /** 6 | * 滚轮日期, 通过秒转换时间数据 7 | * 8 | * @author C.L. Wang 9 | */ 10 | public class WheelCalendar { 11 | 12 | public int year, month, day, hour, minute; 13 | 14 | private boolean mNoRange; // 是否含有数据 15 | 16 | public WheelCalendar(long milliseconds) { 17 | initData(milliseconds); 18 | } 19 | 20 | private void initData(long milliseconds) { 21 | if (milliseconds == 0) { 22 | mNoRange = true; 23 | return; 24 | } 25 | Calendar calendar = Calendar.getInstance(); 26 | calendar.clear(); 27 | calendar.setTimeInMillis(milliseconds); 28 | 29 | year = calendar.get(Calendar.YEAR); 30 | month = calendar.get(Calendar.MONTH) + 1; 31 | day = calendar.get(Calendar.DAY_OF_MONTH); 32 | hour = calendar.get(Calendar.HOUR_OF_DAY); 33 | minute = calendar.get(Calendar.MINUTE); 34 | } 35 | 36 | public boolean isNoRange() { 37 | return mNoRange; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/widget/data/source/TimeDataSource.java: -------------------------------------------------------------------------------- 1 | package org.wangchenlong.datescroller.widget.data.source; 2 | 3 | 4 | import org.wangchenlong.datescroller.widget.data.WheelCalendar; 5 | 6 | /** 7 | * 数据源的接口 8 | * 9 | * @author C.L. Wang 10 | */ 11 | public interface TimeDataSource { 12 | 13 | int getMinYear(); 14 | 15 | int getMaxYear(); 16 | 17 | int getMinMonth(int currentYear); 18 | 19 | int getMaxMonth(int currentYear); 20 | 21 | int getMinDay(int year, int month); 22 | 23 | int getMaxDay(int year, int month); 24 | 25 | int getMinHour(int year, int month, int day); 26 | 27 | int getMaxHour(int year, int month, int day); 28 | 29 | int getMinMinute(int year, int month, int day, int hour); 30 | 31 | int getMaxMinute(int year, int month, int day, int hour); 32 | 33 | boolean isMinYear(int year); 34 | 35 | boolean isMinMonth(int year, int month); 36 | 37 | boolean isMinDay(int year, int month, int day); 38 | 39 | boolean isMinHour(int year, int month, int day, int hour); 40 | 41 | WheelCalendar getDefaultCalendar(); 42 | WheelCalendar getDefaultFinishCalendar(); 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/widget/data/source/TimeRepository.java: -------------------------------------------------------------------------------- 1 | package org.wangchenlong.datescroller.widget.data.source; 2 | 3 | 4 | import org.wangchenlong.datescroller.widget.config.ScrollerConfig; 5 | import org.wangchenlong.datescroller.widget.data.WheelCalendar; 6 | import org.wangchenlong.datescroller.widget.utils.DateConstants; 7 | import org.wangchenlong.datescroller.widget.utils.Utils; 8 | 9 | import java.util.Calendar; 10 | 11 | /** 12 | * 时间数据集合, 代理WheelCalendar和DateConstants 13 | * 14 | * @author C.L. Wang 15 | */ 16 | public class TimeRepository implements TimeDataSource { 17 | private ScrollerConfig mScrollerConfig; 18 | 19 | private WheelCalendar mCalendarMin, mCalendarMax; 20 | 21 | private boolean mIsMinNoRange, mIsMaxNoRange; 22 | 23 | public TimeRepository(ScrollerConfig scrollerConfig) { 24 | mScrollerConfig = scrollerConfig; 25 | mCalendarMin = scrollerConfig.mMinCalendar; 26 | mCalendarMax = scrollerConfig.mMaxCalendar; 27 | 28 | mIsMinNoRange = mCalendarMin.isNoRange(); 29 | mIsMaxNoRange = mCalendarMax.isNoRange(); 30 | } 31 | 32 | @Override 33 | public int getMinYear() { 34 | if (mIsMinNoRange) 35 | return DateConstants.DEFAULT_MIN_YEAR; 36 | else 37 | return mCalendarMin.year; 38 | } 39 | 40 | @Override 41 | public int getMaxYear() { 42 | if (mIsMaxNoRange) 43 | return getMinYear() + DateConstants.YEAR_COUNT; 44 | 45 | return mCalendarMax.year; 46 | } 47 | 48 | @Override 49 | public int getMinMonth(int year) { 50 | if (!mIsMinNoRange && Utils.isTimeEquals(mCalendarMin, year)) 51 | return mCalendarMin.month; 52 | 53 | return DateConstants.MIN_MONTH; 54 | } 55 | 56 | @Override 57 | public int getMaxMonth(int year) { 58 | if (!mIsMaxNoRange && Utils.isTimeEquals(mCalendarMax, year)) 59 | return mCalendarMax.month; 60 | 61 | return DateConstants.MAX_MONTH; 62 | } 63 | 64 | @Override 65 | public int getMinDay(int year, int month) { 66 | if (!mIsMinNoRange && Utils.isTimeEquals(mCalendarMin, year, month)) 67 | return mCalendarMin.day; 68 | 69 | return DateConstants.MIN_DAY; 70 | } 71 | 72 | @Override 73 | public int getMaxDay(int year, int month) { 74 | if (!mIsMaxNoRange && Utils.isTimeEquals(mCalendarMax, year, month)) 75 | return mCalendarMax.day; 76 | 77 | Calendar calendar = Calendar.getInstance(); 78 | calendar.set(Calendar.YEAR, year); 79 | calendar.set(Calendar.DATE, 1); 80 | calendar.set(Calendar.MONTH, month - 1); 81 | 82 | return calendar.getActualMaximum(Calendar.DAY_OF_MONTH); 83 | } 84 | 85 | @Override 86 | public int getMinHour(int year, int month, int day) { 87 | if (!mIsMinNoRange && Utils.isTimeEquals(mCalendarMin, year, month, day)) 88 | return mCalendarMin.hour; 89 | else 90 | return DateConstants.MIN_HOUR; 91 | } 92 | 93 | @Override 94 | public int getMaxHour(int year, int month, int day) { 95 | if (!mIsMaxNoRange && Utils.isTimeEquals(mCalendarMax, year, month, day)) 96 | return mCalendarMax.hour; 97 | else 98 | return DateConstants.MAX_HOUR; 99 | } 100 | 101 | @Override 102 | public int getMinMinute(int year, int month, int day, int hour) { 103 | if (!mIsMinNoRange && Utils.isTimeEquals(mCalendarMin, year, month, day, hour)) 104 | return mCalendarMin.minute + 1; 105 | else 106 | return DateConstants.MIN_MINUTE; 107 | } 108 | 109 | @Override 110 | public int getMaxMinute(int year, int month, int day, int hour) { 111 | if (!mIsMaxNoRange && Utils.isTimeEquals(mCalendarMax, year, month, day, hour)) 112 | return mCalendarMax.minute; 113 | 114 | return DateConstants.MAX_MINUTE; 115 | } 116 | 117 | @Override 118 | public boolean isMinYear(int year) { 119 | return Utils.isTimeEquals(mCalendarMin, year); 120 | } 121 | 122 | @Override 123 | public boolean isMinMonth(int year, int month) { 124 | return Utils.isTimeEquals(mCalendarMin, year, month); 125 | } 126 | 127 | @Override 128 | public boolean isMinDay(int year, int month, int day) { 129 | return Utils.isTimeEquals(mCalendarMin, year, month, day); 130 | } 131 | 132 | @Override 133 | public boolean isMinHour(int year, int month, int day, int hour) { 134 | return Utils.isTimeEquals(mCalendarMin, year, month, day, hour); 135 | } 136 | 137 | 138 | @Override 139 | public WheelCalendar getDefaultCalendar() { 140 | return mScrollerConfig.mCurCalendar; 141 | } 142 | 143 | @Override 144 | public WheelCalendar getDefaultFinishCalendar() { 145 | return mScrollerConfig.mCurFinishCalendar; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/widget/listener/OnDateSetListener.java: -------------------------------------------------------------------------------- 1 | package org.wangchenlong.datescroller.widget.listener; 2 | 3 | 4 | import org.wangchenlong.datescroller.widget.DateScrollerDialog; 5 | 6 | /** 7 | * 日期设置的监听器 8 | * 9 | * @author C.L. Wang 10 | */ 11 | public interface OnDateSetListener { 12 | void onDateSet(DateScrollerDialog timePickerView, long milliseconds, long milliFinishSeconds); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/widget/utils/DateConstants.java: -------------------------------------------------------------------------------- 1 | package org.wangchenlong.datescroller.widget.utils; 2 | 3 | /** 4 | * 时间的若干常量 5 | * 6 | * @author C.L. Wang 7 | */ 8 | public class DateConstants { 9 | public static final String FORMAT = "%02d"; 10 | 11 | public static final int DEFAULT_MIN_YEAR = 2015; 12 | public static final int YEAR_COUNT = 50; 13 | public static final int MIN_MONTH = 1; 14 | public static final int MAX_MONTH = 12; 15 | public static final int MIN_DAY = MIN_MONTH; 16 | public static final int MIN_HOUR = 0; 17 | public static final int MAX_HOUR = 23; 18 | public static final int MIN_MINUTE = 0; 19 | public static final int MAX_MINUTE = 59; 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/widget/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package org.wangchenlong.datescroller.widget.utils; 2 | 3 | import android.content.Context; 4 | import android.view.View; 5 | 6 | import org.wangchenlong.datescroller.widget.data.WheelCalendar; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | 12 | /** 13 | * 工具类 14 | * 15 | * @author C.L.Wang 16 | */ 17 | public class Utils { 18 | 19 | /** 20 | * 判断时间是否相等 21 | * 22 | * @param calendar 日期 23 | * @param params 参数 24 | * @return 时间是否相等 25 | */ 26 | public static boolean isTimeEquals(WheelCalendar calendar, int... params) { 27 | switch (params.length) { 28 | case 1: 29 | return calendar.year == params[0]; 30 | case 2: 31 | return calendar.year == params[0] && 32 | calendar.month == params[1]; 33 | case 3: 34 | return calendar.year == params[0] && 35 | calendar.month == params[1] && 36 | calendar.day == params[2]; 37 | case 4: 38 | return calendar.year == params[0] && 39 | calendar.month == params[1] && 40 | calendar.day == params[2] && 41 | calendar.hour == params[3]; 42 | } 43 | return false; 44 | } 45 | 46 | /** 47 | * 全部隐藏视图 48 | * 49 | * @param views 视图 50 | */ 51 | public static void hideViews(View... views) { 52 | for (View view : views) { 53 | view.setVisibility(View.GONE); 54 | } 55 | } 56 | 57 | public static int px2dp(Context context, float pxValue) { 58 | float m = context.getResources().getDisplayMetrics().density; 59 | return (int) (pxValue / m + 0.5f); 60 | } 61 | 62 | public static int dp2px(Context context, float dipValue) { 63 | float m = context.getResources().getDisplayMetrics().density; 64 | return (int) (dipValue * m + 0.5f); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/widget/wheel/ItemsRange.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Android Wheel Control. 3 | * https://code.google.com/p/android-wheel/ 4 | * 5 | * Copyright 2011 Yuri Kanivets 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package org.wangchenlong.datescroller.widget.wheel; 21 | 22 | /** 23 | * Range for visible items. 24 | */ 25 | public class ItemsRange { 26 | // First item number 27 | private int first; 28 | 29 | // Items count 30 | private int count; 31 | 32 | /** 33 | * Default constructor. Creates an empty range 34 | */ 35 | public ItemsRange() { 36 | this(0, 0); 37 | } 38 | 39 | /** 40 | * Constructor 41 | * 42 | * @param first the number of first item 43 | * @param count the count of items 44 | */ 45 | public ItemsRange(int first, int count) { 46 | this.first = first; 47 | this.count = count; 48 | } 49 | 50 | /** 51 | * Gets number of first item 52 | * 53 | * @return the number of the first item 54 | */ 55 | public int getFirst() { 56 | return first; 57 | } 58 | 59 | /** 60 | * Gets number of last item 61 | * 62 | * @return the number of last item 63 | */ 64 | public int getLast() { 65 | return getFirst() + getCount() - 1; 66 | } 67 | 68 | /** 69 | * Get items count 70 | * 71 | * @return the count of items 72 | */ 73 | public int getCount() { 74 | return count; 75 | } 76 | 77 | /** 78 | * Tests whether item is contained by range 79 | * 80 | * @param index the item number 81 | * @return true if item is contained 82 | */ 83 | public boolean contains(int index) { 84 | return index >= getFirst() && index <= getLast(); 85 | } 86 | } -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/widget/wheel/OnWheelChangedListener.java: -------------------------------------------------------------------------------- 1 | package org.wangchenlong.datescroller.widget.wheel; 2 | 3 | /** 4 | * Wheel changed listener interface. 5 | * The onChanged() method is called whenever current wheel positions is changed: 6 | * New Wheel position is set 7 | * Wheel view is scrolled 8 | */ 9 | public interface OnWheelChangedListener { 10 | /** 11 | * Callback method to be invoked when current item changed 12 | * 13 | * @param wheel the wheel view whose state has changed 14 | * @param oldValue the old value of current item 15 | * @param newValue the new value of current item 16 | */ 17 | void onChanged(WheelView wheel, int oldValue, int newValue); 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/widget/wheel/OnWheelClickedListener.java: -------------------------------------------------------------------------------- 1 | package org.wangchenlong.datescroller.widget.wheel; 2 | 3 | /** 4 | * Wheel clicked listener interface. 5 | * The onItemClicked() method is called whenever a wheel item is clicked 6 | * New Wheel position is set 7 | * Wheel view is scrolled 8 | */ 9 | public interface OnWheelClickedListener { 10 | /** 11 | * Callback method to be invoked when current item clicked 12 | * 13 | * @param wheel the wheel view 14 | * @param itemIndex the index of clicked item 15 | */ 16 | void onItemClicked(WheelView wheel, int itemIndex); 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/widget/wheel/OnWheelScrollListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Yuri Kanivets 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.wangchenlong.datescroller.widget.wheel; 18 | 19 | /** 20 | * Wheel scrolled listener interface. 21 | */ 22 | public interface OnWheelScrollListener { 23 | /** 24 | * Callback method to be invoked when scrolling started. 25 | * 26 | * @param wheel the wheel view whose state has changed. 27 | */ 28 | void onScrollingStarted(WheelView wheel); 29 | 30 | /** 31 | * Callback method to be invoked when scrolling ended. 32 | * 33 | * @param wheel the wheel view whose state has changed. 34 | */ 35 | void onScrollingFinished(WheelView wheel); 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/widget/wheel/WheelRecycle.java: -------------------------------------------------------------------------------- 1 | package org.wangchenlong.datescroller.widget.wheel; 2 | 3 | import android.view.View; 4 | import android.widget.LinearLayout; 5 | 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | 9 | /** 10 | * Recycle stores wheel items to reuse. 11 | */ 12 | public class WheelRecycle { 13 | // Cached items 14 | private List items; 15 | 16 | // Cached empty items 17 | private List emptyItems; 18 | 19 | // Wheel view 20 | private WheelView wheel; 21 | 22 | 23 | public WheelRecycle(WheelView wheel) { 24 | this.wheel = wheel; 25 | } 26 | 27 | public int recycleItems(LinearLayout layout, int firstItem, ItemsRange range, int currentItem) { 28 | int index = firstItem; 29 | for (int i = 0; i < layout.getChildCount(); ) { 30 | if (!range.contains(index)) { 31 | recycleView(layout.getChildAt(i), index, currentItem); 32 | layout.removeViewAt(i); 33 | if (i == 0) { // first item 34 | firstItem++; 35 | } 36 | } else { 37 | i++; // go to next item 38 | } 39 | index++; 40 | } 41 | return firstItem; 42 | } 43 | 44 | /** 45 | * Gets item view 46 | * 47 | * @return the cached view 48 | */ 49 | public View getItem() { 50 | return getCachedView(items); 51 | } 52 | 53 | /** 54 | * Gets empty item view 55 | * 56 | * @return the cached empty view 57 | */ 58 | public View getEmptyItem() { 59 | return getCachedView(emptyItems); 60 | } 61 | 62 | /** 63 | * Clears all views 64 | */ 65 | public void clearAll() { 66 | if (items != null) { 67 | items.clear(); 68 | } 69 | if (emptyItems != null) { 70 | emptyItems.clear(); 71 | } 72 | } 73 | 74 | /** 75 | * Adds view to specified cache. Creates a cache list if it is null. 76 | * 77 | * @param view the view to be cached 78 | * @param cache the cache list 79 | * @return the cache list 80 | */ 81 | private List addView(View view, List cache) { 82 | if (cache == null) { 83 | cache = new LinkedList(); 84 | } 85 | 86 | cache.add(view); 87 | return cache; 88 | } 89 | 90 | /** 91 | * Adds view to cache. Determines view type (item view or empty one) by index. 92 | * 93 | * @param view the view to be cached 94 | * @param index the index of view 95 | */ 96 | private void recycleView(View view, int index, int current) { 97 | int count = wheel.getViewAdapter().getItemsCount(); 98 | 99 | 100 | if ((index < 0 || index >= count) && !wheel.isCyclic()) { 101 | // empty view 102 | emptyItems = addView(view, emptyItems); 103 | } else { 104 | while (index < 0) { 105 | index = count + index; 106 | } 107 | index %= count; 108 | items = addView(view, items); 109 | } 110 | } 111 | 112 | /** 113 | * Gets view from specified cache. 114 | * 115 | * @param cache the cache 116 | * @return the first view from cache. 117 | */ 118 | private View getCachedView(List cache) { 119 | if (cache != null && cache.size() > 0) { 120 | View view = cache.get(0); 121 | cache.remove(0); 122 | return view; 123 | } 124 | return null; 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/widget/wheel/WheelScroller.java: -------------------------------------------------------------------------------- 1 | package org.wangchenlong.datescroller.widget.wheel; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.os.Message; 6 | import android.view.GestureDetector; 7 | import android.view.GestureDetector.SimpleOnGestureListener; 8 | import android.view.MotionEvent; 9 | import android.view.animation.Interpolator; 10 | import android.widget.Scroller; 11 | 12 | public class WheelScroller { 13 | 14 | public static final int MIN_DELTA_FOR_SCROLLING = 1; 15 | private static final int SCROLLING_DURATION = 400; 16 | // Messages 17 | private final int MESSAGE_SCROLL = 0; 18 | private final int MESSAGE_JUSTIFY = 1; 19 | private ScrollingListener listener; 20 | // Context 21 | private Context context; 22 | // Scrolling 23 | private GestureDetector gestureDetector; 24 | private Scroller scroller; 25 | private int lastScrollY; 26 | private float lastTouchedY; 27 | private boolean isScrollingPerformed; 28 | // animation handler 29 | private Handler animationHandler = new Handler() { 30 | public void handleMessage(Message msg) { 31 | scroller.computeScrollOffset(); 32 | int currY = scroller.getCurrY(); 33 | int delta = lastScrollY - currY; 34 | lastScrollY = currY; 35 | if (delta != 0) { 36 | listener.onScroll(delta); 37 | } 38 | 39 | // scrolling is not finished when it comes to final Y 40 | // so, finish it manually 41 | if (Math.abs(currY - scroller.getFinalY()) < MIN_DELTA_FOR_SCROLLING) { 42 | currY = scroller.getFinalY(); 43 | scroller.forceFinished(true); 44 | } 45 | if (!scroller.isFinished()) { 46 | animationHandler.sendEmptyMessage(msg.what); 47 | } else if (msg.what == MESSAGE_SCROLL) { 48 | justify(); 49 | } else { 50 | finishScrolling(); 51 | } 52 | } 53 | }; 54 | // gesture listener 55 | private SimpleOnGestureListener gestureListener = new SimpleOnGestureListener() { 56 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 57 | // Do scrolling in onTouchEvent() since onScroll() are not call immediately 58 | // when user touch and move the wheel 59 | return true; 60 | } 61 | 62 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 63 | lastScrollY = 0; 64 | final int maxY = 0x7FFFFFFF; 65 | final int minY = -maxY; 66 | scroller.fling(0, lastScrollY, 0, (int) -velocityY, 0, 0, minY, maxY); 67 | setNextMessage(MESSAGE_SCROLL); 68 | return true; 69 | } 70 | }; 71 | 72 | 73 | public WheelScroller(Context context, ScrollingListener listener) { 74 | gestureDetector = new GestureDetector(context, gestureListener); 75 | gestureDetector.setIsLongpressEnabled(false); 76 | 77 | scroller = new Scroller(context); 78 | 79 | this.listener = listener; 80 | this.context = context; 81 | } 82 | 83 | 84 | public void setInterpolator(Interpolator interpolator) { 85 | scroller.forceFinished(true); 86 | scroller = new Scroller(context, interpolator); 87 | } 88 | 89 | public void scroll(int distance, int time) { 90 | scroller.forceFinished(true); 91 | 92 | lastScrollY = 0; 93 | 94 | scroller.startScroll(0, 0, 0, distance, time != 0 ? time : SCROLLING_DURATION); 95 | setNextMessage(MESSAGE_SCROLL); 96 | 97 | startScrolling(); 98 | } 99 | 100 | /** 101 | * Stops scrolling 102 | */ 103 | public void stopScrolling() { 104 | scroller.forceFinished(true); 105 | } 106 | 107 | /** 108 | * Handles Touch event 109 | * 110 | * @param event the motion event 111 | * @return 112 | */ 113 | public boolean onTouchEvent(MotionEvent event) { 114 | switch (event.getAction()) { 115 | case MotionEvent.ACTION_DOWN: 116 | lastTouchedY = event.getY(); 117 | scroller.forceFinished(true); 118 | clearMessages(); 119 | break; 120 | 121 | case MotionEvent.ACTION_MOVE: 122 | // perform scrolling 123 | int distanceY = (int) (event.getY() - lastTouchedY); 124 | if (distanceY != 0) { 125 | startScrolling(); 126 | listener.onScroll(distanceY); 127 | lastTouchedY = event.getY(); 128 | } 129 | break; 130 | } 131 | 132 | if (!gestureDetector.onTouchEvent(event) && event.getAction() == MotionEvent.ACTION_UP) { 133 | justify(); 134 | } 135 | 136 | return true; 137 | } 138 | 139 | /** 140 | * Set next message to queue. Clears queue before. 141 | * 142 | * @param message the message to set 143 | */ 144 | private void setNextMessage(int message) { 145 | clearMessages(); 146 | animationHandler.sendEmptyMessage(message); 147 | } 148 | 149 | /** 150 | * Clears messages from queue 151 | */ 152 | private void clearMessages() { 153 | animationHandler.removeMessages(MESSAGE_SCROLL); 154 | animationHandler.removeMessages(MESSAGE_JUSTIFY); 155 | } 156 | 157 | /** 158 | * Justifies wheel 159 | */ 160 | private void justify() { 161 | listener.onJustify(); 162 | setNextMessage(MESSAGE_JUSTIFY); 163 | } 164 | 165 | /** 166 | * Starts scrolling 167 | */ 168 | private void startScrolling() { 169 | if (!isScrollingPerformed) { 170 | isScrollingPerformed = true; 171 | listener.onStarted(); 172 | } 173 | } 174 | 175 | /** 176 | * Finishes scrolling 177 | */ 178 | void finishScrolling() { 179 | if (isScrollingPerformed) { 180 | listener.onFinished(); 181 | isScrollingPerformed = false; 182 | } 183 | } 184 | 185 | public interface ScrollingListener { 186 | /** 187 | * Scrolling callback called when scrolling is performed. 188 | * 189 | * @param distance the distance to scroll 190 | */ 191 | void onScroll(int distance); 192 | 193 | /** 194 | * Starting callback called when scrolling is started 195 | */ 196 | void onStarted(); 197 | 198 | /** 199 | * Finishing callback called after justifying 200 | */ 201 | void onFinished(); 202 | 203 | /** 204 | * Justifying callback called to justify a view when scrolling is ended 205 | */ 206 | void onJustify(); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /app/src/main/java/org/wangchenlong/datescroller/widget/wheel/WheelView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Android Wheel Control. 3 | * https://code.google.com/p/android-wheel/ 4 | * 5 | * Copyright 2011 Yuri Kanivets 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package org.wangchenlong.datescroller.widget.wheel; 21 | 22 | import android.content.Context; 23 | import android.database.DataSetObserver; 24 | import android.graphics.Canvas; 25 | import android.graphics.Paint; 26 | import android.util.AttributeSet; 27 | import android.view.MotionEvent; 28 | import android.view.View; 29 | import android.view.ViewGroup.LayoutParams; 30 | import android.view.animation.Interpolator; 31 | import android.widget.LinearLayout; 32 | import android.widget.TextView; 33 | 34 | 35 | import org.wangchenlong.datescroller.R; 36 | import org.wangchenlong.datescroller.widget.adapters.WheelViewAdapter; 37 | import org.wangchenlong.datescroller.widget.config.DefaultConfig; 38 | import org.wangchenlong.datescroller.widget.config.ScrollerConfig; 39 | import org.wangchenlong.datescroller.widget.utils.Utils; 40 | 41 | import java.util.LinkedList; 42 | import java.util.List; 43 | 44 | 45 | /** 46 | * Numeric wheel view. 47 | */ 48 | public class WheelView extends View { 49 | 50 | /** 51 | * Top and bottom items offset (to hide that) 52 | */ 53 | private static final int ITEM_OFFSET_PERCENT = 10; 54 | 55 | /** 56 | * Left and right padding value 57 | */ 58 | private static final int PADDING = 10; 59 | 60 | /** 61 | * Default count of visible items 62 | */ 63 | private static final int DEF_VISIBLE_ITEMS = 5; 64 | // Cyclic 65 | boolean isCyclic = false; 66 | int defaultColor, selectorColor; 67 | // Wheel Values 68 | private int currentItem = 0; 69 | // Count of visible items 70 | private int visibleItems = DEF_VISIBLE_ITEMS; 71 | // Item height 72 | private int mItemHeight = 0; 73 | // Scrolling 74 | private WheelScroller scroller; 75 | private boolean isScrollingPerformed; 76 | private int scrollingOffset; 77 | // Items layout 78 | private LinearLayout itemsLayout; 79 | // The number of first item in layout 80 | private int firstItem; 81 | // View adapter 82 | private WheelViewAdapter viewAdapter; 83 | 84 | // Recycle 85 | private WheelRecycle recycle = new WheelRecycle(this); 86 | private Paint mPaintLineCenter, mPaintLineRight, mPaintRectCenter; 87 | private int mLineRightMar; 88 | // Listeners 89 | private List changingListeners = new LinkedList(); 90 | private List scrollingListeners = new LinkedList(); 91 | // Scrolling listener 92 | WheelScroller.ScrollingListener scrollingListener = new WheelScroller.ScrollingListener() { 93 | public void onStarted() { 94 | isScrollingPerformed = true; 95 | notifyScrollingListenersAboutStart(); 96 | } 97 | 98 | public void onScroll(int distance) { 99 | doScroll(distance); 100 | 101 | int height = getHeight(); 102 | if (scrollingOffset > height) { 103 | scrollingOffset = height; 104 | scroller.stopScrolling(); 105 | } else if (scrollingOffset < -height) { 106 | scrollingOffset = -height; 107 | scroller.stopScrolling(); 108 | } 109 | } 110 | 111 | public void onFinished() { 112 | if (isScrollingPerformed) { 113 | notifyScrollingListenersAboutEnd(); 114 | isScrollingPerformed = false; 115 | } 116 | 117 | scrollingOffset = 0; 118 | invalidate(); 119 | } 120 | 121 | public void onJustify() { 122 | if (Math.abs(scrollingOffset) > WheelScroller.MIN_DELTA_FOR_SCROLLING) { 123 | scroller.scroll(scrollingOffset, 0); 124 | } 125 | } 126 | }; 127 | private List clickingListeners = new LinkedList(); 128 | // Adapter listener 129 | private DataSetObserver dataObserver = new DataSetObserver() { 130 | @Override 131 | public void onChanged() { 132 | invalidateWheel(false); 133 | } 134 | 135 | @Override 136 | public void onInvalidated() { 137 | invalidateWheel(true); 138 | } 139 | }; 140 | 141 | /** 142 | * Constructor 143 | */ 144 | public WheelView(Context context, AttributeSet attrs, int defStyle) { 145 | super(context, attrs, defStyle); 146 | initData(context); 147 | } 148 | 149 | /** 150 | * Constructor 151 | */ 152 | public WheelView(Context context, AttributeSet attrs) { 153 | super(context, attrs); 154 | initData(context); 155 | } 156 | 157 | /** 158 | * Constructor 159 | */ 160 | public WheelView(Context context) { 161 | super(context); 162 | initData(context); 163 | } 164 | 165 | /** 166 | * Initializes class data 167 | * 168 | * @param context the context 169 | */ 170 | private void initData(Context context) { 171 | scroller = new WheelScroller(getContext(), scrollingListener); 172 | 173 | mPaintLineCenter = new Paint(); 174 | mPaintLineCenter.setColor(getResources().getColor(DefaultConfig.TOOLBAR_BKG_COLOR)); 175 | mPaintLineCenter.setAntiAlias(true); 176 | mPaintLineCenter.setStrokeWidth(1); 177 | mPaintLineCenter.setStyle(Paint.Style.FILL); 178 | 179 | mPaintLineRight = new Paint(); 180 | mPaintLineRight.setColor(0xffe8e8e8); 181 | mPaintLineRight.setAntiAlias(true); 182 | // mPaintLineRight.setStrokeWidth(context.getResources().getDimensionPixelSize(R.dimen.picker_line_width)); 183 | mPaintLineRight.setStrokeWidth(1); 184 | mPaintLineRight.setStyle(Paint.Style.FILL); 185 | 186 | mPaintRectCenter = new Paint(); 187 | mPaintRectCenter.setColor(getResources().getColor(DefaultConfig.TOOLBAR_BKG_COLOR)); 188 | mPaintRectCenter.setAlpha((int) (0.1 * 255)); 189 | mPaintRectCenter.setAntiAlias(true); 190 | mPaintRectCenter.setStyle(Paint.Style.FILL); 191 | 192 | mLineRightMar = context.getResources().getDimensionPixelSize(R.dimen.picker_line_mar); 193 | 194 | defaultColor = DefaultConfig.TV_NORMAL_COLOR; 195 | selectorColor = DefaultConfig.TV_SELECTOR_COLOR; 196 | } 197 | 198 | public void setConfig(ScrollerConfig config) { 199 | mPaintLineCenter.setColor(getResources().getColor(config.mItemSelectorLine)); 200 | 201 | mPaintRectCenter.setColor(getResources().getColor(config.mItemSelectorRect)); 202 | mPaintRectCenter.setAlpha((int) (0.1 * 255)); 203 | 204 | defaultColor = config.mWheelTVNormalColor; 205 | selectorColor = config.mWheelTVSelectorColor; 206 | } 207 | 208 | 209 | /** 210 | * Set the the specified scrolling interpolator 211 | * 212 | * @param interpolator the interpolator 213 | */ 214 | public void setInterpolator(Interpolator interpolator) { 215 | scroller.setInterpolator(interpolator); 216 | } 217 | 218 | /** 219 | * Gets count of visible items 220 | * 221 | * @return the count of visible items 222 | */ 223 | public int getVisibleItems() { 224 | return visibleItems; 225 | } 226 | 227 | /** 228 | * Sets the desired count of visible items. 229 | * Actual amount of visible items depends on wheel layout parameters. 230 | * To apply changes and rebuild view call measure(). 231 | * 232 | * @param count the desired count for visible items 233 | */ 234 | public void setVisibleItems(int count) { 235 | visibleItems = count; 236 | } 237 | 238 | /** 239 | * Gets view adapter 240 | * 241 | * @return the view adapter 242 | */ 243 | public WheelViewAdapter getViewAdapter() { 244 | return viewAdapter; 245 | } 246 | 247 | /** 248 | * Sets view adapter. Usually new adapters contain different views, so 249 | * it needs to rebuild view by calling measure(). 250 | * 251 | * @param viewAdapter the view adapter 252 | */ 253 | public void setViewAdapter(WheelViewAdapter viewAdapter) { 254 | if (this.viewAdapter != null) { 255 | this.viewAdapter.unregisterDataSetObserver(dataObserver); 256 | } 257 | this.viewAdapter = viewAdapter; 258 | if (this.viewAdapter != null) { 259 | this.viewAdapter.registerDataSetObserver(dataObserver); 260 | } 261 | 262 | setConfig(viewAdapter.getConfig()); 263 | invalidateWheel(true); 264 | } 265 | 266 | /** 267 | * Adds wheel changing listener 268 | * 269 | * @param listener the listener 270 | */ 271 | public void addChangingListener(OnWheelChangedListener listener) { 272 | changingListeners.add(listener); 273 | } 274 | 275 | /** 276 | * Removes wheel changing listener 277 | * 278 | * @param listener the listener 279 | */ 280 | public void removeChangingListener(OnWheelChangedListener listener) { 281 | changingListeners.remove(listener); 282 | } 283 | 284 | /** 285 | * Notifies changing listeners 286 | * 287 | * @param oldValue the old wheel value 288 | * @param newValue the new wheel value 289 | */ 290 | protected void notifyChangingListeners(int oldValue, int newValue) { 291 | for (OnWheelChangedListener listener : changingListeners) { 292 | listener.onChanged(this, oldValue, newValue); 293 | } 294 | 295 | if (oldValue < 0 || newValue < 0 || itemsLayout == null) 296 | return; 297 | 298 | View oldView = itemsLayout.getChildAt(oldValue - firstItem); 299 | View newView = itemsLayout.getChildAt(newValue - firstItem); 300 | 301 | refreshTextStatus(oldView, oldValue); 302 | refreshTextStatus(newView, newValue); 303 | 304 | } 305 | 306 | /** 307 | * Adds wheel scrolling listener 308 | * 309 | * @param listener the listener 310 | */ 311 | public void addScrollingListener(OnWheelScrollListener listener) { 312 | scrollingListeners.add(listener); 313 | } 314 | 315 | /** 316 | * Removes wheel scrolling listener 317 | * 318 | * @param listener the listener 319 | */ 320 | public void removeScrollingListener(OnWheelScrollListener listener) { 321 | scrollingListeners.remove(listener); 322 | } 323 | 324 | /** 325 | * Notifies listeners about starting scrolling 326 | */ 327 | protected void notifyScrollingListenersAboutStart() { 328 | for (OnWheelScrollListener listener : scrollingListeners) { 329 | listener.onScrollingStarted(this); 330 | } 331 | } 332 | 333 | /** 334 | * Notifies listeners about ending scrolling 335 | */ 336 | protected void notifyScrollingListenersAboutEnd() { 337 | for (OnWheelScrollListener listener : scrollingListeners) { 338 | listener.onScrollingFinished(this); 339 | } 340 | 341 | 342 | } 343 | 344 | /** 345 | * Adds wheel clicking listener 346 | * 347 | * @param listener the listener 348 | */ 349 | public void addClickingListener(OnWheelClickedListener listener) { 350 | clickingListeners.add(listener); 351 | } 352 | 353 | /** 354 | * Removes wheel clicking listener 355 | * 356 | * @param listener the listener 357 | */ 358 | public void removeClickingListener(OnWheelClickedListener listener) { 359 | clickingListeners.remove(listener); 360 | } 361 | 362 | /** 363 | * Notifies listeners about clicking 364 | */ 365 | protected void notifyClickListenersAboutClick(int item) { 366 | for (OnWheelClickedListener listener : clickingListeners) { 367 | listener.onItemClicked(this, item); 368 | } 369 | } 370 | 371 | /** 372 | * Gets current value 373 | * 374 | * @return the current value 375 | */ 376 | public int getCurrentItem() { 377 | return currentItem; 378 | } 379 | 380 | /** 381 | * Sets the current item w/o animation. Does nothing when index is wrong. 382 | * 383 | * @param index the item index 384 | */ 385 | public void setCurrentItem(int index) { 386 | setCurrentItem(index, false); 387 | } 388 | 389 | /** 390 | * Sets the current item. Does nothing when index is wrong. 391 | * 392 | * @param index the item index 393 | * @param animated the animation flag 394 | */ 395 | public void setCurrentItem(int index, boolean animated) { 396 | if (viewAdapter == null || viewAdapter.getItemsCount() == 0) { 397 | return; // throw? 398 | } 399 | 400 | int itemCount = viewAdapter.getItemsCount(); 401 | if (index < 0 || index >= itemCount) { 402 | if (isCyclic) { 403 | while (index < 0) { 404 | index += itemCount; 405 | } 406 | index %= itemCount; 407 | } else { 408 | return; // throw? 409 | } 410 | } 411 | 412 | 413 | if (index != currentItem) { 414 | if (animated) { 415 | int itemsToScroll = index - currentItem; 416 | if (isCyclic) { 417 | int scroll = itemCount + Math.min(index, currentItem) - Math.max(index, currentItem); 418 | if (scroll < Math.abs(itemsToScroll)) { 419 | itemsToScroll = itemsToScroll < 0 ? scroll : -scroll; 420 | } 421 | } 422 | scroll(itemsToScroll, 0); 423 | } else { 424 | scrollingOffset = 0; 425 | 426 | int old = currentItem; 427 | currentItem = index; 428 | 429 | notifyChangingListeners(old, currentItem); 430 | 431 | invalidate(); 432 | } 433 | 434 | 435 | } 436 | } 437 | 438 | /** 439 | * Tests if wheel is cyclic. That means before the 1st item there is shown the last one 440 | * 441 | * @return true if wheel is cyclic 442 | */ 443 | public boolean isCyclic() { 444 | return isCyclic; 445 | } 446 | 447 | /** 448 | * Set wheel cyclic flag 449 | * 450 | * @param isCyclic the flag to set 451 | */ 452 | public void setCyclic(boolean isCyclic) { 453 | this.isCyclic = isCyclic; 454 | invalidateWheel(false); 455 | } 456 | 457 | /** 458 | * Invalidates wheel 459 | * 460 | * @param clearCaches if true then cached views will be clear 461 | */ 462 | public void invalidateWheel(boolean clearCaches) { 463 | if (clearCaches) { 464 | recycle.clearAll(); 465 | if (itemsLayout != null) { 466 | itemsLayout.removeAllViews(); 467 | } 468 | scrollingOffset = 0; 469 | } else if (itemsLayout != null) { 470 | // cache all items 471 | recycle.recycleItems(itemsLayout, firstItem, new ItemsRange(), currentItem); 472 | } 473 | 474 | invalidate(); 475 | } 476 | 477 | /** 478 | * Initializes resources 479 | */ 480 | private void initResourcesIfNecessary() { 481 | setBackgroundResource(android.R.color.transparent); 482 | } 483 | 484 | /** 485 | * Calculates desired height for layout 486 | * 487 | * @param layout the source layout 488 | * @return the desired layout height 489 | */ 490 | private int getDesiredHeight(LinearLayout layout) { 491 | if (layout != null && layout.getChildAt(0) != null) { 492 | mItemHeight = layout.getChildAt(0).getMeasuredHeight(); 493 | } 494 | 495 | int desired = mItemHeight * visibleItems - mItemHeight * ITEM_OFFSET_PERCENT / 50; 496 | 497 | return Math.max(desired, getSuggestedMinimumHeight()); 498 | } 499 | 500 | /** 501 | * Returns height of wheel item 502 | * 503 | * @return the item height 504 | */ 505 | private int getItemHeight() { 506 | if (mItemHeight != 0) { 507 | return mItemHeight; 508 | } 509 | 510 | if (itemsLayout != null && itemsLayout.getChildAt(0) != null) { 511 | mItemHeight = itemsLayout.getChildAt(0).getHeight(); 512 | return mItemHeight; 513 | } 514 | 515 | return getHeight() / visibleItems; 516 | } 517 | 518 | /** 519 | * Calculates control width and creates text layouts 520 | * 521 | * @param widthSize the input layout width 522 | * @param mode the layout mode 523 | * @return the calculated control width 524 | */ 525 | private int calculateLayoutWidth(int widthSize, int mode) { 526 | initResourcesIfNecessary(); 527 | 528 | // TODO: make it static 529 | itemsLayout.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); 530 | itemsLayout.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.UNSPECIFIED), 531 | MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); 532 | int width = itemsLayout.getMeasuredWidth(); 533 | 534 | if (mode == MeasureSpec.EXACTLY) { 535 | width = widthSize; 536 | } else { 537 | width += 2 * PADDING; 538 | 539 | // Check against our minimum width 540 | width = Math.max(width, getSuggestedMinimumWidth()); 541 | 542 | if (mode == MeasureSpec.AT_MOST && widthSize < width) { 543 | width = widthSize; 544 | } 545 | } 546 | 547 | itemsLayout.measure(MeasureSpec.makeMeasureSpec(width - 2 * PADDING, MeasureSpec.EXACTLY), 548 | MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); 549 | 550 | return width; 551 | } 552 | 553 | @Override 554 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 555 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 556 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 557 | int widthSize = MeasureSpec.getSize(widthMeasureSpec); 558 | int heightSize = MeasureSpec.getSize(heightMeasureSpec); 559 | 560 | int dp = Utils.px2dp(getContext(), heightSize); 561 | 562 | buildViewForMeasuring(); 563 | 564 | int width = calculateLayoutWidth(widthSize, widthMode); 565 | 566 | int height; 567 | if (heightMode == MeasureSpec.EXACTLY) { 568 | height = heightSize; 569 | } else { 570 | height = getDesiredHeight(itemsLayout); 571 | 572 | if (heightMode == MeasureSpec.AT_MOST) { 573 | height = Math.min(height, heightSize); 574 | } 575 | } 576 | 577 | setMeasuredDimension(width, height); 578 | } 579 | 580 | @Override 581 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 582 | layout(r - l, b - t); 583 | } 584 | 585 | /** 586 | * Sets layouts width and height 587 | * 588 | * @param width the layout width 589 | * @param height the layout height 590 | */ 591 | private void layout(int width, int height) { 592 | int itemsWidth = width - 2 * PADDING; 593 | 594 | itemsLayout.layout(0, 0, itemsWidth, height); 595 | } 596 | 597 | @Override 598 | protected void onDraw(Canvas canvas) { 599 | super.onDraw(canvas); 600 | if (viewAdapter != null && viewAdapter.getItemsCount() > 0) { 601 | updateView(); 602 | drawItems(canvas); 603 | drawCenterRect(canvas); 604 | } 605 | } 606 | 607 | 608 | /** 609 | * Draws items 610 | * 611 | * @param canvas the canvas for drawing 612 | */ 613 | private void drawItems(Canvas canvas) { 614 | canvas.save(); 615 | 616 | int top = (currentItem - firstItem) * getItemHeight() + (getItemHeight() - getHeight()) / 2; 617 | canvas.translate(PADDING, -top + scrollingOffset); 618 | 619 | itemsLayout.draw(canvas); 620 | 621 | canvas.restore(); 622 | } 623 | 624 | /** 625 | * Draws rect for current value 626 | * 627 | * @param canvas the canvas for drawing 628 | */ 629 | private void drawCenterRect(Canvas canvas) { 630 | int center = getHeight() / 2; 631 | int offset = (int) (getItemHeight() / 2 * 1.2); 632 | // centerDrawable.setBounds(0, center - offset, getWidth(), center + offset); 633 | // centerDrawable.draw(canvas); 634 | canvas.drawRect(0, center - offset, getWidth(), center + offset, mPaintRectCenter); 635 | 636 | canvas.drawLine(0, center - offset, getWidth(), center - offset, mPaintLineCenter); 637 | canvas.drawLine(0, center + offset, getWidth(), center + offset, mPaintLineCenter); 638 | 639 | // 去掉右侧画线 640 | // int x = getWidth() - 1; 641 | // canvas.drawLine(x, mLineRightMar, x, getHeight() - mLineRightMar, mPaintLineRight); 642 | } 643 | 644 | 645 | @Override 646 | public boolean onTouchEvent(MotionEvent event) { 647 | if (!isEnabled() || getViewAdapter() == null) { 648 | return true; 649 | } 650 | 651 | switch (event.getAction()) { 652 | case MotionEvent.ACTION_MOVE: 653 | if (getParent() != null) { 654 | getParent().requestDisallowInterceptTouchEvent(true); 655 | } 656 | break; 657 | 658 | case MotionEvent.ACTION_UP: 659 | if (!isScrollingPerformed) { 660 | int distance = (int) event.getY() - getHeight() / 2; 661 | if (distance > 0) { 662 | distance += getItemHeight() / 2; 663 | } else { 664 | distance -= getItemHeight() / 2; 665 | } 666 | int items = distance / getItemHeight(); 667 | if (items != 0 && isValidItemIndex(currentItem + items)) { 668 | notifyClickListenersAboutClick(currentItem + items); 669 | } 670 | } 671 | break; 672 | } 673 | 674 | return scroller.onTouchEvent(event); 675 | } 676 | 677 | /** 678 | * Scrolls the wheel 679 | * 680 | * @param delta the scrolling value 681 | */ 682 | private void doScroll(int delta) { 683 | scrollingOffset += delta; 684 | 685 | int itemHeight = getItemHeight(); 686 | int count = scrollingOffset / itemHeight; 687 | 688 | int pos = currentItem - count; 689 | int itemCount = viewAdapter.getItemsCount(); 690 | 691 | int fixPos = scrollingOffset % itemHeight; 692 | if (Math.abs(fixPos) <= itemHeight / 2) { 693 | fixPos = 0; 694 | } 695 | if (isCyclic && itemCount > 0) { 696 | if (fixPos > 0) { 697 | pos--; 698 | count++; 699 | } else if (fixPos < 0) { 700 | pos++; 701 | count--; 702 | } 703 | // fix position by rotating 704 | while (pos < 0) { 705 | pos += itemCount; 706 | } 707 | pos %= itemCount; 708 | } else { 709 | // 710 | if (pos < 0) { 711 | count = currentItem; 712 | pos = 0; 713 | } else if (pos >= itemCount) { 714 | count = currentItem - itemCount + 1; 715 | pos = itemCount - 1; 716 | } else if (pos > 0 && fixPos > 0) { 717 | pos--; 718 | count++; 719 | } else if (pos < itemCount - 1 && fixPos < 0) { 720 | pos++; 721 | count--; 722 | } 723 | } 724 | 725 | int offset = scrollingOffset; 726 | if (pos != currentItem) { 727 | setCurrentItem(pos, false); 728 | } else { 729 | invalidate(); 730 | } 731 | 732 | // update offset 733 | scrollingOffset = offset - count * itemHeight; 734 | if (scrollingOffset > getHeight()) { 735 | scrollingOffset = scrollingOffset % getHeight() + getHeight(); 736 | } 737 | } 738 | 739 | /** 740 | * Scroll the wheel 741 | * 742 | * @param itemsToScroll items to scroll 743 | * @param time scrolling duration 744 | */ 745 | public void scroll(int itemsToScroll, int time) { 746 | int distance = itemsToScroll * getItemHeight() - scrollingOffset; 747 | scroller.scroll(distance, time); 748 | } 749 | 750 | /** 751 | * Calculates range for wheel items 752 | * 753 | * @return the items range 754 | */ 755 | private ItemsRange getItemsRange() { 756 | if (getItemHeight() == 0) { 757 | return null; 758 | } 759 | 760 | int first = currentItem; 761 | int count = 1; 762 | 763 | while (count * getItemHeight() < getHeight()) { 764 | first--; 765 | count += 2; // top + bottom items 766 | } 767 | 768 | if (scrollingOffset != 0) { 769 | if (scrollingOffset > 0) { 770 | first--; 771 | } 772 | count++; 773 | 774 | // process empty items above the first or below the second 775 | int emptyItems = scrollingOffset / getItemHeight(); 776 | first -= emptyItems; 777 | count += Math.asin(emptyItems); 778 | } 779 | return new ItemsRange(first, count); 780 | } 781 | 782 | /** 783 | * Rebuilds wheel items if necessary. Caches all unused items. 784 | * 785 | * @return true if items are rebuilt 786 | */ 787 | private boolean rebuildItems() { 788 | boolean updated = false; 789 | ItemsRange range = getItemsRange(); 790 | if (itemsLayout != null) { 791 | int first = recycle.recycleItems(itemsLayout, firstItem, range, currentItem); 792 | updated = firstItem != first; 793 | firstItem = first; 794 | } else { 795 | createItemsLayout(); 796 | updated = true; 797 | } 798 | 799 | if (!updated) { 800 | updated = firstItem != range.getFirst() || itemsLayout.getChildCount() != range.getCount(); 801 | } 802 | 803 | if (firstItem > range.getFirst() && firstItem <= range.getLast()) { 804 | for (int i = firstItem - 1; i >= range.getFirst(); i--) { 805 | if (!addViewItem(i, true)) { 806 | break; 807 | } 808 | firstItem = i; 809 | } 810 | } else { 811 | firstItem = range.getFirst(); 812 | } 813 | 814 | int first = firstItem; 815 | for (int i = itemsLayout.getChildCount(); i < range.getCount(); i++) { 816 | if (!addViewItem(firstItem + i, false) && itemsLayout.getChildCount() == 0) { 817 | first++; 818 | } 819 | } 820 | firstItem = first; 821 | 822 | return updated; 823 | } 824 | 825 | /** 826 | * Updates view. Rebuilds items and label if necessary, recalculate items sizes. 827 | */ 828 | private void updateView() { 829 | if (rebuildItems()) { 830 | calculateLayoutWidth(getWidth(), MeasureSpec.EXACTLY); 831 | layout(getWidth(), getHeight()); 832 | } 833 | } 834 | 835 | /** 836 | * Creates item layouts if necessary 837 | */ 838 | private void createItemsLayout() { 839 | if (itemsLayout == null) { 840 | itemsLayout = new LinearLayout(getContext()); 841 | itemsLayout.setOrientation(LinearLayout.VERTICAL); 842 | } 843 | } 844 | 845 | /** 846 | * Builds view for measuring 847 | */ 848 | private void buildViewForMeasuring() { 849 | // clear all items 850 | if (itemsLayout != null) { 851 | recycle.recycleItems(itemsLayout, firstItem, new ItemsRange(), currentItem); 852 | } else { 853 | createItemsLayout(); 854 | } 855 | 856 | // add views 857 | int addItems = visibleItems / 2; 858 | for (int i = currentItem + addItems; i >= currentItem - addItems; i--) { 859 | if (addViewItem(i, true)) { 860 | firstItem = i; 861 | } 862 | } 863 | } 864 | 865 | /** 866 | * Adds view for item to items layout 867 | * 868 | * @param index the item index 869 | * @param first the flag indicates if view should be first 870 | * @return true if corresponding item exists and is added 871 | */ 872 | private boolean addViewItem(int index, boolean first) { 873 | View view = getItemView(index); 874 | refreshTextStatus(view, index); 875 | if (view != null) { 876 | if (first) { 877 | itemsLayout.addView(view, 0); 878 | } else { 879 | itemsLayout.addView(view); 880 | } 881 | 882 | return true; 883 | } 884 | 885 | return false; 886 | } 887 | 888 | void refreshTextStatus(View view, int index) { 889 | if (!(view instanceof TextView)) 890 | return; 891 | TextView textView = (TextView) view; 892 | if (index == currentItem) { 893 | textView.setTextColor(getResources().getColor(selectorColor)); 894 | } else { 895 | textView.setTextColor(getResources().getColor(defaultColor)); 896 | } 897 | } 898 | 899 | /** 900 | * Checks whether intem index is valid 901 | * 902 | * @param index the item index 903 | * @return true if item index is not out of bounds or the wheel is cyclic 904 | */ 905 | private boolean isValidItemIndex(int index) { 906 | return viewAdapter != null && viewAdapter.getItemsCount() > 0 && 907 | (isCyclic || index >= 0 && index < viewAdapter.getItemsCount()); 908 | } 909 | 910 | /** 911 | * Returns view for specified item 912 | * 913 | * @param index the item index 914 | * @return item view or empty view if index is out of bounds 915 | */ 916 | private View getItemView(int index) { 917 | if (viewAdapter == null || viewAdapter.getItemsCount() == 0) { 918 | return null; 919 | } 920 | int count = viewAdapter.getItemsCount(); 921 | if (!isValidItemIndex(index)) { 922 | return viewAdapter.getEmptyItem(recycle.getEmptyItem(), itemsLayout); 923 | } else { 924 | while (index < 0) { 925 | index = count + index; 926 | } 927 | } 928 | 929 | index %= count; 930 | 931 | View view = viewAdapter.getItem(index, recycle.getItem(), itemsLayout); 932 | 933 | 934 | return view; 935 | } 936 | 937 | /** 938 | * Stops scrolling 939 | */ 940 | public void stopScrolling() { 941 | scroller.stopScrolling(); 942 | } 943 | } 944 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_in_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_out_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/date_wheel_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 11 | 12 | 13 | 14 | 17 | 18 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/timepicker_divider_line.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/timepicker_sel_text_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 24 | 25 | 33 | 34 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/time_scroller_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 21 | 22 | 31 | 32 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/layout/timepicker_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 11 | 12 | 16 | 17 | 20 | 21 | 28 | 29 | 35 | 36 | 40 | 41 | 46 | 47 | 51 | 52 | 56 | 57 | 63 | 64 | 69 | 70 | 75 | 76 | 80 | 81 | 85 | 86 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /app/src/main/res/layout/timepicker_line.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/tv_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangssh/DateRangePicker/cf5039c7c149506770b9b8539d764886a4ffbba8/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangssh/DateRangePicker/cf5039c7c149506770b9b8539d764886a4ffbba8/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangssh/DateRangePicker/cf5039c7c149506770b9b8539d764886a4ffbba8/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangssh/DateRangePicker/cf5039c7c149506770b9b8539d764886a4ffbba8/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangssh/DateRangePicker/cf5039c7c149506770b9b8539d764886a4ffbba8/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | #999999 8 | #FBFBFB 9 | #323232 10 | #39D167 11 | #E7E7E7 12 | 13 | #C2C2C2 14 | #FFFFFF 15 | 16 | #80000000 17 | #e8e8e8 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | 12sp 7 | 48dp 8 | 180dp 9 | 10 | 5dp 11 | 1dp 12 | 15dp 13 | 230dp 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DateScroller 3 | 4 | 确定 5 | 取消 6 | 请选择日期 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 选择日期 15 | 请选择日期 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 20 | 21 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/test/java/org/wangchenlong/datescroller/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package org.wangchenlong.datescroller; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.2' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangssh/DateRangePicker/cf5039c7c149506770b9b8539d764886a4ffbba8/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 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 Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /效果图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangssh/DateRangePicker/cf5039c7c149506770b9b8539d764886a4ffbba8/效果图.png --------------------------------------------------------------------------------