目 录
1 热点新闻APP界面认识
2 一步步搭建应用
3 热点新闻内容代码详解
4 APP打包及真机查看

一、热点新闻APP界面认识

上一章我们大概了解了mui的概念,以及他提供的强大的工具和框架,这一章我们用一个简单的例子来快速构建我们的APP应用

我们要构建的应用非常简单,就是仿一个简单的新闻类的APP,其中包含了2个界面,一个新闻列表页(可以上拉加载更多、下拉刷新),一个新闻详情页,界面如下:

20161202img01 20161202img02

我们已经知道了我们的目标,接下来我们就要开始创建工程了。

二、一步步搭建应用

打开开发工具“HBuilder”,在左侧工程栏中“右键”-“新建”-“移动APP”项目,输入应用名称“News”,默认选择空模版,点击完成,如下图

20161202img03 20161202img04

建好工程后,按照下图的方式创建好相应的目录,同时导入mui相关的js,css等,其中工程目录的一些说明如下图

20161202img05

PS:此工程已经放置于gogs上,有兴趣的同学可以去下载

按照上图中的目录建设完成,我们可以进行相应的界面内容开发

三、APP内容代码详解

A、数据源

百度API Store中有许多的新闻接口,我们就选择使用百度的API接口,如下code段:

$.ajax({
	type: "GET",
	url: "http://apis.baidu.com/showapi_open_bus/channel_news/search_news?channelId=" + selectedType + "&page=" + pageObj[selectedType],
	beforeSend: function(request) {
		request.setRequestHeader("apikey", "7f6dfa583fe9406f73f2830a5c2fb99c");
	},
	success: function(d) {
		if(d.showapi_res_body.pagebean) {
			var newsArray = d.showapi_res_body.pagebean.contentlist;
			var singleModel = '<div class="news-item">' + $('.news-item-model-single').html() + '</div>';
			var multiModel = '<div class="news-item">' + $('.news-item-model-multi').html() + '</div>';
			var textModel = '<div class="news-item">' + $('.news-item-model-none').html() + '</div>';
			for(var i = 0; i < newsArray.length; i++) {
				//判断新闻是否已经显示过
				if(refreshArt.indexOf(newsArray[i].title) == -1) {
					//设置当前新闻格式,无图片格式、单图片格式、多图片格式
					var model, type;
					if(!newsArray[i].havePic) {
						model = textModel;
						type = 'text';
					} else {
						if(newsArray[i].imageurls.length > 1) {
							model = multiModel;
							type = 'multi';
						} else {
							model = singleModel;
							type = 'single';
						}
					}
					refreshArt.push(newsArray[i].title);
					detailObj[selectedType].push(newsArray[i]);

					$('.' + newId).append(model);
					$('.' + newId).find('.news-item:last').attr('iden', preLength + i);
					$('.' + newId).find('.news-item:last').find('.news-title').html(newsArray[i].title);

					//设置图片
					if(type == 'single') {
						$('.' + newId).find('.news-item:last').find('.news-image img').attr('src', newsArray[i].imageurls[0].url);
					} else {
						for(var j = 0; j < (newsArray[i].imageurls.length > 3 ? 3 : newsArray[i].imageurls.length); j++) {
							$('.' + newId).find('.news-item:last').find('.news-image-multi').find('.flex-element-no-padding:eq(' + j + ')').html('<img src="' + newsArray[i].imageurls[j].url + '"/>')
						}
					}

					$('.' + newId).find('.news-item:last').find('.author-name').html(newsArray[i].source);
					$('.' + newId).find('.news-item:last').find('.pub-date').html(newsArray[i].pubDate);
					//绑定新闻点击事件,点击后进入新闻详情页面
					$('.' + newId).find('.news-item:last').on('tap', function() {
						storage.setItem('newsContent', JSON.stringify(detailObj[selectedType][$(this).attr('iden')]));
						_tl.toUrl('newsDetail.html');
					})
				}
			}

		} else {
			mui.toast('加载失败,请稍后尝试');
		}
		//设置上拉、下拉结束标志
		mui('#pullrefresh').pullRefresh().endPulldownToRefresh(); //refresh completed
		mui('#pullrefresh').pullRefresh().endPullupToRefresh(false);
	}
});

这一段的作用则是从百度API中抓取数据展示至界面中

B. List界面由外部的框架页面和内部列表界面

外部框架页

