├── .gitignore ├── AndroidSegmented.js ├── README.md ├── art ├── segment_1.png └── segment_2.png ├── build.gradle ├── package.json ├── proguard-rules.pro ├── react-native-segmented-android.iml └── src └── main ├── AndroidManifest.xml ├── java └── com │ └── higo │ └── zhangyp │ └── segmented │ ├── AndroidSegmented.java │ ├── AndroidSegmentedEvent.java │ ├── AndroidSegmentedManager.java │ └── AndroidSegmentedPackage.java └── res ├── layout └── radio_button.xml └── values └── strings.xml /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /AndroidSegmented.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react-native'); 4 | var { requireNativeComponent, PropTypes, View } = React; 5 | 6 | var NativeAndroidSegmented = requireNativeComponent('AndroidSegmented', AndroidSegmented); 7 | 8 | class AndroidSegmented extends React.Component { 9 | constructor() { 10 | super(); 11 | this._onChange = this._onChange.bind(this); 12 | } 13 | 14 | _onChange(event) { 15 | if (this.props.onChange) { 16 | this.props.onChange(event.nativeEvent); 17 | } 18 | } 19 | 20 | render() { 21 | return ( 22 | 25 | ); 26 | } 27 | } 28 | 29 | var colorType = function (props, propName, componentName) { 30 | var checker = function() { 31 | var color = props[propName]; 32 | var regex = /^#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/; 33 | if (!regex.test(color)) { 34 | return new Error('Only accept color formats: #RRGGBB and #AARRGGBB'); 35 | } 36 | }; 37 | 38 | return PropTypes.string(props, propName, componentName) || checker(); 39 | } 40 | 41 | AndroidSegmented.propTypes = { 42 | ...View.propTypes, 43 | childText: PropTypes.arrayOf(PropTypes.oneOfType([ PropTypes.string ])), 44 | orientation:PropTypes.string, 45 | tintColor:PropTypes.arrayOf(PropTypes.oneOfType([ PropTypes.string ])), 46 | selectedPosition:PropTypes.number, 47 | onChange: PropTypes.func, 48 | } 49 | 50 | AndroidSegmented.defaultProps = { 51 | 52 | }; 53 | 54 | module.exports = AndroidSegmented; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-segmented-android 2 | a high imitation of iOS segmented Controls 3 | 4 | ![look segmented android example](./art/segment_2.png) 5 | 6 | ## Example 7 | 8 | ```js 9 | 'use strict'; 10 | 11 | var React = require('react-native'); 12 | var { 13 | AppRegistry, 14 | StyleSheet, 15 | Text, 16 | Dimensions, 17 | ToastAndroid, 18 | View, 19 | } = React; 20 | 21 | var AndroidSegmented = require('react-native-segmented-android'); 22 | var deviceWidth = Dimensions.get('window').width; 23 | var deviceHeight = Dimensions.get('window').height; 24 | 25 | var ReactNativeSegmentedExample = React.createClass({ 26 | onSelectPosition:function(event){ 27 | console.log(event); 28 | ToastAndroid.show('segment '+event.selected, ToastAndroid.SHORT) 29 | }, 30 | render: function() { 31 | return ( 32 | 33 | 42 | 43 | 52 | 53 | ); 54 | } 55 | }); 56 | 57 | ``` 58 | 59 | ## Install 60 | 61 | ### Step 1 - Install the npm package 62 | 63 | ```sh 64 | $ npm install react-native-segmented-android --save 65 | ``` 66 | 67 | ### Step 2 - Update Gradle Settings 68 | 69 | ```gradle 70 | // file: android/settings.gradle 71 | ... 72 | 73 | include ':react-native-segmented-android', ':app' 74 | project(':react-native-segmented-android').projectDir = new File(rootProject.projectDir,'../node_modules/react-native-segmented-android') 75 | ``` 76 | 77 | ### Step 3 - Update app Gradle Build 78 | 79 | ```gradle 80 | // file: android/app/build.gradle 81 | ... 82 | 83 | dependencies { 84 | ... 85 | compile project(':react-native-segmented-android') 86 | } 87 | ``` 88 | 89 | ### Step 4 - Register React Package 90 | 91 | ```java 92 | ... 93 | import com.higo.zhangyp.segmented.AndroidSegmentedPackage; // <-- import 94 | 95 | public class MainActivity extends FragmentActivity implements DefaultHardwareBackBtnHandler { 96 | 97 | private ReactInstanceManager mReactInstanceManager; 98 | private ReactRootView mReactRootView; 99 | 100 | @Override 101 | protected void onCreate(Bundle savedInstanceState) { 102 | super.onCreate(savedInstanceState); 103 | mReactRootView = new ReactRootView(this); 104 | mReactInstanceManager = ReactInstanceManager.builder() 105 | .setApplication(getApplication()) 106 | .setBundleAssetName("index.android.bundle") 107 | .setJSMainModuleName("index.android") 108 | .addPackage(new MainReactPackage()) 109 | .addPackage(new AndroidSegmentedPackage()) // <-- Register package here 110 | .setUseDeveloperSupport(BuildConfig.DEBUG) 111 | .setInitialLifecycleState(LifecycleState.RESUMED) 112 | .build(); 113 | mReactRootView.startReactApplication(mReactInstanceManager, "AwesomeProject", null); 114 | setContentView(mReactRootView); 115 | } 116 | ... 117 | -------------------------------------------------------------------------------- /art/segment_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzyyppqq/react-native-segmented-android/e633ee6577fdaf33538809ac6c597fb2c7c64d7a/art/segment_1.png -------------------------------------------------------------------------------- /art/segment_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzyyppqq/react-native-segmented-android/e633ee6577fdaf33538809ac6c597fb2c7c64d7a/art/segment_2.png -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.1" 6 | 7 | defaultConfig { 8 | minSdkVersion 16 9 | targetSdkVersion 23 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(dir: 'libs', include: ['*.jar']) 23 | compile 'com.android.support:appcompat-v7:23.1.1' 24 | compile 'info.hoang8f:android-segmented:1.0.6' 25 | compile 'com.facebook.react:react-native:0.19.+' 26 | } 27 | //react-native run-android 28 | //adb reverse tcp:8081 tcp:8081 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-segmented-android", 3 | "version": "1.0.4", 4 | "description": "a high imitation of iOS segmented Controls", 5 | "main": "AndroidSegmented.js", 6 | "scripts": { 7 | "test": "react-native start" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/zzyyppqq/react-native-segmented-android.git" 12 | }, 13 | "keywords": [ 14 | "android", 15 | "segmented", 16 | "react-component", 17 | "react-native" 18 | ], 19 | "author": "zzyyppqq", 20 | "license": "ISC", 21 | "peerDependencies": { 22 | "react-native": ">= 0.18.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/zhangyipeng/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /react-native-segmented-android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/com/higo/zhangyp/segmented/AndroidSegmented.java: -------------------------------------------------------------------------------- 1 | package com.higo.zhangyp.segmented; 2 | 3 | import android.view.Gravity; 4 | import android.view.ViewGroup; 5 | import android.widget.RadioGroup; 6 | 7 | import com.facebook.react.uimanager.ThemedReactContext; 8 | 9 | import info.hoang8f.android.segmented.SegmentedGroup; 10 | 11 | /** 12 | * Created by zhangyipeng on 15/12/15. 13 | */ 14 | public class AndroidSegmented extends SegmentedGroup{ 15 | 16 | 17 | public void setSegmentOrientation(String str){ 18 | if(str.equals("horizontal")){ 19 | setOrientation(RadioGroup.HORIZONTAL); 20 | }else if(str.equals("vertical")){ 21 | setOrientation(RadioGroup.VERTICAL); 22 | } 23 | } 24 | 25 | 26 | public AndroidSegmented(ThemedReactContext context) { 27 | super(context); 28 | setGravity(Gravity.CENTER); 29 | 30 | setLayoutParams(new ViewGroup.LayoutParams( 31 | ViewGroup.LayoutParams.WRAP_CONTENT, 32 | ViewGroup.LayoutParams.WRAP_CONTENT 33 | )); 34 | } 35 | 36 | private final Runnable mLayoutRunnable = new Runnable() { 37 | @Override 38 | public void run() { 39 | measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY), 40 | MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY)); 41 | layout(getLeft(), getTop(), getRight(), getBottom()); 42 | } 43 | }; 44 | 45 | @Override 46 | public void requestLayout() { 47 | super.requestLayout(); 48 | post(mLayoutRunnable); 49 | } 50 | 51 | @Override 52 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 53 | super.onLayout(changed, l, t, r, b); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/higo/zhangyp/segmented/AndroidSegmentedEvent.java: -------------------------------------------------------------------------------- 1 | package com.higo.zhangyp.segmented; 2 | 3 | import android.util.Log; 4 | 5 | import com.facebook.react.bridge.Arguments; 6 | import com.facebook.react.bridge.WritableMap; 7 | import com.facebook.react.uimanager.events.Event; 8 | import com.facebook.react.uimanager.events.RCTEventEmitter; 9 | 10 | /** 11 | * Created by zhangyipeng on 15/12/15. 12 | */ 13 | public class AndroidSegmentedEvent extends Event { 14 | 15 | public static final String EVENT_NAME = "topChange"; 16 | private final int selectedPosition; 17 | 18 | public AndroidSegmentedEvent(int viewId, long timestampMs, int selectedPosition) { 19 | super(viewId, timestampMs); 20 | this.selectedPosition = selectedPosition; 21 | } 22 | 23 | 24 | @Override 25 | public String getEventName() { 26 | return EVENT_NAME; 27 | } 28 | 29 | @Override 30 | public void dispatch(RCTEventEmitter rctEventEmitter) { 31 | rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData()); 32 | 33 | } 34 | 35 | 36 | @Override 37 | public short getCoalescingKey() { 38 | return 0; 39 | } 40 | 41 | private WritableMap serializeEventData() { 42 | WritableMap eventData = Arguments.createMap(); 43 | // eventData.putInt("target", getViewTag()); 44 | eventData.putInt("selected", getPosition()); 45 | Log.e("AAA","position="+getPosition()); 46 | 47 | return eventData; 48 | } 49 | 50 | private int getPosition() { 51 | return selectedPosition; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/higo/zhangyp/segmented/AndroidSegmentedManager.java: -------------------------------------------------------------------------------- 1 | package com.higo.zhangyp.segmented; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.os.SystemClock; 6 | import android.view.LayoutInflater; 7 | import android.widget.RadioButton; 8 | import android.widget.RadioGroup; 9 | 10 | import com.facebook.react.bridge.JSApplicationIllegalArgumentException; 11 | import com.facebook.react.bridge.ReadableArray; 12 | import com.facebook.react.uimanager.annotations.ReactProp; 13 | import com.facebook.react.uimanager.SimpleViewManager; 14 | import com.facebook.react.uimanager.ThemedReactContext; 15 | import com.facebook.react.uimanager.UIManagerModule; 16 | 17 | /** 18 | * Created by zhangyipeng on 15/12/15. 19 | */ 20 | public class AndroidSegmentedManager extends SimpleViewManager { 21 | 22 | public static final String REACT_CLASS = "AndroidSegmented"; 23 | 24 | private static final String COLOR_REGEX = "^#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$"; 25 | 26 | private Context context; 27 | 28 | @Override 29 | public String getName() { 30 | return REACT_CLASS; 31 | } 32 | 33 | @Override 34 | protected AndroidSegmented createViewInstance(ThemedReactContext reactContext) { 35 | this.context = reactContext; 36 | return new AndroidSegmented(reactContext); 37 | } 38 | 39 | 40 | @Override 41 | protected void addEventEmitters(final ThemedReactContext reactContext, final AndroidSegmented view) { 42 | 43 | view.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { 44 | @Override 45 | public void onCheckedChanged(RadioGroup group, int checkedId) { 46 | 47 | int childCount = view.getChildCount(); 48 | for (int i = 0; i < childCount; i++) { 49 | ((RadioButton)view.getChildAt(i)).setChecked(false); 50 | if (view.getChildAt(i).getId() == checkedId) { 51 | ((RadioButton)view.getChildAt(i)).setChecked(true); 52 | 53 | 54 | reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher() 55 | .dispatchEvent( 56 | new AndroidSegmentedEvent( 57 | view.getId(), 58 | SystemClock.uptimeMillis(), 59 | i)); 60 | } 61 | } 62 | } 63 | }); 64 | 65 | 66 | } 67 | 68 | 69 | @ReactProp(name = "childText") 70 | public void setChildText(AndroidSegmented view, ReadableArray data) { 71 | int childCount = data.size(); 72 | 73 | for (int i = 0; i < childCount; ++i) { 74 | RadioButton child = (RadioButton) LayoutInflater.from(context).inflate(R.layout.radio_button, null); 75 | 76 | child.setText(data.getString(i)); 77 | view.addView(child); 78 | 79 | 80 | } 81 | } 82 | 83 | 84 | @ReactProp(name = "selectedPosition") 85 | public void setSelectedChild(AndroidSegmented view, int position) { 86 | RadioButton radioBt = (RadioButton) (view.getChildAt(position)); 87 | radioBt.setChecked(true); 88 | } 89 | 90 | 91 | @ReactProp(name = "orientation") 92 | public void setOrientation(AndroidSegmented view, String orientation) { 93 | view.setSegmentOrientation(orientation); 94 | } 95 | 96 | 97 | @ReactProp(name = "tintColor") 98 | public void setTintColor(AndroidSegmented view, ReadableArray data) { 99 | 100 | String type0 = data.getType(0).name(); 101 | String type1 = data.getType(1).name(); 102 | 103 | if ("String".equals(type0) && "String".equals(type1)) { 104 | String color0 = data.getString(0); 105 | String color1 = data.getString(1); 106 | if (color0 != null && color1 != null) { 107 | if (color0.matches(COLOR_REGEX) && color1.matches(COLOR_REGEX)) { 108 | 109 | view.setTintColor(Color.parseColor(color0), Color.parseColor(color1)); 110 | } else { 111 | throw new JSApplicationIllegalArgumentException("Invalid arrowColor property: " + color0); 112 | } 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/com/higo/zhangyp/segmented/AndroidSegmentedPackage.java: -------------------------------------------------------------------------------- 1 | package com.higo.zhangyp.segmented; 2 | 3 | 4 | import com.facebook.react.ReactPackage; 5 | import com.facebook.react.bridge.JavaScriptModule; 6 | import com.facebook.react.bridge.NativeModule; 7 | import com.facebook.react.bridge.ReactApplicationContext; 8 | import com.facebook.react.uimanager.ViewManager; 9 | 10 | import java.util.Arrays; 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | 15 | public class AndroidSegmentedPackage implements ReactPackage { 16 | @Override 17 | public List createNativeModules(ReactApplicationContext reactApplicationContext) { 18 | return Collections.emptyList(); 19 | } 20 | 21 | @Override 22 | public List createViewManagers(ReactApplicationContext reactApplicationContext) { 23 | return Arrays.asList( 24 | new AndroidSegmentedManager()); 25 | } 26 | 27 | @Override 28 | public List> createJSModules() { 29 | return Collections.emptyList(); 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/main/res/layout/radio_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | react-native-segmented-android 3 | 4 | --------------------------------------------------------------------------------