Uh oh!
There was an error while loading. Please reload this page.
Support for ScrollView.maintainVisibleContentPosition on Android - #29466
Support for ScrollView.maintainVisibleContentPosition on Android#29466maxbth wants to merge 28 commits into
ScrollView.maintainVisibleContentPosition on Android#29466Conversation
Base commit: 9b4f8e0 |
Base commit: 9b4f8e0 |
Here is an alternative native module implementation, for those who need packagecom.yourapp;
importandroid.view.View;
importcom.facebook.react.bridge.Promise;
importcom.facebook.react.bridge.ReactApplicationContext;
importcom.facebook.react.bridge.ReactContextBaseJavaModule;
importcom.facebook.react.bridge.ReactMethod;
importcom.facebook.react.uimanager.IllegalViewOperationException;
importcom.facebook.react.uimanager.NativeViewHierarchyManager;
importcom.facebook.react.uimanager.ReactShadowNode;
importcom.facebook.react.uimanager.UIBlock;
importcom.facebook.react.uimanager.UIImplementation;
importcom.facebook.react.uimanager.UIManagerModule;
importcom.facebook.react.uimanager.UIManagerModuleListener;
importcom.facebook.react.views.scroll.ReactScrollView;
importcom.facebook.react.views.view.ReactViewGroup;
importjava.util.HashMap;
publicclassScrollViewMagicModuleextendsReactContextBaseJavaModule {
privatefinalReactApplicationContextreactContext;
privateHashMap<Integer, UIManagerModuleListener> uiManagerModuleListeners;
publicScrollViewMagicModule(ReactApplicationContextreactContext) {
super(reactContext);
this.reactContext = reactContext;
}
@OverridepublicStringgetName() {
return"ScrollViewMagic";
}
@Overridepublicvoidinitialize() {
super.initialize();
this.uiManagerModuleListeners = newHashMap<>();
}
@ReactMethodpublicvoidenableMaintainVisibleContentPosition(finalintviewTag, finalPromisepromise) {
finalUIManagerModuleuiManagerModule = this.reactContext.getNativeModule(UIManagerModule.class);
this.reactContext.runOnUiQueueThread(newRunnable() {
@Overridepublicvoidrun() {
try {
finalReactScrollViewscrollView = (ReactScrollView)uiManagerModule.resolveView(viewTag);
finalUIManagerModuleListeneruiManagerModuleListener = newUIManagerModuleListener() {
privateintminIndexForVisible = 0;
privateintprevFirstVisibleTop = 0;
privateViewfirstVisibleView = null;
@OverridepublicvoidwillDispatchViewUpdates(finalUIManagerModuleuiManagerModule) {
uiManagerModule.prependUIBlock(newUIBlock() {
@Overridepublicvoidexecute(NativeViewHierarchyManagernativeViewHierarchyManager) {
ReactViewGroupmContentView = (ReactViewGroup)scrollView.getChildAt(0);
if (mContentView == null) return;
for (intii = minIndexForVisible; ii < mContentView.getChildCount(); ++ii) {
Viewsubview = mContentView.getChildAt(ii);
if (subview.getTop() >= scrollView.getScrollY()) {
prevFirstVisibleTop = subview.getTop();
firstVisibleView = subview;
break;
}
}
}
});
UIImplementation.LayoutUpdateListenerlayoutUpdateListener = newUIImplementation.LayoutUpdateListener() {
@OverridepublicvoidonLayoutUpdated(ReactShadowNoderoot) {
if (firstVisibleView == null) return;
intdeltaY = firstVisibleView.getTop() - prevFirstVisibleTop;
if (Math.abs(deltaY) > 0) {
scrollView.setScrollY(scrollView.getScrollY() + deltaY);
}
uiManagerModule.getUIImplementation().removeLayoutUpdateListener();
}
};
uiManagerModule.getUIImplementation().setLayoutUpdateListener(layoutUpdateListener);
}
};
uiManagerModule.addUIManagerListener(uiManagerModuleListener);
intkey = uiManagerModuleListeners.size() + 1;
uiManagerModuleListeners.put(key, uiManagerModuleListener);
promise.resolve(key);
} catch(IllegalViewOperationExceptione) {
promise.resolve(-1);
}
}
});
}
@ReactMethodpublicvoiddisableMaintainVisibleContentPosition(intkey, Promisepromise) {
if (key >= 0) {
finalUIManagerModuleuiManagerModule = this.reactContext.getNativeModule(UIManagerModule.class);
uiManagerModule.removeUIManagerListener(uiManagerModuleListeners.remove(key));
}
promise.resolve(null);
}
}JS side: constnativeModule: {enableMaintainVisibleContentPosition(viewTag: number): Promise<number>;disableMaintainVisibleContentPosition(handle: number): Promise<void>;}=NativeModules.ScrollViewMagic;// `ref` is the ref to your ScrollView / FlatListuseEffect(()=>{letcleanupPromise: Promise<number>|undefined;if(Constants.isAndroid){constviewTag=findNodeHandle(ref.current);cleanupPromise=nativeModule.enableMaintainVisibleContentPosition(viewTag);}return()=>{voidcleanupPromise?.then((handle)=>{voidnativeModule.disableMaintainVisibleContentPosition(handle);});};},[ref]); |
maxbth
commented
Sep 9, 2020
Hi @chrisglein I saw you roaming around issue #29055 that this PR aims to fix. Since you are a contributor on this repo do you know if there are any next steps I'm missing to get this PR merged? It's been opened for a bit more than a month now and we'd like to have this scroll improvement for our app. |
chrisglein
commented
Sep 12, 2020
I'm mainly acting as an issue first responder, helping logged issues have the best chance of success. I've tagged the issue for this to see if that helps it get attention. You may also want to ping the discord to see if you can get some eyes on this. |
maxbth
commented
Sep 12, 2020
Thank you Chris! I joined the Discord 🤞 |
ALexandreM75013
commented
Apr 27, 2022
Any update ? |
numandev1
commented
Jun 8, 2022
@maxoumime any update? |
@maxoumime +1 |
1 similar comment
andresribeiro
commented
Jun 15, 2022
@maxoumime +1 |
| private final FabricViewStateManager mFabricViewStateManager = new FabricViewStateManager(); | ||
| private @Nullable ReactScrollViewMaintainVisibleContentPositionData | ||
| mMaintainVisibleContentPositionData; | ||
| private @Nullable WeakReference<View> firstVisibleViewForMaintainVisibleContentPosition = null; |
There was a problem hiding this comment.
Why did you choose to use a WeakReference here?
evgeniy-skakun
commented
Jul 11, 2022
any updates ? |
SwikarBhattarai
commented
Sep 14, 2022
any update ? |
ponikar
commented
Sep 22, 2022
Any updates?? |
@janicduplessis and I have a working version of this feature completed in the Expensify fork of react-native, and are just working on more battle-testing of the feature before submitting PR(s) to the upstream repo. This PR was definitely a helpful start, but ultimately the implementation we landed on is this. We've also implemented Disclaimer: Unfortunately it does not seem to work with Fabric enabled though (nor does the existing iOS implementation, AFAICT) 😞 It's unclear that a native implementation of this prop will be needed in a Fabric world, however. My guess is probably not. Note: The latest version of the Expensify RN fork is |
Use maintainVisibleContentPosition instead of existing functionality in FlatList implementation PR 29466 react#29466
darkbasic
commented
Sep 23, 2022
Any plan to upstream your efforts? |
Yes, as stated above we want to production-test the features before attempting to upstream them. You can help out in that effort by using the Expensify fork of React Native in the meantime, which is available on npm just like the normal React Native repo. If you do encounter issues specific to these new features, you can open an issue in our fork and we'll do our best to address them. Edit: I realized that you can't create issues in our fork, so if you need to get our attention the best way will probably be to join our open-source slack |
roryabraham
commented
Oct 24, 2022
@janicduplessis opened a new PR which implements |
This PR is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days. |
johanntony
commented
Feb 17, 2025
有没有iOS端的实现 |
Summary
This PR adds the support for ScrollView's
maintainVisibleContentPositionproperty to Android. This property is currently only available on iOS and is especially useful for chat-like scrollviews, where you want the scroll position to stick after layout changes.Fixes#29055
This PR will impact the documentation, so I opened a draft PR on react/react-native-website#2088.
Changelog
[Android] [Added] -
ScrollView.maintainVisibleContentPosition. This property is not iOS-only anymore.Test Plan
Most of the new code is based on the iOS code. The implementation differs a bit but is working great. You can try it out on the RNTester app, I added a new example called
ScrollViewExpandingExample, available both on Android and iOS. GIFs below.