<!DOCTYPE html>
<html>

	<head>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1, user-scalable=no">
		<meta name="apple-mobile-web-app-capable" content="yes">
		<meta name="apple-mobile-web-app-status-bar-style" content="#fc3434">
		<link rel="stylesheet" href="../css/mui.min.css" />
		<link rel="stylesheet" href="../css/common/base.css" />
		<link rel="stylesheet" href="css/index.css" />
		<title></title>
		<script type="text/javascript" src="../js/common/jquery.js"></script>
		<script type="text/javascript" src="../js/common/flexible/flexible.js"></script>
		<script type="text/javascript" src="../js/mui.min.js"></script>
		<style>
			.android-body .mui-pull-top-pocket{top:1.1rem;margin-top:1.1rem;display: block;height: 1.2rem !important;line-height: 1.2rem;background: #f1f1f1;}
			.android-body .mui-pull{top: 0.1rem;}
		</style>
	</head>

	<body>
		<header class="mui-bar mui-bar-nav">
			<h1 id="title" class="mui-title">热点新闻</h1>
			<div class="news-types">
				<div class="news-types-wrap">
				</div>
			</div>
		</header>
		<script>
			var storage = window.localStorage;
			var selectType;

			function loadChannel() {
				$.getJSON('../data/chanel.json', function(d) {
					$('.news-types-wrap').html('');
					for(var i = 0; i < d.channelList.length; i++) {
						var ar = d.channelList[i];
						$('.news-types-wrap').append('<span type="' + ar.channelId + '">' + ar.name + '</span>');
					}
					$('.news-types-wrap').find('span:first').addClass('selected');
					storage.setItem('selectType', $('.selected').attr('type'));

					//绑定点击事件
					$('.news-types span').on('tap', function() {
						$('.selected').removeClass('selected');
						$(this).addClass('selected');
						storage.setItem('selectType', $(this).attr('type'));
					})
				})
			}

			mui.init({
				statusBarBackground: '#fc3434',
				subpages: [{
					url: 'index.html',
					id: 'index',
					styles: {
						top: parseInt(2.3 * parseInt($('html').css('font-size'))) + 'px', //mui标题栏默认高度为45px;
						bottom: '0px' //默认为0px,可不定义;
					}
				}]
			});

			mui.plusReady(function() {
				//仅支持竖屏显示
				plus.screen.lockOrientation("portrait-primary");
				if(mui.os.android){
					$('body').addClass('android-body');
				}
			})

			$(function() {
				loadChannel();
			})
		</script>
	</body>

</html>

外部框架页定义了,新闻的种类列表,同时使用了mui.init,定义了子页面index.html作为内容列表的提供页

内容列表页index.html

<!DOCTYPE html>
<html>

	<head>
		<meta charset="utf-8">
		<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
		<meta name="apple-mobile-web-app-capable" content="yes">
		<meta name="apple-mobile-web-app-status-bar-style" content="#fc3434">
		<link rel="stylesheet" href="../js/common/swiper/css/swiper.min.css" />
		<link rel="stylesheet" href="../css/mui.min.css" />
		<link rel="stylesheet" href="css/index.css" />
		<title></title>
		<script type="text/javascript" src="../js/common/jquery.js"></script>
		<script type="text/javascript" src="../js/common/flexible/flexible.js"></script>
		<script type="text/javascript" src="../js/common/swiper/js/swiper.jquery.min.js"></script>
		<script type="text/javascript" src="../js/common/circle-progress.js"></script>
		<script type="text/javascript" src="../js/common/tools.js"></script>
		<script type="text/javascript" src="../js/mui.min.js"></script>
		<script type="text/javascript" src="js/index.js"></script>
	</head>

	<body>
		<div class="x-panel-content-full">
			<!--下拉刷新容器-->
			<div id="pullrefresh" class="mui-content mui-scroll-wrapper">
				<div class="mui-scroll">
					<!--数据列表-->
					<ul class="mui-table-view mui-table-view-chevron">
					</ul>
				</div>
			</div>
		</div>

		<div class="news-item-model-single hide">
			<div class="flex-div">
				<div class="news-title flex-element3-no-padding">当你最穷的时候,这样做,不成百万富翁至少也是土豪!</div>
				<div class="news-image flex-element-no-padding">
					<img class="img-01" src="http://05.imgmini.eastday.com/mobile/20161011/20161011123754_349328718cfee5b4d04cd614f1033e3a_1_mwpm_03200403.jpeg" />
				</div>
			</div>
			<div class="news-other flex-div">
				<div class="flex-element-no-padding author-name">中国企业家俱乐部</div>
				<div class="flex-element-no-padding pub-date text-right">2016-10-11 12:37</div>
			</div>
		</div>
		
		<div class="news-item-model-multi hide">
			<div class="news-title">当你最穷的时候,这样做,不成百万富翁至少也是土豪!</div>
			<div class="news-image-multi flex-div">
				<div class="flex-element-no-padding">
				</div>
				<div class="flex-element-no-padding">
				</div>
				<div class="flex-element-no-padding">
				</div>
			</div>
			<div class="news-other flex-div">
				<div class="flex-element-no-padding author-name">中国企业家俱乐部</div>
				<div class="flex-element-no-padding pub-date text-right">2016-10-11 12:37</div>
			</div>
		</div>
		
		<div class="news-item-model-none hide">
			<div class="news-title">当你最穷的时候,这样做,不成百万富翁至少也是土豪!</div>
			<div class="news-other flex-div">
				<div class="flex-element-no-padding author-name">中国企业家俱乐部</div>
				<div class="flex-element-no-padding pub-date text-right">2016-10-11 12:37</div>
			</div>
		</div>

	</body>

</html>

内容列表是一个常规的html5界面,定义了一个上拉下拉刷新的容器,我们来看看index的js定义

index.js

var storage = window.localStorage;
var _tl = getTLInstance();
var detailObj = {}, //新闻内容
	pageObj = {}, //每个模块的分页数
	transObj = {}; //每个模块的滚动条位置

var refreshArt = []; //存储新闻标题的临时数组,防止新闻重复出现

//初始化加载栏
mui.init({
	pullRefresh: {
		container: '#pullrefresh',
		up: {
			contentrefresh: '正在加载更多...',
			callback: doUpLoading
		},
		down: {
			contentrefresh: '正在刷新...',
			callback: doDownLoading
		}
	}
});

这一部分定义了此页面拥有上拉和下拉的功能,并且指定了相应的容器,同时指定了上拉和下拉的回调函数doUpLoading、doDownLoading


$(function() {
	//延迟加载,等待WebView初始化完成
	if(mui.os.plus) {
		mui.plusReady(function() {
			setTimeout(function() {
				triggerUp();
				tempSolution();
			}, 500);

		});
	} else {
		mui.ready(function() {
			setTimeout(function() {
				triggerUp();
				tempSolution();
			}, 100);
		});
	}
	//纪录每个模块的滚动条变化
	setTimeout(function() {
		if(mui.os.android) {
			//Android不识别webkitTransform,因此还是使用的滚动条
			$(window).scroll(function(e) {
				transObj[storage.getItem('selectType')] = document.body.scrollTop;
			})
		} else {
			//IOS、Html识别webkitTransform
			$('.mui-scroll').on('touchend touchcancel', function(e) {
				var str = document.querySelector('.mui-scroll').style.webkitTransform;
				transObj[storage.getItem('selectType')] = $.trim(str.substring(str.indexOf(',') + 1, str.lastIndexOf(',')));
				if(parseInt(transObj[storage.getItem('selectType')]) > 0) {
					transObj[storage.getItem('selectType')] = '0px';
				}
			})
		}
	}, 2100)
})

这一部分定义了页面初始化时要做的事情:触发一个上拉刷新加载内容、同时绑定滚动事件记录当前频道的阅读位置并且存储起来

//触发下拉刷新事件
function triggerUp() {
	var selectedType = storage.getItem('selectType');
	pageObj[selectedType] = 1;
	var newId = 'news-list-' + selectedType;
	$('.news-list').hide();
	//模块初始化后保留上次的加载纪录,避免切换时再次刷新,提高友好度
	if(!$('.' + newId).html()) {
		$('.mui-table-view').append('<div class="news-list ' + newId + '"><div></div></div>');
		mui('#pullrefresh').pullRefresh().pulldownLoading();
	} else {
		$('.' + newId).show();
		//自动定位到每个模块的滚动条位置
		if(mui.os.android) {
			document.body.scrollTop = transObj[selectedType];
		} else {
			mui('.mui-scroll-wrapper').scroll().scrollTo(0, parseInt(transObj[selectedType]), 100);
		}
	}
}

//下拉刷新操作
function doDownLoading() {
	pageObj[storage.getItem('selectType')] = 1;
	loadNews();
}

//上拉加载更多操作
function doUpLoading() {
	pageObj[storage.getItem('selectType')]++;
	loadNews();
}

这一段定义了函数上拉下拉要做的事情;其中triggerUp做了2件事情:1、当点击的频道为第一次加载时,从服务端获取数据;2、当点击的频道为已加载时,自动定位到上次阅读的位置,其中loadNews()为第一段ajax获取数据的部分

//临时解决方案,定时扫描type值,如发生变化则执行刷新操作(IOS、Android无法调用iframe方法)
var lastType;

function tempSolution() {
	setInterval(function() {
		if(!lastType) {
			lastType = storage.getItem('selectType');
		} else {
			if(lastType != storage.getItem('selectType')) {
				lastType = storage.getItem('selectType');
				triggerUp();
			}
		}
	}, 300)
}

C、新闻详情页,由于百度的接口返回的列表中包含了新闻的主体内容,因此在点击新闻时,直接将内容传入展示


<!DOCTYPE html>
<html>

	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
		<meta name="apple-mobile-web-app-capable" content="yes">
		<meta name="apple-mobile-web-app-status-bar-style" content="#fc3434">
		<link rel="stylesheet" href="../js/common/swiper/css/swiper.min.css" />
		<link rel="stylesheet" href="../css/mui.min.css" />
		<link rel="stylesheet" href="css/newsDetail.css" />
		<title></title>
		<script type="text/javascript" src="../js/common/jquery.js"></script>
		<script type="text/javascript" src="../js/common/flexible/flexible.js"></script>
		<script type="text/javascript" src="../js/common/swiper/js/swiper.jquery.min.js"></script>
		<script type="text/javascript" src="../js/common/circle-progress.js"></script>
		<script type="text/javascript" src="../js/common/tools.js"></script>
		<script type="text/javascript" src="../js/mui.min.js"></script>
		<script type="text/javascript" src="js/newsDetail.js"></script>
	</head>

	<body>
		<div class="x-panel-top">
			<div class="x-panel-top-left" onclick="mui.back()">
				<span class="mui-icon mui-icon-back"></span>
			</div>
			<div class="x-panel-top-center">
				<div class="x-title-bar">
					热点新闻
				</div>
			</div>
			<div class="x-panel-top-right">

			</div>
		</div>
		<div class="x-panel-content">
			<article id="J_article" class="J-article article">
				<div id="title">
					<div class="article-title">
						<h1 class="title"></h1>
					</div>
					<div class="article-src-time">
						<span class="src"></span>
					</div>
				</div>
				<div id="content" class="J-article-content article-content" data-pswp-uid="1">

				</div>
			</article>
		</div>
	</body>

</html>

var storage = window.localStorage;
var _tl = getTLInstance();
var _plus;

mui.plusReady(function() {
	_plus = plus;
})

$(function() {
	var news = JSON.parse(storage.getItem('newsContent'));
	$('.title').html(news.title);
	$('.src').html(news.pubDate+'&nbsp;&nbsp;&nbsp;&nbsp;来源:'+news.source);
	var newsContent = news.allList;
	for(var i=0;i<newsContent.length;i++){
		if(typeof(newsContent[i]) == 'string'){
			$('.article-content').append('<p class="section txt">'+newsContent[i]+'</p>');
		}else{
			$('.article-content').append('<figure class="section img"><a class="img-wrap"><img src="'+newsContent[i].url+'"></a></figure>');
		}
	}

})

四、APP打包及真机查看

A 真机调试

mac电脑安装xcode后,即可使用模拟器进行IOS调试、连接iphone后也可以进行真机运行

mac电脑连接android手机也可以进行真机调试,android手机务必打开“开发人员选项”-“USB调试”

window电脑暂时不能使用IOS模拟器,可使用真机运行

B 打包应用

右键应用选择“发行”-“发行为原生安装包”按操作要求进行即可,需说明:

Android打包需要定义keystore,即签名文件

IOS打包需要从apple开发者平台下载证书和描述文件才能打包,在下面的章节我会详细的说明如何下载苹果证书、创建安卓签名文件

结束语

通过上面的例子、结合实例和mui官网的介绍,可以很快速的入门进行h5的APP开发