├── scroll-ruler-ios.gif
├── scroll-ruler-android.gif
├── android
├── src
│ └── main
│ │ ├── res
│ │ └── values
│ │ │ ├── strings.xml
│ │ │ └── attrs.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── shenhuniurou
│ │ └── scrollruler
│ │ ├── RNScrollRulerPackage.java
│ │ ├── RNScrollRulerManager.java
│ │ └── RNScrollRuler.java
└── build.gradle
├── ios
├── RCTScrollRuler.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ ├── xcuserdata
│ │ │ └── Daniel.xcuserdatad
│ │ │ │ └── UserInterfaceState.xcuserstate
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── xcuserdata
│ │ └── Daniel.xcuserdatad
│ │ │ └── xcschemes
│ │ │ ├── xcschememanagement.plist
│ │ │ └── RCTScrollRuler.xcscheme
│ └── project.pbxproj
└── RCTScrollRuler
│ ├── RCTScrollRulerManager.h
│ ├── RCTScrollRuler.h
│ ├── RCTScrollRulerManager.m
│ └── RCTScrollRuler.m
├── index.js
├── package.json
├── LICENSE
├── README.md
├── index.ios.js
└── index.android.js
/scroll-ruler-ios.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shenhuniurou/react-native-scroll-ruler/HEAD/scroll-ruler-ios.gif
--------------------------------------------------------------------------------
/scroll-ruler-android.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shenhuniurou/react-native-scroll-ruler/HEAD/scroll-ruler-android.gif
--------------------------------------------------------------------------------
/android/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | RNScrollRuler
3 |
4 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/ios/RCTScrollRuler.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/RCTScrollRuler.xcodeproj/project.xcworkspace/xcuserdata/Daniel.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shenhuniurou/react-native-scroll-ruler/HEAD/ios/RCTScrollRuler.xcodeproj/project.xcworkspace/xcuserdata/Daniel.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import { Platform } from "react-native";
2 | import RNScrollRulerIOS from "./index.ios.js";
3 | import RNScrollRulerAndroid from "./index.android.js";
4 |
5 | const RNScrollRuler = Platform.OS === "ios"
6 | ? RNScrollRulerIOS
7 | : RNScrollRulerAndroid;
8 |
9 | export default RNScrollRuler;
10 |
--------------------------------------------------------------------------------
/ios/RCTScrollRuler/RCTScrollRulerManager.h:
--------------------------------------------------------------------------------
1 | //
2 | // RCTScrollRulerManager.h
3 | // RCTScrollRuler
4 | //
5 | // Created by Daniel on 2018/5/15.
6 | // Copyright © 2018年 Daniel. All rights reserved.
7 | //
8 |
9 | #import "React/RCTViewManager.h"
10 |
11 | @interface RCTScrollRulerManager : RCTViewManager
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/ios/RCTScrollRuler.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-scroll-ruler",
3 | "version": "1.2.1",
4 | "description": "ReactNative版选择身高体重的横向刻度尺组件,兼容Android和iOS",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [
10 | "react-native",
11 | "react-native-scroll-ruler"
12 | ],
13 | "author": "shenhuniurou",
14 | "license": "MIT"
15 | }
16 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.1"
6 |
7 |
8 | defaultConfig {
9 | minSdkVersion 16
10 | targetSdkVersion 23
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | }
15 | lintOptions {
16 | abortOnError false
17 | }
18 | }
19 |
20 | dependencies {
21 | compile 'com.facebook.react:react-native:0.20.+'
22 | }
23 |
--------------------------------------------------------------------------------
/ios/RCTScrollRuler.xcodeproj/xcuserdata/Daniel.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | RCTScrollRuler.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | 65373D5620AA8B88008AA28A
16 |
17 | primary
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 shenhuniurou
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/ios/RCTScrollRuler/RCTScrollRuler.h:
--------------------------------------------------------------------------------
1 | //
2 | // RCTScrollRuler.h
3 | // RCTScrollRuler
4 | //
5 | // Created by shenhuniurou on 2018/5/11.
6 | // Copyright © 2018年 shenhuniurou. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 |
12 | @class RCTScrollRuler;
13 | @protocol RCTScrollRulerDelegate
14 |
15 | /*
16 | * 游标卡尺滑动,对应value回调
17 | * 滑动视图
18 | * 当前滑动的值
19 | */
20 | -(void)dyScrollRulerView:(RCTScrollRuler *)rulerView valueChange:(float)value;
21 |
22 | @end
23 | @interface RCTScrollRuler : UIView
24 |
25 | @property (nonatomic, copy) RCTBubblingEventBlock onSelect;
26 |
27 | @property(nonatomic,weak)id delegate;
28 |
29 | //滑动时是否改变textfield值
30 | @property(nonatomic, assign)BOOL scrollByHand;
31 |
32 | //三角形颜色
33 | @property(nonatomic,strong)UIColor *triangleColor;
34 | //背景颜色
35 | @property(nonatomic,strong)UIColor *bgColor;
36 |
37 | -(instancetype)initWithFrame:(CGRect)frame theMinValue:(float)minValue theMaxValue:(float)maxValue theStep:(float)step theNum:(NSInteger)betweenNum theUnit:unit;
38 |
39 | -(void)setRealValue:(float)realValue animated:(BOOL)animated;
40 |
41 | +(CGFloat)rulerViewHeight;
42 |
43 | @end
44 |
45 |
--------------------------------------------------------------------------------
/android/src/main/java/com/shenhuniurou/scrollruler/RNScrollRulerPackage.java:
--------------------------------------------------------------------------------
1 | package com.shenhuniurou.scrollruler;
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 | * @author shenhuniurou
15 | * @email shenhuniurou@gmail.com
16 | * @date 2018/5/22 23:55
17 | * @description
18 | */
19 |
20 | public class RNScrollRulerPackage implements ReactPackage {
21 |
22 |
23 | @Override
24 | public List> createJSModules() {
25 | return Collections.emptyList();
26 | }
27 |
28 | @Override
29 | public List createNativeModules(ReactApplicationContext reactContext) {
30 | return Arrays.asList();
31 | }
32 |
33 | @Override
34 | public List createViewManagers(ReactApplicationContext reactContext) {
35 | return Arrays.asList(
36 | new RNScrollRulerManager()
37 | );
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/ios/RCTScrollRuler/RCTScrollRulerManager.m:
--------------------------------------------------------------------------------
1 | //
2 | // RCTScrollRulerManager.m
3 | // RCTScrollRuler
4 | //
5 | // Created by Daniel on 2018/5/15.
6 | // Copyright © 2018年 Daniel. All rights reserved.
7 | //
8 |
9 | #import "RCTScrollRulerManager.h"
10 | #import "RCTScrollRuler.h"
11 | #import
12 | #import
13 | #import
14 |
15 | #define ScreenWidth ([[UIScreen mainScreen] bounds].size.width)
16 | #define ScreenHeight ([[UIScreen mainScreen] bounds].size.height)
17 |
18 | @interface RCTScrollRulerManager()
19 |
20 | @property(nonatomic,strong)RCTScrollRuler *noneZeroRullerView;
21 |
22 | @end
23 |
24 | @implementation RCTScrollRulerManager
25 |
26 | RCT_EXPORT_MODULE()
27 |
28 | RCT_EXPORT_VIEW_PROPERTY(minValue, int);
29 |
30 | RCT_EXPORT_VIEW_PROPERTY(maxValue, int);
31 |
32 | RCT_EXPORT_VIEW_PROPERTY(step, float);
33 |
34 | RCT_EXPORT_VIEW_PROPERTY(defaultValue, int);
35 |
36 | RCT_EXPORT_VIEW_PROPERTY(num, int);
37 |
38 | RCT_EXPORT_VIEW_PROPERTY(unit, NSString);
39 |
40 | RCT_EXPORT_VIEW_PROPERTY(onSelect, RCTBubblingEventBlock)
41 |
42 | - (UIView *)view
43 | {
44 |
45 | CGFloat rullerHeight = [RCTScrollRuler rulerViewHeight];
46 | _noneZeroRullerView = [[RCTScrollRuler alloc]initWithFrame:CGRectMake(10, 0, ScreenWidth-20, rullerHeight) theMinValue:0 theMaxValue:0 theStep:1.0 theNum:10 theUnit:@""];
47 | _noneZeroRullerView.bgColor = [UIColor whiteColor];
48 | _noneZeroRullerView.delegate = self;
49 | _noneZeroRullerView.scrollByHand = YES;
50 |
51 | return _noneZeroRullerView;
52 | }
53 |
54 | #pragma RCTScrollRulerDelegate
55 | -(void)dyScrollRulerView:(RCTScrollRuler *)rulerView valueChange:(float)value{
56 | rulerView.onSelect(@{@"value": @((int)value)});
57 | }
58 |
59 | @end
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-scroll-ruler
2 | ReactNative版滑动刻度尺,兼容Android和iOS。
3 |
4 | ## Gifs
5 | 
6 | 
7 |
8 | ## Get Started
9 |
10 | ### Installation
11 |
12 | Step 1:
13 |
14 | `npm i react-native-scroll-ruler --save`
15 |
16 | or
17 |
18 | `yarn add react-native-scroll-ruler`
19 |
20 | Step 2:
21 |
22 | `react-native link react-native-scroll-ruler`
23 |
24 | That's all!
25 |
26 | ### Usage
27 |
28 | #### Simple
29 |
30 | ```javascript
31 | import RNScrollRuler from 'react-native-scroll-ruler';
32 |
33 | {
36 | }}
37 | minValue={30}
38 | maxValue={180}
39 | step={1}
40 | num={10}
41 | unit={"kg"}
42 | defaultValue={this.state.defaultWeight}
43 | onSelect={(value) => {
44 | this.setState({weight: value});
45 | }}
46 | >
47 |
48 |
49 | {
52 | }}
53 | minValue={120}
54 | maxValue={250}
55 | step={1}
56 | num={10}
57 | unit={"cm"}
58 | defaultValue={this.state.defaultHeight}
59 | onSelect={(value) => {
60 | this.setState({height: value});
61 | }}
62 | >
63 | ```
64 |
65 | #### Props
66 |
67 | |Prop|Description|Type|Required|
68 | |:---|:----|:---|:---|
69 | |minValue|尺子显示的最小值|number|Y|
70 | |maxValue|尺子显示的最大值|number|Y|
71 | |defaultValue|尺子默认值|number|Y|
72 | |step|两个大刻度之间的数值间隔|number|Y|
73 | |num|两个小刻度之间的数值间隔|number|Y|
74 | |unit|单位|string|N|
75 |
76 | #### Methods
77 |
78 | |Method|Description|
79 | |:---|:----|
80 | |onSelect|选中值后的回调方法|
81 |
82 |
83 |
84 |
85 | #### License
86 |
87 | [MIT](https://github.com/shenhuniurou/react-native-scroll-ruler/blob/master/LICENSE)
88 |
--------------------------------------------------------------------------------
/index.ios.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import PropTypes from 'prop-types';
3 | import {requireNativeComponent, View, ViewPropTypes} from 'react-native';
4 |
5 | type PropsType = {
6 | minValue?: number;
7 | maxValue?: number;
8 | step?: number;
9 | defaultValue?: number;
10 | num?: number;
11 | unit?: string;
12 | } & typeof
13 | (View);
14 |
15 | export default class RNScrollRuler extends Component {
16 | static propTypes = {
17 | minValue: PropTypes.number.isRequired,
18 | maxValue: PropTypes.number.isRequired,
19 | step: PropTypes.number.isRequired,
20 | defaultValue: PropTypes.number.isRequired,
21 | num: PropTypes.number.isRequired,
22 | unit: PropTypes.string,
23 | onSelect: PropTypes.func,
24 | ...ViewPropTypes,
25 | };
26 | props: PropsType;
27 | rulerRef: any;
28 |
29 | setNativeProps(props: PropsType) {
30 | this.rulerRef.setNativeProps(props);
31 | }
32 |
33 | _onSelect = (event) => {
34 | if (!this.props.onSelect) {
35 | return;
36 | }
37 | this.props.onSelect(event.nativeEvent.value);
38 | }
39 |
40 | render() {
41 | const {
42 | minValue,
43 | maxValue,
44 | step,
45 | defaultValue,
46 | num,
47 | unit,
48 | onSelect,
49 | ...otherProps
50 | } = this.props;
51 |
52 | return (
53 | {
55 | this.rulerRef = component;
56 | }}
57 | minValue={minValue}
58 | maxValue={maxValue}
59 | step={step}
60 | defaultValue={defaultValue}
61 | num={num}
62 | unit={unit}
63 | onSelect={this._onSelect}
64 | {...otherProps}
65 | />
66 | );
67 | }
68 | }
69 |
70 | const RCTScrollRuler = requireNativeComponent('RCTScrollRuler', null);
--------------------------------------------------------------------------------
/index.android.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import PropTypes from 'prop-types';
3 | import {requireNativeComponent, View, ViewPropTypes} from 'react-native';
4 |
5 | type PropsType = {
6 | minValue?: number;
7 | maxValue?: number;
8 | defaultValue?: number;
9 | step?: number;
10 | num?: number;
11 | unit?: string;
12 | } & typeof
13 | (View);
14 |
15 | export default class ReactScrollRuler extends Component {
16 | static propTypes = {
17 | minValue: PropTypes.number.isRequired,
18 | maxValue: PropTypes.number.isRequired,
19 | defaultValue: PropTypes.number.isRequired,
20 | step: PropTypes.number.isRequired,
21 | num: PropTypes.number.isRequired,
22 | unit: PropTypes.string,
23 | onSelect: PropTypes.func,
24 | ...ViewPropTypes,
25 | };
26 | props: PropsType;
27 | rulerRef: any;
28 |
29 | setNativeProps(props: PropsType) {
30 | this.rulerRef.setNativeProps(props);
31 | }
32 |
33 | _onSelect = (event) => {
34 | if (!this.props.onSelect) {
35 | return;
36 | }
37 | this.props.onSelect(event.nativeEvent.value);
38 | }
39 |
40 | render() {
41 | const {
42 | minValue,
43 | maxValue,
44 | defaultValue,
45 | step,
46 | num,
47 | unit,
48 | onSelect,
49 | ...otherProps
50 | } = this.props;
51 |
52 | return (
53 | {
55 | this.rulerRef = component;
56 | }}
57 | minValue={minValue}
58 | maxValue={maxValue}
59 | defaultValue={defaultValue}
60 | step={step}
61 | num={num}
62 | unit={unit}
63 | onSelect={this._onSelect}
64 | {...otherProps}
65 | />
66 | );
67 | }
68 | }
69 |
70 | const RNScrollRuler = requireNativeComponent('RNScrollRuler', ReactScrollRuler, {
71 | nativeOnly: {onSelect: true}
72 | });
--------------------------------------------------------------------------------
/android/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/android/src/main/java/com/shenhuniurou/scrollruler/RNScrollRulerManager.java:
--------------------------------------------------------------------------------
1 | package com.shenhuniurou.scrollruler;
2 |
3 | import android.view.View;
4 |
5 | import com.facebook.react.bridge.Arguments;
6 | import com.facebook.react.bridge.WritableMap;
7 | import com.facebook.react.uimanager.SimpleViewManager;
8 | import com.facebook.react.uimanager.ThemedReactContext;
9 | import com.facebook.react.uimanager.annotations.ReactProp;
10 | import com.facebook.react.uimanager.events.RCTEventEmitter;
11 |
12 | import javax.annotation.Nullable;
13 |
14 | /**
15 | * @author shenhuniurou
16 | * @email shenhuniurou@gmail.com
17 | * @date 2018/5/23 01:18
18 | * @description
19 | */
20 |
21 | public class RNScrollRulerManager extends SimpleViewManager {
22 |
23 | public static final String REACT_CLASS = "RNScrollRuler";
24 |
25 | @Override
26 | public String getName() {
27 | return REACT_CLASS;
28 | }
29 |
30 | @Override
31 | protected View createViewInstance(final ThemedReactContext reactContext) {
32 | final RNScrollRuler ruler = new RNScrollRuler(reactContext);
33 | ruler.setOnChooseResulterListener(new RNScrollRuler.OnChooseResulterListener() {
34 | @Override
35 | public void onEndResult(String result) {
36 | WritableMap event = Arguments.createMap();
37 | event.putString("value", result);
38 | reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(ruler.getId(), "topSelect", event);
39 | }
40 |
41 | @Override
42 | public void onScrollResult(String result) {
43 |
44 | }
45 | });
46 | return ruler;
47 | }
48 |
49 | @ReactProp(name = "minValue")
50 | public void setMinValue(RNScrollRuler ruler, @Nullable int minValue) {
51 | ruler.setMinScale(minValue);
52 | }
53 |
54 | @ReactProp(name = "maxValue")
55 | public void setMaxValue(RNScrollRuler ruler, @Nullable int maxValue) {
56 | ruler.setMaxScale(maxValue);
57 | }
58 |
59 | @ReactProp(name = "defaultValue")
60 | public void setDefaultValue(RNScrollRuler ruler, @Nullable int defaultValue) {
61 | ruler.setFirstScale(defaultValue);
62 | }
63 |
64 | @ReactProp(name = "unit")
65 | public void setUnit(RNScrollRuler ruler, @Nullable String unit) {
66 | ruler.setUnit(unit);
67 | }
68 |
69 | @ReactProp(name = "step")
70 | public void setStep(RNScrollRuler ruler, @Nullable float step) {
71 | ruler.setScaleLimit((int)(step * 10));
72 | }
73 |
74 | @ReactProp(name = "num")
75 | public void setNum(RNScrollRuler ruler, @Nullable int num) {
76 | ruler.setScaleCount(num);
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/ios/RCTScrollRuler.xcodeproj/xcuserdata/Daniel.xcuserdatad/xcschemes/RCTScrollRuler.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/ios/RCTScrollRuler.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 65373D7B20AA8BB5008AA28A /* RCTScrollRuler.m in Sources */ = {isa = PBXBuildFile; fileRef = 65373D6520AA8BB4008AA28A /* RCTScrollRuler.m */; };
11 | 65373D7C20AA8BB5008AA28A /* RCTScrollRulerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 65373D6620AA8BB4008AA28A /* RCTScrollRulerManager.m */; };
12 | /* End PBXBuildFile section */
13 |
14 | /* Begin PBXCopyFilesBuildPhase section */
15 | 65373D5520AA8B88008AA28A /* CopyFiles */ = {
16 | isa = PBXCopyFilesBuildPhase;
17 | buildActionMask = 2147483647;
18 | dstPath = "include/$(PRODUCT_NAME)";
19 | dstSubfolderSpec = 16;
20 | files = (
21 | );
22 | runOnlyForDeploymentPostprocessing = 0;
23 | };
24 | /* End PBXCopyFilesBuildPhase section */
25 |
26 | /* Begin PBXFileReference section */
27 | 65373D5720AA8B88008AA28A /* libRCTScrollRuler.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTScrollRuler.a; sourceTree = BUILT_PRODUCTS_DIR; };
28 | 65373D6520AA8BB4008AA28A /* RCTScrollRuler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTScrollRuler.m; sourceTree = ""; };
29 | 65373D6620AA8BB4008AA28A /* RCTScrollRulerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTScrollRulerManager.m; sourceTree = ""; };
30 | 65373D7020AA8BB5008AA28A /* RCTScrollRuler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTScrollRuler.h; sourceTree = ""; };
31 | 65373D7220AA8BB5008AA28A /* RCTScrollRulerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTScrollRulerManager.h; sourceTree = ""; };
32 | /* End PBXFileReference section */
33 |
34 | /* Begin PBXFrameworksBuildPhase section */
35 | 65373D5420AA8B88008AA28A /* Frameworks */ = {
36 | isa = PBXFrameworksBuildPhase;
37 | buildActionMask = 2147483647;
38 | files = (
39 | );
40 | runOnlyForDeploymentPostprocessing = 0;
41 | };
42 | /* End PBXFrameworksBuildPhase section */
43 |
44 | /* Begin PBXGroup section */
45 | 65373D4E20AA8B88008AA28A = {
46 | isa = PBXGroup;
47 | children = (
48 | 65373D5920AA8B88008AA28A /* RCTScrollRuler */,
49 | 65373D5820AA8B88008AA28A /* Products */,
50 | );
51 | sourceTree = "";
52 | };
53 | 65373D5820AA8B88008AA28A /* Products */ = {
54 | isa = PBXGroup;
55 | children = (
56 | 65373D5720AA8B88008AA28A /* libRCTScrollRuler.a */,
57 | );
58 | name = Products;
59 | sourceTree = "";
60 | };
61 | 65373D5920AA8B88008AA28A /* RCTScrollRuler */ = {
62 | isa = PBXGroup;
63 | children = (
64 | 65373D7020AA8BB5008AA28A /* RCTScrollRuler.h */,
65 | 65373D6520AA8BB4008AA28A /* RCTScrollRuler.m */,
66 | 65373D7220AA8BB5008AA28A /* RCTScrollRulerManager.h */,
67 | 65373D6620AA8BB4008AA28A /* RCTScrollRulerManager.m */,
68 | );
69 | path = RCTScrollRuler;
70 | sourceTree = "";
71 | };
72 | /* End PBXGroup section */
73 |
74 | /* Begin PBXNativeTarget section */
75 | 65373D5620AA8B88008AA28A /* RCTScrollRuler */ = {
76 | isa = PBXNativeTarget;
77 | buildConfigurationList = 65373D6020AA8B88008AA28A /* Build configuration list for PBXNativeTarget "RCTScrollRuler" */;
78 | buildPhases = (
79 | 65373D5320AA8B88008AA28A /* Sources */,
80 | 65373D5420AA8B88008AA28A /* Frameworks */,
81 | 65373D5520AA8B88008AA28A /* CopyFiles */,
82 | );
83 | buildRules = (
84 | );
85 | dependencies = (
86 | );
87 | name = RCTScrollRuler;
88 | productName = RCTScrollRuler;
89 | productReference = 65373D5720AA8B88008AA28A /* libRCTScrollRuler.a */;
90 | productType = "com.apple.product-type.library.static";
91 | };
92 | /* End PBXNativeTarget section */
93 |
94 | /* Begin PBXProject section */
95 | 65373D4F20AA8B88008AA28A /* Project object */ = {
96 | isa = PBXProject;
97 | attributes = {
98 | LastUpgradeCheck = 0930;
99 | ORGANIZATIONNAME = Daniel;
100 | TargetAttributes = {
101 | 65373D5620AA8B88008AA28A = {
102 | CreatedOnToolsVersion = 9.3;
103 | };
104 | };
105 | };
106 | buildConfigurationList = 65373D5220AA8B88008AA28A /* Build configuration list for PBXProject "RCTScrollRuler" */;
107 | compatibilityVersion = "Xcode 9.3";
108 | developmentRegion = en;
109 | hasScannedForEncodings = 0;
110 | knownRegions = (
111 | en,
112 | );
113 | mainGroup = 65373D4E20AA8B88008AA28A;
114 | productRefGroup = 65373D5820AA8B88008AA28A /* Products */;
115 | projectDirPath = "";
116 | projectRoot = "";
117 | targets = (
118 | 65373D5620AA8B88008AA28A /* RCTScrollRuler */,
119 | );
120 | };
121 | /* End PBXProject section */
122 |
123 | /* Begin PBXSourcesBuildPhase section */
124 | 65373D5320AA8B88008AA28A /* Sources */ = {
125 | isa = PBXSourcesBuildPhase;
126 | buildActionMask = 2147483647;
127 | files = (
128 | 65373D7B20AA8BB5008AA28A /* RCTScrollRuler.m in Sources */,
129 | 65373D7C20AA8BB5008AA28A /* RCTScrollRulerManager.m in Sources */,
130 | );
131 | runOnlyForDeploymentPostprocessing = 0;
132 | };
133 | /* End PBXSourcesBuildPhase section */
134 |
135 | /* Begin XCBuildConfiguration section */
136 | 65373D5E20AA8B88008AA28A /* Debug */ = {
137 | isa = XCBuildConfiguration;
138 | buildSettings = {
139 | ALWAYS_SEARCH_USER_PATHS = NO;
140 | CLANG_ANALYZER_NONNULL = YES;
141 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
142 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
143 | CLANG_CXX_LIBRARY = "libc++";
144 | CLANG_ENABLE_MODULES = YES;
145 | CLANG_ENABLE_OBJC_ARC = YES;
146 | CLANG_ENABLE_OBJC_WEAK = YES;
147 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
148 | CLANG_WARN_BOOL_CONVERSION = YES;
149 | CLANG_WARN_COMMA = YES;
150 | CLANG_WARN_CONSTANT_CONVERSION = YES;
151 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
152 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
153 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
154 | CLANG_WARN_EMPTY_BODY = YES;
155 | CLANG_WARN_ENUM_CONVERSION = YES;
156 | CLANG_WARN_INFINITE_RECURSION = YES;
157 | CLANG_WARN_INT_CONVERSION = YES;
158 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
159 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
160 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
161 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
162 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
163 | CLANG_WARN_STRICT_PROTOTYPES = YES;
164 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
165 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
166 | CLANG_WARN_UNREACHABLE_CODE = YES;
167 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
168 | CODE_SIGN_IDENTITY = "iPhone Developer";
169 | COPY_PHASE_STRIP = NO;
170 | DEBUG_INFORMATION_FORMAT = dwarf;
171 | ENABLE_BITCODE = NO;
172 | ENABLE_STRICT_OBJC_MSGSEND = YES;
173 | ENABLE_TESTABILITY = YES;
174 | GCC_C_LANGUAGE_STANDARD = gnu11;
175 | GCC_DYNAMIC_NO_PIC = NO;
176 | GCC_NO_COMMON_BLOCKS = YES;
177 | GCC_OPTIMIZATION_LEVEL = 0;
178 | GCC_PREPROCESSOR_DEFINITIONS = (
179 | "DEBUG=1",
180 | "$(inherited)",
181 | );
182 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
183 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
184 | GCC_WARN_UNDECLARED_SELECTOR = YES;
185 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
186 | GCC_WARN_UNUSED_FUNCTION = YES;
187 | GCC_WARN_UNUSED_VARIABLE = YES;
188 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
189 | MTL_ENABLE_DEBUG_INFO = YES;
190 | ONLY_ACTIVE_ARCH = NO;
191 | SDKROOT = iphoneos;
192 | };
193 | name = Debug;
194 | };
195 | 65373D5F20AA8B88008AA28A /* Release */ = {
196 | isa = XCBuildConfiguration;
197 | buildSettings = {
198 | ALWAYS_SEARCH_USER_PATHS = NO;
199 | CLANG_ANALYZER_NONNULL = YES;
200 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
201 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
202 | CLANG_CXX_LIBRARY = "libc++";
203 | CLANG_ENABLE_MODULES = YES;
204 | CLANG_ENABLE_OBJC_ARC = YES;
205 | CLANG_ENABLE_OBJC_WEAK = YES;
206 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
207 | CLANG_WARN_BOOL_CONVERSION = YES;
208 | CLANG_WARN_COMMA = YES;
209 | CLANG_WARN_CONSTANT_CONVERSION = YES;
210 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
211 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
212 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
213 | CLANG_WARN_EMPTY_BODY = YES;
214 | CLANG_WARN_ENUM_CONVERSION = YES;
215 | CLANG_WARN_INFINITE_RECURSION = YES;
216 | CLANG_WARN_INT_CONVERSION = YES;
217 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
218 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
219 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
220 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
221 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
222 | CLANG_WARN_STRICT_PROTOTYPES = YES;
223 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
224 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
225 | CLANG_WARN_UNREACHABLE_CODE = YES;
226 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
227 | CODE_SIGN_IDENTITY = "iPhone Developer";
228 | COPY_PHASE_STRIP = NO;
229 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
230 | ENABLE_BITCODE = NO;
231 | ENABLE_NS_ASSERTIONS = NO;
232 | ENABLE_STRICT_OBJC_MSGSEND = YES;
233 | GCC_C_LANGUAGE_STANDARD = gnu11;
234 | GCC_NO_COMMON_BLOCKS = YES;
235 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
236 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
237 | GCC_WARN_UNDECLARED_SELECTOR = YES;
238 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
239 | GCC_WARN_UNUSED_FUNCTION = YES;
240 | GCC_WARN_UNUSED_VARIABLE = YES;
241 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
242 | MTL_ENABLE_DEBUG_INFO = NO;
243 | SDKROOT = iphoneos;
244 | VALIDATE_PRODUCT = YES;
245 | };
246 | name = Release;
247 | };
248 | 65373D6120AA8B88008AA28A /* Debug */ = {
249 | isa = XCBuildConfiguration;
250 | buildSettings = {
251 | CODE_SIGN_STYLE = Automatic;
252 | DEVELOPMENT_TEAM = 33PDAGYH2B;
253 | ENABLE_BITCODE = NO;
254 | ONLY_ACTIVE_ARCH = NO;
255 | OTHER_LDFLAGS = "-ObjC";
256 | PRODUCT_NAME = "$(TARGET_NAME)";
257 | SKIP_INSTALL = YES;
258 | TARGETED_DEVICE_FAMILY = "1,2";
259 | };
260 | name = Debug;
261 | };
262 | 65373D6220AA8B88008AA28A /* Release */ = {
263 | isa = XCBuildConfiguration;
264 | buildSettings = {
265 | CODE_SIGN_STYLE = Automatic;
266 | DEVELOPMENT_TEAM = 33PDAGYH2B;
267 | ENABLE_BITCODE = NO;
268 | OTHER_LDFLAGS = "-ObjC";
269 | PRODUCT_NAME = "$(TARGET_NAME)";
270 | SKIP_INSTALL = YES;
271 | TARGETED_DEVICE_FAMILY = "1,2";
272 | };
273 | name = Release;
274 | };
275 | /* End XCBuildConfiguration section */
276 |
277 | /* Begin XCConfigurationList section */
278 | 65373D5220AA8B88008AA28A /* Build configuration list for PBXProject "RCTScrollRuler" */ = {
279 | isa = XCConfigurationList;
280 | buildConfigurations = (
281 | 65373D5E20AA8B88008AA28A /* Debug */,
282 | 65373D5F20AA8B88008AA28A /* Release */,
283 | );
284 | defaultConfigurationIsVisible = 0;
285 | defaultConfigurationName = Release;
286 | };
287 | 65373D6020AA8B88008AA28A /* Build configuration list for PBXNativeTarget "RCTScrollRuler" */ = {
288 | isa = XCConfigurationList;
289 | buildConfigurations = (
290 | 65373D6120AA8B88008AA28A /* Debug */,
291 | 65373D6220AA8B88008AA28A /* Release */,
292 | );
293 | defaultConfigurationIsVisible = 0;
294 | defaultConfigurationName = Release;
295 | };
296 | /* End XCConfigurationList section */
297 | };
298 | rootObject = 65373D4F20AA8B88008AA28A /* Project object */;
299 | }
300 |
--------------------------------------------------------------------------------
/ios/RCTScrollRuler/RCTScrollRuler.m:
--------------------------------------------------------------------------------
1 | //
2 | // RCTScrollRuler.m
3 | // RCTScrollRuler
4 | //
5 | // Created by shenhuniurou on 2018/5/11.
6 | // Copyright © 2018年 shenhuniurou. All rights reserved.
7 | //
8 |
9 | #define ScreenWidth ([[UIScreen mainScreen] bounds].size.width)
10 | #define ScreenHeight ([[UIScreen mainScreen] bounds].size.height)
11 |
12 | #define TextColorGrayAlpha 1.0 //文字的颜色灰度
13 | #define TextRulerFont [UIFont systemFontOfSize:11]
14 | #define RulerLineColor [UIColor grayColor]
15 |
16 | #define RulerGap 12 //单位距离
17 | #define RulerLong 40
18 | #define RulerShort 30
19 | #define TrangleWidth 16
20 | #define CollectionHeight 70
21 |
22 | #import "RCTScrollRuler.h"
23 |
24 | /**
25 | * 绘制三角形标示
26 | */
27 | @interface DYTriangleView : UIView
28 | @property(nonatomic,strong)UIColor *triangleColor;
29 |
30 | @end
31 | @implementation DYTriangleView
32 |
33 | -(void)drawRect:(CGRect)rect{
34 | //设置背景颜色
35 | [[UIColor clearColor]set];
36 |
37 | UIRectFill([self bounds]);
38 |
39 | //拿到当前视图准备好的画板
40 | CGContextRef context = UIGraphicsGetCurrentContext();
41 | CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0);//设置线的颜色,默认是黑色
42 | CGContextSetLineWidth(context, 2);//设置线的宽度,
43 | CGContextSetLineCap(context, kCGLineCapButt);
44 |
45 | //利用path路径进行绘制三角形
46 | // CGContextBeginPath(context);//标记
47 |
48 | CGContextMoveToPoint(context, rect.size.width, 0);
49 |
50 | // CGContextAddLineToPoint(context, TrangleWidth, 0);
51 | //
52 | // CGContextAddLineToPoint(context, TrangleWidth/2.0, TrangleWidth/2.0);
53 | // CGContextSetLineCap(context, kCGLineCapButt);//线结束时是否绘制端点,该属性不设置。有方形,圆形,自然结束3中设置
54 | // CGContextSetLineJoin(context, kCGLineJoinBevel);//线交叉时设置缺角。有圆角,尖角,缺角3中设置
55 | //
56 | // CGContextClosePath(context);//路径结束标志,不写默认封闭
57 | //
58 | // [_triangleColor setFill];//设置填充色
59 | // [_triangleColor setStroke];//设置边框色
60 | //
61 | // CGContextDrawPath(context, kCGPathFillStroke);//绘制路径path,后属性表示填充
62 |
63 | CGContextAddLineToPoint(context, rect.size.width, rect.size.height);
64 | CGContextStrokePath(context);//开始绘制
65 | }
66 |
67 | @end
68 |
69 |
70 | /***************DY************分************割************线***********/
71 |
72 | @interface DYRulerView : UIView
73 |
74 | @property (nonatomic,assign)NSInteger betweenNumber;
75 | @property (nonatomic,assign)int minValue;
76 | @property (nonatomic,assign)int maxValue;
77 | @property (nonatomic,assign)float step;
78 |
79 | @end
80 | @implementation DYRulerView
81 |
82 | -(void)drawRect:(CGRect)rect{
83 | CGFloat startX = 0;
84 | CGFloat lineCenterX = RulerGap;
85 | CGFloat shortLineY = rect.size.height - RulerLong;
86 | CGFloat longLineY = rect.size.height - RulerShort;
87 | CGFloat topY = 0;
88 |
89 | CGContextRef context = UIGraphicsGetCurrentContext();
90 | CGContextSetRGBStrokeColor(context, (float)192/255.0, (float)192/255.0, (float)192/255.0, 1.0);//设置线的颜色,默认是黑色
91 | CGContextSetLineWidth(context, 1);//设置线的宽度,
92 | CGContextSetLineCap(context, kCGLineCapButt);
93 |
94 | for (int i = 0; i <= _betweenNumber; i ++){
95 | CGContextMoveToPoint(context, startX+lineCenterX*i, topY);
96 | if (i%_betweenNumber == 0){
97 | NSString *num = [NSString stringWithFormat:@"%d", (int)(i * _step) + _minValue];
98 | if ([num isEqualToString:@"0"]) {
99 | num = @"不设";
100 | }
101 | NSDictionary *attribute = @{NSFontAttributeName:TextRulerFont, NSForegroundColorAttributeName:[UIColor lightGrayColor]};
102 | CGFloat width = [num boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:0 attributes:attribute context:nil].size.width;
103 | [num drawInRect:CGRectMake(startX+lineCenterX*i-width/2, longLineY+10, width, 16) withAttributes:attribute];
104 | CGContextAddLineToPoint(context, startX+lineCenterX*i, longLineY);
105 | }else{
106 | CGContextAddLineToPoint(context, startX+lineCenterX*i, shortLineY);
107 | }
108 | CGContextStrokePath(context);//开始绘制
109 | }
110 | }
111 |
112 | @end
113 |
114 |
115 | /***************DY************分************割************线***********/
116 |
117 | @interface DYHeaderRulerView : UIView
118 |
119 | @property(nonatomic,assign)int minValue;
120 |
121 | @end
122 |
123 | @implementation DYHeaderRulerView
124 |
125 | -(void)drawRect:(CGRect)rect{
126 |
127 | CGFloat longLineY = rect.size.height - RulerShort;
128 | CGContextRef context = UIGraphicsGetCurrentContext();
129 | CGContextSetRGBStrokeColor(context, (float)192/255.0, (float)192/255.0,(float)192/255.0, 1.0);
130 | CGContextSetLineWidth(context, 1.0);
131 | CGContextSetLineCap(context, kCGLineCapButt);
132 |
133 | CGContextMoveToPoint(context, rect.size.width, 0);
134 |
135 | NSString *num;
136 | if (_minValue == 0) {
137 | num = @"不设";
138 | } else {
139 | num = [NSString stringWithFormat:@"%d", _minValue];
140 | }
141 |
142 | NSDictionary *attribute = @{NSFontAttributeName:TextRulerFont,NSForegroundColorAttributeName:[UIColor lightGrayColor]};
143 | CGFloat width = [num boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:0 attributes:attribute context:nil].size.width;
144 | [num drawInRect:CGRectMake(rect.size.width-width/2, longLineY+10, width, 16) withAttributes:attribute];
145 | CGContextAddLineToPoint(context, rect.size.width, longLineY);
146 | CGContextStrokePath(context);//开始绘制
147 | }
148 |
149 | @end
150 |
151 |
152 |
153 |
154 | /***************DY************分************割************线***********/
155 | @interface DYFooterRulerView : UIView
156 |
157 | @property(nonatomic,assign)int maxValue;
158 | @end
159 | @implementation DYFooterRulerView
160 |
161 | -(void)drawRect:(CGRect)rect{
162 | CGFloat longLineY = rect.size.height - RulerShort;
163 | CGContextRef context = UIGraphicsGetCurrentContext();
164 | CGContextSetRGBStrokeColor(context, (float)192/255.0, (float)192/255.0, (float)192/255.0, 1.0);
165 | CGContextSetLineWidth(context, 1.0);
166 | CGContextSetLineCap(context, kCGLineCapButt);
167 |
168 | CGContextMoveToPoint(context, 0, 0);//起始点
169 | NSString *num = [NSString stringWithFormat:@"%d",_maxValue];
170 | NSDictionary *attribute = @{NSFontAttributeName:TextRulerFont,NSForegroundColorAttributeName:[UIColor lightGrayColor]};
171 | CGFloat width = [num boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:0 attributes:attribute context:nil].size.width;
172 | [num drawInRect:CGRectMake(0-width/2, longLineY+10, width, 16) withAttributes:attribute];
173 | CGContextAddLineToPoint(context, 0, longLineY);
174 | CGContextStrokePath(context);//开始绘制
175 | }
176 |
177 | @end
178 |
179 | /***************DY************分************割************线***********/
180 |
181 | @interface RCTScrollRuler()
182 |
183 | @property(nonatomic, strong)UILabel *valueLab;
184 | @property(nonatomic, strong)UILabel *unitLab;
185 | @property(nonatomic, strong)UICollectionView*collectionView;
186 | @property(nonatomic, strong)UIView *grayLine;
187 | @property(nonatomic, strong)DYTriangleView *triangle;
188 | @property(nonatomic, assign)int realValue;
189 | @property(nonatomic, assign)int stepNum;//分多少个区
190 | @property(nonatomic, assign)int minValue;//游标的最小值
191 | @property(nonatomic, assign)int maxValue;//游标的最大值
192 | @property(nonatomic, assign)float step;//间隔值,每两条相隔多少值
193 | @property(nonatomic, assign)NSInteger betweenNum;
194 | @property(nonatomic, strong)NSString *unit;//单位
195 | @property (nonatomic,assign)int defaultValue;
196 | @property (nonatomic,assign)int num;
197 | @end
198 | @implementation RCTScrollRuler
199 |
200 | - (void)setMinValue:(int)minValue {
201 | NSLog(@"设置最小值");
202 | [[self subviews]makeObjectsPerformSelector:@selector(removeFromSuperview)];
203 | _minValue = minValue;
204 | _stepNum = (_maxValue-_minValue)/_step/_betweenNum;
205 | _bgColor = [UIColor whiteColor];
206 | _triangleColor = [UIColor redColor];
207 | self.backgroundColor = [UIColor whiteColor];
208 |
209 | [self addSubview:self.valueLab];
210 | [self addSubview:self.unitLab];
211 | [self addSubview:self.collectionView];
212 | [self addSubview:self.triangle];
213 | [self addSubview:self.grayLine];
214 | [self setDefaultValue:_defaultValue];
215 | self.unitLab.text = _unit;
216 | }
217 |
218 | - (void)setMaxValue:(int)maxValue {
219 | NSLog(@"设置最大值");
220 | [[self subviews]makeObjectsPerformSelector:@selector(removeFromSuperview)];
221 | _maxValue = maxValue;
222 | _stepNum = (_maxValue-_minValue)/_step/_betweenNum;
223 | _bgColor = [UIColor whiteColor];
224 | _triangleColor = [UIColor redColor];
225 | self.backgroundColor = [UIColor whiteColor];
226 |
227 | [self addSubview:self.valueLab];
228 | [self addSubview:self.unitLab];
229 | [self addSubview:self.collectionView];
230 | [self addSubview:self.triangle];
231 | [self addSubview:self.grayLine];
232 | [self setDefaultValue:_defaultValue];
233 | self.unitLab.text = _unit;
234 | }
235 |
236 | - (void)setStep:(float)step {
237 | NSLog(@"设置步长");
238 | [[self subviews]makeObjectsPerformSelector:@selector(removeFromSuperview)];
239 | _step = step;
240 | _stepNum = (_maxValue-_minValue)/_step/_betweenNum;
241 | _bgColor = [UIColor whiteColor];
242 | _triangleColor = [UIColor redColor];
243 | self.backgroundColor = [UIColor whiteColor];
244 |
245 | [self addSubview:self.valueLab];
246 | [self addSubview:self.unitLab];
247 | [self addSubview:self.collectionView];
248 | [self addSubview:self.triangle];
249 | [self addSubview:self.grayLine];
250 | [self setDefaultValue:_defaultValue];
251 | self.unitLab.text = _unit;
252 | }
253 |
254 | - (void)setDefaultValue:(int)defaultValue {
255 | NSLog(@"设置默认值");
256 | _defaultValue = defaultValue;
257 | if (_maxValue != 0) {
258 | [self setRealValue:defaultValue];
259 | [_collectionView setContentOffset:CGPointMake(((defaultValue-_minValue)/(float)_step)*RulerGap, 0) animated:YES];
260 | }
261 | NSLog(@"setDefaultValue被调用了,defaultValue=%d", defaultValue);
262 | }
263 |
264 | - (void)setNum:(int)num {
265 | NSLog(@"设置间隔");
266 | [[self subviews]makeObjectsPerformSelector:@selector(removeFromSuperview)];
267 | _num = num;
268 | _stepNum = (_maxValue-_minValue)/_step/_betweenNum;
269 | _bgColor = [UIColor whiteColor];
270 | _triangleColor = [UIColor redColor];
271 | self.backgroundColor = [UIColor whiteColor];
272 |
273 | [self addSubview:self.valueLab];
274 | [self addSubview:self.unitLab];
275 | [self addSubview:self.collectionView];
276 | [self addSubview:self.triangle];
277 | [self addSubview:self.grayLine];
278 | [self setDefaultValue:_defaultValue];
279 | self.unitLab.text = _unit;
280 | }
281 |
282 | - (void)setUnit:(NSString *)unit {
283 | NSLog(@"设置单位");
284 | [[self subviews]makeObjectsPerformSelector:@selector(removeFromSuperview)];
285 | _unit = unit;
286 | _stepNum = (_maxValue-_minValue)/_step/_betweenNum;
287 | _bgColor = [UIColor whiteColor];
288 | _triangleColor = [UIColor redColor];
289 | self.backgroundColor = [UIColor whiteColor];
290 |
291 | [self addSubview:self.valueLab];
292 | [self addSubview:self.unitLab];
293 | [self addSubview:self.collectionView];
294 | [self addSubview:self.triangle];
295 | [self addSubview:self.grayLine];
296 | [self setDefaultValue:_defaultValue];
297 | self.unitLab.text = _unit;
298 | }
299 |
300 | -(instancetype)initWithFrame:(CGRect)frame theMinValue:(float)minValue theMaxValue:(float)maxValue theStep:(float)step theNum:(NSInteger)betweenNum theUnit:unit{
301 |
302 | self = [super initWithFrame:frame];
303 | if (self) {
304 | _minValue = minValue;
305 | _maxValue = maxValue;
306 | _step = step;
307 | _unit = unit;
308 | _stepNum = (_maxValue-_minValue)/_step/betweenNum;
309 | _betweenNum = betweenNum;
310 | _bgColor = [UIColor whiteColor];
311 | _triangleColor = [UIColor redColor];
312 | self.backgroundColor = [UIColor whiteColor];
313 |
314 | [self addSubview:self.valueLab];
315 | [self addSubview:self.unitLab];
316 | [self addSubview:self.collectionView];
317 | [self addSubview:self.triangle];
318 | [self addSubview:self.grayLine];
319 | self.unitLab.text = _unit;
320 | }
321 | return self;
322 | }
323 |
324 | -(UIView *)grayLine {
325 | if (!_grayLine) {
326 | _grayLine = [[UIView alloc]initWithFrame:CGRectMake(0, CGRectGetMaxY(_valueLab.frame), self.bounds.size.width, 1)];
327 | _grayLine.backgroundColor = [UIColor lightGrayColor];
328 | }
329 | return _grayLine;
330 | }
331 |
332 | -(DYTriangleView *)triangle{
333 | if (!_triangle) {
334 | // _triangle = [[DYTriangleView alloc]initWithFrame:CGRectMake(self.bounds.size.width/2-0.5-TrangleWidth/2, CGRectGetMaxY(_valueLab.frame), TrangleWidth, TrangleWidth)];
335 | // _triangle.backgroundColor = [UIColor clearColor];
336 | // _triangle.triangleColor = _triangleColor;
337 | _triangle = [[DYTriangleView alloc]initWithFrame:CGRectMake(self.bounds.size.width/2-0.5, CGRectGetMaxY(_valueLab.frame), 1, RulerLong)];
338 | _triangle.backgroundColor = [UIColor clearColor];
339 | _triangle.triangleColor = _triangleColor;
340 | }
341 | return _triangle;
342 | }
343 | -(UICollectionView *)collectionView{
344 | if (!_collectionView) {
345 | UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc]init];
346 | [flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
347 | [flowLayout setSectionInset:UIEdgeInsetsMake(0, 0, 0, 0)];
348 | _collectionView = [[UICollectionView alloc]initWithFrame:CGRectMake(0, CGRectGetMaxY(_valueLab.frame), self.bounds.size.width, CollectionHeight) collectionViewLayout:flowLayout];
349 | _collectionView.backgroundColor = _bgColor;
350 | _collectionView.bounces = YES;
351 | _collectionView.showsHorizontalScrollIndicator = NO;
352 | _collectionView.showsVerticalScrollIndicator = NO;
353 | _collectionView.dataSource = self;
354 | _collectionView.delegate = self;
355 | _collectionView.contentSize = CGSizeMake(_stepNum*_step+ScreenWidth/2, CollectionHeight);
356 |
357 | [_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"headCell"];
358 | [_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"footerCell"];
359 | [_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"custemCell"];
360 | }
361 | return _collectionView;
362 | }
363 | -(UILabel *)valueLab{
364 | if (!_valueLab) {
365 | _valueLab = [[UILabel alloc]initWithFrame:CGRectMake(self.bounds.size.width/2-70, 10, 80, 40)];
366 | _valueLab.textColor = [UIColor colorWithRed:51/255.0 green:51/255.0 blue:51/255.0 alpha:1.0];;
367 | [_valueLab setFont:[UIFont fontWithName:@"Helvetica-Bold" size:20]];
368 | _valueLab.textAlignment = NSTextAlignmentRight;
369 | }
370 | return _valueLab;
371 | }
372 |
373 | -(UILabel *)unitLab{
374 | if (!_unitLab) {
375 | _unitLab = [[UILabel alloc]initWithFrame:CGRectMake(self.bounds.size.width/2+10, 18, 40, 30)];
376 | _unitLab.textColor = [UIColor colorWithRed:51/255.0 green:51/255.0 blue:51/255.0 alpha:1.0];
377 | [_unitLab setFont:[UIFont fontWithName:@"Helvetica-Bold" size:13]];
378 | _unitLab.textAlignment = NSTextAlignmentLeft;
379 | }
380 | return _unitLab;
381 | }
382 |
383 | -(void)setBgColor:(UIColor *)bgColor{
384 | _bgColor = bgColor;
385 | _collectionView.backgroundColor = _bgColor;
386 | }
387 | -(void)setTriangleColor:(UIColor *)triangleColor{
388 | _triangleColor = triangleColor;
389 | _triangle.triangleColor = _triangleColor;
390 | }
391 |
392 | -(void)setRealValue:(int)realValue{
393 | [self setRealValue:realValue animated:NO];
394 | }
395 | -(void)setRealValue:(float)realValue animated:(BOOL)animated{
396 | _realValue = realValue;
397 | int n = _realValue*_step+_minValue;
398 | if (n == 0) {
399 | _unitLab.hidden = YES;
400 | _valueLab.text = @"不设";
401 | } else {
402 | _unitLab.hidden = NO;
403 | _valueLab.text = [NSString stringWithFormat:@"%d", n];
404 | }
405 | [_collectionView setContentOffset:CGPointMake((int)realValue*RulerGap, 0) animated:animated];
406 | }
407 |
408 | +(CGFloat)rulerViewHeight{
409 | return 40+20+CollectionHeight;
410 | }
411 |
412 | #pragma mark UICollectionViewDataSource & Delegate
413 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
414 | {
415 | return 2+_stepNum;
416 | }
417 | - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
418 | if (indexPath.item == 0){
419 | UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"headCell" forIndexPath:indexPath];
420 | DYHeaderRulerView *headerView = [cell.contentView viewWithTag:1000];
421 | if (!headerView){
422 | headerView = [[DYHeaderRulerView alloc]initWithFrame:CGRectMake(0, 0, self.frame.size.width/2, CollectionHeight)];
423 | headerView.backgroundColor = [UIColor clearColor];
424 | headerView.tag = 1000;
425 | headerView.minValue = _minValue;
426 | [cell.contentView addSubview:headerView];
427 | }
428 |
429 | return cell;
430 | }else if( indexPath.item == _stepNum +1){
431 | UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"footerCell" forIndexPath:indexPath];
432 | DYFooterRulerView *footerView = [cell.contentView viewWithTag:1001];
433 | if (!footerView){
434 | footerView = [[DYFooterRulerView alloc]initWithFrame:CGRectMake(0, 0, self.frame.size.width/2, CollectionHeight)];
435 | footerView.backgroundColor = [UIColor clearColor];
436 | footerView.tag = 1001;
437 | footerView.maxValue = _maxValue;
438 | [cell.contentView addSubview:footerView];
439 | }
440 |
441 | return cell;
442 | }else{
443 | UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"custemCell" forIndexPath:indexPath];
444 | DYRulerView *rulerView = [cell.contentView viewWithTag:1002];
445 | if (!rulerView){
446 | rulerView = [[DYRulerView alloc]initWithFrame:CGRectMake(0, 0, RulerGap*_betweenNum, CollectionHeight)];
447 | rulerView.tag = 1002;
448 | rulerView.step = _step;
449 | rulerView.betweenNumber = _betweenNum;
450 | [cell.contentView addSubview:rulerView];
451 | }
452 | // if(indexPath.item>=8 && indexPath.item<=12){
453 | // rulerView.backgroundColor = [UIColor greenColor];
454 | // }else if(indexPath.item>=13 && indexPath.item<=18){
455 | // rulerView.backgroundColor = [UIColor redColor];
456 | // }else{
457 | // rulerView.backgroundColor = [UIColor grayColor];
458 | // }
459 | rulerView.backgroundColor = [UIColor whiteColor];
460 | rulerView.minValue = (int)(_step*(indexPath.item-1)*_betweenNum+_minValue);
461 | rulerView.maxValue = (int)(_step*indexPath.item*_betweenNum);
462 | [rulerView setNeedsDisplay];
463 |
464 | return cell;
465 | }
466 | }
467 |
468 | -(CGSize )collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
469 | {
470 | if (indexPath.item == 0 || indexPath.item == _stepNum+1){
471 | return CGSizeMake(self.frame.size.width/2, CollectionHeight);
472 | }else{
473 | return CGSizeMake(RulerGap*_betweenNum, CollectionHeight);
474 | }
475 | }
476 | -(UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{
477 | return UIEdgeInsetsMake(0, 0, 0, 0);
478 | }
479 | - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section{
480 | return 0.f;
481 | }
482 | -(CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section{
483 | return 0.f;
484 | }
485 |
486 | #pragma mark -UIScrollViewDelegate
487 | -(void)scrollViewDidScroll:(UIScrollView *)scrollView{
488 | int value = scrollView.contentOffset.x/RulerGap;
489 | float totalValue = value*_step +_minValue;
490 |
491 | if (self.delegate && [self.delegate respondsToSelector:@selector(dyScrollRulerView:valueChange:)]) {
492 | [self.delegate dyScrollRulerView:self valueChange:totalValue];
493 | }
494 |
495 | if (_scrollByHand) {
496 | if (totalValue >= _maxValue) {
497 | _valueLab.text = [NSString stringWithFormat:@"%d",_maxValue];
498 | }else if(totalValue <= _minValue){
499 | if(_minValue == 0) {
500 | _valueLab.text = @"不设";
501 | } else {
502 | _valueLab.text = [NSString stringWithFormat:@"%d",_minValue];
503 | }
504 | }else{
505 | _valueLab.text = [NSString stringWithFormat:@"%d",(int)(value*_step) +_minValue];
506 | }
507 | }
508 | }
509 |
510 | -(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{//拖拽时没有滑动动画
511 | if (!decelerate){
512 | [self setRealValue:round(scrollView.contentOffset.x/(RulerGap)) animated:YES];
513 | }
514 | }
515 |
516 | -(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
517 | int value = scrollView.contentOffset.x/RulerGap;
518 | [self setRealValue:round(value) animated:YES];
519 | }
520 |
521 | @end
522 |
523 |
--------------------------------------------------------------------------------
/android/src/main/java/com/shenhuniurou/scrollruler/RNScrollRuler.java:
--------------------------------------------------------------------------------
1 | package com.shenhuniurou.scrollruler;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.animation.ValueAnimator;
6 | import android.content.Context;
7 | import android.content.res.TypedArray;
8 | import android.graphics.Canvas;
9 | import android.graphics.Paint;
10 | import android.graphics.Rect;
11 | import android.graphics.RectF;
12 | import android.support.annotation.Nullable;
13 | import android.util.AttributeSet;
14 | import android.util.TypedValue;
15 | import android.view.MotionEvent;
16 | import android.view.VelocityTracker;
17 | import android.view.View;
18 | import android.view.animation.DecelerateInterpolator;
19 |
20 | import java.lang.ref.WeakReference;
21 | import java.math.BigDecimal;
22 |
23 | /**
24 | * @author shenhuniurou
25 | * @email shenhuniurou@gmail.com
26 | * @date 2018/5/23 02:10
27 | * @description
28 | */
29 |
30 | public class RNScrollRuler extends View {
31 | private static final String TAG = "RulerView";
32 | /**
33 | * 2个大刻度之间间距,默认为1
34 | */
35 | private int scaleLimit = 10;
36 | /**
37 | * 尺子高度
38 | */
39 | private int rulerHeight = 60;
40 |
41 | /**
42 | * 尺子和屏幕顶部以及结果之间的高度
43 | */
44 | private int rulerToResultgap = rulerHeight / 4;
45 | /**
46 | * 刻度平分多少份
47 | */
48 | private int scaleCount = 10; //刻度评分多少份
49 | /**
50 | * 刻度间距
51 | */
52 | private int scaleGap = 10;
53 | /**
54 | * 刻度最小值
55 | */
56 | private int minScale = 0;
57 | /**
58 | * 第一次显示的刻度
59 | */
60 | private float firstScale = 50;
61 | /**
62 | * 刻度最大值
63 | */
64 | private int maxScale = 100;
65 |
66 | /**
67 | * kg颜色
68 | */
69 | private String unit = "cm";
70 | /**
71 | * kg颜色
72 | */
73 | private int unitColor = 0xff333333;
74 |
75 | /**
76 | * 单位字体大小
77 | */
78 | private int unitTextSize = 14;
79 |
80 | /**
81 | * 背景颜色
82 | */
83 | private int bgColor = 0xfffcfffc;
84 | /**
85 | * 小刻度的颜色
86 | */
87 | private int smallScaleColor = 0xffcccccc;
88 | /**
89 | * 中刻度的颜色
90 | */
91 | private int midScaleColor = 0xffcccccc;
92 | /**
93 | * 大刻度的颜色
94 | */
95 | private int largeScaleColor = 0xffff0000;
96 | /**
97 | * 刻度颜色
98 | */
99 | private int scaleNumColor = 0xffcccccc;
100 | /**
101 | * 结果值颜色
102 | */
103 | private int resultNumColor = 0xff333333;
104 | /**
105 | * 小刻度粗细大小
106 | */
107 | private int smallScaleStroke = 1;
108 | /**
109 | * 中刻度粗细大小
110 | */
111 | private int midScaleStroke = 1;
112 | /**
113 | * 大刻度粗细大小
114 | */
115 | private int largeScaleStroke = 1;
116 | /**
117 | * 结果字体大小
118 | */
119 | private int resultNumTextSize = 20;
120 | /**
121 | * 刻度字体大小
122 | */
123 | private int scaleNumTextSize = 14;
124 | /**
125 | * 是否显示刻度结果
126 | */
127 | private boolean showScaleResult = true;
128 | /**
129 | * 是否背景显示圆角
130 | */
131 | private boolean isBgRoundRect = true;
132 |
133 | /**
134 | * 结果回调
135 | */
136 | private OnChooseResulterListener onChooseResulterListener;
137 | /**
138 | * 滑动选择刻度
139 | */
140 | private float computeScale = -1;
141 | /**
142 | * 当前刻度
143 | */
144 | public float currentScale = firstScale;
145 |
146 | private ValueAnimator valueAnimator;
147 | private VelocityTracker velocityTracker = VelocityTracker.obtain();
148 | private String resultText = String.valueOf(firstScale);
149 | private Paint bgPaint;
150 | private Paint horzitalLinePaint;
151 | private Paint smallScalePaint;
152 | private Paint midScalePaint;
153 | private Paint lagScalePaint;
154 | private Paint scaleNumPaint;
155 | private Paint resultNumPaint;
156 | private Paint kgPaint;
157 | private Rect scaleNumRect;
158 | private Rect resultNumRect;
159 | private Rect kgRect;
160 | private RectF bgRect;
161 | private int height, width;
162 | private int smallScaleHeight;
163 | private int midScaleHeight;
164 | private int lagScaleHeight;
165 | private int rulerRight = 0;
166 | private int resultNumRight;
167 | private float downX;
168 | private float moveX = 0;
169 | private float currentX;
170 | private float lastMoveX = 0;
171 | private boolean isUp = false;
172 | private int leftScroll;
173 | private int rightScroll;
174 | private int xVelocity;
175 |
176 | public RNScrollRuler(Context context) {
177 | this(context, null);
178 | }
179 |
180 | public RNScrollRuler(Context context, @Nullable AttributeSet attrs) {
181 | this(context, attrs, 0);
182 | }
183 |
184 | public RNScrollRuler(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
185 | super(context, attrs, defStyleAttr);
186 | setAttr(attrs, defStyleAttr);
187 | init();
188 | }
189 |
190 | public void setOnChooseResulterListener(OnChooseResulterListener onChooseResulterListener) {
191 | this.onChooseResulterListener = onChooseResulterListener;
192 | }
193 |
194 | private void setAttr(AttributeSet attrs, int defStyleAttr) {
195 |
196 | TypedArray a = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.RulerView, defStyleAttr, 0);
197 |
198 | scaleLimit = a.getInt(R.styleable.RulerView_scaleLimit, scaleLimit);
199 |
200 | rulerHeight = a.getDimensionPixelSize(R.styleable.RulerView_rulerHeight, (int) TypedValue.applyDimension(
201 | TypedValue.COMPLEX_UNIT_DIP, rulerHeight, getResources().getDisplayMetrics()));
202 |
203 | rulerToResultgap = a.getDimensionPixelSize(R.styleable.RulerView_rulerToResultgap, (int) TypedValue.applyDimension(
204 | TypedValue.COMPLEX_UNIT_DIP, rulerToResultgap, getResources().getDisplayMetrics()));
205 |
206 | scaleCount = a.getInt(R.styleable.RulerView_scaleCount, scaleCount);
207 |
208 | scaleGap = a.getDimensionPixelSize(R.styleable.RulerView_scaleGap, (int) TypedValue.applyDimension(
209 | TypedValue.COMPLEX_UNIT_DIP, scaleGap, getResources().getDisplayMetrics()));
210 |
211 | minScale = a.getInt(R.styleable.RulerView_minScale, minScale) / scaleLimit;
212 |
213 | firstScale = a.getFloat(R.styleable.RulerView_firstScale, firstScale) / scaleLimit;
214 |
215 | maxScale = a.getInt(R.styleable.RulerView_maxScale, maxScale) / scaleLimit;
216 |
217 | bgColor = a.getColor(R.styleable.RulerView_bgColor, bgColor);
218 |
219 | smallScaleColor = a.getColor(R.styleable.RulerView_smallScaleColor, smallScaleColor);
220 |
221 | midScaleColor = a.getColor(R.styleable.RulerView_midScaleColor, midScaleColor);
222 |
223 | largeScaleColor = a.getColor(R.styleable.RulerView_largeScaleColor, largeScaleColor);
224 |
225 | scaleNumColor = a.getColor(R.styleable.RulerView_scaleNumColor, scaleNumColor);
226 |
227 | resultNumColor = a.getColor(R.styleable.RulerView_resultNumColor, resultNumColor);
228 |
229 | unitColor = a.getColor(R.styleable.RulerView_unitColor, unitColor);
230 |
231 | smallScaleStroke = a.getDimensionPixelSize(R.styleable.RulerView_smallScaleStroke, (int) TypedValue.applyDimension(
232 | TypedValue.COMPLEX_UNIT_DIP, smallScaleStroke, getResources().getDisplayMetrics()));
233 |
234 | midScaleStroke = a.getDimensionPixelSize(R.styleable.RulerView_midScaleStroke, (int) TypedValue.applyDimension(
235 | TypedValue.COMPLEX_UNIT_DIP, midScaleStroke, getResources().getDisplayMetrics()));
236 | largeScaleStroke = a.getDimensionPixelSize(R.styleable.RulerView_largeScaleStroke, (int) TypedValue.applyDimension(
237 | TypedValue.COMPLEX_UNIT_DIP, largeScaleStroke, getResources().getDisplayMetrics()));
238 | resultNumTextSize = a.getDimensionPixelSize(R.styleable.RulerView_resultNumTextSize, (int) TypedValue.applyDimension(
239 | TypedValue.COMPLEX_UNIT_SP, resultNumTextSize, getResources().getDisplayMetrics()));
240 |
241 | scaleNumTextSize = a.getDimensionPixelSize(R.styleable.RulerView_scaleNumTextSize, (int) TypedValue.applyDimension(
242 | TypedValue.COMPLEX_UNIT_SP, scaleNumTextSize, getResources().getDisplayMetrics()));
243 |
244 | unitTextSize = a.getDimensionPixelSize(R.styleable.RulerView_unitTextSize, (int) TypedValue.applyDimension(
245 | TypedValue.COMPLEX_UNIT_SP, unitTextSize, getResources().getDisplayMetrics()));
246 |
247 |
248 | showScaleResult = a.getBoolean(R.styleable.RulerView_showScaleResult, showScaleResult);
249 | isBgRoundRect = a.getBoolean(R.styleable.RulerView_isBgRoundRect, isBgRoundRect);
250 |
251 | a.recycle();
252 | }
253 |
254 |
255 | private void init() {
256 | bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
257 | horzitalLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
258 | smallScalePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
259 | midScalePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
260 | lagScalePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
261 | scaleNumPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
262 | resultNumPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
263 | kgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
264 |
265 | bgPaint.setColor(bgColor);
266 | horzitalLinePaint.setColor(smallScaleColor);
267 | smallScalePaint.setColor(smallScaleColor);
268 | midScalePaint.setColor(midScaleColor);
269 | lagScalePaint.setColor(largeScaleColor);
270 | scaleNumPaint.setColor(scaleNumColor);
271 | resultNumPaint.setColor(resultNumColor);
272 | resultNumPaint.setFakeBoldText(true);
273 | kgPaint.setColor(unitColor);
274 | kgPaint.setFakeBoldText(true);
275 |
276 | resultNumPaint.setStyle(Paint.Style.FILL);
277 | kgPaint.setStyle(Paint.Style.FILL);
278 | bgPaint.setStyle(Paint.Style.FILL);
279 | horzitalLinePaint.setStyle(Paint.Style.FILL);
280 | smallScalePaint.setStyle(Paint.Style.FILL);
281 | midScalePaint.setStyle(Paint.Style.FILL);
282 | lagScalePaint.setStyle(Paint.Style.FILL);
283 |
284 | // lagScalePaint.setStrokeCap(Paint.Cap.ROUND);
285 | // midScalePaint.setStrokeCap(Paint.Cap.ROUND);
286 | // smallScalePaint.setStrokeCap(Paint.Cap.ROUND);
287 |
288 | smallScalePaint.setStrokeWidth(smallScaleStroke);
289 | midScalePaint.setStrokeWidth(midScaleStroke);
290 | lagScalePaint.setStrokeWidth(largeScaleStroke);
291 | horzitalLinePaint.setStrokeWidth(largeScaleStroke);
292 |
293 | resultNumPaint.setTextSize(resultNumTextSize);
294 | kgPaint.setTextSize(unitTextSize);
295 | scaleNumPaint.setTextSize(scaleNumTextSize);
296 |
297 | bgRect = new RectF();
298 | resultNumRect = new Rect();
299 | scaleNumRect = new Rect();
300 | kgRect = new Rect();
301 |
302 | resultNumPaint.getTextBounds(resultText, 0, resultText.length(), resultNumRect);
303 | kgPaint.getTextBounds(resultText, 0, 1, kgRect);
304 |
305 | smallScaleHeight = rulerHeight / 4;
306 | midScaleHeight = rulerHeight / 2;
307 | lagScaleHeight = rulerHeight / 2 + 5;
308 | valueAnimator = new ValueAnimator();
309 |
310 | }
311 |
312 | @Override
313 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
314 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
315 | int heightModule = MeasureSpec.getMode(heightMeasureSpec);
316 | int heightSize = MeasureSpec.getSize(heightMeasureSpec);
317 | int widthSize = MeasureSpec.getSize(widthMeasureSpec);
318 |
319 | switch (heightModule) {
320 | case MeasureSpec.AT_MOST:
321 | height = rulerHeight + (showScaleResult ? resultNumRect.height() : 0) + rulerToResultgap * 2 + getPaddingTop() + getPaddingBottom();
322 | break;
323 | case MeasureSpec.UNSPECIFIED:
324 | case MeasureSpec.EXACTLY:
325 | height = heightSize + getPaddingTop() + getPaddingBottom();
326 | break;
327 | }
328 |
329 | width = widthSize + getPaddingLeft() + getPaddingRight();
330 |
331 | setMeasuredDimension(width, height);
332 |
333 | }
334 |
335 | @Override
336 | protected void onDraw(Canvas canvas) {
337 | drawBg(canvas);
338 | drawScaleAndNum(canvas);
339 | drawResultText(canvas, resultText);
340 | }
341 |
342 | @Override
343 | public boolean onTouchEvent(MotionEvent event) {
344 | currentX = event.getX();
345 | isUp = false;
346 | velocityTracker.computeCurrentVelocity(500);
347 | velocityTracker.addMovement(event);
348 | switch (event.getAction()) {
349 | case MotionEvent.ACTION_DOWN:
350 | //按下时如果属性动画还没执行完,就终止,记录下当前按下点的位置
351 | if (valueAnimator != null && valueAnimator.isRunning()) {
352 | valueAnimator.end();
353 | valueAnimator.cancel();
354 | }
355 | downX = event.getX();
356 | break;
357 | case MotionEvent.ACTION_MOVE:
358 | //滑动时候,通过假设的滑动距离,做超出左边界以及右边界的限制。
359 | moveX = currentX - downX + lastMoveX;
360 | if (moveX >= width / 2) {
361 | moveX = width / 2;
362 | } else if (moveX <= getWhichScalMovex(maxScale)) {
363 | moveX = getWhichScalMovex(maxScale);
364 | }
365 | break;
366 | case MotionEvent.ACTION_UP:
367 | //手指抬起时候制造惯性滑动
368 | lastMoveX = moveX;
369 | xVelocity = (int) velocityTracker.getXVelocity();
370 | autoVelocityScroll(xVelocity);
371 | velocityTracker.clear();
372 | break;
373 | }
374 | invalidate();
375 | return true;
376 | }
377 |
378 | private void autoVelocityScroll(int xVelocity) {
379 | //惯性滑动的代码,速率和滑动距离,以及滑动时间需要控制的很好,应该网上已经有关于这方面的算法了吧。。这里是经过N次测试调节出来的惯性滑动
380 | if (Math.abs(xVelocity) < 50) {
381 | isUp = true;
382 | return;
383 | }
384 | if (valueAnimator.isRunning()) {
385 | return;
386 | }
387 | valueAnimator = ValueAnimator.ofInt(0, xVelocity / 20).setDuration(Math.abs(xVelocity / 10));
388 | valueAnimator.setInterpolator(new DecelerateInterpolator());
389 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
390 | @Override
391 | public void onAnimationUpdate(ValueAnimator animation) {
392 | moveX += (int) animation.getAnimatedValue();
393 | if (moveX >= width / 2) {
394 | moveX = width / 2;
395 | } else if (moveX <= getWhichScalMovex(maxScale)) {
396 | moveX = getWhichScalMovex(maxScale);
397 | }
398 | lastMoveX = moveX;
399 | invalidate();
400 | }
401 |
402 | });
403 | valueAnimator.addListener(new AnimatorListenerAdapter() {
404 | @Override
405 | public void onAnimationEnd(Animator animation) {
406 | isUp = true;
407 | invalidate();
408 | }
409 | });
410 |
411 | valueAnimator.start();
412 | }
413 |
414 | private float getWhichScalMovex(float scale) {
415 | return width / 2 - scaleGap * scaleCount * (scale - minScale);
416 | }
417 |
418 | private void drawScaleAndNum(Canvas canvas) {
419 | canvas.translate(0, (showScaleResult ? resultNumRect.height() : 0) + rulerToResultgap);//移动画布到结果值的下面
420 |
421 | // 先画横线
422 | canvas.drawLine(0, 0, width, 0, horzitalLinePaint);
423 |
424 | int num1;//确定刻度位置
425 | float num2;
426 |
427 | if (firstScale != -1) { //第一次进来的时候计算出默认刻度对应的假设滑动的距离moveX
428 | moveX = getWhichScalMovex(firstScale); //如果设置了默认滑动位置,计算出需要滑动的距离
429 | lastMoveX = moveX;
430 | firstScale = -1; //将结果置为-1,下次不再计算初始位置
431 | }
432 |
433 | if (computeScale != -1) {//弹性滑动到目标刻度
434 | lastMoveX = moveX;
435 | if (valueAnimator != null && !valueAnimator.isRunning()) {
436 | valueAnimator = ValueAnimator.ofFloat(getWhichScalMovex(currentScale), getWhichScalMovex(computeScale));
437 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
438 | @Override
439 | public void onAnimationUpdate(ValueAnimator animation) {
440 | moveX = (float) animation.getAnimatedValue();
441 | lastMoveX = moveX;
442 | invalidate();
443 | }
444 | });
445 | valueAnimator.addListener(new AnimatorListenerAdapter() {
446 | @Override
447 | public void onAnimationEnd(Animator animation) {
448 | super.onAnimationEnd(animation);
449 | //这里是滑动结束时候回调给使用者的结果值
450 | computeScale = -1;
451 | }
452 | });
453 | valueAnimator.setDuration(Math.abs((long) ((getWhichScalMovex(computeScale) - getWhichScalMovex(currentScale)) / 100)));
454 | valueAnimator.start();
455 | }
456 | }
457 |
458 | num1 = -(int) (moveX / scaleGap); //滑动刻度的整数部分
459 | num2 = (moveX % scaleGap); //滑动刻度的小数部分
460 |
461 | canvas.save(); //保存当前画布
462 |
463 | rulerRight = 0; //准备开始绘制当前屏幕,从最左面开始
464 |
465 | if (isUp) { //这部分代码主要是计算手指抬起时,惯性滑动结束时,刻度需要停留的位置
466 | num2 = ((moveX - width / 2 % scaleGap) % scaleGap); //计算滑动位置距离整点刻度的小数部分距离
467 | if (num2 <= 0) {
468 | num2 = scaleGap - Math.abs(num2);
469 | }
470 | leftScroll = (int) Math.abs(num2); //当前滑动位置距离左边整点刻度的距离
471 | rightScroll = (int) (scaleGap - Math.abs(num2)); //当前滑动位置距离右边整点刻度的距离
472 |
473 | float moveX2 = num2 <= scaleGap / 2 ? moveX - leftScroll : moveX + rightScroll; //最终计算出当前位置到整点刻度位置需要滑动的距离
474 |
475 | if (valueAnimator != null && !valueAnimator.isRunning()) { //手指抬起,并且当前没有惯性滑动在进行,创建一个惯性滑动
476 | valueAnimator = ValueAnimator.ofFloat(moveX, moveX2);
477 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
478 | @Override
479 | public void onAnimationUpdate(ValueAnimator animation) {
480 | moveX = (float) animation.getAnimatedValue(); //不断滑动去更新新的位置
481 | lastMoveX = moveX;
482 | invalidate();
483 | }
484 | });
485 | valueAnimator.addListener(new AnimatorListenerAdapter() { //增加一个监听,用来返回给使用者滑动结束后的最终结果刻度值
486 | @Override
487 | public void onAnimationEnd(Animator animation) {
488 | super.onAnimationEnd(animation);
489 | //这里是滑动结束时候回调给使用者的结果值
490 | if (onChooseResulterListener != null) {
491 | onChooseResulterListener.onEndResult(resultText);
492 | }
493 | }
494 | });
495 | valueAnimator.setDuration(300);
496 | valueAnimator.start();
497 | isUp = false;
498 | }
499 |
500 | num1 = (int) -(moveX / scaleGap); //重新计算当前滑动位置的整数以及小数位置
501 | num2 = (moveX % scaleGap);
502 | }
503 |
504 |
505 | canvas.translate(num2, 0); //不加该偏移的话,滑动时刻度不会落在0~1之间只会落在整数上面,其实这个都能设置一种模式了,毕竟初衷就是指针不会落在小数上面
506 |
507 | //这里是滑动时候不断回调给使用者的结果值
508 | currentScale = new WeakReference<>(new BigDecimal(((width / 2 - moveX) / (scaleGap * scaleCount) + minScale) * scaleLimit)).get().setScale(0, BigDecimal.ROUND_HALF_UP).intValue();
509 | resultText = String.valueOf((int) currentScale);
510 |
511 | if (onChooseResulterListener != null) {
512 | onChooseResulterListener.onScrollResult(resultText); //接口不断回调给使用者结果值
513 | }
514 | //绘制当前屏幕可见刻度,不需要裁剪屏幕,while循环只会执行·屏幕宽度/刻度宽度·次,大部分的绘制都是if(curDis= 0 && rulerRight < moveX - scaleGap) || width / 2 - rulerRight <= getWhichScalMovex(maxScale + 1) - moveX) {
518 | //当滑动出范围的话,不绘制,去除左右边界
519 | } else {
520 | //绘制刻度,绘制刻度数字
521 | canvas.drawLine(0, 0, 0, midScaleHeight, midScalePaint);
522 | if (num1 == 0 && minScale == 0) {
523 | scaleNumPaint.getTextBounds("不设", 0, "不设".length(), scaleNumRect);
524 | canvas.drawText("不设", -scaleNumRect.width() / 2, lagScaleHeight +
525 | (rulerHeight - lagScaleHeight) / 2 + scaleNumRect.height(), scaleNumPaint);
526 | } else {
527 | scaleNumPaint.getTextBounds(num1 / scaleGap + minScale + "", 0, (num1 / scaleGap + minScale + "").length(), scaleNumRect);
528 | canvas.drawText((num1 / scaleCount + minScale) * scaleLimit + "", -scaleNumRect.width() / 2, lagScaleHeight +
529 | (rulerHeight - lagScaleHeight) / 2 + scaleNumRect.height(), scaleNumPaint);
530 | }
531 |
532 | }
533 |
534 | } else { //绘制小数刻度
535 | if ((moveX >= 0 && rulerRight < moveX) || width / 2 - rulerRight < getWhichScalMovex(maxScale) - moveX) {
536 | //当滑动出范围的话,不绘制,去除左右边界
537 | } else {
538 | //绘制小数刻度
539 | canvas.drawLine(0, 0, 0, smallScaleHeight, smallScalePaint);
540 | }
541 | }
542 | ++num1; //刻度加1
543 | rulerRight += scaleGap; //绘制屏幕的距离在原有基础上+1个刻度间距
544 | canvas.translate(scaleGap, 0); //移动画布到下一个刻度
545 | }
546 |
547 | canvas.restore();
548 | //绘制屏幕中间用来选中刻度的最大刻度
549 | canvas.drawLine(width / 2, 0, width / 2, lagScaleHeight, lagScalePaint);
550 |
551 | }
552 |
553 | //绘制上面的结果 结果值+单位
554 | private void drawResultText(Canvas canvas, String resultText) {
555 | if (!showScaleResult) { //判断用户是否设置需要显示当前刻度值,如果否则取消绘制
556 | return;
557 | }
558 | canvas.translate(0, -resultNumRect.height() - rulerToResultgap / 2); //移动画布到正确的位置来绘制结果值
559 | if (resultText.equals("0")) {
560 | resultText = "不设";
561 | resultNumPaint.setTextSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
562 | resultNumPaint.getTextBounds(resultText, 0, resultText.length(), resultNumRect);
563 | canvas.drawText(resultText, width / 2 - resultNumRect.width() / 2, resultNumRect.height(), //绘制当前刻度结果值
564 | resultNumPaint);
565 | } else {
566 | resultNumPaint.setTextSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics()));
567 | resultNumPaint.getTextBounds(resultText, 0, resultText.length(), resultNumRect);
568 | canvas.drawText(resultText, width / 2 - resultNumRect.width() / 2, resultNumRect.height(), //绘制当前刻度结果值
569 | resultNumPaint);
570 | resultNumRight = width / 2 + resultNumRect.width() / 2 + 5;
571 | canvas.drawText(unit, resultNumRight, kgRect.height() + 8, kgPaint); //在当前刻度结果值的又面10px的位置绘制单位
572 | }
573 | }
574 |
575 | private void drawBg(Canvas canvas) {
576 | bgRect.set(0, 0, width, height);
577 | if (isBgRoundRect) {
578 | canvas.drawRoundRect(bgRect, 20, 20, bgPaint); //20->椭圆的用于圆形角x-radius
579 | } else {
580 | canvas.drawRect(bgRect, bgPaint);
581 | }
582 | }
583 |
584 | public void computeScrollTo(float scale) {
585 | scale = scale / scaleLimit;
586 | if (scale < minScale || scale > maxScale) {
587 | return;
588 | }
589 |
590 | computeScale = scale;
591 | invalidate();
592 |
593 | }
594 |
595 | public interface OnChooseResulterListener {
596 | void onEndResult(String result);
597 |
598 | void onScrollResult(String result);
599 | }
600 |
601 | public void setRulerHeight(int rulerHeight) {
602 | this.rulerHeight = rulerHeight;
603 | invalidate();
604 | }
605 |
606 | public void setRulerToResultgap(int rulerToResultgap) {
607 | this.rulerToResultgap = rulerToResultgap;
608 | invalidate();
609 | }
610 |
611 | public void setScaleCount(int scaleCount) {
612 | this.scaleCount = scaleCount;
613 | invalidate();
614 | }
615 |
616 | public void setScaleGap(int scaleGap) {
617 | this.scaleGap = scaleGap;
618 | invalidate();
619 | }
620 |
621 | public void setMinScale(int minScale) {
622 | this.minScale = minScale / scaleLimit;
623 | invalidate();
624 | }
625 |
626 | public void setFirstScale(float firstScale) {
627 | this.firstScale = firstScale / scaleLimit;
628 | invalidate();
629 | }
630 |
631 | public void setMaxScale(int maxScale) {
632 | this.maxScale = maxScale / scaleLimit;
633 | invalidate();
634 | }
635 |
636 | public void setBgColor(int bgColor) {
637 | this.bgColor = bgColor;
638 | invalidate();
639 | }
640 |
641 | public void setSmallScaleColor(int smallScaleColor) {
642 | this.smallScaleColor = smallScaleColor;
643 | invalidate();
644 | }
645 |
646 | public void setMidScaleColor(int midScaleColor) {
647 | this.midScaleColor = midScaleColor;
648 | invalidate();
649 | }
650 |
651 | public void setLargeScaleColor(int largeScaleColor) {
652 | this.largeScaleColor = largeScaleColor;
653 | }
654 |
655 | public void setScaleNumColor(int scaleNumColor) {
656 | this.scaleNumColor = scaleNumColor;
657 | invalidate();
658 | }
659 |
660 | public void setResultNumColor(int resultNumColor) {
661 | this.resultNumColor = resultNumColor;
662 | invalidate();
663 | }
664 |
665 | public void setSmallScaleStroke(int smallScaleStroke) {
666 | this.smallScaleStroke = smallScaleStroke;
667 | invalidate();
668 | }
669 |
670 | public void setMidScaleStroke(int midScaleStroke) {
671 | this.midScaleStroke = midScaleStroke;
672 | invalidate();
673 | }
674 |
675 | public void setLargeScaleStroke(int largeScaleStroke) {
676 | this.largeScaleStroke = largeScaleStroke;
677 | invalidate();
678 | }
679 |
680 | public void setResultNumTextSize(int resultNumTextSize) {
681 | this.resultNumTextSize = resultNumTextSize;
682 | invalidate();
683 | }
684 |
685 | public void setScaleNumTextSize(int scaleNumTextSize) {
686 | this.scaleNumTextSize = scaleNumTextSize;
687 | invalidate();
688 | }
689 |
690 | public void setShowScaleResult(boolean showScaleResult) {
691 | this.showScaleResult = showScaleResult;
692 | invalidate();
693 | }
694 |
695 | public void setIsBgRoundRect(boolean bgRoundRect) {
696 | isBgRoundRect = bgRoundRect;
697 | invalidate();
698 | }
699 |
700 | public void setScaleLimit(int scaleLimit) {
701 | this.scaleLimit = scaleLimit;
702 | invalidate();
703 | }
704 |
705 | public void setUnit(String unit) {
706 | this.unit = unit;
707 | invalidate();
708 | }
709 |
710 | public void setUnitColor(int unitColor) {
711 | this.unitColor = unitColor;
712 | invalidate();
713 | }
714 |
715 | }
716 |
--------------------------------------------------------------------------------