はじめに
シャドウのスタイルをつけたコンポーネントでバナー画像をラップすると、そのままでは高さが設定されず、上下に余計なマージンが入ってしまう感じになってしまいました。
その解決策として、シャドウのコンポーネントで高さ(height)を設定した後に、コールバック化したchildrenにheightをパラメータとして渡してあげるようにしました。
childrenにパラメータを渡す方法をお探しの方には参考となる内容だと思います。
実装
いらすとやからとってきたバナー画像に、以下のようなシャドウをつけます。
まず、シャドウのスタイルをつけるコンポーネントShadowStyledView
を作成します。
onLayout
でView
の width
を取得し、このwidth
にアスペクト比をかけてheight
を算出します。
そして、children
がfunction
のときの場合のみ、width
とheight
を渡してあげるようにします。
また、imageWidth
とimageHeight
をpropsに置くことで、アスペクト比を可変させることもできます。
type ResizedProps = {
width: number;
height: number;
};
type ShadowStyledViewProps = {
containerStyle?: StyleProp<ViewStyle>;
imageWidth?: number;
imageHeight?: number;
children: ReactNode | ((resizedProps: ResizedProps) => ReactNode);
};
export const ShadowStyledView: React.FC<ShadowStyledViewProps> = React.memo(
({ containerStyle, imageWidth = 800, imageHeight = 287, children }) => {
const { onLayout, width } = useLayout();
const height = (width * imageHeight) / imageWidth;
return (
<View onLayout={onLayout} style={[styles.container, containerStyle]}>
{typeof children === "function" ? children({ width, height }) : children}
</View>
);
},
);
ShadowStyledView.displayName = "ShadowStyledView";
const styles = StyleSheet.create({
container: {
...Platform.select({
ios: {
shadowColor: "rgba(0,0,0,0.15)",
shadowOffset: { width: 0, height: 5 },
shadowRadius: 1,
shadowOpacity: 1,
},
android: { elevation: 5 },
}),
},
});
なお、onLayout
とwidth
については、カスタムフックuseLayout()
にまとめます。
export function useLayout() {
const [layout, setLayout] = useState({
x: 0,
y: 0,
width: 0,
height: 0,
});
const onLayout = useCallback(
({ nativeEvent }: LayoutChangeEvent) => setLayout(nativeEvent.layout),
[],
);
return {
onLayout,
...layout,
};
}
バナーのコンポーネントQuestionBanner
は以下のようになります。
ShadowStyledView
の中身でImage
をコールバックで呼び、height
をパラメータとして渡しています。
これで指定したアスペクト比でシャドウをつけたバナーを表示させることができます。
type QuestionBannerProps = {
containerStyle?: StyleProp<ViewStyle>;
children?: never;
};
export const QuestionBanner: React.FC<QuestionBannerProps> = React.memo(({ containerStyle }) => {
return (
<ShadowStyledView containerStyle={[containerStyle]}>
{({ height }) => (
<Image
source={QuestionBannerImage}
style={{
height,
resizeMode: "contain",
borderRadius: 5,
width: "100%",
}}
/>
)}
</ShadowStyledView>
);
});
QuestionBanner.displayName = "QuestionBanner";
参考資料