├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── dictionaries │ └── liuzhao.xml ├── gradle.xml ├── markdown-navigator.xml ├── markdown-navigator │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── android │ │ └── weexlist │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── recommend.js │ │ └── recommendcomponent.js │ ├── java │ │ └── com │ │ │ └── android │ │ │ └── weexlist │ │ │ ├── activity │ │ │ ├── MainActivity.java │ │ │ ├── RecommendActivity.java │ │ │ └── RecommendComponentActivity.java │ │ │ ├── adapter │ │ │ └── WeexImageAdapter.java │ │ │ ├── app │ │ │ └── WeexListApp.java │ │ │ ├── components │ │ │ ├── CircleImageView.java │ │ │ └── RefreshView.java │ │ │ ├── glide │ │ │ └── GlideCircleTransform.java │ │ │ ├── module │ │ │ └── CommonModule.java │ │ │ └── widgets │ │ │ ├── MaterialDesignPtrFrameLayout.java │ │ │ ├── WeexMaterialHeader.java │ │ │ ├── WeexMaterialProgressDrawable.java │ │ │ └── WeexPtrDefaultHandler.java │ ├── res │ │ ├── layout │ │ │ ├── activity_main.xml │ │ │ ├── newactivity_main.xml │ │ │ ├── recommendactivity.xml │ │ │ └── recommendcomponent.xml │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── me_image_man.png │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ └── values │ │ │ ├── arrays.xml │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ └── weex │ │ ├── recommend.vue │ │ └── recommend_component.vue │ └── test │ └── java │ └── com │ └── android │ └── weexlist │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── 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 | -------------------------------------------------------------------------------- /.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/dictionaries/liuzhao.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 35 | 36 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /.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 | Android > Lint > Correctness 39 | 40 | 41 | Android > Lint > Performance 42 | 43 | 44 | Class structureJava 45 | 46 | 47 | Cloning issuesJava 48 | 49 | 50 | Groovy 51 | 52 | 53 | Inheritance issuesJava 54 | 55 | 56 | Internationalization issuesJava 57 | 58 | 59 | Java 60 | 61 | 62 | Numeric issuesJava 63 | 64 | 65 | Performance issuesJava 66 | 67 | 68 | Potentially confusing code constructsGroovy 69 | 70 | 71 | Probable bugsJava 72 | 73 | 74 | Security issuesJava 75 | 76 | 77 | Serialization issuesJava 78 | 79 | 80 | Threading issuesJava 81 | 82 | 83 | 84 | 85 | Android 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 107 | 108 | 109 | 110 | 111 | 1.8 112 | 113 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WeexList 2 | 3 | **关于Weex的探索记录:自定义Component、网络、下拉刷新,及问题解决的思考。** 4 | 5 | # 一、涉及技能 6 | 7 | 1. **Weex完整开发的一个列表页。两种实现方式;** 8 | 2. **自定义Component组件,自定义控件与Weex的结合,包括下拉刷新和圆角图片;** 9 | 3. **Weex与下拉刷新的冲突解决思考思路;** 10 | 4. **完整的网络请求示例:参数、Header、Post请求等;** 11 | 12 | # 二、项目展示 13 | ![项目展示](http://upload-images.jianshu.io/upload_images/4056837-6be5b0c6c344f13f?imageMogr2/auto-orient/strip) 14 | 15 | # 三、项目说明 16 | Clone项目之后可直接运行,两种下拉刷新的实现区别在于: 17 | 1. **下拉刷新写在Native,然后将Weex渲染出来的View加到下拉刷新里;** 18 | 2. **封装下拉刷新为Weex的一个组件,在Weex代码中调用,灵活性更高。** 19 | 20 | 代码目录: 21 | - components为自定义的组件; 22 | - adapter为基础功能的接口,此处为图片下载; 23 | - main/assets里是Weex源文件打包生成的Js文件; 24 | - main/weex里是Weex的源文件; 25 | 26 | # 四、文章说明 27 | 28 | **欢迎关注Weex系列文章,实战+源码分析!** 29 | 30 | - [《Weex系列(一)之Weex入门准备》](http://www.jianshu.com/p/657896f60706) 31 | - [《Weex系列(二)之列表页实战》](http://www.jianshu.com/p/64288751cfc3) 32 | - [《Weex系列(三)之列表页实战冲突解决》](http://www.jianshu.com/p/27420a612d55) 33 | - [《深入Weex系列(四)之Module组件源码解析》](http://www.jianshu.com/p/208abd91f54e) 34 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "24.0.2" 6 | defaultConfig { 7 | applicationId "com.example.weexdemo" 8 | minSdkVersion 15 9 | targetSdkVersion 23 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | 14 | ndk { 15 | abiFilters "x86" 16 | abiFilters "armeabi" 17 | } 18 | 19 | } 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | } 27 | 28 | dependencies { 29 | compile fileTree(dir: 'libs', include: ['*.jar']) 30 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 31 | exclude group: 'com.android.support', module: 'support-annotations' 32 | }) 33 | compile 'com.android.support:appcompat-v7:23.4.0' 34 | compile 'com.android.support:support-v4:24.2.1' 35 | compile 'com.android.support:recyclerview-v7:23.1.1' 36 | testCompile 'junit:junit:4.12' 37 | compile 'com.alibaba:fastjson:1.1.45' 38 | compile 'com.taobao.android:weex_sdk:0.16.0' 39 | compile 'com.github.bumptech.glide:glide:3.7.0' 40 | compile 'in.srain.cube:ultra-ptr:1.0.11' 41 | } 42 | -------------------------------------------------------------------------------- /app/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/liuzhao/Downloads/adt-bundle-mac-x86_64-20140702/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 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/android/weexlist/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.android.weexlist; 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.android.weexlist", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/assets/recommend.js: -------------------------------------------------------------------------------- 1 | // { "framework": "Vue" } 2 | /******/ (function(modules) { // webpackBootstrap 3 | /******/ // The module cache 4 | /******/ var installedModules = {}; 5 | 6 | /******/ // The require function 7 | /******/ function __webpack_require__(moduleId) { 8 | 9 | /******/ // Check if module is in cache 10 | /******/ if(installedModules[moduleId]) 11 | /******/ return installedModules[moduleId].exports; 12 | 13 | /******/ // Create a new module (and put it into the cache) 14 | /******/ var module = installedModules[moduleId] = { 15 | /******/ exports: {}, 16 | /******/ id: moduleId, 17 | /******/ loaded: false 18 | /******/ }; 19 | 20 | /******/ // Execute the module function 21 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 22 | 23 | /******/ // Flag the module as loaded 24 | /******/ module.loaded = true; 25 | 26 | /******/ // Return the exports of the module 27 | /******/ return module.exports; 28 | /******/ } 29 | 30 | 31 | /******/ // expose the modules object (__webpack_modules__) 32 | /******/ __webpack_require__.m = modules; 33 | 34 | /******/ // expose the module cache 35 | /******/ __webpack_require__.c = installedModules; 36 | 37 | /******/ // __webpack_public_path__ 38 | /******/ __webpack_require__.p = ""; 39 | 40 | /******/ // Load entry module and return exports 41 | /******/ return __webpack_require__(0); 42 | /******/ }) 43 | /************************************************************************/ 44 | /******/ ([ 45 | /* 0 */ 46 | /***/ function(module, exports, __webpack_require__) { 47 | 48 | var __vue_exports__, __vue_options__ 49 | var __vue_styles__ = [] 50 | 51 | /* styles */ 52 | __vue_styles__.push(__webpack_require__(1) 53 | ) 54 | 55 | /* script */ 56 | __vue_exports__ = __webpack_require__(2) 57 | 58 | /* template */ 59 | var __vue_template__ = __webpack_require__(6) 60 | __vue_options__ = __vue_exports__ = __vue_exports__ || {} 61 | if ( 62 | typeof __vue_exports__.default === "object" || 63 | typeof __vue_exports__.default === "function" 64 | ) { 65 | if (Object.keys(__vue_exports__).some(function (key) { return key !== "default" && key !== "__esModule" })) {console.error("named exports are not supported in *.vue files.")} 66 | __vue_options__ = __vue_exports__ = __vue_exports__.default 67 | } 68 | if (typeof __vue_options__ === "function") { 69 | __vue_options__ = __vue_options__.options 70 | } 71 | __vue_options__.__file = "/Users/liuzhao/Documents/Git-Android/WeexList/app/src/main/weex/recommend.vue" 72 | __vue_options__.render = __vue_template__.render 73 | __vue_options__.staticRenderFns = __vue_template__.staticRenderFns 74 | __vue_options__.style = __vue_options__.style || {} 75 | __vue_styles__.forEach(function (module) { 76 | for (var name in module) { 77 | __vue_options__.style[name] = module[name] 78 | } 79 | }) 80 | 81 | module.exports = __vue_exports__ 82 | module.exports.el = 'true' 83 | new Vue(module.exports) 84 | 85 | 86 | /***/ }, 87 | /* 1 */ 88 | /***/ function(module, exports) { 89 | 90 | module.exports = { 91 | "itembg": { 92 | "backgroundColor": "#FFDDDD" 93 | }, 94 | "outterbg": { 95 | "backgroundColor": "#FFDDDD", 96 | "position": "absolute", 97 | "top": 0, 98 | "left": 0, 99 | "right": 0, 100 | "bottom": 0 101 | }, 102 | "list": { 103 | "position": "absolute", 104 | "top": 0, 105 | "left": 0, 106 | "right": 0, 107 | "bottom": 0, 108 | "flex": 1, 109 | "backgroundColor": "#FFFFFF" 110 | }, 111 | "jobname": { 112 | "lines": 1, 113 | "padding": 15, 114 | "flexDirection": "row", 115 | "justifyContent": "space-between" 116 | }, 117 | "header_image": { 118 | "width": 750, 119 | "height": 330 120 | }, 121 | "search": { 122 | "marginTop": 20, 123 | "position": "absolute", 124 | "top": 0, 125 | "left": 0, 126 | "flexDirection": "row", 127 | "backgroundColor": "rgba(0,0,0,0)", 128 | "width": 750, 129 | "paddingLeft": 30, 130 | "paddingRight": 30 131 | }, 132 | "center": { 133 | "flex": 1, 134 | "padding": 10, 135 | "flexDirection": "column", 136 | "justifyContent": "center" 137 | }, 138 | "smallimg": { 139 | "width": 20, 140 | "height": 20 141 | }, 142 | "searchimg": { 143 | "width": 35, 144 | "height": 35, 145 | "padding": 5, 146 | "alignItems": "center" 147 | }, 148 | "tv_time": { 149 | "color": "#B3B3B3", 150 | "fontSize": 30 151 | }, 152 | "tv_jobname": { 153 | "color": "#FF5555", 154 | "fontSize": 30 155 | }, 156 | "tv_salary": { 157 | "color": "#FF5A5A", 158 | "fontSize": 25 159 | }, 160 | "tv_location": { 161 | "color": "#B3B3B3", 162 | "fontSize": 28 163 | }, 164 | "lineview": { 165 | "backgroundColor": "#FFDDDD", 166 | "width": 1000, 167 | "height": 20 168 | }, 169 | "img": { 170 | "width": 60, 171 | "height": 35 172 | }, 173 | "cell": { 174 | "borderRadius": 25, 175 | "borderColor": "#000000" 176 | }, 177 | "icon": { 178 | "width": 100, 179 | "height": 100 180 | }, 181 | "loading": { 182 | "justifyContent": "center" 183 | }, 184 | "jobarea": { 185 | "lines": 1, 186 | "padding": 15, 187 | "alignItems": "center", 188 | "flexDirection": "row", 189 | "justifyContent": "flex-start" 190 | }, 191 | "indicator": { 192 | "height": 0, 193 | "color": "#888888", 194 | "fontSize": 42, 195 | "paddingTop": 20, 196 | "paddingBottom": 20, 197 | "textAlign": "center" 198 | }, 199 | "lineviewsmall": { 200 | "width": 1000, 201 | "marginLeft": 30, 202 | "height": 3, 203 | "backgroundColor": "#FFDDDD" 204 | }, 205 | "header": { 206 | "paddingBottom": 20, 207 | "backgroundColor": "#FFDDDD" 208 | }, 209 | "title": { 210 | "position": "absolute", 211 | "top": 50, 212 | "left": 10 213 | }, 214 | "register": { 215 | "position": "absolute", 216 | "right": 50, 217 | "top": 60, 218 | "padding": 10, 219 | "borderStyle": "solid", 220 | "alignItems": "center", 221 | "borderWidth": 1, 222 | "borderColor": "#FFFFFF" 223 | }, 224 | "bottom": { 225 | "position": "absolute", 226 | "top": 260 227 | }, 228 | "bottomlayout": { 229 | "width": 650, 230 | "lines": 1, 231 | "backgroundColor": "#FEADB6", 232 | "borderStyle": "solid", 233 | "borderWidth": 3, 234 | "borderColor": "#FFFFFF", 235 | "color": "#aaaaaa", 236 | "padding": 10, 237 | "marginLeft": 35, 238 | "marginBottom": 5, 239 | "flexDirection": "row" 240 | } 241 | } 242 | 243 | /***/ }, 244 | /* 2 */ 245 | /***/ function(module, exports, __webpack_require__) { 246 | 247 | 'use strict'; 248 | 249 | Object.defineProperty(exports, "__esModule", { 250 | value: true 251 | }); 252 | 253 | var _stringify = __webpack_require__(3); 254 | 255 | var _stringify2 = _interopRequireDefault(_stringify); 256 | 257 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 258 | 259 | // 260 | // 261 | // 262 | // 263 | // 264 | // 265 | // 266 | // 267 | // 268 | // 269 | // 270 | // 271 | // 272 | // 273 | // 274 | // 275 | // 276 | // 277 | // 278 | // 279 | // 280 | // 281 | // 282 | // 283 | // 284 | // 285 | // 286 | // 287 | // 288 | // 289 | // 290 | // 291 | // 292 | // 293 | // 294 | // 295 | // 296 | // 297 | // 298 | // 299 | // 300 | // 301 | // 302 | // 303 | // 304 | // 305 | // 306 | // 307 | 308 | var stream = weex.requireModule('stream'); 309 | var modal = weex.requireModule('commonmodule'); 310 | exports.default = { 311 | data: function data() { 312 | return { 313 | refreshing: false, 314 | items: [], 315 | url: 'https://app.chinahr.com/app/job/getJobRecommend', 316 | page: 1, 317 | showLoading: 'hide', 318 | loadingText: '拼命加载中' 319 | }; 320 | }, 321 | 322 | methods: { 323 | itemclick: function itemclick() { 324 | modal.toast('click'); 325 | items[0].bphoto = 'https://user-gold-cdn.xitu.io/2017/10/25/8452623131c167c204a4652a88b328f2'; 326 | }, 327 | getRecommend: function getRecommend(url) { 328 | var self = this; 329 | var data = "currpage=" + self.page + "°ree=" + 0; 330 | stream.fetch({ 331 | method: 'POST', 332 | type: 'json', 333 | url: url, 334 | headers: { 335 | 'cookie': 'PPS="lt=1524724530664&st=1522564530664<s=9b8f9587f90b&sts=dda593312559&uid=eb07b0e4789a1b57d5041767j&uname=";deviceID=F0F98C85-9F60-4BED-9A2B-0046B39A922A', 336 | 'deviceid': 'F0F98C85-9F60-4BED-9A2B-0046B39A922A', 337 | 'uid': 'eb07b0e4789a1b57d5041767j' 338 | }, 339 | body: data 340 | }, function (res) { 341 | try { 342 | self.showLoading = 'hide'; 343 | console.log('get:' + (0, _stringify2.default)(res)); 344 | var results = res.data.data; 345 | self.refreshing = false; 346 | if (Array.isArray(results)) { 347 | if (self.page == 1) { 348 | self.items = []; 349 | } 350 | ++self.page; 351 | for (var i = 0; i < results.length; i++) { 352 | if (typeof results[i].bphoto !== 'string') { 353 | results[i].bphoto = "http://ox17scdzy.bkt.clouddn.com/18-3-27/95069176.jpg"; 354 | } 355 | self.items.push(results[i]); 356 | } 357 | } 358 | modal.toast(results.length); 359 | } catch (e) { 360 | console.log('获取失败:' + (0, _stringify2.default)(res)); 361 | } 362 | }, function (res) {}); 363 | }, 364 | onloading: function onloading(event) { 365 | modal.toast('loading'); 366 | this.showLoading = 'show'; 367 | this.loadMoreData(); 368 | }, 369 | loadMoreData: function loadMoreData() { 370 | var self = this; 371 | this.getRecommend(self.url); 372 | modal.toast('执行了'); 373 | self.page++; 374 | } 375 | }, 376 | created: function created() { 377 | var self = this; 378 | self.page = 1; 379 | this.getRecommend(self.url); 380 | } 381 | }; 382 | module.exports = exports['default']; 383 | 384 | /***/ }, 385 | /* 3 */ 386 | /***/ function(module, exports, __webpack_require__) { 387 | 388 | module.exports = { "default": __webpack_require__(4), __esModule: true }; 389 | 390 | /***/ }, 391 | /* 4 */ 392 | /***/ function(module, exports, __webpack_require__) { 393 | 394 | var core = __webpack_require__(5) 395 | , $JSON = core.JSON || (core.JSON = {stringify: JSON.stringify}); 396 | module.exports = function stringify(it){ // eslint-disable-line no-unused-vars 397 | return $JSON.stringify.apply($JSON, arguments); 398 | }; 399 | 400 | /***/ }, 401 | /* 5 */ 402 | /***/ function(module, exports) { 403 | 404 | var core = module.exports = {version: '2.4.0'}; 405 | if(typeof __e == 'number')__e = core; // eslint-disable-line no-undef 406 | 407 | /***/ }, 408 | /* 6 */ 409 | /***/ function(module, exports) { 410 | 411 | module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h; 412 | return _c('div', { 413 | staticClass: ["outterbg"] 414 | }, [_c('list', { 415 | staticClass: ["list"] 416 | }, [_vm._m(0), _vm._l((_vm.items), function(item) { 417 | return _c('cell', { 418 | appendAsTree: true, 419 | attrs: { 420 | "append": "tree" 421 | }, 422 | on: { 423 | "click": _vm.itemclick 424 | } 425 | }, [_c('div', { 426 | staticClass: ["itembg"] 427 | }, [_c('div', { 428 | staticStyle: { 429 | marginLeft: "10px", 430 | marginTop: "20px", 431 | marginRight: "20px", 432 | backgroundColor: "white" 433 | } 434 | }, [_c('div', { 435 | staticClass: ["jobname"] 436 | }, [_c('text', { 437 | staticClass: ["tv_jobname"] 438 | }, [_vm._v(_vm._s(item.jobName))]), _c('text', { 439 | staticClass: ["tv_time"] 440 | }, [_vm._v("今天")])]), _c('div', { 441 | staticClass: ["jobname"] 442 | }, [_c('text', { 443 | staticClass: ["tv_salary"] 444 | }, [_vm._v(_vm._s(item.salary))]), (item.medal > 0) ? _c('image', { 445 | staticClass: ["img"], 446 | attrs: { 447 | "src": "http://ox17scdzy.bkt.clouddn.com/18-3-27/94869582.jpg" 448 | } 449 | }) : _vm._e()]), _c('div', { 450 | staticClass: ["jobarea"] 451 | }, [_c('image', { 452 | staticClass: ["smallimg"], 453 | attrs: { 454 | "src": "http://ox17scdzy.bkt.clouddn.com/18-1-12/11695618.jpg" 455 | } 456 | }), _c('text', { 457 | staticClass: ["tv_location"], 458 | staticStyle: { 459 | padding: "5px" 460 | } 461 | }, [_vm._v(_vm._s(item.workplaceList))]), _c('image', { 462 | staticClass: ["smallimg"], 463 | attrs: { 464 | "src": "http://ox17scdzy.bkt.clouddn.com/18-1-12/90428423.jpg" 465 | } 466 | }), _c('text', { 467 | staticClass: ["tv_location"], 468 | staticStyle: { 469 | padding: "5px" 470 | } 471 | }, [_vm._v(_vm._s(item.experience))]), _c('image', { 472 | staticClass: ["smallimg"], 473 | attrs: { 474 | "src": "http://ox17scdzy.bkt.clouddn.com/18-1-12/38143824.jpg" 475 | } 476 | }), _c('text', { 477 | staticClass: ["tv_location"], 478 | staticStyle: { 479 | padding: "5px" 480 | } 481 | }, [_vm._v(_vm._s(item.degree))])]), _c('div', { 482 | staticClass: ["lineviewsmall"] 483 | }), _c('div', { 484 | staticClass: ["jobarea"] 485 | }, [_c('circleImageView', { 486 | staticStyle: { 487 | width: "100", 488 | height: "100" 489 | }, 490 | attrs: { 491 | "setSrc": item.bphoto 492 | } 493 | }), _c('div', { 494 | staticClass: ["center"] 495 | }, [_c('text', { 496 | staticClass: ["tv_location"], 497 | staticStyle: { 498 | padding: "5px" 499 | } 500 | }, [_vm._v(_vm._s(item.bnickname) + "【" + _vm._s(item.bposition) + "】")]), _c('text', { 501 | staticClass: ["tv_location"] 502 | }, [_vm._v(_vm._s(item.name))])])], 1)])])]) 503 | }), _c('loading', { 504 | staticClass: ["loading"], 505 | attrs: { 506 | "display": _vm.showLoading 507 | }, 508 | on: { 509 | "loading": _vm.onloading 510 | } 511 | }, [_c('text', { 512 | staticClass: ["indicator"] 513 | }, [_vm._v("Loading ...")])])], 2), _vm._m(1)]) 514 | },staticRenderFns: [function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h; 515 | return _c('cell', { 516 | appendAsTree: true, 517 | attrs: { 518 | "append": "tree" 519 | } 520 | }, [_c('image', { 521 | staticClass: ["header_image"], 522 | attrs: { 523 | "src": "http://ox17scdzy.bkt.clouddn.com/18-3-27/87691717.jpg" 524 | } 525 | })]) 526 | },function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h; 527 | return _c('div', { 528 | staticClass: ["search"] 529 | }, [_c('text', { 530 | staticStyle: { 531 | padding: "15px", 532 | fontSize: "30px", 533 | backgroundColor: "white", 534 | color: "#000000" 535 | } 536 | }, [_vm._v("订阅+")]), _c('text', { 537 | staticStyle: { 538 | padding: "15px", 539 | fontSize: "30px", 540 | width: "600px", 541 | backgroundColor: "white", 542 | color: "#000000" 543 | } 544 | }, [_vm._v("请输入职位或公司")])]) 545 | }]} 546 | module.exports.render._withStripped = true 547 | 548 | /***/ } 549 | /******/ ]); -------------------------------------------------------------------------------- /app/src/main/assets/recommendcomponent.js: -------------------------------------------------------------------------------- 1 | // { "framework": "Vue" } 2 | /******/ (function(modules) { // webpackBootstrap 3 | /******/ // The module cache 4 | /******/ var installedModules = {}; 5 | 6 | /******/ // The require function 7 | /******/ function __webpack_require__(moduleId) { 8 | 9 | /******/ // Check if module is in cache 10 | /******/ if(installedModules[moduleId]) 11 | /******/ return installedModules[moduleId].exports; 12 | 13 | /******/ // Create a new module (and put it into the cache) 14 | /******/ var module = installedModules[moduleId] = { 15 | /******/ exports: {}, 16 | /******/ id: moduleId, 17 | /******/ loaded: false 18 | /******/ }; 19 | 20 | /******/ // Execute the module function 21 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 22 | 23 | /******/ // Flag the module as loaded 24 | /******/ module.loaded = true; 25 | 26 | /******/ // Return the exports of the module 27 | /******/ return module.exports; 28 | /******/ } 29 | 30 | 31 | /******/ // expose the modules object (__webpack_modules__) 32 | /******/ __webpack_require__.m = modules; 33 | 34 | /******/ // expose the module cache 35 | /******/ __webpack_require__.c = installedModules; 36 | 37 | /******/ // __webpack_public_path__ 38 | /******/ __webpack_require__.p = ""; 39 | 40 | /******/ // Load entry module and return exports 41 | /******/ return __webpack_require__(0); 42 | /******/ }) 43 | /************************************************************************/ 44 | /******/ ([ 45 | /* 0 */ 46 | /***/ function(module, exports, __webpack_require__) { 47 | 48 | var __vue_exports__, __vue_options__ 49 | var __vue_styles__ = [] 50 | 51 | /* styles */ 52 | __vue_styles__.push(__webpack_require__(1) 53 | ) 54 | 55 | /* script */ 56 | __vue_exports__ = __webpack_require__(2) 57 | 58 | /* template */ 59 | var __vue_template__ = __webpack_require__(6) 60 | __vue_options__ = __vue_exports__ = __vue_exports__ || {} 61 | if ( 62 | typeof __vue_exports__.default === "object" || 63 | typeof __vue_exports__.default === "function" 64 | ) { 65 | if (Object.keys(__vue_exports__).some(function (key) { return key !== "default" && key !== "__esModule" })) {console.error("named exports are not supported in *.vue files.")} 66 | __vue_options__ = __vue_exports__ = __vue_exports__.default 67 | } 68 | if (typeof __vue_options__ === "function") { 69 | __vue_options__ = __vue_options__.options 70 | } 71 | __vue_options__.__file = "/Users/liuzhao/Documents/Git-Android/WeexList/app/src/main/weex/recommend_component.vue" 72 | __vue_options__.render = __vue_template__.render 73 | __vue_options__.staticRenderFns = __vue_template__.staticRenderFns 74 | __vue_options__.style = __vue_options__.style || {} 75 | __vue_styles__.forEach(function (module) { 76 | for (var name in module) { 77 | __vue_options__.style[name] = module[name] 78 | } 79 | }) 80 | 81 | module.exports = __vue_exports__ 82 | module.exports.el = 'true' 83 | new Vue(module.exports) 84 | 85 | 86 | /***/ }, 87 | /* 1 */ 88 | /***/ function(module, exports) { 89 | 90 | module.exports = { 91 | "all": { 92 | "backgroundColor": "#FFDDDD" 93 | }, 94 | "lvitem": { 95 | "height": 110, 96 | "padding": 20, 97 | "paddingRight": 10, 98 | "alignItems": "center", 99 | "flexDirection": "row", 100 | "justifyContent": "space-between" 101 | }, 102 | "outterbg": { 103 | "backgroundColor": "#FFDDDD", 104 | "position": "absolute", 105 | "top": 0, 106 | "left": 0, 107 | "right": 0, 108 | "bottom": 0 109 | }, 110 | "list": { 111 | "position": "absolute", 112 | "top": 0, 113 | "left": 0, 114 | "right": 0, 115 | "bottom": 0, 116 | "flex": 1, 117 | "backgroundColor": "#FFFFFF" 118 | }, 119 | "jobname": { 120 | "lines": 1, 121 | "padding": 15, 122 | "flexDirection": "row", 123 | "justifyContent": "space-between" 124 | }, 125 | "header_image": { 126 | "width": 750, 127 | "height": 330 128 | }, 129 | "search": { 130 | "marginTop": 20, 131 | "position": "absolute", 132 | "top": 0, 133 | "left": 0, 134 | "flexDirection": "row", 135 | "backgroundColor": "rgba(0,0,0,0)", 136 | "width": 750, 137 | "paddingLeft": 30, 138 | "paddingRight": 30 139 | }, 140 | "center": { 141 | "flex": 1, 142 | "padding": 10, 143 | "flexDirection": "column", 144 | "justifyContent": "center" 145 | }, 146 | "smallimg": { 147 | "width": 20, 148 | "height": 20 149 | }, 150 | "searchimg": { 151 | "width": 35, 152 | "height": 35, 153 | "padding": 5, 154 | "alignItems": "center" 155 | }, 156 | "tv_time": { 157 | "color": "#B3B3B3", 158 | "fontSize": 30 159 | }, 160 | "tv_jobname": { 161 | "color": "#FF5555", 162 | "fontSize": 30 163 | }, 164 | "tv_salary": { 165 | "color": "#FF5A5A", 166 | "fontSize": 25 167 | }, 168 | "tv_location": { 169 | "color": "#B3B3B3", 170 | "fontSize": 28 171 | }, 172 | "lineview": { 173 | "backgroundColor": "#FFDDDD", 174 | "width": 1000, 175 | "height": 20 176 | }, 177 | "img": { 178 | "width": 60, 179 | "height": 35 180 | }, 181 | "cell": { 182 | "borderRadius": 25, 183 | "borderColor": "#000000" 184 | }, 185 | "icon": { 186 | "width": 100, 187 | "height": 100 188 | }, 189 | "loading": { 190 | "justifyContent": "center" 191 | }, 192 | "jobarea": { 193 | "lines": 1, 194 | "padding": 15, 195 | "alignItems": "center", 196 | "flexDirection": "row", 197 | "justifyContent": "flex-start" 198 | }, 199 | "indicator": { 200 | "height": 0, 201 | "color": "#888888", 202 | "fontSize": 42, 203 | "paddingTop": 20, 204 | "paddingBottom": 20, 205 | "textAlign": "center" 206 | }, 207 | "lineviewsmall": { 208 | "width": 1000, 209 | "marginLeft": 30, 210 | "height": 3, 211 | "backgroundColor": "#FFDDDD" 212 | }, 213 | "header": { 214 | "paddingBottom": 20, 215 | "backgroundColor": "#FFDDDD" 216 | }, 217 | "title": { 218 | "position": "absolute", 219 | "top": 50, 220 | "left": 10 221 | }, 222 | "register": { 223 | "position": "absolute", 224 | "right": 50, 225 | "top": 60, 226 | "padding": 10, 227 | "borderStyle": "solid", 228 | "alignItems": "center", 229 | "borderWidth": 1, 230 | "borderColor": "#FFFFFF" 231 | }, 232 | "bottom": { 233 | "position": "absolute", 234 | "top": 260 235 | }, 236 | "bottomlayout": { 237 | "width": 650, 238 | "lines": 1, 239 | "backgroundColor": "#FEADB6", 240 | "borderStyle": "solid", 241 | "borderWidth": 3, 242 | "borderColor": "#FFFFFF", 243 | "color": "#aaaaaa", 244 | "padding": 10, 245 | "marginLeft": 35, 246 | "marginBottom": 5, 247 | "flexDirection": "row" 248 | } 249 | } 250 | 251 | /***/ }, 252 | /* 2 */ 253 | /***/ function(module, exports, __webpack_require__) { 254 | 255 | 'use strict'; 256 | 257 | Object.defineProperty(exports, "__esModule", { 258 | value: true 259 | }); 260 | 261 | var _stringify = __webpack_require__(3); 262 | 263 | var _stringify2 = _interopRequireDefault(_stringify); 264 | 265 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 266 | 267 | // 268 | // 269 | // 270 | // 271 | // 272 | // 273 | // 274 | // 275 | // 276 | // 277 | // 278 | // 279 | // 280 | // 281 | // 282 | // 283 | // 284 | // 285 | // 286 | // 287 | // 288 | // 289 | // 290 | // 291 | // 292 | // 293 | // 294 | // 295 | // 296 | // 297 | // 298 | // 299 | // 300 | // 301 | // 302 | // 303 | // 304 | // 305 | // 306 | // 307 | // 308 | // 309 | // 310 | // 311 | // 312 | // 313 | // 314 | // 315 | // 316 | // 317 | // 318 | 319 | var stream = weex.requireModule('stream'); 320 | var modal = weex.requireModule('commonmodule'); 321 | 322 | exports.default = { 323 | data: function data() { 324 | return { 325 | refreshing: false, 326 | items: [], 327 | url: 'https://app.chinahr.com/app/job/getJobRecommend', 328 | page: 1, 329 | showLoading: 'hide', 330 | loadingText: '拼命加载中' 331 | }; 332 | }, 333 | 334 | methods: { 335 | itemclick: function itemclick() { 336 | modal.toast('click'); 337 | }, 338 | getRecommend: function getRecommend(url) { 339 | var self = this; 340 | var data = "currpage=" + self.page + "°ree=" + 0; 341 | self.showLoading = 'show'; 342 | stream.fetch({ 343 | method: 'POST', 344 | type: 'json', 345 | url: url, 346 | headers: { 347 | 'cookie': 'PPS="lt=1524724530664&st=1522564530664<s=9b8f9587f90b&sts=dda593312559&uid=eb07b0e4789a1b57d5041767j&uname=";deviceID=F0F98C85-9F60-4BED-9A2B-0046B39A922A', 348 | 'deviceid': 'F0F98C85-9F60-4BED-9A2B-0046B39A922A', 349 | 'uid': 'eb07b0e4789a1b57d5041767j' 350 | }, 351 | body: data 352 | }, function (res) { 353 | try { 354 | self.showLoading = 'hide'; 355 | console.log('get:' + (0, _stringify2.default)(res)); 356 | var results = res.data.data; 357 | self.refreshing = false; 358 | if (Array.isArray(results)) { 359 | if (self.page == 1) { 360 | self.items = []; 361 | } 362 | ++self.page; 363 | for (var i = 0; i < results.length; i++) { 364 | if (typeof results[i].bphoto !== 'string') { 365 | results[i].bphoto = "http://ox17scdzy.bkt.clouddn.com/18-3-27/95069176.jpg"; 366 | } 367 | self.items.push(results[i]); 368 | } 369 | } 370 | modal.toast('返回:' + results.length); 371 | } catch (e) { 372 | console.log('获取失败:' + (0, _stringify2.default)(res)); 373 | } 374 | }, function (res) {}); 375 | }, 376 | onloading: function onloading(event) { 377 | modal.toast('loading'); 378 | this.showLoading = 'show'; 379 | this.loadMoreData(); 380 | }, 381 | loadMoreData: function loadMoreData() { 382 | var self = this; 383 | this.getRecommend(self.url); 384 | modal.toast('执行了'); 385 | self.page++; 386 | } 387 | }, 388 | created: function created() { 389 | var self = this; 390 | self.page = 1; 391 | this.getRecommend(self.url); 392 | } 393 | }; 394 | module.exports = exports['default']; 395 | 396 | /***/ }, 397 | /* 3 */ 398 | /***/ function(module, exports, __webpack_require__) { 399 | 400 | module.exports = { "default": __webpack_require__(4), __esModule: true }; 401 | 402 | /***/ }, 403 | /* 4 */ 404 | /***/ function(module, exports, __webpack_require__) { 405 | 406 | var core = __webpack_require__(5) 407 | , $JSON = core.JSON || (core.JSON = {stringify: JSON.stringify}); 408 | module.exports = function stringify(it){ // eslint-disable-line no-unused-vars 409 | return $JSON.stringify.apply($JSON, arguments); 410 | }; 411 | 412 | /***/ }, 413 | /* 5 */ 414 | /***/ function(module, exports) { 415 | 416 | var core = module.exports = {version: '2.4.0'}; 417 | if(typeof __e == 'number')__e = core; // eslint-disable-line no-undef 418 | 419 | /***/ }, 420 | /* 6 */ 421 | /***/ function(module, exports) { 422 | 423 | module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h; 424 | return _c('div', { 425 | staticClass: ["outterbg"] 426 | }, [_c('refreshview', { 427 | ref: "refreshs", 428 | staticClass: ["list"], 429 | attrs: { 430 | "display": _vm.showLoading 431 | } 432 | }, [_c('div', { 433 | staticClass: ["list"] 434 | }, [_c('list', { 435 | staticClass: ["list"] 436 | }, [_c('cell', { 437 | appendAsTree: true, 438 | attrs: { 439 | "append": "tree" 440 | } 441 | }, [_c('image', { 442 | staticClass: ["header_image"], 443 | attrs: { 444 | "src": "http://ox17scdzy.bkt.clouddn.com/18-3-27/87691717.jpg" 445 | } 446 | })]), _vm._l((_vm.items), function(item) { 447 | return _c('cell', { 448 | appendAsTree: true, 449 | attrs: { 450 | "append": "tree" 451 | }, 452 | on: { 453 | "click": _vm.itemclick 454 | } 455 | }, [_c('div', { 456 | staticStyle: { 457 | backgroundColor: "#FFDDDD" 458 | } 459 | }, [_c('div', { 460 | staticStyle: { 461 | marginLeft: "10px", 462 | marginTop: "20px", 463 | marginRight: "20px", 464 | backgroundColor: "white" 465 | } 466 | }, [_c('div', { 467 | staticClass: ["jobname"] 468 | }, [_c('text', { 469 | staticClass: ["tv_jobname"] 470 | }, [_vm._v(_vm._s(item.jobName))]), _c('text', { 471 | staticClass: ["tv_time"] 472 | }, [_vm._v("今天")])]), _c('div', { 473 | staticClass: ["jobname"] 474 | }, [_c('text', { 475 | staticClass: ["tv_salary"] 476 | }, [_vm._v(_vm._s(item.salary))]), (item.medal > 0) ? _c('image', { 477 | staticClass: ["img"], 478 | attrs: { 479 | "src": "http://ox17scdzy.bkt.clouddn.com/18-3-27/94869582.jpg" 480 | } 481 | }) : _vm._e()]), _c('div', { 482 | staticClass: ["jobarea"] 483 | }, [_c('image', { 484 | staticClass: ["smallimg"], 485 | attrs: { 486 | "src": "http://ox17scdzy.bkt.clouddn.com/18-1-12/11695618.jpg" 487 | } 488 | }), _c('text', { 489 | staticClass: ["tv_location"], 490 | staticStyle: { 491 | padding: "5px" 492 | } 493 | }, [_vm._v(_vm._s(item.workplaceList))]), _c('image', { 494 | staticClass: ["smallimg"], 495 | attrs: { 496 | "src": "http://ox17scdzy.bkt.clouddn.com/18-1-12/90428423.jpg" 497 | } 498 | }), _c('text', { 499 | staticClass: ["tv_location"], 500 | staticStyle: { 501 | padding: "5px" 502 | } 503 | }, [_vm._v(_vm._s(item.experience))]), _c('image', { 504 | staticClass: ["smallimg"], 505 | attrs: { 506 | "src": "http://ox17scdzy.bkt.clouddn.com/18-1-12/38143824.jpg" 507 | } 508 | }), _c('text', { 509 | staticClass: ["tv_location"], 510 | staticStyle: { 511 | padding: "5px" 512 | } 513 | }, [_vm._v(_vm._s(item.degree))])]), _c('div', { 514 | staticClass: ["lineviewsmall"] 515 | }), _c('div', { 516 | staticClass: ["jobarea"] 517 | }, [_c('circleImageView', { 518 | staticStyle: { 519 | width: "100", 520 | height: "100" 521 | }, 522 | attrs: { 523 | "setSrc": item.bphoto 524 | } 525 | }), _c('div', { 526 | staticClass: ["center"] 527 | }, [_c('text', { 528 | staticClass: ["tv_location"], 529 | staticStyle: { 530 | padding: "5px" 531 | } 532 | }, [_vm._v(_vm._s(item.bnickname) + "【" + _vm._s(item.bposition) + "】")]), _c('text', { 533 | staticClass: ["tv_location"] 534 | }, [_vm._v(_vm._s(item.name))])])], 1)])])]) 535 | }), _c('loading', { 536 | staticClass: ["loading"], 537 | attrs: { 538 | "display": _vm.showLoading 539 | }, 540 | on: { 541 | "loading": _vm.onloading 542 | } 543 | }, [_c('text', { 544 | staticClass: ["indicator"] 545 | }, [_vm._v("Loading ...")])])], 2), _c('div', { 546 | staticClass: ["search"] 547 | }, [_c('text', { 548 | staticStyle: { 549 | padding: "15px", 550 | fontSize: "30px", 551 | backgroundColor: "white", 552 | color: "#000000" 553 | } 554 | }, [_vm._v("订阅+")]), _c('text', { 555 | staticStyle: { 556 | padding: "15px", 557 | fontSize: "30px", 558 | width: "600px", 559 | backgroundColor: "white", 560 | color: "#000000" 561 | } 562 | }, [_vm._v("请输入职位或公司")])])])])], 1) 563 | },staticRenderFns: []} 564 | module.exports.render._withStripped = true 565 | 566 | /***/ } 567 | /******/ ]); -------------------------------------------------------------------------------- /app/src/main/java/com/android/weexlist/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.android.weexlist.activity; 2 | 3 | import android.content.Intent; 4 | import android.support.v4.app.FragmentActivity; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | 8 | import com.android.weexlist.R; 9 | 10 | public class MainActivity extends FragmentActivity implements View.OnClickListener { 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_main); 16 | findViewById(R.id.bt_native).setOnClickListener(this); 17 | findViewById(R.id.bt_component).setOnClickListener(this); 18 | } 19 | 20 | @Override 21 | public void onClick(View v) { 22 | switch (v.getId()) { 23 | case R.id.bt_native: 24 | startActivity(new Intent(MainActivity.this,RecommendActivity.class)); 25 | break; 26 | case R.id.bt_component: 27 | startActivity(new Intent(MainActivity.this,RecommendComponentActivity.class)); 28 | break; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/weexlist/activity/RecommendActivity.java: -------------------------------------------------------------------------------- 1 | package com.android.weexlist.activity; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.support.v4.app.FragmentActivity; 6 | import android.view.View; 7 | import android.widget.FrameLayout; 8 | 9 | import com.android.weexlist.R; 10 | import com.android.weexlist.widgets.MaterialDesignPtrFrameLayout; 11 | import com.android.weexlist.widgets.WeexPtrDefaultHandler; 12 | import com.taobao.weex.IWXRenderListener; 13 | import com.taobao.weex.WXSDKInstance; 14 | import com.taobao.weex.common.WXRenderStrategy; 15 | import com.taobao.weex.utils.WXFileUtils; 16 | 17 | import in.srain.cube.views.ptr.PtrFrameLayout; 18 | 19 | /** 20 | * Created by liuzhao on 2017/9/28. 21 | */ 22 | 23 | public class RecommendActivity extends FragmentActivity implements IWXRenderListener { 24 | 25 | private WXSDKInstance mWxsdkInstance; 26 | private FrameLayout frameLayout; 27 | private static MaterialDesignPtrFrameLayout swipeRefreshLayout; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.recommendactivity); 33 | swipeRefreshLayout = (MaterialDesignPtrFrameLayout) findViewById(R.id.subscript_ptr); 34 | frameLayout = (FrameLayout) findViewById(R.id.fl_container); 35 | mWxsdkInstance = new WXSDKInstance(this); 36 | mWxsdkInstance.registerRenderListener(this); 37 | mWxsdkInstance.render("WeexListApp", WXFileUtils.loadAsset("recommend.js", this), null, null, WXRenderStrategy.APPEND_ASYNC); 38 | 39 | swipeRefreshLayout.setPtrHandler(new WeexPtrDefaultHandler() { 40 | @Override 41 | public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) { 42 | return !WeexPtrDefaultHandler.canChildScrollUp(content); 43 | } 44 | @Override 45 | public void onRefreshBegin(PtrFrameLayout frame) { 46 | new Handler().postDelayed(new Runnable() { 47 | @Override 48 | public void run() { 49 | swipeRefreshLayout.refreshComplete(); 50 | } 51 | },1000); 52 | } 53 | 54 | }); 55 | } 56 | 57 | /** 58 | * If {@link WXRenderStrategy#APPEND_ASYNC} is applied, this method 59 | * will be invoked when the rendering of first view is finish. 60 | * If {@link WXRenderStrategy#APPEND_ONCE} is applied, this method will 61 | * be invoked when the rendering of the view tree is finished. 62 | * 63 | * @param instance 64 | * @param view 65 | */ 66 | @Override 67 | public void onViewCreated(WXSDKInstance instance, View view) { 68 | frameLayout.addView(view); 69 | } 70 | 71 | /** 72 | * Called when the render view phase of weex has finished. 73 | * It can be invoked at most once in the entire life of a {@link WXSDKInstance} 74 | * 75 | * @param instance 76 | * @param width 77 | * @param height 78 | */ 79 | @Override 80 | public void onRenderSuccess(WXSDKInstance instance, int width, int height) { 81 | 82 | } 83 | 84 | /** 85 | * Callback method, called when refresh is finished 86 | * 87 | * @param instance 88 | * @param width 89 | * @param height 90 | */ 91 | @Override 92 | public void onRefreshSuccess(WXSDKInstance instance, int width, int height) { 93 | 94 | } 95 | 96 | /** 97 | * Report exception occurred when weex instance is running. Exception may not 98 | * cause user-noticeable failure of weex. 99 | * 100 | * @param instance 101 | * @param errCode 102 | * @param msg 103 | */ 104 | @Override 105 | public void onException(WXSDKInstance instance, String errCode, String msg) { 106 | 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/weexlist/activity/RecommendComponentActivity.java: -------------------------------------------------------------------------------- 1 | package com.android.weexlist.activity; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.widget.LinearLayout; 7 | 8 | import com.android.weexlist.R; 9 | import com.taobao.weex.IWXRenderListener; 10 | import com.taobao.weex.WXSDKInstance; 11 | import com.taobao.weex.common.WXRenderStrategy; 12 | import com.taobao.weex.utils.WXFileUtils; 13 | import com.taobao.weex.utils.WXLogUtils; 14 | 15 | /** 16 | * Created by liuzhao on 2017/9/28. 17 | */ 18 | 19 | public class RecommendComponentActivity extends Activity implements IWXRenderListener { 20 | private LinearLayout linearLayout; 21 | private WXSDKInstance mWxsdkInstance; 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.recommendcomponent); 27 | linearLayout = (LinearLayout) findViewById(R.id.ll_container); 28 | mWxsdkInstance = new WXSDKInstance(this); 29 | mWxsdkInstance.registerRenderListener(this); 30 | mWxsdkInstance.render("WeexListApp", WXFileUtils.loadAsset("recommendcomponent.js", this), null, null, WXRenderStrategy.APPEND_ASYNC); 31 | } 32 | 33 | 34 | /** 35 | * If {@link WXRenderStrategy#APPEND_ASYNC} is applied, this method 36 | * will be invoked when the rendering of first view is finish. 37 | * If {@link WXRenderStrategy#APPEND_ONCE} is applied, this method will 38 | * be invoked when the rendering of the view tree is finished. 39 | * 40 | * @param instance 41 | * @param view 42 | */ 43 | @Override 44 | public void onViewCreated(WXSDKInstance instance, View view) { 45 | linearLayout.addView(view); 46 | } 47 | 48 | /** 49 | * Called when the render view phase of weex has finished. 50 | * It can be invoked at most once in the entire life of a {@link WXSDKInstance} 51 | * 52 | * @param instance 53 | * @param width 54 | * @param height 55 | */ 56 | @Override 57 | public void onRenderSuccess(WXSDKInstance instance, int width, int height) { 58 | WXLogUtils.i("lz", "success"); 59 | } 60 | 61 | /** 62 | * Callback method, called when refresh is finished 63 | * 64 | * @param instance 65 | * @param width 66 | * @param height 67 | */ 68 | @Override 69 | public void onRefreshSuccess(WXSDKInstance instance, int width, int height) { 70 | 71 | } 72 | 73 | /** 74 | * Report exception occurred when weex instance is running. Exception may not 75 | * cause user-noticeable failure of weex. 76 | * 77 | * @param instance 78 | * @param errCode 79 | * @param msg 80 | */ 81 | @Override 82 | public void onException(WXSDKInstance instance, String errCode, String msg) { 83 | WXLogUtils.i("lz", "onException"); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/weexlist/adapter/WeexImageAdapter.java: -------------------------------------------------------------------------------- 1 | package com.android.weexlist.adapter; 2 | 3 | import android.widget.ImageView; 4 | 5 | import com.android.weexlist.R; 6 | import com.bumptech.glide.Glide; 7 | import com.taobao.weex.adapter.IWXImgLoaderAdapter; 8 | import com.taobao.weex.common.WXImageStrategy; 9 | import com.taobao.weex.dom.WXImageQuality; 10 | 11 | /** 12 | * Created by liuzhao on 17/3/8. 13 | */ 14 | 15 | public class WeexImageAdapter implements IWXImgLoaderAdapter { 16 | 17 | @Override 18 | public void setImage(String url, ImageView view, WXImageQuality quality, WXImageStrategy strategy) { 19 | Glide.with(view.getContext()) 20 | .load(url) 21 | .error(R.mipmap.me_image_man) 22 | .into(view); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/weexlist/app/WeexListApp.java: -------------------------------------------------------------------------------- 1 | package com.android.weexlist.app; 2 | 3 | import android.app.Application; 4 | 5 | import com.android.weexlist.adapter.WeexImageAdapter; 6 | import com.android.weexlist.components.CircleImageView; 7 | import com.android.weexlist.components.RefreshView; 8 | import com.android.weexlist.module.CommonModule; 9 | import com.taobao.weex.InitConfig; 10 | import com.taobao.weex.WXSDKEngine; 11 | import com.taobao.weex.ui.SimpleComponentHolder; 12 | 13 | /** 14 | * Created by liuzhao on 2017/9/28. 15 | */ 16 | 17 | public class WeexListApp extends Application { 18 | private static WeexListApp weexListApp; 19 | 20 | @Override 21 | public void onCreate() { 22 | super.onCreate(); 23 | weexListApp = this; 24 | try { 25 | InitConfig config = new InitConfig.Builder().setImgAdapter(new WeexImageAdapter()).build(); 26 | WXSDKEngine.registerComponent("circleImageView", CircleImageView.class); 27 | WXSDKEngine.registerComponent(new SimpleComponentHolder(RefreshView.class, 28 | new RefreshView.Ceator()) 29 | , false, "refreshview"); 30 | WXSDKEngine.registerModule("commonmodule", CommonModule.class); 31 | WXSDKEngine.initialize(this, config); 32 | } catch (Exception e) { 33 | e.printStackTrace(); 34 | } 35 | } 36 | 37 | public static WeexListApp getApp() { 38 | return weexListApp; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/weexlist/components/CircleImageView.java: -------------------------------------------------------------------------------- 1 | package com.android.weexlist.components; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import android.widget.ImageView; 6 | 7 | import com.android.weexlist.glide.GlideCircleTransform; 8 | import com.bumptech.glide.Glide; 9 | import com.taobao.weex.WXSDKInstance; 10 | import com.taobao.weex.dom.WXDomObject; 11 | import com.taobao.weex.ui.component.WXComponent; 12 | import com.taobao.weex.ui.component.WXComponentProp; 13 | import com.taobao.weex.ui.component.WXVContainer; 14 | 15 | /** 16 | * Created by liuzhao on 17/3/22. 17 | */ 18 | 19 | public class CircleImageView extends WXComponent { 20 | 21 | public CircleImageView(WXSDKInstance instance, WXDomObject dom, WXVContainer parent) { 22 | super(instance, dom, parent); 23 | } 24 | 25 | @Override 26 | protected ImageView initComponentHostView(@NonNull Context context) { 27 | ImageView view = new ImageView(context); 28 | return view; 29 | } 30 | 31 | @WXComponentProp(name = "setSrc") 32 | public void setImage(String url) { 33 | Glide.with(getContext()).load(url) 34 | .transform(new GlideCircleTransform(getContext())).into((ImageView) getHostView()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/weexlist/components/RefreshView.java: -------------------------------------------------------------------------------- 1 | package com.android.weexlist.components; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.support.annotation.NonNull; 6 | import android.support.v4.view.ViewCompat; 7 | import android.text.TextUtils; 8 | import android.util.Log; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.AbsListView; 12 | import android.widget.FrameLayout; 13 | 14 | import com.android.weexlist.widgets.MaterialDesignPtrFrameLayout; 15 | import com.taobao.weex.WXSDKInstance; 16 | import com.taobao.weex.dom.WXDomObject; 17 | import com.taobao.weex.ui.ComponentCreator; 18 | import com.taobao.weex.ui.component.WXComponent; 19 | import com.taobao.weex.ui.component.WXComponentProp; 20 | import com.taobao.weex.ui.component.WXVContainer; 21 | import com.taobao.weex.ui.view.refresh.wrapper.BounceRecyclerView; 22 | 23 | import java.lang.reflect.InvocationTargetException; 24 | 25 | import in.srain.cube.views.ptr.PtrDefaultHandler; 26 | import in.srain.cube.views.ptr.PtrFrameLayout; 27 | 28 | /** 29 | * Created by liuzhao on 17/3/21. 30 | */ 31 | 32 | public class RefreshView extends WXVContainer { 33 | public RefreshView(WXSDKInstance instance, WXDomObject dom, WXVContainer parent) { 34 | super(instance, dom, parent); 35 | } 36 | 37 | public static class Ceator implements ComponentCreator { 38 | public WXComponent createInstance(WXSDKInstance instance, WXDomObject node, WXVContainer parent) throws IllegalAccessException, InvocationTargetException, InstantiationException { 39 | return new RefreshView(instance, node, parent); 40 | } 41 | } 42 | 43 | @Override 44 | protected MaterialDesignPtrFrameLayout initComponentHostView(@NonNull Context context) { 45 | final MaterialDesignPtrFrameLayout materialDesignPtrFrameLayout = new MaterialDesignPtrFrameLayout(context); 46 | materialDesignPtrFrameLayout.setPinContent(true);//设置内容不动。 47 | materialDesignPtrFrameLayout.setPtrHandler(new PtrDefaultHandler() { 48 | @Override 49 | public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) { 50 | View realView = content; 51 | if (realView instanceof ViewGroup) { 52 | FrameLayout frameLayout = (FrameLayout) realView; 53 | realView = ((BounceRecyclerView)frameLayout.getChildAt(0)).getInnerView(); 54 | } 55 | if (android.os.Build.VERSION.SDK_INT < 14) { 56 | if (realView instanceof AbsListView) { 57 | final AbsListView absListView = (AbsListView) realView; 58 | return !(absListView.getChildCount() > 0 59 | && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) 60 | .getTop() < absListView.getPaddingTop())); 61 | } else { 62 | return !(realView.getScrollY() > 0); 63 | } 64 | } else { 65 | return !ViewCompat.canScrollVertically(realView, -1); 66 | } 67 | } 68 | @Override 69 | public void onRefreshBegin(PtrFrameLayout frame) { 70 | new Handler().postDelayed(new Runnable() { 71 | @Override 72 | public void run() { 73 | materialDesignPtrFrameLayout.refreshComplete(); 74 | } 75 | },1000); 76 | } 77 | }); 78 | 79 | return materialDesignPtrFrameLayout; 80 | } 81 | 82 | @WXComponentProp(name = "display") 83 | public void setRefreshStatus(String refreshStatus) { 84 | if (TextUtils.equals("show", refreshStatus)) { 85 | ((MaterialDesignPtrFrameLayout) getHostView()).postDelayed(new Runnable() { 86 | @Override 87 | public void run() { 88 | ((MaterialDesignPtrFrameLayout) getHostView()).autoRefresh(true); 89 | } 90 | }, 100); 91 | } else if (TextUtils.equals("hide", refreshStatus)) { 92 | ((MaterialDesignPtrFrameLayout) getHostView()).refreshComplete(); 93 | } 94 | } 95 | 96 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android/weexlist/glide/GlideCircleTransform.java: -------------------------------------------------------------------------------- 1 | package com.android.weexlist.glide; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapShader; 6 | import android.graphics.Canvas; 7 | import android.graphics.Paint; 8 | 9 | import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; 10 | import com.bumptech.glide.load.resource.bitmap.BitmapTransformation; 11 | 12 | /** 13 | * Created by liuzhao on 17/3/22. 14 | */ 15 | 16 | public class GlideCircleTransform extends BitmapTransformation { 17 | public GlideCircleTransform(Context context) { 18 | super(context); 19 | } 20 | 21 | @Override 22 | protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) { 23 | return circleCrop(pool, toTransform); 24 | } 25 | 26 | private static Bitmap circleCrop(BitmapPool pool, Bitmap source) { 27 | if (source == null) return null; 28 | 29 | int size = Math.min(source.getWidth(), source.getHeight()); 30 | int x = (source.getWidth() - size) / 2; 31 | int y = (source.getHeight() - size) / 2; 32 | 33 | // TODO this could be acquired from the pool too 34 | Bitmap squared = Bitmap.createBitmap(source, x, y, size, size); 35 | 36 | Bitmap result = pool.get(size, size, Bitmap.Config.ARGB_8888); 37 | if (result == null) { 38 | result = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); 39 | } 40 | 41 | Canvas canvas = new Canvas(result); 42 | Paint paint = new Paint(); 43 | paint.setShader(new BitmapShader(squared, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP)); 44 | paint.setAntiAlias(true); 45 | float r = size / 2f; 46 | canvas.drawCircle(r, r, r, paint); 47 | return result; 48 | } 49 | 50 | @Override 51 | public String getId() { 52 | return getClass().getName(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/weexlist/module/CommonModule.java: -------------------------------------------------------------------------------- 1 | package com.android.weexlist.module; 2 | 3 | import android.widget.Toast; 4 | 5 | import com.android.weexlist.app.WeexListApp; 6 | import com.taobao.weex.annotation.JSMethod; 7 | import com.taobao.weex.common.WXModule; 8 | 9 | /** 10 | * Created by liuzhao on 2017/10/23. 11 | * 12 | * 展示自定义Module使用 13 | * 14 | */ 15 | public class CommonModule extends WXModule { 16 | 17 | @JSMethod(uiThread = true) 18 | public void toast(String message) { 19 | Toast.makeText(WeexListApp.getApp(), "" + message, 0).show(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/weexlist/widgets/MaterialDesignPtrFrameLayout.java: -------------------------------------------------------------------------------- 1 | package com.android.weexlist.widgets; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.util.Log; 6 | import android.view.View; 7 | 8 | import com.android.weexlist.R; 9 | import com.taobao.weex.ui.view.gesture.WXGesture; 10 | 11 | import in.srain.cube.views.ptr.PtrFrameLayout; 12 | 13 | /** 14 | * Created by liuzhao on 16/6/4. 15 | */ 16 | public class MaterialDesignPtrFrameLayout extends PtrFrameLayout { 17 | 18 | private PtrFrameLayout mPtrFrameLayout; 19 | 20 | private WXGesture wxGesture; 21 | /** 22 | * @param context 23 | */ 24 | public MaterialDesignPtrFrameLayout(Context context) { 25 | this(context, null); 26 | } 27 | 28 | @Override 29 | protected void onFinishInflate() { 30 | super.onFinishInflate(); 31 | Log.i("lz","onFinishInflate"); 32 | } 33 | 34 | public MaterialDesignPtrFrameLayout(Context context, AttributeSet attrs) { 35 | super(context, attrs); 36 | mPtrFrameLayout = this; 37 | // header 38 | final WeexMaterialHeader header = new WeexMaterialHeader(getContext()); 39 | int[] colors = getResources().getIntArray(R.array.google_colors); 40 | header.setColorSchemeColors(colors); 41 | header.setLayoutParams(new LayoutParams(-1, -2)); 42 | header.setPadding(0, 20, 0, 20); 43 | header.setPtrFrameLayout(mPtrFrameLayout); 44 | 45 | mPtrFrameLayout.setLoadingMinTime(700); 46 | mPtrFrameLayout.setDurationToCloseHeader(300); 47 | mPtrFrameLayout.setHeaderView(header); 48 | mPtrFrameLayout.addPtrUIHandler(header); 49 | mPtrFrameLayout.setPinContent(true); 50 | 51 | //页面加载时候自动刷新 52 | // mPtrFrameLayout.postDelayed(new Runnable() { 53 | // @Override 54 | // public void run() { 55 | // mPtrFrameLayout.autoRefresh(true); 56 | // } 57 | // }, 100); 58 | mPtrFrameLayout.setResistance(1.5f);//阻尼系数 默认: 1.7f,越大,感觉下拉时越吃力。 59 | disableWhenHorizontalMove(true); 60 | } 61 | 62 | @Override 63 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 64 | if(mContent == null){ 65 | getContentViewOut(); 66 | }else{ 67 | Log.i("lz","已经有了"); 68 | } 69 | 70 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 71 | } 72 | // 73 | // @Override 74 | // protected void onLayout(boolean flag, int i, int j, int k, int l) { 75 | // super.onLayout(flag, i, j, k, l); 76 | // if (mContent != null) { 77 | // MarginLayoutParams lp = (MarginLayoutParams) mContent.getLayoutParams(); 78 | // final int left = 0; 79 | // final int top = 0; 80 | // final int right = left + 720; 81 | // final int bottom = top + 1080; 82 | // mContent.layout(left, top, right, bottom); 83 | // } 84 | // } 85 | // 86 | private void getContentViewOut(){ 87 | for (int i = 0; i < getChildCount(); i++) { 88 | View child = getChildAt(i); 89 | if (!child.equals(getHeaderView())) { 90 | mContent = child; 91 | Log.i("lz","成功获取到了mContent"); 92 | break; 93 | } 94 | } 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/weexlist/widgets/WeexMaterialHeader.java: -------------------------------------------------------------------------------- 1 | package com.android.weexlist.widgets; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Rect; 7 | import android.graphics.drawable.Drawable; 8 | import android.util.AttributeSet; 9 | import android.view.View; 10 | import android.view.animation.Animation; 11 | import android.view.animation.Transformation; 12 | 13 | import in.srain.cube.views.ptr.PtrFrameLayout; 14 | import in.srain.cube.views.ptr.PtrUIHandler; 15 | import in.srain.cube.views.ptr.PtrUIHandlerHook; 16 | import in.srain.cube.views.ptr.indicator.PtrIndicator; 17 | 18 | /** 19 | * Created by liuzhao on 16/6/7. 20 | */ 21 | public class WeexMaterialHeader extends View implements PtrUIHandler { 22 | private WeexMaterialProgressDrawable mDrawable; 23 | private float mScale = 1f; 24 | private PtrFrameLayout mPtrFrameLayout; 25 | 26 | private Animation mScaleAnimation = new Animation() { 27 | @Override 28 | public void applyTransformation(float interpolatedTime, Transformation t) { 29 | mScale = 1f - interpolatedTime; 30 | mDrawable.setAlpha((int) (255 * mScale)); 31 | invalidate(); 32 | } 33 | }; 34 | 35 | public WeexMaterialHeader(Context context) { 36 | super(context); 37 | initView(); 38 | } 39 | 40 | public WeexMaterialHeader(Context context, AttributeSet attrs) { 41 | super(context, attrs); 42 | initView(); 43 | } 44 | 45 | public WeexMaterialHeader(Context context, AttributeSet attrs, int defStyleAttr) { 46 | super(context, attrs, defStyleAttr); 47 | initView(); 48 | } 49 | 50 | public void setPtrFrameLayout(PtrFrameLayout layout) { 51 | 52 | final PtrUIHandlerHook mPtrUIHandlerHook = new PtrUIHandlerHook() { 53 | @Override 54 | public void run() { 55 | startAnimation(mScaleAnimation); 56 | } 57 | }; 58 | 59 | mScaleAnimation.setDuration(200); 60 | mScaleAnimation.setAnimationListener(new Animation.AnimationListener() { 61 | @Override 62 | public void onAnimationStart(Animation animation) { 63 | 64 | } 65 | 66 | @Override 67 | public void onAnimationEnd(Animation animation) { 68 | mPtrUIHandlerHook.resume(); 69 | } 70 | 71 | @Override 72 | public void onAnimationRepeat(Animation animation) { 73 | 74 | } 75 | }); 76 | 77 | mPtrFrameLayout = layout; 78 | mPtrFrameLayout.setRefreshCompleteHook(mPtrUIHandlerHook); 79 | } 80 | 81 | private void initView() { 82 | mDrawable = new WeexMaterialProgressDrawable(getContext(), this); 83 | mDrawable.setBackgroundColor(Color.WHITE); 84 | mDrawable.setCallback(this); 85 | } 86 | 87 | @Override 88 | public void invalidateDrawable(Drawable dr) { 89 | if (dr == mDrawable) { 90 | invalidate(); 91 | } else { 92 | super.invalidateDrawable(dr); 93 | } 94 | } 95 | 96 | public void setColorSchemeColors(int[] colors) { 97 | mDrawable.setColorSchemeColors(colors); 98 | invalidate(); 99 | } 100 | 101 | @Override 102 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 103 | int height = mDrawable.getIntrinsicHeight() + getPaddingTop() + getPaddingBottom(); 104 | heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); 105 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 106 | } 107 | 108 | @Override 109 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 110 | final int size = mDrawable.getIntrinsicHeight(); 111 | mDrawable.setBounds(0, 0, size, size); 112 | } 113 | 114 | @Override 115 | protected void onDraw(Canvas canvas) { 116 | final int saveCount = canvas.save(); 117 | Rect rect = mDrawable.getBounds(); 118 | int l = getPaddingLeft() + (getMeasuredWidth() - mDrawable.getIntrinsicWidth()) / 2; 119 | canvas.translate(l, getPaddingTop()); 120 | canvas.scale(mScale, mScale, rect.exactCenterX(), rect.exactCenterY()); 121 | mDrawable.draw(canvas); 122 | canvas.restoreToCount(saveCount); 123 | } 124 | 125 | /** 126 | * When the content view has reached top and refresh has been completed, view will be reset. 127 | * 128 | * @param frame 129 | */ 130 | @Override 131 | public void onUIReset(PtrFrameLayout frame) { 132 | mScale = 1f; 133 | mDrawable.stop(); 134 | } 135 | 136 | /** 137 | * prepare for loading 138 | * 139 | * @param frame 140 | */ 141 | @Override 142 | public void onUIRefreshPrepare(PtrFrameLayout frame) { 143 | } 144 | 145 | /** 146 | * perform refreshing UI 147 | * 148 | * @param frame 149 | */ 150 | @Override 151 | public void onUIRefreshBegin(PtrFrameLayout frame) { 152 | mDrawable.setAlpha(255); 153 | mDrawable.start(); 154 | } 155 | 156 | /** 157 | * perform UI after refresh 158 | * 159 | * @param frame 160 | */ 161 | @Override 162 | public void onUIRefreshComplete(PtrFrameLayout frame) { 163 | mDrawable.stop(); 164 | } 165 | 166 | @Override 167 | public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) { 168 | 169 | float percent = Math.min(1f, ptrIndicator.getCurrentPercent()); 170 | 171 | if (status == PtrFrameLayout.PTR_STATUS_PREPARE) { 172 | mDrawable.setAlpha((int) (255 * percent)); 173 | mDrawable.showArrow(true); 174 | 175 | float strokeStart = ((percent) * .8f); 176 | mDrawable.setStartEndTrim(0f, Math.min(0.8f, strokeStart)); 177 | mDrawable.setArrowScale(Math.min(1f, percent)); 178 | 179 | // magic 180 | float rotation = (-0.25f + .4f * percent + percent * 2) * .5f; 181 | mDrawable.setProgressRotation(rotation); 182 | invalidate(); 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/weexlist/widgets/WeexMaterialProgressDrawable.java: -------------------------------------------------------------------------------- 1 | package com.android.weexlist.widgets; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.ColorFilter; 8 | import android.graphics.Paint; 9 | import android.graphics.Path; 10 | import android.graphics.PixelFormat; 11 | import android.graphics.RadialGradient; 12 | import android.graphics.Rect; 13 | import android.graphics.RectF; 14 | import android.graphics.Shader; 15 | import android.graphics.drawable.Animatable; 16 | import android.graphics.drawable.Drawable; 17 | import android.graphics.drawable.ShapeDrawable; 18 | import android.graphics.drawable.shapes.OvalShape; 19 | import android.os.Build; 20 | import android.util.DisplayMetrics; 21 | import android.view.View; 22 | import android.view.animation.AccelerateDecelerateInterpolator; 23 | import android.view.animation.Animation; 24 | import android.view.animation.Interpolator; 25 | import android.view.animation.LinearInterpolator; 26 | import android.view.animation.Transformation; 27 | 28 | import java.util.ArrayList; 29 | 30 | import in.srain.cube.views.ptr.util.PtrLocalDisplay; 31 | 32 | /** 33 | * Created by liuzhao on 16/6/7. 34 | */ 35 | public class WeexMaterialProgressDrawable extends Drawable implements Animatable { 36 | 37 | 38 | // Maps to ProgressBar.Large style 39 | public static final int LARGE = 0; 40 | // Maps to ProgressBar default style 41 | public static final int DEFAULT = 1; 42 | private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); 43 | private static final Interpolator END_CURVE_INTERPOLATOR = new EndCurveInterpolator(); 44 | private static final Interpolator START_CURVE_INTERPOLATOR = new StartCurveInterpolator(); 45 | private static final Interpolator EASE_INTERPOLATOR = new AccelerateDecelerateInterpolator(); 46 | // Maps to ProgressBar default style 47 | // private static final int CIRCLE_DIAMETER = 36; 48 | // private static final float CENTER_RADIUS = 5.75f; //should add up to 10 when + stroke_width 49 | // private static final float STROKE_WIDTH = 1.5f; 50 | 51 | private static final int CIRCLE_DIAMETER = 36; 52 | private static final float CENTER_RADIUS = 8.05f; //should add up to 10 when + stroke_width 53 | private static final float STROKE_WIDTH = 2.5f; 54 | // Maps to ProgressBar.Large style 55 | private static final int CIRCLE_DIAMETER_LARGE = 56; 56 | private static final float CENTER_RADIUS_LARGE = 12.5f; 57 | private static final float STROKE_WIDTH_LARGE = 3f; 58 | /** 59 | * The duration of a single progress spin in milliseconds. 60 | */ 61 | private static final int ANIMATION_DURATION = 1000 * 80 / 60; 62 | /** 63 | * The number of points in the progress "star". 64 | */ 65 | private static final float NUM_POINTS = 5f; 66 | /** 67 | * Layout info for the arrowhead in dp 68 | */ 69 | private static final int ARROW_WIDTH = 10; 70 | private static final int ARROW_HEIGHT = 5; 71 | private static final float ARROW_OFFSET_ANGLE = 5; 72 | /** 73 | * Layout info for the arrowhead for the large spinner in dp 74 | */ 75 | private static final int ARROW_WIDTH_LARGE = 12; 76 | private static final int ARROW_HEIGHT_LARGE = 6; 77 | private static final float MAX_PROGRESS_ARC = .8f; 78 | private static final int KEY_SHADOW_COLOR = 0x1E000000; 79 | private static final int FILL_SHADOW_COLOR = 0x3D000000; 80 | private static final float SHADOW_RADIUS = 3.5f; 81 | private static final float X_OFFSET = 0f; 82 | private static final float Y_OFFSET = 1.75f; 83 | private final int[] COLORS = new int[]{ 84 | // R.color.refresh_color, 85 | // R.color.refresh_color, 86 | // R.color.refresh_color, 87 | // R.color.refresh_color 88 | 89 | 90 | 0xFFC93437, 91 | 0xFF375BF1, 92 | 0xFFF7D23E, 93 | 0xFF34A350 94 | }; 95 | /** 96 | * The data of animators operating on this drawable. 97 | */ 98 | private final ArrayList mAnimators = new ArrayList(); 99 | /** 100 | * The indicator ring, used to manage animation state. 101 | */ 102 | private final Ring mRing; 103 | private final Callback mCallback = new Callback() { 104 | @Override 105 | public void invalidateDrawable(Drawable d) { 106 | invalidateSelf(); 107 | } 108 | 109 | @Override 110 | public void scheduleDrawable(Drawable d, Runnable what, long when) { 111 | scheduleSelf(what, when); 112 | } 113 | 114 | @Override 115 | public void unscheduleDrawable(Drawable d, Runnable what) { 116 | unscheduleSelf(what); 117 | } 118 | }; 119 | /** 120 | * Canvas rotation in degrees. 121 | */ 122 | private float mRotation; 123 | private Resources mResources; 124 | private View mParent; 125 | private Animation mAnimation; 126 | private float mRotationCount; 127 | private double mWidth; 128 | private double mHeight; 129 | private Animation mFinishAnimation; 130 | private int mBackgroundColor; 131 | private ShapeDrawable mShadow; 132 | 133 | public WeexMaterialProgressDrawable(Context context, View parent) { 134 | mParent = parent; 135 | mResources = context.getResources(); 136 | mRing = new Ring(mCallback); 137 | mRing.setColors(COLORS); 138 | updateSizes(DEFAULT); 139 | setupAnimators(); 140 | } 141 | 142 | private void setSizeParameters(double progressCircleWidth, double progressCircleHeight, 143 | double centerRadius, double strokeWidth, float arrowWidth, float arrowHeight) { 144 | final Ring ring = mRing; 145 | final DisplayMetrics metrics = mResources.getDisplayMetrics(); 146 | final float screenDensity = metrics.density; 147 | mWidth = progressCircleWidth * screenDensity; 148 | mHeight = progressCircleHeight * screenDensity; 149 | ring.setStrokeWidth((float) strokeWidth * screenDensity); 150 | ring.setCenterRadius(centerRadius * screenDensity); 151 | ring.setColorIndex(0); 152 | ring.setArrowDimensions(arrowWidth * screenDensity, arrowHeight * screenDensity); 153 | ring.setInsets((int) mWidth, (int) mHeight); 154 | setUp(mWidth); 155 | } 156 | 157 | private void setUp(final double diameter) { 158 | PtrLocalDisplay.init(mParent.getContext()); 159 | final int shadowYOffset = PtrLocalDisplay.dp2px(Y_OFFSET); 160 | final int shadowXOffset = PtrLocalDisplay.dp2px(X_OFFSET); 161 | int mShadowRadius = PtrLocalDisplay.dp2px(SHADOW_RADIUS); 162 | OvalShape oval = new OvalShadow(mShadowRadius, (int) diameter); 163 | mShadow = new ShapeDrawable(oval); 164 | if (Build.VERSION.SDK_INT >= 11) { 165 | mParent.setLayerType(View.LAYER_TYPE_SOFTWARE, mShadow.getPaint()); 166 | } 167 | mShadow.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset, KEY_SHADOW_COLOR); 168 | } 169 | 170 | /** 171 | * Set the overall size for the progress spinner. This updates the radius 172 | * and stroke width of the ring. 173 | * 174 | * @param size One of {@link MaterialProgressDrawable#LARGE} or 175 | * {@link MaterialProgressDrawable#DEFAULT} 176 | */ 177 | public void updateSizes(int size) { 178 | if (size == LARGE) { 179 | setSizeParameters(CIRCLE_DIAMETER_LARGE, CIRCLE_DIAMETER_LARGE, CENTER_RADIUS_LARGE, 180 | STROKE_WIDTH_LARGE, ARROW_WIDTH_LARGE, ARROW_HEIGHT_LARGE); 181 | } else { 182 | setSizeParameters(CIRCLE_DIAMETER, CIRCLE_DIAMETER, CENTER_RADIUS, STROKE_WIDTH, 183 | ARROW_WIDTH, ARROW_HEIGHT); 184 | } 185 | } 186 | 187 | /** 188 | * @param show Set to true to display the arrowhead on the progress spinner. 189 | */ 190 | public void showArrow(boolean show) { 191 | mRing.setShowArrow(show); 192 | } 193 | 194 | /** 195 | * @param scale Set the scale of the arrowhead for the spinner. 196 | */ 197 | public void setArrowScale(float scale) { 198 | mRing.setArrowScale(scale); 199 | } 200 | 201 | /** 202 | * Set the start and end trim for the progress spinner arc. 203 | * 204 | * @param startAngle start angle 205 | * @param endAngle end angle 206 | */ 207 | public void setStartEndTrim(float startAngle, float endAngle) { 208 | mRing.setStartTrim(startAngle); 209 | mRing.setEndTrim(endAngle); 210 | } 211 | 212 | /** 213 | * Set the amount of rotation to apply to the progress spinner. 214 | * 215 | * @param rotation Rotation is from [0..1] 216 | */ 217 | public void setProgressRotation(float rotation) { 218 | mRing.setRotation(rotation); 219 | } 220 | 221 | /** 222 | * Update the background color of the circle image view. 223 | */ 224 | public void setBackgroundColor(int color) { 225 | mBackgroundColor = color; 226 | mRing.setBackgroundColor(color); 227 | } 228 | 229 | /** 230 | * Set the colors used in the progress animation from color resources. 231 | * The first color will also be the color of the bar that grows in response 232 | * to a user swipe gesture. 233 | * 234 | * @param colors 235 | */ 236 | public void setColorSchemeColors(int... colors) { 237 | mRing.setColors(colors); 238 | mRing.setColorIndex(0); 239 | } 240 | 241 | @Override 242 | public int getIntrinsicHeight() { 243 | return (int) mHeight; 244 | } 245 | 246 | @Override 247 | public int getIntrinsicWidth() { 248 | return (int) mWidth; 249 | } 250 | 251 | @Override 252 | public void draw(Canvas c) { 253 | if (mShadow != null) { 254 | mShadow.getPaint().setColor(mBackgroundColor); 255 | mShadow.draw(c); 256 | } 257 | 258 | final Rect bounds = getBounds(); 259 | final int saveCount = c.save(); 260 | c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY()); 261 | mRing.draw(c, bounds); 262 | c.restoreToCount(saveCount); 263 | } 264 | 265 | public int getAlpha() { 266 | return mRing.getAlpha(); 267 | } 268 | 269 | @Override 270 | public void setAlpha(int alpha) { 271 | mRing.setAlpha(alpha); 272 | } 273 | 274 | @Override 275 | public void setColorFilter(ColorFilter colorFilter) { 276 | mRing.setColorFilter(colorFilter); 277 | } 278 | 279 | @SuppressWarnings("unused") 280 | private float getRotation() { 281 | return mRotation; 282 | } 283 | 284 | @SuppressWarnings("unused") 285 | void setRotation(float rotation) { 286 | mRotation = rotation; 287 | invalidateSelf(); 288 | } 289 | 290 | @Override 291 | public int getOpacity() { 292 | return PixelFormat.TRANSLUCENT; 293 | } 294 | 295 | @Override 296 | public boolean isRunning() { 297 | final ArrayList animators = mAnimators; 298 | final int N = animators.size(); 299 | for (int i = 0; i < N; i++) { 300 | final Animation animator = animators.get(i); 301 | if (animator.hasStarted() && !animator.hasEnded()) { 302 | return true; 303 | } 304 | } 305 | return false; 306 | } 307 | 308 | @Override 309 | public void start() { 310 | mAnimation.reset(); 311 | mRing.storeOriginals(); 312 | // Already showing some part of the ring 313 | if (mRing.getEndTrim() != mRing.getStartTrim()) { 314 | mParent.startAnimation(mFinishAnimation); 315 | } else { 316 | mRing.setColorIndex(0); 317 | mRing.resetOriginals(); 318 | mParent.startAnimation(mAnimation); 319 | } 320 | } 321 | 322 | @Override 323 | public void stop() { 324 | mParent.clearAnimation(); 325 | setRotation(0); 326 | mRing.setShowArrow(false); 327 | mRing.setColorIndex(0); 328 | mRing.resetOriginals(); 329 | } 330 | 331 | private void setupAnimators() { 332 | final Ring ring = mRing; 333 | final Animation finishRingAnimation = new Animation() { 334 | public void applyTransformation(float interpolatedTime, Transformation t) { 335 | // shrink back down and complete a full rotation before starting other circles 336 | // Rotation goes between [0..1]. 337 | float targetRotation = (float) (Math.floor(ring.getStartingRotation() 338 | / MAX_PROGRESS_ARC) + 1f); 339 | final float startTrim = ring.getStartingStartTrim() 340 | + (ring.getStartingEndTrim() - ring.getStartingStartTrim()) 341 | * interpolatedTime; 342 | ring.setStartTrim(startTrim); 343 | final float rotation = ring.getStartingRotation() 344 | + ((targetRotation - ring.getStartingRotation()) * interpolatedTime); 345 | ring.setRotation(rotation); 346 | ring.setArrowScale(1 - interpolatedTime); 347 | } 348 | }; 349 | finishRingAnimation.setInterpolator(EASE_INTERPOLATOR); 350 | finishRingAnimation.setDuration(ANIMATION_DURATION / 2); 351 | finishRingAnimation.setAnimationListener(new Animation.AnimationListener() { 352 | @Override 353 | public void onAnimationStart(Animation animation) { 354 | } 355 | 356 | @Override 357 | public void onAnimationEnd(Animation animation) { 358 | ring.goToNextColor(); 359 | ring.storeOriginals(); 360 | ring.setShowArrow(false); 361 | mParent.startAnimation(mAnimation); 362 | } 363 | 364 | @Override 365 | public void onAnimationRepeat(Animation animation) { 366 | } 367 | }); 368 | final Animation animation = new Animation() { 369 | @Override 370 | public void applyTransformation(float interpolatedTime, Transformation t) { 371 | // The minProgressArc is calculated from 0 to create an angle that 372 | // matches the stroke width. 373 | final float minProgressArc = (float) Math.toRadians(ring.getStrokeWidth() 374 | / (2 * Math.PI * ring.getCenterRadius())); 375 | final float startingEndTrim = ring.getStartingEndTrim(); 376 | final float startingTrim = ring.getStartingStartTrim(); 377 | final float startingRotation = ring.getStartingRotation(); 378 | // Offset the minProgressArc to where the endTrim is located. 379 | final float minArc = MAX_PROGRESS_ARC - minProgressArc; 380 | final float endTrim = startingEndTrim 381 | + (minArc * START_CURVE_INTERPOLATOR.getInterpolation(interpolatedTime)); 382 | ring.setEndTrim(endTrim); 383 | final float startTrim = startingTrim 384 | + (MAX_PROGRESS_ARC * END_CURVE_INTERPOLATOR 385 | .getInterpolation(interpolatedTime)); 386 | ring.setStartTrim(startTrim); 387 | final float rotation = startingRotation + (0.25f * interpolatedTime); 388 | ring.setRotation(rotation); 389 | float groupRotation = ((720.0f / NUM_POINTS) * interpolatedTime) 390 | + (720.0f * (mRotationCount / NUM_POINTS)); 391 | setRotation(groupRotation); 392 | } 393 | }; 394 | animation.setRepeatCount(Animation.INFINITE); 395 | animation.setRepeatMode(Animation.RESTART); 396 | animation.setInterpolator(LINEAR_INTERPOLATOR); 397 | animation.setDuration(ANIMATION_DURATION); 398 | animation.setAnimationListener(new Animation.AnimationListener() { 399 | @Override 400 | public void onAnimationStart(Animation animation) { 401 | mRotationCount = 0; 402 | } 403 | 404 | @Override 405 | public void onAnimationEnd(Animation animation) { 406 | // do nothing 407 | } 408 | 409 | @Override 410 | public void onAnimationRepeat(Animation animation) { 411 | ring.storeOriginals(); 412 | ring.goToNextColor(); 413 | ring.setStartTrim(ring.getEndTrim()); 414 | mRotationCount = (mRotationCount + 1) % (NUM_POINTS); 415 | } 416 | }); 417 | mFinishAnimation = finishRingAnimation; 418 | mAnimation = animation; 419 | } 420 | 421 | private static class Ring { 422 | private final RectF mTempBounds = new RectF(); 423 | private final Paint mArcPaint = new Paint(); 424 | private final Paint mArrowPaint = new Paint(); 425 | private final Callback mRingCallback; 426 | private final Paint mCirclePaint = new Paint(); 427 | private float mStartTrim = 0.0f; 428 | private float mEndTrim = 0.0f; 429 | private float mRotation = 0.0f; 430 | private float mStrokeWidth = 5.0f; 431 | private float mStrokeInset = 2.5f; 432 | private int[] mColors; 433 | // mColorIndex represents the offset into the available mColors that the 434 | // progress circle should currently display. As the progress circle is 435 | // animating, the mColorIndex moves by one to the next available color. 436 | private int mColorIndex; 437 | private float mStartingStartTrim; 438 | private float mStartingEndTrim; 439 | private float mStartingRotation; 440 | private boolean mShowArrow; 441 | private Path mArrow; 442 | private float mArrowScale; 443 | private double mRingCenterRadius; 444 | private int mArrowWidth; 445 | private int mArrowHeight; 446 | private int mAlpha; 447 | private int mBackgroundColor; 448 | 449 | public Ring(Callback callback) { 450 | mRingCallback = callback; 451 | mArcPaint.setStrokeCap(Paint.Cap.SQUARE); 452 | mArcPaint.setAntiAlias(true); 453 | mArcPaint.setStyle(Paint.Style.STROKE); 454 | mArrowPaint.setStyle(Paint.Style.FILL); 455 | mArrowPaint.setAntiAlias(true); 456 | 457 | mCirclePaint.setAntiAlias(true); 458 | } 459 | 460 | public void setBackgroundColor(int color) { 461 | mBackgroundColor = color; 462 | } 463 | 464 | /** 465 | * Set the dimensions of the arrowhead. 466 | * 467 | * @param width Width of the hypotenuse of the arrow head 468 | * @param height Height of the arrow point 469 | */ 470 | public void setArrowDimensions(float width, float height) { 471 | mArrowWidth = (int) width; 472 | mArrowHeight = (int) height; 473 | } 474 | 475 | /** 476 | * Draw the progress spinner 477 | */ 478 | public void draw(Canvas c, Rect bounds) { 479 | 480 | mCirclePaint.setColor(mBackgroundColor); 481 | mCirclePaint.setAlpha(mAlpha); 482 | 483 | c.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), bounds.width() / 2, mCirclePaint); 484 | 485 | final RectF arcBounds = mTempBounds; 486 | arcBounds.set(bounds); 487 | arcBounds.inset(mStrokeInset, mStrokeInset); 488 | final float startAngle = (mStartTrim + mRotation) * 360; 489 | final float endAngle = (mEndTrim + mRotation) * 360; 490 | float sweepAngle = endAngle - startAngle; 491 | mArcPaint.setColor(mColors[mColorIndex]); 492 | 493 | mArcPaint.setAlpha(mAlpha); 494 | c.drawArc(arcBounds, startAngle, sweepAngle, false, mArcPaint); 495 | drawTriangle(c, startAngle, sweepAngle, bounds); 496 | } 497 | 498 | private void drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds) { 499 | if (mShowArrow) { 500 | if (mArrow == null) { 501 | mArrow = new Path(); 502 | mArrow.setFillType(Path.FillType.EVEN_ODD); 503 | } else { 504 | mArrow.reset(); 505 | } 506 | // Adjust the position of the triangle so that it is inset as 507 | // much as the arc, but also centered on the arc. 508 | float inset = (int) mStrokeInset / 2 * mArrowScale; 509 | float x = (float) (mRingCenterRadius * Math.cos(0) + bounds.exactCenterX()); 510 | float y = (float) (mRingCenterRadius * Math.sin(0) + bounds.exactCenterY()); 511 | // Update the path each time. This works around an issue in SKIA 512 | // where concatenating a rotation matrix to a scale matrix 513 | // ignored a starting negative rotation. This appears to have 514 | // been fixed as of API 21. 515 | mArrow.moveTo(0, 0); 516 | mArrow.lineTo(mArrowWidth * mArrowScale, 0); 517 | mArrow.lineTo((mArrowWidth * mArrowScale / 2), (mArrowHeight 518 | * mArrowScale)); 519 | mArrow.offset(x - inset, y); 520 | mArrow.close(); 521 | // draw a triangle 522 | mArrowPaint.setColor(mColors[mColorIndex]); 523 | mArrowPaint.setAlpha(mAlpha); 524 | c.rotate(startAngle + sweepAngle - ARROW_OFFSET_ANGLE, bounds.exactCenterX(), 525 | bounds.exactCenterY()); 526 | c.drawPath(mArrow, mArrowPaint); 527 | } 528 | } 529 | 530 | /** 531 | * Set the colors the progress spinner alternates between. 532 | * 533 | * @param colors Array of integers describing the colors. Must be non-null. 534 | */ 535 | public void setColors(int[] colors) { 536 | mColors = colors; 537 | // if colors are reset, make sure to reset the color index as well 538 | setColorIndex(0); 539 | } 540 | 541 | /** 542 | * @param index Index into the color array of the color to display in 543 | * the progress spinner. 544 | */ 545 | public void setColorIndex(int index) { 546 | mColorIndex = index; 547 | } 548 | 549 | /** 550 | * Proceed to the next available ring color. This will automatically 551 | * wrap back to the beginning of colors. 552 | */ 553 | public void goToNextColor() { 554 | mColorIndex = (mColorIndex + 1) % (mColors.length); 555 | } 556 | 557 | public void setColorFilter(ColorFilter filter) { 558 | mArcPaint.setColorFilter(filter); 559 | invalidateSelf(); 560 | } 561 | 562 | /** 563 | * @return Current alpha of the progress spinner and arrowhead. 564 | */ 565 | public int getAlpha() { 566 | return mAlpha; 567 | } 568 | 569 | /** 570 | * @param alpha Set the alpha of the progress spinner and associated arrowhead. 571 | */ 572 | public void setAlpha(int alpha) { 573 | mAlpha = alpha; 574 | } 575 | 576 | @SuppressWarnings("unused") 577 | public float getStrokeWidth() { 578 | return mStrokeWidth; 579 | } 580 | 581 | /** 582 | * @param strokeWidth Set the stroke width of the progress spinner in pixels. 583 | */ 584 | public void setStrokeWidth(float strokeWidth) { 585 | mStrokeWidth = strokeWidth; 586 | mArcPaint.setStrokeWidth(strokeWidth); 587 | invalidateSelf(); 588 | } 589 | 590 | @SuppressWarnings("unused") 591 | public float getStartTrim() { 592 | return mStartTrim; 593 | } 594 | 595 | @SuppressWarnings("unused") 596 | public void setStartTrim(float startTrim) { 597 | mStartTrim = startTrim; 598 | invalidateSelf(); 599 | } 600 | 601 | public float getStartingStartTrim() { 602 | return mStartingStartTrim; 603 | } 604 | 605 | public float getStartingEndTrim() { 606 | return mStartingEndTrim; 607 | } 608 | 609 | @SuppressWarnings("unused") 610 | public float getEndTrim() { 611 | return mEndTrim; 612 | } 613 | 614 | @SuppressWarnings("unused") 615 | public void setEndTrim(float endTrim) { 616 | mEndTrim = endTrim; 617 | invalidateSelf(); 618 | } 619 | 620 | @SuppressWarnings("unused") 621 | public float getRotation() { 622 | return mRotation; 623 | } 624 | 625 | @SuppressWarnings("unused") 626 | public void setRotation(float rotation) { 627 | mRotation = rotation; 628 | invalidateSelf(); 629 | } 630 | 631 | public void setInsets(int width, int height) { 632 | final float minEdge = (float) Math.min(width, height); 633 | float insets; 634 | if (mRingCenterRadius <= 0 || minEdge < 0) { 635 | insets = (float) Math.ceil(mStrokeWidth / 2.0f); 636 | } else { 637 | insets = (float) (minEdge / 2.0f - mRingCenterRadius); 638 | } 639 | mStrokeInset = insets; 640 | } 641 | 642 | @SuppressWarnings("unused") 643 | public float getInsets() { 644 | return mStrokeInset; 645 | } 646 | 647 | public double getCenterRadius() { 648 | return mRingCenterRadius; 649 | } 650 | 651 | /** 652 | * @param centerRadius Inner radius in px of the circle the progress 653 | * spinner arc traces. 654 | */ 655 | public void setCenterRadius(double centerRadius) { 656 | mRingCenterRadius = centerRadius; 657 | } 658 | 659 | /** 660 | * @param show Set to true to show the arrow head on the progress spinner. 661 | */ 662 | public void setShowArrow(boolean show) { 663 | if (mShowArrow != show) { 664 | mShowArrow = show; 665 | invalidateSelf(); 666 | } 667 | } 668 | 669 | /** 670 | * @param scale Set the scale of the arrowhead for the spinner. 671 | */ 672 | public void setArrowScale(float scale) { 673 | if (scale != mArrowScale) { 674 | mArrowScale = scale; 675 | invalidateSelf(); 676 | } 677 | } 678 | 679 | /** 680 | * @return The amount the progress spinner is currently rotated, between [0..1]. 681 | */ 682 | public float getStartingRotation() { 683 | return mStartingRotation; 684 | } 685 | 686 | /** 687 | * If the start / end trim are offset to begin with, store them so that 688 | * animation starts from that offset. 689 | */ 690 | public void storeOriginals() { 691 | mStartingStartTrim = mStartTrim; 692 | mStartingEndTrim = mEndTrim; 693 | mStartingRotation = mRotation; 694 | } 695 | 696 | /** 697 | * Reset the progress spinner to default rotation, start and end angles. 698 | */ 699 | public void resetOriginals() { 700 | mStartingStartTrim = 0; 701 | mStartingEndTrim = 0; 702 | mStartingRotation = 0; 703 | setStartTrim(0); 704 | setEndTrim(0); 705 | setRotation(0); 706 | } 707 | 708 | private void invalidateSelf() { 709 | mRingCallback.invalidateDrawable(null); 710 | } 711 | } 712 | 713 | /** 714 | * Squishes the interpolation curve into the second half of the animation. 715 | */ 716 | private static class EndCurveInterpolator extends AccelerateDecelerateInterpolator { 717 | @Override 718 | public float getInterpolation(float input) { 719 | return super.getInterpolation(Math.max(0, (input - 0.5f) * 2.0f)); 720 | } 721 | } 722 | 723 | /** 724 | * Squishes the interpolation curve into the first half of the animation. 725 | */ 726 | private static class StartCurveInterpolator extends AccelerateDecelerateInterpolator { 727 | @Override 728 | public float getInterpolation(float input) { 729 | return super.getInterpolation(Math.min(1, input * 2.0f)); 730 | } 731 | } 732 | 733 | /** 734 | * Taken from {@link package android.support.v4.widget} 735 | */ 736 | private class OvalShadow extends OvalShape { 737 | private RadialGradient mRadialGradient; 738 | private int mShadowRadius; 739 | private Paint mShadowPaint; 740 | private int mCircleDiameter; 741 | 742 | public OvalShadow(int shadowRadius, int circleDiameter) { 743 | super(); 744 | mShadowPaint = new Paint(); 745 | mShadowRadius = shadowRadius; 746 | mCircleDiameter = circleDiameter; 747 | mRadialGradient = new RadialGradient(mCircleDiameter / 2, mCircleDiameter / 2, 748 | mShadowRadius, new int[]{ 749 | FILL_SHADOW_COLOR, Color.TRANSPARENT 750 | }, null, Shader.TileMode.CLAMP); 751 | mShadowPaint.setShader(mRadialGradient); 752 | } 753 | 754 | @Override 755 | public void draw(Canvas canvas, Paint paint) { 756 | final int viewWidth = getBounds().width(); 757 | final int viewHeight = getBounds().height(); 758 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2 + mShadowRadius), 759 | mShadowPaint); 760 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2), paint); 761 | } 762 | } 763 | } 764 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/weexlist/widgets/WeexPtrDefaultHandler.java: -------------------------------------------------------------------------------- 1 | package com.android.weexlist.widgets; 2 | 3 | import android.support.v4.view.ViewCompat; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.widget.AbsListView; 7 | import android.widget.FrameLayout; 8 | 9 | import com.taobao.weex.RenderContainer; 10 | import com.taobao.weex.ui.view.WXFrameLayout; 11 | import com.taobao.weex.ui.view.refresh.wrapper.BounceRecyclerView; 12 | 13 | import in.srain.cube.views.ptr.PtrDefaultHandler; 14 | 15 | /** 16 | * Created by liuzhao on 17/3/23. 17 | */ 18 | 19 | public abstract class WeexPtrDefaultHandler extends PtrDefaultHandler { 20 | public static boolean canChildScrollUp(View view) { 21 | View realView = view; 22 | if (view instanceof ViewGroup) { 23 | FrameLayout frameLayout = (FrameLayout) view; 24 | RenderContainer renderContainer = (RenderContainer) frameLayout.getChildAt(0); 25 | realView = ((BounceRecyclerView) ((WXFrameLayout) renderContainer.getChildAt(0)).getChildAt(0)).getInnerView(); 26 | } 27 | if (android.os.Build.VERSION.SDK_INT < 14) { 28 | if (realView instanceof AbsListView) { 29 | final AbsListView absListView = (AbsListView) realView; 30 | return absListView.getChildCount() > 0 31 | && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) 32 | .getTop() < absListView.getPaddingTop()); 33 | } else { 34 | return realView.getScrollY() > 0; 35 | } 36 | } else { 37 | return ViewCompat.canScrollVertically(realView, -1); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 |