Ionic Frameworkionic2hybrid APP(ionic)

[Ionic 2从入门到精通] 4.4 Reddit API和H

2018-05-16  本文已影响0人  老牛啃码

Giflist最有趣的地方是他里面没有一个GIF文件。GIF文件尺寸很大,加载很慢,虽然用户更在意他们的数据,但是这个也是个很大的问题。
所以我们要做的是拉取GIF提供的.webm或者.gifv格式。这意味着我们不会展示GIF,我们将展示的是video视频
我们将使用HTML5的video标签来展示这些视频。始终记住,用HTML5来制作移动应用就可以使用HTML5的所有功能。非常典型的一个例子是Geolocation -- 我们可以使用本机【native】API来访问设备的GPS,但是在网页上我们也可以使用HTML5自带的Geolocation API。基本上,任何网页上可以做到的事情,移动应用上也可以做到(很明显,我们可以做到更多,因为我们可以访问本机功能)。
在实现此功能之前,我们先来熟悉一下HTML5 Video。

HTML5 Video在iOS和Android上的行为

使用类似Ionic这样的框架意味着他们帮我们处理了平台之间的差异性,但是当制作跨平台应用的时候,还是会遇到一些平台差异性相关的问题。
首先,对于video元素有一些需要知道的事情:

重点记住,video元素根据运行平台的不同,他的行为会有所不同。

知道了这些不同,我们就需要找出如果解决这些问题。我们基本上有两个可选项:

  1. 接受默认行为,不同平台使用相同的代码
  2. 检查运行平台,运行不同代码来达到需求

个人而言,我更希望嵌入网页播放视频。但是由于在iOS小型设备上会默认全屏,我觉得还是用默认的行为好些。这意味着在iOS和Android上表现会有所不同,但是我觉得两者都很完美都可以接受,同时可以保持我们的代码简单整洁。
好了,我们开始工作了。我们将实现home.ts里面的一些函数定义然后一个个的讲解。

从Reddit获取数据

我们从最复杂最有趣的函数开始,同时也是整个应用最重要的核心功能:fetchData()。我们先添加代码然后讲解。
> 修改 src/app/providers/reddit.ts 的 fetchData 函数为如下:

fetchData(): void {
    //基于用户当前偏好组装URL来访问API
    let url = 'https://www.reddit.com/r/' + this.subreddit + '/' + this.sort + '/.json?limit='+ this.perPage;
    //如果我们不是在第一页的话,我们需要加上after参数才能得到新的结果
    //这个参数基本上就是讲"把 AFTER 这个帖子的帖子给我"
    if(this.after){
        url += '&after=' + this.after;
    }
    //我们现在拉取数据,所有要将loading变量设为 true
    this.loading = true;
    //向指定的URL发起请求然后订阅他的 response
    this.http.get(url).map(res => res.json()).subscribe(data => {
        let stopIndex = this.posts.length;
        this.posts = this.posts.concat(data.data.children);
        //循环所有的 NEW 帖子。
        //我们倒序循环的原因是因为需要移除一些项。
        for(let i = this.posts.length - 1; i >= stopIndex; i--){
            let post = this.posts[i];
            //添加一个新属性用于切换单个帖子的加载动画
            post.showLoader = false;
            post.alreadyLoaded = false;
            //给 NSFW 帖子添加 NSFW 印记
            if(post.data.thumbnail == 'nsfw'){
                this.posts[i].data.thumbnail = 'images/nsfw.png';
            }
            /*
            * 移除所有非 .gifv 或者 .webm 格式的帖子,然后将保留下来的帖子转换成.mp4文件
            * 
            */
            if(post.data.url.indexOf('.gifv') > -1 ||  post.data.url.indexOf('.webm') > -1){
                this.posts[i].data.url = post.data.url.replace('.gifv', '.mp4');
                this.posts[i].data.url = post.data.url.replace('.webm', '.mp4');
                //如果有缩略图的话,将他指定到 post 的 'snapshot'
                if(typeof(post.data.preview) != "undefined"){
                    this.posts[i].data.snapshot =  post.data.preview.images[0].source.url.replace(/&/g, '&');
                    //如果 snapshot 未定义的话, 将他指定为空这样就不会显示一个破裂图
                    if(this.posts[i].data.snapshot == "undefined"){
                        this.posts[i].data.snapshot = "";
                    }
                }
                else {
                    this.posts[i].data.snapshot = "";
                }
            }
            else {
                this.posts.splice(i, 1);
            }
        }
        //如果没有得到够一页的数据那么继续获取GIF
        //但是,如果连续20次都没获取足够的数据的话就放弃
        if(data.data.children.length === 0 || this.moreCount > 20){
            this.moreCount = 0;
            this.loading = false;
        } else {
            this.after = data.data.children[data.data.children.length - 1].data.name;
            if(this.posts.length < this.perPage * this.page){
                this.fetchData();
                this.moreCount++;
            }
            else {
                this.loading = false;
                this.moreCount = 0;
            }
        }
    }, (err) => {
        //静默失败,此时加载旋转动画会持续显示
        console.log("subreddit doesn't exist!");
    });
}

