美文网首页Ionic Frameworkionic2hybrid APP(ionic)
[Ionic 2从入门到精通] 4.4 Reddit API和H

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

作者: 老牛啃码 | 来源:发表于2018-05-16 12:09 被阅读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元素有一些需要知道的事情:

    • 他可以全屏展示也可以嵌入使用
    • 他有一个poster属性用于在视频加载之前作为封面展示
    • 可以控制的视频的:控件是否展示,视频是否自动播放,是否嵌入页面播放

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

    • iOS上视频默认是全屏播放,但是也可以通过webkit-playinline属性来强制默认嵌入页面播放。同时,这也不是所有iOS设备通用的。在小的设备上,即使你指定了webkit-playinline属性,他还是会默认全屏播放(基本上,这个是没法解决的)。
    • Android设备上默认是嵌入页面播放,但是可以设置默认全屏播放。

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

    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数组的长度,然后我们将新帖子加入其中。
    循环处理新加载的帖子的时候,我们做了如下处理:

    • .gifv.webm转换成.mp4
    • 给帖子新增一个‘showLoader’变量用来切换加载动画的显示
    • 给NSFW帖子指定NSFW标志
    • 如果帖子有预览图的话给他添加一个预览图属性

    循环完之后,我们就可以得到一个格式合格的帖子数组来,可以用在列表显示中了。还有一个重要的待作步骤。如果我们每页从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。

    总结

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

    相关文章

      网友评论

        本文标题:[Ionic 2从入门到精通] 4.4 Reddit API和H

        本文链接:https://www.haomeiwen.com/subject/xqrpdftx.html