mirror of
https://github.com/zoriya/react-native-svg.git
synced 2026-05-24 19:48:46 +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;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
@@ -29,9 +30,13 @@ import javax.annotation.Nullable;
|
||||
class GroupView extends RenderableView {
|
||||
@Nullable ReadableMap mFont;
|
||||
private GlyphContext mGlyphContext;
|
||||
private Bitmap mLayerBitmap;
|
||||
private Canvas mLayerCanvas;
|
||||
private final Paint mLayerPaint;
|
||||
|
||||
public GroupView(ReactContext reactContext) {
|
||||
super(reactContext);
|
||||
mLayerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
}
|
||||
|
||||
public void setFont(Dynamic dynamic) {
|
||||
@@ -92,6 +97,20 @@ class GroupView extends RenderableView {
|
||||
final SvgView svg = getSvgView();
|
||||
final GroupView self = this;
|
||||
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<>();
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
View child = getChildAt(i);
|
||||
@@ -107,15 +126,15 @@ class GroupView extends RenderableView {
|
||||
((RenderableView) node).mergeProperties(self);
|
||||
}
|
||||
|
||||
int count = node.saveAndSetupCanvas(canvas, mCTM);
|
||||
node.render(canvas, paint, opacity * mOpacity);
|
||||
int count = node.saveAndSetupCanvas(mLayerCanvas, mCTM);
|
||||
node.render(mLayerCanvas, paint, opacity);
|
||||
RectF r = node.getClientRect();
|
||||
|
||||
if (r != null) {
|
||||
groupRect.union(r);
|
||||
}
|
||||
|
||||
node.restoreCanvas(canvas, count);
|
||||
node.restoreCanvas(mLayerCanvas, count);
|
||||
|
||||
if (node instanceof RenderableView) {
|
||||
((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);
|
||||
popGlyphContext();
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import Test2366 from './src/Test2366';
|
||||
import Test2397 from './src/Test2397';
|
||||
import Test2403 from './src/Test2403';
|
||||
import Test2407 from './src/Test2407';
|
||||
import Test2417 from './src/Test2417';
|
||||
|
||||
export default function App() {
|
||||
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