Skip to content

[Android] skew transform not working #27649

Closed

Description

@usrbowe

Steps To Reproduce

  1. Try to set skewX for View component: transforms: [{ skewX: '45deg' }}
  2. Doesn't change the skew of View on Android (works on iOS)

Describe what you expected to happen:

Basically skewX doesn't apply any skew on the view on Android.
Here is comparison between iOS/Android:

also skewY doesn't render same as on iOS (which looks more correct)

Screenshot 2020-01-02 at 2 13 00 PM

React Native version:

System:
    OS: macOS Mojave 10.14.6
    CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
    Memory: 1.50 GB / 16.00 GB
    Shell: 5.3 - /bin/zsh
  Binaries:
    Node: 12.13.0 - ~/.nvm/versions/node/v12.13.0/bin/node
    Yarn: 1.19.1 - ~/.yarn/bin/yarn
    npm: 6.12.0 - ~/.nvm/versions/node/v12.13.0/bin/npm
  SDKs:
    iOS SDK:
      Platforms: iOS 13.2, DriverKit 19.0, macOS 10.15, tvOS 13.2, watchOS 6.1
    Android SDK:
      API Levels: 25, 28, 29
      Build Tools: 28.0.3, 29.0.2
      System Images: android-28 | Google Play Intel x86 Atom, android-29 | Google APIs Intel x86 Atom
  IDEs:
    Android Studio: 3.5 AI-191.8026.42.35.5977832
    Xcode: 11.3/11C29 - /usr/bin/xcodebuild
  npmPackages:
    react: 16.9.0 => 16.9.0 
    react-native: 0.61.5 => 0.61.5

Related issues

Snack demo

https://snack.expo.io/@usrbowe2/e9b920

Resources

In case anyone interested, here is the source code for skew code:

  1. This is called for SkewX: MatrixMathHelper.applySkewX(helperMatrix, convertToRadians(transform, transformType)); in file: Libraries/StyleSheet/processTransform.js
  2. Which laters uses this matrix helper to apply transformation:
    https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/uimanager/MatrixMathHelper.java

