├── . npmignore
├── README.md
├── android
├── build.gradle
├── index.js
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── heng
│ └── wheel
│ ├── InertiaTimerTask.java
│ ├── LoopView.java
│ ├── LoopViewGestureListener.java
│ ├── MessageHandler.java
│ ├── OnItemSelectedListener.java
│ ├── OnItemSelectedRunnable.java
│ ├── SmoothScrollTimerTask.java
│ ├── WheelPackage.java
│ └── WheelViewManager.java
├── img
├── 1.jpg
├── 2.jpg
└── 3.jpg
├── index.js
└── package.json
/. npmignore:
--------------------------------------------------------------------------------
1 | img
2 | .DS_Store
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PLEASE NOTE, THIS PROJECT IS NO LONGER BEING MAINTAINED
2 |
3 | * * *
4 |
5 | # react-native-wheel [](https://www.npmjs.com/package/react-native-wheel)
6 | RN的Android滚轮组件
7 |
8 | ### [Combination use](https://github.com/beefe/react-native-picker)
9 |
10 | ## PropTypes
11 | * values 数据源(支持 String int double boolean)
12 | * isLoop 是否循环滚动
13 | * textSize 字体大小
14 | * selectedIndex 默认选中的下标
15 | * velocityFling 滚动速度,建议 15-25
16 | * onItemChange 滚动回调
17 |
18 | ## Install And Use
19 |
20 | #### Npm Install
21 |
22 | ```shell
23 | $ npm install --save react-native-wheel
24 | ```
25 |
26 | #### Update Gradle Settings
27 | ```gradle
28 | // file: android/settings.gradle
29 | ...
30 | include ':react-native-wheel'
31 | project(':react-native-wheel').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-wheel/android')
32 | ```
33 |
34 | #### Update app Gradle Build
35 |
36 | ```gradle
37 | // file: android/app/build.gradle
38 | ...
39 |
40 | dependencies {
41 | ...
42 | compile project(':react-native-wheel')
43 | }
44 | ```
45 |
46 | #### Register React Package
47 |
48 | ```java
49 | // file: android/src/main/java/com.xx/MainApplication.java
50 | ...
51 |
52 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
53 | @Override
54 | protected boolean getUseDeveloperSupport() {
55 | return BuildConfig.DEBUG;
56 | }
57 |
58 | @Override
59 | protected List getPackages() {
60 | return Arrays.asList(
61 | new MainReactPackage(),
62 | new WheelPackage() // Added there
63 | );
64 | }
65 | };
66 |
67 | ```
68 |
69 | #### Use
70 |
71 | ```js
72 |
73 | import React, { Component } from 'react';
74 | import {
75 | AppRegistry,
76 | StyleSheet,
77 | Text,
78 | View,
79 | ToastAndroid
80 | } from 'react-native';
81 |
82 | import WheelView from 'react-native-wheel';
83 |
84 | import Dimensions from 'Dimensions';
85 |
86 | let SCREEN_WIDTH = Dimensions.get('window').width;
87 | let SCREEN_HEIGHT = Dimensions.get('window').height;
88 |
89 |
90 | let wheelData = [1,'two',false,0.10,'six','seven','eight','nine','ten'];
91 |
92 | let currentIndex;
93 |
94 | class AwesomeProject extends Component {
95 | ok(){
96 | ToastAndroid.show('select index : ' + currentIndex +' select item : ' + wheelData[currentIndex] ,ToastAndroid.SHORT);
97 | }
98 | _onItemChange(index){
99 | currentIndex = index;
100 | }
101 | render() {
102 | return (
103 |
104 |
105 | 确定
106 |
107 |
116 |
117 | );
118 | }
119 | };
120 |
121 | var styles = StyleSheet.create({
122 | container: {
123 | flex: 1,
124 | justifyContent: 'flex-end',
125 | alignItems: 'center',
126 | backgroundColor: '#F5FCFF',
127 | },
128 | ok: {
129 | margin: 5,
130 | color: '#000000',
131 | fontSize: 18,
132 | },
133 | wheelview: {
134 | width: SCREEN_WIDTH,
135 | height: SCREEN_HEIGHT/5*2,
136 | },
137 | });
138 |
139 | AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject);
140 |
141 | ```
142 |
143 | ## Run Renderings
144 | 
145 | 
146 | 
147 |
148 | ## Reference
149 | https://github.com/weidongjian/androidWheelView
150 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.1"
6 |
7 | defaultConfig {
8 | minSdkVersion 16
9 | targetSdkVersion 22
10 | }
11 | }
12 |
13 | dependencies {
14 | compile 'com.facebook.react:react-native:0.20.1'
15 | }
16 |
--------------------------------------------------------------------------------
/android/index.js:
--------------------------------------------------------------------------------
1 | import React,{
2 | Component,
3 | PropTypes
4 | } from 'react';
5 |
6 | import ReactNative,{
7 | requireNativeComponent
8 | } from 'react-native';
9 |
10 |
11 | let NativeWheelView = requireNativeComponent('RCTWheelView',WheelView);
12 |
13 |
14 | export default class WheelView extends React.Component{
15 | static propTypes = {
16 | onItemChange: PropTypes.func,
17 | values: PropTypes.array,
18 | isLoop: PropTypes.bool,
19 | selectedIndex: PropTypes.number,
20 | textSize: PropTypes.number,
21 | itemsVisible: PropTypes.number,
22 | velocityFling: PropTypes.number,
23 | };
24 |
25 | _onItemChange(event) {
26 | if(this.props.onItemChange){
27 | this.props.onItemChange(event.nativeEvent.index);
28 | }
29 | };
30 |
31 | render(){
32 | return ;
33 | }
34 | };
35 |
36 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/android/src/main/java/com/heng/wheel/InertiaTimerTask.java:
--------------------------------------------------------------------------------
1 | package com.heng.wheel;
2 |
3 | import java.util.TimerTask;
4 |
5 | class InertiaTimerTask extends TimerTask {
6 |
7 | float a;
8 | final float velocityY;
9 | final LoopView loopView;
10 |
11 | InertiaTimerTask(LoopView loopview, float velocityY) {
12 | super();
13 | loopView = loopview;
14 | this.velocityY = velocityY;
15 | a = Integer.MAX_VALUE;
16 | }
17 |
18 | @Override
19 | public final void run() {
20 | if (a == Integer.MAX_VALUE) {
21 | if (Math.abs(velocityY) > 2000F) {
22 | if (velocityY > 0.0F) {
23 | a = 2000F;
24 | } else {
25 | a = -2000F;
26 | }
27 | } else {
28 | a = velocityY;
29 | }
30 | }
31 | if (Math.abs(a) >= 0.0F && Math.abs(a) <= 20F) {
32 | loopView.cancelFuture();
33 | loopView.handler.sendEmptyMessage(MessageHandler.WHAT_SMOOTH_SCROLL);
34 | return;
35 | }
36 | int i = (int) ((a * 10F) / 1000F);
37 | LoopView loopview = loopView;
38 | loopview.totalScrollY = loopview.totalScrollY - i;
39 | if (!loopView.isLoop) {
40 | float itemHeight = loopView.lineSpacingMultiplier * loopView.maxTextHeight;
41 | if (loopView.totalScrollY <= (int) ((float) (-loopView.selectedIndex) * itemHeight)) {
42 | a = 40F;
43 | loopView.totalScrollY = (int) ((float) (-loopView.selectedIndex) * itemHeight);
44 | } else if (loopView.totalScrollY >= (int) ((float) (loopView.items.size() - 1 - loopView.selectedIndex) * itemHeight)) {
45 | loopView.totalScrollY = (int) ((float) (loopView.items.size() - 1 - loopView.selectedIndex) * itemHeight);
46 | a = -40F;
47 | }
48 | }
49 | if (a < 0.0F) {
50 | a = a + 20F;
51 | } else {
52 | a = a - 20F;
53 | }
54 | loopView.handler.sendEmptyMessage(MessageHandler.WHAT_INVALIDATE_LOOP_VIEW);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/android/src/main/java/com/heng/wheel/LoopView.java:
--------------------------------------------------------------------------------
1 | package com.heng.wheel;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Paint;
6 | import android.graphics.Rect;
7 | import android.graphics.RectF;
8 | import android.graphics.Typeface;
9 | import android.os.Handler;
10 | import android.view.GestureDetector;
11 | import android.view.MotionEvent;
12 | import android.view.View;
13 |
14 | import com.facebook.react.bridge.Arguments;
15 | import com.facebook.react.bridge.ReactContext;
16 | import com.facebook.react.bridge.WritableMap;
17 | import com.facebook.react.uimanager.events.RCTEventEmitter;
18 |
19 | import java.util.List;
20 | import java.util.concurrent.Executors;
21 | import java.util.concurrent.ScheduledExecutorService;
22 | import java.util.concurrent.ScheduledFuture;
23 | import java.util.concurrent.TimeUnit;
24 |
25 | public class LoopView extends View {
26 |
27 | public enum ACTION {
28 | CLICK, FLING, DRAG
29 | }
30 |
31 | Context context;
32 |
33 | Handler handler;
34 | private GestureDetector gestureDetector;
35 | OnItemSelectedListener onItemSelectedListener;
36 |
37 | ScheduledExecutorService mExecutor = Executors.newSingleThreadScheduledExecutor();
38 | private ScheduledFuture> mFuture;
39 |
40 | Paint paintOuterText;
41 | Paint paintCenterText;
42 | Paint paintIndicator;
43 |
44 | List items;
45 |
46 | int maxTextWidth;
47 |
48 | int maxTextHeight;
49 | int colorGray = 0xffafafaf;
50 | int colorBlack = 0xff313131;
51 |
52 | int colorLightGray = 0xffc5c5c5;
53 |
54 | float lineSpacingMultiplier;
55 |
56 |
57 | int firstLineY;
58 | int secondLineY;
59 |
60 | int totalScrollY;
61 | int selectedItem;
62 | int preCurrentIndex;
63 | int change;
64 |
65 | final boolean DEFAULT_IS_LOOP = true;
66 |
67 | final int DEFAULT_VISIBLE_COUNT = 9;
68 |
69 | final int DEFAULT_SELECTED_INDEX = 0;
70 |
71 | final int DEFAULT_TEXT_SIZE = 21;
72 |
73 | boolean isLoop = DEFAULT_IS_LOOP;
74 |
75 | int itemsVisible = DEFAULT_VISIBLE_COUNT;
76 |
77 | int selectedIndex;
78 |
79 | int textSize;
80 |
81 | int mViewHeight;
82 | int mViewWidth;
83 |
84 | int halfCircumference;
85 | int radius;
86 |
87 | private int mOffset = 0;
88 | private float previousY;
89 | long startTime = 0;
90 |
91 | private int mVelocityFling;
92 |
93 | public LoopView(Context context) {
94 | super(context);
95 | initLoopView(context);
96 | }
97 |
98 | private void initLoopView(Context context) {
99 | this.context = context;
100 | handler = new MessageHandler(this);
101 | gestureDetector = new GestureDetector(context, new LoopViewGestureListener(this));
102 | gestureDetector.setIsLongpressEnabled(false);
103 |
104 | lineSpacingMultiplier = 2.0F;
105 |
106 | totalScrollY = 0;
107 | selectedIndex = DEFAULT_SELECTED_INDEX;
108 |
109 | initPaints();
110 |
111 | setTextSize(DEFAULT_TEXT_SIZE);
112 |
113 | mVelocityFling = 20;
114 | }
115 |
116 | private void initPaints() {
117 | paintOuterText = new Paint();
118 | paintOuterText.setColor(colorGray);
119 | paintOuterText.setAntiAlias(true);
120 | paintOuterText.setTextAlign(Paint.Align.CENTER);
121 | paintOuterText.setTypeface(Typeface.MONOSPACE);
122 |
123 | paintCenterText = new Paint();
124 | paintCenterText.setColor(colorBlack);
125 | paintCenterText.setAntiAlias(true);
126 | paintCenterText.setTextScaleX(1.05F);
127 | paintCenterText.setTextAlign(Paint.Align.CENTER);
128 | paintCenterText.setTypeface(Typeface.MONOSPACE);
129 |
130 | paintIndicator = new Paint();
131 | paintIndicator.setColor(colorLightGray);
132 | paintIndicator.setAntiAlias(true);
133 |
134 | if (android.os.Build.VERSION.SDK_INT >= 11) {
135 | setLayerType(LAYER_TYPE_SOFTWARE, null);
136 | }
137 | }
138 |
139 | @Override
140 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
141 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
142 | remeasure();
143 | mViewWidth = getMeasuredWidth();
144 | setMeasuredDimension(mViewWidth, mViewHeight);
145 | }
146 |
147 | private void remeasure() {
148 | if (items == null) {
149 | return;
150 | }
151 |
152 | measureTextWidthHeight();
153 |
154 | halfCircumference = (int) (maxTextHeight * lineSpacingMultiplier * (itemsVisible - 1));
155 | mViewHeight = (int) ((halfCircumference * 2) / Math.PI);
156 | radius = (int) (halfCircumference / Math.PI);
157 | firstLineY = (int) ((mViewHeight - lineSpacingMultiplier * maxTextHeight) / 2.0F);
158 | secondLineY = (int) ((mViewHeight + lineSpacingMultiplier * maxTextHeight) / 2.0F);
159 | if (selectedIndex == -1) {
160 | if (isLoop) {
161 | selectedIndex = (items.size() + 1) / 2;
162 | } else {
163 | selectedIndex = 0;
164 | }
165 | }
166 |
167 | preCurrentIndex = selectedIndex;
168 | }
169 |
170 | private void measureTextWidthHeight() {
171 | Rect rect = new Rect();
172 | for (int i = 0; i < items.size(); i++) {
173 | String s1 = items.get(i);
174 | paintCenterText.getTextBounds(s1, 0, s1.length(), rect);
175 | int textWidth = rect.width();
176 | if (textWidth > maxTextWidth) {
177 | maxTextWidth = textWidth;
178 | }
179 | int textHeight = rect.height();
180 | if (textHeight > maxTextHeight) {
181 | maxTextHeight = textHeight;
182 | }
183 | }
184 | }
185 |
186 | @Override
187 | protected void onDraw(Canvas canvas) {
188 | if (items == null) {
189 | return;
190 | }
191 |
192 | String as[] = new String[itemsVisible];
193 | change = (int) (totalScrollY / (lineSpacingMultiplier * maxTextHeight));
194 | preCurrentIndex = selectedIndex + change % items.size();
195 | if (!isLoop) {
196 | if (preCurrentIndex < 0) {
197 | preCurrentIndex = 0;
198 | }
199 | if (preCurrentIndex > items.size() - 1) {
200 | preCurrentIndex = items.size() - 1;
201 | }
202 | } else {
203 | if (preCurrentIndex < 0) {
204 | preCurrentIndex = items.size() + preCurrentIndex;
205 | }
206 | if (preCurrentIndex > items.size() - 1) {
207 | preCurrentIndex = preCurrentIndex - items.size();
208 | }
209 | }
210 |
211 | int j2 = (int) (totalScrollY % (lineSpacingMultiplier * maxTextHeight));
212 | // 设置as数组中每个元素的值
213 | int k1 = 0;
214 | while (k1 < itemsVisible) {
215 | int l1 = preCurrentIndex - (itemsVisible / 2 - k1);
216 | if (isLoop) {
217 | if (l1 < 0) {
218 | l1 = l1 + items.size();
219 | }
220 | if (l1 > items.size() - 1) {
221 | l1 = l1 - items.size();
222 | }
223 | as[k1] = items.get(l1);
224 | } else if (l1 < 0) {
225 | as[k1] = "";
226 | } else if (l1 > items.size() - 1) {
227 | as[k1] = "";
228 | } else {
229 | as[k1] = items.get(l1);
230 | }
231 | k1++;
232 | }
233 | canvas.drawLine(0.0F, firstLineY, mViewWidth, firstLineY, paintIndicator);
234 | canvas.drawLine(0.0F, secondLineY, mViewWidth, secondLineY, paintIndicator);
235 | int j1 = 0;
236 | while (j1 < itemsVisible) {
237 | canvas.save();
238 | // L(弧长)=α(弧度)* r(半径) (弧度制)
239 | // 求弧度--> (L * π ) / (π * r) (弧长X派/半圆周长)
240 | float itemHeight = maxTextHeight * lineSpacingMultiplier;
241 | double radian = ((itemHeight * j1 - j2) * Math.PI) / halfCircumference;
242 | // 弧度转换成角度(把半圆以Y轴为轴心向右转90度,使其处于第一象限及第四象限
243 | float angle = (float) (90D - (radian / Math.PI) * 180D);
244 | if (angle >= 90F || angle <= -90F) {
245 | canvas.restore();
246 | } else {
247 | int translateY = (int) (radius - Math.cos(radian) * radius - (Math.sin(radian) * maxTextHeight) / 2D);
248 | canvas.translate(0.0F, translateY);
249 | canvas.scale(1.0F, (float) Math.sin(radian));
250 | String text = as[j1];
251 | if (translateY <= firstLineY && maxTextHeight + translateY >= firstLineY) {
252 | // 条目经过第一条线
253 | canvas.save();
254 | canvas.clipRect(0, 0, mViewWidth, firstLineY - translateY);
255 | canvas.drawText(text, getXY(paintOuterText)[0] , getXY(paintOuterText)[1], paintOuterText);
256 | canvas.restore();
257 | canvas.save();
258 | canvas.clipRect(0, firstLineY - translateY, mViewWidth, (int) (itemHeight));
259 | canvas.drawText(text, getXY(paintCenterText)[0] , getXY(paintCenterText)[1], paintCenterText);
260 | canvas.restore();
261 | } else if (translateY <= secondLineY && maxTextHeight + translateY >= secondLineY) {
262 | // 条目经过第二条线
263 | canvas.save();
264 | canvas.clipRect(0, 0, mViewWidth, secondLineY - translateY);
265 | canvas.drawText(text, getXY(paintCenterText)[0] , getXY(paintCenterText)[1], paintCenterText);
266 | canvas.restore();
267 | canvas.save();
268 | canvas.clipRect(0, secondLineY - translateY, mViewWidth, (int) (itemHeight));
269 | canvas.drawText(text, getXY(paintOuterText)[0] , getXY(paintOuterText)[1], paintOuterText);
270 | canvas.restore();
271 | } else if (translateY >= firstLineY && maxTextHeight + translateY <= secondLineY) {
272 | // 中间条目
273 | canvas.clipRect(0, 0, mViewWidth, (int) (itemHeight));
274 | canvas.drawText(text, getXY(paintCenterText)[0] , getXY(paintCenterText)[1], paintCenterText);
275 | selectedItem = items.indexOf(as[j1]);
276 | } else {
277 | // 其他条目
278 | canvas.clipRect(0, 0, mViewWidth, (int) (itemHeight));
279 | canvas.drawText(text, getXY(paintOuterText)[0] , getXY(paintOuterText)[1], paintOuterText);
280 | }
281 | canvas.restore();
282 | }
283 | j1++;
284 | }
285 | }
286 |
287 | private float[] getXY(Paint paint) {
288 | float [] xy = new float[2];
289 | xy[0] = mViewWidth / 2;
290 |
291 | Rect rect = new Rect(0, 0, getWidth(), maxTextHeight);
292 | RectF bounds = new RectF();
293 | bounds.bottom = paint.descent() - paint.ascent();
294 | bounds.top += (rect.height() - bounds.bottom) / 2.0f;
295 | xy[1] = bounds.top - paint.ascent();
296 | return xy;
297 | }
298 |
299 | @Override
300 | public boolean onTouchEvent(MotionEvent event) {
301 | boolean eventConsumed = gestureDetector.onTouchEvent(event);
302 | float itemHeight = lineSpacingMultiplier * maxTextHeight;
303 | switch (event.getAction()) {
304 | case MotionEvent.ACTION_DOWN:
305 | startTime = System.currentTimeMillis();
306 | cancelFuture();
307 | previousY = event.getRawY();
308 | if (getParent() != null) {
309 | getParent().requestDisallowInterceptTouchEvent(true);
310 | }
311 | break;
312 | case MotionEvent.ACTION_MOVE:
313 | float dy = previousY - event.getRawY();
314 | previousY = event.getRawY();
315 |
316 | totalScrollY = (int) (totalScrollY + dy);
317 |
318 | if (!isLoop) {
319 | float top = -selectedIndex * itemHeight;
320 | float bottom = (items.size() - 1 - selectedIndex) * itemHeight;
321 |
322 | if (totalScrollY < top) {
323 | totalScrollY = (int) top;
324 | } else if (totalScrollY > bottom) {
325 | totalScrollY = (int) bottom;
326 | }
327 | }
328 | break;
329 |
330 | case MotionEvent.ACTION_UP:
331 | case MotionEvent.ACTION_CANCEL:
332 | default:
333 | if (!eventConsumed) {
334 | float y = event.getY();
335 | double l = Math.acos((radius - y) / radius) * radius;
336 | int circlePosition = (int) ((l + itemHeight / 2) / itemHeight);
337 |
338 | float extraOffset = (totalScrollY % itemHeight + itemHeight) % itemHeight;
339 | mOffset = (int) ((circlePosition - itemsVisible / 2) * itemHeight - extraOffset);
340 |
341 | if ((System.currentTimeMillis() - startTime) > 120) {
342 | smoothScroll(ACTION.DRAG);
343 | } else {
344 | smoothScroll(ACTION.CLICK);
345 | }
346 | }
347 | if (getParent() != null) {
348 | getParent().requestDisallowInterceptTouchEvent(false);
349 | }
350 | break;
351 | }
352 | invalidate();
353 | return true;
354 | }
355 |
356 | void smoothScroll(ACTION action) {
357 | cancelFuture();
358 | if (action == ACTION.FLING || action == ACTION.DRAG) {
359 | float itemHeight = lineSpacingMultiplier * maxTextHeight;
360 | mOffset = (int) ((totalScrollY % itemHeight + itemHeight) % itemHeight);
361 | if ((float) mOffset > itemHeight / 2.0F) {
362 | mOffset = (int) (itemHeight - (float) mOffset);
363 | } else {
364 | mOffset = -mOffset;
365 | }
366 | }
367 | mFuture = mExecutor.scheduleWithFixedDelay(new SmoothScrollTimerTask(this, mOffset), 0, 10, TimeUnit.MILLISECONDS);
368 | }
369 |
370 | protected final void scrollBy(float velocityY) {
371 | cancelFuture();
372 | mFuture = mExecutor.scheduleWithFixedDelay(new InertiaTimerTask(this, velocityY), 0, mVelocityFling, TimeUnit.MILLISECONDS);
373 | }
374 |
375 | public void cancelFuture() {
376 | if (mFuture != null && !mFuture.isCancelled()) {
377 | mFuture.cancel(true);
378 | mFuture = null;
379 | }
380 | }
381 |
382 | public void isLoop(boolean isLoop) {
383 | this.isLoop = isLoop;
384 | }
385 |
386 | public void setTextSize(float size) {
387 | if (size > 0.0F) {
388 | textSize = (int) (context.getResources().getDisplayMetrics().density * size);
389 | paintOuterText.setTextSize(textSize);
390 | paintCenterText.setTextSize(textSize);
391 | }
392 | }
393 |
394 | public void setItemsVisible(int itemsVisible) {
395 | this.itemsVisible = itemsVisible + 2;
396 | }
397 |
398 | public void setSelectedIndex(int selectedIndex) {
399 | this.selectedIndex = selectedIndex;
400 | }
401 |
402 | public void setVelocityFling(int velocityFling) {
403 | this.mVelocityFling = velocityFling;
404 | }
405 |
406 | public void onReceiveNativeEvent(int index) {
407 | WritableMap event = Arguments.createMap();
408 | event.putInt("index", index);
409 | ReactContext reactContext = (ReactContext) getContext();
410 | reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
411 | getId(),
412 | "topChange",
413 | event);
414 | }
415 |
416 | public void setListener(OnItemSelectedListener OnItemSelectedListener) {
417 | this.onItemSelectedListener = OnItemSelectedListener;
418 | }
419 |
420 | public void setItems(List items) {
421 | this.items = items;
422 | remeasure();
423 | invalidate();
424 | }
425 |
426 |
427 | public final int getSelectedIndex() {
428 | return selectedItem;
429 | }
430 |
431 | public String getSelectedItem() {
432 | return items.get(selectedItem);
433 | }
434 |
435 | protected final void onItemSelected() {
436 | if (onItemSelectedListener != null) {
437 | postDelayed(new OnItemSelectedRunnable(this), 200L);
438 | }
439 | }
440 | }
--------------------------------------------------------------------------------
/android/src/main/java/com/heng/wheel/LoopViewGestureListener.java:
--------------------------------------------------------------------------------
1 | package com.heng.wheel;
2 |
3 | import android.view.MotionEvent;
4 |
5 | class LoopViewGestureListener extends android.view.GestureDetector.SimpleOnGestureListener {
6 |
7 | final LoopView loopView;
8 |
9 | LoopViewGestureListener(LoopView loopview) {
10 | loopView = loopview;
11 | }
12 |
13 | @Override
14 | public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
15 | loopView.scrollBy(velocityY);
16 | return true;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/android/src/main/java/com/heng/wheel/MessageHandler.java:
--------------------------------------------------------------------------------
1 | package com.heng.wheel;
2 |
3 | import android.os.Handler;
4 | import android.os.Message;
5 |
6 | class MessageHandler extends Handler {
7 |
8 | public static final int WHAT_INVALIDATE_LOOP_VIEW = 1000;
9 | public static final int WHAT_SMOOTH_SCROLL = 2000;
10 | public static final int WHAT_ITEM_SELECTED = 3000;
11 |
12 | final LoopView loopview;
13 |
14 | MessageHandler(LoopView loopview) {
15 | this.loopview = loopview;
16 | }
17 |
18 | @Override
19 | public final void handleMessage(Message msg) {
20 | switch (msg.what) {
21 | case WHAT_INVALIDATE_LOOP_VIEW:
22 | loopview.invalidate();
23 | break;
24 | case WHAT_SMOOTH_SCROLL:
25 | loopview.smoothScroll(LoopView.ACTION.FLING);
26 | break;
27 | case WHAT_ITEM_SELECTED:
28 | loopview.onItemSelected();
29 | break;
30 | }
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/android/src/main/java/com/heng/wheel/OnItemSelectedListener.java:
--------------------------------------------------------------------------------
1 | package com.heng.wheel;
2 |
3 | public interface OnItemSelectedListener {
4 | void onItemSelected(int index);
5 | }
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/src/main/java/com/heng/wheel/OnItemSelectedRunnable.java:
--------------------------------------------------------------------------------
1 | package com.heng.wheel;
2 |
3 | class OnItemSelectedRunnable implements Runnable {
4 | final LoopView loopView;
5 |
6 | OnItemSelectedRunnable(LoopView loopview) {
7 | loopView = loopview;
8 | }
9 |
10 | @Override
11 | public final void run() {
12 | loopView.onItemSelectedListener.onItemSelected(loopView.getSelectedIndex());
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/android/src/main/java/com/heng/wheel/SmoothScrollTimerTask.java:
--------------------------------------------------------------------------------
1 | package com.heng.wheel;
2 |
3 | import java.util.TimerTask;
4 |
5 | class SmoothScrollTimerTask extends TimerTask {
6 |
7 | int realTotalOffset;
8 | int realOffset;
9 | int offset;
10 | final LoopView loopView;
11 |
12 | SmoothScrollTimerTask(LoopView loopview, int offset) {
13 | this.loopView = loopview;
14 | this.offset = offset;
15 | realTotalOffset = Integer.MAX_VALUE;
16 | realOffset = 0;
17 | }
18 |
19 | @Override
20 | public final void run() {
21 | if (realTotalOffset == Integer.MAX_VALUE) {
22 | realTotalOffset = offset;
23 | }
24 | realOffset = (int) ((float) realTotalOffset * 0.1F);
25 |
26 | if (realOffset == 0) {
27 | if (realTotalOffset < 0) {
28 | realOffset = -1;
29 | } else {
30 | realOffset = 1;
31 | }
32 | }
33 | if (Math.abs(realTotalOffset) <= 0) {
34 | loopView.cancelFuture();
35 | loopView.handler.sendEmptyMessage(MessageHandler.WHAT_ITEM_SELECTED);
36 | } else {
37 | loopView.totalScrollY = loopView.totalScrollY + realOffset;
38 | loopView.handler.sendEmptyMessage(MessageHandler.WHAT_INVALIDATE_LOOP_VIEW);
39 | realTotalOffset = realTotalOffset - realOffset;
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/android/src/main/java/com/heng/wheel/WheelPackage.java:
--------------------------------------------------------------------------------
1 | package com.heng.wheel;
2 |
3 | import com.facebook.react.ReactPackage;
4 | import com.facebook.react.bridge.JavaScriptModule;
5 | import com.facebook.react.bridge.NativeModule;
6 | import com.facebook.react.bridge.ReactApplicationContext;
7 | import com.facebook.react.uimanager.ViewManager;
8 |
9 | import java.util.Arrays;
10 | import java.util.Collections;
11 | import java.util.List;
12 |
13 | /**
14 | * Created by heng on 15/11/27.
15 | */
16 | public class WheelPackage implements ReactPackage {
17 | @Override
18 | public List createNativeModules(ReactApplicationContext reactContext) {
19 | return Collections.emptyList();
20 | }
21 |
22 | @Override
23 | public List> createJSModules() {
24 | return Collections.emptyList();
25 | }
26 |
27 | @Override
28 | public List createViewManagers(ReactApplicationContext reactContext) {
29 | return Arrays.asList(new WheelViewManager());
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/android/src/main/java/com/heng/wheel/WheelViewManager.java:
--------------------------------------------------------------------------------
1 | package com.heng.wheel;
2 |
3 | import com.facebook.react.bridge.ReadableArray;
4 | import com.facebook.react.uimanager.SimpleViewManager;
5 | import com.facebook.react.uimanager.ThemedReactContext;
6 | import com.facebook.react.uimanager.annotations.ReactProp;
7 |
8 | import java.util.ArrayList;
9 |
10 | /**
11 | */
12 | public class WheelViewManager extends SimpleViewManager {
13 |
14 | public static final String REACT_CLASS = "RCTWheelView";
15 |
16 | @Override
17 | public String getName() {
18 | return REACT_CLASS;
19 | }
20 |
21 | @Override
22 | protected LoopView createViewInstance(ThemedReactContext reactContext) {
23 | return new LoopView(reactContext);
24 | }
25 |
26 | @ReactProp(name = "values")
27 | public void setItems(LoopView view, ReadableArray values) {
28 | ArrayList items = new ArrayList<>();
29 | for (int i = 0; i < values.size(); i++) {
30 | String type = values.getType(i).name();
31 | switch (type) {
32 | case "Boolean":
33 | items.add(String.valueOf(values.getBoolean(i)));
34 | break;
35 | case "Number":
36 | try {
37 | items.add(String.valueOf(values.getInt(i)));
38 | } catch (Exception e) {
39 | items.add(String.valueOf(values.getDouble(i)));
40 | }
41 | break;
42 | case "String":
43 | items.add(values.getString(i));
44 | break;
45 | }
46 | }
47 | view.setItems(items);
48 | }
49 |
50 | @ReactProp(name = "isLoop")
51 | public void isLoop(LoopView view, boolean isLoop) {
52 | view.isLoop(isLoop);
53 | }
54 |
55 | @ReactProp(name = "itemsVisible")
56 | public void setItemsVisible(LoopView view, int itemsVisible) {
57 | view.setItemsVisible(itemsVisible);
58 | }
59 |
60 | @ReactProp(name = "velocityFling")
61 | public void setVelocityFling(LoopView view, int velocityFling) {
62 | view.setVelocityFling(velocityFling);
63 | }
64 |
65 | @ReactProp(name = "selectedIndex")
66 | public void setSelectedIndex(LoopView view, int selectedIndex) {
67 | view.setSelectedIndex(selectedIndex);
68 | }
69 |
70 | @ReactProp(name = "textSize", defaultFloat = 16f)
71 | public void setTextSize(LoopView view, float textSize) {
72 | view.setTextSize(textSize);
73 | }
74 |
75 | @ReactProp(name = "onItemChange", defaultBoolean = true)
76 | public void setOnItemChange(final LoopView view, Boolean value) {
77 | view.setListener(new OnItemSelectedListener() {
78 | @Override
79 | public void onItemSelected(int index) {
80 | view.onReceiveNativeEvent(index);
81 | }
82 | });
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/img/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shexiaoheng/react-native-wheel/5bc9c64a5cbc1ef4e04933c48cb4dbf24d41228d/img/1.jpg
--------------------------------------------------------------------------------
/img/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shexiaoheng/react-native-wheel/5bc9c64a5cbc1ef4e04933c48cb4dbf24d41228d/img/2.jpg
--------------------------------------------------------------------------------
/img/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shexiaoheng/react-native-wheel/5bc9c64a5cbc1ef4e04933c48cb4dbf24d41228d/img/3.jpg
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import React,{
2 | Component,
3 | PropTypes
4 | } from 'react';
5 |
6 | import ReactNative,{
7 | requireNativeComponent
8 | } from 'react-native';
9 |
10 |
11 | let NativeWheelView = requireNativeComponent('RCTWheelView',WheelView);
12 |
13 |
14 | export default class WheelView extends React.Component{
15 | static propTypes = {
16 | onItemChange: PropTypes.func,
17 | values: PropTypes.array,
18 | isLoop: PropTypes.bool,
19 | selectedIndex: PropTypes.number,
20 | textSize: PropTypes.number,
21 | itemsVisible: PropTypes.number,
22 | velocityFling: PropTypes.number,
23 | };
24 |
25 | _onItemChange(event) {
26 | if(this.props.onItemChange){
27 | this.props.onItemChange(event.nativeEvent.index);
28 | }
29 | };
30 |
31 | render(){
32 | return ;
33 | }
34 | };
35 |
36 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-wheel",
3 | "version": "1.1.3",
4 | "description": "android wheel view for react-native",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/shexiaoheng/react-native-wheel.git"
12 | },
13 | "keywords": [
14 | "react-native-wheel"
15 | ],
16 | "author": "heng",
17 | "license": "MIT",
18 | "bugs": {
19 | "url": "https://github.com/shexiaoheng/react-native-wheel/issues"
20 | },
21 | "homepage": "https://github.com/shexiaoheng/react-native-wheel#readme",
22 | "dependencies": {},
23 | "devDependencies": {}
24 | }
25 |
--------------------------------------------------------------------------------