知乎专栏:https://www.zhihu.com/people/qi_yue/activities
知乎专栏【小楼昨夜又秋风】:https://zhuanlan.zhihu.com/oldtimes
项目中的一些小问题:https://zhuanlan.zhihu.com/p/24554026

第1-2章 微信小程序与开发环境介绍
- 小程序特性:业务逻辑简单、使用频率低、性能要求低
- 基础:JS+ES6 Promise+CSS (小程序无DOM!)
- 微信Web开发者工具 下载地址
小程序4大特点:
(1)无需下载安装APP (2)触手可及(二维码扫一扫) (3)用完即走 (4)无需卸载
第3章 编写第一个小程序页面 (启动页)
- 小程序目录结构
- View、Image、Text组件
- 移动端设备分辨率及小程序自适应单位RPX
- Flex弹性盒子模型
- 小程序app.json基本配置项
从性能上考虑,建议把静态样式放到wxss文件中,把动态样式放到wxml文件的style属性中;从规范性考虑,应该把样式都放在wxss文件中;
小程序的wxml页面最外围有
page层,在页面中未显示,但在小程序开发工具-调试器-Wxml栏目中可见;可设置page高度(100%)和背景颜色,使整个页面完全填充,如果设置wxml页面可见的最外层标签高度为100%及背景颜色,则背景颜色只能局部填充页面。page{ height: 100%; background: #b3d4db; }移动端设备分辨率及小程序自适应单位RPX
屏幕尺寸(英寸):指的是屏幕对角线的距离
pt:逻辑分辨率,与实际移动设备的尺寸有关,简单理解为长度单位
px:物理分辨率,与实际移动设备的尺寸无关,简单理解为像素点
Reader(px/pt):每个pt包含了几个px,即每个单位长度可以包含几个像素点。同一个单位下,像素点越多,图像的显示更加清晰。
PPI/DPI:指每英寸包含多少个像素点
为什么模拟器下ip6的分辨率是375而设计图一般给750?375是指pt,而设计图的750是px。但小程序中显示的是pt,根据iPhone6的2倍关系,将设计图的物理分辨率折算成逻辑分辨率。
如何适配不同的机型?小程序使用自适应单位RPX ,会自动在不同分辨率下进行转换,而使用px为单位不会自动转换。
转换公式:iPhone6下 1px = 1rpx = 0.5pt为什么要用iphone6的物理分辨率来做设计图?用iphone6的换算关系1px=1rpx,便于计算;若是其他模型,如iPhone6 plus, 换算关系为1px=0.6rpx,不便于计算。
为什么plus版本并不会比相应普通版本更加清晰?视网膜屏,在单位像素密度下,它所承担的像素个数已经达到了人类眼睛所能分辨的极限,再增加像素个数也不会感觉更加清晰。
微信小程序中不是所有的单位都适用rpx,因为有时候无需根据屏幕尺寸做自适应变化,比如主标题字体大小。

Flex弹性盒子模型。
微信小程序中不建议使用float和position布局,取而代之的是Flex布局,主要作用域页面容器上。.container { display: flex; flex-direction: column; }。本项目中关于flex布局较麻烦的一块movie-list-template.wxml页面中“正在热映”与“更多”一栏使用flex布局实现对齐。在app.json中可通过Window属性设置小程序导航栏样式,如navigationBarBackgroundColor属性设置导航栏背景颜色;
第4章 第二个页面:新闻阅读列表
- Swiper组件构建轮播图
- 关于导航栏、标题的全局配置与页面配置
- Page页面与应用程序的生命周期
- 数据绑定(核心)
- setData方法绑定数据
- 数据绑定的扩展用法
- 条件渲染与列表渲染
- 小程序的事件机制——catch与bind
- 冒泡事件
1.Swiper组件构建轮播图
- indicator-dots :添加轮播图底部的小点;
- autoplay='true’设置自动播放;
- interval=‘5000’ 设置间隔为5s ;
- vertical='true’设置纵向滚动,若要设置为false,写成{{false}}形式;
- Swiper组件有默认高度,因此需要根据内容设定高度或者是采用自适应高度的方法;
swiper indicator-dots='true' autoplay='true' interval='5000' vertical='{{false}}' !-- swiper-item中宽高自动设置为100% -- swiper-item image catchtap='onSwiperImageTap' src='/image/wx.png' data-postId="4"/image /swiper-item swiper-item image catchtap='onSwiperImageTap' src='/image/iqiyi.png' data-postId="6"/image /swiper-item swiper-item image catchtap='onSwiperImageTap' src='/image/vr.png' data-postId="5"/image /swiper-item /swiper2.关于导航栏、标题的全局配置与页面配置
//app.json{ "window": { "navigationBarBackgroundColor": "#405f80" }}//post.json{ "navigationBarBackgroundColor": "#405f80", "navigationBarTitleText":"文与字", "navigationBarTextStyle":"white", "enablePullDownRefresh":true}3.wx.navigateTo()与wx.redirectTo() 两者在生命周期上的区别
wx.navigateTo()从父页面跳到子页面,有返回字样(最多有5级),跳转到子页面后,父页面被隐藏了,执行onHide()函数;
wx.redirectTo()两个页面之间平行跳转,没有返回字样;跳转到其他页面后,该页面生命周期结束,执行onUnload()函数;
4.数据绑定
- 在item前面加三个小点,那么在post-item-template.wxml文件中不需要再写item.title ,直接写变量名称title即可
- template是占位符,不能把事件放在template中,可以在template外面放一对view标签,将事件放在view标签上
- is=“postItem” 是template的名字,对应了哪个template
- 当某个页面引入某template模板时,需要引入template模板的wxml和wxss文件,wxss文件引用:
@import "post-item/post-item-template.wxss";
import src="post-item/post-item-template.wxml" / ... block wx:for="{{post_key}}" wx:for-item="item" view catchtap="onPostTap" data-postid="{{item.postId}}" template is="postItem" data="{{...item}}" / /view /block- 以下是template模板,数据由前面的
template is="postItem" data="{{...item}}" /传入。
//post-item-template.wxmltemplate name="postItem" view class='post-container' view class='post-author-date' !-- 元素的显示与隐藏 wx:if="{{false}}" -- image wx:if="{{img_condition}}" src='{{img.avatar}}' class='post-author-img'/image text class='post-date'{{date}}/text /view text class='post-title'{{title}}/text image src='{{img.imgSrc}}' class='post-img'/image text class='post-content'{{content}}/text view class='post-like' image src='/image/icon/chat.png' class='post-like-img'/image text class='post-like-count'{{collection}}/text image src='/image/icon/view.png' class='post-like-img'/image text class='post-like-count'{{reading}}/text /view /view/template- Template模板使用的问题。我们在template模板页面中设置了图片的相对路径,若该emplate模板不仅仅用于post页面,而是用于很多页面,此时设置相对路径就会导致图片读取失败,所以最好是设置绝对路径。
5.setData方法绑定数据
- Page()中的data为页面初始数据,我们可以在其中设置一些初始值,如果要动态改变这些值,就需要用到setData()函数来绑定数据。
onLoad: function () { // 绑定数据 this.setData({ post_key: postData.postList }); // this.data.post_key = postData.postList; },- this.data是用于获取页面data对象的,而this.setData是用来更新界面的;
- this.setData存储的是this.data的副本,而界面是从this.setData里面托管的this.data的副本取数据的,所以更改this.data并不会直接更新界面,this.setData需要将数据从逻辑层发送到视图层,同时改变对应的this.data值。
6.数据绑定的扩展用法
- 在总目录下创建一个新的文件夹data,并在底下新建一个data.js文件,将数据都下入data.js文件,其他页面可通过require(url)获取data.js文件中的数据。
- 使用require()时, 只能用相对路径,用绝对路径会出错
//data.jsvar local_database = [{...},{...},{...}]// 使数据通过出口exports输出到别的脚本中去module.exports = { postList:local_database}//post.js中var postData = require("../../data/post-data.js");7.条件渲染与列表渲染
(1)条件渲染wx:if
view wx:if="{{length 5}}" 1 /viewview wx:elif="{{length 2}}" 2 /viewview wx:else 3 /view(2)列表渲染wx:for
- 在组件上使用wx:for绑定一个数组,使用数组中各项的数据重复渲染该组件。
- 默认数组的当前项的下标默认为index,数组当前项默认为item
block wx:for="{{post_key}}" wx:for-item="item" wx:for-index="idx" view catchtap="onPostTap" data-postid="{{item.postId}}" template is="postItem" data="{{...item}}" / /view /block8.小程序的事件机制——catch与bind
- 事件是视图层到逻辑层的通信方式,将用户的行为反馈到逻辑层处理;当用户触发事件,就会执行逻辑层中对应的事件处理函数。
- tap事件相当于click点击事件;
bindtap=“onTap”,这种绑定不会阻止冒泡事件向上冒泡catchtap = "onTap",这种绑定可以阻止冒泡事件向上冒泡- event对象中的target是事件产生的源头组件,而currentTarget则是当前捕获这个事件的组件。
- 点击post页面的轮播图,实现页面跳转,有以下两种方式:第一,在swiper组件中绑定一个
catchtap='onSwiperTap'事件,在js中通过event.target.dataset.postid获取Id号;第二,在swiper下的每个图片中都绑定一个catchtap='onSwiperImageTap'事件,在js中通过event.currentTarget.dataset.postid获取Id号;
onSwiperTap: function (event) { // target是事件产生的源头组件 var postId = event.target.dataset.postid; wx.navigateTo({ url: "post-detail/post-detail?id=" + postId }) }// onSwiperImageTap放在三个Image中,currentTarget是当前捕获这个事件的组件。 onSwiperImageTap:function(event){ var postId = event.currentTarget.dataset.postid; wx.navigateTo({ url:"post-detail/post-detail?id=" + postId }) }9.冒泡事件
view class='moto-container' bindtap='onTap' text class='moto' bindtap='onTextTap'开启小程序之旅/text/viewPage({ onTap:function(){ console.log("我是父"); }, onTextTap:function(){ console.log("我是子"); } })点击文本 ,输出:“我是子” “我是父”
点击父元素,输出:“我是父”
阻止事件冒泡将bindtap 用 catchtap 来代替使用,可以阻止冒泡事件发生
view class='moto-container' catchtap='onTap' text class='moto' catchtap='onTextTap'开启小程序之旅/text/view第5章 小程序的模板化与模块化
优化代码,使用require构建模块儿化的JavaScript代码以及使用template构建模板化小程序。感受在小程序中使用template的优势。
这块内容在上一章都有提及了,不重复说明了。
- 将业务中的数据分离到单独的数据文件中
- 使用require方法加载js模块儿文件
- template模板的使用
第6章 第三个页面:构建新闻详情页面
新闻详情页面包括音乐播放、文章收藏等功能。本节将讲解音乐播放器的用法,重点学习缓存、条件渲染及Toast、ActionSheet接口的使用。
- 构建新闻详情页面样式
- 使用数据填充新闻详情页面
- 缓存Storage的基本用法
- 使用缓存实现文章收藏功能
- 交互反馈API wx.showToast
- 操作反馈API wx.showModal (显示模态弹窗)
- 交互反馈API wx.showActionSheet (显示操作菜单)
- 音乐播放实现
- 实现swiper组件内容跳转
1.如何把一个数据绑定到某个元素标签的属性中? data-postId = “{{item.postId}}”,data-postId为自定义属性。
2.小程序开发目前不能加载html网页,因为不支持webView,考虑到安全问题和可控性问题;因此在新闻详情页面中填充的仅是简单的文本内容,要想直接添加更加丰富的内容,比如文字加粗等样式或者是在内容中包含图片等,目前还是不可行。
3.缓存Storage的基本用法(设置缓存/获取缓存/删除缓存/清空缓存,四种用法都有同步和异步两种操作)。注意,缓存的上限最大不能超过10MB。
另外,在真机运行中,没有清空所有缓存的功能,只是在模拟器中加入了这个功能,这样会造成一些莫名其妙的问题,比如你在真机上设置了某些缓存,然后在模拟器中改变了这些缓存数据,再放到真机中运行,然而真机中新设置的缓存数据并没有代替原来的缓存数据(更新),而真机中又无法清空缓存数据(更新缓存数据首先要清空真机中的的缓存,再显示新的缓存数据)。如果程序在真机上出现了一些奇怪的问题而程序又使用了缓存,首先考虑是不是缓存导致了这些问题。
解决办法:清空真机上的缓存数据。在程序中放置一个按钮,点击按钮调用wx.clearStorage方法清除真机上的缓存;
4.使用缓存实现文章收藏功能。
用户未收藏则收藏按钮显示为灰色;点击收藏按钮则显示为蓝色。另外,不能像jQuery一样通过getElementById等选择符拿到DOM元素,用脚本动态控制DOM元素实现属性样式的变换,因为小程序中没有DOM,必须通过数据绑定或者小程序组件来实现动态切换,小程序是数据优先的思想。
//条件渲染 判断文章是否被收藏image wx:if="{{collected}}" src='/image/icon/collection.png' catchtap='onCollectionTap'/imageimage wx:else src='/image/icon/collection-anti.png' catchtap='onCollectionTap'/image onCollectionTap: function (event) { this.getPostsCollectedSyc();//同步缓存 }, getPostsCollectedSyc:function(){ var pcs = wx.getStorageSync('posts_Collected'); var pc = pcs[this.data.curId - 1]; // 取反操作 pc = !pc; // 更新缓存 pcs[this.data.curId - 1] = pc; this.showModal(pcs, pc); },注意posts_Collected变量的初始化哦
onLoad:function(option){ ... // 初始化 var pcs = wx.getStorageSync('posts_Collected'); if (pcs) { var pc = pcs[postId-1]; if(typeof(pc) == "undefined"){ pc = false; } this.setData({ collected : pc }) } else{ // 若posts_Collected缓存为空 var pcs = {}; pcs[postId - 1] = false; wx.setStorageSync('posts_Collected', pcs); // 这里没必要再用setData更新数据绑定,因为最开始没有缓存,collected默认为false; }同步异步缓存方法对比
- 同步缺点:若同步调用方法运行很慢,会阻塞整个程序的运行;
- 异步优点:加速主线程的运行,避免同步运阻塞程序;但当程序比较复杂时,异步的回调函数会很长,且代码的可读性会变差,另外很容易产生问题,因此不建议优先使用异步缓存;
- 异步的选择最好是以明确的业务作为指导思想,脱离了业务谈异步没有什么实际性意义。
5.交互反馈 : wx.showToast与wx.showModal
- 区别:前者不需要用户确认自动消失,而后者必须经用户确认反馈。
6.音乐播放API
- 前面关于收藏事件,定义了两个Image标签,在两张图片之间切换;在音乐播放事件中,定义了一个Image 标签,通过判断
isPlayingMusic=true/false选择不同的图片;
image src="{{isPlayingMusic? '/image/music/music-stop.png':'/image/music/music-start.png'}}" class='audio' catchtap='onMusicTap' /onMusicTap事件控制音乐播放事件。当用户点击播放音乐,播放图片变为暂停图片,背景图片变换为封面图片;当用户点击暂停按钮,暂停图片变为播放图片,音乐的封面图片变为原始的背景图片。
data:{ isPlayingMusic:false },onMusicTap:function(event){ var isPlayingMusic = this.data.isPlayingMusic; var curMusic = postData.postList[this.data.curId - 1].music; if(isPlayingMusic){ // 暂停播放 wx.pauseBackgroundAudio(); this.setData({ isPlayingMusic:false }) } else{ // 启动播放 wx.playBackgroundAudio({ dataUrl: curMusic.url, title: curMusic.title, // 在模型中可能无法显示封面图片,在真机上显示正常 coverImgUrl: curMusic.coverImg }) this.setData({ isPlayingMusic:true }) } }- 问题1,点击底部的音乐播放控件如何使顶部的图片做相应的变换调整?监听播放事件完善音乐播放,在onLoad函数中监听。
// 监听音乐播放 var that = this; wx.onBackgroundAudioPlay(function(){ that.setData({ isPlayingMusic : true }) }) // 监听音乐停止 wx.onBackgroundAudioPause(function(){ that.setData({ isPlayingMusic : false }) }) //关于数据绑定的好处: //1.小程序中的后台数据绑定简单方便优于jQuery方式; //2. 同一个状态变量可以绑定多个标签,改变该状态变量使前端相关的数据绑定实现一次性的数据变换; //3. 微信小程序中在做数据绑定时只是操作状态变量,并没有取操作UI,便于做单元测试;问题2,当点击音乐播放后返回到上个页面,再进入该页面发现音乐继续仍然播放,但顶部显示为播放按钮,按理说应该显示为暂停按钮?
当退出该页面再进入该页面,页面重新加载,再次执行了onLoad函数。因此,这里需要一个和页面生命周期无关的状态来保持变量,使用缓存的缺点是即使整个程序关闭了,再次运行该程序还是为程序关闭前的状态,这不符合音乐播放功能要求。所以,我们要求的是当页面关闭时,这个状态依然保存着变量,而当应用程序全部关闭时,所有的状态变量都恢复为默认值。此问题可由全局变量解决,在app.js文件中定义全局变量。
var app = getApp(); onLoad:function(option){...// 判断全局变量是否为真,若为真,则改变局部变量isPlayingMusic为真 if(app.globalData.g_isPlayingMusic){ this.setData({ isPlayingMusic:true }) } this.setAudioMonitor(); } setAudioMonitor:function(){ // 监听音乐播放 var that = this; wx.onBackgroundAudioPlay(function () { that.setData({ isPlayingMusic: true }) app.globalData.g_isPlayingMusic = true; }) // 监听音乐暂停 wx.onBackgroundAudioPause(function () { that.setData({ isPlayingMusic: false }) app.globalData.g_isPlayingMusic = false; }) }问题3,继问题2后,我们在某页面中播放音乐,然后返回进入另一个页面发现另一个页面顶部显示暂停图片,但播放的是原先页面的歌而当前页面并未点击播放啊?
原因:更换页面后,重新读取全局变量
app.globalData.g_isPlayingMusic=true(因为退出前一个页面状态时并未停止音乐播放),导致进入另一个页面时根据该全局变量设置为播放状态;解决方法:再定义一个全局变量来记录当前播放页面的postId。
问题4,音乐播放完成后图标状态没有复位。
解决办法:添加一个事件来监听音乐停止,方法基本同监听音乐暂停事件
// 监听音乐停止 wx.onBackgroundAudioStop(function () { that.setData({ isPlayingMusic: false }) app.globalData.g_isPlayingMusic = false; app.globalData.g_curMusicPostId = null; })第7章 第四个页面:制作电影资讯页面
调用服务器数据,编写电影资讯首页。包括正在热映、即将上映、top250三类电影数据的加载,进一步的深入使用template、学习使用wx.request加载服务器数据。
- 给项目加入 tab 栏,切换页面
- 3个嵌套template标签构建电影资讯页面
- 从服务器加载数据:RESTful API简介及调用豆瓣API,获取正在热映、即将上映和Top250的数据
- 电影页面数据绑定
- 星星评分组件的实现
- 更换电影分类标题
1.在`post.wxml`页面中加入tab栏,实现方式:在`app.json`中设置`tabBar`,注意图片路径最前面不要加"/",会报错。
"tabBar": { "borderStyle":"white", "position":"bottom", "list": [ { "pagePath": "pages/post/post", "iconPath": "image/tab/yuedu.png", "selectedIconPath": "image/tab/yuedu_hl.png", "text": "阅读" }, { "pagePath": "pages/movies/movies", "text": "电影", "iconPath": "image/tab/dianying.png", "selectedIconPath": "image/tab/dianying_hl.png" } ] },1.RESTful API 用于 Web 数据接口
(1)五种操作
- GET : 获取资源
- HEAD:只获取某个资源的头部信息
- POST:新建资源
- PUT : 更新资源的所有属性
- PATCH:更新资源的部分属性
- DELETE:删除资源
(2)URI && URL
- URI 统一资源标识符,表示请求服务器的路径或者资源的名字
- URL 统一资源定位符,同时说明要如何访问这个资源(http://)
- URL是URI的子集,所以URL一定是URI,而URI不一定是URL
- 我们常把网址成为URL,因为它提供了资源的位置信息
(3)状态码
- 1xx:相关信息
- 2xx:操作成功
- 3xx:重定向
- 4xx:客户端错误
- 5xx:服务器错误
(4)服务器回应
- API 返回的是一个 JSON 对象(结构化数据),将服务器回应的 HTTP 头的Content-Type属性要设为”application/json“。
2.以下代码向服务器分别获取了”正在热映“、”即将上映“、”TOP250“的资源,在返回的结果中我们看到返回的数据并不是按调用顺序显示,这是因为从服务器加载数据是异步操作,无法确定先后顺序。
var app = getApp();Page({ onLoad:function(event){ var inTheatersUrl = app.globalData.doubanBase + '/v2/movie/in_theaters'; var comingSoonUrl = app.globalData.doubanBase + '/v2/movie/coming_soon'; var top250Url = app.globalData.doubanBase + '/v2/movie/top250'; this.getMovieListData(inTheatersUrl); this.getMovieListData(comingSoonUrl); this.getMovieListData(top250Url); }, getMovieListData:function(url){ // 发起一个网络请求,这里主要是查询数据,用GET方式,POST是向服务器提交数据 wx.request({ url: url, method: 'GET', header: { "Content-Type": "application/xml" //不能设置为 ”application/json“ }, success: function (res) { console.log(res); }, fail: function (error) { console.log(error); } }) }})3.刚开始设置URL为“https://api.douban.com/v2/movie/…”,但是出现403错误,于是参考博客将URL前半部分改为“https://douban.uieee.com”,成功获取服务器数据,然。。。吃了顿饭回来再次刷新报错112:rate_limit_exceeded2 IP访问速度限制,于是再次换了一个代理地址“http://t.yushu.im”,又成功获取数据啦。
4.星星评分组件的实现。在util.js文件中单独写了一个convertToStarsArray()函数,将服务器中获取的评分数据如3.5,转换成[1,1,1,0,0]形式,在前端显示星星图像时,循环数组判断是否为1,若为1显示为黄色星星;若为0显示为灰色星星;这里只考虑了整数情况,因为没有半颗黄色星星的图标。若是考虑小数,则可以将评分数据转换成[1,1,2,2,0]形式。具体实现如下:
//util.jsfunction convertToStarsArray(stars){ var num = stars.toString().substring(0,1); //获取第一个数字,如3.5就取3 var array = []; for(var i=1;i=5;i++){ if(i=num){ array.push(1); } else{ array.push(0); } } return array; }module.exports = { convertToStarsArray: convertToStarsArray} //在movies.js中 var util = require("../../utils/util.js"); ... processDoubanData: function (moviesDouban, settedkey){ // 获取相关数据 var movies = []; for(var idx in moviesDouban.subjects){ var subject = moviesDouban.subjects[idx]; var title = subject.title; if(title.length = 6) { title = title.substring(0,6) + "..."; } var tmp = { stars:util.convertToStarsArray(subject.rating.stars), //形式为:[1,1,1,0,0] title:title, average:subject.rating.average, coverageUrl: subject.images.large, movieId:subject.id } movies.push(tmp); } // 动态属性解决 var readyData = {}; readyData[settedkey] = { movies:movies }; this.setData(readyData); }//在movie-template.wxml页面中,注意“data="{{stars:stars,score:average}}" ”写法import src="../stars/stars-template.wxml" /template name="movieTemplate" view class='movie-container' image src='{{coverageUrl}}'/image text{{title}}/text template is="starsTemplate" data="{{stars:stars,score:average}}" / /view/template //在stars-template.wxml页面中 template name="starsTemplate" view class='stars-container' view class='stars' block wx:for="{{stars}}" wx:for-item="i" image wx:if="{{i}}" src='/image/icon/star.png'/image image wx:else src='/image/icon/none-star.png'/image !-- 方式二 考虑半颗星星的情况-- !-- image wx:if="{{i==1}}" src='/image/icon/star.png'/image image wx:elif="{{i==2}}" src='/image/icon/none-star.png'/image image wx:else="{{i==0}}" src='/image/icon/none-star.png'/image -- /block /view text{{score}}/text /view/template5.更换电影分类标题。一种是读取服务器数据中的title然后绑定到前端相应位置;这里考虑到只有“正在热映”,“即将上映”,“豆瓣TOP250”三类,直接在this.getMovieListData()函数传入具体的title,如 this.getMovieListData(inTheatersUrl,“inTheaters”,“正在热映”);
第8章 第五个页面:更多电影及电影搜索页面的实现
完成更多电影页面及电影资讯检索页面,template模板将在本章被大量使用,可以充分体现模板化编程思想的重要性。
- 电影类型获取
- 动态设置导航栏标题
- 更多电影页面加载数据
- 实现movie-grid template
- 实现上滑加载更多数据 scroll-view组件【可滚动视图区域】
- 设置loading状态
- 实现下拉刷新数据
- backgroundColor 到底设置的是哪里的颜色
- 电影搜索页面构建
1.电影类型获取。在movie-list-template.wxml页面中给“更多”一项绑定事件,当点击“更多”时,跳转到“更多电影”页面,在movies.js中添加onMoreTap事件,跳转到more-movie.wxml页面,然后在more-movie.js中获取传过来的category值。
//movies.js onMoreTap:function(event){ var category = event.currentTarget.dataset.category; wx.navigateTo({ url: 'more-movie/more-movie?category='+category }) }//more-movie.jsPage({ data: { }, onLoad: function (options) { var category = options.category; }})2.动态设置导航栏标题。
wx.setNavigationBarTitle 为什么要在onready之后的生命周期函数中设置!!!否则不显示 将上面第一步中传过来的category值保存在data中,需要时再取出使用。
Page({ data: { navigateTitle:"" }, onLoad: function (options) { var category = options.category; this.data.navigateTitle = category; }, // 当页面准备完毕了才执行页面函数 onReady:function(event){ wx.setNavigationBarTitle({ title: this.data.navigateTitle, success: function (res) { } }) }})3.更多电影页面【more-movie】加载数据
这里情况类似于上一个“电影资讯页面”,加载数据的方法雷同,首先根据more-movie.js中获取到的电影资讯类别category获取对应的URL,再通过getMovieListData()和processDoubanData()来获取URL地址的数据以及对数据进行筛选处理。
var app = getApp();var util = require("../../../utils/util.js");Page({ data: { navigateTitle: "", movies:{} }, onLoad: function(options) { var category = options.category; this.data.navigateTitle = category; var dataUrl = ""; switch (category) { case "正在热映": //默认返回20条信息 dataUrl = app.globalData.doubanBase + "/v2/movie/in_theaters"; break; case "即将上映": dataUrl = app.globalData.doubanBase + "/v2/movie/coming_soon"; break; case "豆瓣TOP250": dataUrl = app.globalData.doubanBase + "/v2/movie/top250"; break; } console.log(dataUrl); util.http(dataUrl, this.processDoubanData); }, processDoubanData: function (moviesDouban) { // 获取相关数据 var movies = []; for (var idx in moviesDouban.subjects) { var subject = moviesDouban.subjects[idx]; var title = subject.title; if (title.length = 6) { title = title.substring(0, 6) + "..."; } var tmp = { stars: util.convertToStarsArray(subject.rating.stars), //形式为:[1,1,1,0,0] title: title, average: subject.rating.average, coverageUrl: subject.images.large, movieId: subject.id } movies.push(tmp); } this.setData({ movies: movies }); }, // 当页面准备完毕了才执行页面函数 onReady: function(event) { wx.setNavigationBarTitle({ title: this.data.navigateTitle, success: function(res) {} }) }})4.实现movie-grid template
经上一步筛选得到数据后,接下来就是把数据绑定到前台页面,首先实现一个movie-grid-template ,其中嵌套了movie-template,再将movie-grid-template添入more-movie页面,添加more-movie页面样式,页面效果如下,每个电影模块相当于一个movie-template,整个电影版块相当于一个movie-grid-template
//more-movie.wxmlimport src="../movie-grid/movie-grid-template.wxml" /template is="movieGridTemplate" data="{{movies}}"/template//movie-grid-template.wxml页面import src="../movie/movie-template.wxml" /template name="movieGridTemplate" view class='grid-container' block wx:for="{{movies}}" wx:for-item="movie" view class='single-view-container' template is="movieTemplate" data="{{...movie}}"/template /view /block /view/template5.实现上滑加载更多数据【敲重点!!!】
(1)scroll-view组件代替view组件实现可视区域上下滚动,且scroll-view组件需要给当前元素指定一个高度,否则无法确定什么时候算开始滚动。bindscrolltolower指定页面滚动到底部时需要触发的事件“onScrollLower”
scroll-view scroll-y="true" scroll-x="false" bindscrolltolower="onScrollLower" class='grid-container' ... /scroll-view(2)操作事件函数,
onScrollLower:function(){ var nextUrl = this.data.requestUrl + "?start=" + this.data.totalCount + "&count=20"; util.http(nextUrl, this.processDoubanData);}第一,requestUrl就是onLoad函数中的dataUrl,但现在需要将dataUrl从一个函数传给另一个函数,需要将dataUrl保存到this.data中,即this.data.requestUrl = dataUrl;,再在onScrollLower函数中取出requestUrl,记得在data:{}中给requestUrl 一个初始化data: { navigateTitle: "", movies:{}, requestUrl:"", totalCount:0, isEmpty:true }。
第二,用变量totalCount来记录当前获取的电影条目的总数量,接下来应该从totalCount开始继续往后取20(默认每次从服务器取20条)条数据,形成新的nextUrl地址,继续做数据绑定util.http(nextUrl, this.processDoubanData);。但刷新页面后发现,上滑页面只显示了新的20条电影数据,而原始的数据消失了。这里需要把新加载的20条数据与原先的数据整合在一起。每次增加数据,实际需要把全部数据都重新显示一遍。
processDoubanData: function (moviesDouban) { // 获取相关数据 ... var totalMovies = {}; //若isEmpty为true,则数据为空 ,直接将movies赋给totalMovies;否则将新获取的movies数据与原先的数据合并,然后再赋给totalMovies if (!this.data.isEmpty){ totalMovies = this.data.movies.concat(movies); } else{ totalMovies = movies; this.data.isEmpty = false; } this.setData({ movies: totalMovies totalCount: this.data.totalCount +20 }); }6.设置loading状态。当用户发起上滑页面时出现loading,当setData新数据时结束loading。启动loading状态:wx.showNavigationBarLoading();。结束loading状态:wx.hideNavigationBarLoading();
7.实现下拉刷新数据。
在more-movie.json中添加状态"enablePullDownRefresh": "true",以实现下拉刷新,如果在全局app.json中定义此状态,则会导致每个页面都会下拉刷新;
130400版本更新后,如果页面包含有scroll-view组件,则无法实现下拉刷新,解决方案更新在知乎上。
最坑最无语的一点,按照知乎上的方案更改后,下拉刷新还是失败,onPullDownRefresh依然无法触发,最后才意识到mac上下拉刷新需要用一只手指按住(一定要按下去啊)触摸板往下拉才会触发onPullDownRefresh???~ 我败了 ~
具体效果可以下拉刷新然后查看Network的状态。
还有一个问题,下拉刷新后数据依然不止20条,每次下拉刷新数据都重复出现,下拉刷新要记得清除原先的记录哦 !!!
onPullDownRefresh:function(event){ console.log("下拉"); var refreshUrl = this.data.requestUrl + "?start=0&count=20"; // 下拉刷新要记得清除原先的记录哦 this.data.movies = {}; this.data.isEmpty = true; this.data.totalCount = 0; util.http(refreshUrl, this.processDoubanData); wx.showNavigationBarLoading(); //启动loading状态 }8.backgroundColor 到底设置的是哪里的颜色
在more-movie.json中设置"backgroundColor":"red",发现下拉刷新时顶部空出来的一部分变成了红色。
9.电影搜索页面构建。在movies.wxml顶部添加一个input搜索框,给input绑定bindfocus="onBindFocus"事件,同时在movies.wxml添加view显示电影搜索结果/view,通过事件与两个布尔型变量来切换电影搜索页面的显示与隐藏。
onBindFocus:function(event){ console.log("show search"); this.setData({ containerShow:false, searchPanelShow:true }) }第二步,在input后添加一个关闭的图像,绑定bindtap='onCancelImgTap'事件,当点击关闭图标后,切换“电影资讯页面”与“电影搜索页面”的显示与隐藏状态,同时要清空电影搜索页面内容;另外,对关闭图标也要做一个相应的显示与隐藏操作,其状态是与“电影搜索页面”状态同步的。
onCancelImgTap:function(event){ console.log("cancel search"); this.setData({ containerShow: true, searchPanelShow: false, searchResult:{} }) }第三步,电影搜索页面数据绑定。首先给input绑定bindblur='onBindChange'事件,当用户点击input外面或是按回车键时,触发资源搜索,由searchResult对象保存搜索结果,最后将搜索结果显示到页面。
onBindChange:function(event){ // detail属性自定义事件所携带的数据,如表单的提交事件会携带用户的输入 var text = event.detail.value; console.log(text); var searchUrl = app.globalData.doubanBase + "/v2/movie/search?q=" + text; this.getMovieListData(searchUrl,"searchResult","") }!-- 搜索页面 --view class='search-pannel' wx:if="{{searchPanelShow}}" template is="movieGridTemplate" data="{{...searchResult}}" //view第9章 第六个页面:构建电影详情页面
- 电影详情页面数据绑定
- 电影详情页面中图片的缩放与裁剪
- 电影详情页面样式
1.点击某个电影时,跳转到电影详情页面,并传递相应的电影ID号,然后根据ID从豆瓣获取资源再反馈到页面上。
//movie-template.wxml页面import src="../stars/stars-template.wxml" /template name="movieTemplate" view class='movie-container' catchtap='onMovieTap' data-movieId = "{{movieId}}" image src='{{coverageUrl}}'/image text{{title}}/text template is="starsTemplate" data="{{stars:stars,score:average}}" / /view/template //movies.js onMovieTap:function(event){ // dataset后面都是小写 var movieId = event.currentTarget.dataset.movieid; wx.navigateTo({ url: 'movie-detail/movie-detail?id=' + movieId }) } //在movie-detail.js中获取传过来的Id值 onLoad: function (options) { var movieId = options.id; var url = app.globalData.doubanBase + "/v2/movie/subject/" + movieId; util.http(url,this.processDoubanData); }, processDoubanData:function(data){ console.log(data); ... } 2.电影详情页面中图片的缩放与裁剪
Image组件默认宽为300px,高为225px,其model有12种模式,包含4种缩放模式和9种裁剪模式。
当然,使用缩放模式前,需要给图片设置宽高。
image class='head-img' src="{{movie.movieImg}}" mode='aspectFill'/image
3.在设计页面样式中:
- 模糊图片,设置图片属性
-webkit-filter: blur(20px); - 给图片添加“点击查看”功能,绑定
catchtap='viewMoviePostImg'事件
// 图片预览 viewMoviePostImg:function(event){ var src = event.currentTarget.dataset.src; wx.previewImage({ current:src, //当前显示的图片 urls:[src] //需要预览的图片列表,可以放入多张图片 }) }- @import引入template样式要注意页面样式是否被template样式覆盖
铛铛铛~到此为止,整个程序终于结束了,这是慕课网两年前的课程啦,现在的微信小程序开发工具又做了一些改进,所以学习过程中总是碰到一些奇怪的问题,但所幸都成功解决啦,再接再厉,探索永无止境。













