基于mui+vue框架的跨平台客户端app---购物商城
涉及到知识点:
- vue.js
- 首页底部Tab
- banner广告
- 沉浸式状态栏
- 分类顶部切换的slider
- 图片预览并保存到相册
- 拍照、扫码、选择本地文件
- 侧滑栏
- 搜索列表页两层侧滑栏筛选
- 上拉刷新下拉加载
- 登录、退出逻辑处理
主要功能点:
首页商品展示,商品分类,搜索列表,商品详情,购物车,下订单,个人中心,我的订单/退单,收货地址管理
示例图片
常见问题
- 首页底部的Tab
html页面
<nav class="mui-bar mui-bar-tab footerBar">
<a id="defaultTab" class="mui-tab-item ft-tab-item mui-active" href="home.html">
<span class=" mui-icon iconfont icon-m-ao"></span>
<span class="mui-tab-label">首页</span>
</a>
<a id="tab1" class="mui-tab-item ft-tab-item" href="classify.html">
<span class="mui-icon iconfont icon-m-am"></span>
<span class="mui-tab-label">分类</span>
</a>
<a id="tab2" class="mui-tab-item ft-tab-item" href="cart.html">
<span class="mui-icon iconfont icon-m-y"></span>
<span class="mui-tab-label">购物车</span>
</a>
<a id="tab3" class="mui-tab-item ft-tab-item" href="my.html">
<span class="mui-icon iconfont icon-m-aq"></span>
<span class="mui-tab-label">我的</span>
</a>
</nav>
js代码
//底部选项卡切换跳转
(function jumpPage() {
//跳转页面
var subpages = ['view/home/home.html', 'view/home/classify.html', 'view/home/cart.html', 'view/home/my.html'];
var ids = ['home.html', 'classify.html', 'cart.html', 'my.html'];
var aniShow = {};
//创建子页面,首个选项卡页面显示,其它均隐藏;
mui.plusReady(function() {
plus.screen.lockOrientation("portrait-primary");
var subpage_style = {
top: '0px',
bottom: '51px'
};
//设置bottom绝对位置
//iphoneX中出现遮挡底部tab现象,采用js判断屏幕大小方式改变bottom值
//isIPhoneX() 要在plusReady后调用
if (isIPhoneX()) {
subpage_style = {
top: '0px',
bottom: '88px', //34px
styles: {
"render": "always",
}
};
}
var self = plus.webview.currentWebview();
for (var i = 0; i < 4; i++) {
var temp = {};
var sub = plus.webview.create(subpages[i], ids[i], subpage_style);
if (i > 0) {
sub.hide();
} else {
temp[ids[i]] = "true";
mui.extend(aniShow, temp);
}
self.append(sub);
}
});
//当前激活选项
var activeTab = ids[0];
//选项卡点击事件
mui('.mui-bar-tab').on('tap', 'a', function(e) {
e.preventDefault();
var targetTab = this.getAttribute('href');
if (targetTab == activeTab) {
return;
}
//显示目标选项卡
//若为iOS平台或非首次显示,则直接显示
if (mui.os.ios || aniShow[targetTab]) {
plus.webview.show(targetTab);
} else {
//否则,使用fade-in动画,且保存变量
var temp = {};
temp[targetTab] = "true";
mui.extend(aniShow, temp);
plus.webview.show(targetTab, "fade-in", 300);
}
//隐藏当前;
plus.webview.hide(activeTab);
//更改当前活跃的选项卡
activeTab = targetTab;
});
})()
注意iphoneX中出现遮挡底部tab现象,采用js判断屏幕大小方式改变bottom值,isIPhoneX(),isIPhoneX() 要在plusReady后调用。
- 沉浸式状态栏
1.在manifest.json文件,切换到代码视图,在plus -> statusbar 下添加immersed节点并设置值为true
"statusbar" : {
"immersed" : true
},
2.设置了沉浸式状态栏后,状态栏的高度变为0,如图所示
https://github.com/gs-wenbing/mui-mall/blob/master/img/show/status1.jpg
输入框把状态挡住了,这时候需要重写mui.css或者mui.min.css样式表,在样式表底部添加如下一段样式
*解决沉寖式状态栏导致导航栏高度少20px的问题*/
.mui-bar-nav {
height: 64px;
padding-top: 22px;
}
.mui-bar-nav ~ .mui-content
{
padding-top: 64px;
}
.mui-bar-nav ~ .mui-content .mui-pull-top-pocket
{
top: 64px;
}
.mui-bar-nav ~ .mui-content.mui-scroll-wrapper .mui-scrollbar-vertical
{
top: 64px;
}
.mui-bar-nav ~ .mui-content .mui-slider.mui-fullscreen
{
top: 64px;
}
显示效果如图
https://github.com/gs-wenbing/mui-mall/blob/master/img/show/status2.jpg
注意:以上操作后Android沉浸式状态就完成了,但是IOS还需在distribute节点下的apple节点下添加
"UIReserveStatusbarOffset" : false
本以为沉浸式状态栏就完成了,结果老板iPhoneX手机显示有问题,于是又单独适配iPhoneX,具体操作:
在mui.js或者mui.min.js中底部添加如下一段代码:
/**
* 适配iPhone X 系列手机的导航栏(包括状态栏)
*/
mui.plusReady(function(){
if(plus.navigator.isImmersedStatusbar() && isIPhoneX()){
//.mui-bar-nav
var nav = document.querySelector(".mui-bar-nav");
if(nav){
nav.style.cssText="height:88px; padding-top: 44px;";
} else {
return;
}
//.mui-bar-nav ~ .mui-content
var content = document.querySelector(".mui-content");
if (content) {
content.style.paddingTop = "88px";
} else {
return;
}
//.mui-bar-nav ~ .mui-content .mui-pull-top-pocket
var pullTopPocket_Arr = content.querySelectorAll(".mui-pull-top-pocket");
if (pullTopPocket_Arr) {
pullTopPocket_Arr.forEach(function(value){
value.style.top = "88px";
});
}
//.mui-bar-nav ~ .mui-content.mui-scroll-wrapper .mui-scrollbar-vertical
var scrollbarVertical = document.querySelector(".mui-content.mui-scroll-wrapper .mui-scrollbar-vertical");
if (scrollbarVertical) {
scrollbarVertical.style.top = "88px";
}
//.mui-bar-nav ~ .mui-content .mui-slider.mui-fullscreen
var slider_fullscreen_Arr = content.querySelectorAll(".mui-content .mui-slider.mui-fullscreen");
if (slider_fullscreen_Arr) {
slider_fullscreen_Arr.forEach(function(value){
value.style.top = "88px";
});
}
}
});
/**
* 判断是否为iPhone X 系列机型
*/
function isIPhoneX(){
if(plus.device.model.indexOf('iPhone') > -1 && screen.height >= 812){
return true;
}else{
return false;
}
}
- 标题栏在IOS上存在的问题
原生标题栏
var styles = { // 窗口参数 参考5+规范中的WebviewStyle,也就是说WebviewStyle下的参数都可以在此设置
titleNView: { // 窗口的标题栏控件
titleText: title, // 标题栏文字,当不设置此属性时,默认加载当前页面的标题,并自动更新页面的标题
titleColor: "#FFFFFF", // 字体颜色,颜色值格式为"#RRGGBB",默认值为"#000000"
backgroundColor: "#E60012", // 控件背景颜色,颜色值格式为"#RRGGBB",默认值为"#F7F7F7"
autoBackButton: true
}
}
plus.webview.create(url, id, styles, extras);
1.mui原生标题栏,假如titleColor的值为小写(#ffffff)的话,在IOS上不显示标题,必须要大写(#FFFFFF)才显示,亲测<br>
2.非原生标题栏,假如页面中有输入框的话,软键盘弹出,IOS上会把标题栏顶上去,因为ios弹出软键盘的时候,webview的高度没有变化导致超出屏幕范围,
而plus这时候又会自动把header的 position:fixed 属性设置为 position:relative,header就跟着滚动了。在mui社区找到一个的解决方案:
http://ask.dcloud.net.cn/question/10629
plus.webview.currentWebview().setStyle({
softinputMode: "adjustResize" // 弹出软键盘时自动改变webview的高度
});
html, body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
-webkit-touch-callout: none;
-webkit-user-select: none;
}
.mui-content {
height: 100%;
overflow: auto;
}
这样会解决IOS标题栏顶上去的问题,但是这样处理后,页面打开标题栏会有抖动,虽然很短暂,但是看着不爽,有更好的方案可以联系我(QQ742330561)
具体的demo地址: https://github.com/gs-wenbing/mui-mall
如果对您有帮助,您可以"Star" 支持一下 谢谢! ^^
涉及到知识点:
- vue.js
- 首页底部Tab
- banner广告
- 沉浸式状态栏
- 分类顶部切换的slider
- 图片预览并保存到相册
- 拍照、扫码、选择本地文件
- 侧滑栏
- 搜索列表页两层侧滑栏筛选
- 上拉刷新下拉加载
- 登录、退出逻辑处理
主要功能点:
首页商品展示,商品分类,搜索列表,商品详情,购物车,下订单,个人中心,我的订单/退单,收货地址管理
示例图片
常见问题
- 首页底部的Tab
html页面
<nav class="mui-bar mui-bar-tab footerBar">
<a id="defaultTab" class="mui-tab-item ft-tab-item mui-active" href="home.html">
<span class=" mui-icon iconfont icon-m-ao"></span>
<span class="mui-tab-label">首页</span>
</a>
<a id="tab1" class="mui-tab-item ft-tab-item" href="classify.html">
<span class="mui-icon iconfont icon-m-am"></span>
<span class="mui-tab-label">分类</span>
</a>
<a id="tab2" class="mui-tab-item ft-tab-item" href="cart.html">
<span class="mui-icon iconfont icon-m-y"></span>
<span class="mui-tab-label">购物车</span>
</a>
<a id="tab3" class="mui-tab-item ft-tab-item" href="my.html">
<span class="mui-icon iconfont icon-m-aq"></span>
<span class="mui-tab-label">我的</span>
</a>
</nav>
js代码
//底部选项卡切换跳转
(function jumpPage() {
//跳转页面
var subpages = ['view/home/home.html', 'view/home/classify.html', 'view/home/cart.html', 'view/home/my.html'];
var ids = ['home.html', 'classify.html', 'cart.html', 'my.html'];
var aniShow = {};
//创建子页面,首个选项卡页面显示,其它均隐藏;
mui.plusReady(function() {
plus.screen.lockOrientation("portrait-primary");
var subpage_style = {
top: '0px',
bottom: '51px'
};
//设置bottom绝对位置
//iphoneX中出现遮挡底部tab现象,采用js判断屏幕大小方式改变bottom值
//isIPhoneX() 要在plusReady后调用
if (isIPhoneX()) {
subpage_style = {
top: '0px',
bottom: '88px', //34px
styles: {
"render": "always",
}
};
}
var self = plus.webview.currentWebview();
for (var i = 0; i < 4; i++) {
var temp = {};
var sub = plus.webview.create(subpages[i], ids[i], subpage_style);
if (i > 0) {
sub.hide();
} else {
temp[ids[i]] = "true";
mui.extend(aniShow, temp);
}
self.append(sub);
}
});
//当前激活选项
var activeTab = ids[0];
//选项卡点击事件
mui('.mui-bar-tab').on('tap', 'a', function(e) {
e.preventDefault();
var targetTab = this.getAttribute('href');
if (targetTab == activeTab) {
return;
}
//显示目标选项卡
//若为iOS平台或非首次显示,则直接显示
if (mui.os.ios || aniShow[targetTab]) {
plus.webview.show(targetTab);
} else {
//否则,使用fade-in动画,且保存变量
var temp = {};
temp[targetTab] = "true";
mui.extend(aniShow, temp);
plus.webview.show(targetTab, "fade-in", 300);
}
//隐藏当前;
plus.webview.hide(activeTab);
//更改当前活跃的选项卡
activeTab = targetTab;
});
})()
注意iphoneX中出现遮挡底部tab现象,采用js判断屏幕大小方式改变bottom值,isIPhoneX(),isIPhoneX() 要在plusReady后调用。
- 沉浸式状态栏
1.在manifest.json文件,切换到代码视图,在plus -> statusbar 下添加immersed节点并设置值为true
"statusbar" : {
"immersed" : true
},
2.设置了沉浸式状态栏后,状态栏的高度变为0,如图所示
https://github.com/gs-wenbing/mui-mall/blob/master/img/show/status1.jpg
输入框把状态挡住了,这时候需要重写mui.css或者mui.min.css样式表,在样式表底部添加如下一段样式
*解决沉寖式状态栏导致导航栏高度少20px的问题*/
.mui-bar-nav {
height: 64px;
padding-top: 22px;
}
.mui-bar-nav ~ .mui-content
{
padding-top: 64px;
}
.mui-bar-nav ~ .mui-content .mui-pull-top-pocket
{
top: 64px;
}
.mui-bar-nav ~ .mui-content.mui-scroll-wrapper .mui-scrollbar-vertical
{
top: 64px;
}
.mui-bar-nav ~ .mui-content .mui-slider.mui-fullscreen
{
top: 64px;
}
显示效果如图
https://github.com/gs-wenbing/mui-mall/blob/master/img/show/status2.jpg
注意:以上操作后Android沉浸式状态就完成了,但是IOS还需在distribute节点下的apple节点下添加
"UIReserveStatusbarOffset" : false
本以为沉浸式状态栏就完成了,结果老板iPhoneX手机显示有问题,于是又单独适配iPhoneX,具体操作:
在mui.js或者mui.min.js中底部添加如下一段代码:
/**
* 适配iPhone X 系列手机的导航栏(包括状态栏)
*/
mui.plusReady(function(){
if(plus.navigator.isImmersedStatusbar() && isIPhoneX()){
//.mui-bar-nav
var nav = document.querySelector(".mui-bar-nav");
if(nav){
nav.style.cssText="height:88px; padding-top: 44px;";
} else {
return;
}
//.mui-bar-nav ~ .mui-content
var content = document.querySelector(".mui-content");
if (content) {
content.style.paddingTop = "88px";
} else {
return;
}
//.mui-bar-nav ~ .mui-content .mui-pull-top-pocket
var pullTopPocket_Arr = content.querySelectorAll(".mui-pull-top-pocket");
if (pullTopPocket_Arr) {
pullTopPocket_Arr.forEach(function(value){
value.style.top = "88px";
});
}
//.mui-bar-nav ~ .mui-content.mui-scroll-wrapper .mui-scrollbar-vertical
var scrollbarVertical = document.querySelector(".mui-content.mui-scroll-wrapper .mui-scrollbar-vertical");
if (scrollbarVertical) {
scrollbarVertical.style.top = "88px";
}
//.mui-bar-nav ~ .mui-content .mui-slider.mui-fullscreen
var slider_fullscreen_Arr = content.querySelectorAll(".mui-content .mui-slider.mui-fullscreen");
if (slider_fullscreen_Arr) {
slider_fullscreen_Arr.forEach(function(value){
value.style.top = "88px";
});
}
}
});
/**
* 判断是否为iPhone X 系列机型
*/
function isIPhoneX(){
if(plus.device.model.indexOf('iPhone') > -1 && screen.height >= 812){
return true;
}else{
return false;
}
}
- 标题栏在IOS上存在的问题
原生标题栏
var styles = { // 窗口参数 参考5+规范中的WebviewStyle,也就是说WebviewStyle下的参数都可以在此设置
titleNView: { // 窗口的标题栏控件
titleText: title, // 标题栏文字,当不设置此属性时,默认加载当前页面的标题,并自动更新页面的标题
titleColor: "#FFFFFF", // 字体颜色,颜色值格式为"#RRGGBB",默认值为"#000000"
backgroundColor: "#E60012", // 控件背景颜色,颜色值格式为"#RRGGBB",默认值为"#F7F7F7"
autoBackButton: true
}
}
plus.webview.create(url, id, styles, extras);
1.mui原生标题栏,假如titleColor的值为小写(#ffffff)的话,在IOS上不显示标题,必须要大写(#FFFFFF)才显示,亲测<br>
2.非原生标题栏,假如页面中有输入框的话,软键盘弹出,IOS上会把标题栏顶上去,因为ios弹出软键盘的时候,webview的高度没有变化导致超出屏幕范围,
而plus这时候又会自动把header的 position:fixed 属性设置为 position:relative,header就跟着滚动了。在mui社区找到一个的解决方案:
http://ask.dcloud.net.cn/question/10629
plus.webview.currentWebview().setStyle({
softinputMode: "adjustResize" // 弹出软键盘时自动改变webview的高度
});
html, body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
-webkit-touch-callout: none;
-webkit-user-select: none;
}
.mui-content {
height: 100%;
overflow: auto;
}
这样会解决IOS标题栏顶上去的问题,但是这样处理后,页面打开标题栏会有抖动,虽然很短暂,但是看着不爽,有更好的方案可以联系我(QQ742330561)
具体的demo地址: https://github.com/gs-wenbing/mui-mall
如果对您有帮助,您可以"Star" 支持一下 谢谢! ^^
支付宝小程序分享后,用户通过分享的应用点进来之后,tabbar消失。
支付宝小程序分享后,用户通过分享的应用点进来之后,tabbar消失。
第二个图是分享后用户点进来的页面!
第三个是正常的!
支付宝小程序分享后,用户通过分享的应用点进来之后,tabbar消失。
第二个图是分享后用户点进来的页面!
第三个是正常的!
Windows电脑直接申请iOS证书p12及.mobileprovision文件过程
Appuploader软件可以辅助在Windows电脑直接申请iOS证书,并且可以上传ipa到App Store审核!
对于没有Mac电脑的开发者,是一个很好的iOS上架辅助工具
下面介绍申请一套iOS开发证书申请及在HBuilder平台打包的过程!
一、下载安装iOS证书申请及上架辅助软件Appuploader
下载软件包后解压直接使用,无需安装。
二、登录Appuploader申请iOS证书文件p12
2.1打开Appuploader,用苹果开发者账号登录进去。
2.2、选择证书项目进入
2.3、点击右下角+ADD选择
类型:选择开发证书(开发证书打包的才能安装到手机)
输入证书名称:不要中文、随意设置
邮箱:(随意)
密码:证书的密码,不是开发者账号密码,如123这样不用很复杂,记好、打包时要用、很重要。
应用id:这里不用选!
点击ok创建。
注意:iOS开发证书最多能申请2个,如果账号已经有2个开发证书了,将申请报错(如下图)。
一个开发证书可以用于多个APP测试,不用每个app都对应申请个开发证书p12,开发证书p12是可以公用的,区分开下面步骤申请的的描述文件就行了,一个p12可以对应无数描述文件。
当然也可以删除之前的重新申请
2.4、创建成功后,找到刚创建的iOS开发证书(iOS Development这个类型的就是开发证书,如果之前创建过看过期时间就知道哪个是新创建的了),点击p12 文件,下载保存.p12证书文件到电脑。
三、登录Appuploader申请iOS开发证书描述文件mobileprovision
iOS证书文件有两个,刚申请了p12文件,接下来申请mobileprovision描述文件
在申请ios描述文件之前,先添加好两样基础信息,应用id及苹果手机设备号udid
如果你之前添加过应用id和设备,应用id点击三角符号下拉可以看到,设备选择开发版profile选项可以看到之前添加的。
如果之前加过要用以前加过的应用id,跳过此步。
初次使用或者需要上架另一个项目app需要先创建一个应用id及添加相关的开发测试人员设备。
3.1、先介绍添加应用ID,点击添加应用id!(不同的APP需要编写不同的应用id相当于app的身份证)
应用id:三段式格式、如app名称是淘宝,可以编写为com.app.taobao,自由编写!不能重复!具有唯一性@
名称:数字或者字母,自由编写,不要中文,不能重复。
如果添加报错(应用id具有唯一性,可能重复添加或者别人用过这个应用id),解决办法就是修改下应用id,重新编下。
在AU软件添加的应用id只有默认权限,如果你需要开通推送通知、苹果支付等权限需要到苹果开发者中心配置!
点击ok只要没弹出报错就是添加成功了,注意先关掉窗口,重新点右下角+ADD进入下拉应用id可查看刚添加的应用id是否存在。
填加好应用id下步添加设置udid
3.2 添加测试设备udid
要安装到哪个苹果手机测试就添加哪个手机的udid,添加了udid的手机才能安装,最多能加100个!
如果你以前加过udid,不需要加新的测试苹果手机上去,跳过此步,直接申请证书!
先来获取udid
udid如果你的手机链接了电脑并且安装好了相关驱动,AU软件会自动获取,直接点ok添加就行了
其他两种获取udid的方式
3.3、苹果手机助手获取UDID
如爱思助手,电脑下载爱思助手,连上苹果手机,设备信息里面那个设备标识就是udid。
3.4、扫码获取苹果手机udid
使用 iPhone 或 iPad 微信扫码选择自带的浏览器safari浏览器打开二维码里的链接,即可快速获取 UDID
这个一长串的就是设备的Udid
udid实例:2D4B87350609342980CB144F72FD2E66B66AEF6C
获取到udid输入
名称name:数字或者名字,自由编写,不能重复,不能中文。
点击ok无提示即为添加成功
在苹果开发中心也可以添加设备,在AU软件添加的设备在开发者中心也会有显示。
如果苹果设备udid添加报错(如下图),检查这个udid是否正确或者之前添加过!
Type选择开发版即可看到刚添加的设备。
3.5、勾选相关信息生成ios开发证书描述文件
Type:选择开发版profile(安装到手机测试需要开发证书)
应用id:选择添加的对应的应用id
Devices:勾选需要测试苹果手机
名称:数字或者字母,随意输入,注意不要重复。
点击ok创建
3.6、选择刚创建的iOS开发版描述文件(iOS Developer这个类型的就是开发描述文件,找到刚创建的输入的名字),点击Download下载,保存到电脑
申请得到了两个iOS证书文件p12和.mobileprovision就可以去打包ipa了。
打开HBuilder工具,选择开发好的项目,点击发行,选择发行为原生安装包。
AppID:跟申请证书描述.mobileprovision时选择的要一致(又称套装id,appid,BundleID,应用id,包名)
profile文件:选择上传配置文件.mobileprovision
私钥证书:上传.p12文件
私钥密码:输入创建p12设置的密码。
然后点击打包。
(
打包成功后,下载保存ipa,这个ipa包就能安装到手机测试了。
(
Appuploader软件可以辅助在Windows电脑直接申请iOS证书,并且可以上传ipa到App Store审核!
对于没有Mac电脑的开发者,是一个很好的iOS上架辅助工具
下面介绍申请一套iOS开发证书申请及在HBuilder平台打包的过程!
一、下载安装iOS证书申请及上架辅助软件Appuploader
下载软件包后解压直接使用,无需安装。
二、登录Appuploader申请iOS证书文件p12
2.1打开Appuploader,用苹果开发者账号登录进去。
2.2、选择证书项目进入
2.3、点击右下角+ADD选择
类型:选择开发证书(开发证书打包的才能安装到手机)
输入证书名称:不要中文、随意设置
邮箱:(随意)
密码:证书的密码,不是开发者账号密码,如123这样不用很复杂,记好、打包时要用、很重要。
应用id:这里不用选!
点击ok创建。
注意:iOS开发证书最多能申请2个,如果账号已经有2个开发证书了,将申请报错(如下图)。
一个开发证书可以用于多个APP测试,不用每个app都对应申请个开发证书p12,开发证书p12是可以公用的,区分开下面步骤申请的的描述文件就行了,一个p12可以对应无数描述文件。
当然也可以删除之前的重新申请
2.4、创建成功后,找到刚创建的iOS开发证书(iOS Development这个类型的就是开发证书,如果之前创建过看过期时间就知道哪个是新创建的了),点击p12 文件,下载保存.p12证书文件到电脑。
三、登录Appuploader申请iOS开发证书描述文件mobileprovision
iOS证书文件有两个,刚申请了p12文件,接下来申请mobileprovision描述文件
在申请ios描述文件之前,先添加好两样基础信息,应用id及苹果手机设备号udid
如果你之前添加过应用id和设备,应用id点击三角符号下拉可以看到,设备选择开发版profile选项可以看到之前添加的。
如果之前加过要用以前加过的应用id,跳过此步。
初次使用或者需要上架另一个项目app需要先创建一个应用id及添加相关的开发测试人员设备。
3.1、先介绍添加应用ID,点击添加应用id!(不同的APP需要编写不同的应用id相当于app的身份证)
应用id:三段式格式、如app名称是淘宝,可以编写为com.app.taobao,自由编写!不能重复!具有唯一性@
名称:数字或者字母,自由编写,不要中文,不能重复。
如果添加报错(应用id具有唯一性,可能重复添加或者别人用过这个应用id),解决办法就是修改下应用id,重新编下。
在AU软件添加的应用id只有默认权限,如果你需要开通推送通知、苹果支付等权限需要到苹果开发者中心配置!
点击ok只要没弹出报错就是添加成功了,注意先关掉窗口,重新点右下角+ADD进入下拉应用id可查看刚添加的应用id是否存在。
填加好应用id下步添加设置udid
3.2 添加测试设备udid
要安装到哪个苹果手机测试就添加哪个手机的udid,添加了udid的手机才能安装,最多能加100个!
如果你以前加过udid,不需要加新的测试苹果手机上去,跳过此步,直接申请证书!
先来获取udid
udid如果你的手机链接了电脑并且安装好了相关驱动,AU软件会自动获取,直接点ok添加就行了
其他两种获取udid的方式
3.3、苹果手机助手获取UDID
如爱思助手,电脑下载爱思助手,连上苹果手机,设备信息里面那个设备标识就是udid。
3.4、扫码获取苹果手机udid
使用 iPhone 或 iPad 微信扫码选择自带的浏览器safari浏览器打开二维码里的链接,即可快速获取 UDID
这个一长串的就是设备的Udid
udid实例:2D4B87350609342980CB144F72FD2E66B66AEF6C
获取到udid输入
名称name:数字或者名字,自由编写,不能重复,不能中文。
点击ok无提示即为添加成功
在苹果开发中心也可以添加设备,在AU软件添加的设备在开发者中心也会有显示。
如果苹果设备udid添加报错(如下图),检查这个udid是否正确或者之前添加过!
Type选择开发版即可看到刚添加的设备。
3.5、勾选相关信息生成ios开发证书描述文件
Type:选择开发版profile(安装到手机测试需要开发证书)
应用id:选择添加的对应的应用id
Devices:勾选需要测试苹果手机
名称:数字或者字母,随意输入,注意不要重复。
点击ok创建
3.6、选择刚创建的iOS开发版描述文件(iOS Developer这个类型的就是开发描述文件,找到刚创建的输入的名字),点击Download下载,保存到电脑
申请得到了两个iOS证书文件p12和.mobileprovision就可以去打包ipa了。
打开HBuilder工具,选择开发好的项目,点击发行,选择发行为原生安装包。
AppID:跟申请证书描述.mobileprovision时选择的要一致(又称套装id,appid,BundleID,应用id,包名)
profile文件:选择上传配置文件.mobileprovision
私钥证书:上传.p12文件
私钥密码:输入创建p12设置的密码。
然后点击打包。
(
打包成功后,下载保存ipa,这个ipa包就能安装到手机测试了。
(
收起阅读 »Android平台App支持CPU类型配置说明
此文档不再维护,请参考新文档地址:https://uniapp.dcloud.io/tutorial/app-android-abifilters
HBuilderX2.7.0+ 调整 云端打包默认不再包含 x86 CPU类型库,减少apk包体积 <a href="https://ask.dcloud.net.cn/article/36195#nox86" target="_self">详情</a>
HBuilderX2.1.5+ 开始支持Android平台的新增适配64位CPU类型,云端打包支持配置App支持的CPU类型
满足Google Play从2019年8月1日起上传的App必需支持64位CPU的要求。
不管5+App、wap2app、uni-app的各种编译模式,均已支持。
上一代HBuilder不支持,请升级更新为HBuilderX。
概要
Android平台配置CPU类型针对的是为了提高运行效率使用C/C++语言开发生成的so库,需要为各cpu类型平台单独编译生成对应指令的so库。Java语言开发的代码运行在虚拟机中,由虚拟机适配CPU类型,不涉及到此问题。
HBuilder/HBuilderX中使用so库的功能(模块)
- Audio(录音):支持mp3格式
- Geolocation(定位):百度
- LivePush(直播推流)
- Maps(地图):高德、百度
- OAuth(登录鉴权):新浪微博
- Push(消息推送):个推、UniPush
- Share(分享):新浪微博
- Speech(语音输入):百度,注意:讯飞不支持64位
- Weex(原生渲染):uni-app(自定义组件模式、nvue页面), 注意:HBuilderX2.1.5及以上版本支持
CPU类型
目前HBuilder/HBulderX适配支持以下主流CPU类型:
- armeabi-v7a
第7代及以上的ARM处理器(ARM32位),市面上大多数手机使用此CPU类型。 - arm64-v8a
第8代、64位ARM处理器(ARM64位),最近两年新发的设备使用此CPU类型,可以兼容使用armeabi-v7a的so库。 - x86
少部分平板使用x86,AS模拟器中选了intel x86时使用x86处理器,以及其它常用三方模拟器通常使用x86
注意:不勾选x86在模拟器上可能无法正常运行,以下是常见模拟器是否需要包含x86的情况
- 雷电模拟器:
3.x必须包含x86,否则无法正常运行;4.x无需包含x86。 - 夜神模拟器:
必须包含x86,否则无法正常运行 - MuMu模拟器:
无需包含x86 - 逍遥模拟器:
无需包含x86 - BlueStacks(蓝叠模拟器):
无需包含x86 - 腾讯模拟器(手游助手):
必须包含x86,否则无法正常运行 - 其它模拟器:
未测试验证,建议包含x86,确保在模拟器正常运行
云端打包配置
可视化界面配置
打开项目的manifest.json文件,切换到“App常用其它设置”页面,勾选需要支持的cpu类型:
源码视图配置
打开项目的manifest.json文件,切换到“源码视图”
5+APP项目在 "plus" -> "distribute" -> "google" -> "abiFilters",uni-app项目在 "app-plus" -> "distribute" -> "android" -> "abiFilters" 添加要支持的CPU类型:
"abiFilters":[
"armeabi-v7a",
"arm64-v8a"
],
在上面节点配置需要支持的cpu类型,不需要的删除即可,不设置此字段则默认使用armeabi-v7a(ARM32位)
提交Google Play时要求支持64位,请选择"armeabi-v7a"、"arm64-v8a"两个即可。不要勾选“x86”
保存后提交云端打包生效。
查看打包后插件apk支持的CPU类型
使用解压工具打开apk,在lib目录下可以查看到支持的CPU类型,如下图所示:
离线打包配置
配置CPU类型
使用Android studio打开Android原生项目,打开对应项目的build.gradle文件。
在Android -> defaultConfig下添加支持的CPU类型,如下示例:
defaultConfig{
ndk {
abiFilters 'arm64-v8a','armeabi-v7a'
}
}
注意:离线打包仅支持arm64-v8a、armeabi-v7a、x86三种类型,建议根据自己需求选择打包的CPU类型
CPU类型选择建议
ARM64位(arm64-v8a)CPU可以兼容ARM32的指令,也就是说只选择armeabi-v7a类型的so库也可以在64位手机上运行,只是没有完全发挥CPU的性能。
选择支持的CPU类型时请参考以下建议:
- 如果不在意apk大小,三种CPU类型都勾选
- 如果在意apk大小,选择ARM32位即可(几乎在所有ARM指令的所有设备上都可正常运行)
- 如果要兼容一些平板和模拟器,选择ARM32位和X86
不是所有模拟都仅支持x86指令,如雷电(4.x)、MuMu等模拟器也是支持ARM指令。
CPU类型错误安装提示
如果打包选择的CPU类型与设备不兼容,会导致无法正常安装。
通过adb命令安装通常会提示如下错误:
Performing Streamed Install
adb: failed to install android_debug.apk: Failure [INSTALL_FAILED_NO_MATCHING_ABIS: Failed to extract native libraries, res=-113]
使用Android Studio自带的x86模拟器,将不包含x86 cpu类型的apk拖到模拟器安装时会弹出如下提示框:
<a id="nox86"/>
HBuilderx2.7.0+ 云端打包默认CPU类型不再包含x86
目前市面上常见的手机都是使用ARM处理器,很少有设备使用x86处理器,因此从HBuilderX2.7.0开始云端打包调整为默认不再包含x86的CPU类型,减少apk包大小:
- uni-app项目
基础功能apk减少5M+,使用的三方SDK及uni原生插件越多,减少的包尺寸越大,具体值取决于其包含的x86类型的so库大小 - 5+App、Wap2App项目
基础功能apk减少100K+,如果使用的三方SDK中存在so库则减少的尺寸较大,具体值取决于其包含的x86类型的so库大小
注意:大多数模拟器(如夜神)必须包含x86,否则应用启动时可能会白屏,请参考上面“配置CPU类型”章节勾选“x86”
此文档不再维护,请参考新文档地址:https://uniapp.dcloud.io/tutorial/app-android-abifilters
HBuilderX2.7.0+ 调整 云端打包默认不再包含 x86 CPU类型库,减少apk包体积 <a href="https://ask.dcloud.net.cn/article/36195#nox86" target="_self">详情</a>
HBuilderX2.1.5+ 开始支持Android平台的新增适配64位CPU类型,云端打包支持配置App支持的CPU类型
满足Google Play从2019年8月1日起上传的App必需支持64位CPU的要求。
不管5+App、wap2app、uni-app的各种编译模式,均已支持。
上一代HBuilder不支持,请升级更新为HBuilderX。
概要
Android平台配置CPU类型针对的是为了提高运行效率使用C/C++语言开发生成的so库,需要为各cpu类型平台单独编译生成对应指令的so库。Java语言开发的代码运行在虚拟机中,由虚拟机适配CPU类型,不涉及到此问题。
HBuilder/HBuilderX中使用so库的功能(模块)
- Audio(录音):支持mp3格式
- Geolocation(定位):百度
- LivePush(直播推流)
- Maps(地图):高德、百度
- OAuth(登录鉴权):新浪微博
- Push(消息推送):个推、UniPush
- Share(分享):新浪微博
- Speech(语音输入):百度,注意:讯飞不支持64位
- Weex(原生渲染):uni-app(自定义组件模式、nvue页面), 注意:HBuilderX2.1.5及以上版本支持
CPU类型
目前HBuilder/HBulderX适配支持以下主流CPU类型:
- armeabi-v7a
第7代及以上的ARM处理器(ARM32位),市面上大多数手机使用此CPU类型。 - arm64-v8a
第8代、64位ARM处理器(ARM64位),最近两年新发的设备使用此CPU类型,可以兼容使用armeabi-v7a的so库。 - x86
少部分平板使用x86,AS模拟器中选了intel x86时使用x86处理器,以及其它常用三方模拟器通常使用x86
注意:不勾选x86在模拟器上可能无法正常运行,以下是常见模拟器是否需要包含x86的情况
- 雷电模拟器:
3.x必须包含x86,否则无法正常运行;4.x无需包含x86。 - 夜神模拟器:
必须包含x86,否则无法正常运行 - MuMu模拟器:
无需包含x86 - 逍遥模拟器:
无需包含x86 - BlueStacks(蓝叠模拟器):
无需包含x86 - 腾讯模拟器(手游助手):
必须包含x86,否则无法正常运行 - 其它模拟器:
未测试验证,建议包含x86,确保在模拟器正常运行
云端打包配置
可视化界面配置
打开项目的manifest.json文件,切换到“App常用其它设置”页面,勾选需要支持的cpu类型:
源码视图配置
打开项目的manifest.json文件,切换到“源码视图”
5+APP项目在 "plus" -> "distribute" -> "google" -> "abiFilters",uni-app项目在 "app-plus" -> "distribute" -> "android" -> "abiFilters" 添加要支持的CPU类型:
"abiFilters":[
"armeabi-v7a",
"arm64-v8a"
],
在上面节点配置需要支持的cpu类型,不需要的删除即可,不设置此字段则默认使用armeabi-v7a(ARM32位)
提交Google Play时要求支持64位,请选择"armeabi-v7a"、"arm64-v8a"两个即可。不要勾选“x86”
保存后提交云端打包生效。
查看打包后插件apk支持的CPU类型
使用解压工具打开apk,在lib目录下可以查看到支持的CPU类型,如下图所示:
离线打包配置
配置CPU类型
使用Android studio打开Android原生项目,打开对应项目的build.gradle文件。
在Android -> defaultConfig下添加支持的CPU类型,如下示例:
defaultConfig{
ndk {
abiFilters 'arm64-v8a','armeabi-v7a'
}
}
注意:离线打包仅支持arm64-v8a、armeabi-v7a、x86三种类型,建议根据自己需求选择打包的CPU类型
CPU类型选择建议
ARM64位(arm64-v8a)CPU可以兼容ARM32的指令,也就是说只选择armeabi-v7a类型的so库也可以在64位手机上运行,只是没有完全发挥CPU的性能。
选择支持的CPU类型时请参考以下建议:
- 如果不在意apk大小,三种CPU类型都勾选
- 如果在意apk大小,选择ARM32位即可(几乎在所有ARM指令的所有设备上都可正常运行)
- 如果要兼容一些平板和模拟器,选择ARM32位和X86
不是所有模拟都仅支持x86指令,如雷电(4.x)、MuMu等模拟器也是支持ARM指令。
CPU类型错误安装提示
如果打包选择的CPU类型与设备不兼容,会导致无法正常安装。
通过adb命令安装通常会提示如下错误:
Performing Streamed Install
adb: failed to install android_debug.apk: Failure [INSTALL_FAILED_NO_MATCHING_ABIS: Failed to extract native libraries, res=-113]
使用Android Studio自带的x86模拟器,将不包含x86 cpu类型的apk拖到模拟器安装时会弹出如下提示框:
<a id="nox86"/>
HBuilderx2.7.0+ 云端打包默认CPU类型不再包含x86
目前市面上常见的手机都是使用ARM处理器,很少有设备使用x86处理器,因此从HBuilderX2.7.0开始云端打包调整为默认不再包含x86的CPU类型,减少apk包大小:
- uni-app项目
基础功能apk减少5M+,使用的三方SDK及uni原生插件越多,减少的包尺寸越大,具体值取决于其包含的x86类型的so库大小 - 5+App、Wap2App项目
基础功能apk减少100K+,如果使用的三方SDK中存在so库则减少的尺寸较大,具体值取决于其包含的x86类型的so库大小
注意:大多数模拟器(如夜神)必须包含x86,否则应用启动时可能会白屏,请参考上面“配置CPU类型”章节勾选“x86”
收起阅读 »用wap2app在线视频网站打包播放视频没有全屏的已经解决
1、网站打包方法请参加这个网址
http://ask.dcloud.net.cn/docs/#//ask.dcloud.net.cn/article/13425。
2、在线视频网站打包后,有一些视频格式可以全屏观看的例;MP4,其它格式就不可以全屏了
我这个方法只能全屏,其它操作不行。
红色圈那个是全屏。
播放器增加全屏操作方法
1、在你的网站空间找到,播放器文件,js/player/有很多播放文件。你随便打一个播放文件找到下面这段代码。
跟你代码对比后发现了这个代码: allowfullscreen="allowfullscreen" 复制到你播放文件后,再刷新一下就可以了。
如果不明白操作的,加我的QQ370413232 备注播放器全屏,不备注不通过。
1、网站打包方法请参加这个网址
http://ask.dcloud.net.cn/docs/#//ask.dcloud.net.cn/article/13425。
2、在线视频网站打包后,有一些视频格式可以全屏观看的例;MP4,其它格式就不可以全屏了
我这个方法只能全屏,其它操作不行。
红色圈那个是全屏。
播放器增加全屏操作方法
1、在你的网站空间找到,播放器文件,js/player/有很多播放文件。你随便打一个播放文件找到下面这段代码。
跟你代码对比后发现了这个代码: allowfullscreen="allowfullscreen" 复制到你播放文件后,再刷新一下就可以了。
如果不明白操作的,加我的QQ370413232 备注播放器全屏,不备注不通过。
收起阅读 »uni-app使用vue-i18n实现小程序端国际化
- vue-i18n的基础写法就不详细说明了,相关文档请到官网查看
- 官网的文档真的写的还不错,闲话少说直接上代码,我将主要代码分成三个文件,index.js,zh-CHS.js,en-US.js:
index.js主代码
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
// 国际化
const i18n = new VueI18n({
locale: 'zh-CHS', // set locale
messages: {
'zh-CHS': require('./zh-CHS.js'), // 中文语言包
'en-US': require('./en-US.js') // 英文语言包
},
silentTranslationWarn: true
})
// window.i18n = i18n
export default i18n
中文zh-CHS.js
module.exports = {
"home": "首页"
}
英文en-US.js
module.exports = {
"home": "Home"
}
- 在mian.js中引用
import Vue from 'vue' import App from 'App' import i18n from './language'
Vue.config.productionTip = false
Vue.prototype.i18n = i18n
App.mpType = 'app'
const app = new Vue({
i18n,
...App
})
app.$mount()
4. 使用过vue-i18n的同学都知道,使用i18n-loader用于单文件或组件中,自定义块的语言环境信息,而uni-app并不支持
无奈要实现国际化,读了vue-i18n-loader的源码,但其中并没有时间部分,只有对不同文件的解析,官网也没有详细写,然后只能自己实现了,悲催呀!
各种摩擦,整了一天终于出来了,还是官网一个例子给我了启发:
官网例子:
```javascript
new Vue({
i18n: new VueI18n({
locale: 'en',
messages: {
en: { hello: 'hi there!' },
ja: { hello: 'こんにちは!' }
}
}),
data: { path: 'hello' }
}).$mount('#string-syntax')
根据这个实现了功能。
5.最终实现代码我放github上,地址https://github.com/sjpeter/web/blob/master/vue/i18n/main.vue
- 这种写法应该也适用于其他端,但为测试。
- vue-i18n的基础写法就不详细说明了,相关文档请到官网查看
- 官网的文档真的写的还不错,闲话少说直接上代码,我将主要代码分成三个文件,index.js,zh-CHS.js,en-US.js:
index.js主代码
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
// 国际化
const i18n = new VueI18n({
locale: 'zh-CHS', // set locale
messages: {
'zh-CHS': require('./zh-CHS.js'), // 中文语言包
'en-US': require('./en-US.js') // 英文语言包
},
silentTranslationWarn: true
})
// window.i18n = i18n
export default i18n
中文zh-CHS.js
module.exports = {
"home": "首页"
}
英文en-US.js
module.exports = {
"home": "Home"
}
- 在mian.js中引用
import Vue from 'vue' import App from 'App' import i18n from './language'
Vue.config.productionTip = false
Vue.prototype.i18n = i18n
App.mpType = 'app'
const app = new Vue({
i18n,
...App
})
app.$mount()
4. 使用过vue-i18n的同学都知道,使用i18n-loader用于单文件或组件中,自定义块的语言环境信息,而uni-app并不支持
无奈要实现国际化,读了vue-i18n-loader的源码,但其中并没有时间部分,只有对不同文件的解析,官网也没有详细写,然后只能自己实现了,悲催呀!
各种摩擦,整了一天终于出来了,还是官网一个例子给我了启发:
官网例子:
```javascript
new Vue({
i18n: new VueI18n({
locale: 'en',
messages: {
en: { hello: 'hi there!' },
ja: { hello: 'こんにちは!' }
}
}),
data: { path: 'hello' }
}).$mount('#string-syntax')
根据这个实现了功能。
5.最终实现代码我放github上,地址https://github.com/sjpeter/web/blob/master/vue/i18n/main.vue
- 这种写法应该也适用于其他端,但为测试。
Android离线打包 高德地图配置经验分享
请先按照附件中的图片修改好
注意application节点里面配置时 附件图片里面不是太清楚 这里我给贴出一个完整的节点配置
application节点下配置实例
<meta-data android:name="com.amap.api.v2.apikey" android:value="52488663213be8ssssf3247b47924916" />
<service android:name="com.amap.api.location.APSService"></service>
最重要一步配置,官方没有贴出来
修改build.gradle文件(app目录下),加入如下两句话:
implementation(name: 'amap-libs-release', ext: 'aar')
implementation(name: 'geolocation-amap-release', ext: 'aar')
最后请离线打包测试看是否能够正常获取定位点。如果有问题请提问,大家一起交流,仅限高德地图离线配置的问题。
本人本着无私分享的精神,如果不能解决你的问题 请不要生气。
附件图片在这里
请先按照附件中的图片修改好
注意application节点里面配置时 附件图片里面不是太清楚 这里我给贴出一个完整的节点配置
application节点下配置实例
<meta-data android:name="com.amap.api.v2.apikey" android:value="52488663213be8ssssf3247b47924916" />
<service android:name="com.amap.api.location.APSService"></service>
最重要一步配置,官方没有贴出来
修改build.gradle文件(app目录下),加入如下两句话:
implementation(name: 'amap-libs-release', ext: 'aar')
implementation(name: 'geolocation-amap-release', ext: 'aar')
最后请离线打包测试看是否能够正常获取定位点。如果有问题请提问,大家一起交流,仅限高德地图离线配置的问题。
本人本着无私分享的精神,如果不能解决你的问题 请不要生气。
附件图片在这里
收起阅读 »App发起微信支付
1.测试微信支付时使用自定义基座,不然会提示包名和微信开放平台中配置的包名不一致(我这里是这样)
2.uni-app发起支付方法requestPayment中有一个参数provider,参数值是固定的,微信支付就是wxpay,需要在manifest.json中进行参数配置,在发起支付的时候打印一下,看一下是否是wxpay,如果没有获取到就会报支付模块不存在
3.orderInfo是一个字符串,uni-app的文档没有说什么内容,json中的值都是从后台获取,微信支付文档上有其含义app支付,尤其注意Json中的package和参数package的值不是一样的,json中是固定格式,参数package中的值是prepay_id=xx
uni.requestPayment({
provider: c.provider[0],
orderInfo: JSON.stringify({
appid: res.data.map.appid,
noncestr: res.data.map.nonceStr,
package:"Sign=WXPay",
partnerid: res.data.map.partnerid,
prepayid: res.data.map.prepayid,
timestamp: res.data.map.timeStamp,
sign: res.data.map.paySign,
}),
timeStamp: JSON.stringify(res.data.map.timeStamp),
nonceStr: res.data.map.nonceStr,
package: res.data.map.package,
signType: res.data.map.signType,
success: (res) => {
},
fail: (res) => {
}
})
1.测试微信支付时使用自定义基座,不然会提示包名和微信开放平台中配置的包名不一致(我这里是这样)
2.uni-app发起支付方法requestPayment中有一个参数provider,参数值是固定的,微信支付就是wxpay,需要在manifest.json中进行参数配置,在发起支付的时候打印一下,看一下是否是wxpay,如果没有获取到就会报支付模块不存在
3.orderInfo是一个字符串,uni-app的文档没有说什么内容,json中的值都是从后台获取,微信支付文档上有其含义app支付,尤其注意Json中的package和参数package的值不是一样的,json中是固定格式,参数package中的值是prepay_id=xx
uni.requestPayment({
provider: c.provider[0],
orderInfo: JSON.stringify({
appid: res.data.map.appid,
noncestr: res.data.map.nonceStr,
package:"Sign=WXPay",
partnerid: res.data.map.partnerid,
prepayid: res.data.map.prepayid,
timestamp: res.data.map.timeStamp,
sign: res.data.map.paySign,
}),
timeStamp: JSON.stringify(res.data.map.timeStamp),
nonceStr: res.data.map.nonceStr,
package: res.data.map.package,
signType: res.data.map.signType,
success: (res) => {
},
fail: (res) => {
}
})
收起阅读 »
安卓原生与uniapp 互相通讯 (原生发送数据给uniapp uniapp 发送数据给原生)
首先我们要写三个java (类|接口)
1 .事件对象 用来传递数据的
package test;
public class MyEvent {
//数据
private Object data;
//事件来源 字符串
private String source;
//触发对象
private Object trigger;
public MyEvent(Object data) {
this.data = data;
}
public Object getTrigger() {
return trigger;
}
@Override
public String toString() {
return "MyEvent{" +
"data=" + data +
", source='" + source + '\'' +
", trigger=" + trigger +
'}';
}
public void setTrigger(Object trigger) {
this.trigger = trigger;
}
public MyEvent(Object data, String source) {
this.data = data;
this.source = source;
}
public MyEvent() {
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
}
2.监听器接口
package test;
import java.util.EventListener;
public interface MyListener extends EventListener {
void onChange(MyEvent myEvent);
}
3.事件管理器 用来 处理 事件 与通知 监听器
package test;
import android.util.Log;
import java.util.*;
/***
* 自定义事件管理类
*/
public class MyEventManager {
private static MyEventManager myEventManager;
private Map<String, Collection<MyListener>> listeners;
/**
* 不能外部 new 实例化
*/
private MyEventManager() {
this.listeners = new HashMap<String, Collection<MyListener>>();
}
/**
* 返回监听 总数
*
* @return
*/
public int getSize() {
int size = 0;
for (String str : listeners.keySet()) {
size = size + listeners.get(str).size();
}
return size;
}
public Map<String, Collection<MyListener>> getListeners() {
return listeners;
}
/**
* 单例模式
*
* @return
*/
public static MyEventManager getMyEventManager() {
if (myEventManager == null) {
synchronized (MyEventManager.class) {
if (myEventManager == null) {
myEventManager = new MyEventManager();
}
}
}
return myEventManager;
}
/***
* 添加事件
* @param listener 事件对象
* @param source 来源
*/
public MyListener addListener(MyListener listener, String source) {
if (listener != null && source != null) {
Collection<MyListener> myListeners = listeners.get(source);
if (myListeners == null) {
myListeners = new HashSet<MyListener>();
listeners.put(source, myListeners);
}
myListeners.add(listener);
}
return listener;
}
/***
* 添加事件
* @param source 来源
* @param listener 事件对象
*/
public MyListener addListener(String source, MyListener listener) {
return addListener(listener, source);
}
/**
* 移除监听
*
* @param listener
*/
public void removeListener(MyListener listener) {
if (listeners == null || listener == null) {
return;
}
//变量所有 找出相同的 删除
for (String str : listeners.keySet()) {
Collection collection = listeners.get(str);
Iterator<MyListener> iter = collection.iterator();
while (iter.hasNext()) {
MyListener next = (MyListener) iter.next();
if (next == listener) {
collection.remove(next);
return;
}
}
}
}
/***
* 发送数据
* @param data 数据
* @param source 来源
* @return
*/
public static MyEvent postMsg(Object data, String source) {
MyEventManager myEventManager = MyEventManager.getMyEventManager();
MyEvent myEvent = new MyEvent(data);
myEvent.setSource(source);
if (myEventManager.listeners == null)
return myEvent;
myEventManager.notifyListeners(myEvent, myEvent.getSource());
return myEvent;
}
/**
* 通知所有的myListener 相同的 (source) 来源才通知
*/
private void notifyListeners(MyEvent event, String source) {
//取出 key为source 的 监听器集合
Collection<MyListener> collection = listeners.get(source);
Log.i(MyEventManager.class.getName(), source + "--->" + event.getData());
if (collection == null) {
return;
}
//遍历监听器集合
Iterator<MyListener> iter = collection.iterator();
while (iter.hasNext()) {
MyListener next = iter.next();
//通知回调
next.onChange(event);
}
//销毁事件对象
event = null;
}
}
uniapp 添加监听器 监听 原生或者 uniapp 其他页面 传过来的数据
this.myEventManager = plus.android.importClass("test.MyEventManager");
this.eventManager = this.myEventManager.getMyEventManager();
//新建监听器
this.myListener = plus.android.implements("test.MyListener", {
onChange:function(event){
//导入类
plus.android.importClass(event);
//获取数据
console.log(event.getData());
//获取来源
console.log(event.getSource();
}
})
//添加监听器
this.eventManager.addListener("onShowXX",this.myListener);
uniapp 发送数据给 原生 获取其他页面 看清 onShowXX字符串 我随意写的 只要和添加监听器的字符串 保持一致就能通知到 类似 weex 的 数据传递
this.myEventManager = plus.android.importClass("test.MyEventManager");
this.eventManager = this.myEventManager.getMyEventManager();
this.myEventManager.postMsg("app 显示了","onShowXX");
原生 接收 从uniapp 或者原生传过来的数据
MyEventManager.getMyEventManager().addListener(new MyListener() {
@Override
public void onChange(MyEvent myEvent) {
//从uniapp 或者原生传过来的数据
Object data = myEvent.getData();
}
},"onShowXX");
原生发数据 到 原生或者 uniapp
MyEventManager.postMsg("我是从原生发来的数据","onShowXX");
具体例子 app显示就发数据给原生或者 uniapp
<script>
export default {
onLaunch: function() {
console.log('App Launch')
this.myEventManager = plus.android.importClass("test.MyEventManager");
this.eventManager = this.myEventManager.getMyEventManager();
//新建监听器
this.myListener = plus.android.implements("test.MyListener", {
onChange:function(event){
//导入类
plus.android.importClass(event);
//获取数据
console.log(event.getData());
//获取来源
console.log(event.getSource();
}
})
//添加监听器
this.eventManager.addListener("onShow",this.myListener);
},
onShow: function() {
//发送数据
this.myEventManager.postMsg("app 显示了","onShow");
},
onHide: function() {
console.log('App Hide')
}
}
</script>
<style>
/*每个页面公共css */
</style>
首先我们要写三个java (类|接口)
1 .事件对象 用来传递数据的
package test;
public class MyEvent {
//数据
private Object data;
//事件来源 字符串
private String source;
//触发对象
private Object trigger;
public MyEvent(Object data) {
this.data = data;
}
public Object getTrigger() {
return trigger;
}
@Override
public String toString() {
return "MyEvent{" +
"data=" + data +
", source='" + source + '\'' +
", trigger=" + trigger +
'}';
}
public void setTrigger(Object trigger) {
this.trigger = trigger;
}
public MyEvent(Object data, String source) {
this.data = data;
this.source = source;
}
public MyEvent() {
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
}
2.监听器接口
package test;
import java.util.EventListener;
public interface MyListener extends EventListener {
void onChange(MyEvent myEvent);
}
3.事件管理器 用来 处理 事件 与通知 监听器
package test;
import android.util.Log;
import java.util.*;
/***
* 自定义事件管理类
*/
public class MyEventManager {
private static MyEventManager myEventManager;
private Map<String, Collection<MyListener>> listeners;
/**
* 不能外部 new 实例化
*/
private MyEventManager() {
this.listeners = new HashMap<String, Collection<MyListener>>();
}
/**
* 返回监听 总数
*
* @return
*/
public int getSize() {
int size = 0;
for (String str : listeners.keySet()) {
size = size + listeners.get(str).size();
}
return size;
}
public Map<String, Collection<MyListener>> getListeners() {
return listeners;
}
/**
* 单例模式
*
* @return
*/
public static MyEventManager getMyEventManager() {
if (myEventManager == null) {
synchronized (MyEventManager.class) {
if (myEventManager == null) {
myEventManager = new MyEventManager();
}
}
}
return myEventManager;
}
/***
* 添加事件
* @param listener 事件对象
* @param source 来源
*/
public MyListener addListener(MyListener listener, String source) {
if (listener != null && source != null) {
Collection<MyListener> myListeners = listeners.get(source);
if (myListeners == null) {
myListeners = new HashSet<MyListener>();
listeners.put(source, myListeners);
}
myListeners.add(listener);
}
return listener;
}
/***
* 添加事件
* @param source 来源
* @param listener 事件对象
*/
public MyListener addListener(String source, MyListener listener) {
return addListener(listener, source);
}
/**
* 移除监听
*
* @param listener
*/
public void removeListener(MyListener listener) {
if (listeners == null || listener == null) {
return;
}
//变量所有 找出相同的 删除
for (String str : listeners.keySet()) {
Collection collection = listeners.get(str);
Iterator<MyListener> iter = collection.iterator();
while (iter.hasNext()) {
MyListener next = (MyListener) iter.next();
if (next == listener) {
collection.remove(next);
return;
}
}
}
}
/***
* 发送数据
* @param data 数据
* @param source 来源
* @return
*/
public static MyEvent postMsg(Object data, String source) {
MyEventManager myEventManager = MyEventManager.getMyEventManager();
MyEvent myEvent = new MyEvent(data);
myEvent.setSource(source);
if (myEventManager.listeners == null)
return myEvent;
myEventManager.notifyListeners(myEvent, myEvent.getSource());
return myEvent;
}
/**
* 通知所有的myListener 相同的 (source) 来源才通知
*/
private void notifyListeners(MyEvent event, String source) {
//取出 key为source 的 监听器集合
Collection<MyListener> collection = listeners.get(source);
Log.i(MyEventManager.class.getName(), source + "--->" + event.getData());
if (collection == null) {
return;
}
//遍历监听器集合
Iterator<MyListener> iter = collection.iterator();
while (iter.hasNext()) {
MyListener next = iter.next();
//通知回调
next.onChange(event);
}
//销毁事件对象
event = null;
}
}
uniapp 添加监听器 监听 原生或者 uniapp 其他页面 传过来的数据
this.myEventManager = plus.android.importClass("test.MyEventManager");
this.eventManager = this.myEventManager.getMyEventManager();
//新建监听器
this.myListener = plus.android.implements("test.MyListener", {
onChange:function(event){
//导入类
plus.android.importClass(event);
//获取数据
console.log(event.getData());
//获取来源
console.log(event.getSource();
}
})
//添加监听器
this.eventManager.addListener("onShowXX",this.myListener);
uniapp 发送数据给 原生 获取其他页面 看清 onShowXX字符串 我随意写的 只要和添加监听器的字符串 保持一致就能通知到 类似 weex 的 数据传递
this.myEventManager = plus.android.importClass("test.MyEventManager");
this.eventManager = this.myEventManager.getMyEventManager();
this.myEventManager.postMsg("app 显示了","onShowXX");
原生 接收 从uniapp 或者原生传过来的数据
MyEventManager.getMyEventManager().addListener(new MyListener() {
@Override
public void onChange(MyEvent myEvent) {
//从uniapp 或者原生传过来的数据
Object data = myEvent.getData();
}
},"onShowXX");
原生发数据 到 原生或者 uniapp
MyEventManager.postMsg("我是从原生发来的数据","onShowXX");
具体例子 app显示就发数据给原生或者 uniapp
<script>
export default {
onLaunch: function() {
console.log('App Launch')
this.myEventManager = plus.android.importClass("test.MyEventManager");
this.eventManager = this.myEventManager.getMyEventManager();
//新建监听器
this.myListener = plus.android.implements("test.MyListener", {
onChange:function(event){
//导入类
plus.android.importClass(event);
//获取数据
console.log(event.getData());
//获取来源
console.log(event.getSource();
}
})
//添加监听器
this.eventManager.addListener("onShow",this.myListener);
},
onShow: function() {
//发送数据
this.myEventManager.postMsg("app 显示了","onShow");
},
onHide: function() {
console.log('App Hide')
}
}
</script>
<style>
/*每个页面公共css */
</style>
收起阅读 »
uni-app项目瀑布流布局
插件预览图
使用教程
1.插件代码拷贝
- 下载后把components目录下waterfall.vue文件拷贝到自己项目目录下
2.插件全局配置
- 在项目里main.js中配置如下代码
import waterfall from './components/waterfall.vue'
Vue.component('waterfall',waterfall)
3.插件使用
- vue页面使用
<template>
<view>
<!-- 瀑布流(display: flex) H5 IOS Android支持 -->
<waterfall></waterfall>
</view>
</template>
兼容性
uni-app项目中使用都兼容
插件预览图
使用教程
1.插件代码拷贝
- 下载后把components目录下waterfall.vue文件拷贝到自己项目目录下
2.插件全局配置
- 在项目里main.js中配置如下代码
import waterfall from './components/waterfall.vue'
Vue.component('waterfall',waterfall)
3.插件使用
- vue页面使用
<template>
<view>
<!-- 瀑布流(display: flex) H5 IOS Android支持 -->
<waterfall></waterfall>
</view>
</template>
兼容性
uni-app项目中使用都兼容
收起阅读 »