├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── markdown-navigator.xml ├── markdown-navigator │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── androidexample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── zyj │ │ └── example │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── demo.html │ │ ├── native.js │ │ └── zepto.js │ ├── java │ │ └── com │ │ │ └── zyj │ │ │ └── example │ │ │ ├── MainActivity.java │ │ │ ├── Utils.java │ │ │ ├── entity │ │ │ ├── DeviceInfoEntity.java │ │ │ ├── JsMsgCallEntity.java │ │ │ ├── JsMsgPhotoEntity.java │ │ │ └── PhotoEntity.java │ │ │ └── iml │ │ │ ├── JsCall.java │ │ │ ├── JsDeviceInfo.java │ │ │ ├── JsSelectPhoto.java │ │ │ └── JsTakePhoto.java │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── zyj │ └── example │ └── ExampleUnitTest.java ├── build.gradle ├── demo.gif ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── js └── native.js ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── coracle │ │ └── com │ │ └── mylibrary │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── zyj │ │ │ └── hybridbridge │ │ │ ├── HandleJsMessage.java │ │ │ ├── HandleResult.java │ │ │ ├── JsAction.java │ │ │ ├── JsBridge.java │ │ │ ├── JsMessage.java │ │ │ └── RxBus.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── coracle │ └── com │ └── mylibrary │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | /gradle.properties 11 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 32 | 33 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /.idea/markdown-navigator/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HybridBridge 2 | A basic framework for Android hybrid development, bridge between JavaScript and java. 3 | ![demo](https://github.com/YouJZ/HybridBridge/blob/master/demo.gif) 4 | ## use in android 5 | 6 | ### Adding to project 7 | 8 | compile 'com.zyj:hybridbridge:0.1.0' 9 | ### step.1 10 | webView.loadUrl("you url"); 11 | JsBridge.getInstance().init(this, webView); 12 | ### step.2 13 | public class JsDeviceInfo extends JsAction { 14 | public static final String ACTION = "deviceinfo"; 15 | @Override 16 | protected void handleAction(Activity context, String jsonStr) { 17 | HandleResult resultEntity =new HandleResult(); 18 | ... 19 | resultEntity.setData(...); 20 | RxBus.getInstance().post(resultEntity); 21 | } 22 | } 23 | JsBridge.getInstance().addJsAction(JsDeviceInfo.ACTION, JsDeviceInfo.class); 24 | ### step.3 25 | JsBridge.getInstance().destroy(); 26 | ## use in js 27 | ### step.1 28 | window.nativeCallback = function(data) {...} 29 | ### step.2 30 | var Senddata={ 31 | action:"deviceinfo", 32 | callback:"nativeCallback", 33 | data:data, 34 | } 35 | ### step.3 36 | window.native.sendMessage(sendDataStr); 37 | -------------------------------------------------------------------------------- /androidexample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /androidexample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "24.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.zyj.example" 9 | minSdkVersion 14 10 | targetSdkVersion 24 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | compile fileTree(include: ['*.jar'], dir: 'libs') 27 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 28 | exclude group: 'com.android.support', module: 'support-annotations' 29 | }) 30 | compile 'com.android.support:appcompat-v7:24.2.1' 31 | testCompile 'junit:junit:4.12' 32 | compile project(':library') 33 | } 34 | -------------------------------------------------------------------------------- /androidexample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/zhaoyoujun/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /androidexample/src/androidTest/java/com/zyj/example/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.zyj.example; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.zyj.example", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /androidexample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /androidexample/src/main/assets/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JsBridge Dome 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 31 |
32 | 33 | 34 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /androidexample/src/main/assets/native.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /////////////////////////////// 4 | // 调用原生接口 // 5 | /////////////////////////////// 6 | ;(function($) { 7 | "use strict"//使用严格模式 8 | function native(params) { 9 | params = params||{}; 10 | if (params==="undefind")return; 11 | if (params.action==="undefind")return; 12 | //固定的三个属性和native端一样,否则native端和解析出错 13 | var Senddata={ 14 | action:params.action, 15 | callback:"nativeCallback", 16 | data:params.data, 17 | } 18 | window.nativeCallback = function(data) { 19 | if (params.callback!=="undefind") { 20 | params.callback(data); 21 | } 22 | } 23 | var sendDataStr=JSON.stringify(Senddata); 24 | window.native.sendMessage(sendDataStr); 25 | } 26 | $.native = native; 27 | })($); 28 | -------------------------------------------------------------------------------- /androidexample/src/main/assets/zepto.js: -------------------------------------------------------------------------------- 1 | /* Zepto v1.2.0 - zepto event ajax form ie - zeptojs.com/license */ 2 | (function(global, factory) { 3 | if (typeof define === 'function' && define.amd) 4 | define(function() { return factory(global) }) 5 | else 6 | factory(global) 7 | }(this, function(window) { 8 | var Zepto = (function() { 9 | var undefined, key, $, classList, emptyArray = [], concat = emptyArray.concat, filter = emptyArray.filter, slice = emptyArray.slice, 10 | document = window.document, 11 | elementDisplay = {}, classCache = {}, 12 | cssNumber = { 'column-count': 1, 'columns': 1, 'font-weight': 1, 'line-height': 1,'opacity': 1, 'z-index': 1, 'zoom': 1 }, 13 | fragmentRE = /^\s*<(\w+|!)[^>]*>/, 14 | singleTagRE = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, 15 | tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, 16 | rootNodeRE = /^(?:body|html)$/i, 17 | capitalRE = /([A-Z])/g, 18 | 19 | // special attributes that should be get/set via method calls 20 | methodAttributes = ['val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset'], 21 | 22 | adjacencyOperators = [ 'after', 'prepend', 'before', 'append' ], 23 | table = document.createElement('table'), 24 | tableRow = document.createElement('tr'), 25 | containers = { 26 | 'tr': document.createElement('tbody'), 27 | 'tbody': table, 'thead': table, 'tfoot': table, 28 | 'td': tableRow, 'th': tableRow, 29 | '*': document.createElement('div') 30 | }, 31 | readyRE = /complete|loaded|interactive/, 32 | simpleSelectorRE = /^[\w-]*$/, 33 | class2type = {}, 34 | toString = class2type.toString, 35 | zepto = {}, 36 | camelize, uniq, 37 | tempParent = document.createElement('div'), 38 | propMap = { 39 | 'tabindex': 'tabIndex', 40 | 'readonly': 'readOnly', 41 | 'for': 'htmlFor', 42 | 'class': 'className', 43 | 'maxlength': 'maxLength', 44 | 'cellspacing': 'cellSpacing', 45 | 'cellpadding': 'cellPadding', 46 | 'rowspan': 'rowSpan', 47 | 'colspan': 'colSpan', 48 | 'usemap': 'useMap', 49 | 'frameborder': 'frameBorder', 50 | 'contenteditable': 'contentEditable' 51 | }, 52 | isArray = Array.isArray || 53 | function(object){ return object instanceof Array } 54 | 55 | zepto.matches = function(element, selector) { 56 | if (!selector || !element || element.nodeType !== 1) return false 57 | var matchesSelector = element.matches || element.webkitMatchesSelector || 58 | element.mozMatchesSelector || element.oMatchesSelector || 59 | element.matchesSelector 60 | if (matchesSelector) return matchesSelector.call(element, selector) 61 | // fall back to performing a selector: 62 | var match, parent = element.parentNode, temp = !parent 63 | if (temp) (parent = tempParent).appendChild(element) 64 | match = ~zepto.qsa(parent, selector).indexOf(element) 65 | temp && tempParent.removeChild(element) 66 | return match 67 | } 68 | 69 | function type(obj) { 70 | return obj == null ? String(obj) : 71 | class2type[toString.call(obj)] || "object" 72 | } 73 | 74 | function isFunction(value) { return type(value) == "function" } 75 | function isWindow(obj) { return obj != null && obj == obj.window } 76 | function isDocument(obj) { return obj != null && obj.nodeType == obj.DOCUMENT_NODE } 77 | function isObject(obj) { return type(obj) == "object" } 78 | function isPlainObject(obj) { 79 | return isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype 80 | } 81 | 82 | function likeArray(obj) { 83 | var length = !!obj && 'length' in obj && obj.length, 84 | type = $.type(obj) 85 | 86 | return 'function' != type && !isWindow(obj) && ( 87 | 'array' == type || length === 0 || 88 | (typeof length == 'number' && length > 0 && (length - 1) in obj) 89 | ) 90 | } 91 | 92 | function compact(array) { return filter.call(array, function(item){ return item != null }) } 93 | function flatten(array) { return array.length > 0 ? $.fn.concat.apply([], array) : array } 94 | camelize = function(str){ return str.replace(/-+(.)?/g, function(match, chr){ return chr ? chr.toUpperCase() : '' }) } 95 | function dasherize(str) { 96 | return str.replace(/::/g, '/') 97 | .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') 98 | .replace(/([a-z\d])([A-Z])/g, '$1_$2') 99 | .replace(/_/g, '-') 100 | .toLowerCase() 101 | } 102 | uniq = function(array){ return filter.call(array, function(item, idx){ return array.indexOf(item) == idx }) } 103 | 104 | function classRE(name) { 105 | return name in classCache ? 106 | classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)')) 107 | } 108 | 109 | function maybeAddPx(name, value) { 110 | return (typeof value == "number" && !cssNumber[dasherize(name)]) ? value + "px" : value 111 | } 112 | 113 | function defaultDisplay(nodeName) { 114 | var element, display 115 | if (!elementDisplay[nodeName]) { 116 | element = document.createElement(nodeName) 117 | document.body.appendChild(element) 118 | display = getComputedStyle(element, '').getPropertyValue("display") 119 | element.parentNode.removeChild(element) 120 | display == "none" && (display = "block") 121 | elementDisplay[nodeName] = display 122 | } 123 | return elementDisplay[nodeName] 124 | } 125 | 126 | function children(element) { 127 | return 'children' in element ? 128 | slice.call(element.children) : 129 | $.map(element.childNodes, function(node){ if (node.nodeType == 1) return node }) 130 | } 131 | 132 | function Z(dom, selector) { 133 | var i, len = dom ? dom.length : 0 134 | for (i = 0; i < len; i++) this[i] = dom[i] 135 | this.length = len 136 | this.selector = selector || '' 137 | } 138 | 139 | // `$.zepto.fragment` takes a html string and an optional tag name 140 | // to generate DOM nodes from the given html string. 141 | // The generated DOM nodes are returned as an array. 142 | // This function can be overridden in plugins for example to make 143 | // it compatible with browsers that don't support the DOM fully. 144 | zepto.fragment = function(html, name, properties) { 145 | var dom, nodes, container 146 | 147 | // A special case optimization for a single tag 148 | if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1)) 149 | 150 | if (!dom) { 151 | if (html.replace) html = html.replace(tagExpanderRE, "<$1>") 152 | if (name === undefined) name = fragmentRE.test(html) && RegExp.$1 153 | if (!(name in containers)) name = '*' 154 | 155 | container = containers[name] 156 | container.innerHTML = '' + html 157 | dom = $.each(slice.call(container.childNodes), function(){ 158 | container.removeChild(this) 159 | }) 160 | } 161 | 162 | if (isPlainObject(properties)) { 163 | nodes = $(dom) 164 | $.each(properties, function(key, value) { 165 | if (methodAttributes.indexOf(key) > -1) nodes[key](value) 166 | else nodes.attr(key, value) 167 | }) 168 | } 169 | 170 | return dom 171 | } 172 | 173 | // `$.zepto.Z` swaps out the prototype of the given `dom` array 174 | // of nodes with `$.fn` and thus supplying all the Zepto functions 175 | // to the array. This method can be overridden in plugins. 176 | zepto.Z = function(dom, selector) { 177 | return new Z(dom, selector) 178 | } 179 | 180 | // `$.zepto.isZ` should return `true` if the given object is a Zepto 181 | // collection. This method can be overridden in plugins. 182 | zepto.isZ = function(object) { 183 | return object instanceof zepto.Z 184 | } 185 | 186 | // `$.zepto.init` is Zepto's counterpart to jQuery's `$.fn.init` and 187 | // takes a CSS selector and an optional context (and handles various 188 | // special cases). 189 | // This method can be overridden in plugins. 190 | zepto.init = function(selector, context) { 191 | var dom 192 | // If nothing given, return an empty Zepto collection 193 | if (!selector) return zepto.Z() 194 | // Optimize for string selectors 195 | else if (typeof selector == 'string') { 196 | selector = selector.trim() 197 | // If it's a html fragment, create nodes from it 198 | // Note: In both Chrome 21 and Firefox 15, DOM error 12 199 | // is thrown if the fragment doesn't begin with < 200 | if (selector[0] == '<' && fragmentRE.test(selector)) 201 | dom = zepto.fragment(selector, RegExp.$1, context), selector = null 202 | // If there's a context, create a collection on that context first, and select 203 | // nodes from there 204 | else if (context !== undefined) return $(context).find(selector) 205 | // If it's a CSS selector, use it to select nodes. 206 | else dom = zepto.qsa(document, selector) 207 | } 208 | // If a function is given, call it when the DOM is ready 209 | else if (isFunction(selector)) return $(document).ready(selector) 210 | // If a Zepto collection is given, just return it 211 | else if (zepto.isZ(selector)) return selector 212 | else { 213 | // normalize array if an array of nodes is given 214 | if (isArray(selector)) dom = compact(selector) 215 | // Wrap DOM nodes. 216 | else if (isObject(selector)) 217 | dom = [selector], selector = null 218 | // If it's a html fragment, create nodes from it 219 | else if (fragmentRE.test(selector)) 220 | dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null 221 | // If there's a context, create a collection on that context first, and select 222 | // nodes from there 223 | else if (context !== undefined) return $(context).find(selector) 224 | // And last but no least, if it's a CSS selector, use it to select nodes. 225 | else dom = zepto.qsa(document, selector) 226 | } 227 | // create a new Zepto collection from the nodes found 228 | return zepto.Z(dom, selector) 229 | } 230 | 231 | // `$` will be the base `Zepto` object. When calling this 232 | // function just call `$.zepto.init, which makes the implementation 233 | // details of selecting nodes and creating Zepto collections 234 | // patchable in plugins. 235 | $ = function(selector, context){ 236 | return zepto.init(selector, context) 237 | } 238 | 239 | function extend(target, source, deep) { 240 | for (key in source) 241 | if (deep && (isPlainObject(source[key]) || isArray(source[key]))) { 242 | if (isPlainObject(source[key]) && !isPlainObject(target[key])) 243 | target[key] = {} 244 | if (isArray(source[key]) && !isArray(target[key])) 245 | target[key] = [] 246 | extend(target[key], source[key], deep) 247 | } 248 | else if (source[key] !== undefined) target[key] = source[key] 249 | } 250 | 251 | // Copy all but undefined properties from one or more 252 | // objects to the `target` object. 253 | $.extend = function(target){ 254 | var deep, args = slice.call(arguments, 1) 255 | if (typeof target == 'boolean') { 256 | deep = target 257 | target = args.shift() 258 | } 259 | args.forEach(function(arg){ extend(target, arg, deep) }) 260 | return target 261 | } 262 | 263 | // `$.zepto.qsa` is Zepto's CSS selector implementation which 264 | // uses `document.querySelectorAll` and optimizes for some special cases, like `#id`. 265 | // This method can be overridden in plugins. 266 | zepto.qsa = function(element, selector){ 267 | var found, 268 | maybeID = selector[0] == '#', 269 | maybeClass = !maybeID && selector[0] == '.', 270 | nameOnly = maybeID || maybeClass ? selector.slice(1) : selector, // Ensure that a 1 char tag name still gets checked 271 | isSimple = simpleSelectorRE.test(nameOnly) 272 | return (element.getElementById && isSimple && maybeID) ? // Safari DocumentFragment doesn't have getElementById 273 | ( (found = element.getElementById(nameOnly)) ? [found] : [] ) : 274 | (element.nodeType !== 1 && element.nodeType !== 9 && element.nodeType !== 11) ? [] : 275 | slice.call( 276 | isSimple && !maybeID && element.getElementsByClassName ? // DocumentFragment doesn't have getElementsByClassName/TagName 277 | maybeClass ? element.getElementsByClassName(nameOnly) : // If it's simple, it could be a class 278 | element.getElementsByTagName(selector) : // Or a tag 279 | element.querySelectorAll(selector) // Or it's not simple, and we need to query all 280 | ) 281 | } 282 | 283 | function filtered(nodes, selector) { 284 | return selector == null ? $(nodes) : $(nodes).filter(selector) 285 | } 286 | 287 | $.contains = document.documentElement.contains ? 288 | function(parent, node) { 289 | return parent !== node && parent.contains(node) 290 | } : 291 | function(parent, node) { 292 | while (node && (node = node.parentNode)) 293 | if (node === parent) return true 294 | return false 295 | } 296 | 297 | function funcArg(context, arg, idx, payload) { 298 | return isFunction(arg) ? arg.call(context, idx, payload) : arg 299 | } 300 | 301 | function setAttribute(node, name, value) { 302 | value == null ? node.removeAttribute(name) : node.setAttribute(name, value) 303 | } 304 | 305 | // access className property while respecting SVGAnimatedString 306 | function className(node, value){ 307 | var klass = node.className || '', 308 | svg = klass && klass.baseVal !== undefined 309 | 310 | if (value === undefined) return svg ? klass.baseVal : klass 311 | svg ? (klass.baseVal = value) : (node.className = value) 312 | } 313 | 314 | // "true" => true 315 | // "false" => false 316 | // "null" => null 317 | // "42" => 42 318 | // "42.5" => 42.5 319 | // "08" => "08" 320 | // JSON => parse if valid 321 | // String => self 322 | function deserializeValue(value) { 323 | try { 324 | return value ? 325 | value == "true" || 326 | ( value == "false" ? false : 327 | value == "null" ? null : 328 | +value + "" == value ? +value : 329 | /^[\[\{]/.test(value) ? $.parseJSON(value) : 330 | value ) 331 | : value 332 | } catch(e) { 333 | return value 334 | } 335 | } 336 | 337 | $.type = type 338 | $.isFunction = isFunction 339 | $.isWindow = isWindow 340 | $.isArray = isArray 341 | $.isPlainObject = isPlainObject 342 | 343 | $.isEmptyObject = function(obj) { 344 | var name 345 | for (name in obj) return false 346 | return true 347 | } 348 | 349 | $.isNumeric = function(val) { 350 | var num = Number(val), type = typeof val 351 | return val != null && type != 'boolean' && 352 | (type != 'string' || val.length) && 353 | !isNaN(num) && isFinite(num) || false 354 | } 355 | 356 | $.inArray = function(elem, array, i){ 357 | return emptyArray.indexOf.call(array, elem, i) 358 | } 359 | 360 | $.camelCase = camelize 361 | $.trim = function(str) { 362 | return str == null ? "" : String.prototype.trim.call(str) 363 | } 364 | 365 | // plugin compatibility 366 | $.uuid = 0 367 | $.support = { } 368 | $.expr = { } 369 | $.noop = function() {} 370 | 371 | $.map = function(elements, callback){ 372 | var value, values = [], i, key 373 | if (likeArray(elements)) 374 | for (i = 0; i < elements.length; i++) { 375 | value = callback(elements[i], i) 376 | if (value != null) values.push(value) 377 | } 378 | else 379 | for (key in elements) { 380 | value = callback(elements[key], key) 381 | if (value != null) values.push(value) 382 | } 383 | return flatten(values) 384 | } 385 | 386 | $.each = function(elements, callback){ 387 | var i, key 388 | if (likeArray(elements)) { 389 | for (i = 0; i < elements.length; i++) 390 | if (callback.call(elements[i], i, elements[i]) === false) return elements 391 | } else { 392 | for (key in elements) 393 | if (callback.call(elements[key], key, elements[key]) === false) return elements 394 | } 395 | 396 | return elements 397 | } 398 | 399 | $.grep = function(elements, callback){ 400 | return filter.call(elements, callback) 401 | } 402 | 403 | if (window.JSON) $.parseJSON = JSON.parse 404 | 405 | // Populate the class2type map 406 | $.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { 407 | class2type[ "[object " + name + "]" ] = name.toLowerCase() 408 | }) 409 | 410 | // Define methods that will be available on all 411 | // Zepto collections 412 | $.fn = { 413 | constructor: zepto.Z, 414 | length: 0, 415 | 416 | // Because a collection acts like an array 417 | // copy over these useful array functions. 418 | forEach: emptyArray.forEach, 419 | reduce: emptyArray.reduce, 420 | push: emptyArray.push, 421 | sort: emptyArray.sort, 422 | splice: emptyArray.splice, 423 | indexOf: emptyArray.indexOf, 424 | concat: function(){ 425 | var i, value, args = [] 426 | for (i = 0; i < arguments.length; i++) { 427 | value = arguments[i] 428 | args[i] = zepto.isZ(value) ? value.toArray() : value 429 | } 430 | return concat.apply(zepto.isZ(this) ? this.toArray() : this, args) 431 | }, 432 | 433 | // `map` and `slice` in the jQuery API work differently 434 | // from their array counterparts 435 | map: function(fn){ 436 | return $($.map(this, function(el, i){ return fn.call(el, i, el) })) 437 | }, 438 | slice: function(){ 439 | return $(slice.apply(this, arguments)) 440 | }, 441 | 442 | ready: function(callback){ 443 | // need to check if document.body exists for IE as that browser reports 444 | // document ready when it hasn't yet created the body element 445 | if (readyRE.test(document.readyState) && document.body) callback($) 446 | else document.addEventListener('DOMContentLoaded', function(){ callback($) }, false) 447 | return this 448 | }, 449 | get: function(idx){ 450 | return idx === undefined ? slice.call(this) : this[idx >= 0 ? idx : idx + this.length] 451 | }, 452 | toArray: function(){ return this.get() }, 453 | size: function(){ 454 | return this.length 455 | }, 456 | remove: function(){ 457 | return this.each(function(){ 458 | if (this.parentNode != null) 459 | this.parentNode.removeChild(this) 460 | }) 461 | }, 462 | each: function(callback){ 463 | emptyArray.every.call(this, function(el, idx){ 464 | return callback.call(el, idx, el) !== false 465 | }) 466 | return this 467 | }, 468 | filter: function(selector){ 469 | if (isFunction(selector)) return this.not(this.not(selector)) 470 | return $(filter.call(this, function(element){ 471 | return zepto.matches(element, selector) 472 | })) 473 | }, 474 | add: function(selector,context){ 475 | return $(uniq(this.concat($(selector,context)))) 476 | }, 477 | is: function(selector){ 478 | return this.length > 0 && zepto.matches(this[0], selector) 479 | }, 480 | not: function(selector){ 481 | var nodes=[] 482 | if (isFunction(selector) && selector.call !== undefined) 483 | this.each(function(idx){ 484 | if (!selector.call(this,idx)) nodes.push(this) 485 | }) 486 | else { 487 | var excludes = typeof selector == 'string' ? this.filter(selector) : 488 | (likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector) 489 | this.forEach(function(el){ 490 | if (excludes.indexOf(el) < 0) nodes.push(el) 491 | }) 492 | } 493 | return $(nodes) 494 | }, 495 | has: function(selector){ 496 | return this.filter(function(){ 497 | return isObject(selector) ? 498 | $.contains(this, selector) : 499 | $(this).find(selector).size() 500 | }) 501 | }, 502 | eq: function(idx){ 503 | return idx === -1 ? this.slice(idx) : this.slice(idx, + idx + 1) 504 | }, 505 | first: function(){ 506 | var el = this[0] 507 | return el && !isObject(el) ? el : $(el) 508 | }, 509 | last: function(){ 510 | var el = this[this.length - 1] 511 | return el && !isObject(el) ? el : $(el) 512 | }, 513 | find: function(selector){ 514 | var result, $this = this 515 | if (!selector) result = $() 516 | else if (typeof selector == 'object') 517 | result = $(selector).filter(function(){ 518 | var node = this 519 | return emptyArray.some.call($this, function(parent){ 520 | return $.contains(parent, node) 521 | }) 522 | }) 523 | else if (this.length == 1) result = $(zepto.qsa(this[0], selector)) 524 | else result = this.map(function(){ return zepto.qsa(this, selector) }) 525 | return result 526 | }, 527 | closest: function(selector, context){ 528 | var nodes = [], collection = typeof selector == 'object' && $(selector) 529 | this.each(function(_, node){ 530 | while (node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector))) 531 | node = node !== context && !isDocument(node) && node.parentNode 532 | if (node && nodes.indexOf(node) < 0) nodes.push(node) 533 | }) 534 | return $(nodes) 535 | }, 536 | parents: function(selector){ 537 | var ancestors = [], nodes = this 538 | while (nodes.length > 0) 539 | nodes = $.map(nodes, function(node){ 540 | if ((node = node.parentNode) && !isDocument(node) && ancestors.indexOf(node) < 0) { 541 | ancestors.push(node) 542 | return node 543 | } 544 | }) 545 | return filtered(ancestors, selector) 546 | }, 547 | parent: function(selector){ 548 | return filtered(uniq(this.pluck('parentNode')), selector) 549 | }, 550 | children: function(selector){ 551 | return filtered(this.map(function(){ return children(this) }), selector) 552 | }, 553 | contents: function() { 554 | return this.map(function() { return this.contentDocument || slice.call(this.childNodes) }) 555 | }, 556 | siblings: function(selector){ 557 | return filtered(this.map(function(i, el){ 558 | return filter.call(children(el.parentNode), function(child){ return child!==el }) 559 | }), selector) 560 | }, 561 | empty: function(){ 562 | return this.each(function(){ this.innerHTML = '' }) 563 | }, 564 | // `pluck` is borrowed from Prototype.js 565 | pluck: function(property){ 566 | return $.map(this, function(el){ return el[property] }) 567 | }, 568 | show: function(){ 569 | return this.each(function(){ 570 | this.style.display == "none" && (this.style.display = '') 571 | if (getComputedStyle(this, '').getPropertyValue("display") == "none") 572 | this.style.display = defaultDisplay(this.nodeName) 573 | }) 574 | }, 575 | replaceWith: function(newContent){ 576 | return this.before(newContent).remove() 577 | }, 578 | wrap: function(structure){ 579 | var func = isFunction(structure) 580 | if (this[0] && !func) 581 | var dom = $(structure).get(0), 582 | clone = dom.parentNode || this.length > 1 583 | 584 | return this.each(function(index){ 585 | $(this).wrapAll( 586 | func ? structure.call(this, index) : 587 | clone ? dom.cloneNode(true) : dom 588 | ) 589 | }) 590 | }, 591 | wrapAll: function(structure){ 592 | if (this[0]) { 593 | $(this[0]).before(structure = $(structure)) 594 | var children 595 | // drill down to the inmost element 596 | while ((children = structure.children()).length) structure = children.first() 597 | $(structure).append(this) 598 | } 599 | return this 600 | }, 601 | wrapInner: function(structure){ 602 | var func = isFunction(structure) 603 | return this.each(function(index){ 604 | var self = $(this), contents = self.contents(), 605 | dom = func ? structure.call(this, index) : structure 606 | contents.length ? contents.wrapAll(dom) : self.append(dom) 607 | }) 608 | }, 609 | unwrap: function(){ 610 | this.parent().each(function(){ 611 | $(this).replaceWith($(this).children()) 612 | }) 613 | return this 614 | }, 615 | clone: function(){ 616 | return this.map(function(){ return this.cloneNode(true) }) 617 | }, 618 | hide: function(){ 619 | return this.css("display", "none") 620 | }, 621 | toggle: function(setting){ 622 | return this.each(function(){ 623 | var el = $(this) 624 | ;(setting === undefined ? el.css("display") == "none" : setting) ? el.show() : el.hide() 625 | }) 626 | }, 627 | prev: function(selector){ return $(this.pluck('previousElementSibling')).filter(selector || '*') }, 628 | next: function(selector){ return $(this.pluck('nextElementSibling')).filter(selector || '*') }, 629 | html: function(html){ 630 | return 0 in arguments ? 631 | this.each(function(idx){ 632 | var originHtml = this.innerHTML 633 | $(this).empty().append( funcArg(this, html, idx, originHtml) ) 634 | }) : 635 | (0 in this ? this[0].innerHTML : null) 636 | }, 637 | text: function(text){ 638 | return 0 in arguments ? 639 | this.each(function(idx){ 640 | var newText = funcArg(this, text, idx, this.textContent) 641 | this.textContent = newText == null ? '' : ''+newText 642 | }) : 643 | (0 in this ? this.pluck('textContent').join("") : null) 644 | }, 645 | attr: function(name, value){ 646 | var result 647 | return (typeof name == 'string' && !(1 in arguments)) ? 648 | (0 in this && this[0].nodeType == 1 && (result = this[0].getAttribute(name)) != null ? result : undefined) : 649 | this.each(function(idx){ 650 | if (this.nodeType !== 1) return 651 | if (isObject(name)) for (key in name) setAttribute(this, key, name[key]) 652 | else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name))) 653 | }) 654 | }, 655 | removeAttr: function(name){ 656 | return this.each(function(){ this.nodeType === 1 && name.split(' ').forEach(function(attribute){ 657 | setAttribute(this, attribute) 658 | }, this)}) 659 | }, 660 | prop: function(name, value){ 661 | name = propMap[name] || name 662 | return (1 in arguments) ? 663 | this.each(function(idx){ 664 | this[name] = funcArg(this, value, idx, this[name]) 665 | }) : 666 | (this[0] && this[0][name]) 667 | }, 668 | removeProp: function(name){ 669 | name = propMap[name] || name 670 | return this.each(function(){ delete this[name] }) 671 | }, 672 | data: function(name, value){ 673 | var attrName = 'data-' + name.replace(capitalRE, '-$1').toLowerCase() 674 | 675 | var data = (1 in arguments) ? 676 | this.attr(attrName, value) : 677 | this.attr(attrName) 678 | 679 | return data !== null ? deserializeValue(data) : undefined 680 | }, 681 | val: function(value){ 682 | if (0 in arguments) { 683 | if (value == null) value = "" 684 | return this.each(function(idx){ 685 | this.value = funcArg(this, value, idx, this.value) 686 | }) 687 | } else { 688 | return this[0] && (this[0].multiple ? 689 | $(this[0]).find('option').filter(function(){ return this.selected }).pluck('value') : 690 | this[0].value) 691 | } 692 | }, 693 | offset: function(coordinates){ 694 | if (coordinates) return this.each(function(index){ 695 | var $this = $(this), 696 | coords = funcArg(this, coordinates, index, $this.offset()), 697 | parentOffset = $this.offsetParent().offset(), 698 | props = { 699 | top: coords.top - parentOffset.top, 700 | left: coords.left - parentOffset.left 701 | } 702 | 703 | if ($this.css('position') == 'static') props['position'] = 'relative' 704 | $this.css(props) 705 | }) 706 | if (!this.length) return null 707 | if (document.documentElement !== this[0] && !$.contains(document.documentElement, this[0])) 708 | return {top: 0, left: 0} 709 | var obj = this[0].getBoundingClientRect() 710 | return { 711 | left: obj.left + window.pageXOffset, 712 | top: obj.top + window.pageYOffset, 713 | width: Math.round(obj.width), 714 | height: Math.round(obj.height) 715 | } 716 | }, 717 | css: function(property, value){ 718 | if (arguments.length < 2) { 719 | var element = this[0] 720 | if (typeof property == 'string') { 721 | if (!element) return 722 | return element.style[camelize(property)] || getComputedStyle(element, '').getPropertyValue(property) 723 | } else if (isArray(property)) { 724 | if (!element) return 725 | var props = {} 726 | var computedStyle = getComputedStyle(element, '') 727 | $.each(property, function(_, prop){ 728 | props[prop] = (element.style[camelize(prop)] || computedStyle.getPropertyValue(prop)) 729 | }) 730 | return props 731 | } 732 | } 733 | 734 | var css = '' 735 | if (type(property) == 'string') { 736 | if (!value && value !== 0) 737 | this.each(function(){ this.style.removeProperty(dasherize(property)) }) 738 | else 739 | css = dasherize(property) + ":" + maybeAddPx(property, value) 740 | } else { 741 | for (key in property) 742 | if (!property[key] && property[key] !== 0) 743 | this.each(function(){ this.style.removeProperty(dasherize(key)) }) 744 | else 745 | css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';' 746 | } 747 | 748 | return this.each(function(){ this.style.cssText += ';' + css }) 749 | }, 750 | index: function(element){ 751 | return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0]) 752 | }, 753 | hasClass: function(name){ 754 | if (!name) return false 755 | return emptyArray.some.call(this, function(el){ 756 | return this.test(className(el)) 757 | }, classRE(name)) 758 | }, 759 | addClass: function(name){ 760 | if (!name) return this 761 | return this.each(function(idx){ 762 | if (!('className' in this)) return 763 | classList = [] 764 | var cls = className(this), newName = funcArg(this, name, idx, cls) 765 | newName.split(/\s+/g).forEach(function(klass){ 766 | if (!$(this).hasClass(klass)) classList.push(klass) 767 | }, this) 768 | classList.length && className(this, cls + (cls ? " " : "") + classList.join(" ")) 769 | }) 770 | }, 771 | removeClass: function(name){ 772 | return this.each(function(idx){ 773 | if (!('className' in this)) return 774 | if (name === undefined) return className(this, '') 775 | classList = className(this) 776 | funcArg(this, name, idx, classList).split(/\s+/g).forEach(function(klass){ 777 | classList = classList.replace(classRE(klass), " ") 778 | }) 779 | className(this, classList.trim()) 780 | }) 781 | }, 782 | toggleClass: function(name, when){ 783 | if (!name) return this 784 | return this.each(function(idx){ 785 | var $this = $(this), names = funcArg(this, name, idx, className(this)) 786 | names.split(/\s+/g).forEach(function(klass){ 787 | (when === undefined ? !$this.hasClass(klass) : when) ? 788 | $this.addClass(klass) : $this.removeClass(klass) 789 | }) 790 | }) 791 | }, 792 | scrollTop: function(value){ 793 | if (!this.length) return 794 | var hasScrollTop = 'scrollTop' in this[0] 795 | if (value === undefined) return hasScrollTop ? this[0].scrollTop : this[0].pageYOffset 796 | return this.each(hasScrollTop ? 797 | function(){ this.scrollTop = value } : 798 | function(){ this.scrollTo(this.scrollX, value) }) 799 | }, 800 | scrollLeft: function(value){ 801 | if (!this.length) return 802 | var hasScrollLeft = 'scrollLeft' in this[0] 803 | if (value === undefined) return hasScrollLeft ? this[0].scrollLeft : this[0].pageXOffset 804 | return this.each(hasScrollLeft ? 805 | function(){ this.scrollLeft = value } : 806 | function(){ this.scrollTo(value, this.scrollY) }) 807 | }, 808 | position: function() { 809 | if (!this.length) return 810 | 811 | var elem = this[0], 812 | // Get *real* offsetParent 813 | offsetParent = this.offsetParent(), 814 | // Get correct offsets 815 | offset = this.offset(), 816 | parentOffset = rootNodeRE.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset() 817 | 818 | // Subtract element margins 819 | // note: when an element has margin: auto the offsetLeft and marginLeft 820 | // are the same in Safari causing offset.left to incorrectly be 0 821 | offset.top -= parseFloat( $(elem).css('margin-top') ) || 0 822 | offset.left -= parseFloat( $(elem).css('margin-left') ) || 0 823 | 824 | // Add offsetParent borders 825 | parentOffset.top += parseFloat( $(offsetParent[0]).css('border-top-width') ) || 0 826 | parentOffset.left += parseFloat( $(offsetParent[0]).css('border-left-width') ) || 0 827 | 828 | // Subtract the two offsets 829 | return { 830 | top: offset.top - parentOffset.top, 831 | left: offset.left - parentOffset.left 832 | } 833 | }, 834 | offsetParent: function() { 835 | return this.map(function(){ 836 | var parent = this.offsetParent || document.body 837 | while (parent && !rootNodeRE.test(parent.nodeName) && $(parent).css("position") == "static") 838 | parent = parent.offsetParent 839 | return parent 840 | }) 841 | } 842 | } 843 | 844 | // for now 845 | $.fn.detach = $.fn.remove 846 | 847 | // Generate the `width` and `height` functions 848 | ;['width', 'height'].forEach(function(dimension){ 849 | var dimensionProperty = 850 | dimension.replace(/./, function(m){ return m[0].toUpperCase() }) 851 | 852 | $.fn[dimension] = function(value){ 853 | var offset, el = this[0] 854 | if (value === undefined) return isWindow(el) ? el['inner' + dimensionProperty] : 855 | isDocument(el) ? el.documentElement['scroll' + dimensionProperty] : 856 | (offset = this.offset()) && offset[dimension] 857 | else return this.each(function(idx){ 858 | el = $(this) 859 | el.css(dimension, funcArg(this, value, idx, el[dimension]())) 860 | }) 861 | } 862 | }) 863 | 864 | function traverseNode(node, fun) { 865 | fun(node) 866 | for (var i = 0, len = node.childNodes.length; i < len; i++) 867 | traverseNode(node.childNodes[i], fun) 868 | } 869 | 870 | // Generate the `after`, `prepend`, `before`, `append`, 871 | // `insertAfter`, `insertBefore`, `appendTo`, and `prependTo` methods. 872 | adjacencyOperators.forEach(function(operator, operatorIndex) { 873 | var inside = operatorIndex % 2 //=> prepend, append 874 | 875 | $.fn[operator] = function(){ 876 | // arguments can be nodes, arrays of nodes, Zepto objects and HTML strings 877 | var argType, nodes = $.map(arguments, function(arg) { 878 | var arr = [] 879 | argType = type(arg) 880 | if (argType == "array") { 881 | arg.forEach(function(el) { 882 | if (el.nodeType !== undefined) return arr.push(el) 883 | else if ($.zepto.isZ(el)) return arr = arr.concat(el.get()) 884 | arr = arr.concat(zepto.fragment(el)) 885 | }) 886 | return arr 887 | } 888 | return argType == "object" || arg == null ? 889 | arg : zepto.fragment(arg) 890 | }), 891 | parent, copyByClone = this.length > 1 892 | if (nodes.length < 1) return this 893 | 894 | return this.each(function(_, target){ 895 | parent = inside ? target : target.parentNode 896 | 897 | // convert all methods to a "before" operation 898 | target = operatorIndex == 0 ? target.nextSibling : 899 | operatorIndex == 1 ? target.firstChild : 900 | operatorIndex == 2 ? target : 901 | null 902 | 903 | var parentInDocument = $.contains(document.documentElement, parent) 904 | 905 | nodes.forEach(function(node){ 906 | if (copyByClone) node = node.cloneNode(true) 907 | else if (!parent) return $(node).remove() 908 | 909 | parent.insertBefore(node, target) 910 | if (parentInDocument) traverseNode(node, function(el){ 911 | if (el.nodeName != null && el.nodeName.toUpperCase() === 'SCRIPT' && 912 | (!el.type || el.type === 'text/javascript') && !el.src){ 913 | var target = el.ownerDocument ? el.ownerDocument.defaultView : window 914 | target['eval'].call(target, el.innerHTML) 915 | } 916 | }) 917 | }) 918 | }) 919 | } 920 | 921 | // after => insertAfter 922 | // prepend => prependTo 923 | // before => insertBefore 924 | // append => appendTo 925 | $.fn[inside ? operator+'To' : 'insert'+(operatorIndex ? 'Before' : 'After')] = function(html){ 926 | $(html)[operator](this) 927 | return this 928 | } 929 | }) 930 | 931 | zepto.Z.prototype = Z.prototype = $.fn 932 | 933 | // Export internal API functions in the `$.zepto` namespace 934 | zepto.uniq = uniq 935 | zepto.deserializeValue = deserializeValue 936 | $.zepto = zepto 937 | 938 | return $ 939 | })() 940 | 941 | window.Zepto = Zepto 942 | window.$ === undefined && (window.$ = Zepto) 943 | 944 | ;(function($){ 945 | var _zid = 1, undefined, 946 | slice = Array.prototype.slice, 947 | isFunction = $.isFunction, 948 | isString = function(obj){ return typeof obj == 'string' }, 949 | handlers = {}, 950 | specialEvents={}, 951 | focusinSupported = 'onfocusin' in window, 952 | focus = { focus: 'focusin', blur: 'focusout' }, 953 | hover = { mouseenter: 'mouseover', mouseleave: 'mouseout' } 954 | 955 | specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents' 956 | 957 | function zid(element) { 958 | return element._zid || (element._zid = _zid++) 959 | } 960 | function findHandlers(element, event, fn, selector) { 961 | event = parse(event) 962 | if (event.ns) var matcher = matcherFor(event.ns) 963 | return (handlers[zid(element)] || []).filter(function(handler) { 964 | return handler 965 | && (!event.e || handler.e == event.e) 966 | && (!event.ns || matcher.test(handler.ns)) 967 | && (!fn || zid(handler.fn) === zid(fn)) 968 | && (!selector || handler.sel == selector) 969 | }) 970 | } 971 | function parse(event) { 972 | var parts = ('' + event).split('.') 973 | return {e: parts[0], ns: parts.slice(1).sort().join(' ')} 974 | } 975 | function matcherFor(ns) { 976 | return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)') 977 | } 978 | 979 | function eventCapture(handler, captureSetting) { 980 | return handler.del && 981 | (!focusinSupported && (handler.e in focus)) || 982 | !!captureSetting 983 | } 984 | 985 | function realEvent(type) { 986 | return hover[type] || (focusinSupported && focus[type]) || type 987 | } 988 | 989 | function add(element, events, fn, data, selector, delegator, capture){ 990 | var id = zid(element), set = (handlers[id] || (handlers[id] = [])) 991 | events.split(/\s/).forEach(function(event){ 992 | if (event == 'ready') return $(document).ready(fn) 993 | var handler = parse(event) 994 | handler.fn = fn 995 | handler.sel = selector 996 | // emulate mouseenter, mouseleave 997 | if (handler.e in hover) fn = function(e){ 998 | var related = e.relatedTarget 999 | if (!related || (related !== this && !$.contains(this, related))) 1000 | return handler.fn.apply(this, arguments) 1001 | } 1002 | handler.del = delegator 1003 | var callback = delegator || fn 1004 | handler.proxy = function(e){ 1005 | e = compatible(e) 1006 | if (e.isImmediatePropagationStopped()) return 1007 | e.data = data 1008 | var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args)) 1009 | if (result === false) e.preventDefault(), e.stopPropagation() 1010 | return result 1011 | } 1012 | handler.i = set.length 1013 | set.push(handler) 1014 | if ('addEventListener' in element) 1015 | element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture)) 1016 | }) 1017 | } 1018 | function remove(element, events, fn, selector, capture){ 1019 | var id = zid(element) 1020 | ;(events || '').split(/\s/).forEach(function(event){ 1021 | findHandlers(element, event, fn, selector).forEach(function(handler){ 1022 | delete handlers[id][handler.i] 1023 | if ('removeEventListener' in element) 1024 | element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture)) 1025 | }) 1026 | }) 1027 | } 1028 | 1029 | $.event = { add: add, remove: remove } 1030 | 1031 | $.proxy = function(fn, context) { 1032 | var args = (2 in arguments) && slice.call(arguments, 2) 1033 | if (isFunction(fn)) { 1034 | var proxyFn = function(){ return fn.apply(context, args ? args.concat(slice.call(arguments)) : arguments) } 1035 | proxyFn._zid = zid(fn) 1036 | return proxyFn 1037 | } else if (isString(context)) { 1038 | if (args) { 1039 | args.unshift(fn[context], fn) 1040 | return $.proxy.apply(null, args) 1041 | } else { 1042 | return $.proxy(fn[context], fn) 1043 | } 1044 | } else { 1045 | throw new TypeError("expected function") 1046 | } 1047 | } 1048 | 1049 | $.fn.bind = function(event, data, callback){ 1050 | return this.on(event, data, callback) 1051 | } 1052 | $.fn.unbind = function(event, callback){ 1053 | return this.off(event, callback) 1054 | } 1055 | $.fn.one = function(event, selector, data, callback){ 1056 | return this.on(event, selector, data, callback, 1) 1057 | } 1058 | 1059 | var returnTrue = function(){return true}, 1060 | returnFalse = function(){return false}, 1061 | ignoreProperties = /^([A-Z]|returnValue$|layer[XY]$|webkitMovement[XY]$)/, 1062 | eventMethods = { 1063 | preventDefault: 'isDefaultPrevented', 1064 | stopImmediatePropagation: 'isImmediatePropagationStopped', 1065 | stopPropagation: 'isPropagationStopped' 1066 | } 1067 | 1068 | function compatible(event, source) { 1069 | if (source || !event.isDefaultPrevented) { 1070 | source || (source = event) 1071 | 1072 | $.each(eventMethods, function(name, predicate) { 1073 | var sourceMethod = source[name] 1074 | event[name] = function(){ 1075 | this[predicate] = returnTrue 1076 | return sourceMethod && sourceMethod.apply(source, arguments) 1077 | } 1078 | event[predicate] = returnFalse 1079 | }) 1080 | 1081 | event.timeStamp || (event.timeStamp = Date.now()) 1082 | 1083 | if (source.defaultPrevented !== undefined ? source.defaultPrevented : 1084 | 'returnValue' in source ? source.returnValue === false : 1085 | source.getPreventDefault && source.getPreventDefault()) 1086 | event.isDefaultPrevented = returnTrue 1087 | } 1088 | return event 1089 | } 1090 | 1091 | function createProxy(event) { 1092 | var key, proxy = { originalEvent: event } 1093 | for (key in event) 1094 | if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key] 1095 | 1096 | return compatible(proxy, event) 1097 | } 1098 | 1099 | $.fn.delegate = function(selector, event, callback){ 1100 | return this.on(event, selector, callback) 1101 | } 1102 | $.fn.undelegate = function(selector, event, callback){ 1103 | return this.off(event, selector, callback) 1104 | } 1105 | 1106 | $.fn.live = function(event, callback){ 1107 | $(document.body).delegate(this.selector, event, callback) 1108 | return this 1109 | } 1110 | $.fn.die = function(event, callback){ 1111 | $(document.body).undelegate(this.selector, event, callback) 1112 | return this 1113 | } 1114 | 1115 | $.fn.on = function(event, selector, data, callback, one){ 1116 | var autoRemove, delegator, $this = this 1117 | if (event && !isString(event)) { 1118 | $.each(event, function(type, fn){ 1119 | $this.on(type, selector, data, fn, one) 1120 | }) 1121 | return $this 1122 | } 1123 | 1124 | if (!isString(selector) && !isFunction(callback) && callback !== false) 1125 | callback = data, data = selector, selector = undefined 1126 | if (callback === undefined || data === false) 1127 | callback = data, data = undefined 1128 | 1129 | if (callback === false) callback = returnFalse 1130 | 1131 | return $this.each(function(_, element){ 1132 | if (one) autoRemove = function(e){ 1133 | remove(element, e.type, callback) 1134 | return callback.apply(this, arguments) 1135 | } 1136 | 1137 | if (selector) delegator = function(e){ 1138 | var evt, match = $(e.target).closest(selector, element).get(0) 1139 | if (match && match !== element) { 1140 | evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element}) 1141 | return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1))) 1142 | } 1143 | } 1144 | 1145 | add(element, event, callback, data, selector, delegator || autoRemove) 1146 | }) 1147 | } 1148 | $.fn.off = function(event, selector, callback){ 1149 | var $this = this 1150 | if (event && !isString(event)) { 1151 | $.each(event, function(type, fn){ 1152 | $this.off(type, selector, fn) 1153 | }) 1154 | return $this 1155 | } 1156 | 1157 | if (!isString(selector) && !isFunction(callback) && callback !== false) 1158 | callback = selector, selector = undefined 1159 | 1160 | if (callback === false) callback = returnFalse 1161 | 1162 | return $this.each(function(){ 1163 | remove(this, event, callback, selector) 1164 | }) 1165 | } 1166 | 1167 | $.fn.trigger = function(event, args){ 1168 | event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event) 1169 | event._args = args 1170 | return this.each(function(){ 1171 | // handle focus(), blur() by calling them directly 1172 | if (event.type in focus && typeof this[event.type] == "function") this[event.type]() 1173 | // items in the collection might not be DOM elements 1174 | else if ('dispatchEvent' in this) this.dispatchEvent(event) 1175 | else $(this).triggerHandler(event, args) 1176 | }) 1177 | } 1178 | 1179 | // triggers event handlers on current element just as if an event occurred, 1180 | // doesn't trigger an actual event, doesn't bubble 1181 | $.fn.triggerHandler = function(event, args){ 1182 | var e, result 1183 | this.each(function(i, element){ 1184 | e = createProxy(isString(event) ? $.Event(event) : event) 1185 | e._args = args 1186 | e.target = element 1187 | $.each(findHandlers(element, event.type || event), function(i, handler){ 1188 | result = handler.proxy(e) 1189 | if (e.isImmediatePropagationStopped()) return false 1190 | }) 1191 | }) 1192 | return result 1193 | } 1194 | 1195 | // shortcut methods for `.bind(event, fn)` for each event type 1196 | ;('focusin focusout focus blur load resize scroll unload click dblclick '+ 1197 | 'mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave '+ 1198 | 'change select keydown keypress keyup error').split(' ').forEach(function(event) { 1199 | $.fn[event] = function(callback) { 1200 | return (0 in arguments) ? 1201 | this.bind(event, callback) : 1202 | this.trigger(event) 1203 | } 1204 | }) 1205 | 1206 | $.Event = function(type, props) { 1207 | if (!isString(type)) props = type, type = props.type 1208 | var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true 1209 | if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name]) 1210 | event.initEvent(type, bubbles, true) 1211 | return compatible(event) 1212 | } 1213 | 1214 | })(Zepto) 1215 | 1216 | ;(function($){ 1217 | var jsonpID = +new Date(), 1218 | document = window.document, 1219 | key, 1220 | name, 1221 | rscript = /)<[^<]*)*<\/script>/gi, 1222 | scriptTypeRE = /^(?:text|application)\/javascript/i, 1223 | xmlTypeRE = /^(?:text|application)\/xml/i, 1224 | jsonType = 'application/json', 1225 | htmlType = 'text/html', 1226 | blankRE = /^\s*$/, 1227 | originAnchor = document.createElement('a') 1228 | 1229 | originAnchor.href = window.location.href 1230 | 1231 | // trigger a custom event and return false if it was cancelled 1232 | function triggerAndReturn(context, eventName, data) { 1233 | var event = $.Event(eventName) 1234 | $(context).trigger(event, data) 1235 | return !event.isDefaultPrevented() 1236 | } 1237 | 1238 | // trigger an Ajax "global" event 1239 | function triggerGlobal(settings, context, eventName, data) { 1240 | if (settings.global) return triggerAndReturn(context || document, eventName, data) 1241 | } 1242 | 1243 | // Number of active Ajax requests 1244 | $.active = 0 1245 | 1246 | function ajaxStart(settings) { 1247 | if (settings.global && $.active++ === 0) triggerGlobal(settings, null, 'ajaxStart') 1248 | } 1249 | function ajaxStop(settings) { 1250 | if (settings.global && !(--$.active)) triggerGlobal(settings, null, 'ajaxStop') 1251 | } 1252 | 1253 | // triggers an extra global event "ajaxBeforeSend" that's like "ajaxSend" but cancelable 1254 | function ajaxBeforeSend(xhr, settings) { 1255 | var context = settings.context 1256 | if (settings.beforeSend.call(context, xhr, settings) === false || 1257 | triggerGlobal(settings, context, 'ajaxBeforeSend', [xhr, settings]) === false) 1258 | return false 1259 | 1260 | triggerGlobal(settings, context, 'ajaxSend', [xhr, settings]) 1261 | } 1262 | function ajaxSuccess(data, xhr, settings, deferred) { 1263 | var context = settings.context, status = 'success' 1264 | settings.success.call(context, data, status, xhr) 1265 | if (deferred) deferred.resolveWith(context, [data, status, xhr]) 1266 | triggerGlobal(settings, context, 'ajaxSuccess', [xhr, settings, data]) 1267 | ajaxComplete(status, xhr, settings) 1268 | } 1269 | // type: "timeout", "error", "abort", "parsererror" 1270 | function ajaxError(error, type, xhr, settings, deferred) { 1271 | var context = settings.context 1272 | settings.error.call(context, xhr, type, error) 1273 | if (deferred) deferred.rejectWith(context, [xhr, type, error]) 1274 | triggerGlobal(settings, context, 'ajaxError', [xhr, settings, error || type]) 1275 | ajaxComplete(type, xhr, settings) 1276 | } 1277 | // status: "success", "notmodified", "error", "timeout", "abort", "parsererror" 1278 | function ajaxComplete(status, xhr, settings) { 1279 | var context = settings.context 1280 | settings.complete.call(context, xhr, status) 1281 | triggerGlobal(settings, context, 'ajaxComplete', [xhr, settings]) 1282 | ajaxStop(settings) 1283 | } 1284 | 1285 | function ajaxDataFilter(data, type, settings) { 1286 | if (settings.dataFilter == empty) return data 1287 | var context = settings.context 1288 | return settings.dataFilter.call(context, data, type) 1289 | } 1290 | 1291 | // Empty function, used as default callback 1292 | function empty() {} 1293 | 1294 | $.ajaxJSONP = function(options, deferred){ 1295 | if (!('type' in options)) return $.ajax(options) 1296 | 1297 | var _callbackName = options.jsonpCallback, 1298 | callbackName = ($.isFunction(_callbackName) ? 1299 | _callbackName() : _callbackName) || ('Zepto' + (jsonpID++)), 1300 | script = document.createElement('script'), 1301 | originalCallback = window[callbackName], 1302 | responseData, 1303 | abort = function(errorType) { 1304 | $(script).triggerHandler('error', errorType || 'abort') 1305 | }, 1306 | xhr = { abort: abort }, abortTimeout 1307 | 1308 | if (deferred) deferred.promise(xhr) 1309 | 1310 | $(script).on('load error', function(e, errorType){ 1311 | clearTimeout(abortTimeout) 1312 | $(script).off().remove() 1313 | 1314 | if (e.type == 'error' || !responseData) { 1315 | ajaxError(null, errorType || 'error', xhr, options, deferred) 1316 | } else { 1317 | ajaxSuccess(responseData[0], xhr, options, deferred) 1318 | } 1319 | 1320 | window[callbackName] = originalCallback 1321 | if (responseData && $.isFunction(originalCallback)) 1322 | originalCallback(responseData[0]) 1323 | 1324 | originalCallback = responseData = undefined 1325 | }) 1326 | 1327 | if (ajaxBeforeSend(xhr, options) === false) { 1328 | abort('abort') 1329 | return xhr 1330 | } 1331 | 1332 | window[callbackName] = function(){ 1333 | responseData = arguments 1334 | } 1335 | 1336 | script.src = options.url.replace(/\?(.+)=\?/, '?$1=' + callbackName) 1337 | document.head.appendChild(script) 1338 | 1339 | if (options.timeout > 0) abortTimeout = setTimeout(function(){ 1340 | abort('timeout') 1341 | }, options.timeout) 1342 | 1343 | return xhr 1344 | } 1345 | 1346 | $.ajaxSettings = { 1347 | // Default type of request 1348 | type: 'GET', 1349 | // Callback that is executed before request 1350 | beforeSend: empty, 1351 | // Callback that is executed if the request succeeds 1352 | success: empty, 1353 | // Callback that is executed the the server drops error 1354 | error: empty, 1355 | // Callback that is executed on request complete (both: error and success) 1356 | complete: empty, 1357 | // The context for the callbacks 1358 | context: null, 1359 | // Whether to trigger "global" Ajax events 1360 | global: true, 1361 | // Transport 1362 | xhr: function () { 1363 | return new window.XMLHttpRequest() 1364 | }, 1365 | // MIME types mapping 1366 | // IIS returns Javascript as "application/x-javascript" 1367 | accepts: { 1368 | script: 'text/javascript, application/javascript, application/x-javascript', 1369 | json: jsonType, 1370 | xml: 'application/xml, text/xml', 1371 | html: htmlType, 1372 | text: 'text/plain' 1373 | }, 1374 | // Whether the request is to another domain 1375 | crossDomain: false, 1376 | // Default timeout 1377 | timeout: 0, 1378 | // Whether data should be serialized to string 1379 | processData: true, 1380 | // Whether the browser should be allowed to cache GET responses 1381 | cache: true, 1382 | //Used to handle the raw response data of XMLHttpRequest. 1383 | //This is a pre-filtering function to sanitize the response. 1384 | //The sanitized response should be returned 1385 | dataFilter: empty 1386 | } 1387 | 1388 | function mimeToDataType(mime) { 1389 | if (mime) mime = mime.split(';', 2)[0] 1390 | return mime && ( mime == htmlType ? 'html' : 1391 | mime == jsonType ? 'json' : 1392 | scriptTypeRE.test(mime) ? 'script' : 1393 | xmlTypeRE.test(mime) && 'xml' ) || 'text' 1394 | } 1395 | 1396 | function appendQuery(url, query) { 1397 | if (query == '') return url 1398 | return (url + '&' + query).replace(/[&?]{1,2}/, '?') 1399 | } 1400 | 1401 | // serialize payload and append it to the URL for GET requests 1402 | function serializeData(options) { 1403 | if (options.processData && options.data && $.type(options.data) != "string") 1404 | options.data = $.param(options.data, options.traditional) 1405 | if (options.data && (!options.type || options.type.toUpperCase() == 'GET' || 'jsonp' == options.dataType)) 1406 | options.url = appendQuery(options.url, options.data), options.data = undefined 1407 | } 1408 | 1409 | $.ajax = function(options){ 1410 | var settings = $.extend({}, options || {}), 1411 | deferred = $.Deferred && $.Deferred(), 1412 | urlAnchor, hashIndex 1413 | for (key in $.ajaxSettings) if (settings[key] === undefined) settings[key] = $.ajaxSettings[key] 1414 | 1415 | ajaxStart(settings) 1416 | 1417 | if (!settings.crossDomain) { 1418 | urlAnchor = document.createElement('a') 1419 | urlAnchor.href = settings.url 1420 | // cleans up URL for .href (IE only), see https://github.com/madrobby/zepto/pull/1049 1421 | urlAnchor.href = urlAnchor.href 1422 | settings.crossDomain = (originAnchor.protocol + '//' + originAnchor.host) !== (urlAnchor.protocol + '//' + urlAnchor.host) 1423 | } 1424 | 1425 | if (!settings.url) settings.url = window.location.toString() 1426 | if ((hashIndex = settings.url.indexOf('#')) > -1) settings.url = settings.url.slice(0, hashIndex) 1427 | serializeData(settings) 1428 | 1429 | var dataType = settings.dataType, hasPlaceholder = /\?.+=\?/.test(settings.url) 1430 | if (hasPlaceholder) dataType = 'jsonp' 1431 | 1432 | if (settings.cache === false || ( 1433 | (!options || options.cache !== true) && 1434 | ('script' == dataType || 'jsonp' == dataType) 1435 | )) 1436 | settings.url = appendQuery(settings.url, '_=' + Date.now()) 1437 | 1438 | if ('jsonp' == dataType) { 1439 | if (!hasPlaceholder) 1440 | settings.url = appendQuery(settings.url, 1441 | settings.jsonp ? (settings.jsonp + '=?') : settings.jsonp === false ? '' : 'callback=?') 1442 | return $.ajaxJSONP(settings, deferred) 1443 | } 1444 | 1445 | var mime = settings.accepts[dataType], 1446 | headers = { }, 1447 | setHeader = function(name, value) { headers[name.toLowerCase()] = [name, value] }, 1448 | protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol, 1449 | xhr = settings.xhr(), 1450 | nativeSetHeader = xhr.setRequestHeader, 1451 | abortTimeout 1452 | 1453 | if (deferred) deferred.promise(xhr) 1454 | 1455 | if (!settings.crossDomain) setHeader('X-Requested-With', 'XMLHttpRequest') 1456 | setHeader('Accept', mime || '*/*') 1457 | if (mime = settings.mimeType || mime) { 1458 | if (mime.indexOf(',') > -1) mime = mime.split(',', 2)[0] 1459 | xhr.overrideMimeType && xhr.overrideMimeType(mime) 1460 | } 1461 | if (settings.contentType || (settings.contentType !== false && settings.data && settings.type.toUpperCase() != 'GET')) 1462 | setHeader('Content-Type', settings.contentType || 'application/x-www-form-urlencoded') 1463 | 1464 | if (settings.headers) for (name in settings.headers) setHeader(name, settings.headers[name]) 1465 | xhr.setRequestHeader = setHeader 1466 | 1467 | xhr.onreadystatechange = function(){ 1468 | if (xhr.readyState == 4) { 1469 | xhr.onreadystatechange = empty 1470 | clearTimeout(abortTimeout) 1471 | var result, error = false 1472 | if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 || (xhr.status == 0 && protocol == 'file:')) { 1473 | dataType = dataType || mimeToDataType(settings.mimeType || xhr.getResponseHeader('content-type')) 1474 | 1475 | if (xhr.responseType == 'arraybuffer' || xhr.responseType == 'blob') 1476 | result = xhr.response 1477 | else { 1478 | result = xhr.responseText 1479 | 1480 | try { 1481 | // http://perfectionkills.com/global-eval-what-are-the-options/ 1482 | // sanitize response accordingly if data filter callback provided 1483 | result = ajaxDataFilter(result, dataType, settings) 1484 | if (dataType == 'script') (1,eval)(result) 1485 | else if (dataType == 'xml') result = xhr.responseXML 1486 | else if (dataType == 'json') result = blankRE.test(result) ? null : $.parseJSON(result) 1487 | } catch (e) { error = e } 1488 | 1489 | if (error) return ajaxError(error, 'parsererror', xhr, settings, deferred) 1490 | } 1491 | 1492 | ajaxSuccess(result, xhr, settings, deferred) 1493 | } else { 1494 | ajaxError(xhr.statusText || null, xhr.status ? 'error' : 'abort', xhr, settings, deferred) 1495 | } 1496 | } 1497 | } 1498 | 1499 | if (ajaxBeforeSend(xhr, settings) === false) { 1500 | xhr.abort() 1501 | ajaxError(null, 'abort', xhr, settings, deferred) 1502 | return xhr 1503 | } 1504 | 1505 | var async = 'async' in settings ? settings.async : true 1506 | xhr.open(settings.type, settings.url, async, settings.username, settings.password) 1507 | 1508 | if (settings.xhrFields) for (name in settings.xhrFields) xhr[name] = settings.xhrFields[name] 1509 | 1510 | for (name in headers) nativeSetHeader.apply(xhr, headers[name]) 1511 | 1512 | if (settings.timeout > 0) abortTimeout = setTimeout(function(){ 1513 | xhr.onreadystatechange = empty 1514 | xhr.abort() 1515 | ajaxError(null, 'timeout', xhr, settings, deferred) 1516 | }, settings.timeout) 1517 | 1518 | // avoid sending empty string (#319) 1519 | xhr.send(settings.data ? settings.data : null) 1520 | return xhr 1521 | } 1522 | 1523 | // handle optional data/success arguments 1524 | function parseArguments(url, data, success, dataType) { 1525 | if ($.isFunction(data)) dataType = success, success = data, data = undefined 1526 | if (!$.isFunction(success)) dataType = success, success = undefined 1527 | return { 1528 | url: url 1529 | , data: data 1530 | , success: success 1531 | , dataType: dataType 1532 | } 1533 | } 1534 | 1535 | $.get = function(/* url, data, success, dataType */){ 1536 | return $.ajax(parseArguments.apply(null, arguments)) 1537 | } 1538 | 1539 | $.post = function(/* url, data, success, dataType */){ 1540 | var options = parseArguments.apply(null, arguments) 1541 | options.type = 'POST' 1542 | return $.ajax(options) 1543 | } 1544 | 1545 | $.getJSON = function(/* url, data, success */){ 1546 | var options = parseArguments.apply(null, arguments) 1547 | options.dataType = 'json' 1548 | return $.ajax(options) 1549 | } 1550 | 1551 | $.fn.load = function(url, data, success){ 1552 | if (!this.length) return this 1553 | var self = this, parts = url.split(/\s/), selector, 1554 | options = parseArguments(url, data, success), 1555 | callback = options.success 1556 | if (parts.length > 1) options.url = parts[0], selector = parts[1] 1557 | options.success = function(response){ 1558 | self.html(selector ? 1559 | $('
').html(response.replace(rscript, "")).find(selector) 1560 | : response) 1561 | callback && callback.apply(self, arguments) 1562 | } 1563 | $.ajax(options) 1564 | return this 1565 | } 1566 | 1567 | var escape = encodeURIComponent 1568 | 1569 | function serialize(params, obj, traditional, scope){ 1570 | var type, array = $.isArray(obj), hash = $.isPlainObject(obj) 1571 | $.each(obj, function(key, value) { 1572 | type = $.type(value) 1573 | if (scope) key = traditional ? scope : 1574 | scope + '[' + (hash || type == 'object' || type == 'array' ? key : '') + ']' 1575 | // handle data in serializeArray() format 1576 | if (!scope && array) params.add(value.name, value.value) 1577 | // recurse into nested objects 1578 | else if (type == "array" || (!traditional && type == "object")) 1579 | serialize(params, value, traditional, key) 1580 | else params.add(key, value) 1581 | }) 1582 | } 1583 | 1584 | $.param = function(obj, traditional){ 1585 | var params = [] 1586 | params.add = function(key, value) { 1587 | if ($.isFunction(value)) value = value() 1588 | if (value == null) value = "" 1589 | this.push(escape(key) + '=' + escape(value)) 1590 | } 1591 | serialize(params, obj, traditional) 1592 | return params.join('&').replace(/%20/g, '+') 1593 | } 1594 | })(Zepto) 1595 | 1596 | ;(function($){ 1597 | $.fn.serializeArray = function() { 1598 | var name, type, result = [], 1599 | add = function(value) { 1600 | if (value.forEach) return value.forEach(add) 1601 | result.push({ name: name, value: value }) 1602 | } 1603 | if (this[0]) $.each(this[0].elements, function(_, field){ 1604 | type = field.type, name = field.name 1605 | if (name && field.nodeName.toLowerCase() != 'fieldset' && 1606 | !field.disabled && type != 'submit' && type != 'reset' && type != 'button' && type != 'file' && 1607 | ((type != 'radio' && type != 'checkbox') || field.checked)) 1608 | add($(field).val()) 1609 | }) 1610 | return result 1611 | } 1612 | 1613 | $.fn.serialize = function(){ 1614 | var result = [] 1615 | this.serializeArray().forEach(function(elm){ 1616 | result.push(encodeURIComponent(elm.name) + '=' + encodeURIComponent(elm.value)) 1617 | }) 1618 | return result.join('&') 1619 | } 1620 | 1621 | $.fn.submit = function(callback) { 1622 | if (0 in arguments) this.bind('submit', callback) 1623 | else if (this.length) { 1624 | var event = $.Event('submit') 1625 | this.eq(0).trigger(event) 1626 | if (!event.isDefaultPrevented()) this.get(0).submit() 1627 | } 1628 | return this 1629 | } 1630 | 1631 | })(Zepto) 1632 | 1633 | ;(function(){ 1634 | // getComputedStyle shouldn't freak out when called 1635 | // without a valid element as argument 1636 | try { 1637 | getComputedStyle(undefined) 1638 | } catch(e) { 1639 | var nativeGetComputedStyle = getComputedStyle 1640 | window.getComputedStyle = function(element, pseudoElement){ 1641 | try { 1642 | return nativeGetComputedStyle(element, pseudoElement) 1643 | } catch(e) { 1644 | return null 1645 | } 1646 | } 1647 | } 1648 | })() 1649 | return Zepto 1650 | })) -------------------------------------------------------------------------------- /androidexample/src/main/java/com/zyj/example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.zyj.example; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.database.Cursor; 6 | import android.graphics.Bitmap; 7 | import android.net.Uri; 8 | import android.os.Bundle; 9 | import android.provider.MediaStore; 10 | import android.webkit.WebView; 11 | 12 | import com.zyj.example.entity.JsMsgPhotoEntity; 13 | import com.zyj.example.entity.PhotoEntity; 14 | import com.zyj.example.iml.JsCall; 15 | import com.zyj.example.iml.JsDeviceInfo; 16 | import com.zyj.example.iml.JsSelectPhoto; 17 | import com.zyj.example.iml.JsTakePhoto; 18 | import com.zyj.hybridbridge.HandleResult; 19 | import com.zyj.hybridbridge.JsBridge; 20 | import com.zyj.hybridbridge.RxBus; 21 | 22 | import static com.zyj.example.Utils.saveBitmapToFile; 23 | 24 | public class MainActivity extends Activity { 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_main); 30 | WebView webView = ((WebView) findViewById(R.id.webView)); 31 | webView.loadUrl("file:///android_asset/demo.html"); 32 | JsBridge.getInstance().init(this, webView) 33 | .addJsAction(JsCall.ACTION, JsCall.class) 34 | .addJsAction(JsSelectPhoto.ACTION, JsSelectPhoto.class) 35 | .addJsAction(JsTakePhoto.ACTION, JsTakePhoto.class) 36 | .addJsAction(JsDeviceInfo.ACTION, JsDeviceInfo.class); 37 | } 38 | 39 | 40 | @Override 41 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 42 | super.onActivityResult(requestCode, resultCode, data); 43 | if (resultCode != Activity.RESULT_OK) return; 44 | if (requestCode == JsMsgPhotoEntity.TAKEREQUESTCODE) { 45 | takePhoto(data); 46 | } else if (requestCode == JsMsgPhotoEntity.SELECTREQUESTCODE) { 47 | selectPhoto(data); 48 | } 49 | } 50 | /** 51 | * 拍照 52 | * 53 | * @param data 图片数据 54 | */ 55 | private void takePhoto(Intent data) { 56 | Bundle bundle = data.getExtras(); 57 | Bitmap bitmap = (Bitmap) bundle.get("data");// 获取相机返回的数据,并转换为Bitmap图片格式 58 | String imagePath = saveBitmapToFile(bitmap,null); 59 | String smallImagePath = saveBitmapToFile(Utils.getSmallBitmap(imagePath),"1"); 60 | 61 | PhotoEntity photoEntity =new PhotoEntity(); 62 | photoEntity.setImagePath(imagePath); 63 | photoEntity.setSmImagePath(smallImagePath); 64 | RxBus.getInstance().post( new HandleResult(photoEntity)); 65 | } 66 | /** 67 | * 图库选择 68 | * 69 | * @param data 图片数据 70 | */ 71 | private void selectPhoto(Intent data) { 72 | Uri selectImageUri = data.getData(); 73 | String[] filePathColumn = new String[]{MediaStore.Images.Media.DATA};//要查询的列 74 | Cursor cursor = getContentResolver().query(selectImageUri, filePathColumn, null, null, null); 75 | String pirPath = null; 76 | String smallImagePath=null; 77 | while (cursor.moveToNext()) { 78 | pirPath = cursor.getString(cursor.getColumnIndex(filePathColumn[0]));//所选择的图片路径 79 | smallImagePath = saveBitmapToFile(Utils.getSmallBitmap(pirPath),null); 80 | } 81 | cursor.close(); 82 | PhotoEntity photoEntity =new PhotoEntity(); 83 | photoEntity.setImagePath(pirPath); 84 | photoEntity.setSmImagePath(smallImagePath); 85 | RxBus.getInstance().post(new HandleResult(photoEntity)); 86 | } 87 | 88 | 89 | 90 | 91 | 92 | @Override 93 | protected void onDestroy() { 94 | super.onDestroy(); 95 | JsBridge.getInstance().destroy(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /androidexample/src/main/java/com/zyj/example/Utils.java: -------------------------------------------------------------------------------- 1 | package com.zyj.example; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.os.Environment; 7 | 8 | import java.io.File; 9 | import java.io.FileOutputStream; 10 | import java.io.IOException; 11 | import java.text.SimpleDateFormat; 12 | import java.util.Date; 13 | 14 | /** 15 | * dec: 16 | * createBy yjzhao 17 | * createTime 16/5/14 11:08 18 | */ 19 | public class Utils { 20 | public static String saveBitmapToFile(Bitmap bitmap, String name){ 21 | @SuppressLint("SimpleDateFormat") 22 | String fileName = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + (null==name?"":name)+".png"; 23 | File photoFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), fileName); 24 | try { 25 | FileOutputStream b =new FileOutputStream(photoFile); 26 | bitmap.compress(Bitmap.CompressFormat.PNG,100,b); 27 | b.flush(); 28 | b.close(); 29 | } catch (IOException e) { 30 | e.printStackTrace(); 31 | } 32 | return photoFile.getAbsolutePath(); 33 | } 34 | //计算图片的缩放值 35 | public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { 36 | final int height = options.outHeight; 37 | final int width = options.outWidth; 38 | int inSampleSize = 1; 39 | 40 | if (height > reqHeight || width > reqWidth) { 41 | final int heightRatio = Math.round((float) height / (float) reqHeight); 42 | final int widthRatio = Math.round((float) width / (float) reqWidth); 43 | inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; 44 | } 45 | return inSampleSize; 46 | } 47 | // 根据路径获得图片并压缩,返回bitmap用于显示 48 | public static Bitmap getSmallBitmap(String filePath) { 49 | final BitmapFactory.Options options = new BitmapFactory.Options(); 50 | options.inJustDecodeBounds = true; 51 | BitmapFactory.decodeFile(filePath, options); 52 | 53 | // Calculate inSampleSize 54 | options.inSampleSize = calculateInSampleSize(options, 480, 800); 55 | 56 | // Decode bitmap with inSampleSize set 57 | options.inJustDecodeBounds = false; 58 | 59 | return BitmapFactory.decodeFile(filePath, options); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /androidexample/src/main/java/com/zyj/example/entity/DeviceInfoEntity.java: -------------------------------------------------------------------------------- 1 | package com.zyj.example.entity; 2 | 3 | /** 4 | * dec:设备信息 5 | * createBy yjzhao 6 | * createTime 16/5/14 11:08 7 | */ 8 | public class DeviceInfoEntity { 9 | 10 | private String deviceName; 11 | 12 | public String getDeviceName() { 13 | return deviceName; 14 | } 15 | 16 | public void setDeviceName(String deviceName) { 17 | this.deviceName = deviceName; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /androidexample/src/main/java/com/zyj/example/entity/JsMsgCallEntity.java: -------------------------------------------------------------------------------- 1 | package com.zyj.example.entity; 2 | 3 | import com.zyj.hybridbridge.JsMessage; 4 | 5 | /** 6 | * dec:拍照 7 | * createBy yjzhao 8 | * createTime 16/5/14 11:08 9 | */ 10 | public class JsMsgCallEntity extends JsMessage { 11 | public static final int CALLCODE=10003; 12 | 13 | 14 | /** 15 | * data : {"number":1} 16 | */ 17 | 18 | private DataBean data; 19 | 20 | public DataBean getData() { 21 | return data; 22 | } 23 | 24 | public void setData(DataBean data) { 25 | this.data = data; 26 | } 27 | 28 | public static class DataBean { 29 | /** 30 | * number : 1 31 | */ 32 | 33 | private int number; 34 | 35 | public int getNumber() { 36 | return number; 37 | } 38 | 39 | public void setNumber(int number) { 40 | this.number = number; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /androidexample/src/main/java/com/zyj/example/entity/JsMsgPhotoEntity.java: -------------------------------------------------------------------------------- 1 | package com.zyj.example.entity; 2 | 3 | import com.zyj.hybridbridge.JsMessage; 4 | 5 | /** 6 | * dec:拍照 7 | * createBy yjzhao 8 | * createTime 16/5/14 11:08 9 | */ 10 | public class JsMsgPhotoEntity extends JsMessage { 11 | public static final int TAKEREQUESTCODE=10001; 12 | public static final int SELECTREQUESTCODE=10002; 13 | 14 | 15 | /** 16 | * data : {"width":1,"height":2,"iscompress":true} 17 | */ 18 | 19 | private DataBean data; 20 | 21 | public DataBean getData() { 22 | return data; 23 | } 24 | 25 | public void setData(DataBean data) { 26 | this.data = data; 27 | } 28 | 29 | public static class DataBean { 30 | /** 31 | * width : 1 32 | * height : 2 33 | * iscompress : true 34 | */ 35 | 36 | private int width; 37 | private int height; 38 | private boolean iscompress; 39 | 40 | public int getWidth() { 41 | return width; 42 | } 43 | 44 | public void setWidth(int width) { 45 | this.width = width; 46 | } 47 | 48 | public int getHeight() { 49 | return height; 50 | } 51 | 52 | public void setHeight(int height) { 53 | this.height = height; 54 | } 55 | 56 | public boolean isIscompress() { 57 | return iscompress; 58 | } 59 | 60 | public void setIscompress(boolean iscompress) { 61 | this.iscompress = iscompress; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /androidexample/src/main/java/com/zyj/example/entity/PhotoEntity.java: -------------------------------------------------------------------------------- 1 | package com.zyj.example.entity; 2 | 3 | /** 4 | * dec: 5 | * createBy yjzhao 6 | * createTime 16/5/14 11:08 7 | */ 8 | public class PhotoEntity { 9 | private String smImagePath; 10 | private String imagePath; 11 | 12 | public String getSmImagePath() { 13 | return smImagePath; 14 | } 15 | 16 | public void setSmImagePath(String smImagePath) { 17 | this.smImagePath = smImagePath; 18 | } 19 | 20 | public String getImagePath() { 21 | return imagePath; 22 | } 23 | 24 | public void setImagePath(String imagePath) { 25 | this.imagePath = imagePath; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /androidexample/src/main/java/com/zyj/example/iml/JsCall.java: -------------------------------------------------------------------------------- 1 | package com.zyj.example.iml; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.net.Uri; 6 | import android.os.Build; 7 | 8 | import com.google.gson.Gson; 9 | import com.zyj.example.entity.JsMsgCallEntity; 10 | import com.zyj.hybridbridge.JsAction; 11 | 12 | /** 13 | * dec:拨打电话 14 | * createBy yjzhao 15 | * createTime 16/5/14 11:08 16 | */ 17 | public class JsCall extends JsAction { 18 | public static final String ACTION = "call"; 19 | @Override 20 | protected void handleAction(Activity context, String jsonStr) { 21 | JsMsgCallEntity msgCallEntity = new Gson().fromJson(jsonStr, JsMsgCallEntity.class); 22 | 23 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 24 | context.requestPermissions(new String[]{"android.permission.CALL_PHONE"}, 10001); 25 | } 26 | Intent phoneIntent = new Intent("android.intent.action.CALL", Uri.parse("tel:" + msgCallEntity.getData().getNumber())); 27 | context.startActivity(phoneIntent); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /androidexample/src/main/java/com/zyj/example/iml/JsDeviceInfo.java: -------------------------------------------------------------------------------- 1 | package com.zyj.example.iml; 2 | 3 | import android.app.Activity; 4 | 5 | import com.zyj.example.entity.DeviceInfoEntity; 6 | import com.zyj.hybridbridge.HandleResult; 7 | import com.zyj.hybridbridge.JsAction; 8 | import com.zyj.hybridbridge.RxBus; 9 | 10 | /** 11 | * dec:设备信息接口 12 | * createBy yjzhao 13 | * createTime 16/5/14 11:08 14 | */ 15 | public class JsDeviceInfo extends JsAction { 16 | public static final String ACTION = "deviceinfo"; 17 | 18 | @Override 19 | protected void handleAction(Activity context, String jsonStr) { 20 | HandleResult resultEntity =new HandleResult(); 21 | DeviceInfoEntity deviceInfoEntity =new DeviceInfoEntity(); 22 | deviceInfoEntity.setDeviceName("我的Android客户端!"); 23 | resultEntity.setData(deviceInfoEntity); 24 | RxBus.getInstance().post(resultEntity); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /androidexample/src/main/java/com/zyj/example/iml/JsSelectPhoto.java: -------------------------------------------------------------------------------- 1 | package com.zyj.example.iml; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Build; 6 | 7 | import com.google.gson.Gson; 8 | import com.zyj.example.entity.JsMsgPhotoEntity; 9 | 10 | import com.zyj.hybridbridge.JsAction; 11 | 12 | /** 13 | * dec:选择照片处理 14 | * createBy yjzhao 15 | * createTime 16/5/14 11:08 16 | */ 17 | public class JsSelectPhoto extends JsAction { 18 | public static final String ACTION="selectphoto"; 19 | 20 | @Override 21 | protected void handleAction(Activity context, String jsonStr) { 22 | JsMsgPhotoEntity photoEntity = new Gson().fromJson(jsonStr, JsMsgPhotoEntity.class); 23 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 24 | context.requestPermissions(new String[]{"android.permission.WRITE_EXTERNAL_STORAGE", "android.permission.CAMERA"}, 10000); 25 | } 26 | Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); 27 | context.startActivityForResult(intent, JsMsgPhotoEntity.SELECTREQUESTCODE); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /androidexample/src/main/java/com/zyj/example/iml/JsTakePhoto.java: -------------------------------------------------------------------------------- 1 | package com.zyj.example.iml; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Build; 6 | import android.provider.MediaStore; 7 | 8 | import com.google.gson.Gson; 9 | import com.zyj.example.entity.JsMsgPhotoEntity; 10 | import com.zyj.hybridbridge.JsAction; 11 | 12 | /** 13 | * dec:拍照处理 14 | * createBy yjzhao 15 | * createTime 16/5/14 11:08 16 | */ 17 | public class JsTakePhoto extends JsAction { 18 | 19 | public static final String ACTION="takephoto"; 20 | 21 | @Override 22 | protected void handleAction(Activity context, String jsonStr) { 23 | JsMsgPhotoEntity photoEntity = new Gson().fromJson(jsonStr, JsMsgPhotoEntity.class); 24 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 25 | context.requestPermissions(new String[]{"android.permission.WRITE_EXTERNAL_STORAGE", "android.permission.CAMERA"}, 10000); 26 | } 27 | Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 28 | intent.putExtra("autofocus",true); // 自动对焦 29 | context.startActivityForResult(intent, JsMsgPhotoEntity.TAKEREQUESTCODE); 30 | } 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /androidexample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /androidexample/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YouJZ/HybridBridge/320473ac75b531dfb46bd9f8af4a7eec98eda3f2/androidexample/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /androidexample/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YouJZ/HybridBridge/320473ac75b531dfb46bd9f8af4a7eec98eda3f2/androidexample/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /androidexample/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YouJZ/HybridBridge/320473ac75b531dfb46bd9f8af4a7eec98eda3f2/androidexample/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /androidexample/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YouJZ/HybridBridge/320473ac75b531dfb46bd9f8af4a7eec98eda3f2/androidexample/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /androidexample/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YouJZ/HybridBridge/320473ac75b531dfb46bd9f8af4a7eec98eda3f2/androidexample/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /androidexample/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /androidexample/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /androidexample/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /androidexample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AndroidExample 3 | 4 | -------------------------------------------------------------------------------- /androidexample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /androidexample/src/test/java/com/zyj/example/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.zyj.example; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.2' 9 | classpath 'com.novoda:bintray-release:0.3.4' 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YouJZ/HybridBridge/320473ac75b531dfb46bd9f8af4a7eec98eda3f2/demo.gif -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YouJZ/HybridBridge/320473ac75b531dfb46bd9f8af4a7eec98eda3f2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /js/native.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /////////////////////////////// 4 | // 调用原生接口 // 5 | /////////////////////////////// 6 | ;(function($) { 7 | "use strict"//使用严格模式 8 | function native(params) { 9 | params = params||{}; 10 | if (params==="undefind")return; 11 | if (params.action==="undefind")return; 12 | //固定的三个属性和native端一样,否则native端和解析出错 13 | var Senddata={ 14 | action:params.action, 15 | callback:"nativeCallback", 16 | data:params.data, 17 | } 18 | window.nativeCallback = function(data) { 19 | if (params.callback!=="undefind") { 20 | params.callback(data); 21 | } 22 | } 23 | var sendDataStr=JSON.stringify(Senddata); 24 | window.native.sendMessage(sendDataStr); 25 | } 26 | $.native = native; 27 | })($); 28 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.novoda.bintray-release' 3 | 4 | 5 | 6 | android { 7 | compileSdkVersion 24 8 | buildToolsVersion "24.0.2" 9 | 10 | defaultConfig { 11 | minSdkVersion 14 12 | targetSdkVersion 24 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 17 | 18 | } 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | } 26 | 27 | dependencies { 28 | compile fileTree(include: ['*.jar'], dir: 'libs') 29 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 30 | exclude group: 'com.android.support', module: 'support-annotations' 31 | }) 32 | compile 'com.android.support:appcompat-v7:24.2.1' 33 | testCompile 'junit:junit:4.12' 34 | compile 'com.google.code.gson:gson:2.8.0' 35 | compile 'io.reactivex:rxandroid:1.2.1' 36 | } 37 | 38 | 39 | 40 | 41 | 42 | publish { 43 | userOrg = 'zyj'//bintray.com用户名 44 | groupId = 'com.zyj'//jcenter上的路径 45 | artifactId = 'hybridbridge'//项目名称 46 | publishVersion = '0.1.0'//版本号 47 | desc = 'A basic framework for Android hybrid development, bridge between JavaScript and java.'//描述,不重要 48 | website = 'https://github.com/YouJZ/HybridBridge'// 49 | } 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/zhaoyoujun/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /library/src/androidTest/java/coracle/com/mylibrary/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package coracle.com.mylibrary; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("coracle.com.mylibrary.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /library/src/main/java/com/zyj/hybridbridge/HandleJsMessage.java: -------------------------------------------------------------------------------- 1 | package com.zyj.hybridbridge; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.Activity; 5 | import android.os.Build; 6 | import android.webkit.WebView; 7 | 8 | import com.google.gson.Gson; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import rx.Subscription; 14 | import rx.functions.Action1; 15 | 16 | /** 17 | * dec:处理消息类 18 | * createBy yjzhao 19 | * createTime 16/5/14 11:08 20 | */ 21 | public class HandleJsMessage { 22 | private Activity mContext; 23 | private WebView mWebView; 24 | private Map> mActionMap; 25 | private JsAction mJsAction; 26 | private String jsCallback; 27 | private Subscription mJsSubscription; 28 | 29 | public HandleJsMessage() { 30 | mActionMap = new HashMap<>(); 31 | 32 | } 33 | 34 | public HandleJsMessage(Activity context, WebView webView) { 35 | this(); 36 | mContext = context; 37 | mWebView = webView; 38 | mJsSubscription = RxBus.getInstance().toObservable(HandleResult.class).subscribe(new Action1() { 39 | @Override 40 | public void call(HandleResult result) { 41 | mJsAction.callback(mWebView, jsCallback, result.getData()); 42 | } 43 | }); 44 | } 45 | 46 | /** 47 | * 处理js传递过来的数据 48 | * @param jsonStr js传递的数据 49 | * @return 是否处理 50 | */ 51 | @TargetApi(Build.VERSION_CODES.KITKAT) 52 | boolean handle(String jsonStr) { 53 | JsMessage jsMessage = new Gson().fromJson(jsonStr, JsMessage.class); 54 | String action = jsMessage.getAction(); 55 | jsCallback = jsMessage.getCallback(); 56 | if (null == jsMessage.getAction()) 57 | return false; 58 | if (HandleAction(jsonStr, action, mActionMap)) return true; 59 | return false; 60 | } 61 | 62 | /** 63 | * 根据js传递过来的action将事件分发下去 64 | * @param jsonStr js传递的数据 65 | * @param action js意图 66 | * @param map js意图集合 67 | * @return 是否处理存在处理次意图的接口 68 | */ 69 | @TargetApi(Build.VERSION_CODES.KITKAT) 70 | private boolean HandleAction(String jsonStr, String action, Map> map) { 71 | for (String mapAction : map.keySet()) { 72 | if (mapAction.equals(action)) { 73 | try { 74 | mJsAction = map.get(mapAction).newInstance(); 75 | if (mJsAction != null) { 76 | mJsAction.handleAction(mContext, jsonStr); 77 | } 78 | } catch (InstantiationException | IllegalAccessException e) { 79 | e.printStackTrace(); 80 | } 81 | return true; 82 | } 83 | } 84 | return false; 85 | } 86 | 87 | public Map> getActionMap() { 88 | return mActionMap; 89 | } 90 | 91 | public void destroy() { 92 | if (mJsSubscription!=null)mJsSubscription.unsubscribe(); 93 | if (mWebView != null) { 94 | mWebView.removeAllViews(); 95 | mWebView.destroy(); 96 | } 97 | if (mContext != null) mContext = null; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /library/src/main/java/com/zyj/hybridbridge/HandleResult.java: -------------------------------------------------------------------------------- 1 | package com.zyj.hybridbridge; 2 | 3 | /** 4 | * dec: 5 | * createBy yjzhao 6 | * createTime 16/5/14 11:08 7 | */ 8 | public class HandleResult { 9 | private Object mData; 10 | 11 | public Object getData() { 12 | return mData; 13 | } 14 | 15 | public void setData(Object data) { 16 | mData = data; 17 | } 18 | 19 | public HandleResult(Object data) { 20 | mData = data; 21 | } 22 | public HandleResult() { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /library/src/main/java/com/zyj/hybridbridge/JsAction.java: -------------------------------------------------------------------------------- 1 | package com.zyj.hybridbridge; 2 | 3 | import android.app.Activity; 4 | import android.webkit.WebView; 5 | 6 | import com.google.gson.Gson; 7 | 8 | import rx.Observable; 9 | import rx.Observer; 10 | import rx.Subscriber; 11 | import rx.android.schedulers.AndroidSchedulers; 12 | import rx.schedulers.Schedulers; 13 | 14 | /** 15 | * dec: 16 | * createBy yjzhao 17 | * createTime 16/5/14 11:08 18 | */ 19 | public abstract class JsAction { 20 | protected abstract void handleAction(Activity context,String jsonStr); 21 | 22 | public void callback(final WebView webView, final String callback, final Object result){ 23 | //切换至主线程 24 | Observable.create(new Observable.OnSubscribe() { 25 | @Override 26 | public void call(Subscriber subscriber) { 27 | subscriber.onNext(""); 28 | } 29 | }).subscribeOn(Schedulers.immediate()) 30 | .observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer() { 31 | @Override 32 | public void onCompleted() { 33 | 34 | } 35 | 36 | @Override 37 | public void onError(Throwable e) { 38 | 39 | } 40 | 41 | @Override 42 | public void onNext(Object o) { 43 | if (null==result||null==callback||"".equals(callback))return; 44 | String resultStr= new Gson().toJson(result); 45 | String url = "javascript:"+callback+"("+resultStr+")"; 46 | webView.loadUrl(url); 47 | 48 | } 49 | }); 50 | 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /library/src/main/java/com/zyj/hybridbridge/JsBridge.java: -------------------------------------------------------------------------------- 1 | package com.zyj.hybridbridge; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.os.Build; 7 | import android.support.annotation.Nullable; 8 | import android.view.View; 9 | import android.webkit.JavascriptInterface; 10 | import android.webkit.WebSettings; 11 | import android.webkit.WebView; 12 | 13 | /** 14 | * dec: 15 | * createBy yjzhao 16 | * createTime 16/5/14 11:08 17 | */ 18 | public class JsBridge { 19 | 20 | private HandleJsMessage mHandleJsMessage; 21 | 22 | private JsBridge() { 23 | } 24 | 25 | public static JsBridge getInstance() { 26 | return JsBridgeHolder.getJsBridgeInstance; 27 | 28 | } 29 | 30 | private static final class JsBridgeHolder { 31 | private final static JsBridge getJsBridgeInstance = new JsBridge(); 32 | } 33 | 34 | public JsBridge addJsAction(@Nullable String action, @Nullable Class jsActionImp) { 35 | if (mHandleJsMessage != null) 36 | mHandleJsMessage.getActionMap().put(action, jsActionImp); 37 | return this; 38 | } 39 | 40 | 41 | public JsBridge init(@Nullable Activity context, @Nullable WebView webView) { 42 | mHandleJsMessage = new HandleJsMessage(context, webView); 43 | setting(context, webView); 44 | return this; 45 | 46 | 47 | } 48 | 49 | private void setting(Context context, WebView webView) { 50 | //设置加载到webView的页面是否可以使用chrome上调试(调试阶段) 51 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 52 | WebView.setWebContentsDebuggingEnabled(true); 53 | } 54 | WebSettings webSettings = webView.getSettings(); 55 | //设置可与js交互 56 | webSettings.setJavaScriptEnabled(true); 57 | // 可以读取文件缓存(manifest生效) 58 | webSettings.setAllowFileAccess(true); 59 | // 设置可以使用localStorage 60 | webSettings.setDomStorageEnabled(true); 61 | // 应用可以有数据库 62 | webSettings.setDatabaseEnabled(true); 63 | String dbPath = context.getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath(); 64 | webSettings.setDatabasePath(dbPath); 65 | // 启用地理定位 66 | webSettings.setGeolocationEnabled(true); 67 | // 设置定位的数据库路径 68 | webSettings.setGeolocationDatabasePath(dbPath); 69 | // 设置编码格式 70 | webSettings.setDefaultTextEncodingName("utf-8"); 71 | // 设置了这个,页面中就不会出现两边白边了 72 | webView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY); 73 | // 支持跨域请求 74 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) 75 | webSettings.setAllowUniversalAccessFromFileURLs(true); 76 | // 应用可以有缓存 77 | webSettings.setAppCacheEnabled(true); 78 | String appCaceDir = context.getApplicationContext().getDir("cache", Context.MODE_PRIVATE).getPath(); 79 | webSettings.setAppCachePath(appCaceDir); 80 | // 默认使用缓存 81 | webSettings.setCacheMode(WebSettings.LOAD_DEFAULT); 82 | // 设置缓存最多可以有8M 83 | webSettings.setAppCacheMaxSize(8 * 1024 * 1024); 84 | addJavascriptInterface(webView); 85 | 86 | 87 | } 88 | 89 | /** 90 | * 暴露给js一个统一的接口 91 | * 92 | * @param webView 93 | */ 94 | @SuppressLint({"JavascriptInterface", "AddJavascriptInterface"}) 95 | private void addJavascriptInterface(WebView webView) { 96 | webView.addJavascriptInterface(new Object() { 97 | @JavascriptInterface 98 | public void sendMessage(String jsonStr) { 99 | mHandleJsMessage.handle(jsonStr); 100 | } 101 | }, "native"); 102 | } 103 | 104 | 105 | public void destroy() { 106 | mHandleJsMessage.destroy(); 107 | } 108 | 109 | 110 | } 111 | -------------------------------------------------------------------------------- /library/src/main/java/com/zyj/hybridbridge/JsMessage.java: -------------------------------------------------------------------------------- 1 | package com.zyj.hybridbridge; 2 | 3 | /** 4 | * dec: 5 | * createBy yjzhao 6 | * createTime 16/5/14 11:08 7 | */ 8 | public class JsMessage { 9 | 10 | /** 11 | * action : js的意图 12 | * callback : native处理后调用js 13 | */ 14 | 15 | private String action; 16 | private String callback; 17 | 18 | public String getAction() { 19 | return action; 20 | } 21 | 22 | public void setAction(String action) { 23 | this.action = action; 24 | } 25 | 26 | public String getCallback() { 27 | return callback; 28 | } 29 | 30 | public void setCallback(String callback) { 31 | this.callback = callback; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /library/src/main/java/com/zyj/hybridbridge/RxBus.java: -------------------------------------------------------------------------------- 1 | package com.zyj.hybridbridge; 2 | 3 | import rx.Observable; 4 | import rx.subjects.PublishSubject; 5 | import rx.subjects.SerializedSubject; 6 | import rx.subjects.Subject; 7 | 8 | /** 9 | * dec: 10 | * createBy yjzhao 11 | * createTime 2016/11/16 14:47 12 | */ 13 | 14 | public class RxBus { 15 | private final Subject bus; 16 | 17 | private RxBus() { 18 | bus = new SerializedSubject<>(PublishSubject.create()); 19 | } 20 | 21 | public static RxBus getInstance() { 22 | return RxBusInstance.rxBus; 23 | } 24 | 25 | private static final class RxBusInstance { 26 | 27 | static final RxBus rxBus = new RxBus(); 28 | } 29 | 30 | 31 | public void post(Object o) { 32 | bus.onNext(o); 33 | } 34 | 35 | public Observable toObservable(Class eventType) { 36 | return bus.ofType(eventType); 37 | } 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MyLibrary 3 | 4 | -------------------------------------------------------------------------------- /library/src/test/java/coracle/com/mylibrary/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package coracle.com.mylibrary; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':library', ':androidexample' 2 | --------------------------------------------------------------------------------