-
Notifications
You must be signed in to change notification settings - Fork 24.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add zIndex support #7825
Add zIndex support #7825
Conversation
By analyzing the blame information on this pull request, we identified @mkonicek and @bestander to be potential reviewers. |
@tuckerconnelly updated the pull request. |
commonStyles, | ||
{marginLeft: 150, backgroundColor: '#64B5F6', zIndex: -1} | ||
]}> | ||
<Text>ZIndex -1</Text> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I'm missing something, but shouldn't a view with zIndex = -1 appear underneath a view view with zIndex = 0? In [email protected], it's on top.
This looks promising, but I think the implementation on iOS needs to work by sorting the actual view order instead of adjusting the layer.zPosition. (I also think you may have got the sign wrong on the zPosition anyway, as it appears that views with higher zIndex are being drawn behind views with lower zIndex, whereas according to the CSS spec "An element with greater stack order is always in front of an element with a lower stack order.") |
Nick, in the comment above:
Doesn't matter though 'cause zPosition won't work :P. Would Also, for Android, will |
Ah, that makes sense.
Yes, but we need to be very careful when messing with the view order so that subsequent updates don't end up removing the wrong view. I think we need to figure out the best place to do the view sorting. Options are:
I'm not sure which of these is the best option, although 3) has the nice benefit that we could share the code between iOS and Android and avoid platform-specific hacks.
Sorry, I don't know anything about Android ¯_(ツ)_/¯ |
Now I think about it, 3) probably isn't viable since it will affect the flexbox layout if we change the order prior to sending the views to native. That means the order of shadowViews can't be changed based on zIndex, we can only change the order for UIViews. |
UIManager seems like the simplest to me--if it was done in the setReactSubviews method I think it'd have to be done in all the components' setReactSubviews methods. I'll give it a shot :) |
@tuckerconnelly I've got an idea how to approach this using setReactSubviews actually. I'll let you know if I can make it work. |
@tuckerconnelly I have a solution working for iOS. I'm just checking that it doesn't negatively affect performance. |
Summary: This diff implement the CSS z-index for React Native iOS views. We've had numerous pull request for this feature, but they've all attempted to use the `layer.zPosition` property, which is problematic for two reasons: 1. zPosition only affects rendering order, not event processing order. Views with a higher zPosition will appear in front of others in the hierarchy, but won't be the first to receive touch events, and may be blocked by views that are visually behind them. 2. when using a perspective transform matrix, views with a nonzero zPosition will be rendered in a different position due to parallax, which probably isn't desirable. See #7825 for further discussion of this problem. So instead of using `layer.zPosition`, I've implemented this by actually adjusting the order of the subviews within their parent based on the zIndex. This can't be done on the JS side because it would affect layout, which is order-dependent, so I'm doing it inside the view itself. It works as follows: 1. The `reactSubviews` array is set, whose order matches the order of the JS components and shadowView components, as specified by the UIManager. 2. `didUpdateReactSubviews` is called, which in turn calls `sortedSubviews` (which lazily generates a sorted array of `reactSubviews` by zIndex) and inserts the result into the view. 3. If a subview is added or removed, or the zIndex of any subview is changed, the previous `sortedSubviews` array is cleared and `didUpdateReactSubviews` is called again. To demonstrate it working, I've modified the UIExplorer example from #7825 Reviewed By: javache Differential Revision: D3365717 fbshipit-source-id: b34aa8bfad577bce023f8af5414f9b974aafd8aa
@tuckerconnelly OK, I've landed zIndex support for iOS: d64368b You should be able to rebase your PR on top to add Android support. |
Cc: @dmmiller Dave, can you review the Android implementation here? I don't know the platform well enough to judge if this is an appropriate solution, or if we need to implement something like what I've done on iOS. |
DUDE YEAH! I can tomorrow |
👍 |
Is this on every single view or are some exempted for some reason? In general, the core seems correct. I'm a little sad about doing this with an interface and 10 copy/pasted lines in every single view. This also won't scale if we have views that are purely native and not wrapped by react native. Is there a way to maybe do this with an annotation on the view (that addresses the first part, but not the second)? |
Yeah I didn't like the interface either. I can't really imagine a way to do it with annotations without stepping out of ViewManager-manages-View pattern, but I'm not a pro with Java :) What if we created a generic View and have every subclass of View contain a reference to the android component. So something like: public class ReactGenericView<T extends android.view.View> {
private @Nullable float mZIndex;
public void setZIndex(float zIndex) {
mZIndex = zIndex;
}
public float getZIndex() {
return mZIndex;
}
// Other future props
// Could also put borderRadius, etc. in here
public abstract T getComponent()
}
class ReactImageView extends ReactGenericView<GenericDraweeView> {
// Stuff specific to Images
@Override
public GenericDraweeView getComponent() {
// return lazily-initialized GenericDraweeView
}
}
class ReactImageManager {
@ReactProp(name = "tintColor", customType = "Color")
public void setTintColor(ReactImageView view, @Nullable Integer tintColor) {
if (tintColor == null) {
view.getComponent().clearColorFilter();
} else {
view.getComponent().setColorFilter(tintColor, Mode.SRC_IN);
}
}
} That's a pretty big change though. Re: purely native views, I could put a default in to treat them as zIndex: 0, and if they wanted to set their own zIndex, they could implement the interface (or extend GenericView) and React would pick up on it. Edit: Actually that wouldn't work with the interface because it wouldn't re-order the siblings when set. It would work with GenericView though, if the siblings were re-ordered on setZIndex. |
@lexs do you know a good way to do this with annotations? |
@@ -116,6 +122,15 @@ public void invalidateDrawable(Drawable drawable) { | |||
super.invalidateDrawable(drawable); | |||
} | |||
|
|||
public void setZIndex(float targetZIndex) { | |||
zIndex = targetZIndex; | |||
ViewGroupManager.reorderChildrenByZIndex((ViewGroup) this.getParent()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this special?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, that's old. I'll delete it.
We talked about the Android side internally and have a few wishes. Instead of forcing each view to implement an interface can we have a look-aside map like: We'd also like to move to a model where addChild() doesn't require sorting children if none of them have a z-index because we're afraid this might hurt perf. As I'd really like to see this land I don't think we need to do it in this PR but we (you or us internally) should look into this in the future depending on how inefficient the current implementation is. We have a few ideas on how to do this by for example keeping track of which ViewGroups contain z-indexed children and skipping sorting if we know they contain no z-children. |
FWIW, the iOS implementation has this optimisation. I currently do it in a fairly naive way – instead of keeping track as the views are added, I just do a single pass through the views to see if any has a non-zero zIndex before I sort them – but that means the common case is O(n) instead of O(n log n) so it still seems worth doing. |
@tuckerconnelly,@lexs What's the status? Does this need review or changes? |
It needs to be rebased and updated. |
9e8ea72
to
f0a62d9
Compare
e62f646
to
340382d
Compare
Alright it's up. I also added the optimization to check if there are any non-zero z-indexes before sorting 😃 |
@tuckerconnelly Thanks, I'll import this now and test it internally for perf regressions. I'll merge it on Monday if everything goes well :) |
@facebook-github-bot import |
Thanks for importing. If you are an FB employee go to Phabricator to review. |
Summary: This diff implement the CSS z-index for React Native iOS views. We've had numerous pull request for this feature, but they've all attempted to use the `layer.zPosition` property, which is problematic for two reasons: 1. zPosition only affects rendering order, not event processing order. Views with a higher zPosition will appear in front of others in the hierarchy, but won't be the first to receive touch events, and may be blocked by views that are visually behind them. 2. when using a perspective transform matrix, views with a nonzero zPosition will be rendered in a different position due to parallax, which probably isn't desirable. See facebook/react-native#7825 for further discussion of this problem. So instead of using `layer.zPosition`, I've implemented this by actually adjusting the order of the subviews within their parent based on the zIndex. This can't be done on the JS side because it would affect layout, which is order-dependent, so I'm doing it inside the view itself. It works as follows: 1. The `reactSubviews` array is set, whose order matches the order of the JS components and shadowView components, as specified by the UIManager. 2. `didUpdateReactSubviews` is called, which in turn calls `sortedSubviews` (which lazily generates a sorted array of `reactSubviews` by zIndex) and inserts the result into the view. 3. If a subview is added or removed, or the zIndex of any subview is changed, the previous `sortedSubviews` array is cleared and `didUpdateReactSubviews` is called again. To demonstrate it working, I've modified the UIExplorer example from facebook/react-native#7825 Reviewed By: javache Differential Revision: D3365717 fbshipit-source-id: b34aa8bfad577bce023f8af5414f9b974aafd8aa
@facebook-github-bot shipit |
1 similar comment
@facebook-github-bot shipit |
Thanks for importing. If you are an FB employee go to Phabricator to review. |
3d3b067
I am a bit confused about the changed images for the iOS snapshot tests. |
@lexs, the test was failing on the CI for the PR https://travis-ci.org/facebook/react-native/builds/137840201, be careful :) |
Tests got broken after facebook#7825, reverting changes Test Plan: ./scritps/objc-test.sh
Picked the wrong yours/theirs in my revert/rebase. Had a hard time getting the Snapshot Test Cases to run again so just ignored it :P I can try to get them fixed up tomorrow morning |
No worries, fixd it now bb0fda7 |
Summary: Adds zIndex support :) **Test Plan** Tested the following components by adding two of each, overlapping them, and setting a high zIndex on the first of the two: ActivityIndicator Image MapView Picker ScrollView Slider Switch Text TextInput View WebView Tested on Android 4.1 and iOS 8.4. Also tested updating zIndexes on Views in my own app. <img width="359" alt="ios activityindicator" src="https://cloud.githubusercontent.com/assets/4349082/15633473/88f842cc-257b-11e6-8539-c41c0b179f80.png"> <img width="330" alt="android activityindicator" src="https://cloud.githubusercontent.com/assets/4349082/15633475/88f95784-257b-11e6-80c0-2bf3ed836503.png"> <img width="357" alt="ios image" src="https://cloud.githubusercontent.com/assets/4349082/15633474/88f93d80-257b-11e6-9e54-4ff8e4d25f71.png"> <img width="340" alt="android image" src="https://cloud.githubusercontent.com/assets/4349082/15633478/88fd2788-257b-11e6-8c80-29078e65e808.png"> <img width="342" alt="android picker" src="ht Closes facebook/react-native#7825 Differential Revision: D3469374 Pulled By: lexs fbshipit-source-id: b2b74b71d968ebf73ecfd457ace3f35f8f7c7658
Summary: Tests got broken after facebook/react-native#7825, reverting changes Closes facebook/react-native#8508 Differential Revision: D3503439 Pulled By: bestander fbshipit-source-id: 95b9283371654265234d975e1b0df540f4ef55fa
Summary: This diff implement the CSS z-index for React Native iOS views. We've had numerous pull request for this feature, but they've all attempted to use the `layer.zPosition` property, which is problematic for two reasons: 1. zPosition only affects rendering order, not event processing order. Views with a higher zPosition will appear in front of others in the hierarchy, but won't be the first to receive touch events, and may be blocked by views that are visually behind them. 2. when using a perspective transform matrix, views with a nonzero zPosition will be rendered in a different position due to parallax, which probably isn't desirable. See facebook#7825 for further discussion of this problem. So instead of using `layer.zPosition`, I've implemented this by actually adjusting the order of the subviews within their parent based on the zIndex. This can't be done on the JS side because it would affect layout, which is order-dependent, so I'm doing it inside the view itself. It works as follows: 1. The `reactSubviews` array is set, whose order matches the order of the JS components and shadowView components, as specified by the UIManager. 2. `didUpdateReactSubviews` is called, which in turn calls `sortedSubviews` (which lazily generates a sorted array of `reactSubviews` by zIndex) and inserts the result into the view. 3. If a subview is added or removed, or the zIndex of any subview is changed, the previous `sortedSubviews` array is cleared and `didUpdateReactSubviews` is called again. To demonstrate it working, I've modified the UIExplorer example from facebook#7825 Reviewed By: javache Differential Revision: D3365717 fbshipit-source-id: b34aa8bfad577bce023f8af5414f9b974aafd8aa
Summary: Adds zIndex support :) **Test Plan** Tested the following components by adding two of each, overlapping them, and setting a high zIndex on the first of the two: ActivityIndicator Image MapView Picker ScrollView Slider Switch Text TextInput View WebView Tested on Android 4.1 and iOS 8.4. Also tested updating zIndexes on Views in my own app. <img width="359" alt="ios activityindicator" src="https://cloud.githubusercontent.com/assets/4349082/15633473/88f842cc-257b-11e6-8539-c41c0b179f80.png"> <img width="330" alt="android activityindicator" src="https://cloud.githubusercontent.com/assets/4349082/15633475/88f95784-257b-11e6-80c0-2bf3ed836503.png"> <img width="357" alt="ios image" src="https://cloud.githubusercontent.com/assets/4349082/15633474/88f93d80-257b-11e6-9e54-4ff8e4d25f71.png"> <img width="340" alt="android image" src="https://cloud.githubusercontent.com/assets/4349082/15633478/88fd2788-257b-11e6-8c80-29078e65e808.png"> <img width="342" alt="android picker" src="ht Closes facebook#7825 Differential Revision: D3469374 Pulled By: lexs fbshipit-source-id: b2b74b71d968ebf73ecfd457ace3f35f8f7c7658
Summary: Tests got broken after facebook#7825, reverting changes Closes facebook#8508 Differential Revision: D3503439 Pulled By: bestander fbshipit-source-id: 95b9283371654265234d975e1b0df540f4ef55fa
Summary: This diff implement the CSS z-index for React Native iOS views. We've had numerous pull request for this feature, but they've all attempted to use the `layer.zPosition` property, which is problematic for two reasons: 1. zPosition only affects rendering order, not event processing order. Views with a higher zPosition will appear in front of others in the hierarchy, but won't be the first to receive touch events, and may be blocked by views that are visually behind them. 2. when using a perspective transform matrix, views with a nonzero zPosition will be rendered in a different position due to parallax, which probably isn't desirable. See facebook#7825 for further discussion of this problem. So instead of using `layer.zPosition`, I've implemented this by actually adjusting the order of the subviews within their parent based on the zIndex. This can't be done on the JS side because it would affect layout, which is order-dependent, so I'm doing it inside the view itself. It works as follows: 1. The `reactSubviews` array is set, whose order matches the order of the JS components and shadowView components, as specified by the UIManager. 2. `didUpdateReactSubviews` is called, which in turn calls `sortedSubviews` (which lazily generates a sorted array of `reactSubviews` by zIndex) and inserts the result into the view. 3. If a subview is added or removed, or the zIndex of any subview is changed, the previous `sortedSubviews` array is cleared and `didUpdateReactSubviews` is called again. To demonstrate it working, I've modified the UIExplorer example from facebook#7825 Reviewed By: javache Differential Revision: D3365717 fbshipit-source-id: b34aa8bfad577bce023f8af5414f9b974aafd8aa
Summary: Adds zIndex support :) **Test Plan** Tested the following components by adding two of each, overlapping them, and setting a high zIndex on the first of the two: ActivityIndicator Image MapView Picker ScrollView Slider Switch Text TextInput View WebView Tested on Android 4.1 and iOS 8.4. Also tested updating zIndexes on Views in my own app. <img width="359" alt="ios activityindicator" src="https://cloud.githubusercontent.com/assets/4349082/15633473/88f842cc-257b-11e6-8539-c41c0b179f80.png"> <img width="330" alt="android activityindicator" src="https://cloud.githubusercontent.com/assets/4349082/15633475/88f95784-257b-11e6-80c0-2bf3ed836503.png"> <img width="357" alt="ios image" src="https://cloud.githubusercontent.com/assets/4349082/15633474/88f93d80-257b-11e6-9e54-4ff8e4d25f71.png"> <img width="340" alt="android image" src="https://cloud.githubusercontent.com/assets/4349082/15633478/88fd2788-257b-11e6-8c80-29078e65e808.png"> <img width="342" alt="android picker" src="ht Closes facebook#7825 Differential Revision: D3469374 Pulled By: lexs fbshipit-source-id: b2b74b71d968ebf73ecfd457ace3f35f8f7c7658
Summary: Tests got broken after facebook#7825, reverting changes Closes facebook#8508 Differential Revision: D3503439 Pulled By: bestander fbshipit-source-id: 95b9283371654265234d975e1b0df540f4ef55fa
Summary: Adds zIndex support :) **Test Plan** Tested the following components by adding two of each, overlapping them, and setting a high zIndex on the first of the two: ActivityIndicator Image MapView Picker ScrollView Slider Switch Text TextInput View WebView Tested on Android 4.1 and iOS 8.4. Also tested updating zIndexes on Views in my own app. <img width="359" alt="ios activityindicator" src="https://cloud.githubusercontent.com/assets/4349082/15633473/88f842cc-257b-11e6-8539-c41c0b179f80.png"> <img width="330" alt="android activityindicator" src="https://cloud.githubusercontent.com/assets/4349082/15633475/88f95784-257b-11e6-80c0-2bf3ed836503.png"> <img width="357" alt="ios image" src="https://cloud.githubusercontent.com/assets/4349082/15633474/88f93d80-257b-11e6-9e54-4ff8e4d25f71.png"> <img width="340" alt="android image" src="https://cloud.githubusercontent.com/assets/4349082/15633478/88fd2788-257b-11e6-8c80-29078e65e808.png"> <img width="342" alt="android picker" src="ht Closes facebook#7825 Differential Revision: D3469374 Pulled By: lexs fbshipit-source-id: b2b74b71d968ebf73ecfd457ace3f35f8f7c7658
Summary: Adds zIndex support :) **Test Plan** Tested the following components by adding two of each, overlapping them, and setting a high zIndex on the first of the two: ActivityIndicator Image MapView Picker ScrollView Slider Switch Text TextInput View WebView Tested on Android 4.1 and iOS 8.4. Also tested updating zIndexes on Views in my own app. <img width="359" alt="ios activityindicator" src="https://cloud.githubusercontent.com/assets/4349082/15633473/88f842cc-257b-11e6-8539-c41c0b179f80.png"> <img width="330" alt="android activityindicator" src="https://cloud.githubusercontent.com/assets/4349082/15633475/88f95784-257b-11e6-80c0-2bf3ed836503.png"> <img width="357" alt="ios image" src="https://cloud.githubusercontent.com/assets/4349082/15633474/88f93d80-257b-11e6-9e54-4ff8e4d25f71.png"> <img width="340" alt="android image" src="https://cloud.githubusercontent.com/assets/4349082/15633478/88fd2788-257b-11e6-8c80-29078e65e808.png"> <img width="342" alt="android picker" src="ht Closes facebook#7825 Differential Revision: D3469374 Pulled By: lexs fbshipit-source-id: b2b74b71d968ebf73ecfd457ace3f35f8f7c7658
Summary:
Adds zIndex support :)
Test Plan
Tested the following components by adding two of each, overlapping them, and setting a high zIndex on the first of the two:
ActivityIndicator
Image
MapView
Picker
ScrollView
Slider
Switch
Text
TextInput
View
WebView
Tested on Android 4.1 and iOS 8.4. Also tested updating zIndexes on Views in my own app.
iOS seems to have a bug where zPosition is ignored when calling renderInContext (http://lists.apple.com/archives/cocoa-dev/2013/Jan/msg00084.html), so the test snapshots won't be z-indexed. If it's important enough I can look into another solution besides using zPosition.
iOS WebView was tough. Apparently
RCTWebView
is a subview? I set thezPosition
on thesuperview
inwebViewDidFinishLoad
, which seems a little hacky to me, so maybe someone can suggest a better solution :)