
iOS讯飞语音自定义界面
MUI的人没空回答只能看他们的插件源码自己写了,废话不多说代码如下:
JS脚本 xfyun-plugin.js文件
;define(function(require, exports, module) {
var _BARCODE = 'XFYunPlugin',
B = window.plus.bridge;
var XFYunPlugin =
{
/**
* 开始语音识别
*/
startListen : function (successCallback, errorCallback)
{
var success = typeof successCallback !== 'function' ? null : function(args)
{
successCallback(args);
},
fail = typeof errorCallback !== 'function' ? null : function(code)
{
errorCallback(code);
};
callbackID = B.callbackId(success, fail);
return B.exec(_BARCODE, "startListen", [callbackID]);
},
/**
* 停止语音识别
*/
stopListen : function (successCallback, errorCallback)
{
var success = typeof successCallback !== 'function' ? null : function(args)
{
successCallback(args);
},
fail = typeof errorCallback !== 'function' ? null : function(code)
{
errorCallback(code);
};
callbackID = B.callbackId(success, fail);
return B.exec(_BARCODE, "stopListen", [callbackID]);
}
};
window.plus.XFYunPlugin = XFYunPlugin;
});
原生插件
PGNXFYunPlugin.h文件
#include "PGPlugin.h"
#include "PGMethod.h"
#import <Foundation/Foundation.h>
// 讯飞
#import "iflyMSC/IFlySpeechRecognizerDelegate.h"
#import "iflyMSC/IFlySpeechRecognizer.h"
#import "iflyMSC/IFlySpeechConstant.h"
#import "iflyMSC/IFlySpeechError.h"
@interface PGXFYunPlugin : PGPlugin<IFlySpeechRecognizerDelegate>
{
IFlySpeechRecognizer *iflySpeechRecognizer;
}
@property(nonatomic, strong)NSString *callBackID;
@property (nonatomic, assign) BOOL isSpeeching; // 是否正在录音
// 开始语音识别
- (void)startListen:(PGMethod *)command;
// 停止语音识别
- (void)stopListen:(PGMethod *)command;
@end
PGNXFYunPlugin.m文件
#import "PGXFYunPlugin.h"
@implementation PGXFYunPlugin
@synthesize callBackID;
@synthesize isSpeeching;
// 开始语音识别
- (void)startListen:(PGMethod *)command
{
if (command && !self.isSpeeching) {
// CallBackid 异步方法的回调id,H5+ 会根据回调ID通知JS层运行结果成功或者失败
NSString *cbId = [command.arguments objectAtIndex:0];
self.callBackID = cbId;
self.isSpeeching = YES;
// 创建语音听写对象
if (iflySpeechRecognizer == nil) {
iflySpeechRecognizer = [IFlySpeechRecognizer sharedInstance];
[iflySpeechRecognizer setParameter:@"" forKey:[IFlySpeechConstant PARAMS]];
// 设置听写参数
[iflySpeechRecognizer setParameter:@"iat" forKey:[IFlySpeechConstant IFLY_DOMAIN]];
//asr_audio_path保存录音文件名,如不再需要,设置value为nil表示取消,默认目录是Library/cache
[iflySpeechRecognizer setParameter:nil forKey:[IFlySpeechConstant ASR_AUDIO_PATH]];
//设置最长录音时间12s
[iflySpeechRecognizer setParameter:@"12000" forKey:[IFlySpeechConstant SPEECH_TIMEOUT]];
//网络等待时间30s
[iflySpeechRecognizer setParameter:@"20000" forKey:[IFlySpeechConstant NET_TIMEOUT]];
//设置是否返回标点符号
[iflySpeechRecognizer setParameter:@"0" forKey:[IFlySpeechConstant ASR_PTT]];
}
iflySpeechRecognizer.delegate = self;
// 启动识别服务
[iflySpeechRecognizer startListening];
}
}
// 停止语音识别
- (void)stopListen:(PGMethod *)command {
if (command) {
self.isSpeeching = NO;
if (iflySpeechRecognizer != nil) {
// 停止识别服务
[iflySpeechRecognizer stopListening];
}
}
}
#pragma mark IFlySpeechRecognizerDelegate.h delegate
/*
识别结果返回代理
*/
- (void)onResults:(NSArray *)results isLast:(BOOL)isLast
{
NSLog(@"onResults");
self.isSpeeching = NO;
if (self.callBackID != nil) {
NSMutableString *resultMutableString = [[NSMutableString alloc] init];
NSDictionary *dic = results[0];
for (NSString *key in dic) {
[resultMutableString appendFormat:@"%@",key];
}
NSString *resultString = [NSString stringWithFormat:@"%@", resultMutableString];
NSString *resultFromJson = [self stringFromJson:resultString];
NSDictionary *dicResult = [NSDictionary dictionaryWithObjectsAndKeys:
@"type", @"text",
@"value", resultFromJson,
nil];
// 运行Native代码结果和预期相同,调用回调通知JS层运行成功并返回结果
PDRPluginResult *result = [PDRPluginResult resultWithStatus:PDRCommandStatusOK messageAsDictionary:dicResult];
// 通知JS层Native层运行结果,JS Pluginbridge会根据cbid找到对应的回调方法并触发
[self toCallback:self.callBackID withReslut:[result toJSONString]];
}
}
/*
识别会话结束返回代理 (注:无论听写是否正确都会回调)
error.errorCode =
0 听写正确
other 听写出错
*/
- (void) onError:(IFlySpeechError *) error
{
NSLog(@"onError");
self.isSpeeching = NO;
if (error.errorCode != 0 && self.callBackID != nil ) {
NSDictionary *dicResult = [NSDictionary dictionaryWithObjectsAndKeys:
@"code", [NSString stringWithFormat:@"%d", error.errorCode],
@"desc", error.errorDesc,
nil];
// 如果Native代码运行结果和预期不同,需要通过回调通知JS层出现错误,并返回错误提示
PDRPluginResult *result = [PDRPluginResult resultWithStatus:PDRCommandStatusError messageAsDictionary:dicResult];
// 通知JS层Native层运行结果,JS Pluginbridge会根据cbid找到对应的回调方法并触发
[self toCallback:self.callBackID withReslut:[result toJSONString]];
}
}
/*
开始录音回调
*/
- (void)onBeginOfSpeech
{
NSLog(@"onBeginOfSpeech");
}
/*
停止录音回调
*/
- (void)onEndOfSpeech
{
NSLog(@"onEndOfSpeech");
self.isSpeeching = NO;
}
/*
音量回调函数
volume 0-30
*/
- (void)onVolumeChanged:(int)volume
{
if (self.callBackID != nil ) {
NSDictionary *dicResult = [NSDictionary dictionaryWithObjectsAndKeys:
@"type", @"volume",
@"value", [NSString stringWithFormat:@"%d", volume],
nil];
// 运行Native代码结果和预期相同,调用回调通知JS层运行成功并返回结果
PDRPluginResult *result = [PDRPluginResult resultWithStatus:PDRCommandStatusOK messageAsDictionary: dicResult];
// 通知JS层Native层运行结果,JS Pluginbridge会根据cbid找到对应的回调方法并触发
[self toCallback:self.callBackID withReslut:[result toJSONString]];
}
}
- (void)dealloc {
[super dealloc];
[iflySpeechRecognizer cancel]; //取消识别
[iflySpeechRecognizer setDelegate:nil];
[iflySpeechRecognizer setParameter:@"" forKey:[IFlySpeechConstant PARAMS]];
self.callBackID = nil;
self.isSpeeching = nil;
}
/**
解析听写json格式的数据
params例如:
{"sn":1,"ls":true,"bg":0,"ed":0,"ws":[{"bg":0,"cw":[{"w":"白日","sc":0}]},{"bg":0,"cw":[{"w":"依山","sc":0}]},{"bg":0,"cw":[{"w":"尽","sc":0}]},{"bg":0,"cw":[{"w":"黄河入海流","sc":0}]},{"bg":0,"cw":[{"w":"。","sc":0}]}]}
****/
- (NSString *)stringFromJson:(NSString*)params
{
if (params == NULL) {
return nil;
}
NSMutableString *tempStr = [[NSMutableString alloc] init];
NSDictionary *resultDic = [NSJSONSerialization JSONObjectWithData: //返回的格式必须为utf8的,否则发生未知错误
[params dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:nil];
if (resultDic!= nil) {
NSArray *wordArray = [resultDic objectForKey:@"ws"];
for (int i = 0; i < [wordArray count]; i++) {
NSDictionary *wsDic = [wordArray objectAtIndex: i];
NSArray *cwArray = [wsDic objectForKey:@"cw"];
for (int j = 0; j < [cwArray count]; j++) {
NSDictionary *wDic = [cwArray objectAtIndex:j];
NSString *str = [wDic objectForKey:@"w"];
[tempStr appendString: str];
}
}
}
return tempStr;
}
@end
// 调用方法
plus.XFYunPlugin.startListen(function(result) {
// 成功回调函数
JSON.stringify(result);
}, function(error) {
// 失败回调函数
JSON.stringify(error);
});
MUI的人没空回答只能看他们的插件源码自己写了,废话不多说代码如下:
JS脚本 xfyun-plugin.js文件
;define(function(require, exports, module) {
var _BARCODE = 'XFYunPlugin',
B = window.plus.bridge;
var XFYunPlugin =
{
/**
* 开始语音识别
*/
startListen : function (successCallback, errorCallback)
{
var success = typeof successCallback !== 'function' ? null : function(args)
{
successCallback(args);
},
fail = typeof errorCallback !== 'function' ? null : function(code)
{
errorCallback(code);
};
callbackID = B.callbackId(success, fail);
return B.exec(_BARCODE, "startListen", [callbackID]);
},
/**
* 停止语音识别
*/
stopListen : function (successCallback, errorCallback)
{
var success = typeof successCallback !== 'function' ? null : function(args)
{
successCallback(args);
},
fail = typeof errorCallback !== 'function' ? null : function(code)
{
errorCallback(code);
};
callbackID = B.callbackId(success, fail);
return B.exec(_BARCODE, "stopListen", [callbackID]);
}
};
window.plus.XFYunPlugin = XFYunPlugin;
});
原生插件
PGNXFYunPlugin.h文件
#include "PGPlugin.h"
#include "PGMethod.h"
#import <Foundation/Foundation.h>
// 讯飞
#import "iflyMSC/IFlySpeechRecognizerDelegate.h"
#import "iflyMSC/IFlySpeechRecognizer.h"
#import "iflyMSC/IFlySpeechConstant.h"
#import "iflyMSC/IFlySpeechError.h"
@interface PGXFYunPlugin : PGPlugin<IFlySpeechRecognizerDelegate>
{
IFlySpeechRecognizer *iflySpeechRecognizer;
}
@property(nonatomic, strong)NSString *callBackID;
@property (nonatomic, assign) BOOL isSpeeching; // 是否正在录音
// 开始语音识别
- (void)startListen:(PGMethod *)command;
// 停止语音识别
- (void)stopListen:(PGMethod *)command;
@end
PGNXFYunPlugin.m文件
#import "PGXFYunPlugin.h"
@implementation PGXFYunPlugin
@synthesize callBackID;
@synthesize isSpeeching;
// 开始语音识别
- (void)startListen:(PGMethod *)command
{
if (command && !self.isSpeeching) {
// CallBackid 异步方法的回调id,H5+ 会根据回调ID通知JS层运行结果成功或者失败
NSString *cbId = [command.arguments objectAtIndex:0];
self.callBackID = cbId;
self.isSpeeching = YES;
// 创建语音听写对象
if (iflySpeechRecognizer == nil) {
iflySpeechRecognizer = [IFlySpeechRecognizer sharedInstance];
[iflySpeechRecognizer setParameter:@"" forKey:[IFlySpeechConstant PARAMS]];
// 设置听写参数
[iflySpeechRecognizer setParameter:@"iat" forKey:[IFlySpeechConstant IFLY_DOMAIN]];
//asr_audio_path保存录音文件名,如不再需要,设置value为nil表示取消,默认目录是Library/cache
[iflySpeechRecognizer setParameter:nil forKey:[IFlySpeechConstant ASR_AUDIO_PATH]];
//设置最长录音时间12s
[iflySpeechRecognizer setParameter:@"12000" forKey:[IFlySpeechConstant SPEECH_TIMEOUT]];
//网络等待时间30s
[iflySpeechRecognizer setParameter:@"20000" forKey:[IFlySpeechConstant NET_TIMEOUT]];
//设置是否返回标点符号
[iflySpeechRecognizer setParameter:@"0" forKey:[IFlySpeechConstant ASR_PTT]];
}
iflySpeechRecognizer.delegate = self;
// 启动识别服务
[iflySpeechRecognizer startListening];
}
}
// 停止语音识别
- (void)stopListen:(PGMethod *)command {
if (command) {
self.isSpeeching = NO;
if (iflySpeechRecognizer != nil) {
// 停止识别服务
[iflySpeechRecognizer stopListening];
}
}
}
#pragma mark IFlySpeechRecognizerDelegate.h delegate
/*
识别结果返回代理
*/
- (void)onResults:(NSArray *)results isLast:(BOOL)isLast
{
NSLog(@"onResults");
self.isSpeeching = NO;
if (self.callBackID != nil) {
NSMutableString *resultMutableString = [[NSMutableString alloc] init];
NSDictionary *dic = results[0];
for (NSString *key in dic) {
[resultMutableString appendFormat:@"%@",key];
}
NSString *resultString = [NSString stringWithFormat:@"%@", resultMutableString];
NSString *resultFromJson = [self stringFromJson:resultString];
NSDictionary *dicResult = [NSDictionary dictionaryWithObjectsAndKeys:
@"type", @"text",
@"value", resultFromJson,
nil];
// 运行Native代码结果和预期相同,调用回调通知JS层运行成功并返回结果
PDRPluginResult *result = [PDRPluginResult resultWithStatus:PDRCommandStatusOK messageAsDictionary:dicResult];
// 通知JS层Native层运行结果,JS Pluginbridge会根据cbid找到对应的回调方法并触发
[self toCallback:self.callBackID withReslut:[result toJSONString]];
}
}
/*
识别会话结束返回代理 (注:无论听写是否正确都会回调)
error.errorCode =
0 听写正确
other 听写出错
*/
- (void) onError:(IFlySpeechError *) error
{
NSLog(@"onError");
self.isSpeeching = NO;
if (error.errorCode != 0 && self.callBackID != nil ) {
NSDictionary *dicResult = [NSDictionary dictionaryWithObjectsAndKeys:
@"code", [NSString stringWithFormat:@"%d", error.errorCode],
@"desc", error.errorDesc,
nil];
// 如果Native代码运行结果和预期不同,需要通过回调通知JS层出现错误,并返回错误提示
PDRPluginResult *result = [PDRPluginResult resultWithStatus:PDRCommandStatusError messageAsDictionary:dicResult];
// 通知JS层Native层运行结果,JS Pluginbridge会根据cbid找到对应的回调方法并触发
[self toCallback:self.callBackID withReslut:[result toJSONString]];
}
}
/*
开始录音回调
*/
- (void)onBeginOfSpeech
{
NSLog(@"onBeginOfSpeech");
}
/*
停止录音回调
*/
- (void)onEndOfSpeech
{
NSLog(@"onEndOfSpeech");
self.isSpeeching = NO;
}
/*
音量回调函数
volume 0-30
*/
- (void)onVolumeChanged:(int)volume
{
if (self.callBackID != nil ) {
NSDictionary *dicResult = [NSDictionary dictionaryWithObjectsAndKeys:
@"type", @"volume",
@"value", [NSString stringWithFormat:@"%d", volume],
nil];
// 运行Native代码结果和预期相同,调用回调通知JS层运行成功并返回结果
PDRPluginResult *result = [PDRPluginResult resultWithStatus:PDRCommandStatusOK messageAsDictionary: dicResult];
// 通知JS层Native层运行结果,JS Pluginbridge会根据cbid找到对应的回调方法并触发
[self toCallback:self.callBackID withReslut:[result toJSONString]];
}
}
- (void)dealloc {
[super dealloc];
[iflySpeechRecognizer cancel]; //取消识别
[iflySpeechRecognizer setDelegate:nil];
[iflySpeechRecognizer setParameter:@"" forKey:[IFlySpeechConstant PARAMS]];
self.callBackID = nil;
self.isSpeeching = nil;
}
/**
解析听写json格式的数据
params例如:
{"sn":1,"ls":true,"bg":0,"ed":0,"ws":[{"bg":0,"cw":[{"w":"白日","sc":0}]},{"bg":0,"cw":[{"w":"依山","sc":0}]},{"bg":0,"cw":[{"w":"尽","sc":0}]},{"bg":0,"cw":[{"w":"黄河入海流","sc":0}]},{"bg":0,"cw":[{"w":"。","sc":0}]}]}
****/
- (NSString *)stringFromJson:(NSString*)params
{
if (params == NULL) {
return nil;
}
NSMutableString *tempStr = [[NSMutableString alloc] init];
NSDictionary *resultDic = [NSJSONSerialization JSONObjectWithData: //返回的格式必须为utf8的,否则发生未知错误
[params dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:nil];
if (resultDic!= nil) {
NSArray *wordArray = [resultDic objectForKey:@"ws"];
for (int i = 0; i < [wordArray count]; i++) {
NSDictionary *wsDic = [wordArray objectAtIndex: i];
NSArray *cwArray = [wsDic objectForKey:@"cw"];
for (int j = 0; j < [cwArray count]; j++) {
NSDictionary *wDic = [cwArray objectAtIndex:j];
NSString *str = [wDic objectForKey:@"w"];
[tempStr appendString: str];
}
}
}
return tempStr;
}
@end
// 调用方法
plus.XFYunPlugin.startListen(function(result) {
// 成功回调函数
JSON.stringify(result);
}, function(error) {
// 失败回调函数
JSON.stringify(error);
});
收起阅读 »

mui原生日期选择器封装
插件js
/* 时间选择器封装 @山东-三言
* 共有两种初始化方式:
* @第一种方式:mui(".mui-getDate").getDateInfo(); 类名可以随便定义,优点可以定义多项,缺点无法设置回调函数
* @第二种方式:var test = new mui.getDateInfo(document.getElementById("testGetDate"),function(d){alert(d)});
* @第一个参数为element类型,就是要初始化的节点
* @第二个参数为function类型,回调函数,回调的有一个参数为获取的时间字符串
* ------------------------------------------------------------
* 时间类型说明 例子
* data命名 data-dateType <input class="mui-getDate" data-dateType="date" id='' name=''>
*
* 可选值 返回结果说明
* date 日期类型,例如 2016-11-11
* time 时间类型,例如 18:22 24小时制
* @! 如果不设置data-dateType默认为date类型
* ------------------------------------------------------------
* 【data-dateType为date类型】 !!!刚给data-minDate和data-maxDate增加了一个active的值,如果传active可以设置为当前日期
* data参数名 说明 默认值 格式都为"2016-11-11"
* data-defaultDate 默认选中的时间 当前日期
* data-minDate 日期最小值 无
* data-maxDate 日期最大值 无
*
* 【data-dateType为time类型】
* data参数名 说明 默认值 格式都为"13:22"
* data-defaultDate data-defaultDate 12:00
* -----------------------------------------------------------
* 返回值 如果有回调函数会调用回调函数,返回值为选中的日期字符串
* 当element为input的时候会设置value为选中的日期字符串
*/
(function($) {
var GetDateInfo = $.getDateInfo = $.Class.extend({
init: function(element, fun) {
var self = this;
if(element.nodeType) {
self.ele = element;
fun && (self.fun = fun);
self.EventInit();
} else {
throw new Error('传入的参数不是Element类型!');
}
return self;
},
//绑定事件
EventInit: function() {
var self = this,
selfEle = this.ele,
isBindingEvent = selfEle.hasAttribute("data-getDate-event"); //是否绑定点击事件
!isBindingEvent && selfEle.addEventListener("tap", function() {
self.getDate()
}), selfEle.setAttribute("data-getDate-event", true); //绑定点击事件
!selfEle.hasAttribute("readonly") && selfEle.tagName == 'INPUT' && selfEle.setAttribute("readonly", "readonly"); //如果是INPUT添加禁用状态
return self;
},
/*为了不犯和官方numbox插件不能动态设置最大值和最小值的错误,本插件采用的都是实时获取attr的值*/
getDate: function() {
var self = this,
selfEle = self.ele,
attrDateType = selfEle.getAttribute("data-dateType") || "date";
attrDateType == "date" && self.setDate();
attrDateType == "time" && self.setTime();
return self;
},
setDate: function() {
var self = this,
selfEle = self.ele,
attrDefaultDate = selfEle.getAttribute("data-defaultDate"), //获取attr设置的当前日期
dDate = new Date(), //当前日期对象
activeDate = [dDate.getFullYear(), dDate.getMonth(), dDate.getDate()], //获取当前日期
attrMinDate = selfEle.getAttribute("data-minDate"), //获取最小日期
attrMaxDate = selfEle.getAttribute("data-maxDate"), //获取最大日期
minDate, maxDate; //最大值、最大值对象
/*设置默认日期*/
attrDefaultDate ? dDate = self.setDateStr(attrDefaultDate) : dDate.setFullYear(activeDate[0], activeDate[1], activeDate[2]);
/*设置最小日期*/
attrMinDate && (minDate = attrMinDate=="active"?self.setDateObj(activeDate):self.setDateStr(attrMinDate));
/*设置最大日期*/
attrMaxDate && (maxDate = attrMaxDate=="active"?self.setDateObj(activeDate):self.setDateStr(attrMaxDate));
plus && plus.nativeUI.pickDate(function(e) {
var d = e.date,
dStr = d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate();
selfEle.tagName == 'INPUT' && (selfEle.value = dStr);
self.fun && self.fun(dStr);
}, function(e) {
}, {
title: "请选择日期",
date: dDate,
minDate: minDate,
maxDate: maxDate
});
return self;
},
setTime: function() {
var self = this,
selfEle = self.ele,
attrDefaultTime = selfEle.getAttribute("data-defaultTime")?selfEle.getAttribute("data-defaultTime").split(":"):null,
dTime = new Date();
attrDefaultTime?dTime.setHours(attrDefaultTime[0], attrDefaultTime[1]):dTime.setHours(12, 0);
plus.nativeUI.pickTime(function(e) {
var d = e.date,
dStr = d.getHours() + ":" + d.getMinutes();
selfEle.tagName == 'INPUT' && (selfEle.value = dStr);
self.fun && self.fun(dStr);
}, function(e) {
}, {
title: "请选择时间",
is24Hour: true,
time: dTime
});
return self;
},
setDateStr: function(str) {
if(typeof str === 'string') {
var returnAry = str.split("-").map(function(item, index) {
if(index == 1) {
return +item - 1;
} else {
return +item;
}
});
var dateObj = new Date();
dateObj.setFullYear(returnAry[0], returnAry[1], returnAry[2]);
return dateObj;
} else {
throw new Error('传入的参数不是字符串类型!');
}
},
setDateObj: function(dateAry){
var d = new Date();
d.setFullYear(dateAry[0],dateAry[1],dateAry[2]);
return d;
}
});
$.fn.getDateInfo = function() {
this.each(function(i, item) {
if(item.GetDateInfo) {
return false;
} else {
item.getDateInfo = new GetDateInfo(item);
return true;
}
});
};
})(mui);
前台代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<title></title>
<script src="js/mui.min.js"></script>
<link href="css/mui.min.css" rel="stylesheet" />
<script type="text/javascript" charset="utf-8">
mui.init();
</script>
<style type="text/css">
#test2 {
margin: 20px auto;
width: 300px;
background-color: #007AFF;
padding: 10px;
color: #fff;
margin-bottom: 15px;
border-radius: 4px;
text-align: center;
}
input {
-webkit-user-select: auto !important;
}
</style>
</head>
<body>
<form class="mui-input-group">
<span style="display: block;text-align: center;line-height: 40px;color:#0062CC;background-color: #f7f7f7;;">本插件只在app中有效</span>
<div class="mui-input-row">
<label>无data参数</label>
<input type="text" class="mui-input-clear " placeholder="无data参数">
</div>
<div class="mui-input-row">
<label>选择日期</label>
<input type="text" data-dateType="date" class="mui-input-clear" placeholder="选择日期">
</div>
<div class="mui-input-row">
<label>选择时间</label>
<input type="text" data-dateType="time" class="mui-input-clear" placeholder="选择时间">
</div>
<div class="mui-input-row">
<label>日期默认值</label>
<input type="text" data-defaultDate="2016-11-11" class="mui-input-clear" placeholder="日期默认值">
</div>
<div class="mui-input-row">
<label style="width: 60%;">日期默认、最大最小值</label>
<input class="mui-input-clear" data-defaultDate="2016-11-11" data-minDate="2016-11-2" data-maxDate="2016-11-28" placeholder="日期默认、最大最小值" style="width:40%" type="text">
</div>
<div class="mui-input-row">
<label style="width: 50%;">dateTime类型</label>
<input class="mui-input-clear" data-dateType="dateTime" data-defaultDate="2016-11-11" data-minDate="2016-11-2" data-maxDate="2016-11-28" data-defaultTime="18:22" placeholder="dateTime类型" style="width:50%" type="text">
</div>
<div class="mui-input-row">
<label>时间默认,</label>
<input class="mui-input-clear" data-dateType="time" data-defaultTime="16:32" placeholder="时间默认" type="text">
</div>
<div id="test2" data-dateType="time">选择时间,动态设置,支持任何参数设置</div>
<p class="mui-text-center">更多的组合效果等你来发掘!</p>
<script src="js/getDate.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
mui.plusReady(function() {
//第一种方法调用,可以为N个元素绑定日期选择
mui("input").getDateInfo();
//第二种方法,可以设置回调函数,第一个返回值为日期字符串,第二个是返回当前的element
var test = new mui.getDateInfo(document.getElementById("test2"), function(d, el) {
//切换状态
var state = el.getAttribute("data-dateType") == "time" ? "date" : "time";
alert("选择的日期为:" + d + "\n我的状态变成了:" + state + "\n在单击下我试试看");
//设置状态
el.setAttribute("data-dateType", state);
});
});
</script>
</form>
</body>
</html>
2016-11-25日更新日志
·增加了新的data-dateType类型 "dateTime"
支持可以同时选择日期和时间,各项设置参数都通用
下面是修改后的js源码
(function($) {
var GetDateInfo = $.getDateInfo = $.Class.extend({
init: function(element, fun) {
var self = this;
if(element.nodeType) {
self.ele = element;
fun && (self.fun = fun);
self.EventInit();
} else {
throw new Error('传入的参数不是Element类型!');
}
return self;
},
//绑定事件
EventInit: function() {
var self = this,
selfEle = this.ele,
isBindingEvent = selfEle.hasAttribute("data-getDate-event"); //是否绑定点击事件
!isBindingEvent && selfEle.addEventListener("tap", function() {
self.getDate()
}), selfEle.setAttribute("data-getDate-event", true); //绑定点击事件
!selfEle.hasAttribute("readonly") && selfEle.tagName == 'INPUT' && selfEle.setAttribute("readonly", "readonly"); //如果是INPUT添加禁用状态
return self;
},
/*为了不犯和官方numbox插件不能动态设置最大值和最小值的错误,本插件采用的都是实时获取attr的值*/
getDate: function() {
var self = this,
selfEle = self.ele,
attrDateType = self.dateType = selfEle.getAttribute("data-dateType") || "date";
attrDateType == "date" && self.setDate();
attrDateType == "time" && self.setTime();
attrDateType == "dateTime" && self.setDateTime();
return self;
},
setDate: function(fn) {
var self = this,
selfEle = self.ele,
attrDefaultDate = selfEle.getAttribute("data-defaultDate"), //获取attr设置的当前日期
dDate = new Date(), //当前日期对象
activeDate = [dDate.getFullYear(), dDate.getMonth(), dDate.getDate()], //获取当前日期
attrMinDate = selfEle.getAttribute("data-minDate"), //获取最小日期
attrMaxDate = selfEle.getAttribute("data-maxDate"), //获取最大日期
minDate, maxDate; //最大值、最大值对象
/*设置默认日期*/
attrDefaultDate ? dDate = self.setDateStr(attrDefaultDate) : dDate.setFullYear(activeDate[0], activeDate[1], activeDate[2]);
/*设置最小日期*/
attrMinDate && (minDate = attrMinDate=="active"?self.setDateObj(activeDate):self.setDateStr(attrMinDate));
/*设置最大日期*/
attrMaxDate && (maxDate = attrMaxDate=="active"?self.setDateObj(activeDate):self.setDateStr(attrMaxDate));
plus && plus.nativeUI.pickDate(function(e) {
var d = e.date,
dStr = d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate();
if(self.dateType == 'dateTime'){
fn && fn(dStr);
}else{
selfEle.tagName == 'INPUT' && (selfEle.value = dStr);
self.fun && self.fun(dStr);
}
}, function(e) {
}, {
title: "请选择日期",
date: dDate,
minDate: minDate,
maxDate: maxDate
});
},
setTime: function(fn) {
var self = this,
selfEle = self.ele,
attrDefaultTime = selfEle.getAttribute("data-defaultTime")?selfEle.getAttribute("data-defaultTime").split(":"):null,
dTime = new Date();
attrDefaultTime?dTime.setHours(attrDefaultTime[0], attrDefaultTime[1]):dTime.setHours(12, 0);
plus.nativeUI.pickTime(function(e) {
var d = e.date,
dStr = d.getHours() + ":" + d.getMinutes();
if(self.dateType == 'dateTime'){
fn && fn(dStr);
}else{
selfEle.tagName == 'INPUT' && (selfEle.value = dStr);
self.fun && self.fun(dStr);
}
}, function(e) {
}, {
title: "请选择时间",
is24Hour: true,
time: dTime
});
return self;
},
setDateTime:function(){
var self = this,
selfEle = self.ele,
returnDateStr = '';
self.setDate(function(str){
returnDateStr = str;
self.setTime(function(str){
returnDateStr +=' '+str;
selfEle.tagName == 'INPUT' && (selfEle.value = returnDateStr);
self.fun && self.fun(returnDateStr);
});
})
},
setDateStr: function(str) {
if(typeof str === 'string') {
var returnAry = str.split("-").map(function(item, index) {
if(index == 1) {
return +item - 1;
} else {
return +item;
}
});
var dateObj = new Date();
dateObj.setFullYear(returnAry[0], returnAry[1], returnAry[2]);
return dateObj;
} else {
throw new Error('传入的参数不是字符串类型!');
}
},
setDateObj: function(dateAry){
var d = new Date();
d.setFullYear(dateAry[0],dateAry[1],dateAry[2]);
return d;
}
});
$.fn.getDateInfo = function() {
this.each(function(i, item) {
if(item.GetDateInfo) {
return false;
} else {
item.getDateInfo = new GetDateInfo(item);
return true;
}
});
};
})(mui);
插件js
/* 时间选择器封装 @山东-三言
* 共有两种初始化方式:
* @第一种方式:mui(".mui-getDate").getDateInfo(); 类名可以随便定义,优点可以定义多项,缺点无法设置回调函数
* @第二种方式:var test = new mui.getDateInfo(document.getElementById("testGetDate"),function(d){alert(d)});
* @第一个参数为element类型,就是要初始化的节点
* @第二个参数为function类型,回调函数,回调的有一个参数为获取的时间字符串
* ------------------------------------------------------------
* 时间类型说明 例子
* data命名 data-dateType <input class="mui-getDate" data-dateType="date" id='' name=''>
*
* 可选值 返回结果说明
* date 日期类型,例如 2016-11-11
* time 时间类型,例如 18:22 24小时制
* @! 如果不设置data-dateType默认为date类型
* ------------------------------------------------------------
* 【data-dateType为date类型】 !!!刚给data-minDate和data-maxDate增加了一个active的值,如果传active可以设置为当前日期
* data参数名 说明 默认值 格式都为"2016-11-11"
* data-defaultDate 默认选中的时间 当前日期
* data-minDate 日期最小值 无
* data-maxDate 日期最大值 无
*
* 【data-dateType为time类型】
* data参数名 说明 默认值 格式都为"13:22"
* data-defaultDate data-defaultDate 12:00
* -----------------------------------------------------------
* 返回值 如果有回调函数会调用回调函数,返回值为选中的日期字符串
* 当element为input的时候会设置value为选中的日期字符串
*/
(function($) {
var GetDateInfo = $.getDateInfo = $.Class.extend({
init: function(element, fun) {
var self = this;
if(element.nodeType) {
self.ele = element;
fun && (self.fun = fun);
self.EventInit();
} else {
throw new Error('传入的参数不是Element类型!');
}
return self;
},
//绑定事件
EventInit: function() {
var self = this,
selfEle = this.ele,
isBindingEvent = selfEle.hasAttribute("data-getDate-event"); //是否绑定点击事件
!isBindingEvent && selfEle.addEventListener("tap", function() {
self.getDate()
}), selfEle.setAttribute("data-getDate-event", true); //绑定点击事件
!selfEle.hasAttribute("readonly") && selfEle.tagName == 'INPUT' && selfEle.setAttribute("readonly", "readonly"); //如果是INPUT添加禁用状态
return self;
},
/*为了不犯和官方numbox插件不能动态设置最大值和最小值的错误,本插件采用的都是实时获取attr的值*/
getDate: function() {
var self = this,
selfEle = self.ele,
attrDateType = selfEle.getAttribute("data-dateType") || "date";
attrDateType == "date" && self.setDate();
attrDateType == "time" && self.setTime();
return self;
},
setDate: function() {
var self = this,
selfEle = self.ele,
attrDefaultDate = selfEle.getAttribute("data-defaultDate"), //获取attr设置的当前日期
dDate = new Date(), //当前日期对象
activeDate = [dDate.getFullYear(), dDate.getMonth(), dDate.getDate()], //获取当前日期
attrMinDate = selfEle.getAttribute("data-minDate"), //获取最小日期
attrMaxDate = selfEle.getAttribute("data-maxDate"), //获取最大日期
minDate, maxDate; //最大值、最大值对象
/*设置默认日期*/
attrDefaultDate ? dDate = self.setDateStr(attrDefaultDate) : dDate.setFullYear(activeDate[0], activeDate[1], activeDate[2]);
/*设置最小日期*/
attrMinDate && (minDate = attrMinDate=="active"?self.setDateObj(activeDate):self.setDateStr(attrMinDate));
/*设置最大日期*/
attrMaxDate && (maxDate = attrMaxDate=="active"?self.setDateObj(activeDate):self.setDateStr(attrMaxDate));
plus && plus.nativeUI.pickDate(function(e) {
var d = e.date,
dStr = d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate();
selfEle.tagName == 'INPUT' && (selfEle.value = dStr);
self.fun && self.fun(dStr);
}, function(e) {
}, {
title: "请选择日期",
date: dDate,
minDate: minDate,
maxDate: maxDate
});
return self;
},
setTime: function() {
var self = this,
selfEle = self.ele,
attrDefaultTime = selfEle.getAttribute("data-defaultTime")?selfEle.getAttribute("data-defaultTime").split(":"):null,
dTime = new Date();
attrDefaultTime?dTime.setHours(attrDefaultTime[0], attrDefaultTime[1]):dTime.setHours(12, 0);
plus.nativeUI.pickTime(function(e) {
var d = e.date,
dStr = d.getHours() + ":" + d.getMinutes();
selfEle.tagName == 'INPUT' && (selfEle.value = dStr);
self.fun && self.fun(dStr);
}, function(e) {
}, {
title: "请选择时间",
is24Hour: true,
time: dTime
});
return self;
},
setDateStr: function(str) {
if(typeof str === 'string') {
var returnAry = str.split("-").map(function(item, index) {
if(index == 1) {
return +item - 1;
} else {
return +item;
}
});
var dateObj = new Date();
dateObj.setFullYear(returnAry[0], returnAry[1], returnAry[2]);
return dateObj;
} else {
throw new Error('传入的参数不是字符串类型!');
}
},
setDateObj: function(dateAry){
var d = new Date();
d.setFullYear(dateAry[0],dateAry[1],dateAry[2]);
return d;
}
});
$.fn.getDateInfo = function() {
this.each(function(i, item) {
if(item.GetDateInfo) {
return false;
} else {
item.getDateInfo = new GetDateInfo(item);
return true;
}
});
};
})(mui);
前台代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<title></title>
<script src="js/mui.min.js"></script>
<link href="css/mui.min.css" rel="stylesheet" />
<script type="text/javascript" charset="utf-8">
mui.init();
</script>
<style type="text/css">
#test2 {
margin: 20px auto;
width: 300px;
background-color: #007AFF;
padding: 10px;
color: #fff;
margin-bottom: 15px;
border-radius: 4px;
text-align: center;
}
input {
-webkit-user-select: auto !important;
}
</style>
</head>
<body>
<form class="mui-input-group">
<span style="display: block;text-align: center;line-height: 40px;color:#0062CC;background-color: #f7f7f7;;">本插件只在app中有效</span>
<div class="mui-input-row">
<label>无data参数</label>
<input type="text" class="mui-input-clear " placeholder="无data参数">
</div>
<div class="mui-input-row">
<label>选择日期</label>
<input type="text" data-dateType="date" class="mui-input-clear" placeholder="选择日期">
</div>
<div class="mui-input-row">
<label>选择时间</label>
<input type="text" data-dateType="time" class="mui-input-clear" placeholder="选择时间">
</div>
<div class="mui-input-row">
<label>日期默认值</label>
<input type="text" data-defaultDate="2016-11-11" class="mui-input-clear" placeholder="日期默认值">
</div>
<div class="mui-input-row">
<label style="width: 60%;">日期默认、最大最小值</label>
<input class="mui-input-clear" data-defaultDate="2016-11-11" data-minDate="2016-11-2" data-maxDate="2016-11-28" placeholder="日期默认、最大最小值" style="width:40%" type="text">
</div>
<div class="mui-input-row">
<label style="width: 50%;">dateTime类型</label>
<input class="mui-input-clear" data-dateType="dateTime" data-defaultDate="2016-11-11" data-minDate="2016-11-2" data-maxDate="2016-11-28" data-defaultTime="18:22" placeholder="dateTime类型" style="width:50%" type="text">
</div>
<div class="mui-input-row">
<label>时间默认,</label>
<input class="mui-input-clear" data-dateType="time" data-defaultTime="16:32" placeholder="时间默认" type="text">
</div>
<div id="test2" data-dateType="time">选择时间,动态设置,支持任何参数设置</div>
<p class="mui-text-center">更多的组合效果等你来发掘!</p>
<script src="js/getDate.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
mui.plusReady(function() {
//第一种方法调用,可以为N个元素绑定日期选择
mui("input").getDateInfo();
//第二种方法,可以设置回调函数,第一个返回值为日期字符串,第二个是返回当前的element
var test = new mui.getDateInfo(document.getElementById("test2"), function(d, el) {
//切换状态
var state = el.getAttribute("data-dateType") == "time" ? "date" : "time";
alert("选择的日期为:" + d + "\n我的状态变成了:" + state + "\n在单击下我试试看");
//设置状态
el.setAttribute("data-dateType", state);
});
});
</script>
</form>
</body>
</html>
2016-11-25日更新日志
·增加了新的data-dateType类型 "dateTime"
支持可以同时选择日期和时间,各项设置参数都通用
下面是修改后的js源码
(function($) {
var GetDateInfo = $.getDateInfo = $.Class.extend({
init: function(element, fun) {
var self = this;
if(element.nodeType) {
self.ele = element;
fun && (self.fun = fun);
self.EventInit();
} else {
throw new Error('传入的参数不是Element类型!');
}
return self;
},
//绑定事件
EventInit: function() {
var self = this,
selfEle = this.ele,
isBindingEvent = selfEle.hasAttribute("data-getDate-event"); //是否绑定点击事件
!isBindingEvent && selfEle.addEventListener("tap", function() {
self.getDate()
}), selfEle.setAttribute("data-getDate-event", true); //绑定点击事件
!selfEle.hasAttribute("readonly") && selfEle.tagName == 'INPUT' && selfEle.setAttribute("readonly", "readonly"); //如果是INPUT添加禁用状态
return self;
},
/*为了不犯和官方numbox插件不能动态设置最大值和最小值的错误,本插件采用的都是实时获取attr的值*/
getDate: function() {
var self = this,
selfEle = self.ele,
attrDateType = self.dateType = selfEle.getAttribute("data-dateType") || "date";
attrDateType == "date" && self.setDate();
attrDateType == "time" && self.setTime();
attrDateType == "dateTime" && self.setDateTime();
return self;
},
setDate: function(fn) {
var self = this,
selfEle = self.ele,
attrDefaultDate = selfEle.getAttribute("data-defaultDate"), //获取attr设置的当前日期
dDate = new Date(), //当前日期对象
activeDate = [dDate.getFullYear(), dDate.getMonth(), dDate.getDate()], //获取当前日期
attrMinDate = selfEle.getAttribute("data-minDate"), //获取最小日期
attrMaxDate = selfEle.getAttribute("data-maxDate"), //获取最大日期
minDate, maxDate; //最大值、最大值对象
/*设置默认日期*/
attrDefaultDate ? dDate = self.setDateStr(attrDefaultDate) : dDate.setFullYear(activeDate[0], activeDate[1], activeDate[2]);
/*设置最小日期*/
attrMinDate && (minDate = attrMinDate=="active"?self.setDateObj(activeDate):self.setDateStr(attrMinDate));
/*设置最大日期*/
attrMaxDate && (maxDate = attrMaxDate=="active"?self.setDateObj(activeDate):self.setDateStr(attrMaxDate));
plus && plus.nativeUI.pickDate(function(e) {
var d = e.date,
dStr = d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate();
if(self.dateType == 'dateTime'){
fn && fn(dStr);
}else{
selfEle.tagName == 'INPUT' && (selfEle.value = dStr);
self.fun && self.fun(dStr);
}
}, function(e) {
}, {
title: "请选择日期",
date: dDate,
minDate: minDate,
maxDate: maxDate
});
},
setTime: function(fn) {
var self = this,
selfEle = self.ele,
attrDefaultTime = selfEle.getAttribute("data-defaultTime")?selfEle.getAttribute("data-defaultTime").split(":"):null,
dTime = new Date();
attrDefaultTime?dTime.setHours(attrDefaultTime[0], attrDefaultTime[1]):dTime.setHours(12, 0);
plus.nativeUI.pickTime(function(e) {
var d = e.date,
dStr = d.getHours() + ":" + d.getMinutes();
if(self.dateType == 'dateTime'){
fn && fn(dStr);
}else{
selfEle.tagName == 'INPUT' && (selfEle.value = dStr);
self.fun && self.fun(dStr);
}
}, function(e) {
}, {
title: "请选择时间",
is24Hour: true,
time: dTime
});
return self;
},
setDateTime:function(){
var self = this,
selfEle = self.ele,
returnDateStr = '';
self.setDate(function(str){
returnDateStr = str;
self.setTime(function(str){
returnDateStr +=' '+str;
selfEle.tagName == 'INPUT' && (selfEle.value = returnDateStr);
self.fun && self.fun(returnDateStr);
});
})
},
setDateStr: function(str) {
if(typeof str === 'string') {
var returnAry = str.split("-").map(function(item, index) {
if(index == 1) {
return +item - 1;
} else {
return +item;
}
});
var dateObj = new Date();
dateObj.setFullYear(returnAry[0], returnAry[1], returnAry[2]);
return dateObj;
} else {
throw new Error('传入的参数不是字符串类型!');
}
},
setDateObj: function(dateAry){
var d = new Date();
d.setFullYear(dateAry[0],dateAry[1],dateAry[2]);
return d;
}
});
$.fn.getDateInfo = function() {
this.each(function(i, item) {
if(item.GetDateInfo) {
return false;
} else {
item.getDateInfo = new GetDateInfo(item);
return true;
}
});
};
})(mui);
收起阅读 »

