手把手教你实现微信小视频iOS代码实现

前段时间项目要求需要在聊天模块中加入类似微信的小视频功能,这边博客主要是为了总结遇到的问题和解决方法,希望能够对有同样需求的朋友有所帮助。

效果预览:

这里先罗列遇到的主要问题:  
 1.视频剪裁  微信的小视频只是取了摄像头获取的一部分画面
 2.滚动预览的卡顿问题  AVPlayer播放视频在滚动中会出现很卡的问题

接下来让我们一步步来实现。
Part 1 实现视频录制
1.录制类WKMovieRecorder实现
创建一个录制类WKMovieRecorder,负责视频录制。

@interface WKMovieRecorder : NSObject

+ (WKMovieRecorder*) sharedRecorder;
 - (instancetype)initWithMaxDuration:(NSTimeInterval)duration;
 @end

定义回调block

/**
 * 录制结束
 *
 * @param info   回调信息
 * @param isCancle YES:取消 NO:正常结束
 */
typedef void(^FinishRecordingBlock)(NSDictionary *info, WKRecorderFinishedReason finishReason);
/**
 * 焦点改变
 */
typedef void(^FocusAreaDidChanged)();
/**
 * 权限验证
 *
 * @param success 是否成功
 */
typedef void(^AuthorizationResult)(BOOL success);

@interface WKMovieRecorder : NSObject
//回调
@property (nonatomic, copy) FinishRecordingBlock finishBlock;//录制结束回调
@property (nonatomic, copy) FocusAreaDidChanged focusAreaDidChangedBlock;
@property (nonatomic, copy) AuthorizationResult authorizationResultBlock;
@end

定义一个cropSize用于视频裁剪 
@property (nonatomic, assign) CGSize cropSize;

接下来就是capture的实现了,这里代码有点长,懒得看的可以直接看后面的视频剪裁部分

录制配置:

@interface WKMovieRecorder ()
<
AVCaptureVideoDataOutputSampleBufferDelegate,
AVCaptureAudioDataOutputSampleBufferDelegate,
WKMovieWriterDelegate
>

{
  AVCaptureSession* _session;
  AVCaptureVideoPreviewLayer* _preview;
  WKMovieWriter* _writer;
  //暂停录制
  BOOL _isCapturing;
  BOOL _isPaused;
  BOOL _discont;
  int _currentFile;
  CMTime _timeOffset;
  CMTime _lastVideo;
  CMTime _lastAudio;

  NSTimeInterval _maxDuration;
}

// Session management.
@property (nonatomic, strong) dispatch_queue_t sessionQueue;
@property (nonatomic, strong) dispatch_queue_t videoDataOutputQueue;
@property (nonatomic, strong) AVCaptureSession *session;
@property (nonatomic, strong) AVCaptureDevice *captureDevice;
@property (nonatomic, strong) AVCaptureDeviceInput *videoDeviceInput;
@property (nonatomic, strong) AVCaptureStillImageOutput *stillImageOutput;
@property (nonatomic, strong) AVCaptureConnection *videoConnection;
@property (nonatomic, strong) AVCaptureConnection *audioConnection;
@property (nonatomic, strong) NSDictionary *videoCompressionSettings;
@property (nonatomic, strong) NSDictionary *audioCompressionSettings;
@property (nonatomic, strong) AVAssetWriterInputPixelBufferAdaptor *adaptor;
@property (nonatomic, strong) AVCaptureVideoDataOutput *videoDataOutput;

//Utilities
@property (nonatomic, strong) NSMutableArray *frames;//存储录制帧
@property (nonatomic, assign) CaptureAVSetupResult result;
@property (atomic, readwrite) BOOL isCapturing;
@property (atomic, readwrite) BOOL isPaused;
@property (nonatomic, strong) NSTimer *durationTimer;

@property (nonatomic, assign) WKRecorderFinishedReason finishReason;

@end

实例化方法:

