Feat/eightbitlab blurview (#358)

* Update build.gradle

* Update build.gradle

* feat(android-blurview): migrate to com.eightbitlab.blurview

* fix(types): proptypes and typescript

- removed viewRef references

* fix(offset): fixing offset on android

* feat(harmonized-behavior): blurview can now have children on android

- Same behavior on iOS and Android now

* Update BlurView.android.js

* Update index.js.flow

* Create App.js

* Delete App.android.js

* Delete App.ios.js

* Update README.md
This commit is contained in:
David Guerin 2020-03-04 14:46:11 -03:00 committed by GitHub
parent dc00052b41
commit 47a199ef98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 70 additions and 450 deletions

View File

@ -2,7 +2,7 @@
[![npm version](https://badge.fury.io/js/%40react-native-community%2Fblur.svg)](https://badge.fury.io/js/%40react-native-community%2Fblur)
A component for UIVisualEffectView's blur and vibrancy effect on iOS, and [500px-android-blur](https://github.com/500px/500px-android-blur) on Android.<br>
A component for UIVisualEffectView's blur and vibrancy effect on iOS, and [BlurView](https://github.com/Dimezis/BlurView) on Android.<br>
<img src='https://cloud.githubusercontent.com/assets/139536/25066337/3c9d44c0-224d-11e7-8ca6-028478bf4a7d.gif' />
@ -102,18 +102,10 @@ import { BlurView, VibrancyView } from "@react-native-community/blur";
```javascript
import React, { Component } from "react";
import { View, Image, Text, findNodeHandle, StyleSheet } from "react-native";
import { View, Image, Text, StyleSheet } from "react-native";
import { BlurView } from "@react-native-community/blur";
export default class Menu extends Component {
constructor(props) {
super(props);
this.state = { viewRef: null };
}
imageLoaded() {
this.setState({ viewRef: findNodeHandle(this.backgroundImage) });
}
render() {
return (
@ -124,14 +116,12 @@ export default class Menu extends Component {
viewRef={this.state.viewRef}
blurType="light"
blurAmount={10}
/>
>
<Text>I'm the BlurView content on both iOS and Android</Text>
</BlurView>
<Image
ref={img => {
this.backgroundImage = img;
}}
source={{ uri }}
style={styles.absolute}
onLoadEnd={this.imageLoaded.bind(this)}
/>
</View>
);
@ -155,8 +145,6 @@ const styles = StyleSheet.create({
In this example, the `Image` component will be blurred, because the `BlurView` in positioned on top. But the `Text` will stay unblurred.
Note that `viewRef` is only required if you need to support Android. See the [Android section](#android) for more details.
### VibrancyView
**Uses the same properties as `BlurView` (`blurType` and `blurAmount`).**
@ -183,12 +171,7 @@ export default class Menu extends Component {
### Android
Android uses the [500px-android-blur](https://github.com/500px/500px-android-blur) library, which works by blurring a referenced view. This means that you must wait until the view you want to blur is rendered. You then use `findNodeHandle` to get a reference to that view, and pass that reference to the `BlurView` as the `viewRef` prop. Take a look at [the BlurView example](#blurview) to see how it works.
The Android library introduces some limitations:
- `BlurView` cannot be a child of the view that is being blurred (this would cause an infinite loop)
- `BlurView` cannot contain any child components.
Android uses the [BlurView](https://github.com/Dimezis/BlurView).
If you only need to support iOS, then you can safely ignore these limitations.

View File

@ -40,4 +40,5 @@ repositories {
dependencies {
//noinspection GradleDynamicVersion
implementation 'com.facebook.react:react-native:+'
implementation 'com.eightbitlab:blurview:1.6.3'
}

View File

@ -1,63 +1,62 @@
package com.cmcewen.blurview;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
import java.util.Objects;
import javax.annotation.Nonnull;
import eightbitlab.com.blurview.BlurView;
import eightbitlab.com.blurview.RenderScriptBlur;
@SuppressWarnings("unused")
class BlurViewManager extends SimpleViewManager<BlurringView> {
class BlurViewManager extends ViewGroupManager<BlurView> {
private static final String REACT_CLASS = "BlurView";
private static final int defaultRadius = 10;
private static final int defaultSampling = 10;
private static ThemedReactContext context;
@Override
public @Nonnull String getName() {
return REACT_CLASS;
}
@Override
public @Nonnull BlurringView createViewInstance(@Nonnull ThemedReactContext ctx) {
context = ctx;
BlurringView blurringView = new BlurringView(ctx);
blurringView.setBlurRadius(defaultRadius);
blurringView.setDownsampleFactor(defaultSampling);
return blurringView;
public @Nonnull BlurView createViewInstance(@Nonnull ThemedReactContext ctx) {
BlurView blurView = new BlurView(ctx);
View decorView = Objects.requireNonNull(ctx.getCurrentActivity()).getWindow().getDecorView();
ViewGroup rootView = decorView.findViewById(android.R.id.content);
Drawable windowBackground = decorView.getBackground();
blurView.setupWith(rootView)
.setFrameClearDrawable(windowBackground)
.setBlurAlgorithm(new RenderScriptBlur(ctx))
.setBlurRadius(defaultRadius)
.setHasFixedTransformationMatrix(false);
return blurView;
}
@ReactProp(name = "blurRadius", defaultInt = defaultRadius)
public void setRadius(BlurringView view, int radius) {
public void setRadius(BlurView view, int radius) {
view.setBlurRadius(radius);
view.invalidate();
}
@ReactProp(name = "overlayColor", customType = "Color")
public void setColor(BlurringView view, int color) {
public void setColor(BlurView view, int color) {
view.setOverlayColor(color);
view.invalidate();
}
@ReactProp(name = "downsampleFactor", defaultInt = defaultSampling)
public void setDownsampleFactor(BlurringView view, int factor) {
view.setDownsampleFactor(factor);
}
public void setDownsampleFactor(BlurView view, int factor) {
@ReactProp(name = "viewRef")
public void setViewRef(BlurringView view, int viewRef) {
if (context != null && context.getCurrentActivity() != null) {
View viewToBlur = context.getCurrentActivity().findViewById(viewRef);
if (viewToBlur != null) {
view.setBlurredView(viewToBlur);
}
}
}
}

View File

@ -1,209 +0,0 @@
package com.cmcewen.blurview;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RenderScript;
import android.renderscript.ScriptIntrinsicBlur;
import android.util.AttributeSet;
import android.view.View;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.ThemedReactContext;
/**
* A custom view for presenting a dynamically blurred version of another view's content.
* <p/>
* Use {@link #setBlurredView(android.view.View)} to set up the reference to the view to be blurred.
* After that, call {@link #invalidate()} to trigger blurring whenever necessary.
*/
public class BlurringView extends View {
public BlurringView(ThemedReactContext context) {
this(context, null);
}
public BlurringView(Context context, AttributeSet attrs) {
super(context, attrs);
final Resources res = getResources();
final int defaultBlurRadius = res.getInteger(R.integer.default_blur_radius);
final int defaultDownsampleFactor = res.getInteger(R.integer.default_downsample_factor);
final int defaultOverlayColor = res.getColor(R.color.default_overlay_color);
initializeRenderScript(context);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BlurringView);
setBlurRadius(a.getInt(R.styleable.BlurringView_blurRadius, defaultBlurRadius));
setDownsampleFactor(a.getInt(R.styleable.BlurringView_downsampleFactor,
defaultDownsampleFactor));
setOverlayColor(a.getColor(R.styleable.BlurringView_overlayColor, defaultOverlayColor));
a.recycle();
}
public void setBlurredView(View blurredView) {
mBlurredView = blurredView;
checkForCircularReference();
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mBlurredView != null) {
if (prepare()) {
// If the background of the blurred view is a color drawable, we use it to clear
// the blurring canvas, which ensures that edges of the child views are blurred
// as well; otherwise we clear the blurring canvas with a transparent color.
if (mBlurredView.getBackground() != null && mBlurredView.getBackground() instanceof ColorDrawable) {
mBitmapToBlur.eraseColor(((ColorDrawable) mBlurredView.getBackground()).getColor());
} else {
mBitmapToBlur.eraseColor(Color.TRANSPARENT);
}
mBlurredView.draw(mBlurringCanvas);
blur();
canvas.save();
canvas.translate(mBlurredView.getX() - getX(), mBlurredView.getY() - getY());
canvas.scale(mDownsampleFactor, mDownsampleFactor);
canvas.drawBitmap(mBlurredBitmap, 0, 0, null);
canvas.restore();
}
canvas.drawColor(mOverlayColor);
}
}
public void setBlurRadius(int radius) {
mBlurScript.setRadius(radius);
invalidate();
}
public void setDownsampleFactor(int factor) {
if (factor <= 0) {
throw new IllegalArgumentException("Downsample factor must be greater than 0.");
}
if (mDownsampleFactor != factor) {
mDownsampleFactor = factor;
mDownsampleFactorChanged = true;
invalidate();
}
}
public void setOverlayColor(int color) {
if (mOverlayColor != color) {
mOverlayColor = color;
invalidate();
}
}
private void initializeRenderScript(Context context) {
mRenderScript = RenderScript.create(context);
mBlurScript = ScriptIntrinsicBlur.create(mRenderScript, Element.U8_4(mRenderScript));
}
private boolean prepare() {
final int width = mBlurredView.getWidth();
final int height = mBlurredView.getHeight();
if (mBlurringCanvas == null || mDownsampleFactorChanged
|| mBlurredViewWidth != width || mBlurredViewHeight != height) {
mDownsampleFactorChanged = false;
mBlurredViewWidth = width;
mBlurredViewHeight = height;
int scaledWidth = width / mDownsampleFactor;
int scaledHeight = height / mDownsampleFactor;
// This is no longer necessary on newer versions of RenderScript (23+)
// -------------------------------------------------------------------
// The following manipulation is to avoid some RenderScript artifacts at the edge.
//scaledWidth = scaledWidth - scaledWidth % 4 + 4;
//scaledHeight = scaledHeight - scaledHeight % 4 + 4;
if (mBlurredBitmap == null
|| mBlurredBitmap.getWidth() != scaledWidth
|| mBlurredBitmap.getHeight() != scaledHeight) {
mBitmapToBlur = Bitmap.createBitmap(scaledWidth, scaledHeight,
Bitmap.Config.ARGB_8888);
if (mBitmapToBlur == null) {
return false;
}
mBlurredBitmap = Bitmap.createBitmap(scaledWidth, scaledHeight,
Bitmap.Config.ARGB_8888);
if (mBlurredBitmap == null) {
return false;
}
}
mBlurringCanvas = new Canvas(mBitmapToBlur);
mBlurringCanvas.scale(1f / mDownsampleFactor, 1f / mDownsampleFactor);
mBlurInput = Allocation.createFromBitmap(mRenderScript, mBitmapToBlur,
Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
mBlurOutput = Allocation.createTyped(mRenderScript, mBlurInput.getType());
}
return true;
}
private void blur() {
mBlurInput.copyFrom(mBitmapToBlur);
mBlurScript.setInput(mBlurInput);
mBlurScript.forEach(mBlurOutput);
mBlurOutput.copyTo(mBlurredBitmap);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
invalidate();
checkForCircularReference();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mRenderScript != null) {
mRenderScript.destroy();
}
}
private void checkForCircularReference() {
// Need to wait until blurredView is set and the view is attached to window.
if (mBlurredView == null || getParent() == null) return;
boolean circularReference = (mBlurredView.findViewById(getId()) != null);
if (circularReference) {
ThemedReactContext reactContext = (ThemedReactContext) getContext();
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("ReactNativeBlurError",
"BlurView must not be a child of the view that is being blurred.");
setBlurredView(null);
invalidate();
}
}
private int mDownsampleFactor;
private int mOverlayColor;
private View mBlurredView;
private int mBlurredViewWidth, mBlurredViewHeight;
private boolean mDownsampleFactorChanged;
private Bitmap mBitmapToBlur, mBlurredBitmap;
private Canvas mBlurringCanvas;
private RenderScript mRenderScript;
private ScriptIntrinsicBlur mBlurScript;
private Allocation mBlurInput, mBlurOutput;
}

View File

@ -1,148 +0,0 @@
/**
* Basic [Android] Example for react-native-blur
* https://github.com/react-native-community/react-native-blur
*/
import React, { Component } from 'react';
import {
Dimensions,
Image,
InteractionManager,
StyleSheet,
Switch,
Text,
View,
findNodeHandle,
} from 'react-native';
import SegmentedControlTab from 'react-native-segmented-control-tab';
import { BlurView } from '@react-native-community/blur';
const BLUR_TYPES = ['xlight', 'light', 'dark'];
export default class Basic extends Component {
constructor() {
super();
this.state = {
showBlur: true,
viewRef: null,
activeSegment: 2,
blurType: 'dark',
};
}
imageLoaded() {
// Workaround for a tricky race condition on initial load.
InteractionManager.runAfterInteractions(() => {
setTimeout(() => {
this.setState({ viewRef: findNodeHandle(this.refs.backgroundImage) });
}, 500);
});
}
_onChange(selected) {
this.setState({
activeSegment: selected,
blurType: BLUR_TYPES[selected],
});
}
renderBlurView() {
const tintColor = ['#ffffff', '#000000'];
if (this.state.blurType === 'xlight') {tintColor.reverse();}
return (
<View style={styles.container}>
{this.state.viewRef && <BlurView
viewRef={this.state.viewRef}
style={styles.blurView}
blurRadius={9}
blurType={this.state.blurType}
// The following props are also available on Android:
// blurRadius={20}
// downsampleFactor={10}
// overlayColor={'rgba(0, 0, 255, .6)'} // set a blue overlay
/>}
<Text style={[styles.text, { color: tintColor[0] }]}>
Blur component (Android)
</Text>
<SegmentedControlTab
tintColor={tintColor}
style={{
width: Dimensions.get('window').width,
height: 28,
justifyContent: 'center',
alignItems: 'center',
}}
values={BLUR_TYPES}
orientation="horizontal"
selectedIndex={this.state.activeSegment}
onTabPress={this._onChange.bind(this)} />
</View>
);
}
render() {
return (
<View style={styles.container}>
<Image
source={require('./bgimage.jpeg')}
style={styles.image}
ref={'backgroundImage'}
onLoadEnd={this.imageLoaded.bind(this)} />
{ this.state.showBlur ? this.renderBlurView() : null }
<View
style={styles.blurToggle}>
<Switch
onValueChange={(value) => this.setState({showBlur: value})}
value={this.state.showBlur} />
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: 'transparent',
},
image: {
position: 'absolute',
left: 0,
top: 0,
bottom: 0,
right: 0,
resizeMode: 'cover',
width: null,
height: null,
},
blurView: {
position: 'absolute',
left: 0,
top: 0,
bottom: 0,
right: 0,
},
text: {
fontSize: 22,
fontWeight: 'bold',
textAlign: 'center',
margin: 10,
color: '#FFFFFF',
},
blurToggle: {
position: 'absolute',
top: 30,
right: 10,
alignItems: 'flex-end',
},
});

View File

@ -7,6 +7,7 @@ import {
Image,
SegmentedControlIOS,
StyleSheet,
Platform,
Switch,
Text,
View,
@ -59,42 +60,47 @@ export default class Basic extends Component {
<BlurView
blurType={this.state.blurBlurType}
blurAmount={100}
style={[styles.blurView]} />
<Text style={[styles.text, { color: tintColor }]}>
Blur component (iOS)
</Text>
<SegmentedControlIOS
values={['xlight', 'light', 'dark', 'regular', 'prominent']}
selectedIndex={this.state.blurActiveSegment}
onChange={(event) => {this._onBlurChange(event);}}
onValueChange={(value) => {this._onBlurValueChange(value);}}
tintColor={tintColor}
/>
style={[styles.blurView]}>
<Text style={[styles.text, { color: tintColor }]}>
Blur component (iOS)
</Text>
{{
Platform.OS === "ios" &&
<SegmentedControlIOS
values={['xlight', 'light', 'dark', 'regular', 'prominent']}
selectedIndex={this.state.blurActiveSegment}
onChange={(event) => {this._onBlurChange(event);}}
onValueChange={(value) => {this._onBlurValueChange(value);}}
tintColor={tintColor}
/>
}}
</BlurView>
</View>
{/*
VibrancyView is only supported on iOS, and must contain child views,
otherwise the vibrancy effect doesn't work.
*/}
<VibrancyView
blurType={this.state.vibrancyBlurType}
blurAmount={10}
style={[styles.container, styles.blurContainer]}>
{{
Platform.OS === "ios" &&
<VibrancyView
blurType={this.state.vibrancyBlurType}
blurAmount={10}
style={[styles.container, styles.blurContainer]}>
<Text style={styles.text}>
Vibrancy component (iOS-only)
</Text>
<Text style={styles.text}>
Vibrancy component (iOS-only)
</Text>
<SegmentedControlIOS
values={['xlight', 'light', 'dark', 'regular', 'prominent']}
selectedIndex={this.state.vibrancyActiveSegment}
onChange={(event) => {this._onVibrancyChange(event);}}
onValueChange={(value) => {this._onVibrancyValueChange(value);}}
tintColor="white"
/>
</VibrancyView>
<SegmentedControlIOS
values={['xlight', 'light', 'dark', 'regular', 'prominent']}
selectedIndex={this.state.vibrancyActiveSegment}
onChange={(event) => {this._onVibrancyChange(event);}}
onValueChange={(value) => {this._onVibrancyValueChange(value);}}
tintColor="white"
/>
</VibrancyView>
}}
</View>
);
}

1
index.d.ts vendored
View File

@ -13,7 +13,6 @@ export interface BlurViewProperties {
| "extraDark"
blurAmount?: number // 0 - 100
style?: StyleProp<ViewStyle>
viewRef?: number | null
blurRadius?: number
downsampleFactor?: number
overlayColor?: string

View File

@ -20,7 +20,6 @@ export type BlurViewProps = {
blurType: BlurType,
blurAmount: number, // 0 - 100
style?: ?ViewStyleProp,
viewRef?: ?React.ElementRef<any>,
};
export class BlurView extends React.Component<BlurViewProps> {

View File

@ -1,6 +1,6 @@
{
"name": "@react-native-community/blur",
"version": "3.4.1",
"version": "3.5.0",
"description": "React Native Blur component",
"main": "index.js",
"scripts": {

View File

@ -64,24 +64,18 @@ class BlurView extends Component {
}
render() {
if (this.props.children != null) {
throw new Error(
'[ReactNativeBlur]: BlurView cannot contain any child views on Android. ' +
'You should use "position: absolute" on the BlurView, ' +
'and place other views in front of it.'
);
}
const { viewRef, style } = this.props;
const { style } = this.props;
return (
<NativeBlurView
viewRef={viewRef}
blurRadius={this.blurRadius()}
downsampleFactor={this.downsampleFactor()}
overlayColor={this.overlayColor()}
pointerEvents="none"
style={StyleSheet.compose(styles.transparent, style)}
/>
>
{this.props.children}
</NativeBlurView>
);
}
}
@ -98,10 +92,6 @@ BlurView.propTypes = {
blurRadius: PropTypes.number,
downsampleFactor: PropTypes.number,
overlayColor: PropTypes.string,
viewRef: Platform.select({
android: PropTypes.number.isRequired,
default: PropTypes.number,
}),
};
BlurView.defaultProps = {