iOS上的CSS样式协议 VKCssProtocol
早先,写过一阵子RN,前一阵子写微信小程序,深深地觉得CSS这个东西写起来很爽,样式与界面完全隔离,写好一套一套的样式 CSS Class 然后,在写界面HTML的时候直接对界面元素,无论是什么HTML标签,什么控件,只要指定 CSS Class 的名字就能自动生效。
样式和界面完全隔离解耦
样式之间可以自由任意排列组合创建新CSS Class
对任意界面元素指定样式Class名就能自动生效
客户端场景
UI出App设计图也会有一套标准UI库
不同的底色,字号,字色,圆角,字体,阴影等等样式属性相互组合
UI有时候更换整体设计风格的时候,所有项目中用到的标准组件,应该随着UI设计一起变为最新的设计效果
标准UI库扩展延伸一下,就是客户端主题风格,换肤等系统
工厂模式
看到上面的设计需求,相信很多人第一时间想到的都是客户端里构建一套工厂模式
由工厂模式统一生成UI设计的标准控件
所有需要使用标注控件的地方都用工厂去生成
当UI需要整体修改样式属性,修改工厂模式的构造方法就能实现整体应用新效果
我不是很喜欢这种模式
写法不统一,必须让使用者使用工厂的构造方法来创建标准UI,非标准UI写法就随意了
耦合性比较强,必须引入工厂模块
Css样式,Protocol协议
希望引入Css的样式思想,让样式与界面分离
将UI给的统一标准设计图,专心的写成 .css 文件
可以动态下发,可以动态替换,动态更新效果。
希望像iOS Protocol协议那样工作
不管控件是Label Button Image View,CSSClass都可以直接指定一个协议名字,样式功能就自动生效
不管控件是用工厂创建的,还是Xib拖线的,还是手写代码写的,都能无缝接入这个cssprotocol
xxview.protocol = "cssclassname1 cssclassname2" 这种感觉
协议可以同时指定多个样式,以空格区分,样式之间自由组合
我不想动态修改界面元素布局,动态创建全新的界面
大家都知道,CSS有一个很大的用途就是用于界面布局,并且css的布局写法和iOS原生的布局写法有很大的区别,所以在这里我想强调一点,我这里写的cssprotocol,不想包含任何跟布局有关,只是单纯的动态配置外观样式,丝毫不影响布局。
原因是,项目里总会存在着各种各样的布局方式,有frame布局,有masonry autolayout,也有XIB拖线autolayout,我希望我写的东西能让使用的人很快的在自己项目里接入,而不是一下删掉旧的布局方案,全都替换成我的。
我希望使用者直接在现有的代码里,无论是哪种方式实现的界面,取到UIView,直接指定cssprotocol,就自动样式生效了,不要让使用者需要大规模改动现有代码。
同理,我也没想让这一套能够动态的创建UI,真要动态创建原生UI,直接用samurai reactnative weex好了。
还原我的初衷,我还是希望原生开发者能在不改变自己的项目的情况下,很快的接入这个工具,对于主题样式能够控制的更灵活和方便。
题外话:
我就很不喜欢ASDK的设计,一整套异步渲染,flexbox页面布局,网络,缓存,滚动控制等等一堆完整解决方案杂糅在一起,让使用者的代价异常的高,哪怕提供了UIKit转ASNode的简单入口也无法改变这一笨重无比的事实
其实ASDK每一个feature,单看源码,单独拆出来模块,学习思想,吸收进入自己的项目都是很好地。
VKCssProtocol
整个项目的代码,以及使用demo,都在上面
这其实是一个为native开发准备的工具,是OC的代码,OC的实现,别被CSS的名字欺骗了╮(╯_╰)╭
对于这样的iOS客户端开发的场景,多少会有一定的帮助
UI出App设计图有一套标准UI库,包括大中小标题,大中小按钮,bar配色,分割线等
每种标准样式都含有不同的底色,字号,字色,圆角,字体,阴影等等样式属性,属性之间相互自有组合
UI有时候更换整体设计风格的时候,所有项目中用到的标准组件,应该随着UI设计一起动态生效为最新的设计效果
客户端主题风格切换,换肤等系统
基本用法
简单的看一个GIF吧,左边就是CSS代码,后续我会给出目前已支持的CSS列表,在这里写完后,右侧可以实时看到css效果,可以看到我准备了2个view样式,准备了2个文字样式,然后四个UI进行排列组合,任意交叉组合,实现各种灵活的设计
先在项目里创建.css文件
然后在里面写Css代码,这里我粘个样例
.commenView1{
background-color:orange;
border-top:3pxsolid#9AFF02;
border-left:5pxsolid black;
}
.commenView2{
background-color:#FF9D6F;
border-color:black;
border-width:2px;
border-radius:15px;
}
.commenText1{
color:white ;
font-size:20px;
text-align : right;
text-transform: lowercase;
text-decoration: line-through;
}
.commenText2{
color:black ;
font-size:15px;
text-align : right;
text-transform: uppercase;
text-decoration: underline;
}
在iOS项目代码里加载Css
在didFinishLaunch or 某个你打算加载整体Css文件的位置
//先import 头文件
import "VKCssProtocol.h"
//读取bundle中名为cssDemo的css文件
@loadBundleCss(@"cssDemo");
对任意UI指定协议
UILabel*btabc = [[UILabelalloc]initWithFrame:CGRectMake(20,50,self.view.bounds.size.width -40,80)];
btabc.text = @"commenView1 commenText1";
[self.view addSubview:btabc];
UILabel*lbabc = [[UILabelalloc]initWithFrame:CGRectMake(20,150,self.view.bounds.size.width -40,80)];
lbabc.text = @"commenView2 commenText1";
[self.view addSubview:lbabc];
UILabel*btabcd = [[UILabelalloc]initWithFrame:CGRectMake(20,250,self.view.bounds.size.width -40,80)];
btabcd.text = @"commenView1 commenText2";
[self.view addSubview:btabcd];
UILabel*lbabcd = [[UILabelalloc]initWithFrame:CGRectMake(20,350,self.view.bounds.size.width -40,80)];
lbabcd.text = @"commenView2 commenText2";
[self.view addSubview:lbabcd];
上面的UI创建可以用任意方法创建,frame,autolayout,xib,随便创建
只需要对指定的UI对象,赋值cssClass属性,就可以指定css协议,就直接生效了,
btabc.cssClass = @"commenView1 commenText1";
lbabc.cssClass = @"commenView2 commenText1";
btabcd.cssClass = @"commenView1 commenText2";
lbabcd.cssClass = @"commenView2 commenText2";
可以对一个UI对象,指定多个cssClass协议,他们一起组合生效,优先级按最后生效的算
加载CSS的API
加载css主要依赖的是 VKCssClassManager 这个类,但提供了4个宏,可以快速方便的加载css
VKLoadBundleCss(@"cssDemo");
加载bundle内文件名为cssDemo的.css文件
VKLoadPathCss(@"xxx/xxx.css");
加载路径path下的css文件
@loadBundleCss(@"cssDemo");
等同于VKLoadBundleCss,模拟了@语法糖
@loadPathCss(@"xxx/xxx.css");
等同于VKLoadPathCss,模拟了@语法糖
吐槽:
模拟@selector()这种的OC语法糖的方案真TM坑爹
凡是这种@loadBundleCss的宏,是无法获得xcode提供的代码自动补全的
直接使用VKLoadBundleCss,是可以获得xcode代码自动补全的
跟RAC的@strongify @weakify一样,无法获得代码自动补全
这真的是一种只有装B,没球用的,看起来很pro的写法
指定cssClass
上面贴过代码,我对所有的UIView都扩写了一个category,里面新增了一个属性 cssClass ,对这个属性赋值,就相当于给这个UIView对象指定所遵从的cssClass协议,可以同时指定多个cssClass协议,用空格分开。
一个cssClass其实是一系列样式属性style的集合,将这一系列样式属性组合在一起,起个名字就是cssClass了,样给一个UI指定了cssClass就相当于一组style都生效了。
btabc.cssClass = @"commenView1 commenText1";
lbabc.cssClass = @"commenView2 commenText1";
btabcd.cssClass = @"commenView1 commenText2";
lbabcd.cssClass = @"commenView2 commenText2";
指定cssStyle
如果使用者并不打算专门写一个cssClass,只是打算简单的使用这个工具给一个ui赋值一个或几个style,这也是支持的(嗯,常规的html组件也是可以写class属性和style属性的嘛)
btabc.cssStyle = @"background-color:black border-color:black";
我扩写的category里,还新增了一个属性 cssStyle ,对这个属性赋值,就相当于给这个UIView对象不创建一个cssClass,直接写一个或多个style使之生效
相当于你把一个或多个style写法,用空格分开,直接赋值给cssStyle即可
目前支持的style
background-color:orange; View的背景色样式,冒号后是颜色参数,可以直接输入颜色英文or #ffffff这样的十六进制色值
color:#ffffff 如果含有文字,文字的颜色,冒号后是颜色参数,可以直接输入颜色英文or #ffffff这样的十六进制色值
font-size: 20px ; 如果含有文字,文字的字体大小,冒号后面是字号参数
border-color:red View的边框颜色,等同于layer.borderColor,冒号后是颜色参数,可以直接输入颜色英文or #ffffff这样的十六进制色值
border-width: 2px View的边框宽度,等同于layer.borderWidth,冒号后是宽度参数
text-align: center 如果含有文字,文字的左右居中对齐,等同于TextAlignment,参数可以输入left center right justify
border-radius: 2px View的边框圆角,等同于layer.cornerRadius,冒号后面是半径参数
text: abcdefg 如果含有文字,文字的内容,后面参数是字符串
font-family: fontname 如果含有文字,文字的字体,等同于UIFont fontWithName的name,也可以直接输入systemFont,boldSystemFont,italicSystemFont三个快捷输入
background-image: imagenamed 如果含有image,image的名字,等同于UIImage的imageNamed的name
text-shadow: 2px 如果含有文字,文字的阴影宽度,后面是数字参数
text-transform:uppercase 如果含有文字,文字的变化,包含uppercase,lowercase,capitalize三个值,全小写,全大写,首字母大写
text-decoration:underline 如果含有文字,文字加特殊处理,包含underline,line-through两个值,下划线,删除线
border-top: 3px solid #9AFF02 对UIView进行上右下左的单独边线处理,这个值是上边线,第一个参数是宽度,solid后面是颜色
border-right: 3px solid #9AFF02 对UIView进行上右下左的单独边线处理,这个值是右边线,第一个参数是宽度,solid后面是颜色
border-bottom: 3px solid #9AFF02 对UIView进行上右下左的单独边线处理,这个值是下边线,第一个参数是宽度,solid后面是颜色
border-left: 3px solid #9AFF02 对UIView进行上右下左的单独边线处理,这个值是左边线,第一个参数是宽度,solid后面是颜色
支持灵活扩展
上面提到的每一个style都是一个模块化组件,如果希望扩展新的style,只需要遵循并且实现模块化协议即可轻松地在整个框架里,加入全新的style模块
以 background-color 这个style模块为例
随便新建一个继承自NSObject的类,让这个类遵从 <VKCssStyleProtocol> 协议
import <Foundation/Foundation.h>
import "VKCssStylePch.h"
@interfaceVKBackgroundcolorStyle:NSObject<VKCssStyleProtocol>
@end
然后在.m文件实现里,先使用 VK_REGISTE_ATTRIBUTE() 宏向框架注册,然后必须实现2个类方法协议
+styleName: 实现这个协议决定于你写css的时候冒号前的名字
+setTarget: styleValue: 实现这个协议决定于你如何解读css里面冒号后面的参数,并且处理传入的target,也就是目标UIView
@implementationVKBackgroundcolorStyle
VK_REGISTE_ATTRIBUTE()
-
(NSString*)styleName{
return@"background-color";
} -
(void)setTarget:(id)target styleValue:(id)value{
UIColorcolor = [value VKIdToColor];
if([target isKindOfClass:[UIViewclass]]) {
[(UIView)target setBackgroundColor:color];
}
}
@end
动态更新样式
VKCssClassManager 这个类负责管理所有的css样式表,我们希望这个css文件就好像配置表一样,可以动态下发,这样在未来发版之后,也能改变app的主题样式,自然就需要一套刷新机制
-
(void)readBundleCssFile:(NSString*)cssFile;
-
(void)readCssFilePath:(NSString*)cssFilePath;
-
(void)reloadCssFile;
-
(void)clearCssFile;
上面是 VKCssClassManager 的接口,由于bundle里的css文件是不可更新的,因此刷新机制与readBundleCssFile没啥关系,只有通过readCssFilePath路径加载的刷新机制才有意义
reloadCssFile 的用处就是沿着原路径重新加载css,使用场景是新的css覆盖了旧CSS路径不变,在reloadCssFile的时候会自动触发clearCssFile;
clearCssFile 的用处是让cssClassManager清空目前所管理的所有class;
在不直接使用reloadCssFile的情况下,可以先执行clearCssFile,再执行readCssFilePath,从而实现清空css后加载新路径的css文件
HotReloader
大家在Gif里看到了像playground一样,无需编译和重新运行,每改一行代码,界面就立刻实时生效的效果,主要是额外写了一个插件 HotReloader
由于HotReloader的设计初衷是给调试,高效的实时看效果用的,因此整个HotReloader通过编译控制,所有函数只有在模拟器编译的情况下才有效,真机下HotReloader回自动失效
这个HotReloader不是必须的,你完全可以不使用它,整个CssProtocol一样可以work
想要使用它需要先import头文件 #import "VKCssHotReloader.h" ,然后在准备加载Css的地方用预编译控制,控制模拟器下加载css的代码变为hotReloader监听Css
if TARGET_IPHONE_SIMULATOR
//playground调试
//JS测试包的本地绝对路径
NSStringrootPath = [[NSBundlemainBundle] objectForInfoDictionaryKey:@"projectPath"];;
NSStringcssPath = [NSStringstringWithFormat:@"%@%@", rootPath,@"/cssDemo.css"];
[VKCssHotReloader hotReloaderListenCssPath:cssPath];
else
VKLoadBundleCss(@"cssDemo");
endif
这个绝对路径一定要填Mac的磁盘文件路径哟,用过JSPatchPlaygroundTool的一定不会陌生
做完这件事之后还要注意2个事情
在你打算开启调试的地方调用 [VKCssHotReloader startHotReloader]; (比如某个界面的ViewDidLoad)
在你打算停止调试的地方调用 [VKCssHotReloader endHotReloader]; (比如某个界面的dealloc)
为什么要这么做,因为一旦当你startHotReloader的时候,所有进行过cssClass,cssStyle设置的view都会被建立一个监听,因此会造成View对象的额外持有导致的不释放,因此当你不打算HotReload了就要关闭这个监听endHotReloader
因为这样的设计有可能造成使用不当的内存Leak,所以对HotReloader的所有代码都进行了编译控制,只有模拟器下才会工作,真机orRelease包下,无论你怎么忘记写endHotReloader都不会造成Leak
来自:http://awhisper.github.io/2016/11/01/cssprotocol/
早先,写过一阵子RN,前一阵子写微信小程序,深深地觉得CSS这个东西写起来很爽,样式与界面完全隔离,写好一套一套的样式 CSS Class 然后,在写界面HTML的时候直接对界面元素,无论是什么HTML标签,什么控件,只要指定 CSS Class 的名字就能自动生效。
样式和界面完全隔离解耦
样式之间可以自由任意排列组合创建新CSS Class
对任意界面元素指定样式Class名就能自动生效
客户端场景
UI出App设计图也会有一套标准UI库
不同的底色,字号,字色,圆角,字体,阴影等等样式属性相互组合
UI有时候更换整体设计风格的时候,所有项目中用到的标准组件,应该随着UI设计一起变为最新的设计效果
标准UI库扩展延伸一下,就是客户端主题风格,换肤等系统
工厂模式
看到上面的设计需求,相信很多人第一时间想到的都是客户端里构建一套工厂模式
由工厂模式统一生成UI设计的标准控件
所有需要使用标注控件的地方都用工厂去生成
当UI需要整体修改样式属性,修改工厂模式的构造方法就能实现整体应用新效果
我不是很喜欢这种模式
写法不统一,必须让使用者使用工厂的构造方法来创建标准UI,非标准UI写法就随意了
耦合性比较强,必须引入工厂模块
Css样式,Protocol协议
希望引入Css的样式思想,让样式与界面分离
将UI给的统一标准设计图,专心的写成 .css 文件
可以动态下发,可以动态替换,动态更新效果。
希望像iOS Protocol协议那样工作
不管控件是Label Button Image View,CSSClass都可以直接指定一个协议名字,样式功能就自动生效
不管控件是用工厂创建的,还是Xib拖线的,还是手写代码写的,都能无缝接入这个cssprotocol
xxview.protocol = "cssclassname1 cssclassname2" 这种感觉
协议可以同时指定多个样式,以空格区分,样式之间自由组合
我不想动态修改界面元素布局,动态创建全新的界面
大家都知道,CSS有一个很大的用途就是用于界面布局,并且css的布局写法和iOS原生的布局写法有很大的区别,所以在这里我想强调一点,我这里写的cssprotocol,不想包含任何跟布局有关,只是单纯的动态配置外观样式,丝毫不影响布局。
原因是,项目里总会存在着各种各样的布局方式,有frame布局,有masonry autolayout,也有XIB拖线autolayout,我希望我写的东西能让使用的人很快的在自己项目里接入,而不是一下删掉旧的布局方案,全都替换成我的。
我希望使用者直接在现有的代码里,无论是哪种方式实现的界面,取到UIView,直接指定cssprotocol,就自动样式生效了,不要让使用者需要大规模改动现有代码。
同理,我也没想让这一套能够动态的创建UI,真要动态创建原生UI,直接用samurai reactnative weex好了。
还原我的初衷,我还是希望原生开发者能在不改变自己的项目的情况下,很快的接入这个工具,对于主题样式能够控制的更灵活和方便。
题外话:
我就很不喜欢ASDK的设计,一整套异步渲染,flexbox页面布局,网络,缓存,滚动控制等等一堆完整解决方案杂糅在一起,让使用者的代价异常的高,哪怕提供了UIKit转ASNode的简单入口也无法改变这一笨重无比的事实
其实ASDK每一个feature,单看源码,单独拆出来模块,学习思想,吸收进入自己的项目都是很好地。
VKCssProtocol
整个项目的代码,以及使用demo,都在上面
这其实是一个为native开发准备的工具,是OC的代码,OC的实现,别被CSS的名字欺骗了╮(╯_╰)╭
对于这样的iOS客户端开发的场景,多少会有一定的帮助
UI出App设计图有一套标准UI库,包括大中小标题,大中小按钮,bar配色,分割线等
每种标准样式都含有不同的底色,字号,字色,圆角,字体,阴影等等样式属性,属性之间相互自有组合
UI有时候更换整体设计风格的时候,所有项目中用到的标准组件,应该随着UI设计一起动态生效为最新的设计效果
客户端主题风格切换,换肤等系统
基本用法
简单的看一个GIF吧,左边就是CSS代码,后续我会给出目前已支持的CSS列表,在这里写完后,右侧可以实时看到css效果,可以看到我准备了2个view样式,准备了2个文字样式,然后四个UI进行排列组合,任意交叉组合,实现各种灵活的设计
先在项目里创建.css文件
然后在里面写Css代码,这里我粘个样例
.commenView1{
background-color:orange;
border-top:3pxsolid#9AFF02;
border-left:5pxsolid black;
}
.commenView2{
background-color:#FF9D6F;
border-color:black;
border-width:2px;
border-radius:15px;
}
.commenText1{
color:white ;
font-size:20px;
text-align : right;
text-transform: lowercase;
text-decoration: line-through;
}
.commenText2{
color:black ;
font-size:15px;
text-align : right;
text-transform: uppercase;
text-decoration: underline;
}
在iOS项目代码里加载Css
在didFinishLaunch or 某个你打算加载整体Css文件的位置
//先import 头文件
import "VKCssProtocol.h"
//读取bundle中名为cssDemo的css文件
@loadBundleCss(@"cssDemo");
对任意UI指定协议
UILabel*btabc = [[UILabelalloc]initWithFrame:CGRectMake(20,50,self.view.bounds.size.width -40,80)];
btabc.text = @"commenView1 commenText1";
[self.view addSubview:btabc];
UILabel*lbabc = [[UILabelalloc]initWithFrame:CGRectMake(20,150,self.view.bounds.size.width -40,80)];
lbabc.text = @"commenView2 commenText1";
[self.view addSubview:lbabc];
UILabel*btabcd = [[UILabelalloc]initWithFrame:CGRectMake(20,250,self.view.bounds.size.width -40,80)];
btabcd.text = @"commenView1 commenText2";
[self.view addSubview:btabcd];
UILabel*lbabcd = [[UILabelalloc]initWithFrame:CGRectMake(20,350,self.view.bounds.size.width -40,80)];
lbabcd.text = @"commenView2 commenText2";
[self.view addSubview:lbabcd];
上面的UI创建可以用任意方法创建,frame,autolayout,xib,随便创建
只需要对指定的UI对象,赋值cssClass属性,就可以指定css协议,就直接生效了,
btabc.cssClass = @"commenView1 commenText1";
lbabc.cssClass = @"commenView2 commenText1";
btabcd.cssClass = @"commenView1 commenText2";
lbabcd.cssClass = @"commenView2 commenText2";
可以对一个UI对象,指定多个cssClass协议,他们一起组合生效,优先级按最后生效的算
加载CSS的API
加载css主要依赖的是 VKCssClassManager 这个类,但提供了4个宏,可以快速方便的加载css
VKLoadBundleCss(@"cssDemo");
加载bundle内文件名为cssDemo的.css文件
VKLoadPathCss(@"xxx/xxx.css");
加载路径path下的css文件
@loadBundleCss(@"cssDemo");
等同于VKLoadBundleCss,模拟了@语法糖
@loadPathCss(@"xxx/xxx.css");
等同于VKLoadPathCss,模拟了@语法糖
吐槽:
模拟@selector()这种的OC语法糖的方案真TM坑爹
凡是这种@loadBundleCss的宏,是无法获得xcode提供的代码自动补全的
直接使用VKLoadBundleCss,是可以获得xcode代码自动补全的
跟RAC的@strongify @weakify一样,无法获得代码自动补全
这真的是一种只有装B,没球用的,看起来很pro的写法
指定cssClass
上面贴过代码,我对所有的UIView都扩写了一个category,里面新增了一个属性 cssClass ,对这个属性赋值,就相当于给这个UIView对象指定所遵从的cssClass协议,可以同时指定多个cssClass协议,用空格分开。
一个cssClass其实是一系列样式属性style的集合,将这一系列样式属性组合在一起,起个名字就是cssClass了,样给一个UI指定了cssClass就相当于一组style都生效了。
btabc.cssClass = @"commenView1 commenText1";
lbabc.cssClass = @"commenView2 commenText1";
btabcd.cssClass = @"commenView1 commenText2";
lbabcd.cssClass = @"commenView2 commenText2";
指定cssStyle
如果使用者并不打算专门写一个cssClass,只是打算简单的使用这个工具给一个ui赋值一个或几个style,这也是支持的(嗯,常规的html组件也是可以写class属性和style属性的嘛)
btabc.cssStyle = @"background-color:black border-color:black";
我扩写的category里,还新增了一个属性 cssStyle ,对这个属性赋值,就相当于给这个UIView对象不创建一个cssClass,直接写一个或多个style使之生效
相当于你把一个或多个style写法,用空格分开,直接赋值给cssStyle即可
目前支持的style
background-color:orange; View的背景色样式,冒号后是颜色参数,可以直接输入颜色英文or #ffffff这样的十六进制色值
color:#ffffff 如果含有文字,文字的颜色,冒号后是颜色参数,可以直接输入颜色英文or #ffffff这样的十六进制色值
font-size: 20px ; 如果含有文字,文字的字体大小,冒号后面是字号参数
border-color:red View的边框颜色,等同于layer.borderColor,冒号后是颜色参数,可以直接输入颜色英文or #ffffff这样的十六进制色值
border-width: 2px View的边框宽度,等同于layer.borderWidth,冒号后是宽度参数
text-align: center 如果含有文字,文字的左右居中对齐,等同于TextAlignment,参数可以输入left center right justify
border-radius: 2px View的边框圆角,等同于layer.cornerRadius,冒号后面是半径参数
text: abcdefg 如果含有文字,文字的内容,后面参数是字符串
font-family: fontname 如果含有文字,文字的字体,等同于UIFont fontWithName的name,也可以直接输入systemFont,boldSystemFont,italicSystemFont三个快捷输入
background-image: imagenamed 如果含有image,image的名字,等同于UIImage的imageNamed的name
text-shadow: 2px 如果含有文字,文字的阴影宽度,后面是数字参数
text-transform:uppercase 如果含有文字,文字的变化,包含uppercase,lowercase,capitalize三个值,全小写,全大写,首字母大写
text-decoration:underline 如果含有文字,文字加特殊处理,包含underline,line-through两个值,下划线,删除线
border-top: 3px solid #9AFF02 对UIView进行上右下左的单独边线处理,这个值是上边线,第一个参数是宽度,solid后面是颜色
border-right: 3px solid #9AFF02 对UIView进行上右下左的单独边线处理,这个值是右边线,第一个参数是宽度,solid后面是颜色
border-bottom: 3px solid #9AFF02 对UIView进行上右下左的单独边线处理,这个值是下边线,第一个参数是宽度,solid后面是颜色
border-left: 3px solid #9AFF02 对UIView进行上右下左的单独边线处理,这个值是左边线,第一个参数是宽度,solid后面是颜色
支持灵活扩展
上面提到的每一个style都是一个模块化组件,如果希望扩展新的style,只需要遵循并且实现模块化协议即可轻松地在整个框架里,加入全新的style模块
以 background-color 这个style模块为例
随便新建一个继承自NSObject的类,让这个类遵从 <VKCssStyleProtocol> 协议
import <Foundation/Foundation.h>
import "VKCssStylePch.h"
@interfaceVKBackgroundcolorStyle:NSObject<VKCssStyleProtocol>
@end
然后在.m文件实现里,先使用 VK_REGISTE_ATTRIBUTE() 宏向框架注册,然后必须实现2个类方法协议
+styleName: 实现这个协议决定于你写css的时候冒号前的名字
+setTarget: styleValue: 实现这个协议决定于你如何解读css里面冒号后面的参数,并且处理传入的target,也就是目标UIView
@implementationVKBackgroundcolorStyle
VK_REGISTE_ATTRIBUTE()
-
(NSString*)styleName{
return@"background-color";
} -
(void)setTarget:(id)target styleValue:(id)value{
UIColorcolor = [value VKIdToColor];
if([target isKindOfClass:[UIViewclass]]) {
[(UIView)target setBackgroundColor:color];
}
}
@end
动态更新样式
VKCssClassManager 这个类负责管理所有的css样式表,我们希望这个css文件就好像配置表一样,可以动态下发,这样在未来发版之后,也能改变app的主题样式,自然就需要一套刷新机制
-
(void)readBundleCssFile:(NSString*)cssFile;
-
(void)readCssFilePath:(NSString*)cssFilePath;
-
(void)reloadCssFile;
-
(void)clearCssFile;
上面是 VKCssClassManager 的接口,由于bundle里的css文件是不可更新的,因此刷新机制与readBundleCssFile没啥关系,只有通过readCssFilePath路径加载的刷新机制才有意义
reloadCssFile 的用处就是沿着原路径重新加载css,使用场景是新的css覆盖了旧CSS路径不变,在reloadCssFile的时候会自动触发clearCssFile;
clearCssFile 的用处是让cssClassManager清空目前所管理的所有class;
在不直接使用reloadCssFile的情况下,可以先执行clearCssFile,再执行readCssFilePath,从而实现清空css后加载新路径的css文件
HotReloader
大家在Gif里看到了像playground一样,无需编译和重新运行,每改一行代码,界面就立刻实时生效的效果,主要是额外写了一个插件 HotReloader
由于HotReloader的设计初衷是给调试,高效的实时看效果用的,因此整个HotReloader通过编译控制,所有函数只有在模拟器编译的情况下才有效,真机下HotReloader回自动失效
这个HotReloader不是必须的,你完全可以不使用它,整个CssProtocol一样可以work
想要使用它需要先import头文件 #import "VKCssHotReloader.h" ,然后在准备加载Css的地方用预编译控制,控制模拟器下加载css的代码变为hotReloader监听Css
if TARGET_IPHONE_SIMULATOR
//playground调试
//JS测试包的本地绝对路径
NSStringrootPath = [[NSBundlemainBundle] objectForInfoDictionaryKey:@"projectPath"];;
NSStringcssPath = [NSStringstringWithFormat:@"%@%@", rootPath,@"/cssDemo.css"];
[VKCssHotReloader hotReloaderListenCssPath:cssPath];
else
VKLoadBundleCss(@"cssDemo");
endif
这个绝对路径一定要填Mac的磁盘文件路径哟,用过JSPatchPlaygroundTool的一定不会陌生
做完这件事之后还要注意2个事情
在你打算开启调试的地方调用 [VKCssHotReloader startHotReloader]; (比如某个界面的ViewDidLoad)
在你打算停止调试的地方调用 [VKCssHotReloader endHotReloader]; (比如某个界面的dealloc)
为什么要这么做,因为一旦当你startHotReloader的时候,所有进行过cssClass,cssStyle设置的view都会被建立一个监听,因此会造成View对象的额外持有导致的不释放,因此当你不打算HotReload了就要关闭这个监听endHotReloader
因为这样的设计有可能造成使用不当的内存Leak,所以对HotReloader的所有代码都进行了编译控制,只有模拟器下才会工作,真机orRelease包下,无论你怎么忘记写endHotReloader都不会造成Leak
来自:http://awhisper.github.io/2016/11/01/cssprotocol/
收起阅读 »
mui 轮播空白问题
今天使用MUI的轮播,开始是静态图片显示正常,因为需求变更为从后台获取图片和链接,所以得动态生成轮播图片。问题就出现了,
一共两张图片,从第二张转换为第一张的时候总是出现一张空白图片。多次查找原因后发现是因为首尾图片顺序的问题,一定要
遵循官网文档规则“图片顺序变为:4、1、2、3、4、1”