+ (WKMovieRecorder *)sharedRecorder
{
  static WKMovieRecorder *recorder;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    recorder = [[WKMovieRecorder alloc] initWithMaxDuration:CGFLOAT_MAX];
  });

  return recorder;
}

- (instancetype)initWithMaxDuration:(NSTimeInterval)duration
{
  if(self = [self init]){
    _maxDuration = duration;
    _duration = 0.f;
  }

  return self;
}

- (instancetype)init
{
  self = [super init];
  if (self) {
    _maxDuration = CGFLOAT_MAX;
    _duration = 0.f;
    _sessionQueue = dispatch_queue_create("wukong.movieRecorder.queue", DISPATCH_QUEUE_SERIAL );
    _videoDataOutputQueue = dispatch_queue_create( "wukong.movieRecorder.video", DISPATCH_QUEUE_SERIAL );
    dispatch_set_target_queue( _videoDataOutputQueue, dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0 ) );
  }
  return self;
}

2.初始化设置
初始化设置分别为session创建、权限检查以及session配置
1).session创建
self.session = [[AVCaptureSession alloc] init];
self.result = CaptureAVSetupResultSuccess;

2).权限检查

//权限检查
    switch ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) {
      case AVAuthorizationStatusNotDetermined: {
        [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
          if (granted) {
            self.result = CaptureAVSetupResultSuccess;
          }
        }];
        break;
      }
      case AVAuthorizationStatusAuthorized: {

        break;
      }
      default:{
        self.result = CaptureAVSetupResultCameraNotAuthorized;
      }
    }

    if ( self.result != CaptureAVSetupResultSuccess) {

      if (self.authorizationResultBlock) {
        self.authorizationResultBlock(NO);
      }
      return;
    }

3).session配置
session配置是需要注意的是AVCaptureSession的配置不能在主线程, 需要自行创建串行线程。 
3.1.1 获取输入设备与输入流

AVCaptureDevice *captureDevice = [[self class] deviceWithMediaType:AVMediaTypeVideo preferringPosition:AVCaptureDevicePositionBack];
 _captureDevice = captureDevice;

 NSError *error = nil;
 _videoDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:captureDevice error:&error];

 if (!_videoDeviceInput) {
  NSLog(@"未找到设备");
 }

3.1.2 录制帧数设置
帧数设置的主要目的是适配iPhone4,毕竟是应该淘汰的机器了

int frameRate;
      if ( [NSProcessInfo processInfo].processorCount == 1 )
      {
        if ([self.session canSetSessionPreset:AVCaptureSessionPresetLow]) {
          [self.session setSessionPreset:AVCaptureSessionPresetLow];
        }
        frameRate = 10;
      }else{
        if ([self.session canSetSessionPreset:AVCaptureSessionPreset640x480]) {
          [self.session setSessionPreset:AVCaptureSessionPreset640x480];
        }
        frameRate = 30;
      }

      CMTime frameDuration = CMTimeMake( 1, frameRate );

      if ( [_captureDevice lockForConfiguration:&error] ) {
        _captureDevice.activeVideoMaxFrameDuration = frameDuration;
        _captureDevice.activeVideoMinFrameDuration = frameDuration;
        [_captureDevice unlockForConfiguration];
      }
      else {
        NSLog( @"videoDevice lockForConfiguration returned error %@", error );
      }

3.1.3 视频输出设置
视频输出设置需要注意的问题是:要设置videoConnection的方向,这样才能保证设备旋转时的显示正常。

 //Video
      if ([self.session canAddInput:_videoDeviceInput]) {

        [self.session addInput:_videoDeviceInput];
        self.videoDeviceInput = _videoDeviceInput;
        [self.session removeOutput:_videoDataOutput];

        AVCaptureVideoDataOutput *videoOutput = [[AVCaptureVideoDataOutput alloc] init];
        _videoDataOutput = videoOutput;
        videoOutput.videoSettings = @{ (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) };

        [videoOutput setSampleBufferDelegate:self queue:_videoDataOutputQueue];

        videoOutput.alwaysDiscardsLateVideoFrames = NO;

        if ( [_session canAddOutput:videoOutput] ) {
          [_session addOutput:videoOutput];

          [_captureDevice addObserver:self forKeyPath:@"adjustingFocus" options:NSKeyValueObservingOptionNew context:FocusAreaChangedContext];

          _videoConnection = [videoOutput connectionWithMediaType:AVMediaTypeVideo];

          if(_videoConnection.isVideoStabilizationSupported){
            _videoConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
          }

          UIInterfaceOrientation statusBarOrientation = [UIApplication sharedApplication].statusBarOrientation;
          AVCaptureVideoOrientation initialVideoOrientation = AVCaptureVideoOrientationPortrait;
          if ( statusBarOrientation != UIInterfaceOrientationUnknown ) {
            initialVideoOrientation = (AVCaptureVideoOrientation)statusBarOrientation;
          }

          _videoConnection.videoOrientation = initialVideoOrientation;
        }

      }
      else{
        NSLog(@"无法添加视频输入到会话");
      }

3.1.4 音频设置 
需要注意的是为了不丢帧,需要把音频输出的回调队列放在串行队列中

//audio
      AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
      AVCaptureDeviceInput *audioDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error];

      if ( ! audioDeviceInput ) {
        NSLog( @"Could not create audio device input: %@", error );
      }

      if ( [self.session canAddInput:audioDeviceInput] ) {
        [self.session addInput:audioDeviceInput];

      }
      else {
        NSLog( @"Could not add audio device input to the session" );
      }

      AVCaptureAudioDataOutput *audioOut = [[AVCaptureAudioDataOutput alloc] init];
      // Put audio on its own queue to ensure that our video processing doesn't cause us to drop audio
      dispatch_queue_t audioCaptureQueue = dispatch_queue_create( "wukong.movieRecorder.audio", DISPATCH_QUEUE_SERIAL );
      [audioOut setSampleBufferDelegate:self queue:audioCaptureQueue];

      if ( [self.session canAddOutput:audioOut] ) {
        [self.session addOutput:audioOut];
      }
      _audioConnection = [audioOut connectionWithMediaType:AVMediaTypeAudio];

还需要注意一个问题就是对于session的配置代码应该是这样的 
[self.session beginConfiguration];

...配置代码

[self.session commitConfiguration];

由于篇幅问题,后面的录制代码我就挑重点的讲了。
3.2  视频存储
现在我们需要在AVCaptureVideoDataOutputSampleBufferDelegate与AVCaptureAudioDataOutputSampleBufferDelegate的回调中,将音频和视频写入沙盒。在这个过程中需要注意的,在启动session后获取到的第一帧黑色的,需要放弃。
3.2.1 创建WKMovieWriter类来封装视频存储操作
WKMovieWriter的主要作用是利用AVAssetWriter拿到CMSampleBufferRef,剪裁后再写入到沙盒中。
这是剪裁配置的代码,AVAssetWriter会根据cropSize来剪裁视频,这里需要注意的一个问题是cropSize的width必须是320的整数倍,不然的话剪裁出来的视频右侧会出现一条绿色的线

 NSDictionary *videoSettings;
  if (_cropSize.height == 0 || _cropSize.width == 0) {

    _cropSize = [UIScreen mainScreen].bounds.size;

  }

  videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
           AVVideoCodecH264, AVVideoCodecKey,
           [NSNumber numberWithInt:_cropSize.width], AVVideoWidthKey,
           [NSNumber numberWithInt:_cropSize.height], AVVideoHeightKey,
           AVVideoScalingModeResizeAspectFill,AVVideoScalingModeKey,
           nil];

至此,视频录制就完成了。
接下来需要解决的预览的问题了

Part 2 卡顿问题解决
1.1 gif图生成 
通过查资料发现了这篇blog 介绍说微信团队解决预览卡顿的问题使用的是播放图片gif,但是博客中的示例代码有问题,通过CoreAnimation来播放图片导致内存暴涨而crash。但是,还是给了我一些灵感,因为之前项目的启动页用到了gif图片的播放,所以我就想能不能把视频转成图片,然后再转成gif图进行播放,这样不就解决了问题了吗。于是我开始google功夫不负有心人找到了,图片数组转gif图片的方法。

gif图转换代码

static void makeAnimatedGif(NSArray *images, NSURL *gifURL, NSTimeInterval duration) {
  NSTimeInterval perSecond = duration /images.count;

  NSDictionary *fileProperties = @{
                   (__bridge id)kCGImagePropertyGIFDictionary: @{
                       (__bridge id)kCGImagePropertyGIFLoopCount: @0, // 0 means loop forever
                       }
                   };

  NSDictionary *frameProperties = @{
                   (__bridge id)kCGImagePropertyGIFDictionary: @{
                       (__bridge id)kCGImagePropertyGIFDelayTime: @(perSecond), // a float (not double!) in seconds, rounded to centiseconds in the GIF data
                       }
                   };

  CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)gifURL, kUTTypeGIF, images.count, NULL);
  CGImageDestinationSetProperties(destination, (__bridge CFDictionaryRef)fileProperties);

  for (UIImage *image in images) {
    @autoreleasepool {

      CGImageDestinationAddImage(destination, image.CGImage, (__bridge CFDictionaryRef)frameProperties);
    }
  }

  if (!CGImageDestinationFinalize(destination)) {
    NSLog(@"failed to finalize image destination");
  }else{

  }
  CFRelease(destination);
}

转换是转换成功了,但是出现了新的问题,使用ImageIO生成gif图片时会导致内存暴涨,瞬间涨到100M以上,如果多个gif图同时生成的话一样会crash掉,为了解决这个问题需要用一个串行队列来进行gif图的生成  

1.2 视频转换为UIImages
主要是通过AVAssetReader、AVAssetTrack、AVAssetReaderTrackOutput 来进行转换

//转成UIImage
- (void)convertVideoUIImagesWithURL:(NSURL *)url finishBlock:(void (^)(id images, NSTimeInterval duration))finishBlock
{
    AVAsset *asset = [AVAsset assetWithURL:url];
    NSError *error = nil;
    self.reader = [[AVAssetReader alloc] initWithAsset:asset error:&error];

    NSTimeInterval duration = CMTimeGetSeconds(asset.duration);
    __weak typeof(self)weakSelf = self;
    dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    dispatch_async(backgroundQueue, ^{
      __strong typeof(weakSelf) strongSelf = weakSelf;
      NSLog(@"");

      if (error) {
        NSLog(@"%@", [error localizedDescription]);

      }

      NSArray *videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo];

      AVAssetTrack *videoTrack =[videoTracks firstObject];
      if (!videoTrack) {
        return ;
      }
      int m_pixelFormatType;
      //   视频播放时,
      m_pixelFormatType = kCVPixelFormatType_32BGRA;
      // 其他用途,如视频压缩
      //  m_pixelFormatType = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;

      NSMutableDictionary *options = [NSMutableDictionary dictionary];
      [options setObject:@(m_pixelFormatType) forKey:(id)kCVPixelBufferPixelFormatTypeKey];
      AVAssetReaderTrackOutput *videoReaderOutput = [[AVAssetReaderTrackOutput alloc] initWithTrack:videoTrack outputSettings:options];

      if ([strongSelf.reader canAddOutput:videoReaderOutput]) {

        [strongSelf.reader addOutput:videoReaderOutput];
      }
      [strongSelf.reader startReading];

      NSMutableArray *images = [NSMutableArray array];
      // 要确保nominalFrameRate>0,之前出现过android拍的0帧视频
      while ([strongSelf.reader status] == AVAssetReaderStatusReading && videoTrack.nominalFrameRate > 0) {
         @autoreleasepool {
        // 读取 video sample
        CMSampleBufferRef videoBuffer = [videoReaderOutput copyNextSampleBuffer];

        if (!videoBuffer) {
          break;
        }

        [images addObject:[WKVideoConverter convertSampleBufferRefToUIImage:videoBuffer]];

        CFRelease(videoBuffer);
      }

     }
      if (finishBlock) {
        dispatch_async(dispatch_get_main_queue(), ^{
          finishBlock(images, duration);
        });
      }
    });

}

