rn-relates icon indicating copy to clipboard operation
rn-relates copied to clipboard

RN实现长屏截图

Open ljunb opened this issue 8 years ago • 4 comments

当前参与的项目中,个别RN界面需要生成长屏图片进行分享,目前是使用了 react-native-view-shot 。安装之后,简单使用方法:

import ViewShot, {captureRef} from 'react-native-view-shot';

export default class SomeView extends Component {
  render() {
    return(
      <View style={styles.container}>
        <ViewShot>
          <ScrollView ref={r => this.scrollView = r}>
            ...
          </ScrollView>
        </ViewShot>
      </View>
    )
  }
}

调用方法:

handleCaptureImage = () => captureRef(this.scrollView, {snapshotContentContainer: true}).then(uri => this.setState({uri}));

文档中对snapshotContentContainer 有说明,具体可参阅 captureRef(view, options) ,注意这个参数其实是属于options里面的,文档罗列参数的层级容易让人误会,我在尝试时就以为它跟options是同层级关系,导致长屏截图无果。

因为其他原生界面也有截屏需求,且同事已经实现相应界面,所以计划在RN界面截图之后,就将图片uri发送到原生,由UIScrollView配合UIImageView来展示图片,同事的实现代码中,contentSize由截图的size决定。

一切都按部就班的进行,但是图片展示出来之后,发现宽度居然是屏幕两倍,导致UIScrollVeiw可以左右滑动,并且高度值也比预想的大,总之问题表现就是图片变大了。于是断点调试打印下图片对象:

(lldb) po shotImage
<UIImage: 0x1c04b8180> size {750, 2211} orientation 0 scale 1.000000

可以看到图片sizewidth750,而自己手机是iPhone 6s,截图宽度是375才正确,这里明显放大了一倍。把展示图片的contentSizewidth改为屏幕宽度,图片明显被压缩了,这不是合适的处理方法。

当对一个组件有疑问的时候,自然是去查看源码实现了。见 源码line67源码line92 ,当没有在options中设置宽高时,且截图内容组件为RCTScrollView子类,则截图size为该可滚动组件的contentSize,这没毛病。再看 源码line108,此行即为实际绘制截图的代码,为何导致当前问题,基本可以在该行代码找到答案了。

这里涉及一个绘图方法UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale),各参数说明:

  • size:很明显,该参数用于设置想要渲染的图片的尺寸
  • opaque:指定所生成图片的背景是否为不透明,YES为黑色,NO为透明
  • scale:表示位图的缩放比例,如果设置为0,则让图片的缩放因子根据屏幕的分辨率而变化

再看组件的实现代码:

UIGraphicsBeginImageContextWithOptions(size, NO, 0);

所以组件在绘制时,位图的缩放比例是由屏幕分辨率决定的,而自己的手机的[UIScreen mainScreen].scale2,所以图片实际被放大了一倍。注意不要被上面lldb输出的scale给搞混了,那个是生成之后的图片比例。

既然图片被放大,那么显示之前,再做个缩放好了:

  ...
  // 由截图uri生成UIImage
  UIImage *shotImage = [[UIImage alloc] initWithContentsOfFile:uri];
  CGFloat shotImgW = shotImage.size.width;
  CGFloat shotImgH = shotImage.size.height;
  CGFloat targetW = SCREEN_WIDTH;
  CGFloat targetH =(shotImgH * targetW) / shotImgW;
  CGSize targetSize = CGSizeMake(targetW, targetH);

  // scale不为1时,采用该方法,否则缩放后不清晰
  // 为避免内存占用过高,scale为2或3时,统一设置缩放比例为2
  if ([[UIScreen mainScreen] scale] == 2.0 || [[UIScreen mainScreen] scale] == 3.0) {
    UIGraphicsBeginImageContextWithOptions(targetSize, NO, 2.0);
  } else {
    UIGraphicsBeginImageContext(targetSize);
  }
  [shotImage drawInRect:CGRectMake(0, 0, targetW, targetH)];

  // 最终图片
  UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();
  UIGraphicsEndImageContext();
  ...

至此,由RN生成长屏截图,再由原生UIImageView正确展示图片的功能,就完成了☕️。

ljunb avatar Oct 11 '17 09:10 ljunb

你好,你这个是iOS平台的,Android平台的有用过吗?会不会出现截图很模糊,是怎么处理的?

jun58 avatar Oct 17 '19 09:10 jun58

@jun58 这个是跨平台的,没有遇到截图模糊的情况

ljunb avatar Oct 23 '19 06:10 ljunb

@ljunb 现状是,图越长越模糊

jun58 avatar Oct 23 '19 06:10 jun58

@jun58 看来那年的业务场景,还没覆盖到这个。你可以在issue列表找找看吧,或者反馈下给作者

ljunb avatar Oct 23 '19 07:10 ljunb