├── 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 | ![](https://github.com/shenhuniurou/react-native-scroll-ruler/blob/master/scroll-ruler-ios.gif) 6 | ![](https://github.com/shenhuniurou/react-native-scroll-ruler/blob/master/scroll-ruler-android.gif) 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 | --------------------------------------------------------------------------------