今天使用MUI的轮播,开始是静态图片显示正常,因为需求变更为从后台获取图片和链接,所以得动态生成轮播图片。问题就出现了,
一共两张图片,从第二张转换为第一张的时候总是出现一张空白图片。多次查找原因后发现是因为首尾图片顺序的问题,一定要
遵循官网文档规则“图片顺序变为:4、1、2、3、4、1”

Android 调用DatagramSocket,实现UDP发送数据!
var DatagramSocket = plus.android.importClass("java.net.DatagramSocket");
var DatagramPacket = plus.android.importClass("java.net.DatagramPacket");
var InetAddress = plus.android.importClass("java.net.InetAddress");
var String = plus.android.importClass("java.lang.String");
var udp = new DatagramSocket();
var data = new String("测试发送数据").getBytes("gb2312");//发送中文需要指定编码
var packet = new DatagramPacket(data, data.length, InetAddress.getByName("192.168.0.255"), 1024);
udp.send(packet);
udp.close();
测试成功,至于如何接收数据,还有待实现。
var DatagramSocket = plus.android.importClass("java.net.DatagramSocket");
var DatagramPacket = plus.android.importClass("java.net.DatagramPacket");
var InetAddress = plus.android.importClass("java.net.InetAddress");
var String = plus.android.importClass("java.lang.String");
var udp = new DatagramSocket();
var data = new String("测试发送数据").getBytes("gb2312");//发送中文需要指定编码
var packet = new DatagramPacket(data, data.length, InetAddress.getByName("192.168.0.255"), 1024);
udp.send(packet);
udp.close();
测试成功,至于如何接收数据,还有待实现。