Activity

  1. MarekJavurek commented on Jan 3, 2020

    @MarekJavurek

    This is real problem!

  2. HarshitMadhav commented on Jan 4, 2020

    @HarshitMadhav
    Contributor

    I faced this issue too in android but for iOS, it is working fine.

  3. JX0829 commented on Jan 6, 2020

    @JX0829

    i also have this issue, anyone have a workaround? I had search this issue on google, there have many people said use the native canvas have no problem, so whether modify the MatrixMathHelper.java could resolve this issue?

  4. HarshitMadhav commented on Jan 6, 2020

    @HarshitMadhav
    Contributor

    @JX0829 As of now I have searched a lot for workarounds but found nothing. Sadly, I had to remove skew for android.

  5. DaniAkash commented on Feb 9, 2020

    @DaniAkash

    A live preview of this issue can be found at the example in https://deploy-preview-1613--react-native.netlify.com/docs/next/transforms

  6. Valegox commented on Apr 13, 2020

    @Valegox

    I found this package: https://github.com/wumke/react-native-skewable-view
    It skews the shape of the view in android but not images and texts that are inside...
    As work around you can also try using canvas, with for example this package: https://github.com/iddan/react-native-canvas#readme. But personally when I try ctx.drawImage(img, width, height) I get an error: "JSON.stringify cannot serialize cyclic structures."
    So I'm still facing this problem for images.

  7. Valegox commented on Apr 13, 2020

    @Valegox

    For images I just recreated it by myself: I croped 100 same images with ImageEditor and aligned each part in offset to create the one I needed...

  8. ramyma commented on Jun 9, 2021

    @ramyma

    I can see that skew style rules are still not working correctly on Android with RN v0.64.2.

    Even the example provided on the transforms documentation page doesn't work:

    anroid-skew-not-working

    However it works on iOS as described in the original issue:

    ios-skew-working

    Can we reopen this issue as it seems like the applied fix didn't resolve the reported issue?

  9. ghaschel commented on Jun 22, 2021

    @ghaschel

    I'm still facing this issue. SkewY works, skewX doesn't. Skew works properly on iOS but it doesn't on android

  10. kelset commented on Jun 23, 2021

    @kelset
    Contributor

    reopening for now.

    @ramyma @ghaschel can either of you try if it still repros with RN 65 RC2?

  11. 38 remaining items

  12. quantizor commented on May 4, 2026

    @quantizor
    Contributor

    Just hit this on the latest stable line, sharing a fresh repro since the issue was stale-closed in February.

    Confirmed reproducing on:

    • React Native 0.85.2
    • New architecture (Fabric) enabled
    • Hermes
    • Pixel 8a emulator, Android 16

    Minimal vanilla repro, with skewX array form, mirrors what's been reported here for years:

    <View style={{ width: 88, height: 88, backgroundColor: 'tomato', transform: [{ skewX: '-12deg' }, { translateY: 8 }] }} />

    The square renders upright. The translateY half of the same array is honored, so the view does receive a transform, but the skewX component is silently dropped. Same result for the CSS-string form transform: 'skewX(-12deg) translateY(8px)'. Tracing the pipeline:

    1. processTransform.js correctly produces { skewX: '-12deg' } in the decomposed array.
    2. Native side, TransformHelper.kt calls MatrixMathHelper.applySkewX, which writes m[4] = tan(radians) into the matrix correctly.
    3. MatrixMathHelper.decomposeMatrix does compute a skew field on MatrixDecompositionContext.
    4. BaseViewManager.java reads only translation, rotationDegrees, and scale from the decomposition context. The skew field is never consumed.

    @javache's 2023 explanation still holds: Android View has no setSkewX / setSkewY. @MaxGraey's note in 2025 frames why a clean fix is hard, since polar / SVD decomposition can't disambiguate skew from scale, so an animation crossing the boundary would visibly snap.

    Given this has now been documented as a platform limitation for ~6 years across paper and Fabric, would the team accept a documentation-side resolution: marking skewX / skewY as iOS-only on https://reactnative.dev/docs/transforms and emitting a one-time dev warning when those keys are used on Android? @delventhalz proposed this in 2024 and it has community upvotes. It would at least surface the platform divergence at authoring time instead of silently dropping at render time.

    Could this be reopened? Happy to send a docs PR + a __DEV__ warning if there's interest.

  13. javache commented on May 6, 2026

    @javache
    Contributor

    A documentation fix for this and/or warning would definitely be welcome.

  14. reopened this on May 6, 2026
  15. MaxGraey commented on May 6, 2026

    @MaxGraey
    Contributor

    The only solution is to redesign the entire process architecturally and store the transformations not as a single matrix, but, for example, as a composition of vectors. And only lower to affine 3x3 matrix when needed. The Android versions that support pre-required API for these architectures should be minimum requirement for quite some time now I guess

    Show Full Example
    struct Vec2 {
      float x = 0.0f;
      float y = 0.0f;
    };
    
    struct Rotor2 {
      float c = 1.0f;
      float s = 0.0f;
      ...
    }
    
    struct Transform2D {
      ...
      Mat3x3 toAffineMat() const {
         /* <- T * R * Hy * Hx * S */
        ... 
      }
    }
    struct Vec2 {
      float x = 0.0f;
      float y = 0.0f;
    };
    
    struct Rotor2 {
      float c = 1.0f;
      float s = 0.0f;
    
      static Rotor2 fromRadians(float angle) {
        return {
          .c = std::cos(angle),
          .s = std::sin(angle),
        };
      }
    
      Vec2 apply(Vec2 v) const {
        return {
          .x = c * v.x - s * v.y,
          .y = s * v.x + c * v.y,
        };
      }
    };
    
    struct Transform2D {
      Vec2 position = {0.0f, 0.0f};
      Rotor2 rotor = {};
      Vec2 scale = {1.0f, 1.0f};
    
      // skew.x = shear X: x += skew.x * y
      // skew.y = shear Y: y += skew.y * x
      Vec2 skewXY = {0.0f, 0.0f};
    
      Vec2 transform(Vec2 p) const {
        // Scale.
        p.x *= scale.x;
        p.y *= scale.y;
    
        // Sequential skew: Hx then Hy.
        p.x += skewXY.x * p.y;
        p.y += skewXY.y * p.x;
    
        // Rotation.
        p = rotor.apply(p);
    
        // Translation.
        p.x += position.x;
        p.y += position.y;
    
        return p;
      }
    
      Vec2 transformVec(Vec2 v) const {
        v.x *= scale.x;
        v.y *= scale.y;
    
        v.x += skewXY.x * v.y;
        v.y += skewXY.y * v.x;
    
        return rotor.apply(v);
      }
    
     Mat3x3 toAffineMat() const {
        const float tx = position.x;
        const float ty = position.y;
    
        const float c = rotor.c;
        const float s = rotor.s;
    
        const float sx = scale.x;
        const float sy = scale.y;
    
        const float kx = skewXY.x;
        const float ky = skewXY.y;
    
        // M = T * R * Hy * Hx * S
        return {
          .m00 = sx * (c - s * ky),
          .m01 = sy * (c * kx - s * (1.0f + ky * kx)),
          .m02 = tx,
    
          .m10 = sx * (s + c * ky),
          .m11 = sy * (s * kx + c * (1.0f + ky * kx)),
          .m12 = ty,
    
          .m20 = 0.0f,
          .m21 = 0.0f,
          .m22 = 1.0f,
        };
      }
    };
  16. removed
    StaleThere has been a lack of activity on this issue and it may be closed soon.
    on May 7, 2026
  17. qflen commented on May 7, 2026

    @qflen
    Contributor

    Thanks for reopening, @javache, and to @quantizor for the fresh repro and the trace through the call site that identified the dropped-skew bug at BaseViewManager.

    Want to sanity-check a rendering-fix shape with you before opening a PR.

    The dispatch in BaseViewManager.setTransformProperty, after the existing null-transform reset:

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
        && hasSkewTransform(transforms)
        && isAffine2DTransform(transforms)) {
      Matrix affine = buildAffine2DMatrix(
          transforms,
          PixelUtil.toDIPFromPixel(view.getWidth()),
          PixelUtil.toDIPFromPixel(view.getHeight()),
          transformOrigin);
      view.setTranslationX(0);
      view.setTranslationY(0);
      view.setRotation(0);
      view.setRotationX(0);
      view.setRotationY(0);
      view.setScaleX(1);
      view.setScaleY(1);
      view.setCameraDistance(0);
      view.setAnimationMatrix(affine);
      return;
    }
    // Fallthrough: existing decompose-and-set-View-props path runs unchanged.

    isAffine2DTransform rejects matrix, perspective, rotateX, rotateY, and Z-translate; hasSkewTransform is true only for skewX and skewY. So rotate, scale, and translate transforms keep flowing through the View-property code unchanged. buildAffine2DMatrix walks the ops left-to-right with preRotate, preScale, preSkew, and preTranslate around the resolved pivot. Pre-Q silently drops skew, which matches today, so there is no regression.

    The working prototype renders correctly on emulator (Android 16, Pixel 8 Pro), with hit testing following via View.getMatrix().

    Your #38494 review cited the SDK doc framing setAnimationMatrix as an animation API. androidx.transition.ViewUtilsApi21 calls it via reflection on pre-Q to drive the public Transition API for static transforms, and the API was promoted to public in Q. Is that precedent enough, or is there a deeper concern from #38494 that was not visible in the public thread?

    Will open the PR this week unless you flag a reason to hold.

  18. tovgroliv commented on May 26, 2026

    @tovgroliv

    What are the updates? When can we expect the fix? Or is it possible to do it locally somehow?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions