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:
Di Da 2019-11-25 13:48:16 -08:00 коммит произвёл msftbot[bot]
Родитель 1d917cbb45
Коммит 93f2e8a1af
9 изменённых файлов: 195 добавлений и 40 удалений

Просмотреть файл

@ -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 &&parameters) {

Просмотреть файл

@ -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 &&parameters);
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;
}