这个函数还是蛮大的。我在里面加入了一些注释来帮助理解,但是我们还是来详细讲解一下每片代码。
首先,我们创建了用来向Reddit API获取数据的URL。我们用到了用户当前设置的subredditsortperPage。你可以任意修改这些值,他将会输出成对应的JSON。如果有提供after的话,那么他也会输出到JSON。这就是Reddit API的“分页”方式,如果用户点击“Load More”按钮三次的话,我们只会返回第三页的帖子,即,如果每页5个的话就是10-15.你可以给Reddit API提供一个帖子“name”,他只会返回这个帖子后面的帖子。
然后我们用这个URL来发起Http请求,然后在将Reddit返回结果JSON字符串JSON话成一个对象之后,订阅他的Observable。
之后,我们可以循环返回的数据对其施展魔法。我们不需要循环存储在this.posts变量中的每个帖,因为其中大部分都“处理过”,我们只要处理新加载的就可以了 -- 所以我们创建了一个“stopIndex”作为this.posts数组的长度,然后我们将新帖子加入其中。
循环处理新加载的帖子的时候,我们做了如下处理:

循环完之后,我们就可以得到一个格式合格的帖子数组来,可以用在列表显示中了。还有一个重要的待作步骤。如果我们每页从Reddit API加载10个帖子的话,但是其中只有3个帖子适应GIF,我们的页面尺寸将变成3。这对于用户来讲就不是很友好了。
解决这个问题的方法是我们将在fetchData()内递归多次调用fetchData()函数。这样我们的帖子数组里面将会有越来越多的帖子直到填满10个(或者当前页面尺寸)为止。同时我们也的设置一个限制以防无限调用这个函数,所以在调用了20次之后还没有填满的话我们会自动放弃。
同时我们也给http.get添加了错误处理器。如果请求成功将会运行上面讨论的代码,如果失败的话(即用户想要访问的subreddit返回结果404),那么将会进入错误处理器而不是上面的代码。
现在我们有了GIF加载到应用中,我们就可以将真实数据展示到列表中。但是,首先,我们的更新模板来用于展示真实数据。
> 修改 src/pages/home/home.html 为如下:

<ion-header>
    <ion-navbar color="secondary">
    <ion-title>
    <ion-searchbar color="primary" placeholder="enter subreddit name..."  [(ngModel)]="subredditValue" [formControl]="subredditControl" value=""></ion-searchbar>
    </ion-title>
    <ion-buttons end>
    <button ion-button icon-only (click)="openSettings()"><ion-icon name="settings"></ion-icon></button>
    </ion-buttons>
    </ion-navbar>
</ion-header>
<ion-content>
    <ion-list>
        <div *ngFor="let post of redditService.posts">
            <ion-item (click)="playVideo($event, post)" no-lines style="background-color: #000;">
            <img src="assets/images/loader.gif" *ngIf="post.showLoader" />
            <video loop [src]="post.data.url" [poster]="post.data.snapshot">
            </video>
            </ion-item>
            <ion-list-header (click)="showComments(post)" style="text-align: left;">
            {{post.data.title}}
            </ion-list-header>
        </div>
        <ion-item *ngIf="redditService.loading" no-lines style="text-align:center;">
            <img src="assets/images/loader.gif" style="width: 50px" />
        </ion-item>
    </ion-list>
    <button ion-button color="light" full (click)="loadMore()">Load More...</button>
</ion-content>

如上所示,我们把帖子的URL作为video元素的源,同时也将预览snapshot作为海报,title作为页首。我们也添加了其他一些东西。
我们添加了一个点击处理器当视频被点击的时候调用playVideo,传入$event(稍后解释)以及点击到的post的引用。对于<ion-list-header>有一个单独的点击事件用于在InAppBrowser中启动这个主题。
我们也给列表添加了加载GIF动画。当用户点击视频的时候,需要一点时间来加载他,所以我们加上一个加载动画这样用户知道应用在处理一些事情。没有他的话,用户可能会觉得应用啥都没干。
我们现在来给loadSettings函数加点代码,这样运行应用的时候就会调用fetchData(因为loadSettings是在构造器中调用的)。这就是应用会发生改变的做法,我们稍后来改动他。
> 修改 src/pages/home/home.ts 的 loadSettings 函数为如下:

loadSettings(): void {
    this.redditService.fetchData();
}

如果在浏览器中重新加载应用的话,应该可以看到这样的画面。

预览

看起来还是蛮矬的,但是还是稍有改进,因为我们可以看到一些酷的GIF展示出来(现在还不能播放)。我们再改改。

播放GIF(视频)

现在列表里面GIF准备就绪,我们现在要让他们摇摆起来。我们所以现在来实现playVideo函数。
> 修改 src/pages/home/home.ts 的 playVideo 函数为如下:

playVideo(e, post): void {
    //创建视频的引用
    let video = e.target.getElementsByTagName('video')[0];
    if(!post.alreadyLoaded){
        post.showLoader = true;
    }
    //切换视频播放
    if(video.paused){
        //展示加载gif
        video.play();
        //一旦开始播放视频,隐藏加载gif
        video.addEventListener("playing", function(e){
            post.showLoader = false;
            post.alreadyLoaded = true;
        });
    } else {
        video.pause();
    }
}

用户点击视频的时候,我们会将视频在播放和暂停之间切换。我们用模板传入的事件来获取视频本身,然后就可以对这个视频进行播放或者暂停。此处我们也切换了showLoader属性以决定加载图标显示与否。我们只需要用户在第一次点击视频的时候显示加载动画,其他时间只在用户暂停的时候显示,所以在切换他之前我们先检查一下alreadyLoaded标记。

在In App Browser里启动Comments

还记得我们设置应用的时候,有安装In App Browser插件吧?我们要用上了。当用户点击视频的头的时候,我们会启动一个浏览器来展示原帖。
> 添加以下导入语句到 src/pages/home/home.ts 顶部:

import { InAppBrowser } from 'ionic-native';

> 修改 src/pages/home/home.ts 的 showComments 函数为如下:

showComments(post): void {
    let browser = new InAppBrowser('http://reddit.com' + post.data.permalink,'_system');
}

跟其他函数对比,这个函数很简单了。我们简单的获取了帖子数据的连接燃油使用InAppBrowser来启动这个链接。

加载更多GIF

现在还有个重要的函数没有实现:loadMore,这个是用户点击‘LoadMore’按钮的时候调用的函数。这个函数要做的就是加载下一页的GIF。由于我们设置好了fetchData函数,所以这个功能就非常简单了。
> 添加以下函数到 src/providers/reddit.ts :

nextPage(){
    this.page++;
    this.fetchData();
}

> 修改 src/pages/home/home.ts 的 loadMore 函数为如下:

loadMore(): void {
    this.redditService.nextPage();
}

我们所作的只是增加页数然后重新调用fetchData,简单!!!

更换Subreddit

最后需要完成的函数是changeSubreddit(后面还有一点点)函数。但是,首先,我们的给Reddit提供者添加一个新的函数来操作subreddit的变更。
> 添加以下函数到 src/providers/reddit.ts 顶部:

resetPosts(){
    this.page = 1;
    this.posts = [];
    this.after = null;
    this.fetchData();
}

> 修改 src/pages/home/home.ts 的 changeSubreddit 函数为如下:

changeSubreddit(): void {
    this.redditService.resetPosts();
}

我们早先把最难的骨头啃下来,这也是另一个非常简单的函数。当subreddit改变的时候我们需要重置所有相关事物。如果我们已经来到了‘chemicalreactiongifs’第五页的时候,我们在切换到‘perfectloops’之后就不用继续去加载第五页了。我们这里所作的就是重置页面,清理帖子数据,清理after值,然后重新调用fetchData函数。我们已经在他处设置了subreddit,所以调用fetchData的时候他会直接使用当前的subreddit。

总结

本课所讲内容是整个应用的大部分了,还有些许待作工作,但是现在可以稍作歇息。此时你的应用已经可以很好的工作了 -- 看起来还是很难看并且没有保存设置的能力,但是他能正常跑起来。下节课就来解决这两个问题。

上一篇下一篇

猜你喜欢

热点阅读