在这里有一个值得注意的问题,在视频转image的过程中,由于转换时间很短,在短时间内videoBuffer不能够及时得到释放,在多个视频同时转换时任然会出现内存问题,这个时候就需要用autoreleasepool来实现及时释放

@autoreleasepool {
 // 读取 video sample
 CMSampleBufferRef videoBuffer = [videoReaderOutput copyNextSampleBuffer];
   if (!videoBuffer) {
   break;
   }

   [images addObject:[WKVideoConverter convertSampleBufferRefToUIImage:videoBuffer]];
    CFRelease(videoBuffer); }

至此,微信小视频的难点(我认为的)就解决了,至于其他的实现代码请看demo就基本实现了,demo可以从这里下载。

视频暂停录制 http://www.gdcl.co.uk/2013/02/20/iPhone-Pause.html
视频crop绿边解决 http://stackoverflow.com/questions/22883525/avassetexportsession-giving-me-a-green-border-on-right-and-bottom-of-output-vide
视频裁剪:http://stackoverflow.com/questions/15737781/video-capture-with-11-aspect-ratio-in-ios/16910263#16910263
CMSampleBufferRef转image https://developer.apple.com/library/ios/qa/qa1702/_index.html
微信小视频分析 http://www.jianshu.com/p/3d5ccbde0de1

感谢以上文章的作者

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • iOS微信第三方登录实现

    一.接入微信第三方登录准备工作. 移动应用微信登录是基于OAuth2.0协议标准构建的微信OAuth2.0授权登录系统. 在进行微信OAuth2.0授权登录接入之前,在微信开放平台注册开发者帐号,并拥有一个已审核通过的移动应用,并获得相应的AppID和AppSecret,申请微信登录且通过审核后,可开始接入流程.(注意) 1.下载iOS微信SDK. 下载地址 2.将SDK放到工程目录中. 3.补充导入一些依赖框架. 4.添加URL Types 5.添加iOS9 URL Schemes. 注意:如

  • ionic在开发ios系统微信时键盘挡住输入框的解决方法(键盘弹出问题)

    在使用ionic开发IOS系统微信的时候会有一个苦恼的问题,填写表单的时候键盘会挡住输入框,其实并不算什么大问题,只要用户输入一个字就可以立刻看见输入框了. 可惜的是,有些客户是不讲理的,他才不管这个问题,反正就是不行,所以在一天睡觉的时候突然惊醒,想出来这个方案. 我就不仔细讲代码了,直接上图 angular.module('MyApp') .directive('focusInput', ['$ionicScrollDelegate', '$window', '$timeout', '$io

  • 微信JSSDK多图片上传并且解决IOS系统上传一直加载的问题

    微信多图片上传必须挨个上传,也就是不能并行,得串行: 那么我们可以定义一个如下所示的上传函数: var serverIds = []; function uploadImages(localImagesIds) { if (localImagesIds.length === 0) { $.showPreloader('正在提交数据...'); $('form').submit(); } wx.uploadImage({ localId: localImagesIds[0], // 需要上传的图片

  • 微信支付开发IOS图文教程案例

    前言:下面介绍微信支付的开发流程的细节,图文并茂,你可以按照我的随笔流程过一遍代码.包你也学会了微信支付.而且支付也是面试常问的内容. 正文: 1.首先在开始使用微信支付之前,有一些东西是开发者必须要知道的,打开下面链接: https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=3_1 然后可以看到下面的页面,这个就是微信支付商户平台的开发文档,很多东西是可以查阅和了解的,在开发使用微信SDK支付功能的时候,遇到了问题也可以到这找找相关须知信

  • iOS中类似微信红点显示功能

    设计思路:给UIView增加一个分类 所有的视图都可以根据需要来进行红点显示 #import <UIKit/UIKit.h> @interface UIView (CHRRedDot) @property (readonly, nonatomic) CALayer * chr_redDotLayer; /** 红点圆心的位置,与各个边之间的距离.如果距离<=0,则忽略距离 */ @property (nonatomic, assign) UIEdgeInsets chr_redDotEd

  • 微信支付终于成功了(安卓、iOS)在此分享

    经过了几天的痛苦煎熬,终于把微信支付调通,整个调试过程很痛苦,痛苦的主要来源是微信支付的调试真的是,以前调试公众号支付也是一波三折啊.好吧,开始!首先说明,我这里主要没有使用getToken,getOrder方法,我的所有参数全部是在后端生成传递给前端的,看了一下前面朋友分享的源代码,还用到了jquery,md5,sha对于新手来说简直是天文啊,而且jquery在apicloud中效率不好,所以放弃了研究那个代码,另外官方也说了,最好签名等参数全部服务器端生成,微信也是这么说的. 注意:微信本身

  • IOS客户端接入微信支付

    实际上,从代码的角度,调起支付APP就是把一些关键的参数通过一定方式打包成为一个订单,然后发送到支付平台的服务器.所以,只要搞清楚了参数设置,搞清楚了每个支付平台的SDK里面一些关键API的使用,基本上就可以很简单的支持支付. 今天记录一下客户端里面,如何支持微信支付.首先.我们要仔细阅读一下微信SDK的开发文档,了解一下整个支付的大概流程. 然后根据提示,把相应的SDK下载下来,所谓的SDK,也就是一个链接库和两个头文件,很简单. 下载完毕,需要把SDK导入到工程里面,并且配置一下工程.因为开

  • 手把手教你实现微信小视频iOS代码实现

    前段时间项目要求需要在聊天模块中加入类似微信的小视频功能,这边博客主要是为了总结遇到的问题和解决方法,希望能够对有同样需求的朋友有所帮助. 效果预览: 这里先罗列遇到的主要问题:  1.视频剪裁  微信的小视频只是取了摄像头获取的一部分画面  2.滚动预览的卡顿问题  AVPlayer播放视频在滚动中会出现很卡的问题 接下来让我们一步步来实现. Part 1 实现视频录制 1.录制类WKMovieRecorder实现 创建一个录制类WKMovieRecorder,负责视频录制. @interfa

  • Android 微信小视频录制功能实现详细介绍

    Android 微信小视频录制功能 开发之前 这几天接触了一下和视频相关的控件, 所以, 继之前的微信摇一摇, 我想到了来实现一下微信小视频录制的功能, 它的功能点比较多, 我每天都抽出点时间来写写, 说实话, 有些东西还是比较费劲, 希望大家认真看看, 说得不对的地方还请大家在评论中指正. 废话不多说, 进入正题. 开发环境 最近刚更新的, 没更新的小伙伴们抓紧了 Android Studio 2.2.2 JDK1.7 API 24 Gradle 2.2.2 相关知识点 视频录制界面 Surf

  • 微信小程序iOS下拉白屏晃动问题解决方案

    这篇文章主要介绍了微信小程序iOS下拉白屏晃动问题解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 问题 感觉ios的小程序每个页面都可以下拉出现白屏 有时页面带有滑动的属性会跟着晃动,体验不是很好 解决办法: 先禁止页面下拉 <config> { navigationBarTitleText: "购物车", disableScroll:true } </config> 这样的话页面整个都拉不动了,下面溢

  • 手把手带你入门微信小程序新框架Kbone的使用

    Kbone 框架 前些天在微信上收到了微信开发者公众号的文章推送<揭开微信小程序Kbone的神秘面纱>,心想:微信小程序有新框架了?抱着学习的态度点进去看了一眼,看过之后觉得这框架也太宠开发者了吧,不愧是微信团队出品. 原来这个框架早在去年就已经发布了,看完只恨自己没有早点知道消息开始学习这个框架.我写本文的目的也是为了跟个风,想要让更多的人能够知道这个框架,感受它的便利,希望好学的你可以停下脚步看看~ Kbone 是什么? 看到这里我也不多说了,简单介绍一下 Kbone 是什么.用官方高大上

  • 用Python手把手教你实现2048小游戏

    一.开发环境 Python版本:3.6.4 相关模块: pygame模块: 以及一些Python自带的模块. 二.环境搭建 安装Python并添加到环境变量,pip安装需要的相关模块即可. 三.原理介绍 "使用方向键移动方块,两个数字相同的方块撞在一起后,将会合并为一个数字是原来两倍的新方块.游戏的时候尽可能多地合并这些数字方块就行了." 大概了解了游戏规则之后,我们就可以开始写这个游戏啦~首先,进行一下游戏初始化操作并播放一首自己喜欢的游戏背景音乐: # 游戏初始化 pygame.i

  • 手把手教你uniapp和小程序分包(图文)

    目录 一.小程序分包 二.uniapp分包小程序 分包步骤: 1.配置manifest.json 2.配置pages.json 3.分包预载配置(preloadRule) 一.小程序分包 每个使用分包小程序必定含有一个主包.所谓的主包,即放置默认启动页面/TabBar 页面,以及一些所有分包都需用到公共资源/JS 脚本:而分包则是根据开发者的配置进行划分. 在小程序启动时,默认会下载主包并启动主包内页面,当用户进入分包内某个页面时,客户端会把对应分包下载下来,下载完成后再进行展示 目前小程序分包

  • 微信小程序实用代码段(收藏版)

    前言 排名不分先后,按自己的习惯来的. 总结经验,不喜勿喷哦~ 一.tab切换 <view class=" {{currentTab==0 ? 'select' : ''}}" data-current="0" bindtap="swichNav"> tab1</view> <view class=" {{currentTab==1 ? 'select' : ''}}" data-current=

  • 一步步教你实现微信小程序自定义组件

    目录 前言 组件的声明与使用 组件通信 方法一 WXML 数据绑定 方法二 事件 方法三 selectComponent 获取组件实例对象 方法四 url 参数通信 参数过长怎么办?路由 api 不支持携带参数呢? 方法五 EventChannel 事件派发通信 会出现数据无法监听的情况吗? 使用自定义的事件中心 EventBus 小结 附:组件和页面的区别 总结 前言 在微信小程序开发过程中,对于一些可能在多个页面都使用的页面模块,可以把它封装成一个组件,以提高开发效率.虽然说我们可以引入整个

  • 手把手教你把网上下载视频刻录成VCD、DVD的图文教程第1/2页

    准备以下必备工具: 1:realalt123.real解码器,一定用它,兼容性好. 2:ac3filter. ac3音频解码器. 3:TMPGEnc-2.54-Plus.视频转换软件,一定用它,兼容性好. 4:TMPGEnc DVD Author.DVD打包软件. 5:DivX6.x.DivX文件解码器.网上下载avi视频几乎都用到他. 6:nero.刻录软件. 以上软件安装完毕,继续以下操作. 你要转换的视频可能是pal制式,也可能是ntsc制式,有个简单的识别方法,用暴风的播放器播放视频,查

  • 手把手教你设置IntelliJ IDEA 的彩色代码主题的图文教程

    温馨提示:本教程的 GitHub 地址为「intellij-idea-tutorial」,欢迎感兴趣的童鞋Star.Fork,纠错. 首先,给出一系列 IntelliJ IDEA 代码的彩色主题,供大家选择: VibrantUnknown(Darcula) FadeComments NicePython Solarized Havenjark GeditForElegantGnome Gvim 在选完我们中意的主题之后,需要大家到「intellij-idea-tutorial」中下载相应的主题.

随机推荐