微信、支付宝支付那点事
公司要在MUI开发的APP里添加上支付功能,然后爬坑开始了。。
因为公司用的是Java语言开发的服务端,所以就要找Java版本的支付代码了
首先在dcloud的问答里搜索看有没有相关文章,找到了下面两篇有用的
第一篇是配置支付宝支付的,第二篇是我在下面发了一个回复求微信支付的代码(所以代码在回复里)
dcloud官方给的php代码地址:https://github.com/dcloudio/H5P.Server
下面说说我在爬坑的时候碰到的障碍
- Java语言输出方法有print(),println() 切记一定不能用println() 这个方法输出后会换行,所以一直是失败状态,我开发的时候是支付宝报的错ALI10
- 微信支付,从APP里请求到服务端接口,在接口里会调用一次微信的接口,此时涉及到一次签名(sign),切记在签名前发送给微信服务器的参数要按照a-z排列好,然后在去签名,完成之后请求微信支付接口,微信给返回一些XML数据,其中只有prepay_id有用,其他需要的参数基本上都是在微信的配置类里配置好了的,此时转换成的json格式数据写出的还有一次签名(sign),这个签名跟上面第一次的签名不一样,要记得在签名参数最后带上key
- 支付宝支付的参数,地址类的字符串比如:notify_url等,不需要URLEncoder.encode(),参数都要加上""
- xcode里,如果APP跳转到支付宝打开的是网页版的,而不是支付宝APP,就需要在xcode里配置plist文件添加下面这些代码
<key>LSApplicationQueriesSchemes</key> <array> <string>weixin</string> <string>wechat</string> <string>alipay</string> <string>sinaweibo</string> <string>weibosdk</string> <string>tencentweiboSdkv2</string> <string>weibosdk2.5</string> <string>mqq</string> <string>mqqOpensdkSSoLogin</string> <string>mqqopensdkapiV2</string> <string>mqqwpa</string> <string>mqqopensdkapiV3</string> <string>wtloginmqq2</string> </array>
下面是我Java服务端的代码,给大家分享一下
支付宝(使用了支付宝商户SDK)
public void alipayapi(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setContentType("text/plain; charset=UTF-8");
PrintWriter out = response.getWriter();
////////////////////////////////////请求参数//////////////////////////////////////
//支付类型
String payment_type = "1";
//必填,不能修改
//服务器异步通知页面路径
String notify_url = ApplicationListener.getBasePath() + "pay/sdk/alipay/notify";
//需http://格式的完整路径,不能加?id=123这类自定义参数
//页面跳转同步通知页面路径
// String return_url = URLEncoder.encode(ApplicationListener.getBasePath() + "pay/wap/alipay/return");
//需http://格式的完整路径,不能加?id=123这类自定义参数,不能写成http://localhost/
//商户订单号
String out_trade_no = request.getParameter("orderId");
//商户网站订单系统中唯一订单号,必填
//订单名称
String subject = "支付预定金";
//必填
//付款金额
String total_fee = request.getParameter("total_fee");
//必填
//商品展示地址
String show_url = ApplicationListener.getBasePath();
//必填,需以http://开头的完整路径,例如:http://www.商户网址.com/myorder.html
//订单描述
String body = "订单支付定金";
//选填
//超时时间
String it_b_pay = "1d";
//选填
//把请求参数打包成数组
Map<String, String> sParaTemp = new HashMap<String, String>();
sParaTemp.put("service", "mobile.securitypay.pay");
sParaTemp.put("partner", AlipayConfig.partner);
sParaTemp.put("seller_id", AlipayConfig.seller_id);
sParaTemp.put("_input_charset", AlipayConfig.input_charset);
sParaTemp.put("payment_type", payment_type);
sParaTemp.put("notify_url", notify_url);
sParaTemp.put("out_trade_no", out_trade_no);
sParaTemp.put("subject", subject);
sParaTemp.put("total_fee", total_fee);
sParaTemp.put("show_url", show_url);
sParaTemp.put("body", body);
sParaTemp.put("it_b_pay", it_b_pay);
String sHtmlText = "";
for(Iterator iter = sParaTemp.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String value = sParaTemp.get(name);
sHtmlText += name + "=\"" + value + "\"&";
}
//建立请求
System.out.println(sHtmlText);
sHtmlText = sHtmlText.substring(0, sHtmlText.length()-1);
String sign = RSA.sign(sHtmlText, AlipayConfig.PRIVATE, AlipayConfig.input_charset);
String outText = sHtmlText + "&sign=\"" + URLEncoder.encode(sign, "UTF-8") + "\"&sign_type=\""+AlipayConfig.sign_type+"\"";
out.print(outText);
}
微信支付(用到了微信Java版SDK,下载地址)
RequestData.java
public class RequestData {
private String appid;
private String body;
private String mch_id;
private String nonce_str;
private String notify_url;
private String out_trade_no;
private String sign;
private String spbill_create_ip;
private String total_fee;
private String trade_type;
//getter,setter
}
public void alipayapi(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setContentType("text/plain; charset=UTF-8");
PrintWriter out = response.getWriter();
//微信分配的公众账号ID(企业号corpid即为此appId)
String appid = Configure.getAppid();
//必填
//商品或支付单简要描述
String body = "预定金支付";
//必填
//微信支付分配的商户号
String mch_id = Configure.getMchid();
//必填
//随机字符串,不长于32位。推荐随机数生成算法
String nonce_str = RandomStringGenerator.getRandomStringByLength(32);
//必填
//接收微信支付异步通知回调地址
String notify_url = ApplicationListener.getBasePath() + "pay/sdk/wxpay/notify";
//必填
//商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号
String out_trade_no = request.getParameter("orderId");
//必填
//签名,详见签名生成算法
String sign = "";
//必填
//APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。
String spbill_create_ip = IpUtil.getIpAddr(request);
//必填
//订单总金额,单位为分,详见支付金额
Double total_fee_d = Double.parseDouble(request.getParameter("total_fee"));
Double total_fee_s = total_fee_d * 100;
String total_fee = total_fee_s.intValue() + "";
//必填
//取值如下:JSAPI,NATIVE,APP,详细说明见参数规定
String trade_type = request.getParameter("trade_type");
//必填
//=============================以下参数 非必填 ===============================
//商品名称明细列表
String detail = "";
//非必填
//附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
String attach = "";
//非必填
//符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型
String fee_type = "";
//非必填
//终端设备号(门店号或收银设备ID),注意:PC网页或公众号内支付请传"WEB"
String device_info = "";
//非必填
//商品标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠
String goods_tag = "";
//非必填
//订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则
String time_start = "";
//非必填
//订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则
//注意:最短失效时间间隔必须大于5分钟
String time_expire = "";
//非必填
//trade_type=NATIVE,此参数必传。此id为二维码中包含的商品ID,商户自行定义。
String product_id = "";
//非必填
//no_credit--指定不能使用信用卡支付
String limit_pay = "";
//非必填
//trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。openid如何获取,可参考【获取openid】。
//企业号请使用【企业号OAuth2.0接口】获取企业号内成员userid,再调用【企业号userid转openid接口】进行转换
String openid = "";
//非必填
RequestData reqData = new RequestData();
reqData.setAppid(appid);
reqData.setBody(body);
reqData.setMch_id(mch_id);
reqData.setNonce_str(nonce_str);
reqData.setNotify_url(notify_url);
reqData.setOut_trade_no(out_trade_no);
reqData.setSpbill_create_ip(spbill_create_ip);
reqData.setTotal_fee(total_fee);
reqData.setTrade_type(trade_type);
sign = Signature.getSign(reqData);
reqData.setSign(sign);
String result = new HttpsRequest().sendPost("https://api.mch.weixin.qq.com/pay/unifiedorder", reqData);
System.out.println(result);
Map map = XMLParser.getMapFromXML(result);
String prepay_id = (String) map.get("prepay_id");
System.out.println(prepay_id);
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String s="appid="+appid+"&noncestr="+nonce_str+"&package=Sign=WXPay"+"&partnerid="+
mch_id+"&prepayid="+prepay_id+"×tamp="+timestamp+"&key=" + Configure.getKey();
String newSign = MD5.MD5Encode(s).toUpperCase();
StringBuffer json = new StringBuffer();
json.append("{\"appid\":\"");
json.append(appid);
json.append("\",\"noncestr\":\"");
json.append(nonce_str);
json.append("\",\"package\":\"");
json.append("Sign=WXPay");
json.append("\",\"partnerid\":\"");
json.append(mch_id);
json.append("\",\"prepayid\":\"");
json.append(prepay_id);
json.append("\",\"timestamp\":\"");
json.append(timestamp);
json.append("\",\"sign\":\"");
json.append(newSign);
json.append("\"}");
System.out.println(json.toString());
out.print(json.toString());
} 公司要在MUI开发的APP里添加上支付功能,然后爬坑开始了。。
因为公司用的是Java语言开发的服务端,所以就要找Java版本的支付代码了
首先在dcloud的问答里搜索看有没有相关文章,找到了下面两篇有用的
第一篇是配置支付宝支付的,第二篇是我在下面发了一个回复求微信支付的代码(所以代码在回复里)
dcloud官方给的php代码地址:https://github.com/dcloudio/H5P.Server
下面说说我在爬坑的时候碰到的障碍
- Java语言输出方法有print(),println() 切记一定不能用println() 这个方法输出后会换行,所以一直是失败状态,我开发的时候是支付宝报的错ALI10
- 微信支付,从APP里请求到服务端接口,在接口里会调用一次微信的接口,此时涉及到一次签名(sign),切记在签名前发送给微信服务器的参数要按照a-z排列好,然后在去签名,完成之后请求微信支付接口,微信给返回一些XML数据,其中只有prepay_id有用,其他需要的参数基本上都是在微信的配置类里配置好了的,此时转换成的json格式数据写出的还有一次签名(sign),这个签名跟上面第一次的签名不一样,要记得在签名参数最后带上key
- 支付宝支付的参数,地址类的字符串比如:notify_url等,不需要URLEncoder.encode(),参数都要加上""
- xcode里,如果APP跳转到支付宝打开的是网页版的,而不是支付宝APP,就需要在xcode里配置plist文件添加下面这些代码
<key>LSApplicationQueriesSchemes</key> <array> <string>weixin</string> <string>wechat</string> <string>alipay</string> <string>sinaweibo</string> <string>weibosdk</string> <string>tencentweiboSdkv2</string> <string>weibosdk2.5</string> <string>mqq</string> <string>mqqOpensdkSSoLogin</string> <string>mqqopensdkapiV2</string> <string>mqqwpa</string> <string>mqqopensdkapiV3</string> <string>wtloginmqq2</string> </array>
下面是我Java服务端的代码,给大家分享一下
支付宝(使用了支付宝商户SDK)
public void alipayapi(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setContentType("text/plain; charset=UTF-8");
PrintWriter out = response.getWriter();
////////////////////////////////////请求参数//////////////////////////////////////
//支付类型
String payment_type = "1";
//必填,不能修改
//服务器异步通知页面路径
String notify_url = ApplicationListener.getBasePath() + "pay/sdk/alipay/notify";
//需http://格式的完整路径,不能加?id=123这类自定义参数
//页面跳转同步通知页面路径
// String return_url = URLEncoder.encode(ApplicationListener.getBasePath() + "pay/wap/alipay/return");
//需http://格式的完整路径,不能加?id=123这类自定义参数,不能写成http://localhost/
//商户订单号
String out_trade_no = request.getParameter("orderId");
//商户网站订单系统中唯一订单号,必填
//订单名称
String subject = "支付预定金";
//必填
//付款金额
String total_fee = request.getParameter("total_fee");
//必填
//商品展示地址
String show_url = ApplicationListener.getBasePath();
//必填,需以http://开头的完整路径,例如:http://www.商户网址.com/myorder.html
//订单描述
String body = "订单支付定金";
//选填
//超时时间
String it_b_pay = "1d";
//选填
//把请求参数打包成数组
Map<String, String> sParaTemp = new HashMap<String, String>();
sParaTemp.put("service", "mobile.securitypay.pay");
sParaTemp.put("partner", AlipayConfig.partner);
sParaTemp.put("seller_id", AlipayConfig.seller_id);
sParaTemp.put("_input_charset", AlipayConfig.input_charset);
sParaTemp.put("payment_type", payment_type);
sParaTemp.put("notify_url", notify_url);
sParaTemp.put("out_trade_no", out_trade_no);
sParaTemp.put("subject", subject);
sParaTemp.put("total_fee", total_fee);
sParaTemp.put("show_url", show_url);
sParaTemp.put("body", body);
sParaTemp.put("it_b_pay", it_b_pay);
String sHtmlText = "";
for(Iterator iter = sParaTemp.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String value = sParaTemp.get(name);
sHtmlText += name + "=\"" + value + "\"&";
}
//建立请求
System.out.println(sHtmlText);
sHtmlText = sHtmlText.substring(0, sHtmlText.length()-1);
String sign = RSA.sign(sHtmlText, AlipayConfig.PRIVATE, AlipayConfig.input_charset);
String outText = sHtmlText + "&sign=\"" + URLEncoder.encode(sign, "UTF-8") + "\"&sign_type=\""+AlipayConfig.sign_type+"\"";
out.print(outText);
}
微信支付(用到了微信Java版SDK,下载地址)
RequestData.java
public class RequestData {
private String appid;
private String body;
private String mch_id;
private String nonce_str;
private String notify_url;
private String out_trade_no;
private String sign;
private String spbill_create_ip;
private String total_fee;
private String trade_type;
//getter,setter
}
public void alipayapi(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setContentType("text/plain; charset=UTF-8");
PrintWriter out = response.getWriter();
//微信分配的公众账号ID(企业号corpid即为此appId)
String appid = Configure.getAppid();
//必填
//商品或支付单简要描述
String body = "预定金支付";
//必填
//微信支付分配的商户号
String mch_id = Configure.getMchid();
//必填
//随机字符串,不长于32位。推荐随机数生成算法
String nonce_str = RandomStringGenerator.getRandomStringByLength(32);
//必填
//接收微信支付异步通知回调地址
String notify_url = ApplicationListener.getBasePath() + "pay/sdk/wxpay/notify";
//必填
//商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号
String out_trade_no = request.getParameter("orderId");
//必填
//签名,详见签名生成算法
String sign = "";
//必填
//APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。
String spbill_create_ip = IpUtil.getIpAddr(request);
//必填
//订单总金额,单位为分,详见支付金额
Double total_fee_d = Double.parseDouble(request.getParameter("total_fee"));
Double total_fee_s = total_fee_d * 100;
String total_fee = total_fee_s.intValue() + "";
//必填
//取值如下:JSAPI,NATIVE,APP,详细说明见参数规定
String trade_type = request.getParameter("trade_type");
//必填
//=============================以下参数 非必填 ===============================
//商品名称明细列表
String detail = "";
//非必填
//附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
String attach = "";
//非必填
//符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型
String fee_type = "";
//非必填
//终端设备号(门店号或收银设备ID),注意:PC网页或公众号内支付请传"WEB"
String device_info = "";
//非必填
//商品标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠
String goods_tag = "";
//非必填
//订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则
String time_start = "";
//非必填
//订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则
//注意:最短失效时间间隔必须大于5分钟
String time_expire = "";
//非必填
//trade_type=NATIVE,此参数必传。此id为二维码中包含的商品ID,商户自行定义。
String product_id = "";
//非必填
//no_credit--指定不能使用信用卡支付
String limit_pay = "";
//非必填
//trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。openid如何获取,可参考【获取openid】。
//企业号请使用【企业号OAuth2.0接口】获取企业号内成员userid,再调用【企业号userid转openid接口】进行转换
String openid = "";
//非必填
RequestData reqData = new RequestData();
reqData.setAppid(appid);
reqData.setBody(body);
reqData.setMch_id(mch_id);
reqData.setNonce_str(nonce_str);
reqData.setNotify_url(notify_url);
reqData.setOut_trade_no(out_trade_no);
reqData.setSpbill_create_ip(spbill_create_ip);
reqData.setTotal_fee(total_fee);
reqData.setTrade_type(trade_type);
sign = Signature.getSign(reqData);
reqData.setSign(sign);
String result = new HttpsRequest().sendPost("https://api.mch.weixin.qq.com/pay/unifiedorder", reqData);
System.out.println(result);
Map map = XMLParser.getMapFromXML(result);
String prepay_id = (String) map.get("prepay_id");
System.out.println(prepay_id);
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String s="appid="+appid+"&noncestr="+nonce_str+"&package=Sign=WXPay"+"&partnerid="+
mch_id+"&prepayid="+prepay_id+"×tamp="+timestamp+"&key=" + Configure.getKey();
String newSign = MD5.MD5Encode(s).toUpperCase();
StringBuffer json = new StringBuffer();
json.append("{\"appid\":\"");
json.append(appid);
json.append("\",\"noncestr\":\"");
json.append(nonce_str);
json.append("\",\"package\":\"");
json.append("Sign=WXPay");
json.append("\",\"partnerid\":\"");
json.append(mch_id);
json.append("\",\"prepayid\":\"");
json.append(prepay_id);
json.append("\",\"timestamp\":\"");
json.append(timestamp);
json.append("\",\"sign\":\"");
json.append(newSign);
json.append("\"}");
System.out.println(json.toString());
out.print(json.toString());
} 收起阅读 »
url中文字符串建议转码
调用到plus.runtime.openURL、plus.webview.currentWebview().loadURL()等用到url的地方,必须注意调用接口时url中存在中文字符参数的建议先转码后调用,否则可能会出现的调用失败异常。
调用到plus.runtime.openURL、plus.webview.currentWebview().loadURL()等用到url的地方,必须注意调用接口时url中存在中文字符参数的建议先转码后调用,否则可能会出现的调用失败异常。
【分享】跨 webview 的 js 函数调用
webview 是 5+ SDK 的重要组成部分,也是 HBuilder 区别于其它“H5 混合模式”移动端开发方案的一个重要特色和利器。
在实际使用中,webview 也会给程序设计带来一些麻烦。因为一个 webview 实例就相当于桌面浏览器里的一个窗口页签,每个 webview 的 js 上下文都是相互独立的,并不能互相访问。
幸好 HTML5+ 规范中定义了 WebviewObject,evalJS() 这个接口,为两个平行宇宙之间的互相通信提供了可能。MUI 就利用这个接口实现了“自定义事件”功能,可以用来跨 webview 传递消息。
不过,evalJS() 只是底层实现的一个单向通信机制,MUI 的自定义事件也是单向消息,没有返回值。虽然这已经解决了很多问题,但还是有不少应用场景更适合用接近“函数调用”的方式来实现。
有鉴于此,我做了这个 hbuilder-rpc。分享在这里,希望能有用。如果这段代码将来有机会成为 MUI 的一部分,我将尤感欣慰。
演示界面截图:
相关程序代码:
/**
* 以当前的 WebView 为媒介,向其它 WebView 中的 js 提供服务接口。
*/
window.RpcServer = {
/**
* 服务提供者调用此函数注册一个服务接口。
* @param {String} service_name 接口名称
* @param {Function} fnService 注册的服务函数,具有如下形式:
* function(params, finish) {
* // params 是调用参数。
* // finish 是回调函数,应该在服务完成后调用,并传入唯一参数表示服务执行结果。
* // 即使服务出错,也要确保回调函数被调用,并用传入参数来表示错误状态。
* }
*/
expose: function(service_name, fnService) {
var me = this;
if (me.exposed[service_name] != undefined) {
throw new Error('RpcServer.expose: service already exists: ' + service_name);
}
me.exposed[service_name] = fnService;
},
exposed: {}, // 注册的服务接口
/**
* RpcClient 通过 evalJS() 调用此函数,访问服务接口。
* @param {String} service_name 服务接口名称
* @param {Mixed} params 入口参数
* @param {String} vw_id 调用源的 webview id
* @param {String} cb_id 调用源的 callback id
*/
invoke: function(service_name, params, vw_id, cb_id) {
var fn = this.exposed[service_name];
if (typeof fn != 'function') {
throw new Error('RpcServer.invoke: service not found: ' + service_name);
}
fn(params, function(ret) {
var vw = plus.webview.getWebviewById(vw_id);
if (!vw) return;
var js = 'RpcClient.callback(' + JSON.stringify(cb_id);
js += ',' + JSON.stringify(ret);
js += ')';
vw.evalJS(js);
});
}
};
/**
* 远程访问 RpcServer 提供的服务接口。
*/
window.RpcClient = {
/**
* 调用一个远程服务接口。
* @param {String} server_id rpc server 的 webview id。
* @param {String} service_name 服务接口名称。
* @param {Mixed} params 服务入口参数。
* @param {Function} callback 回调函数,用于回传服务执行结果。
*/
invoke: function(server_id, service_name, params, callback) {
var me = this;
var cs = plus.webview.getWebviewById(server_id);
if (!cs) throw new Error('RpcServer view not found: ' + server_id);
var js = 'RpcServer.invoke(' + JSON.stringify(service_name);
js += ',' + JSON.stringify(params);
if (typeof callback == 'function') {
js += ',' + JSON.stringify(plus.webview.currentWebview().id);
js += ',' + me.next_callback_id;
me.callbacks[me.next_callback_id] = callback;
me.next_callback_id ++;
}
js += ')';
cs.evalJS(js);
},
next_callback_id: 1,
callbacks: {},
callback: function(cb_id, ret) {
var me = this;
var cb = me.callbacks[cb_id];
if (typeof cb != 'function') return;
cb.call(undefined, ret);
delete me.callbacks[cb_id];
}
};
// 通过 RpcServer.expose() 暴露一个服务函数供其它 WebView 中的 js 调用
RpcServer.expose('demo-rpc-service', function(params, finish) {
// 入口参数
mui('#rpc-call-params')[0].innerText = JSON.stringify(params, undefined, ' ');
// 服务功能完成后,调用 finish() 把结果发回给调用者
finish({
success: true,
result: {
reply: 'hi, ' + params.from + '.',
num: window._call_num = (window._call_num || 0) + 1
}
});
});
// 通过 RpcClient.invoke() 调用另一个 WebView 中的服务函数
RpcClient.invoke('demo-rpc-server', 'demo-rpc-service', {
greeting: 'hi !',
from: plus.webview.currentWebview().id,
num: window._call_num = (window._call_num || 0) + 1
}, function(resp) {
// resp 是服务执行结果
mui('#rpc-call-resp')[0].innerText = JSON.stringify(resp, undefined, ' ');
});
演示项目源代码可通过附件下载。
webview 是 5+ SDK 的重要组成部分,也是 HBuilder 区别于其它“H5 混合模式”移动端开发方案的一个重要特色和利器。
在实际使用中,webview 也会给程序设计带来一些麻烦。因为一个 webview 实例就相当于桌面浏览器里的一个窗口页签,每个 webview 的 js 上下文都是相互独立的,并不能互相访问。
幸好 HTML5+ 规范中定义了 WebviewObject,evalJS() 这个接口,为两个平行宇宙之间的互相通信提供了可能。MUI 就利用这个接口实现了“自定义事件”功能,可以用来跨 webview 传递消息。
不过,evalJS() 只是底层实现的一个单向通信机制,MUI 的自定义事件也是单向消息,没有返回值。虽然这已经解决了很多问题,但还是有不少应用场景更适合用接近“函数调用”的方式来实现。
有鉴于此,我做了这个 hbuilder-rpc。分享在这里,希望能有用。如果这段代码将来有机会成为 MUI 的一部分,我将尤感欣慰。
演示界面截图:
相关程序代码:
/**
* 以当前的 WebView 为媒介,向其它 WebView 中的 js 提供服务接口。
*/
window.RpcServer = {
/**
* 服务提供者调用此函数注册一个服务接口。
* @param {String} service_name 接口名称
* @param {Function} fnService 注册的服务函数,具有如下形式:
* function(params, finish) {
* // params 是调用参数。
* // finish 是回调函数,应该在服务完成后调用,并传入唯一参数表示服务执行结果。
* // 即使服务出错,也要确保回调函数被调用,并用传入参数来表示错误状态。
* }
*/
expose: function(service_name, fnService) {
var me = this;
if (me.exposed[service_name] != undefined) {
throw new Error('RpcServer.expose: service already exists: ' + service_name);
}
me.exposed[service_name] = fnService;
},
exposed: {}, // 注册的服务接口
/**
* RpcClient 通过 evalJS() 调用此函数,访问服务接口。
* @param {String} service_name 服务接口名称
* @param {Mixed} params 入口参数
* @param {String} vw_id 调用源的 webview id
* @param {String} cb_id 调用源的 callback id
*/
invoke: function(service_name, params, vw_id, cb_id) {
var fn = this.exposed[service_name];
if (typeof fn != 'function') {
throw new Error('RpcServer.invoke: service not found: ' + service_name);
}
fn(params, function(ret) {
var vw = plus.webview.getWebviewById(vw_id);
if (!vw) return;
var js = 'RpcClient.callback(' + JSON.stringify(cb_id);
js += ',' + JSON.stringify(ret);
js += ')';
vw.evalJS(js);
});
}
};
/**
* 远程访问 RpcServer 提供的服务接口。
*/
window.RpcClient = {
/**
* 调用一个远程服务接口。
* @param {String} server_id rpc server 的 webview id。
* @param {String} service_name 服务接口名称。
* @param {Mixed} params 服务入口参数。
* @param {Function} callback 回调函数,用于回传服务执行结果。
*/
invoke: function(server_id, service_name, params, callback) {
var me = this;
var cs = plus.webview.getWebviewById(server_id);
if (!cs) throw new Error('RpcServer view not found: ' + server_id);
var js = 'RpcServer.invoke(' + JSON.stringify(service_name);
js += ',' + JSON.stringify(params);
if (typeof callback == 'function') {
js += ',' + JSON.stringify(plus.webview.currentWebview().id);
js += ',' + me.next_callback_id;
me.callbacks[me.next_callback_id] = callback;
me.next_callback_id ++;
}
js += ')';
cs.evalJS(js);
},
next_callback_id: 1,
callbacks: {},
callback: function(cb_id, ret) {
var me = this;
var cb = me.callbacks[cb_id];
if (typeof cb != 'function') return;
cb.call(undefined, ret);
delete me.callbacks[cb_id];
}
};
// 通过 RpcServer.expose() 暴露一个服务函数供其它 WebView 中的 js 调用
RpcServer.expose('demo-rpc-service', function(params, finish) {
// 入口参数
mui('#rpc-call-params')[0].innerText = JSON.stringify(params, undefined, ' ');
// 服务功能完成后,调用 finish() 把结果发回给调用者
finish({
success: true,
result: {
reply: 'hi, ' + params.from + '.',
num: window._call_num = (window._call_num || 0) + 1
}
});
});
// 通过 RpcClient.invoke() 调用另一个 WebView 中的服务函数
RpcClient.invoke('demo-rpc-server', 'demo-rpc-service', {
greeting: 'hi !',
from: plus.webview.currentWebview().id,
num: window._call_num = (window._call_num || 0) + 1
}, function(resp) {
// resp 是服务执行结果
mui('#rpc-call-resp')[0].innerText = JSON.stringify(resp, undefined, ' ');
});
演示项目源代码可通过附件下载。
收起阅读 »页面引用关系使用教程
什么是页面引用关系
页面引用关系是用来描述App项目页面与项目资源文件之间引用的关系。如下图
页面引用关系可以帮助开发者做什么
梳理和理解App流程
点击左侧树中任一文件,都可以查看该文件包含的资源、该文件可打开的次级页面,以及该页面未使用到的页面。开发者从App的入口页面开始点击,可以依次看到每个页面包含的内容及次级页面,可以辅助开发者梳理App流程以及项目新成员理解App流程。
辅助测试人员测试App
测试人员可参照页面引用关系,按流程对App进行测试
检查App闲置资源
页面右侧树中,文件带问号的表示该文件不是任何文件的资源,也不是任何资源的可打开文件。开发者可倒查原因以优化App的体积。
发布流应用
发布流应用时,流应用页面关系配置可由页面引用关系确定。
未来页面引用关系还将用于工程重构和App性能自动优化
页面引用关系是一个基础的功能,用途很多,所以请开发者重视维护页面引用关系。
如何使用
页面组成
如上图所示,界面分左右两部分,左边为当前项目下所有的页面,单击左侧树中的文件可在右侧查看选中文件的页面(双击文件可在编辑器中打开文件)。
右侧如下图分A、B、C3个部分
A部分为“本页面包含的资源”,指页面包含的js、css、字体、子webview、iframe内嵌页面,特别的如果使用5+API预加载的一些页面如侧滑菜单属于本页面包含的资源(预加载的页面在本页面显示的均为本页面包含的资源)。
B部分为“可从本页面打开的页面”,特别的,使用返回键“返回”的页面不属于可从本页面打开的页面
C部分为既非“本页面包含的资源”也非“可从本页面打开的页面”的“其他资源”,特别的,此列表中带?角标的资源为未被其他任何页面使用,也不能从其他页面打开的闲置资源。
注意为避免出现性能问题,闲置资源的刷新并非实时的,如开发者核对闲置资源前请先点击右侧列表上方的“刷新其他资源列表”
如何生成页面引用关系
扫描代码生成
1.首次进入页面引用关系的可视化操作界面,HBuilder会自动检查是否有页面引用关系。如果没有,则通过分析项目代码生成初步的页面引用关系。
- 也可以点击下图中“扫描代码”链接手动触发扫描代码
注:
1.JS的写法太灵活,可能会导致扫描结果不全,或将页面资源当做下级可打开的页面,需要人工干预 - 扫描完毕后,HBuilder将新增部分以黑色加粗字体显示,开发者在使用过程中需要着重检查此部分结果,如下图
3.点击“本页面包含的资源”或“可从本页面打开的下级页面”列表中黑色加粗条目,或将其移动到其他列表中,表示开发者已确认,保存后黑色加粗消失真机运行
点击"页面引用关系"顶部的专用真机运行链接,使用Android设备或模拟器将整个App运行一遍,即可自动生成App的页面引用关系。
运行的页面越全面,包括各种业务逻辑都点到,则生成的App页面引用关系越完整。如下图
手动调整
当然,你可以手动调整页面引用关系,选中一个页面,如果你认为包含的页面不正确,实际上是可打开的次级页面,你可以将其移动到可打开的页面。同理,你可以把本页面不含的资源移动到包含的资源或可打开的页面
代码视图
点击manifest.json代码视图,页面引用关系记录在dependencies节点下。同时,在pages节点下可手动为流应用配置页面下载的优先级,修改priority的值即可
什么是页面引用关系
页面引用关系是用来描述App项目页面与项目资源文件之间引用的关系。如下图
页面引用关系可以帮助开发者做什么
梳理和理解App流程
点击左侧树中任一文件,都可以查看该文件包含的资源、该文件可打开的次级页面,以及该页面未使用到的页面。开发者从App的入口页面开始点击,可以依次看到每个页面包含的内容及次级页面,可以辅助开发者梳理App流程以及项目新成员理解App流程。
辅助测试人员测试App
测试人员可参照页面引用关系,按流程对App进行测试
检查App闲置资源
页面右侧树中,文件带问号的表示该文件不是任何文件的资源,也不是任何资源的可打开文件。开发者可倒查原因以优化App的体积。
发布流应用
发布流应用时,流应用页面关系配置可由页面引用关系确定。
未来页面引用关系还将用于工程重构和App性能自动优化
页面引用关系是一个基础的功能,用途很多,所以请开发者重视维护页面引用关系。
如何使用
页面组成
如上图所示,界面分左右两部分,左边为当前项目下所有的页面,单击左侧树中的文件可在右侧查看选中文件的页面(双击文件可在编辑器中打开文件)。
右侧如下图分A、B、C3个部分
A部分为“本页面包含的资源”,指页面包含的js、css、字体、子webview、iframe内嵌页面,特别的如果使用5+API预加载的一些页面如侧滑菜单属于本页面包含的资源(预加载的页面在本页面显示的均为本页面包含的资源)。
B部分为“可从本页面打开的页面”,特别的,使用返回键“返回”的页面不属于可从本页面打开的页面
C部分为既非“本页面包含的资源”也非“可从本页面打开的页面”的“其他资源”,特别的,此列表中带?角标的资源为未被其他任何页面使用,也不能从其他页面打开的闲置资源。
注意为避免出现性能问题,闲置资源的刷新并非实时的,如开发者核对闲置资源前请先点击右侧列表上方的“刷新其他资源列表”
如何生成页面引用关系
扫描代码生成
1.首次进入页面引用关系的可视化操作界面,HBuilder会自动检查是否有页面引用关系。如果没有,则通过分析项目代码生成初步的页面引用关系。
- 也可以点击下图中“扫描代码”链接手动触发扫描代码
注:
1.JS的写法太灵活,可能会导致扫描结果不全,或将页面资源当做下级可打开的页面,需要人工干预 - 扫描完毕后,HBuilder将新增部分以黑色加粗字体显示,开发者在使用过程中需要着重检查此部分结果,如下图
3.点击“本页面包含的资源”或“可从本页面打开的下级页面”列表中黑色加粗条目,或将其移动到其他列表中,表示开发者已确认,保存后黑色加粗消失真机运行
点击"页面引用关系"顶部的专用真机运行链接,使用Android设备或模拟器将整个App运行一遍,即可自动生成App的页面引用关系。
运行的页面越全面,包括各种业务逻辑都点到,则生成的App页面引用关系越完整。如下图
手动调整
当然,你可以手动调整页面引用关系,选中一个页面,如果你认为包含的页面不正确,实际上是可打开的次级页面,你可以将其移动到可打开的页面。同理,你可以把本页面不含的资源移动到包含的资源或可打开的页面
代码视图
点击manifest.json代码视图,页面引用关系记录在dependencies节点下。同时,在pages节点下可手动为流应用配置页面下载的优先级,修改priority的值即可
诚聘H5前端开发人员
工作地点: 北京
工作内容:开发维护公司的网站及移动APP
工作要求:
Web开发经验、移动端开发经验
熟练运用HTML、CSS进行网页制作。
熟悉HTML5及CSS3特性,了解响应式设计
精通Javascript语言的各特性,掌握Ajax
能熟练使用Hbuilder,H5 ,Mui框架者更佳
有Html5 APP经验者优先
请发送简历至 mike@kingnova.net
不怕经验不足,就怕没进取心,期待您的加入!
工作地点: 北京
工作内容:开发维护公司的网站及移动APP
工作要求:
Web开发经验、移动端开发经验
熟练运用HTML、CSS进行网页制作。
熟悉HTML5及CSS3特性,了解响应式设计
精通Javascript语言的各特性,掌握Ajax
能熟练使用Hbuilder,H5 ,Mui框架者更佳
有Html5 APP经验者优先
请发送简历至 mike@kingnova.net
不怕经验不足,就怕没进取心,期待您的加入!
收起阅读 »IOS平台使用SDK集成,Native代码和HTML页面进行交互的方法
Native代码保存数据,HTML页面读取数据
用户可在Native代码或者HTML页面中使用NSUserDefaults类来保存数据,并在另一侧获取
页面保存数据native代码获取
JS Code
在HTML页面中调用如下代码实现数据的保存
function SetUserDefault(key, value)
{
if (typeof value != 'undefined' && typeof key === "string")
{
var UserDefaultsClass = plus.ios.importClass("NSUserDefaults");
var standardUserDefaults = UserDefaultsClass.standardUserDefaults();
plus.ios.invoke(standardUserDefaults, "setObject:forKey:", value, key);
plus.ios.invoke(standardUserDefaults,"synchronize");
}
}
Objective-c Code
在native代码中调用如下方法,获取在HTML页面中保存的数据
NSUserDefaults* pDefDefaults = [NSUserDefaults standardUserDefaults];
if (pDefDefaults) {
NSString* pString = [pDefDefaults objectForKey:@"Input Your Key"];
}
Native保存数据,页面获取数据
JS Code
在HTML页面中调用如下方法,实现读取Native保存的数据
function getUserDefault(key)
{
if(typeof key != 'undefined' && typeof key === "string")
{
var UserDefaultsClass = plus.ios.importClass("NSUserDefaults");
var standardUserDefaults = UserDefaultsClass.standardUserDefaults();
return plus.ios.invoke(standardUserDefaults, "objectForKey:", key);
}
return null;
}
Objective-c Code
在Native代码中调用如下方法,实现保存数据
NSUserDefaults* pDefDefaults = [NSUserDefaults standardUserDefaults];
if (pDefDefaults) {
[pDefDefaults setObject:@"Input Your Value" forKey:@"Input Your Key"];
}
Native代码调用当前运行应用的指定HTML页面内的JS方法
开发者可通过以下方法,在Native层找到需要交互的Webview对象,并触发该页面中的JS方法
根据webview的ID找到对应的HTML页面,并执行页面的JS方法
PDRCoreAppFrame* pFrame = [[[[[PDRCore Instance] appManager] activeApp] appWindow] getFrameByName:@"plus/audio.html"];
if (pFrame)
{
// 找到指定的页面并调用stringByEvaluatingJavaScriptFromString方法调用该html页面的JS方法
[pFrame stringByEvaluatingJavaScriptFromString:@"InputJSFunctionNameInHtmlPage()"];
}
找到应用的主页面,并调用HTML中的JS方法
PDRCoreAppFrame* pMainFrame =[[[[PDRCore Instance] appManager] activeApp] mainFrame] ;
if (pMainFrame) {
[pMainFrame stringByEvaluatingJavaScriptFromString:@"InputJSFunctionNameInHtmlPage()"];
}
HTML 页面调用原生方法
开发者可使用NativeJS调用已经写好的原生类的方法,也可以用NativeJS发送一个消息,由原生代码接收这个消息并处理
//IOS NJS 代码发送消息到原生层的示例
var notiClass = plus.ios.importClass("NSNotificationCenter");
notiClass.defaultCenter().postNotificationNameobject("CloseWebAPP",null);
Native代码保存数据,HTML页面读取数据
用户可在Native代码或者HTML页面中使用NSUserDefaults类来保存数据,并在另一侧获取
页面保存数据native代码获取
JS Code
在HTML页面中调用如下代码实现数据的保存
function SetUserDefault(key, value)
{
if (typeof value != 'undefined' && typeof key === "string")
{
var UserDefaultsClass = plus.ios.importClass("NSUserDefaults");
var standardUserDefaults = UserDefaultsClass.standardUserDefaults();
plus.ios.invoke(standardUserDefaults, "setObject:forKey:", value, key);
plus.ios.invoke(standardUserDefaults,"synchronize");
}
}
Objective-c Code
在native代码中调用如下方法,获取在HTML页面中保存的数据
NSUserDefaults* pDefDefaults = [NSUserDefaults standardUserDefaults];
if (pDefDefaults) {
NSString* pString = [pDefDefaults objectForKey:@"Input Your Key"];
}
Native保存数据,页面获取数据
JS Code
在HTML页面中调用如下方法,实现读取Native保存的数据
function getUserDefault(key)
{
if(typeof key != 'undefined' && typeof key === "string")
{
var UserDefaultsClass = plus.ios.importClass("NSUserDefaults");
var standardUserDefaults = UserDefaultsClass.standardUserDefaults();
return plus.ios.invoke(standardUserDefaults, "objectForKey:", key);
}
return null;
}
Objective-c Code
在Native代码中调用如下方法,实现保存数据
NSUserDefaults* pDefDefaults = [NSUserDefaults standardUserDefaults];
if (pDefDefaults) {
[pDefDefaults setObject:@"Input Your Value" forKey:@"Input Your Key"];
}
Native代码调用当前运行应用的指定HTML页面内的JS方法
开发者可通过以下方法,在Native层找到需要交互的Webview对象,并触发该页面中的JS方法
根据webview的ID找到对应的HTML页面,并执行页面的JS方法
PDRCoreAppFrame* pFrame = [[[[[PDRCore Instance] appManager] activeApp] appWindow] getFrameByName:@"plus/audio.html"];
if (pFrame)
{
// 找到指定的页面并调用stringByEvaluatingJavaScriptFromString方法调用该html页面的JS方法
[pFrame stringByEvaluatingJavaScriptFromString:@"InputJSFunctionNameInHtmlPage()"];
}
找到应用的主页面,并调用HTML中的JS方法
PDRCoreAppFrame* pMainFrame =[[[[PDRCore Instance] appManager] activeApp] mainFrame] ;
if (pMainFrame) {
[pMainFrame stringByEvaluatingJavaScriptFromString:@"InputJSFunctionNameInHtmlPage()"];
}
HTML 页面调用原生方法
开发者可使用NativeJS调用已经写好的原生类的方法,也可以用NativeJS发送一个消息,由原生代码接收这个消息并处理
//IOS NJS 代码发送消息到原生层的示例
var notiClass = plus.ios.importClass("NSNotificationCenter");
notiClass.defaultCenter().postNotificationNameobject("CloseWebAPP",null);
ios 下拉刷新中进行重置上拉加载失效的问题
情况如下:
ajax获取20条数据显示在滚动页面上,上拉加载获取8条数据,3次上拉后数据加载完毕底部出现没有更多数据了,进行下拉刷新后上拉出现底部没有更多数据了但不进行上拉加载数据
(查文档说用mui('#下拉容器id').pullRefresh().refresh(true);是进行上拉加载重新激活)
下拉刷新数据加载完后加了(加粗的部分)
mui('#pullrefresh').pullRefresh().refresh(true);
mui('#pullrefresh').pullRefresh().endPulldownToRefresh();
在android中测试上拉加载到出现没有更多数据了,在进行下拉刷新后在进行上拉加载可以加载数据,测试正常,
但是在iphone5s ios8中测试出现下拉刷新与上拉加载失效
后来换成
mui('#pullrefresh').pullRefresh().endPulldownToRefresh();
mui('#pullrefresh').pullRefresh().refresh(true);
ios测试正常 有点坑,遂记之。
情况如下:
ajax获取20条数据显示在滚动页面上,上拉加载获取8条数据,3次上拉后数据加载完毕底部出现没有更多数据了,进行下拉刷新后上拉出现底部没有更多数据了但不进行上拉加载数据
(查文档说用mui('#下拉容器id').pullRefresh().refresh(true);是进行上拉加载重新激活)
下拉刷新数据加载完后加了(加粗的部分)
mui('#pullrefresh').pullRefresh().refresh(true);
mui('#pullrefresh').pullRefresh().endPulldownToRefresh();
在android中测试上拉加载到出现没有更多数据了,在进行下拉刷新后在进行上拉加载可以加载数据,测试正常,
但是在iphone5s ios8中测试出现下拉刷新与上拉加载失效
后来换成
mui('#pullrefresh').pullRefresh().endPulldownToRefresh();
mui('#pullrefresh').pullRefresh().refresh(true);
ios测试正常 有点坑,遂记之。
官方的demo里的tab-webview-main.html里面如何能够让webview打开第三方网站?
比如点击后打开http://www.dcloud.io
<a id="defaultTab" class="mui-tab-item mui-active" href="http://www.dclound.io/">
<span class="mui-icon mui-icon-home"></span>
<span class="mui-tab-label">首页</span>
</a>
刚接触mui。
比如点击后打开http://www.dcloud.io
<a id="defaultTab" class="mui-tab-item mui-active" href="http://www.dclound.io/">
<span class="mui-icon mui-icon-home"></span>
<span class="mui-tab-label">首页</span>
</a>
刚接触mui。
收起阅读 »Objective-C中的存取方法
在iOS开发中,因为Objective-C的发展问题,Objective-C里存取方法有好几种,不同时期的代码采用的不同方式,往往让人感到迷惑,下面就来看看Objective-C中有哪些存取方法吧。
通过@property关键字
在.h接口文件中通过@property关键字去声明变量
@property id variableName;
然后再在.m实现文件中,通过@synthesize指令,告诉编译器,让其帮你生成相应的setter和getter方法
不用@systhesize指令
我们也经常会在一些教程的代码中看到,变量名开头有个下划线,这个其实就是采用了@property关键字,但是没有使用@systhesize指令去让编译器自动生成相应的方法,这时访问实例变量需要再原来的变量名前面加一个下划线。
自己去实现
比较原始的方法就是这个,自己去实现getter和setter方法,
- (void) setProperty: (id) value;
- (id) property;
在iOS开发中,因为Objective-C的发展问题,Objective-C里存取方法有好几种,不同时期的代码采用的不同方式,往往让人感到迷惑,下面就来看看Objective-C中有哪些存取方法吧。
通过@property关键字
在.h接口文件中通过@property关键字去声明变量
@property id variableName;
然后再在.m实现文件中,通过@synthesize指令,告诉编译器,让其帮你生成相应的setter和getter方法
不用@systhesize指令
我们也经常会在一些教程的代码中看到,变量名开头有个下划线,这个其实就是采用了@property关键字,但是没有使用@systhesize指令去让编译器自动生成相应的方法,这时访问实例变量需要再原来的变量名前面加一个下划线。
自己去实现
比较原始的方法就是这个,自己去实现getter和setter方法,
- (void) setProperty: (id) value;
- (id) property;
简约风格在网页设计中的应用
随着网页技术的不断发展,网页设计的趋势在改变,扁平化、响应式、视觉差滚动、卡片式等给设计师带来了更多的发挥空间。在网页中,简约的设计,往往具清晰的页面结构、简单的交互操作等特征,在满足传递信息的同时,从视觉体验的角度,为用户带来轻松、愉悦的美感,那么在网页中,通过以下几种方式可以做到简约设计。
1、大背景
大图片背景,是目前应用最多的一种网页呈现方式。将整张大图片作为背景,不仅能产生强烈的视觉冲击力,还可以对网页内容起到一个很好的补充,有效的突出品牌形象。
2、大标题
将大标题作为网页的视觉中心,简洁图片作为大标题的背景,这样很容易将视觉聚焦,让人一眼就能看出这个网页是做什么的。另外,大标题在字体和色彩上,也尽量选择一些简单的、颜色单一的效果,这样视觉会更纯粹。
3、“纤薄”文本框和铵钮
在页面中,将文本框或者铵钮做得轻薄简洁,或为纯色或者没有纹理的一层薄线框。这种设计在确保功能性的同时,给人一种纤细的视觉美感。
4、留白
留白,是页面构图的重要组成部分,适当的加入留白,能让文字便于阅读,让页面各元素得到很好的区分,这样的设计通俗易懂,有得于提升一个页面的品质感。
5、单纯色彩
一个页面,给人带来第一印象的往往是它的色彩。不同的颜色带给人不同的心理感受。当我们看惯色彩绚丽、对比鲜明的网页之后,静下来,回归最自然的状态,运用单纯的色彩,会有不一样的体会和感悟。
网页中的简约通常是多种表现形式结合使用,简约并不等于简单,它需要我们去用心发现、感受、提炼,通过视觉去发掘、去实现。简约的网页设计不仅能带给用户轻松、实用、愉快的体验,而且还能让信息更为有效合理的传达给用户。
随着网页技术的不断发展,网页设计的趋势在改变,扁平化、响应式、视觉差滚动、卡片式等给设计师带来了更多的发挥空间。在网页中,简约的设计,往往具清晰的页面结构、简单的交互操作等特征,在满足传递信息的同时,从视觉体验的角度,为用户带来轻松、愉悦的美感,那么在网页中,通过以下几种方式可以做到简约设计。
1、大背景
大图片背景,是目前应用最多的一种网页呈现方式。将整张大图片作为背景,不仅能产生强烈的视觉冲击力,还可以对网页内容起到一个很好的补充,有效的突出品牌形象。
2、大标题
将大标题作为网页的视觉中心,简洁图片作为大标题的背景,这样很容易将视觉聚焦,让人一眼就能看出这个网页是做什么的。另外,大标题在字体和色彩上,也尽量选择一些简单的、颜色单一的效果,这样视觉会更纯粹。
3、“纤薄”文本框和铵钮
在页面中,将文本框或者铵钮做得轻薄简洁,或为纯色或者没有纹理的一层薄线框。这种设计在确保功能性的同时,给人一种纤细的视觉美感。
4、留白
留白,是页面构图的重要组成部分,适当的加入留白,能让文字便于阅读,让页面各元素得到很好的区分,这样的设计通俗易懂,有得于提升一个页面的品质感。
5、单纯色彩
一个页面,给人带来第一印象的往往是它的色彩。不同的颜色带给人不同的心理感受。当我们看惯色彩绚丽、对比鲜明的网页之后,静下来,回归最自然的状态,运用单纯的色彩,会有不一样的体会和感悟。
网页中的简约通常是多种表现形式结合使用,简约并不等于简单,它需要我们去用心发现、感受、提炼,通过视觉去发掘、去实现。简约的网页设计不仅能带给用户轻松、实用、愉快的体验,而且还能让信息更为有效合理的传达给用户。
收起阅读 »不知道是怎么回事,打不出大于号,有人遇到过么?
不知道是怎么回事,打不出大于号,有人遇到过么?也就是“>”这个符号
不知道是怎么回事,打不出大于号,有人遇到过么?也就是“>”这个符号
2015,Objective-C的这些新改变
当众人的目光聚焦在WWDC 2015新推出的Swift 2和iOS 9上时,还有多少人会想起自己初入iOS开发时所看的那一本本Objective-C 资料,如今Objective-C 的种种局限已不复存在,2015年的Objective-C 以全新的面貌重新出现在我们眼前,下面就来看看这位熟悉的旧友有了哪些改变吧。
会弱化Swift代码和可读性
很遗憾,Swift支持泛型(generics)就意味着Objective-C 只会以optional的AnyObject集合的形式出现。如此一来,开发者要使用该属性就必须在Swift和Objective-C之间进行转换。
Nullability Annotations
单单一个属性就引发了这么多担忧,还挺让人不安的。如果代码本身引发很多质疑,出现error的可能性就大大增加,更别提在广为熟知的Objective-C和语言新秀Swift之间相互调用(interoperability)了。现在有了nullability annotations,问题就简单多了,编程也会省下很多麻烦。
@property (strong, nonatomic, nonnull) NSArray *someViews;
intent.大大提升了Objective-C,而且这个属性也不会在Swift里满满都是optional了,开发者看看代码就知道有没有nil pointer了。计算机的静态检验和Swift的可用性都得到了提升,最重要的是实现了API的intent通讯。
泛型的出现,泛型的缺席一直以来是Objective-C开发者心头之痛,而诞生32年之后,Objective-C终于也支持泛型了,支持泛型将带来诸多改变,而且都是积极的改变。
现在可以定义属性,下指令给编译器来显示所有UIView:
@property (strong, nonatomic, nonnull) NSArray<UIView > someViews;
向属性强加UIView之外的东西时,编译器会报错,而且如今不用做大量头痛的转换(cast)了。
Objective-C支持泛型对Swift而言也是好消息。上次更新时,让Swift知道对象不应该是optional的,现在Swift还知道它们是UIViews,如此一来含混不清的AnyObject声明就不需要了。如今的Objective-C可以像C#、C++、Swift等语言一样通过<>括号来表示类型了。虽然通常是对协议表示一致性(conformance),但编译器知道何时、何地以及如何运用它们,且运用是经过推理的。
再进一步,可以用参数来表示扩展(extensions)、类别(categories)和类(classes),好处不仅仅体现在集合(collections)上。泛型的强大体现在整个Objective-C之中,集合仅仅是结果而已。
type erasure不但能实现二进制兼容,而且不改变Objective-C的执行时间。所以开发者们,C#的泛型的确胜过其他语言,这点依旧不会改变,所以发发牢骚就好了。
KindOf Types,再次调用之前定义的属性,就会显示UIView。判断里面包含着views和buttons是再正常不过的事。这种情况下,添加如下代码会发生什么呢?
[self.someViews[0]addTarget:selfaction:selector(aMethod:)
forControlEvents:UIControlEventTouchUpInside];
编译器警告,因为即便可以在这个属性里插入一个button,就算可以假设是个UIView,button也不一定没有经过转换,现在新的KindOf特性能够轻松解决这种始料未及的情况:
@property (strong, nonatomic, nonnull) NSArray<__kindof UIView > someViews;
实际上编译器已经知道了:属性及其集合会出现一些UIView。这样在类型协议里显示更多我们之前看不到的信息,其本质是向下转型(downcasting)。这意味着上述代码编译没什么问题,因为编译器知道集合里肯定会出现一个button,现在那些担忧就都解释得清了。
虽然不喜欢Swift的人可能会刻意夸大Objective-C的优点,但如今两种语言实现了互相调用,这是Objective-C所有提升的最大价值所在,毋庸置疑,Objective-C的确比以往更加强大。
虽然如今Swift正以迅雷不及掩耳之势蚕食着Objective-C,但Objective-C 对每一个iOS开发者来说,就如初恋一般的美好,因此如今Objective-C的提升对开发者而言,是件非常值得高兴的事,它能够帮助开发者写出更好的代码,而且这些优势已经在Foundation中随处可见了。
当众人的目光聚焦在WWDC 2015新推出的Swift 2和iOS 9上时,还有多少人会想起自己初入iOS开发时所看的那一本本Objective-C 资料,如今Objective-C 的种种局限已不复存在,2015年的Objective-C 以全新的面貌重新出现在我们眼前,下面就来看看这位熟悉的旧友有了哪些改变吧。
会弱化Swift代码和可读性
很遗憾,Swift支持泛型(generics)就意味着Objective-C 只会以optional的AnyObject集合的形式出现。如此一来,开发者要使用该属性就必须在Swift和Objective-C之间进行转换。
Nullability Annotations
单单一个属性就引发了这么多担忧,还挺让人不安的。如果代码本身引发很多质疑,出现error的可能性就大大增加,更别提在广为熟知的Objective-C和语言新秀Swift之间相互调用(interoperability)了。现在有了nullability annotations,问题就简单多了,编程也会省下很多麻烦。
@property (strong, nonatomic, nonnull) NSArray *someViews;
intent.大大提升了Objective-C,而且这个属性也不会在Swift里满满都是optional了,开发者看看代码就知道有没有nil pointer了。计算机的静态检验和Swift的可用性都得到了提升,最重要的是实现了API的intent通讯。
泛型的出现,泛型的缺席一直以来是Objective-C开发者心头之痛,而诞生32年之后,Objective-C终于也支持泛型了,支持泛型将带来诸多改变,而且都是积极的改变。
现在可以定义属性,下指令给编译器来显示所有UIView:
@property (strong, nonatomic, nonnull) NSArray<UIView > someViews;
向属性强加UIView之外的东西时,编译器会报错,而且如今不用做大量头痛的转换(cast)了。
Objective-C支持泛型对Swift而言也是好消息。上次更新时,让Swift知道对象不应该是optional的,现在Swift还知道它们是UIViews,如此一来含混不清的AnyObject声明就不需要了。如今的Objective-C可以像C#、C++、Swift等语言一样通过<>括号来表示类型了。虽然通常是对协议表示一致性(conformance),但编译器知道何时、何地以及如何运用它们,且运用是经过推理的。
再进一步,可以用参数来表示扩展(extensions)、类别(categories)和类(classes),好处不仅仅体现在集合(collections)上。泛型的强大体现在整个Objective-C之中,集合仅仅是结果而已。
type erasure不但能实现二进制兼容,而且不改变Objective-C的执行时间。所以开发者们,C#的泛型的确胜过其他语言,这点依旧不会改变,所以发发牢骚就好了。
KindOf Types,再次调用之前定义的属性,就会显示UIView。判断里面包含着views和buttons是再正常不过的事。这种情况下,添加如下代码会发生什么呢?
[self.someViews[0]addTarget:selfaction:selector(aMethod:)
forControlEvents:UIControlEventTouchUpInside];
编译器警告,因为即便可以在这个属性里插入一个button,就算可以假设是个UIView,button也不一定没有经过转换,现在新的KindOf特性能够轻松解决这种始料未及的情况:
@property (strong, nonatomic, nonnull) NSArray<__kindof UIView > someViews;
实际上编译器已经知道了:属性及其集合会出现一些UIView。这样在类型协议里显示更多我们之前看不到的信息,其本质是向下转型(downcasting)。这意味着上述代码编译没什么问题,因为编译器知道集合里肯定会出现一个button,现在那些担忧就都解释得清了。
虽然不喜欢Swift的人可能会刻意夸大Objective-C的优点,但如今两种语言实现了互相调用,这是Objective-C所有提升的最大价值所在,毋庸置疑,Objective-C的确比以往更加强大。
虽然如今Swift正以迅雷不及掩耳之势蚕食着Objective-C,但Objective-C 对每一个iOS开发者来说,就如初恋一般的美好,因此如今Objective-C的提升对开发者而言,是件非常值得高兴的事,它能够帮助开发者写出更好的代码,而且这些优势已经在Foundation中随处可见了。
收起阅读 »









