mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-08 09:37:21 +08:00
Open source Android slider
Reviewed By: bestander Differential Revision: D3127200 fb-gh-sync-id: d3d51b312c2e32cc7a0f4c0bc084139343e97c3e fbshipit-source-id: d3d51b312c2e32cc7a0f4c0bc084139343e97c3e
This commit is contained in:
committed by
Facebook Github Bot 4
parent
29a1a05cbb
commit
a461d25601
@@ -17,6 +17,7 @@ android_library(
|
||||
react_native_target('java/com/facebook/react/views/progressbar:progressbar'),
|
||||
react_native_target('java/com/facebook/react/views/recyclerview:recyclerview'),
|
||||
react_native_target('java/com/facebook/react/views/scroll:scroll'),
|
||||
react_native_target('java/com/facebook/react/views/slider:slider'),
|
||||
react_native_target('java/com/facebook/react/views/swiperefresh:swiperefresh'),
|
||||
react_native_target('java/com/facebook/react/views/switchview:switchview'),
|
||||
react_native_target('java/com/facebook/react/views/text:text'),
|
||||
|
||||
@@ -47,6 +47,7 @@ import com.facebook.react.views.progressbar.ReactProgressBarViewManager;
|
||||
import com.facebook.react.views.recyclerview.RecyclerViewBackedScrollViewManager;
|
||||
import com.facebook.react.views.scroll.ReactHorizontalScrollViewManager;
|
||||
import com.facebook.react.views.scroll.ReactScrollViewManager;
|
||||
import com.facebook.react.views.slider.ReactSliderManager;
|
||||
import com.facebook.react.views.swiperefresh.SwipeRefreshLayoutManager;
|
||||
import com.facebook.react.views.switchview.ReactSwitchManager;
|
||||
import com.facebook.react.views.text.ReactRawTextManager;
|
||||
@@ -109,6 +110,7 @@ public class MainReactPackage implements ReactPackage {
|
||||
new ReactProgressBarViewManager(),
|
||||
new ReactRawTextManager(),
|
||||
new ReactScrollViewManager(),
|
||||
new ReactSliderManager(),
|
||||
new ReactSwitchManager(),
|
||||
new FrescoBasedReactTextInlineImageViewManager(),
|
||||
new ReactTextInputManager(),
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
include_defs('//ReactAndroid/DEFS')
|
||||
|
||||
android_library(
|
||||
name = 'slider',
|
||||
srcs = glob(['*.java']),
|
||||
deps = [
|
||||
react_native_target('java/com/facebook/react/bridge:bridge'),
|
||||
react_native_target('java/com/facebook/react/common:common'),
|
||||
react_native_target('java/com/facebook/csslayout:csslayout'),
|
||||
react_native_target('java/com/facebook/react/uimanager:uimanager'),
|
||||
react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
|
||||
react_native_dep('android_res/android/support/v7/appcompat-orig:res-for-react-native'),
|
||||
react_native_dep('third-party/android/support/v7/appcompat-orig:appcompat'),
|
||||
react_native_dep('third-party/java/jsr-305:jsr-305'),
|
||||
],
|
||||
visibility = [
|
||||
'PUBLIC',
|
||||
],
|
||||
)
|
||||
|
||||
project_config(
|
||||
src_target = ':slider',
|
||||
)
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.slider;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Slider that behaves more like the iOS one, for consistency.
|
||||
*
|
||||
* On iOS, the value is 0..1. Android SeekBar only supports integer values.
|
||||
* For consistency, we pretend in JS that the value is 0..1 but set the
|
||||
* SeekBar value to 0..100.
|
||||
*
|
||||
* Note that the slider is _not_ a controlled component (setValue isn't called
|
||||
* during dragging).
|
||||
*/
|
||||
public class ReactSlider extends SeekBar {
|
||||
|
||||
/**
|
||||
* If step is 0 (unset) we default to this total number of steps.
|
||||
* Don't use 100 which leads to rounding errors (0.200000000001).
|
||||
*/
|
||||
private static int DEFAULT_TOTAL_STEPS = 128;
|
||||
|
||||
/**
|
||||
* We want custom min..max range.
|
||||
* Android only supports 0..max range so we implement this ourselves.
|
||||
*/
|
||||
private double mMinValue = 0;
|
||||
private double mMaxValue = 0;
|
||||
|
||||
/**
|
||||
* Value sent from JS (setState).
|
||||
* Doesn't get updated during drag (slider is not a controlled component).
|
||||
*/
|
||||
private double mValue = 0;
|
||||
|
||||
/**
|
||||
* If zero it's determined automatically.
|
||||
*/
|
||||
private double mStep = 0;
|
||||
|
||||
public ReactSlider(Context context, @Nullable AttributeSet attrs, int style) {
|
||||
super(context, attrs, style);
|
||||
}
|
||||
|
||||
/* package */ void setMaxValue(double max) {
|
||||
mMaxValue = max;
|
||||
updateAll();
|
||||
}
|
||||
|
||||
/* package */ void setMinValue(double min) {
|
||||
mMinValue = min;
|
||||
updateAll();
|
||||
}
|
||||
|
||||
/* package */ void setValue(double value) {
|
||||
mValue = value;
|
||||
updateValue();
|
||||
}
|
||||
|
||||
/* package */ void setStep(double step) {
|
||||
mStep = step;
|
||||
updateAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert SeekBar's native progress value (e.g. 0..100) to a value
|
||||
* passed to JS (e.g. -1.0..2.5).
|
||||
*/
|
||||
public double toRealProgress(int seekBarProgress) {
|
||||
if (seekBarProgress == getMax()) {
|
||||
return mMaxValue;
|
||||
}
|
||||
return seekBarProgress * mStep + mMinValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update underlying native SeekBar's values.
|
||||
*/
|
||||
private void updateAll() {
|
||||
if (mStep == 0) {
|
||||
mStep = (mMaxValue - mMinValue) / (double) DEFAULT_TOTAL_STEPS;
|
||||
}
|
||||
setMax(getTotalSteps());
|
||||
updateValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update value only (optimization in case only value is set).
|
||||
*/
|
||||
private void updateValue() {
|
||||
setProgress((int) Math.round(
|
||||
(mValue - mMinValue) / (mMaxValue - mMinValue) * getTotalSteps()));
|
||||
}
|
||||
|
||||
private int getTotalSteps() {
|
||||
return (int) Math.ceil((mMaxValue - mMinValue) / mStep);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.slider;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
|
||||
/**
|
||||
* Event emitted by a ReactSliderManager when user changes slider position.
|
||||
*/
|
||||
public class ReactSliderEvent extends Event<ReactSliderEvent> {
|
||||
|
||||
public static final String EVENT_NAME = "topChange";
|
||||
|
||||
private final double mValue;
|
||||
private final boolean mFromUser;
|
||||
|
||||
public ReactSliderEvent(int viewId, long timestampMs, double value, boolean fromUser) {
|
||||
super(viewId, timestampMs);
|
||||
mValue = value;
|
||||
mFromUser = fromUser;
|
||||
}
|
||||
|
||||
public double getValue() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
public boolean isFromUser() {
|
||||
return mFromUser;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return EVENT_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getCoalescingKey() {
|
||||
return 0;
|
||||
}
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||
}
|
||||
|
||||
private WritableMap serializeEventData() {
|
||||
WritableMap eventData = Arguments.createMap();
|
||||
eventData.putInt("target", getViewTag());
|
||||
eventData.putDouble("value", getValue());
|
||||
eventData.putBoolean("fromUser", isFromUser());
|
||||
return eventData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.slider;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
import com.facebook.csslayout.CSSNode;
|
||||
import com.facebook.csslayout.MeasureOutput;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.common.SystemClock;
|
||||
import com.facebook.react.uimanager.LayoutShadowNode;
|
||||
import com.facebook.react.uimanager.SimpleViewManager;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.facebook.react.uimanager.ViewProps;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
|
||||
/**
|
||||
* Manages instances of {@code ReactSlider}.
|
||||
*
|
||||
* Note that the slider is _not_ a controlled component.
|
||||
*/
|
||||
public class ReactSliderManager extends SimpleViewManager<ReactSlider> {
|
||||
|
||||
private static final int STYLE = android.R.attr.seekBarStyle;
|
||||
|
||||
private static final String REACT_CLASS = "RCTSlider";
|
||||
|
||||
static class ReactSliderShadowNode extends LayoutShadowNode implements
|
||||
CSSNode.MeasureFunction {
|
||||
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
private boolean mMeasured;
|
||||
|
||||
private ReactSliderShadowNode() {
|
||||
setMeasureFunction(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void measure(CSSNode node, float width, float height, MeasureOutput measureOutput) {
|
||||
if (!mMeasured) {
|
||||
SeekBar reactSlider = new ReactSlider(getThemedContext(), null, STYLE);
|
||||
final int spec = View.MeasureSpec.makeMeasureSpec(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
View.MeasureSpec.UNSPECIFIED);
|
||||
reactSlider.measure(spec, spec);
|
||||
mWidth = reactSlider.getMeasuredWidth();
|
||||
mHeight = reactSlider.getMeasuredHeight();
|
||||
mMeasured = true;
|
||||
}
|
||||
measureOutput.width = mWidth;
|
||||
measureOutput.height = mHeight;
|
||||
}
|
||||
}
|
||||
|
||||
private static final SeekBar.OnSeekBarChangeListener ON_CHANGE_LISTENER =
|
||||
new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) {
|
||||
ReactContext reactContext = (ReactContext) seekbar.getContext();
|
||||
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(
|
||||
new ReactSliderEvent(
|
||||
seekbar.getId(),
|
||||
SystemClock.nanoTime(),
|
||||
((ReactSlider)seekbar).toRealProgress(progress),
|
||||
fromUser));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekbar) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekbar) {
|
||||
ReactContext reactContext = (ReactContext) seekbar.getContext();
|
||||
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(
|
||||
new ReactSlidingCompleteEvent(
|
||||
seekbar.getId(),
|
||||
SystemClock.nanoTime(),
|
||||
((ReactSlider)seekbar).toRealProgress(seekbar.getProgress())));
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LayoutShadowNode createShadowNodeInstance() {
|
||||
return new ReactSliderShadowNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class getShadowNodeClass() {
|
||||
return ReactSliderShadowNode.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ReactSlider createViewInstance(ThemedReactContext context) {
|
||||
return new ReactSlider(context, null, STYLE);
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.ENABLED, defaultBoolean = true)
|
||||
public void setEnabled(ReactSlider view, boolean enabled) {
|
||||
view.setEnabled(enabled);
|
||||
}
|
||||
|
||||
@ReactProp(name = "value", defaultDouble = 0d)
|
||||
public void setValue(ReactSlider view, double value) {
|
||||
view.setOnSeekBarChangeListener(null);
|
||||
view.setValue(value);
|
||||
view.setOnSeekBarChangeListener(ON_CHANGE_LISTENER);
|
||||
}
|
||||
|
||||
@ReactProp(name = "minimumValue", defaultDouble = 0d)
|
||||
public void setMinimumValue(ReactSlider view, double value) {
|
||||
view.setMinValue(value);
|
||||
}
|
||||
|
||||
@ReactProp(name = "maximumValue", defaultDouble = 1d)
|
||||
public void setMaximumValue(ReactSlider view, double value) {
|
||||
view.setMaxValue(value);
|
||||
}
|
||||
|
||||
@ReactProp(name = "step", defaultDouble = 0d)
|
||||
public void setStep(ReactSlider view, double value) {
|
||||
view.setStep(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addEventEmitters(final ThemedReactContext reactContext, final ReactSlider view) {
|
||||
view.setOnSeekBarChangeListener(ON_CHANGE_LISTENER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map getExportedCustomDirectEventTypeConstants() {
|
||||
return MapBuilder.of(
|
||||
ReactSlidingCompleteEvent.EVENT_NAME,
|
||||
MapBuilder.of("registrationName", "onSlidingComplete"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.slider;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
|
||||
/**
|
||||
* Event emitted when the user finishes dragging the slider.
|
||||
*/
|
||||
public class ReactSlidingCompleteEvent extends Event<ReactSlidingCompleteEvent> {
|
||||
|
||||
public static final String EVENT_NAME = "topSlidingComplete";
|
||||
|
||||
private final double mValue;
|
||||
|
||||
public ReactSlidingCompleteEvent(int viewId, long timestampMs, double value) {
|
||||
super(viewId, timestampMs);
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
public double getValue() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return EVENT_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getCoalescingKey() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canCoalesce() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||
}
|
||||
|
||||
private WritableMap serializeEventData() {
|
||||
WritableMap eventData = Arguments.createMap();
|
||||
eventData.putInt("target", getViewTag());
|
||||
eventData.putDouble("value", getValue());
|
||||
return eventData;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user