Support ScrollView keyboardDismissMode - with crash fixed (#3696)
* Revert "Revert "Support ScrollView keyboardDismissMode" (#3692)"
This reverts commit 55ef15e598
.
* Properly support ScrollView KeyboardDismissMode
* Change files
* buildci
* Merge update
This commit is contained in:
Родитель
1d917cbb45
Коммит
93f2e8a1af
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"type": "prerelease",
|
||||
"comment": "Properly support ScrollView KeyboardDismissMode",
|
||||
"packageName": "react-native-windows",
|
||||
"email": "dida@ntdev.microsoft.com",
|
||||
"commit": "6b4df0e13a371763a12ecee273011ad762bd9322",
|
||||
"date": "2019-11-22T22:27:58.216Z"
|
||||
}
|
|
@ -30,6 +30,7 @@ export default class Bootstrap extends React.Component<{}, any> {
|
|||
zoomValue: false,
|
||||
alignToStartValue: true,
|
||||
refreshing: false,
|
||||
keyboardDismiss: false,
|
||||
snapToOffsets: true,
|
||||
pagingEnabled: false,
|
||||
};
|
||||
|
@ -62,6 +63,10 @@ export default class Bootstrap extends React.Component<{}, any> {
|
|||
this.setState({pagingEnabled: value});
|
||||
};
|
||||
|
||||
toggleSwitch8 = (value: boolean) => {
|
||||
this.setState({keyboardDismiss: value});
|
||||
};
|
||||
|
||||
onRefresh = () => {
|
||||
this.setState({refreshing: true});
|
||||
wait(2000).then(() => this.setState({refreshing: false}));
|
||||
|
@ -190,16 +195,29 @@ export default class Bootstrap extends React.Component<{}, any> {
|
|||
justifyContent: 'center',
|
||||
padding: 20,
|
||||
}}>
|
||||
<Text>
|
||||
{this.state.pagingEnabled
|
||||
? 'pagingEnabled on'
|
||||
: 'pagingEnabled off'}
|
||||
</Text>
|
||||
<Text>{'PagingEnabled'}</Text>
|
||||
<Switch
|
||||
onValueChange={this.toggleSwitch7}
|
||||
value={this.state.pagingEnabled}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'column',
|
||||
alignSelf: 'stretch',
|
||||
justifyContent: 'center',
|
||||
padding: 20,
|
||||
}}>
|
||||
<Text>
|
||||
{'KeyboardDismiss: '.concat(
|
||||
this.state.keyboardDismiss ? 'on-drag' : 'none',
|
||||
)}
|
||||
</Text>
|
||||
<Switch
|
||||
onValueChange={this.toggleSwitch8}
|
||||
value={this.state.keyboardDismiss}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={{flex: 0.8, alignSelf: 'center', flexDirection: 'column'}}>
|
||||
<ScrollView
|
||||
|
@ -214,6 +232,9 @@ export default class Bootstrap extends React.Component<{}, any> {
|
|||
onRefresh={this.onRefresh}
|
||||
/>
|
||||
}
|
||||
keyboardDismissMode={
|
||||
this.state.keyboardDismiss ? 'on-drag' : 'none'
|
||||
}
|
||||
snapToOffsets={
|
||||
this.state.snapToOffsets ? [100.0, 500.0] : undefined
|
||||
}
|
||||
|
|
|
@ -58,12 +58,37 @@ bool IsAPIContractVxAvailable() {
|
|||
return isAPIContractVxAvailable;
|
||||
}
|
||||
|
||||
bool IsAPIContractV5Available() {
|
||||
return IsAPIContractVxAvailable<5>();
|
||||
}
|
||||
|
||||
bool IsAPIContractV6Available() {
|
||||
return IsAPIContractVxAvailable<6>();
|
||||
}
|
||||
|
||||
bool IsAPIContractV7Available() {
|
||||
return IsAPIContractVxAvailable<7>();
|
||||
}
|
||||
|
||||
bool IsAPIContractV8Available() {
|
||||
return IsAPIContractVxAvailable<8>();
|
||||
}
|
||||
|
||||
bool IsRS3OrHigher() {
|
||||
return IsAPIContractV5Available();
|
||||
}
|
||||
|
||||
bool IsRS4OrHigher() {
|
||||
return IsAPIContractV6Available();
|
||||
}
|
||||
|
||||
bool IsRS5OrHigher() {
|
||||
return IsAPIContractV7Available();
|
||||
}
|
||||
|
||||
bool Is19H1OrHigher() {
|
||||
return IsAPIContractV8Available();
|
||||
}
|
||||
|
||||
} // namespace uwp
|
||||
}; // namespace react
|
||||
|
|
|
@ -30,6 +30,9 @@ inline typename T asEnum(folly::dynamic const &obj) {
|
|||
ReactId getViewId(_In_ IReactInstance *instance, winrt::FrameworkElement const &fe);
|
||||
std::int32_t CountOpenPopups();
|
||||
|
||||
bool IsRS3OrHigher();
|
||||
bool IsRS4OrHigher();
|
||||
bool IsRS5OrHigher();
|
||||
bool Is19H1OrHigher();
|
||||
} // namespace uwp
|
||||
} // namespace react
|
||||
|
|
|
@ -68,11 +68,13 @@ PreviewKeyboardEventHandler::PreviewKeyboardEventHandler(KeyboardEventCallback &
|
|||
|
||||
void PreviewKeyboardEventHandler::hook(XamlView xamlView) {
|
||||
auto uiElement = xamlView.as<winrt::UIElement>();
|
||||
if (m_keyDownCallback)
|
||||
m_previewKeyDownRevoker = uiElement.PreviewKeyDown(winrt::auto_revoke, m_keyDownCallback);
|
||||
if (uiElement.try_as<winrt::IUIElement7>()) {
|
||||
if (m_keyDownCallback)
|
||||
m_previewKeyDownRevoker = uiElement.PreviewKeyDown(winrt::auto_revoke, m_keyDownCallback);
|
||||
|
||||
if (m_keyUpCallback)
|
||||
m_previewKeyUpRevoker = uiElement.PreviewKeyUp(winrt::auto_revoke, m_keyUpCallback);
|
||||
if (m_keyUpCallback)
|
||||
m_previewKeyUpRevoker = uiElement.PreviewKeyUp(winrt::auto_revoke, m_keyUpCallback);
|
||||
}
|
||||
}
|
||||
|
||||
void PreviewKeyboardEventHandler::unhook() {
|
||||
|
|
|
@ -203,6 +203,7 @@ void ReactControl::AttachRoot() noexcept {
|
|||
|
||||
m_touchEventHandler->AddTouchHandlers(m_xamlRootView);
|
||||
m_previewKeyboardEventHandlerOnRoot->hook(m_xamlRootView);
|
||||
m_SIPEventHandler->AttachView(m_xamlRootView, true /*fireKeyboradEvents*/);
|
||||
|
||||
auto initialProps = m_initialProps;
|
||||
m_reactInstance->AttachMeasuredRootView(m_pParent, std::move(initialProps));
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <Modules/NativeUIManager.h>
|
||||
|
||||
#include <ReactUWP\Utils\Helpers.h>
|
||||
#include <winrt/Windows.ApplicationModel.Core.h>
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
|
||||
|
@ -15,43 +16,96 @@ using namespace Windows::Foundation;
|
|||
using namespace Windows::Foundation::Collections;
|
||||
using namespace Windows::UI::ViewManagement::Core;
|
||||
using namespace Windows::UI::Xaml;
|
||||
using namespace Windows::UI::Xaml::Media;
|
||||
} // namespace winrt
|
||||
namespace react {
|
||||
namespace uwp {
|
||||
|
||||
SIPEventHandler::SIPEventHandler(const std::weak_ptr<IReactInstance> &reactInstance)
|
||||
: m_wkReactInstance(reactInstance) {
|
||||
auto coreInputView = winrt::CoreInputView::GetForCurrentView();
|
||||
if (coreInputView) {
|
||||
m_occlusionsChanged_revoker = coreInputView.OcclusionsChanged(
|
||||
winrt::auto_revoke, [=](auto &&, const winrt::CoreInputViewOcclusionsChangedEventArgs &e) {
|
||||
if (!e.Handled()) {
|
||||
winrt::Rect finalRect = winrt::RectHelper::Empty();
|
||||
winrt::IVectorView<winrt::CoreInputViewOcclusion> occlusions = e.Occlusions();
|
||||
for (uint32_t i = 0; i < occlusions.Size(); i++) {
|
||||
winrt::CoreInputViewOcclusion occlusion = occlusions.GetAt(i);
|
||||
if (occlusion.OcclusionKind() == winrt::CoreInputViewOcclusionKind::Docked) {
|
||||
finalRect = winrt::RectHelper::Union(finalRect, occlusion.OccludingRect());
|
||||
}
|
||||
}
|
||||
|
||||
if (winrt::RectHelper::GetIsEmpty(finalRect)) {
|
||||
folly::dynamic params = folly::dynamic::object("screenY", 0)("screenX", 0)("width", 0)("height", 0);
|
||||
SendEvent("keyboardDidHide", std::move(params));
|
||||
} else {
|
||||
folly::dynamic params = folly::dynamic::object(
|
||||
"endCoordinates",
|
||||
folly::dynamic::object("screenY", finalRect.Y)("screenX", finalRect.X)("width", finalRect.Width)(
|
||||
"height", finalRect.Height));
|
||||
SendEvent("keyboardDidShow", std::move(params));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
: m_wkReactInstance(reactInstance), m_fireKeyboradEvents(false), m_finalRect(winrt::RectHelper::Empty()){};
|
||||
|
||||
SIPEventHandler::~SIPEventHandler() {
|
||||
m_occlusionsChanged_revoker = {};
|
||||
m_loadedRevoker = {};
|
||||
}
|
||||
// keyboardDidHide and keyboardDidShow events works on >= RS3
|
||||
// TryShow and TryHide works on >= RS5
|
||||
|
||||
void SIPEventHandler::AttachView(XamlView xamlView, bool fireKeyboardEvents) {
|
||||
m_fireKeyboradEvents = fireKeyboardEvents;
|
||||
// hookup CoreInputView only after element is in the tree
|
||||
m_view = winrt::make_weak(xamlView);
|
||||
if (winrt::VisualTreeHelper::GetParent(xamlView)) {
|
||||
InitializeCoreInputView();
|
||||
} else {
|
||||
m_loadedRevoker = xamlView.as<winrt::FrameworkElement>().Loaded(
|
||||
winrt::auto_revoke, [this](const auto &sender, const auto &) { InitializeCoreInputView(); });
|
||||
}
|
||||
}
|
||||
|
||||
void SIPEventHandler::InitializeCoreInputView() {
|
||||
if (const auto xamlView = m_view.get()) {
|
||||
if (!IsRS3OrHigher()) {
|
||||
return; // CoreInputView is only supported on >= RS3.
|
||||
}
|
||||
|
||||
if (Is19H1OrHigher()) {
|
||||
// 19H1 and higher supports island scenarios
|
||||
auto uiElement(xamlView.as<winrt::UIElement>());
|
||||
m_coreInputView = winrt::CoreInputView::GetForUIContext(uiElement.UIContext());
|
||||
} else {
|
||||
m_coreInputView = winrt::CoreInputView::GetForCurrentView();
|
||||
}
|
||||
|
||||
if (m_coreInputView) {
|
||||
auto occlusions = m_coreInputView.GetCoreInputViewOcclusions();
|
||||
m_isShowing = !IsOcclusionsEmpty(occlusions);
|
||||
m_occlusionsChanged_revoker = m_coreInputView.OcclusionsChanged(
|
||||
winrt::auto_revoke, [this](auto &&, const winrt::CoreInputViewOcclusionsChangedEventArgs &e) {
|
||||
if (!e.Handled()) {
|
||||
bool wasShowing = m_isShowing;
|
||||
m_isShowing = !IsOcclusionsEmpty(e.Occlusions());
|
||||
if (wasShowing != m_isShowing && m_fireKeyboradEvents) {
|
||||
if (!m_isShowing) {
|
||||
folly::dynamic params = folly::dynamic::object("screenY", 0)("screenX", 0)("width", 0)("height", 0);
|
||||
SendEvent("keyboardDidHide", std::move(params));
|
||||
} else {
|
||||
folly::dynamic params = folly::dynamic::object(
|
||||
"endCoordinates",
|
||||
folly::dynamic::object("screenY", m_finalRect.Y)("screenX", m_finalRect.X)(
|
||||
"width", m_finalRect.Width)("height", m_finalRect.Height));
|
||||
SendEvent("keyboardDidShow", std::move(params));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
void SIPEventHandler::TryShow() {
|
||||
if (IsRS5OrHigher() && m_coreInputView && !m_isShowing) { // CoreInputView.TryShow is only avaliable after RS5
|
||||
m_coreInputView.TryShow();
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
void SIPEventHandler::TryHide() {
|
||||
if (IsRS5OrHigher() && m_coreInputView && m_isShowing) { // CoreInputView.TryHide is only avaliable after RS5
|
||||
m_coreInputView.TryHide();
|
||||
}
|
||||
}
|
||||
|
||||
bool SIPEventHandler::IsOcclusionsEmpty(winrt::IVectorView<winrt::CoreInputViewOcclusion> occlusions) {
|
||||
m_finalRect = winrt::RectHelper::Empty();
|
||||
if (occlusions) {
|
||||
for (const auto &occlusion : occlusions) {
|
||||
if (occlusion.OcclusionKind() == winrt::CoreInputViewOcclusionKind::Docked) {
|
||||
m_finalRect = winrt::RectHelper::Union(m_finalRect, occlusion.OccludingRect());
|
||||
}
|
||||
}
|
||||
}
|
||||
return (winrt::RectHelper::GetIsEmpty(m_finalRect));
|
||||
}
|
||||
|
||||
void SIPEventHandler::SendEvent(std::string &&eventName, folly::dynamic &¶meters) {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
#include <winrt/Windows.UI.ViewManagement.Core.h>
|
||||
|
||||
namespace winrt {
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::Foundation::Collections;
|
||||
using namespace Windows::UI::ViewManagement::Core;
|
||||
} // namespace winrt
|
||||
|
||||
|
@ -20,10 +20,27 @@ class SIPEventHandler {
|
|||
SIPEventHandler(const std::weak_ptr<IReactInstance> &reactInstance);
|
||||
virtual ~SIPEventHandler();
|
||||
|
||||
bool IsSIPShowing() {
|
||||
return m_isShowing;
|
||||
}
|
||||
|
||||
void AttachView(XamlView xamlView, bool fireKeyboardEvents);
|
||||
// void TryShow();
|
||||
void TryHide();
|
||||
|
||||
private:
|
||||
bool IsOcclusionsEmpty(winrt::IVectorView<winrt::CoreInputViewOcclusion> occlusions);
|
||||
void SendEvent(std::string &&eventName, folly::dynamic &¶meters);
|
||||
std::weak_ptr<IReactInstance> m_wkReactInstance;
|
||||
winrt::CoreInputView::OcclusionsChanged_revoker m_occlusionsChanged_revoker;
|
||||
winrt::Rect m_finalRect;
|
||||
winrt::CoreInputView m_coreInputView{nullptr};
|
||||
winrt::weak_ref<XamlView> m_view{};
|
||||
winrt::Windows::UI::Xaml::FrameworkElement::Loaded_revoker m_loadedRevoker{};
|
||||
bool m_isShowing{false};
|
||||
bool m_fireKeyboradEvents;
|
||||
|
||||
void InitializeCoreInputView();
|
||||
};
|
||||
|
||||
} // namespace uwp
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "pch.h"
|
||||
|
||||
#include <ReactUWP\Views\SIPEventHandler.h>
|
||||
#include <Views/ShadowNodeBase.h>
|
||||
#include "Impl/ScrollViewUWPImplementation.h"
|
||||
#include "ScrollViewManager.h"
|
||||
|
@ -20,6 +21,7 @@ class ScrollViewShadowNode : public ShadowNodeBase {
|
|||
|
||||
public:
|
||||
ScrollViewShadowNode();
|
||||
~ScrollViewShadowNode();
|
||||
void dispatchCommand(int64_t commandId, const folly::dynamic &commandArgs) override;
|
||||
void createView() override;
|
||||
void updateProperties(const folly::dynamic &&props) override;
|
||||
|
@ -44,6 +46,9 @@ class ScrollViewShadowNode : public ShadowNodeBase {
|
|||
bool m_isHorizontal = false;
|
||||
bool m_isScrollingEnabled = true;
|
||||
bool m_changeViewAfterLoaded = false;
|
||||
bool m_dismissKeyboardOnDrag = false;
|
||||
|
||||
std::shared_ptr<SIPEventHandler> m_SIPEventHandler;
|
||||
|
||||
winrt::FrameworkElement::SizeChanged_revoker m_scrollViewerSizeChangedRevoker{};
|
||||
winrt::FrameworkElement::SizeChanged_revoker m_contentSizeChangedRevoker{};
|
||||
|
@ -56,6 +61,10 @@ class ScrollViewShadowNode : public ShadowNodeBase {
|
|||
|
||||
ScrollViewShadowNode::ScrollViewShadowNode() {}
|
||||
|
||||
ScrollViewShadowNode::~ScrollViewShadowNode() {
|
||||
m_SIPEventHandler.reset();
|
||||
}
|
||||
|
||||
void ScrollViewShadowNode::dispatchCommand(int64_t commandId, const folly::dynamic &commandArgs) {
|
||||
const auto scrollViewer = GetView().as<winrt::ScrollViewer>();
|
||||
if (scrollViewer == nullptr)
|
||||
|
@ -186,6 +195,16 @@ void ScrollViewShadowNode::updateProperties(const folly::dynamic &&reactDiffMap)
|
|||
if (valid) {
|
||||
ScrollViewUWPImplementation(scrollViewer).SnapToEnd(snapToEnd);
|
||||
}
|
||||
} else if (propertyName == "keyboardDismissMode") {
|
||||
m_dismissKeyboardOnDrag = false;
|
||||
if (propertyValue.isString()) {
|
||||
m_dismissKeyboardOnDrag = (propertyValue.getString() == "on-drag");
|
||||
if (m_dismissKeyboardOnDrag) {
|
||||
auto wkinstance = GetViewManager()->GetReactInstance();
|
||||
m_SIPEventHandler = std::make_unique<SIPEventHandler>(wkinstance);
|
||||
m_SIPEventHandler->AttachView(GetView(), false /*fireKeyboardEvents*/);
|
||||
}
|
||||
}
|
||||
} else if (propertyName == "snapToAlignment") {
|
||||
const auto [valid, snapToAlignment] = getPropertyAndValidity(propertyValue, winrt::SnapPointsAlignment::Near);
|
||||
if (valid) {
|
||||
|
@ -241,6 +260,11 @@ void ScrollViewShadowNode::AddHandlers(const winrt::ScrollViewer &scrollViewer)
|
|||
m_scrollViewerDirectManipulationStartedRevoker =
|
||||
scrollViewer.DirectManipulationStarted(winrt::auto_revoke, [this](const auto &sender, const auto &) {
|
||||
m_isScrolling = true;
|
||||
|
||||
if (m_dismissKeyboardOnDrag && m_SIPEventHandler) {
|
||||
m_SIPEventHandler->TryHide();
|
||||
}
|
||||
|
||||
const auto scrollViewer = sender.as<winrt::ScrollViewer>();
|
||||
EmitScrollEvent(
|
||||
scrollViewer,
|
||||
|
@ -402,7 +426,7 @@ folly::dynamic ScrollViewManager::GetNativeProps() const {
|
|||
"showsHorizontalScrollIndicator", "boolean")("showsVerticalScrollIndicator", "boolean")(
|
||||
"minimumZoomScale", "float")("maximumZoomScale", "float")("zoomScale", "float")("snapToInterval", "float")(
|
||||
"snapToOffsets", "array")("snapToAlignment", "number")("snapToStart", "boolean")("snapToEnd", "boolean")(
|
||||
"pagingEnabled", "boolean"));
|
||||
"pagingEnabled", "boolean")("keyboardDismissMode", "string"));
|
||||
|
||||
return props;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче