mirror of
https://github.com/zoriya/react-native-svg.git
synced 2026-06-07 08:45:00 +00:00
fix: Android group opacity prop (#2417)
# Summary Currently, on Android, when the `opacity` prop is applied to `G` or `Svg` elements, it is rendered incorrectly. Instead of rendering the children "offscreen" and then applying the opacity to the entire result, the child elements themselves are rendered with the specified opacity. Fixes #2046 ## Example ```tsx import {View} from 'react-native'; import {G, Rect, Svg} from 'react-native-svg'; export default function App() { return ( <View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}> <Svg width="150" height="150" viewBox="0 0 150 150"> <G opacity="0.5"> <Rect width="100" height="100" fill="red" /> <Rect x="50" y="50" width="100" height="100" fill="green" /> </G> </Svg> <Svg width="150" height="150" viewBox="0 0 150 150" opacity="0.5"> <Rect width="100" height="100" fill="red" /> <Rect x="50" y="50" width="100" height="100" fill="green" /> </Svg> </View> ); } ``` | Before | After | Web reference | | --- | --- | --- | | <img width="200" alt="Zrzut ekranu 2024-08-20 o 15 44 13" src="https://github.com/user-attachments/assets/68c57f7d-0375-4703-8c3c-a358fe124daa"> | <img width="200" alt="Zrzut ekranu 2024-08-20 o 15 44 26" src="https://github.com/user-attachments/assets/ae7e24b9-edae-4b44-8785-3fc95a39fdd4"> | <img width="190" alt="image" src="https://github.com/user-attachments/assets/1aa0491f-2936-4845-b18c-1fa669c34118"> | ## Test Plan Example above is available in `test-examples` app as `Test2417` ## Compatibility | OS | Implemented | | ------- | :---------: | | Android | ✅ |
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
package com.horcrux.svg;
|
package com.horcrux.svg;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Matrix;
|
import android.graphics.Matrix;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
@@ -29,9 +30,13 @@ import javax.annotation.Nullable;
|
|||||||
class GroupView extends RenderableView {
|
class GroupView extends RenderableView {
|
||||||
@Nullable ReadableMap mFont;
|
@Nullable ReadableMap mFont;
|
||||||
private GlyphContext mGlyphContext;
|
private GlyphContext mGlyphContext;
|
||||||
|
private Bitmap mLayerBitmap;
|
||||||
|
private Canvas mLayerCanvas;
|
||||||
|
private final Paint mLayerPaint;
|
||||||
|
|
||||||
public GroupView(ReactContext reactContext) {
|
public GroupView(ReactContext reactContext) {
|
||||||
super(reactContext);
|
super(reactContext);
|
||||||
|
mLayerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFont(Dynamic dynamic) {
|
public void setFont(Dynamic dynamic) {
|
||||||
@@ -92,6 +97,20 @@ class GroupView extends RenderableView {
|
|||||||
final SvgView svg = getSvgView();
|
final SvgView svg = getSvgView();
|
||||||
final GroupView self = this;
|
final GroupView self = this;
|
||||||
final RectF groupRect = new RectF();
|
final RectF groupRect = new RectF();
|
||||||
|
if (mLayerBitmap == null) {
|
||||||
|
mLayerBitmap =
|
||||||
|
Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888);
|
||||||
|
mLayerCanvas = new Canvas(mLayerBitmap);
|
||||||
|
} else {
|
||||||
|
mLayerBitmap.recycle();
|
||||||
|
mLayerBitmap =
|
||||||
|
Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888);
|
||||||
|
mLayerCanvas.setBitmap(mLayerBitmap);
|
||||||
|
}
|
||||||
|
// Copy current matrix from original canvas
|
||||||
|
int saveCount = mLayerCanvas.save();
|
||||||
|
mLayerCanvas.setMatrix(canvas.getMatrix());
|
||||||
|
|
||||||
elements = new ArrayList<>();
|
elements = new ArrayList<>();
|
||||||
for (int i = 0; i < getChildCount(); i++) {
|
for (int i = 0; i < getChildCount(); i++) {
|
||||||
View child = getChildAt(i);
|
View child = getChildAt(i);
|
||||||
@@ -107,15 +126,15 @@ class GroupView extends RenderableView {
|
|||||||
((RenderableView) node).mergeProperties(self);
|
((RenderableView) node).mergeProperties(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
int count = node.saveAndSetupCanvas(canvas, mCTM);
|
int count = node.saveAndSetupCanvas(mLayerCanvas, mCTM);
|
||||||
node.render(canvas, paint, opacity * mOpacity);
|
node.render(mLayerCanvas, paint, opacity);
|
||||||
RectF r = node.getClientRect();
|
RectF r = node.getClientRect();
|
||||||
|
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
groupRect.union(r);
|
groupRect.union(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
node.restoreCanvas(canvas, count);
|
node.restoreCanvas(mLayerCanvas, count);
|
||||||
|
|
||||||
if (node instanceof RenderableView) {
|
if (node instanceof RenderableView) {
|
||||||
((RenderableView) node).resetProperties();
|
((RenderableView) node).resetProperties();
|
||||||
@@ -137,6 +156,14 @@ class GroupView extends RenderableView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore copied canvas and temporary reset original canvas matrix to draw bitmap 1:1
|
||||||
|
mLayerCanvas.restoreToCount(saveCount);
|
||||||
|
saveCount = canvas.save();
|
||||||
|
canvas.setMatrix(null);
|
||||||
|
mLayerPaint.setAlpha((int) (mOpacity * 255));
|
||||||
|
canvas.drawBitmap(mLayerBitmap, 0, 0, mLayerPaint);
|
||||||
|
canvas.restoreToCount(saveCount);
|
||||||
this.setClientRect(groupRect);
|
this.setClientRect(groupRect);
|
||||||
popGlyphContext();
|
popGlyphContext();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import Test2366 from './src/Test2366';
|
|||||||
import Test2397 from './src/Test2397';
|
import Test2397 from './src/Test2397';
|
||||||
import Test2403 from './src/Test2403';
|
import Test2403 from './src/Test2403';
|
||||||
import Test2407 from './src/Test2407';
|
import Test2407 from './src/Test2407';
|
||||||
|
import Test2417 from './src/Test2417';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return <ColorTest />;
|
return <ColorTest />;
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import {View} from 'react-native';
|
||||||
|
import {G, Rect, Svg} from 'react-native-svg';
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
return (
|
||||||
|
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
|
||||||
|
<Svg width="150" height="150" viewBox="0 0 150 150">
|
||||||
|
<G opacity="0.5">
|
||||||
|
<Rect width="100" height="100" fill="red" />
|
||||||
|
<Rect x="50" y="50" width="100" height="100" fill="green" />
|
||||||
|
</G>
|
||||||
|
</Svg>
|
||||||
|
|
||||||
|
<Svg width="150" height="150" viewBox="0 0 150 150" opacity="0.5">
|
||||||
|
<Rect width="100" height="100" fill="red" />
|
||||||
|
<Rect x="50" y="50" width="100" height="100" fill="green" />
|
||||||
|
</Svg>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user