页面间的反向传值
近期的功能用到了页面的反向传值(从这一页面向上一页面传值),具体写法如下:
在此页面获取值id,如下:
mui.fire(detailPage,"salesNameAndBack",{
id:id
});
在上一界面,通过event.detail.id接收到id值,如下:
window.addEventListener("salesNameAndBack", function(event) {
Id = event.detail.id;
});
近期的功能用到了页面的反向传值(从这一页面向上一页面传值),具体写法如下:
在此页面获取值id,如下:
mui.fire(detailPage,"salesNameAndBack",{
id:id
});
在上一界面,通过event.detail.id接收到id值,如下:
window.addEventListener("salesNameAndBack", function(event) {
Id = event.detail.id;
});

头像裁剪会旋转的解决方法
https://github.com/fengyuanchen/cropper 下载最新的代码即可
https://github.com/fengyuanchen/cropper 下载最新的代码即可

web前端页面性能优化小结
影响用户访问的最大部分是前端的页面。网站的划分一般为二:前端和后台。我们可以理解成后台是用来实现网站的功能的,比如:实现用户注册,用户能够为文章发表评论等等。而前端呢?其实应该是属于功能的表现。
而我们建设网站的目的是什么呢?不就是为了让目标人群来访问吗?所以我们可以理解成前端才是真正和用户接触的。
除了后台需要在性能上做优化外,其实前端的页面更需要在性能优化上下功夫,只有这样才能给我们的用户带来更好的用户体验。不仅仅如此,如果前端优化得好,他不仅可以为企业节约成本,他还能给用户带来更多的用户,因为增强的用户体验。说了这么多,那么我们应该如何对我们前端的页面进行性能优化呢?
前端的页面主要包括xhtml,css,js。其实xhtml就是现实中所谈到的内容,页面的内容:文字,图片,flash,视频等。
而前端开发工作者可以控制的是什么呢?那就是xhtml,css,js的代码及相应的修饰(背景)图片。下面我就根据我自己的经验来说说:
一、提倡前端开发工程师在书写xhtml的时候做到结构语义化。
结构中主要包括了head和body两个部分,但是我们经常说的是结构语义化主要是body中的标签,但是我在这里还是简单的说一下head,head中其实包括了一些对于我们seo很有用的一些东西,比如title,Description,Keywords,这些东西在蜘蛛抓取的时候都是有帮助的,当然,还有其他的一些,我在此就不一一说明了,比如设置缓存等一些其他的信息。
那么body中的话,包括的标签就很多了,我觉得作为一个合格的前端开发人员你应该去熟悉他们,比如div,span,h,ul,ol,dl,p等等这类的标签的使用。应该非常合理,还有就是注意h标签的断层,及h1标签的使用,这些都是非常重要的。
同时在我们的结构中不要出现style和onclick这样的内联的样式和事件。希望大家能够注意结构与表现、行为的分离。
(PS:标签语义化的好处:1.有利于搜索引擎;2.结构清晰的HTML在团队合作中的作用,就不必说了吧;3.有利于盲人屏幕阅读器。至于如何做到标签语义化,就看个人的理解了,这方面我也觉得模糊,跟个人的习惯估计也有一定的关系。)
二、css,js文件数量及大小的优化
那么关于css、js的优化的话,一般情况下建议css和js采用外联式。但是如果你的页面内容比较多,设计师把整个效果做得比较花的话,恐怕css就非常多了,那么这种情况下,你一定要把你的css规划好,尽量的采用缩写,这样可以减少css文件的大小,那么对css做相应的规划也可以减少css的个数,减少http请求数,js同理。
(PS:减少重复性代码,代码重复利用,在这里显得特别重要)
三、背景图片数量及大小的优化
四、内容图片的大小的优化
转自blueidear:http://bbs.blueidea.com/thread-2936073-1-1.html
影响用户访问的最大部分是前端的页面。网站的划分一般为二:前端和后台。我们可以理解成后台是用来实现网站的功能的,比如:实现用户注册,用户能够为文章发表评论等等。而前端呢?其实应该是属于功能的表现。
而我们建设网站的目的是什么呢?不就是为了让目标人群来访问吗?所以我们可以理解成前端才是真正和用户接触的。
除了后台需要在性能上做优化外,其实前端的页面更需要在性能优化上下功夫,只有这样才能给我们的用户带来更好的用户体验。不仅仅如此,如果前端优化得好,他不仅可以为企业节约成本,他还能给用户带来更多的用户,因为增强的用户体验。说了这么多,那么我们应该如何对我们前端的页面进行性能优化呢?
前端的页面主要包括xhtml,css,js。其实xhtml就是现实中所谈到的内容,页面的内容:文字,图片,flash,视频等。
而前端开发工作者可以控制的是什么呢?那就是xhtml,css,js的代码及相应的修饰(背景)图片。下面我就根据我自己的经验来说说:
一、提倡前端开发工程师在书写xhtml的时候做到结构语义化。
结构中主要包括了head和body两个部分,但是我们经常说的是结构语义化主要是body中的标签,但是我在这里还是简单的说一下head,head中其实包括了一些对于我们seo很有用的一些东西,比如title,Description,Keywords,这些东西在蜘蛛抓取的时候都是有帮助的,当然,还有其他的一些,我在此就不一一说明了,比如设置缓存等一些其他的信息。
那么body中的话,包括的标签就很多了,我觉得作为一个合格的前端开发人员你应该去熟悉他们,比如div,span,h,ul,ol,dl,p等等这类的标签的使用。应该非常合理,还有就是注意h标签的断层,及h1标签的使用,这些都是非常重要的。
同时在我们的结构中不要出现style和onclick这样的内联的样式和事件。希望大家能够注意结构与表现、行为的分离。
(PS:标签语义化的好处:1.有利于搜索引擎;2.结构清晰的HTML在团队合作中的作用,就不必说了吧;3.有利于盲人屏幕阅读器。至于如何做到标签语义化,就看个人的理解了,这方面我也觉得模糊,跟个人的习惯估计也有一定的关系。)
二、css,js文件数量及大小的优化
那么关于css、js的优化的话,一般情况下建议css和js采用外联式。但是如果你的页面内容比较多,设计师把整个效果做得比较花的话,恐怕css就非常多了,那么这种情况下,你一定要把你的css规划好,尽量的采用缩写,这样可以减少css文件的大小,那么对css做相应的规划也可以减少css的个数,减少http请求数,js同理。
(PS:减少重复性代码,代码重复利用,在这里显得特别重要)
三、背景图片数量及大小的优化
四、内容图片的大小的优化
转自blueidear:http://bbs.blueidea.com/thread-2936073-1-1.html
收起阅读 »
HBuilder调试夜神安卓模拟器方法
HBuilder调试夜神安卓模拟器方法
现在开发手机app的IDE很多,今天我就以我个人开发使用的HBuider开发工具讲一下手机app开发调试。HBuider支持真机调试,这个比较简单,只要安装好手机的驱动,把手机和电脑通过数据线连接就可以调试发布了,如果手机连接不上,可以在电脑和手机上都安装360手机助手基本上就可以了。今天重点说一下使用夜神安卓模拟器和HBuider的连接调试方法。
第一步:下载安装夜神模拟器,这个在百度搜索就可以找官网,下载 安装就可以了。
第二步:查找已经安装的夜神模拟的端口,这里说一下夜神模拟器默认端口是62001,但是有些版本可能不是这个端口,怎么查找到底是哪个端口呢?按照如下顺序进行就可以查找到你按装的夜神模拟器端口了。
1.打开夜神模拟器,确保正常启动后,出现安卓桌面。
2.打开夜神模拟器的安装文件夹,找到D:\YS\Nox\bin文件夹打开(D:\YS是夜神模拟器安装的路径根目录),找到【debugReport.bat】文件,双击启动该批处理文件。会显示如下图:
nox adb port:52001
already connected to 127.0.0.1:52001
这里面显示了夜神模拟器的adb port 端口为:52001,并且已经可以正常连接了,出现这个内容证明夜神模拟器已经正常工作了。
或者找到【debug.bat】文件,双击启动该批处理文件,也可以查看端口。
3.找到夜神模拟器的adb 端口后,打开HBuider开发工具,在【工具】-->【选项】打开界面如下图,选择【HBuider】,在最下方的" 第三方Android模拟器端口"中输入52001后【确定】就可以了,通过这些设置就可以在调试中看到夜神模拟器的选项了。
4.如果没有出现夜神模拟器的连接项,那就需要你在电脑和夜神模拟器上安装360手机助手了,安装完360手机助手后,启动360卫士和360手机助手后,在360手机助手中连接手机,找到夜神手机后连接成功就行了。如果找不到也没有关系,也可以正常连接,这时再打开HBuider开发工具,就可以了。
这是我个人的开发测试的过程,在此写出来仅供大家参考!
探索者 QQ:71804584
HBuilder调试夜神安卓模拟器方法
现在开发手机app的IDE很多,今天我就以我个人开发使用的HBuider开发工具讲一下手机app开发调试。HBuider支持真机调试,这个比较简单,只要安装好手机的驱动,把手机和电脑通过数据线连接就可以调试发布了,如果手机连接不上,可以在电脑和手机上都安装360手机助手基本上就可以了。今天重点说一下使用夜神安卓模拟器和HBuider的连接调试方法。
第一步:下载安装夜神模拟器,这个在百度搜索就可以找官网,下载 安装就可以了。
第二步:查找已经安装的夜神模拟的端口,这里说一下夜神模拟器默认端口是62001,但是有些版本可能不是这个端口,怎么查找到底是哪个端口呢?按照如下顺序进行就可以查找到你按装的夜神模拟器端口了。
1.打开夜神模拟器,确保正常启动后,出现安卓桌面。
2.打开夜神模拟器的安装文件夹,找到D:\YS\Nox\bin文件夹打开(D:\YS是夜神模拟器安装的路径根目录),找到【debugReport.bat】文件,双击启动该批处理文件。会显示如下图:
nox adb port:52001
already connected to 127.0.0.1:52001
这里面显示了夜神模拟器的adb port 端口为:52001,并且已经可以正常连接了,出现这个内容证明夜神模拟器已经正常工作了。
或者找到【debug.bat】文件,双击启动该批处理文件,也可以查看端口。
3.找到夜神模拟器的adb 端口后,打开HBuider开发工具,在【工具】-->【选项】打开界面如下图,选择【HBuider】,在最下方的" 第三方Android模拟器端口"中输入52001后【确定】就可以了,通过这些设置就可以在调试中看到夜神模拟器的选项了。
4.如果没有出现夜神模拟器的连接项,那就需要你在电脑和夜神模拟器上安装360手机助手了,安装完360手机助手后,启动360卫士和360手机助手后,在360手机助手中连接手机,找到夜神手机后连接成功就行了。如果找不到也没有关系,也可以正常连接,这时再打开HBuider开发工具,就可以了。
这是我个人的开发测试的过程,在此写出来仅供大家参考!
探索者 QQ:71804584

分享一个防止刷新的60秒后发送短信验证码
<script type="text/javascript">
mui.plusReady(function(){
countdown = localStorage.countdown;
if (countdown != "0") {
$("#action_code").html("重新发送 ( " + countdown + " )");
timedown();
}
});
function timedown(obj) {
countdown = localStorage.countdown;
if (countdown == 0) {
$("#action_code").html("获取验证码");
return clearTimeout();//清除定时,没有的话会导致后面每次减一越来越快
} else {
$("#action_code").html("重新发送 ( " + countdown + " )");
countdown = countdown-1;
localStorage.countdown=countdown;
}
setTimeout(function() {
timedown(obj);
}, 1000);//定时每秒减一
}
//手机验证,判断60s之后再发送验证码
function settime(obj) {
var phone = document.getElementById("phone");
var password = document.getElementById("password");
var password_confirm = document.getElementById("password_confirm");
var check_phone_number = /^1[3458]\d{9}$/;
if (phone.value.length == 0) {
plus.ui.toast("手机号不能为空");
return;
}
if (phone.value.length != 11) {
plus.ui.toast("请输入有效的手机号!");
return;
}
if (!phone.value.match(check_phone_number)) {
plus.ui.toast("请输入有效的手机号");
return;
} else {
//短信发送
// mui.getJSON("url", { phone: phone }, function(json){
// if(json.status == "error")
// {
// mui.toast(json.msg);
// }
// else
// {
// localStorage.setItem("msncode",json.code)
// mui.toast(json.msg);
// }
// });
localStorage.countdown="60";
timedown(obj);
}
}
</script>
<script type="text/javascript">
mui.plusReady(function(){
countdown = localStorage.countdown;
if (countdown != "0") {
$("#action_code").html("重新发送 ( " + countdown + " )");
timedown();
}
});
function timedown(obj) {
countdown = localStorage.countdown;
if (countdown == 0) {
$("#action_code").html("获取验证码");
return clearTimeout();//清除定时,没有的话会导致后面每次减一越来越快
} else {
$("#action_code").html("重新发送 ( " + countdown + " )");
countdown = countdown-1;
localStorage.countdown=countdown;
}
setTimeout(function() {
timedown(obj);
}, 1000);//定时每秒减一
}
//手机验证,判断60s之后再发送验证码
function settime(obj) {
var phone = document.getElementById("phone");
var password = document.getElementById("password");
var password_confirm = document.getElementById("password_confirm");
var check_phone_number = /^1[3458]\d{9}$/;
if (phone.value.length == 0) {
plus.ui.toast("手机号不能为空");
return;
}
if (phone.value.length != 11) {
plus.ui.toast("请输入有效的手机号!");
return;
}
if (!phone.value.match(check_phone_number)) {
plus.ui.toast("请输入有效的手机号");
return;
} else {
//短信发送
// mui.getJSON("url", { phone: phone }, function(json){
// if(json.status == "error")
// {
// mui.toast(json.msg);
// }
// else
// {
// localStorage.setItem("msncode",json.code)
// mui.toast(json.msg);
// }
// });
localStorage.countdown="60";
timedown(obj);
}
}
</script>
收起阅读 »

【示例】使用mui懒加载插件实现动态懒加载图文信息
依赖
- mui.js
- mui.lazyload.js
- mui.lazyload.img.js
在HBuilder中新建Hello mui示例工程中获取以上文件。
MUI在github上的开源地址最接近原生APP体验的高性能框架
在HBuilder中使用egit插件同步最新的MUI等资源
实现
以Hello mui示例工程中的lazyload-image.html例子为基础,做一些适当的调整即可。
初始化
初始化懒加载控件,设置占位图。注意,这里需要将autoDestroy属性设置为false(不自动销毁),以便后面继续调用。
var lazyLoad = mui('#list').imageLazyload({
placeholder: '../images/60x60.gif',
autoDestroy: false
});
加载
新增一个按钮,用于新增图文信息。点击后直接调用原有的createFragment ()方法创建n个元素,并将新的元素添加至懒加载的容器中。
<!--html部分-->
<button type="button" class="mui-btn mui-btn-blue mui-btn-block" id="get_list">加载图文信息</button>
// JavaScript部分
var list = document.getElementById('list');
document.getElementById('get_list').addEventListener('tap', function() {
list.appendChild(createFragment(5));
});
不过,这样仅仅将元素添加至容器内,是不会实现懒加载的。简单测试下,从结果可以看到,图片信息并未加载。
刷新
每次添加新的图文信息,需要刷新懒加载控件。也就是通知懒加载容器,有新的元素进来了,需要加载一下图片信息。
那么,按钮tap事件的回调函数,需要调整为以下代码:
list.appendChild(createFragment(5));
lazyLoad.refresh(true);
此时,再进行测试,就可以看到所预期的懒加载效果。
附件
修改后的lazyload-image.html,见附件lazyload-image.zip。
依赖
- mui.js
- mui.lazyload.js
- mui.lazyload.img.js
在HBuilder中新建Hello mui示例工程中获取以上文件。
MUI在github上的开源地址最接近原生APP体验的高性能框架
在HBuilder中使用egit插件同步最新的MUI等资源
实现
以Hello mui示例工程中的lazyload-image.html例子为基础,做一些适当的调整即可。
初始化
初始化懒加载控件,设置占位图。注意,这里需要将autoDestroy属性设置为false(不自动销毁),以便后面继续调用。
var lazyLoad = mui('#list').imageLazyload({
placeholder: '../images/60x60.gif',
autoDestroy: false
});
加载
新增一个按钮,用于新增图文信息。点击后直接调用原有的createFragment ()方法创建n个元素,并将新的元素添加至懒加载的容器中。
<!--html部分-->
<button type="button" class="mui-btn mui-btn-blue mui-btn-block" id="get_list">加载图文信息</button>
// JavaScript部分
var list = document.getElementById('list');
document.getElementById('get_list').addEventListener('tap', function() {
list.appendChild(createFragment(5));
});
不过,这样仅仅将元素添加至容器内,是不会实现懒加载的。简单测试下,从结果可以看到,图片信息并未加载。
刷新
每次添加新的图文信息,需要刷新懒加载控件。也就是通知懒加载容器,有新的元素进来了,需要加载一下图片信息。
那么,按钮tap事件的回调函数,需要调整为以下代码:
list.appendChild(createFragment(5));
lazyLoad.refresh(true);
此时,再进行测试,就可以看到所预期的懒加载效果。
附件
修改后的lazyload-image.html,见附件lazyload-image.zip。
收起阅读 »
解决mac上用chrome调试的跨域问题
方法一
open -a "Google Chrome" --args --disable-web-security --user-data-dir=~/ChromeUserData/
方法二
1.打开 Terminal 进入终端状态,默认的提示符应该是 $;
2.进入 Chrome.app 目录;
$ cd "/Applications/Google Chrome.app/Contents/MacOS/"
3.将原先的启动脚本改个名字;
$ sudo mv "Google Chrome" Google.real
4.使用管道操作创建新的启动脚本,注意其中加入你所需要的启动参数,这里的例子是启用内置的 Flash;
sudo printf '#!/bin/bash\ncd "/Applications/Google Chrome.app/Contents/MacOS"\n"/Applications/Google Chrome.app/Contents/MacOS/Google.real" --disable-web-security "$@"\n' > Google\ Chrome
5.给新的脚本加上运行权限;
$ sudo chmod u+x "Google Chrome”
方法一
open -a "Google Chrome" --args --disable-web-security --user-data-dir=~/ChromeUserData/
方法二
1.打开 Terminal 进入终端状态,默认的提示符应该是 $;
2.进入 Chrome.app 目录;
$ cd "/Applications/Google Chrome.app/Contents/MacOS/"
3.将原先的启动脚本改个名字;
$ sudo mv "Google Chrome" Google.real
4.使用管道操作创建新的启动脚本,注意其中加入你所需要的启动参数,这里的例子是启用内置的 Flash;
sudo printf '#!/bin/bash\ncd "/Applications/Google Chrome.app/Contents/MacOS"\n"/Applications/Google Chrome.app/Contents/MacOS/Google.real" --disable-web-security "$@"\n' > Google\ Chrome
5.给新的脚本加上运行权限;
$ sudo chmod u+x "Google Chrome”
收起阅读 »