├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── moore │ │ └── picktimeview │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── moore │ │ │ └── picktimeview │ │ │ ├── MainActivity.java │ │ │ └── widget │ │ │ ├── MyNumberPicker.java │ │ │ ├── PickTimeView.java │ │ │ └── PickValueView.java │ └── res │ │ ├── layout │ │ └── activity_main.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 │ └── com │ └── example │ └── moore │ └── picktimeview │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.7 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ###滚轮选择时间、数值、字符串 2 | 效果如下: 3 | ![enter image description here](http://img.blog.csdn.net/20161101113231176) 4 | 5 | PickTimeView:选择时间和选择日期,通过设置type来切换 6 | PickValueView:选择数值和字符串选项,传入不同类型的数据即可。 -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "24.0.3" 6 | defaultConfig { 7 | applicationId "com.example.moore.picktimeview" 8 | minSdkVersion 15 9 | targetSdkVersion 24 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:24.2.1' 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 D:\sdk\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/com/example/moore/picktimeview/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.example.moore.picktimeview; 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("com.example.moore.picktimeview", 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/com/example/moore/picktimeview/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.moore.picktimeview; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.view.View; 6 | import android.widget.LinearLayout; 7 | import android.widget.TextView; 8 | 9 | import com.example.moore.picktimeview.widget.PickTimeView; 10 | import com.example.moore.picktimeview.widget.PickValueView; 11 | 12 | import java.text.SimpleDateFormat; 13 | 14 | public class MainActivity extends AppCompatActivity implements PickTimeView.onSelectedChangeListener, PickValueView.onSelectedChangeListener { 15 | LinearLayout pvLayout; 16 | PickTimeView pickTime; 17 | PickTimeView pickDate; 18 | PickValueView pickValue; 19 | PickValueView pickValues; 20 | PickValueView pickString; 21 | TextView tvSelected; 22 | SimpleDateFormat sdfTime; 23 | SimpleDateFormat sdfDate; 24 | 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_main); 30 | 31 | pvLayout = (LinearLayout) findViewById(R.id.Main_pvLayout); 32 | pickTime = (PickTimeView) findViewById(R.id.pickTime); 33 | pickDate = (PickTimeView) findViewById(R.id.pickDate); 34 | pickValue = (PickValueView) findViewById(R.id.pickValue); 35 | pickValues = (PickValueView) findViewById(R.id.pickValues); 36 | pickString = (PickValueView) findViewById(R.id.pickString); 37 | 38 | tvSelected = (TextView) findViewById(R.id.Main_tvSelected); 39 | 40 | pickTime.setViewType(PickTimeView.TYPE_PICK_TIME); 41 | pickDate.setViewType(PickTimeView.TYPE_PICK_DATE); 42 | 43 | sdfTime = new SimpleDateFormat("MM-dd EEE HH:mm"); 44 | sdfDate = new SimpleDateFormat("yyyy-MM-dd"); 45 | 46 | pickTime.setOnSelectedChangeListener(this); 47 | pickDate.setOnSelectedChangeListener(this); 48 | pickValue.setOnSelectedChangeListener(this); 49 | pickValues.setOnSelectedChangeListener(this); 50 | pickString.setOnSelectedChangeListener(this); 51 | 52 | initData(); 53 | 54 | } 55 | 56 | private void initData() { 57 | pickTime.setViewType(PickTimeView.TYPE_PICK_TIME); 58 | pickDate.setViewType(PickTimeView.TYPE_PICK_DATE); 59 | 60 | Integer value[] = new Integer[20]; 61 | for (int i = 0; i < value.length; i++) { 62 | value[i] = i + 1; 63 | } 64 | Integer middle[] = new Integer[15]; 65 | for (int i = 0; i < middle.length; i++) { 66 | middle[i] = i + 1; 67 | } 68 | Integer right[] = new Integer[10]; 69 | for (int i = 0; i < right.length; i++) { 70 | right[i] = i; 71 | } 72 | String[] valueStr = new String[]{"跑步", "散步", "打篮球", "游泳", "广场舞", "太极拳"}; 73 | 74 | pickValue.setValueData(value, value[4]); 75 | pickValues.setValueData(value, value[0], middle, middle[0], right, right[0]); 76 | pickString.setValueData(valueStr, valueStr[1]); 77 | } 78 | 79 | private void showView(View view) { 80 | for (int i = 0; i < pvLayout.getChildCount(); i++) { 81 | pvLayout.getChildAt(i).setVisibility(View.GONE); 82 | } 83 | view.setVisibility(View.VISIBLE); 84 | } 85 | 86 | @Override 87 | public void onSelected(PickTimeView view, long timeMillis) { 88 | if (view == pickTime) { 89 | String str = sdfTime.format(timeMillis); 90 | tvSelected.setText(str); 91 | } else if (view == pickDate) { 92 | String str = sdfDate.format(timeMillis); 93 | tvSelected.setText(str); 94 | } 95 | } 96 | 97 | @Override 98 | public void onSelected(PickValueView view, Object leftValue, Object middleValue, Object rightValue) { 99 | if (view == pickValue) { 100 | int left = (int) leftValue; 101 | tvSelected.setText("selected:" + left); 102 | } else if (view == pickValues) { 103 | int left = (int) leftValue; 104 | int middle = (int) middleValue; 105 | int right = (int) rightValue; 106 | tvSelected.setText("selected: left:" + left + " middle:" + middle + " right:" + right); 107 | } else { 108 | String selectedStr = (String) leftValue; 109 | tvSelected.setText(selectedStr); 110 | } 111 | } 112 | 113 | public void btnClick(View view) { 114 | switch (view.getId()) { 115 | case R.id.Main_btnTime: 116 | showView(pickTime); 117 | break; 118 | case R.id.Main_btnDat: 119 | showView(pickDate); 120 | break; 121 | case R.id.Main_btnValue: 122 | showView(pickValue); 123 | break; 124 | case R.id.Main_btnValues: 125 | showView(pickValues); 126 | break; 127 | case R.id.Main_btnType: 128 | showView(pickString); 129 | break; 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/moore/picktimeview/widget/MyNumberPicker.java: -------------------------------------------------------------------------------- 1 | package com.example.moore.picktimeview.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Rect; 8 | import android.graphics.drawable.ColorDrawable; 9 | import android.graphics.drawable.Drawable; 10 | import android.util.AttributeSet; 11 | import android.util.SparseArray; 12 | import android.util.TypedValue; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.widget.EditText; 16 | import android.widget.NumberPicker; 17 | 18 | import java.lang.reflect.Field; 19 | 20 | /** 21 | * Created by Moore on 2016/10/20. 22 | */ 23 | 24 | public class MyNumberPicker extends NumberPicker { 25 | private Context mContext; 26 | NumberPicker picker; 27 | 28 | public MyNumberPicker(Context context) { 29 | super(context); 30 | picker = this; 31 | mContext = context; 32 | } 33 | 34 | public MyNumberPicker(Context context, AttributeSet attrs) { 35 | super(context, attrs); 36 | picker = this; 37 | mContext = context; 38 | } 39 | 40 | public MyNumberPicker(Context context, AttributeSet attrs, int defStyleAttr) { 41 | super(context, attrs, defStyleAttr); 42 | picker = this; 43 | mContext = context; 44 | } 45 | 46 | @Override 47 | public void addView(View child) { 48 | super.addView(child); 49 | updateView(child); 50 | } 51 | 52 | @Override 53 | public void addView(View child, int index, ViewGroup.LayoutParams params) { 54 | super.addView(child, index, params); 55 | updateView(child); 56 | } 57 | 58 | @Override 59 | public void addView(View child, ViewGroup.LayoutParams params) { 60 | super.addView(child, params); 61 | updateView(child); 62 | } 63 | 64 | private void updateView(View view) { 65 | if (view instanceof EditText) { 66 | ((EditText) view).setTextSize(TypedValue.COMPLEX_UNIT_SP,20); 67 | ((EditText) view).setTextColor(Color.parseColor("#6495ED")); 68 | } 69 | } 70 | 71 | private int mRight, mLeft, mBottom; 72 | private int mTopSelectionDividerTop; 73 | private int mBottomSelectionDividerBottom; 74 | private int[] mSelectorIndices; 75 | private int mScrollState; 76 | private SparseArray mSelectorIndexToStringCache; 77 | private EditText mInputText; 78 | private Paint mSelectorWheelPaint; 79 | private int mSelectorElementHeight; 80 | private int mCurrentScrollOffset; 81 | private boolean mHasSelectorWheel; 82 | private boolean mHideWheelUntilFocused; 83 | private Drawable mSelectionDivider; 84 | 85 | /** 86 | * 通过反射获取值 87 | */ 88 | private void getMyValue() { 89 | mLeft = super.getLeft(); 90 | mRight = super.getRight(); 91 | mBottom = super.getBottom(); 92 | Field[] pickerFields = NumberPicker.class.getDeclaredFields(); 93 | for (Field field : pickerFields) { 94 | field.setAccessible(true); 95 | if (field.getName().equals("mSelectorWheelPaint")) { 96 | try { 97 | mSelectorWheelPaint = (Paint) field.get(picker); 98 | } catch (IllegalAccessException e) { 99 | e.printStackTrace(); 100 | } 101 | break; 102 | } 103 | } 104 | for (Field field : pickerFields) { 105 | field.setAccessible(true); 106 | if (field.getName().equals("mSelectorElementHeight")) { 107 | try { 108 | mSelectorElementHeight = (int) field.get(picker); 109 | } catch (IllegalAccessException e) { 110 | e.printStackTrace(); 111 | } 112 | break; 113 | } 114 | } 115 | for (Field field : pickerFields) { 116 | field.setAccessible(true); 117 | if (field.getName().equals("mCurrentScrollOffset")) { 118 | try { 119 | mCurrentScrollOffset = (int) field.get(picker); 120 | } catch (IllegalAccessException e) { 121 | e.printStackTrace(); 122 | } 123 | break; 124 | } 125 | } 126 | for (Field field : pickerFields) { 127 | field.setAccessible(true); 128 | if (field.getName().equals("mInputText")) { 129 | try { 130 | mInputText = (EditText) field.get(picker); 131 | } catch (IllegalAccessException e) { 132 | e.printStackTrace(); 133 | } 134 | break; 135 | } 136 | } 137 | for (Field field : pickerFields) { 138 | field.setAccessible(true); 139 | if (field.getName().equals("mSelectorIndexToStringCache")) { 140 | try { 141 | mSelectorIndexToStringCache = (SparseArray) field.get(picker); 142 | } catch (IllegalAccessException e) { 143 | e.printStackTrace(); 144 | } 145 | break; 146 | } 147 | } 148 | for (Field field : pickerFields) { 149 | field.setAccessible(true); 150 | if (field.getName().equals("mSelectorIndices")) { 151 | try { 152 | mSelectorIndices = (int[]) field.get(picker); 153 | } catch (IllegalAccessException e) { 154 | e.printStackTrace(); 155 | } 156 | break; 157 | } 158 | } 159 | for (Field field : pickerFields) { 160 | field.setAccessible(true); 161 | if (field.getName().equals("mHasSelectorWheel")) { 162 | try { 163 | mHasSelectorWheel = (boolean) field.get(picker); 164 | } catch (IllegalAccessException e) { 165 | e.printStackTrace(); 166 | } 167 | break; 168 | } 169 | } 170 | for (Field field : pickerFields) { 171 | field.setAccessible(true); 172 | if (field.getName().equals("mHideWheelUntilFocused")) { 173 | try { 174 | mHideWheelUntilFocused = (boolean) field.get(picker); 175 | } catch (IllegalAccessException e) { 176 | e.printStackTrace(); 177 | } 178 | break; 179 | } 180 | } 181 | // for (Field field : pickerFields) { 182 | // field.setAccessible(true); 183 | // if (field.getName().equals("mDecrementVirtualButtonPressed")) { 184 | // try { 185 | // mDecrementVirtualButtonPressed = (boolean) field.get(picker); 186 | // } catch (IllegalAccessException e) { 187 | // e.printStackTrace(); 188 | // } 189 | // break; 190 | // } 191 | // } 192 | // for (Field field : pickerFields) { 193 | // field.setAccessible(true); 194 | // if (field.getName().equals("mIncrementVirtualButtonPressed")) { 195 | // try { 196 | // mIncrementVirtualButtonPressed = (boolean) field.get(picker); 197 | // } catch (IllegalAccessException e) { 198 | // e.printStackTrace(); 199 | // } 200 | // break; 201 | // } 202 | // } 203 | // for (Field field : pickerFields) { 204 | // field.setAccessible(true); 205 | // if (field.getName().equals("mVirtualButtonPressedDrawable")) { 206 | // try { 207 | // mVirtualButtonPressedDrawable = (Drawable) field.get(picker); 208 | // } catch (IllegalAccessException e) { 209 | // e.printStackTrace(); 210 | // } 211 | // break; 212 | // } 213 | // } 214 | for (Field field : pickerFields) { 215 | field.setAccessible(true); 216 | if (field.getName().equals("mScrollState")) { 217 | try { 218 | mScrollState = (int) field.get(picker); 219 | } catch (IllegalAccessException e) { 220 | e.printStackTrace(); 221 | } 222 | break; 223 | } 224 | } 225 | for (Field field : pickerFields) { 226 | field.setAccessible(true); 227 | if (field.getName().equals("mTopSelectionDividerTop")) { 228 | try { 229 | mTopSelectionDividerTop = (int) field.get(picker); 230 | } catch (IllegalAccessException e) { 231 | e.printStackTrace(); 232 | } 233 | break; 234 | } 235 | } 236 | for (Field field : pickerFields) { 237 | field.setAccessible(true); 238 | if (field.getName().equals("mBottomSelectionDividerBottom")) { 239 | try { 240 | mBottomSelectionDividerBottom = (int) field.get(picker); 241 | } catch (IllegalAccessException e) { 242 | e.printStackTrace(); 243 | } 244 | break; 245 | } 246 | } 247 | for (Field field : pickerFields) { 248 | field.setAccessible(true); 249 | if (field.getName().equals("mSelectionDivider")) { 250 | try { 251 | mSelectionDivider = (Drawable) field.get(picker); 252 | } catch (IllegalAccessException e) { 253 | e.printStackTrace(); 254 | } 255 | break; 256 | } 257 | } 258 | 259 | } 260 | 261 | @Override 262 | protected void onDraw(Canvas canvas) { 263 | // super.onDraw(canvas); 264 | getMyValue(); 265 | mSelectorWheelPaint.setColor(Color.BLUE); 266 | 267 | if (!mHasSelectorWheel) { 268 | super.onDraw(canvas); 269 | return; 270 | } 271 | final boolean showSelectorWheel = mHideWheelUntilFocused ? hasFocus() : true; 272 | float x = (mRight - mLeft) / 2; 273 | float y = mCurrentScrollOffset; 274 | 275 | // if (showSelectorWheel && mVirtualButtonPressedDrawable != null 276 | // && mScrollState == OnScrollListener.SCROLL_STATE_IDLE) { 277 | // if (mDecrementVirtualButtonPressed) { 278 | // mVirtualButtonPressedDrawable.setState(View.PRESSED_STATE_SET); 279 | // mVirtualButtonPressedDrawable.setBounds(0, 0, mRight, mTopSelectionDividerTop); 280 | // mVirtualButtonPressedDrawable.draw(canvas); 281 | // } 282 | // if (mIncrementVirtualButtonPressed) { 283 | // mVirtualButtonPressedDrawable.setState(PRESSED_STATE_SET); 284 | // mVirtualButtonPressedDrawable.setBounds(0, mBottomSelectionDividerBottom, mRight, 285 | // mBottom); 286 | // mVirtualButtonPressedDrawable.draw(canvas); 287 | // } 288 | // } 289 | 290 | int[] selectorIndices = mSelectorIndices; 291 | for (int i = 0; i < selectorIndices.length; i++) { 292 | int selectorIndex = selectorIndices[i]; 293 | String scrollSelectorValue = mSelectorIndexToStringCache.get(selectorIndex); 294 | if (i != 1) { 295 | mSelectorWheelPaint.setColor(Color.BLACK); 296 | mSelectorWheelPaint.setTextSize(sp2px(16)); 297 | } else { 298 | mSelectorWheelPaint.setColor(Color.parseColor("#6495ED")); 299 | mSelectorWheelPaint.setTextSize(sp2px(20)); 300 | } 301 | 302 | if ((showSelectorWheel && i != 1) || 303 | (i == 1 && mInputText.getVisibility() != VISIBLE)) { 304 | Rect mRect = new Rect(); 305 | mSelectorWheelPaint.getTextBounds(scrollSelectorValue, 0, scrollSelectorValue.length(), mRect); 306 | canvas.drawText(scrollSelectorValue, x, y, mSelectorWheelPaint); 307 | } 308 | y += mSelectorElementHeight; 309 | } 310 | 311 | // draw the selection dividers 312 | if (showSelectorWheel && mSelectionDivider != null) { 313 | mSelectionDivider = new ColorDrawable(Color.parseColor("#a0c4c4c4")); 314 | // draw the top divider 315 | int topOfTopDivider = mTopSelectionDividerTop; 316 | int bottomOfTopDivider = topOfTopDivider + 2; 317 | mSelectionDivider.setBounds(0, topOfTopDivider, mRight, bottomOfTopDivider); 318 | mSelectionDivider.draw(canvas); 319 | 320 | // draw the bottom divider 321 | int bottomOfBottomDivider = mBottomSelectionDividerBottom; 322 | int topOfBottomDivider = bottomOfBottomDivider - 2; 323 | mSelectionDivider.setBounds(0, topOfBottomDivider, mRight, bottomOfBottomDivider); 324 | mSelectionDivider.draw(canvas); 325 | } 326 | } 327 | 328 | private int sp2px(int sp){ 329 | float scale = mContext.getResources().getDisplayMetrics().scaledDensity; 330 | return (int) (scale*sp+0.5f); 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/moore/picktimeview/widget/PickTimeView.java: -------------------------------------------------------------------------------- 1 | package com.example.moore.picktimeview.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.util.AttributeSet; 6 | import android.util.Log; 7 | import android.view.Gravity; 8 | import android.view.ViewGroup; 9 | import android.widget.LinearLayout; 10 | import android.widget.NumberPicker; 11 | import android.widget.TextView; 12 | 13 | import java.text.SimpleDateFormat; 14 | import java.util.Calendar; 15 | 16 | /** 17 | * Created by Moore on 2016/10/19. 18 | */ 19 | 20 | public class PickTimeView extends LinearLayout implements NumberPicker.OnValueChangeListener { 21 | private final String TAG = getClass().getSimpleName(); 22 | private Context mContext; 23 | /** 24 | * 视图控件 25 | */ 26 | // private NumberPicker mNpLeft, mNpMiddle, mNpRight; 27 | private MyNumberPicker mNpLeft, mNpMiddle, mNpRight; 28 | private TextView mMinuteTip; 29 | /** 30 | * 选择框之间距离(dp) 31 | */ 32 | private int mOffsetMargin = 12; 33 | 34 | /** 35 | * 一组数据长度 36 | */ 37 | private final int DATA_SIZE = 5; 38 | 39 | /** 40 | * type:选择日期控件 41 | */ 42 | public static final int TYPE_PICK_DATE = 1; 43 | /** 44 | * type:选择时间控件 45 | */ 46 | public static final int TYPE_PICK_TIME = 2; 47 | /** 48 | * 当前type值 49 | */ 50 | private int mCurrentType = TYPE_PICK_TIME; 51 | /** 52 | * 当前时间戳 53 | */ 54 | private long mTimeMillis; 55 | /** 56 | * 选中回调监听 57 | */ 58 | private onSelectedChangeListener mOnSelectedChangeListener; 59 | 60 | private SimpleDateFormat test; 61 | 62 | public PickTimeView(Context context) { 63 | super(context); 64 | this.mContext = context; 65 | generateView(); 66 | initPicker(); 67 | } 68 | 69 | public PickTimeView(Context context, AttributeSet attrs) { 70 | super(context, attrs); 71 | this.mContext = context; 72 | generateView(); 73 | initPicker(); 74 | } 75 | 76 | public PickTimeView(Context context, AttributeSet attrs, int defStyleAttr) { 77 | super(context, attrs, defStyleAttr); 78 | this.mContext = context; 79 | generateView(); 80 | initPicker(); 81 | } 82 | 83 | /** 84 | * 绘制视图 85 | */ 86 | private void generateView() { 87 | this.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 88 | this.setOrientation(VERTICAL); 89 | this.setGravity(Gravity.CENTER); 90 | LinearLayout container = new LinearLayout(mContext); 91 | container.setOrientation(HORIZONTAL); 92 | container.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 93 | container.setGravity(Gravity.CENTER); 94 | // mNpLeft = new NumberPicker(mContext); 95 | // mNpMiddle = new NumberPicker(mContext); 96 | // mNpRight = new NumberPicker(mContext); 97 | mNpLeft = new MyNumberPicker(mContext); 98 | mNpMiddle = new MyNumberPicker(mContext); 99 | mNpRight = new MyNumberPicker(mContext); 100 | mMinuteTip = new TextView(mContext); 101 | mMinuteTip.setText(":"); 102 | mNpLeft.setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS); 103 | mNpMiddle.setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS); 104 | mNpRight.setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS); 105 | 106 | // mNpLeft.setTextColor(Color.BLUE); 107 | // mNpMiddle.setTextSize(28); 108 | // mNpRight.setDividerColor(Color.GREEN); 109 | /** 110 | * 设置宽高和边距 111 | */ 112 | LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 113 | params.setMargins(0, 0, 0, 0); 114 | mNpLeft.setLayoutParams(params); 115 | params.setMargins(dip2px(mOffsetMargin), 0, 0, 0); 116 | mNpMiddle.setLayoutParams(params); 117 | mNpRight.setLayoutParams(params); 118 | mMinuteTip.setLayoutParams(params); 119 | 120 | mNpLeft.setOnValueChangedListener(this); 121 | mNpMiddle.setOnValueChangedListener(this); 122 | mNpRight.setOnValueChangedListener(this); 123 | 124 | container.addView(mNpLeft); 125 | container.addView(mNpMiddle); 126 | container.addView(mMinuteTip); 127 | container.addView(mNpRight); 128 | this.addView(container); 129 | initTime(); 130 | } 131 | 132 | /** 133 | * 初始化控件 134 | */ 135 | private void initPicker() { 136 | test = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 137 | if (mCurrentType == TYPE_PICK_DATE) { 138 | mMinuteTip.setVisibility(GONE); 139 | } else { 140 | mMinuteTip.setVisibility(VISIBLE); 141 | } 142 | 143 | mNpLeft.setMinValue(0); 144 | mNpLeft.setMaxValue(DATA_SIZE - 1); 145 | updateLeftValue(mTimeMillis); 146 | 147 | mNpMiddle.setMinValue(0); 148 | mNpMiddle.setMaxValue(DATA_SIZE - 1); 149 | updateMiddleValue(mTimeMillis); 150 | 151 | mNpRight.setMinValue(0); 152 | mNpRight.setMaxValue(DATA_SIZE - 1); 153 | updateRightValue(mTimeMillis); 154 | } 155 | 156 | /** 157 | * 更新左侧控件 158 | * 日期:选择年控件 159 | * 时间:选择月份和日期控件 160 | * 161 | * @param timeMillis 162 | */ 163 | private void updateLeftValue(long timeMillis) { 164 | SimpleDateFormat sdf; 165 | String str[] = new String[DATA_SIZE]; 166 | if (mCurrentType == TYPE_PICK_DATE) { 167 | sdf = new SimpleDateFormat("yyyy"); 168 | for (int i = 0; i < DATA_SIZE; i++) { 169 | Calendar cal = Calendar.getInstance(); 170 | cal.setTimeInMillis(timeMillis); 171 | cal.add(Calendar.YEAR, (i - DATA_SIZE / 2)); 172 | str[i] = sdf.format(cal.getTimeInMillis()); 173 | } 174 | } else { 175 | sdf = new SimpleDateFormat("MM-dd EEE"); 176 | for (int i = 0; i < DATA_SIZE; i++) { 177 | Calendar cal = Calendar.getInstance(); 178 | cal.setTimeInMillis(timeMillis); 179 | cal.add(Calendar.DAY_OF_MONTH, (i - DATA_SIZE / 2)); 180 | str[i] = sdf.format(cal.getTimeInMillis()); 181 | } 182 | } 183 | mNpLeft.setDisplayedValues(str); 184 | mNpLeft.setValue(DATA_SIZE / 2); 185 | mNpLeft.postInvalidate(); 186 | } 187 | 188 | /** 189 | * 更新中间控件 190 | * 日期:选择月份控件 191 | * 时间:选择小时控件 192 | * 193 | * @param timeMillis 194 | */ 195 | private void updateMiddleValue(long timeMillis) { 196 | SimpleDateFormat sdf; 197 | String str[] = new String[DATA_SIZE]; 198 | if (mCurrentType == TYPE_PICK_DATE) { 199 | sdf = new SimpleDateFormat("MM"); 200 | for (int i = 0; i < DATA_SIZE; i++) { 201 | Calendar cal = Calendar.getInstance(); 202 | cal.setTimeInMillis(timeMillis); 203 | cal.add(Calendar.MONTH, (i - DATA_SIZE / 2)); 204 | str[i] = sdf.format(cal.getTimeInMillis()); 205 | } 206 | } else { 207 | sdf = new SimpleDateFormat("HH"); 208 | for (int i = 0; i < DATA_SIZE; i++) { 209 | Calendar cal = Calendar.getInstance(); 210 | cal.setTimeInMillis(timeMillis); 211 | cal.add(Calendar.HOUR_OF_DAY, (i - DATA_SIZE / 2)); 212 | str[i] = sdf.format(cal.getTimeInMillis()); 213 | } 214 | } 215 | mNpMiddle.setDisplayedValues(str); 216 | mNpMiddle.setValue(DATA_SIZE / 2); 217 | mNpMiddle.postInvalidate(); 218 | } 219 | 220 | /** 221 | * 更新右侧控件 222 | * 日期:选择日期控件 223 | * 时间:选择分钟控件 224 | * 225 | * @param timeMillis 226 | */ 227 | private void updateRightValue(long timeMillis) { 228 | SimpleDateFormat sdf; 229 | String str[] = new String[DATA_SIZE]; 230 | if (mCurrentType == TYPE_PICK_DATE) { 231 | Calendar calendar = Calendar.getInstance(); 232 | calendar.setTimeInMillis(timeMillis); 233 | int days = getMaxDayOfMonth(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1); 234 | int currentDay = calendar.get(Calendar.DAY_OF_MONTH); 235 | for (int i = 0; i < DATA_SIZE; i++) { 236 | int temp = currentDay - (DATA_SIZE / 2 - i); 237 | if (temp > days) { 238 | temp -= days; 239 | } 240 | if (temp < 1) { 241 | temp += days; 242 | } 243 | str[i] = temp > 9 ? temp + "" : "0" + temp; 244 | } 245 | } else { 246 | sdf = new SimpleDateFormat("mm"); 247 | for (int i = 0; i < DATA_SIZE; i++) { 248 | Calendar cal = Calendar.getInstance(); 249 | cal.setTimeInMillis(timeMillis); 250 | cal.add(Calendar.MINUTE, (i - DATA_SIZE / 2)); 251 | str[i] = sdf.format(cal.getTimeInMillis()); 252 | } 253 | } 254 | mNpRight.setDisplayedValues(str); 255 | mNpRight.setValue(DATA_SIZE / 2); 256 | mNpRight.postInvalidate(); 257 | } 258 | 259 | @Override 260 | public void onValueChange(NumberPicker picker, int oldVal, int newVal) { 261 | Calendar calendar = Calendar.getInstance(); 262 | calendar.setTimeInMillis(mTimeMillis); 263 | int year = calendar.get(Calendar.YEAR); 264 | int month = calendar.get(Calendar.MONTH); 265 | int day = calendar.get(Calendar.DAY_OF_MONTH); 266 | int hour = calendar.get(Calendar.HOUR_OF_DAY); 267 | int offset = newVal - oldVal; 268 | if (picker == mNpLeft) { 269 | if (mCurrentType == TYPE_PICK_DATE) { 270 | calendar.add(Calendar.YEAR, offset); 271 | } else { 272 | calendar.add(Calendar.DAY_OF_MONTH, offset); 273 | } 274 | updateLeftValue(calendar.getTimeInMillis()); 275 | mTimeMillis = calendar.getTimeInMillis(); 276 | } else if (picker == mNpMiddle) { 277 | if (mCurrentType == TYPE_PICK_DATE) { 278 | calendar.add(Calendar.MONTH, offset); 279 | if (calendar.get(Calendar.YEAR) != year) { 280 | calendar.set(Calendar.YEAR, year); 281 | } 282 | } else { 283 | calendar.add(Calendar.HOUR_OF_DAY, offset); 284 | if (calendar.get(Calendar.DAY_OF_MONTH) != day) { 285 | calendar.set(Calendar.DAY_OF_MONTH, day); 286 | } 287 | if (calendar.get(Calendar.MONTH) != month) { 288 | calendar.set(Calendar.MONTH, month); 289 | } 290 | if (calendar.get(Calendar.YEAR) != year) { 291 | calendar.set(Calendar.YEAR, year); 292 | } 293 | } 294 | updateMiddleValue(calendar.getTimeInMillis()); 295 | updateRightValue(calendar.getTimeInMillis()); 296 | mTimeMillis = calendar.getTimeInMillis(); 297 | } else if (picker == mNpRight) { 298 | if (mCurrentType == TYPE_PICK_DATE) { 299 | int days = getMaxDayOfMonth(year, month + 1); 300 | if(day == 1 && offset < 0){ 301 | calendar.set(Calendar.DAY_OF_MONTH,days); 302 | }else if(day == days && offset > 0){ 303 | calendar.set(Calendar.DAY_OF_MONTH,1); 304 | }else{ 305 | calendar.add(Calendar.DAY_OF_MONTH, offset); 306 | } 307 | 308 | if (calendar.get(Calendar.MONTH) != month) { 309 | calendar.set(Calendar.MONTH, month); 310 | } 311 | if (calendar.get(Calendar.YEAR) != year) { 312 | calendar.set(Calendar.YEAR, year); 313 | } 314 | Log.e(TAG,"time:::"+test.format(calendar.getTimeInMillis())); 315 | } else { 316 | calendar.add(Calendar.MINUTE, offset); 317 | if (calendar.get(Calendar.HOUR_OF_DAY) != hour) { 318 | calendar.set(Calendar.HOUR_OF_DAY, hour); 319 | } 320 | if (calendar.get(Calendar.DAY_OF_MONTH) != day) { 321 | calendar.set(Calendar.DAY_OF_MONTH, day); 322 | } 323 | if (calendar.get(Calendar.MONTH) != month) { 324 | calendar.set(Calendar.MONTH, month); 325 | } 326 | if (calendar.get(Calendar.YEAR) != year) { 327 | calendar.set(Calendar.YEAR, year); 328 | } 329 | } 330 | updateRightValue(calendar.getTimeInMillis()); 331 | mTimeMillis = calendar.getTimeInMillis(); 332 | } 333 | /** 334 | * 向外部发送当前选中时间 335 | */ 336 | if (mOnSelectedChangeListener != null) { 337 | mOnSelectedChangeListener.onSelected(this,mTimeMillis); 338 | } 339 | Log.e(TAG, "selected time:" + test.format(mTimeMillis)); 340 | } 341 | 342 | /** 343 | * 未设置时间默认取当前时间 344 | */ 345 | private void initTime() { 346 | Calendar calendar = Calendar.getInstance(); 347 | mTimeMillis = calendar.getTimeInMillis(); 348 | } 349 | 350 | /** 351 | * 设置初始化时间 352 | * 353 | * @param timeMillis 354 | */ 355 | public void setTimeMillis(long timeMillis) { 356 | if (timeMillis != 0) { 357 | this.mTimeMillis = timeMillis; 358 | initPicker(); 359 | this.postInvalidate(); 360 | } else { 361 | initTime(); 362 | } 363 | } 364 | 365 | /** 366 | * 设置视图类型 年月日控件or时间控件 367 | * 368 | * @param type 369 | */ 370 | public void setViewType(int type) { 371 | if (type == TYPE_PICK_DATE || type == TYPE_PICK_TIME) { 372 | this.mCurrentType = type; 373 | } else { 374 | this.mCurrentType = TYPE_PICK_TIME; 375 | } 376 | initPicker(); 377 | } 378 | 379 | /** 380 | * 设置选中监听回调 381 | * 382 | * @param listener 383 | */ 384 | public void setOnSelectedChangeListener(onSelectedChangeListener listener) { 385 | this.mOnSelectedChangeListener = listener; 386 | } 387 | 388 | /** 389 | * dp转px 390 | * 391 | * @param dp 392 | * @return 393 | */ 394 | private int dip2px(int dp) { 395 | float scale = mContext.getResources().getDisplayMetrics().density; 396 | return (int) (scale * dp + 0.5f); 397 | } 398 | 399 | /** 400 | * 获取某年某月的天数 401 | * 402 | * @param year 403 | * @param month 404 | * @return 405 | */ 406 | private int getMaxDayOfMonth(int year, int month) { 407 | int result = 0; 408 | if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) { 409 | result = 31; 410 | } else if (month == 4 || month == 6 || month == 9 || month == 11) { 411 | result = 30; 412 | } else { 413 | if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) { 414 | result = 29; 415 | } else { 416 | result = 28; 417 | } 418 | } 419 | return result; 420 | } 421 | 422 | /** 423 | * 选中回调接口 424 | */ 425 | public interface onSelectedChangeListener { 426 | void onSelected(PickTimeView view,long timeMillis); 427 | } 428 | } 429 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/moore/picktimeview/widget/PickValueView.java: -------------------------------------------------------------------------------- 1 | package com.example.moore.picktimeview.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.util.AttributeSet; 6 | import android.util.Log; 7 | import android.view.Gravity; 8 | import android.view.ViewGroup; 9 | import android.widget.LinearLayout; 10 | import android.widget.NumberPicker; 11 | import android.widget.TextView; 12 | 13 | /** 14 | * Created by Moore on 2016/10/21. 15 | */ 16 | 17 | public class PickValueView extends LinearLayout implements NumberPicker.OnValueChangeListener { 18 | private Context mContext; 19 | /** 20 | * 组件 标题、单位、滚轮 21 | */ 22 | private TextView mTitleLeft, mTitleMiddle, mTitleRight; 23 | private TextView mUnitLeft, mUnitMiddle, mUnitRight; 24 | private MyNumberPicker mNpLeft, mNpMiddle, mNpRight; 25 | /** 26 | * 数据个数 1列 or 2列 or 3列 27 | */ 28 | private int mViewCount = 1; 29 | /** 30 | * 一组数据长度 31 | */ 32 | private final int DATA_SIZE = 3; 33 | 34 | /** 35 | * 需要设置的值与默认值 36 | */ 37 | private Object[] mLeftValues; 38 | private Object[] mMiddleValues; 39 | private Object[] mRightValues; 40 | private Object mDefaultLeftValue; 41 | private Object mDefaultMiddleValue; 42 | private Object mDefaultRightValue; 43 | /** 44 | * 当前正在显示的值 45 | */ 46 | private Object[] mShowingLeft = new Object[DATA_SIZE]; 47 | private Object[] mShowingMiddle = new Object[DATA_SIZE]; 48 | private Object[] mShowingRight = new Object[DATA_SIZE]; 49 | 50 | /** 51 | * 步长 52 | */ 53 | private int mLeftStep = 5; 54 | private int mMiddleStep = 1; 55 | private int mRightStep = 1; 56 | /** 57 | * 回调接口对象 58 | */ 59 | private onSelectedChangeListener mSelectedChangeListener; 60 | 61 | public PickValueView(Context context) { 62 | super(context); 63 | this.mContext = context; 64 | generateView(); 65 | } 66 | 67 | public PickValueView(Context context, AttributeSet attrs) { 68 | super(context, attrs); 69 | this.mContext = context; 70 | generateView(); 71 | } 72 | 73 | public PickValueView(Context context, AttributeSet attrs, int defStyleAttr) { 74 | super(context, attrs, defStyleAttr); 75 | this.mContext = context; 76 | generateView(); 77 | } 78 | 79 | /** 80 | * 生成视图 81 | */ 82 | private void generateView() { 83 | //标题 84 | LinearLayout titleLayout = new LinearLayout(mContext); 85 | LayoutParams titleParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 86 | titleParams.setMargins(0, 0, 0, dip2px(12)); 87 | titleLayout.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 88 | titleLayout.setOrientation(HORIZONTAL); 89 | mTitleLeft = new TextView(mContext); 90 | mTitleMiddle = new TextView(mContext); 91 | mTitleRight = new TextView(mContext); 92 | 93 | LayoutParams params = new LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1); 94 | TextView[] titles = new TextView[]{mTitleLeft, mTitleMiddle, mTitleRight}; 95 | for (int i = 0; i < titles.length; i++) { 96 | titles[i].setLayoutParams(params); 97 | titles[i].setGravity(Gravity.CENTER); 98 | titles[i].setTextColor(Color.parseColor("#3434EE")); 99 | } 100 | titleLayout.addView(mTitleLeft); 101 | titleLayout.addView(mTitleMiddle); 102 | titleLayout.addView(mTitleRight); 103 | //内容 104 | LinearLayout contentLayout = new LinearLayout(mContext); 105 | contentLayout.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 106 | contentLayout.setOrientation(HORIZONTAL); 107 | contentLayout.setGravity(Gravity.CENTER); 108 | mNpLeft = new MyNumberPicker(mContext); 109 | mNpMiddle = new MyNumberPicker(mContext); 110 | mNpRight = new MyNumberPicker(mContext); 111 | mUnitLeft = new TextView(mContext); 112 | mUnitMiddle = new TextView(mContext); 113 | mUnitRight = new TextView(mContext); 114 | 115 | MyNumberPicker[] nps = new MyNumberPicker[]{mNpLeft, mNpMiddle, mNpRight}; 116 | for (int i = 0; i < nps.length; i++) { 117 | nps[i].setLayoutParams(params); 118 | nps[i].setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS); 119 | nps[i].setOnValueChangedListener(this); 120 | } 121 | 122 | contentLayout.addView(mNpLeft); 123 | contentLayout.addView(mUnitLeft); 124 | contentLayout.addView(mNpMiddle); 125 | contentLayout.addView(mUnitMiddle); 126 | contentLayout.addView(mNpRight); 127 | contentLayout.addView(mUnitRight); 128 | 129 | this.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 130 | this.setOrientation(VERTICAL); 131 | this.addView(titleLayout); 132 | this.addView(contentLayout); 133 | } 134 | 135 | /** 136 | * 初始化数据和值 137 | */ 138 | private void initViewAndPicker() { 139 | if (mViewCount == 1) { 140 | this.mNpMiddle.setVisibility(GONE); 141 | this.mNpRight.setVisibility(GONE); 142 | this.mUnitMiddle.setVisibility(GONE); 143 | this.mUnitRight.setVisibility(GONE); 144 | } else if (mViewCount == 2) { 145 | this.mNpRight.setVisibility(GONE); 146 | this.mUnitRight.setVisibility(GONE); 147 | } 148 | 149 | //初始化数组值 150 | if (mLeftValues != null && mLeftValues.length != 0) { 151 | if (mLeftValues.length < DATA_SIZE) { 152 | for (int i = 0; i < mLeftValues.length; i++) { 153 | mShowingLeft[i] = mLeftValues[i]; 154 | } 155 | for (int i = mLeftValues.length; i < DATA_SIZE; i++) { 156 | mShowingLeft[i] = -9999; 157 | } 158 | } else { 159 | for (int i = 0; i < DATA_SIZE; i++) { 160 | mShowingLeft[i] = mLeftValues[i]; 161 | } 162 | } 163 | mNpLeft.setMinValue(0); 164 | mNpLeft.setMaxValue(DATA_SIZE - 1); 165 | if (mDefaultLeftValue != null) 166 | updateLeftView(mDefaultLeftValue); 167 | else 168 | updateLeftView(mShowingLeft[0]); 169 | } 170 | /** 171 | * 中间控件 172 | */ 173 | if (mViewCount == 2 || mViewCount == 3) { 174 | if (mMiddleValues != null && mMiddleValues.length != 0) { 175 | if (mMiddleValues.length < DATA_SIZE) { 176 | for (int i = 0; i < mMiddleValues.length; i++) { 177 | mShowingMiddle[i] = mMiddleValues[i]; 178 | } 179 | for (int i = mMiddleValues.length; i < DATA_SIZE; i++) { 180 | mShowingMiddle[i] = -9999; 181 | } 182 | } else { 183 | for (int i = 0; i < DATA_SIZE; i++) { 184 | mShowingMiddle[i] = mMiddleValues[i]; 185 | } 186 | } 187 | mNpMiddle.setMinValue(0); 188 | mNpMiddle.setMaxValue(DATA_SIZE - 1); 189 | if (mDefaultMiddleValue != null) 190 | updateMiddleView(mDefaultMiddleValue); 191 | else 192 | updateMiddleView(mShowingMiddle[0]); 193 | } 194 | } 195 | 196 | /** 197 | * 右侧控件 198 | */ 199 | if (mViewCount == 3) { 200 | if (mRightValues != null && mRightValues.length != 0) { 201 | if (mRightValues.length < DATA_SIZE) { 202 | for (int i = 0; i < mRightValues.length; i++) { 203 | mShowingRight[i] = mRightValues[i]; 204 | } 205 | for (int i = mRightValues.length; i < DATA_SIZE; i++) { 206 | mShowingRight[i] = -9999; 207 | } 208 | } else { 209 | for (int i = 0; i < DATA_SIZE; i++) { 210 | mShowingRight[i] = mRightValues[i]; 211 | } 212 | } 213 | mNpRight.setMinValue(0); 214 | mNpRight.setMaxValue(DATA_SIZE - 1); 215 | if (mDefaultRightValue != null) 216 | updateRightView(mDefaultRightValue); 217 | else 218 | updateRightView(mShowingRight[0]); 219 | } 220 | } 221 | 222 | 223 | } 224 | 225 | private void updateLeftView(Object value) { 226 | updateValue(value, 0); 227 | } 228 | 229 | private void updateMiddleView(Object value) { 230 | updateValue(value, 1); 231 | } 232 | 233 | private void updateRightView(Object value) { 234 | updateValue(value, 2); 235 | } 236 | 237 | /** 238 | * 更新滚轮视图 239 | * 240 | * @param value 241 | * @param index 242 | */ 243 | private void updateValue(Object value, int index) { 244 | String showStr[] = new String[DATA_SIZE]; 245 | MyNumberPicker picker; 246 | Object[] showingValue; 247 | Object[] values; 248 | int step; 249 | if (index == 0) { 250 | picker = mNpLeft; 251 | showingValue = mShowingLeft; 252 | values = mLeftValues; 253 | step = mLeftStep; 254 | } else if (index == 1) { 255 | picker = mNpMiddle; 256 | showingValue = mShowingMiddle; 257 | values = mMiddleValues; 258 | step = mMiddleStep; 259 | } else { 260 | picker = mNpRight; 261 | showingValue = mShowingRight; 262 | values = mRightValues; 263 | step = mRightStep; 264 | } 265 | 266 | if (values instanceof Integer[]) { 267 | for (int i = 0; i < DATA_SIZE; i++) { 268 | showingValue[i] = (int) value - step * (DATA_SIZE / 2 - i); 269 | int offset = (int) values[values.length - 1] - (int) values[0] + step; 270 | if ((int) showingValue[i] < (int) values[0]) { 271 | showingValue[i] = (int) showingValue[i] + offset; 272 | } 273 | if ((int) showingValue[i] > (int) values[values.length - 1]) { 274 | showingValue[i] = (int) showingValue[i] - offset; 275 | } 276 | showStr[i] = "" + showingValue[i]; 277 | } 278 | } else { 279 | int strIndex = 0; 280 | for (int i = 0; i < values.length; i++) { 281 | if (values[i].equals(value)) { 282 | strIndex = i; 283 | break; 284 | } 285 | } 286 | for (int i = 0; i < DATA_SIZE; i++) { 287 | int temp = strIndex - (DATA_SIZE / 2 - i); 288 | if (temp < 0) { 289 | temp += values.length; 290 | } 291 | if (temp >= values.length) { 292 | temp -= values.length; 293 | } 294 | showingValue[i] = values[temp]; 295 | showStr[i] = (String) values[temp]; 296 | } 297 | } 298 | picker.setDisplayedValues(showStr); 299 | picker.setValue(DATA_SIZE / 2); 300 | picker.postInvalidate(); 301 | } 302 | 303 | 304 | @Override 305 | public void onValueChange(NumberPicker picker, int oldVal, int newVal) { 306 | if (picker == mNpLeft) { 307 | updateLeftView(mShowingLeft[newVal]); 308 | } else if (picker == mNpMiddle) { 309 | updateMiddleView(mShowingMiddle[newVal]); 310 | } else if (picker == mNpRight) { 311 | updateRightView(mShowingRight[newVal]); 312 | } 313 | if (mSelectedChangeListener != null) { 314 | mSelectedChangeListener.onSelected(this, mShowingLeft[DATA_SIZE / 2], mShowingMiddle[DATA_SIZE / 2], mShowingRight[DATA_SIZE / 2]); 315 | } 316 | } 317 | 318 | /** 319 | * 设置数据--单列数据 320 | * 321 | * @param leftValues 322 | * @param mDefaultLeftValue 323 | */ 324 | public void setValueData(Object[] leftValues, Object mDefaultLeftValue) { 325 | this.mViewCount = 1; 326 | this.mLeftValues = leftValues; 327 | this.mDefaultLeftValue = mDefaultLeftValue; 328 | 329 | initViewAndPicker(); 330 | } 331 | 332 | /** 333 | * 设置数据--两列数据 334 | * 335 | * @param leftValues 336 | * @param mDefaultLeftValue 337 | * @param middleValues 338 | * @param defaultMiddleValue 339 | */ 340 | public void setValueData(Object[] leftValues, Object mDefaultLeftValue, Object[] middleValues, Object defaultMiddleValue) { 341 | this.mViewCount = 2; 342 | this.mLeftValues = leftValues; 343 | this.mDefaultLeftValue = mDefaultLeftValue; 344 | 345 | this.mMiddleValues = middleValues; 346 | this.mDefaultMiddleValue = defaultMiddleValue; 347 | 348 | initViewAndPicker(); 349 | } 350 | 351 | /** 352 | * 设置数据--三列数据 353 | * 354 | * @param leftValues 355 | * @param mDefaultLeftValue 356 | * @param middleValues 357 | * @param defaultMiddleValue 358 | * @param rightValues 359 | * @param defaultRightValue 360 | */ 361 | public void setValueData(Object[] leftValues, Object mDefaultLeftValue, Object[] middleValues, Object defaultMiddleValue, Object[] rightValues, Object defaultRightValue) { 362 | this.mViewCount = 3; 363 | this.mLeftValues = leftValues; 364 | this.mDefaultLeftValue = mDefaultLeftValue; 365 | 366 | this.mMiddleValues = middleValues; 367 | this.mDefaultMiddleValue = defaultMiddleValue; 368 | 369 | this.mRightValues = rightValues; 370 | this.mDefaultRightValue = defaultRightValue; 371 | 372 | initViewAndPicker(); 373 | } 374 | 375 | /** 376 | * 设置左边数据步长 377 | * 378 | * @param step 379 | */ 380 | public void setLeftStep(int step) { 381 | this.mLeftStep = step; 382 | initViewAndPicker(); 383 | } 384 | 385 | /** 386 | * 设置中间数据步长 387 | * 388 | * @param step 389 | */ 390 | public void setMiddleStep(int step) { 391 | this.mMiddleStep = step; 392 | initViewAndPicker(); 393 | } 394 | 395 | /** 396 | * 设置右边数据步长 397 | * 398 | * @param step 399 | */ 400 | public void setRightStep(int step) { 401 | this.mRightStep = step; 402 | initViewAndPicker(); 403 | } 404 | 405 | /** 406 | * 设置标题 407 | * 408 | * @param left 409 | * @param middle 410 | * @param right 411 | */ 412 | public void setTitle(String left, String middle, String right) { 413 | if (left != null) { 414 | mTitleLeft.setVisibility(VISIBLE); 415 | mTitleLeft.setText(left); 416 | } else { 417 | mTitleLeft.setVisibility(GONE); 418 | } 419 | if (middle != null) { 420 | mTitleMiddle.setVisibility(VISIBLE); 421 | mTitleMiddle.setText(middle); 422 | } else { 423 | mTitleMiddle.setVisibility(GONE); 424 | } 425 | if (right != null) { 426 | mTitleRight.setVisibility(VISIBLE); 427 | mTitleRight.setText(right); 428 | } else { 429 | mTitleRight.setVisibility(GONE); 430 | } 431 | this.postInvalidate(); 432 | } 433 | 434 | public void setUnitLeft(String unitLeft) { 435 | setUnit(unitLeft, 0); 436 | } 437 | 438 | public void setmUnitMiddle(String unitMiddle) { 439 | setUnit(unitMiddle, 1); 440 | } 441 | 442 | public void setUnitRight(String unitRight) { 443 | setUnit(unitRight, 2); 444 | } 445 | 446 | private void setUnit(String unit, int index) { 447 | TextView tvUnit; 448 | if (index == 0) { 449 | tvUnit = mUnitLeft; 450 | } else if (index == 1) { 451 | tvUnit = mUnitMiddle; 452 | } else { 453 | tvUnit = mUnitRight; 454 | } 455 | if (unit != null) { 456 | tvUnit.setText(unit); 457 | } else { 458 | tvUnit.setText(" "); 459 | } 460 | initViewAndPicker(); 461 | } 462 | 463 | /** 464 | * 设置回调 465 | * 466 | * @param listener 467 | */ 468 | public void setOnSelectedChangeListener(onSelectedChangeListener listener) { 469 | this.mSelectedChangeListener = listener; 470 | } 471 | 472 | /** 473 | * dp转px 474 | * 475 | * @param dp 476 | * @return 477 | */ 478 | private int dip2px(int dp) { 479 | float scale = mContext.getResources().getDisplayMetrics().density; 480 | return (int) (scale * dp + 0.5f); 481 | } 482 | 483 | /** 484 | * 回调接口 485 | */ 486 | public interface onSelectedChangeListener { 487 | void onSelected(PickValueView view, Object leftValue, Object middleValue, Object rightValue); 488 | } 489 | } 490 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 |