├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── aswifter │ │ └── material │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── book_author.txt │ ├── book_content.txt │ ├── book_menu.txt │ └── news.css │ ├── ic_launcher-web.png │ ├── java │ └── com │ │ └── aswifter │ │ └── material │ │ ├── AboutFragment.java │ │ ├── BlogFragment.java │ │ ├── ExampleFragment.java │ │ ├── MainActivity.java │ │ ├── MaterialApplication.java │ │ ├── book │ │ ├── Book.java │ │ ├── BookDetailActivity.java │ │ ├── BookResponse.java │ │ ├── BooksFragment.java │ │ ├── BooksSupplier.java │ │ ├── MyAdapter.java │ │ ├── OnRefreshObservable.java │ │ └── ViewHolder.java │ │ ├── common │ │ ├── AppClient.java │ │ ├── BaseActivity.java │ │ ├── ThreadPool.java │ │ └── Utils.java │ │ ├── example │ │ ├── AppBarDetailActivity.java │ │ ├── BottomTabActivity.java │ │ ├── CardViewActivity.java │ │ ├── DetailFragment.java │ │ └── EditTextFLActivity.java │ │ ├── news │ │ ├── ConverterName.java │ │ ├── NewsAdapter.java │ │ ├── NewsDetailActivity.java │ │ ├── NewsDetailResponse.java │ │ ├── NewsListActivity.java │ │ ├── NewsObservable.java │ │ ├── NewsResponse.java │ │ ├── NewsSupplier.java │ │ ├── Story.java │ │ └── TopStory.java │ │ ├── utils │ │ └── DisplayUtil.java │ │ └── widget │ │ ├── BackHandledFragment.java │ │ ├── CircleImageView.java │ │ ├── DividerItemDecoration.java │ │ ├── DividerOffsetDecoration.java │ │ ├── MaterialProgressDrawable.java │ │ ├── ProgressWebView.java │ │ ├── PullToRefreshLayout.java │ │ ├── RecyclerItemClickListener.java │ │ ├── RefreshLayout.java │ │ └── WebViewProgressBar.java │ └── res │ ├── color │ └── tab_text.xml │ ├── drawable-xxhdpi │ ├── book1.jpg │ ├── book2.jpg │ ├── book3.jpg │ ├── ic_favorite.png │ ├── ic_find_in_page_white.png │ └── news_item.xml │ ├── layout │ ├── activity_appbar_detail.xml │ ├── activity_bottom_tab.xml │ ├── activity_cardview.xml │ ├── activity_edittext_fl.xml │ ├── activity_main.xml │ ├── activity_news_detail.xml │ ├── activity_recycler_view.xml │ ├── book_item.xml │ ├── custom_tab.xml │ ├── fragment_about.xml │ ├── fragment_blog.xml │ ├── fragment_books.xml │ ├── fragment_detail.xml │ ├── fragment_example.xml │ ├── fragment_page.xml │ ├── include_toolbar.xml │ ├── item_view.xml │ ├── navigation_header.xml │ └── progress_bar.xml │ ├── menu │ ├── drawer.xml │ └── menu_main.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── profile.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-v21 │ └── styles.xml │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ ├── styles.xml │ ├── transition.xml │ └── values.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── app-debug.apk ├── bookdetail.gif ├── cardview.png ├── demo.apk ├── pic1.png └── pic2.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | #built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | build/ 15 | # Local configuration file (sdk path, etc) 16 | local.properties 17 | 18 | # Windows thumbnail db 19 | Thumbs.db 20 | 21 | # OSX files 22 | .DS_Store 23 | 24 | # Eclipse project files 25 | .classpath 26 | .project 27 | 28 | # Android Studio 29 | *.iml 30 | .idea 31 | #.idea/workspace.xml - remove # and delete .idea if it better suit your needs. 32 | .gradle 33 | build/ 34 | 35 | *~ 36 | *.swp -------------------------------------------------------------------------------- /.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/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.8 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 本APP用来演示Material Design控件的使用。 2 | 3 | ![](http://7xjq5l.com1.z0.glb.clouddn.com/android_transition.gif) 4 |
5 | ![](http://7xjq5l.com1.z0.glb.clouddn.com/appbar_detail.gif) 6 |
7 | ![](http://7xjq5l.com1.z0.glb.clouddn.com/m1.png) 8 |
9 | ![](http://7xjq5l.com1.z0.glb.clouddn.com/m2.png) 10 |
11 | ![](http://7xjq5l.com1.z0.glb.clouddn.com/m3.png) 12 |
13 | ![](http://7xjq5l.com1.z0.glb.clouddn.com/m4.png) 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'me.tatarka.retrolambda' 3 | apply plugin: 'android-apt' 4 | android { 5 | compileSdkVersion 25 6 | buildToolsVersion = '25.0.3' 7 | 8 | defaultConfig { 9 | applicationId "com.aswifter.materialexample" 10 | minSdkVersion 15 11 | targetSdkVersion 25 12 | versionCode 2 13 | versionName "1.1" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | shrinkResources true 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | } 23 | 24 | 25 | dependencies { 26 | compile fileTree(dir: 'libs', include: ['*.jar']) 27 | compile 'com.android.support:design:25.3.1' 28 | compile 'com.android.support:appcompat-v7:25.3.1' 29 | compile 'com.android.support:cardview-v7:25.3.1' 30 | compile 'com.android.support:recyclerview-v7:25.3.1' 31 | 32 | compile 'com.github.bumptech.glide:glide:3.7.0' 33 | compile 'de.hdodenhof:circleimageview:1.3.0' 34 | compile 'com.squareup.okhttp3:okhttp:3.2.0' 35 | compile 'com.google.code.gson:gson:2.8.0' 36 | 37 | 38 | compile 'com.afollestad.material-dialogs:core:0.9.4.5' 39 | 40 | compile 'com.squareup.retrofit2:retrofit:2.3.0' 41 | compile 'com.squareup.retrofit2:converter-gson:2.3.0' 42 | //glide transform https://github.com/wasabeef/glide-transformations 43 | compile 'jp.wasabeef:glide-transformations:2.0.1' 44 | // If you want to use the GPU Filters 45 | compile 'jp.co.cyberagent.android.gpuimage:gpuimage-library:1.3.0' 46 | 47 | //butterknife https://github.com/JakeWharton/butterknife 48 | compile 'com.jakewharton:butterknife:8.1.0' 49 | apt 'com.jakewharton:butterknife-compiler:8.1.0' 50 | 51 | compile 'com.google.android.agera:agera:1.3.0' 52 | 53 | } 54 | -------------------------------------------------------------------------------- /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 C:\Android\adt-bundle-windows-x86_64-20140624\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/aswifter/material/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/assets/book_author.txt: -------------------------------------------------------------------------------- 1 | 凯文·凯利(Kevin Kelly,1952年4月27日~,人们昵称他为 KK), 2 | 他影响了苹果公司的史蒂夫·乔布斯、《连线》杂志的总编克里斯·安德森、《黑客帝国》的导演沃卓斯基兄弟、《少数派报告》的导演史蒂文·斯皮尔伯格;他参与创办了《连线》杂志、发起第一届黑客大会、创作《失 控》……;他是网络文化的发言人和观察者……他是——凯文·凯利 3 | 《连线》(Wired)杂志创始主编。在创办《连线》之前,是《全球概览》杂志(The Whole Earth Catalog,乔布斯最喜欢的杂志)的编辑和出版人。1984年,KK发起了第一届黑客大会(Hackers Conference)。他的文章还出现在《纽约时报》、《经济学人》、《时代》、《科学》等重量级媒体和杂志上。 4 | 凯文·凯利被看作是“网络文化”(Cyberculture)的发言人和观察者,也有人称之为“游侠”(maverick)。 -------------------------------------------------------------------------------- /app/src/main/assets/book_content.txt: -------------------------------------------------------------------------------- 1 | 《失控》,全名为《失控:机器、社会与经济的新生物学》(Out of Control: The New Biology of Machines, Social Systems, and the Economic World)。 2 | 2006年,《长尾》作者克里斯·安德森在亚马逊网站上这样评价该书: 3 | “这可能是90年代最重要的一本书”,并且是“少有的一年比一年卖得好的书”。“尽管书中的一些例子在十几年后可能有些过时,但(它们所表达的)信息却越来越成为真知灼见”。“在那时人们还无法想象博客和维基等大众智慧的突起,但凯利却分毫不差地预见到了。这可能是过去十年来最聪明的一本书。” 4 | 这是一部思考人类社会(或更一般意义上的复杂系统)进化的“大部头”著作,对于那些不惧于“头脑体操”的读者来说,必然会开卷有益。 5 | 《失控》成书于1994年,作者是《连线》杂志的创始主编凯文·凯利。这本书所记述的,是他对当时科技、社会和经济最前沿的一次漫游,以及借此所窥得的未来图景。 6 | 书中提到并且今天正在兴起或大热的概念包括:大众智慧、云计算、物联网、虚拟现实、敏捷开发、协作、双赢、共生、共同进化、网络社区、网络经济,等等。说它是一本“预言式”的书并不为过。其中必定还隐藏着我们尚未印证或窥破的对未来的“预言”。 -------------------------------------------------------------------------------- /app/src/main/assets/book_menu.txt: -------------------------------------------------------------------------------- 1 | 《失控》上冊 2 | 第一章 人造与天生 3 | 1.1 新生物文明 4 | 1.2 生物逻辑的胜利 5 | 1.3 学会向我们的创造物低头 6 | 第二章 蜂群思维 7 | 2.1 蜜蜂之道:分布式管理 8 | 2.2 群氓的集体智慧 9 | 2.3 非匀质的看不见的手 10 | 2.4 认知行为的分散记忆 11 | 2.5 从量变到质变 12 | 2.6 群集的利与弊 13 | 2.7 网络是二十一世纪的图标 14 | 第三章 有心智的机器 15 | 3.1 取悦有身体的机器 16 | 3.2 快速、廉价、失控 17 | 3.3 众愚成智 18 | 3.4 嵌套层级的优点 19 | 3.5 利用现实世界的反馈实现交流 20 | 3.6 无躯体则无意识 21 | 3.7 心智/躯体的黑盲性精神错乱 22 | 第四章 组装复杂性 23 | 4.1 生物——机器的未来 24 | 4.2 用火和软体种子恢复草原 25 | 4.3 通往稳定生态系统的随机路线 26 | 4.4 如何同时做好一切 27 | 4.5 艰巨的“拼蛋壳”任务 28 | 第五章:共同进化 29 | 5.1 放在镜子上的变色龙是什么颜色的? 30 | 5.2 生命之无法理喻之处 31 | 5.3. 在持久的摇摇欲坠状态中保持平衡 32 | 5.4. 岩石乃节奏缓慢的生命 33 | 5.5. 不讲交情或无远见的合作 34 | 第六章 自然之流变 35 | 6.1 均衡即死亡 36 | 6.2 谁先出现,稳定性还是多样性? 37 | 6.3 生态系统:超有机体,抑或是身份作坊? 38 | 6.4 变化的起源 39 | 6.5 生生不息的生命 40 | 6.6 负熵 41 | 6.7 第四个间断:生成之环 42 | 第七章 控制的兴起 43 | 7.1 古希腊的第一个人工自我 44 | 7.2 机械自我的成熟 45 | 7.3 抽水马桶:套套逻辑的原型 46 | 7.4 自我能动派 47 | 第八章 封闭系统 48 | 8.1 密封的瓶装生命 49 | 8.2 邮购盖亚 50 | 8.3 人与绿藻息息相关 51 | 8.4 巨大的生态技术玻璃球 52 | 8.5 在持久的混沌中进行的实验 53 | 8.6 另外一种合成生态系统 54 | 第九章 “冒出”的生态圈 55 | 9.1 一亿美元玻璃方舟的副驾驶 56 | 9.2 城市野草 57 | 9.3 有意的季节调配 58 | 9.4 生命科学的回旋加速器 59 | 9.5 终极技术 60 | 第十章 工业生态学 61 | 10.1 全天候、全方位的接入 62 | 10.3 咬人的房间与不咬人的房间 63 | 10.4 规划一个共同体 64 | 10.5 闭环制造 65 | 10.5 适应的技术 66 | 第十一章 网络经济学 67 | 11.1 脱离实体 68 | 11.2 以联结取代计算 69 | 11.3 信息工厂 70 | 11.4 与错误打交道 71 | 11.5 联通所有的一切 72 | 第十二章 电子货币 73 | 12.1 密码无政府状态:加密永胜 74 | 12.2 传真机效应和收益递增定律 75 | 12.3 超级传播 76 | 12.4 带电荷的东西就可用于电子货币充值 77 | 12.5 点对点金融与超级小钱 78 | 12.6 对隐密经济的恐惧 79 | 《失控》下冊 80 | 第十三章 上帝的游戏 81 | 13.1 电子神格 82 | 13.2 有交互界面的理论 83 | 13.3 “神”临其境 84 | 13.4 拟像的传送 85 | 13.5 数字之战 86 | 13.6 无缝分布的军队 87 | 13.7 一个万千碎片的超真实 88 | 13.8 两厢情愿的文字超级有机体 89 | 13.9 放手则赢 90 | 第十四章 在形式的图书馆中 91 | 14.1 “大千”图书馆之旅 22836 92 | 14.2 一切可能图像之空间 93 | 14.3 倘佯在生物形态王国 94 | 14.4 御变异体而行 95 | 14.5 形式库中也有性 96 | 14.6 三步轻松繁育艺术杰作 97 | 14.7 穿越随机性 98 | 第十五章 人工进化 99 | 15.1 汤姆•雷的电进化机 25879 100 | 15.2 你力所不逮的,进化能行 101 | 15.3 并行实施的盲目行为 102 | 15.4 计算中的军备竞赛 103 | 15.5 驾驭野性的进化 104 | 15.6 进化聪明分子的愚钝科学家 105 | 15.7 死亡是最好的老师 106 | 15.8 蚂蚁的算法天赋 107 | 15.9 工程霸权的终结 108 | 第十六章 控制的未来 109 | 16.1 玩具世界的卡通物理学 19952 110 | 16.2 合成角色的诞生 111 | 16.3 没有实体的机器人 112 | 16.4 行为学架构中的代理 113 | 16.5 给自由意志强加宿命 114 | 16.6 米老鼠重装上阵 115 | 16.7 寻求协同控制 116 | 第十七章 开放的宇宙 117 | 17.1 拓展生存的空间 19117 118 | 17.2 生成图像的基元组 119 | 17.3 无心插柳柳成荫 120 | 17.4 打破规则求生存 121 | 17.5 掌握进化工具 122 | 17.6 从滑翔意外到生命游戏 123 | 17.7 生命的动词 124 | 17.8 在超生命的国度中安家落户 125 | 第十八章 有组织的变化之架构 126 | 18.1 日常进化的革命 10587 127 | 18.2 绕开中心法则 128 | 18.3 学习和进化之间的区别 129 | 18.4 进化的进化 130 | 18.5 进化解释一切 131 | 第十九章 后达尔文主义 132 | 19.1 达尔文进化论不完备之处 21928 133 | 19.2 只有自然选择还不够 134 | 19.3 生命之树上的连理枝 135 | 19.4 非随机突变的前提 136 | 19.5 怪亦有道 137 | 19.6 化抽象为具象 138 | 19.7 物以类聚 139 | 19.8 DNA并不能给所有东西编码 140 | 19.9 不确定的生物搜索空间密度 141 | 19.10 自然选择之数学原理 142 | 第二十章 沉睡的蝴蝶 143 | 20.1 无序之有序 13147 144 | 20.2 反直觉的网络数学 145 | 20.3 迭坐,喷涌,自催化 146 | 20.4 值得一问的问题 147 | 20.5 自调节的活系统 148 | 第二十一章 水往高处流 149 | 21.1 四十亿年的庞氏骗局 15184 150 | 21.2 进化的目的是什么 151 | 21.3 超进化的七个趋势 152 | 21.4 土狼般的自我进化 153 | 第二十二章 预言机 154 | 22.1 接球的大脑 29557 155 | 22.2 混沌的另一面 156 | 22.3 具有正面意义的短视 157 | 22.4 从可预测性范围里挣大钱 158 | 22.5 前瞻:内视行动 159 | 22.6 预测的多样性 160 | 22.7 以万变求不变 161 | 22.8 系统存在的目的就是揭示未来 162 | 22.9 全球模型的诸多问题 163 | 22.10 舵手是大家 164 | 第二十三章 整体,空洞,以及空间 165 | 23.1 控制论怎么了? 16268 166 | 23.2 科学知识网之缺口 167 | 23.3 令人惊讶的琐碎小事 168 | 23.4 超文本:权威的终结 169 | 23.5 新的思考空间 170 | 第二十四章 九律 171 | 24.1 如何无中生有 172 | 24.2 将宇宙据为己有 173 | 總計字數: 500千字 -------------------------------------------------------------------------------- /app/src/main/assets/news.css: -------------------------------------------------------------------------------- 1 | article, 2 | aside, 3 | details, 4 | figcaption, 5 | figure, 6 | footer, 7 | header, 8 | hgroup, 9 | main, 10 | nav, 11 | section, 12 | summary { 13 | display: block; 14 | } 15 | audio, 16 | canvas, 17 | video { 18 | display: inline-block; 19 | } 20 | audio:not([controls]) { 21 | display: none; 22 | height: 0; 23 | } 24 | html { 25 | font-family: sans-serif; 26 | -webkit-text-size-adjust: 100%; 27 | } 28 | body { 29 | font-family: 'Helvetica Neue', Helvetica, Arial, Sans-serif; 30 | background: #fff; 31 | padding-top: 0; 32 | margin: 0; 33 | } 34 | a:focus { 35 | outline: thin dotted; 36 | } 37 | a:active, 38 | a:hover { 39 | outline: 0; 40 | } 41 | h1 { 42 | margin: .67em 0; 43 | } 44 | h1, 45 | h2, 46 | h3, 47 | h4, 48 | h5, 49 | h6 { 50 | font-size: 16px; 51 | } 52 | abbr[title] { 53 | border-bottom: 1px dotted; 54 | } 55 | hr { 56 | box-sizing: content-box; 57 | height: 0; 58 | } 59 | mark { 60 | background: #ff0; 61 | color: #000; 62 | } 63 | code, 64 | kbd, 65 | pre, 66 | samp { 67 | font-family: monospace,serif; 68 | font-size: 1em; 69 | } 70 | pre { 71 | white-space: pre-wrap; 72 | } 73 | q { 74 | quotes: \201C\201D\2018\2019; 75 | } 76 | small { 77 | font-size: 80%; 78 | } 79 | sub, 80 | sup { 81 | font-size: 75%; 82 | line-height: 0; 83 | position: relative; 84 | vertical-align: baseline; 85 | } 86 | sup { 87 | top: -0.5em; 88 | } 89 | sub { 90 | bottom: -0.25em; 91 | } 92 | img { 93 | border: 0; 94 | vertical-align: middle; 95 | color: transparent; 96 | font-size: 0; 97 | } 98 | svg:not(:root) { 99 | overflow: hidden; 100 | } 101 | figure { 102 | margin: 0; 103 | } 104 | fieldset { 105 | border: 1px solid silver; 106 | margin: 0 2px; 107 | padding: .35em .625em .75em; 108 | } 109 | legend { 110 | border: 0; 111 | padding: 0; 112 | } 113 | table { 114 | border-collapse: collapse; 115 | border-spacing: 0; 116 | overflow: hidden; 117 | } 118 | a { 119 | text-decoration: none; 120 | } 121 | blockquote { 122 | border-left: 3px solid #D0E5F2; 123 | font-style: normal; 124 | display: block; 125 | line-height: 1.4em; 126 | vertical-align: baseline; 127 | font-size: 100%; 128 | margin: .5em 0; 129 | padding: 0 0 0 .5em; 130 | } 131 | ul, 132 | ol { 133 | padding-left: 20px; 134 | } 135 | .main-wrap { 136 | max-width: 100%; 137 | min-width: 300px; 138 | margin: 0 auto; 139 | } 140 | .content-wrap { 141 | overflow: hidden; 142 | background-color: #f9f9f9; 143 | } 144 | .content-wrap a { 145 | word-break: break-all; 146 | } 147 | .headline { 148 | border-bottom: 4px solid #f6f6f6; 149 | } 150 | .headline-title.onlyheading { 151 | margin: 20px 0; 152 | } 153 | .headline img { 154 | max-width: 100%; 155 | vertical-align: top; 156 | } 157 | .headline-background-link { 158 | line-height: 2em; 159 | position: relative; 160 | display: block; 161 | padding: 20px 45px 20px 20px !important; 162 | } 163 | .icon-arrow-right { 164 | position: absolute; 165 | top: 50%; 166 | right: 20px; 167 | background-image: url(http://static.daily.zhihu.com/img/share-icons.png); 168 | background-repeat: no-repeat; 169 | display: inline-block; 170 | vertical-align: middle; 171 | background-position: -70px -20px; 172 | width: 10px; 173 | height: 15px; 174 | margin-top: -7.5px; 175 | } 176 | .headline-background .heading { 177 | color: #999; 178 | font-size: 15px!important; 179 | margin-bottom: 8px; 180 | line-height: 1em; 181 | } 182 | .headline-background .heading-content { 183 | color: #444; 184 | font-size: 17px!important; 185 | line-height: 1.2em; 186 | } 187 | .headline-title { 188 | line-height: 1.2em; 189 | color: #000; 190 | font-size: 22px; 191 | margin: 20px 0 10px; 192 | padding: 0 20px!important; 193 | font-weight: bold; 194 | } 195 | .meta { 196 | white-space: nowrap; 197 | text-overflow: ellipsis; 198 | overflow: hidden; 199 | font-size: 16px; 200 | color: #b8b8b8; 201 | } 202 | .meta .source-icon { 203 | width: 20px; 204 | height: 20px; 205 | margin-right: 4px; 206 | } 207 | .meta .time { 208 | float: right; 209 | margin-top: 2px; 210 | } 211 | .content { 212 | color: #444; 213 | line-height: 1.6em; 214 | font-size: 17px; 215 | margin: 10px 0 20px; 216 | } 217 | .content img { 218 | max-width: 100%; 219 | display: block; 220 | margin: 10px 0; 221 | } 222 | .content img[src*="zhihu.com/equation"] { 223 | display: inline-block; 224 | margin: 0 3px; 225 | } 226 | .content a { 227 | color: #259; 228 | } 229 | .content a:hover { 230 | text-decoration: underline; 231 | } 232 | .view-more { 233 | margin-bottom: 25px; 234 | text-align: center; 235 | } 236 | .view-more a { 237 | font-size: 16px; 238 | display: inline-block; 239 | width: 125px; 240 | height: 30px; 241 | line-height: 30px; 242 | background: #f0f0f0; 243 | color: #B8B8B8; 244 | } 245 | .question { 246 | overflow: hidden; 247 | padding: 0 20px!important; 248 | } 249 | .question + .question { 250 | border-top: 5px solid #f6f6f6; 251 | } 252 | .question-title { 253 | line-height: 1.4em; 254 | color: #000; 255 | font-weight: 700; 256 | font-size: 18px; 257 | margin: 20px 0; 258 | } 259 | .meta .author { 260 | color: #444; 261 | font-weight: 700; 262 | } 263 | .answer + .answer { 264 | border-top: 2px solid #f6f6f6; 265 | padding-top: 20px; 266 | } 267 | .footer { 268 | text-align: center; 269 | color: #b8b8b8; 270 | font-size: 13px; 271 | padding: 20px 0; 272 | } 273 | .footer a { 274 | color: #b8b8b8; 275 | } 276 | .question .view-more a { 277 | width: 100%; 278 | display: block; 279 | } 280 | .hot-comment { 281 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 282 | } 283 | .comment-label { 284 | font-size: 16px; 285 | color: #333; 286 | line-height: 1.5em; 287 | font-weight: 700; 288 | border-top: 1px solid #eee; 289 | border-bottom: 1px solid #eee; 290 | margin: 0; 291 | padding: 9px 20px; 292 | } 293 | .comment-list { 294 | margin-bottom: 20px; 295 | } 296 | .comment-item { 297 | font-size: 15px; 298 | color: #666; 299 | border-bottom: 1px solid #eee; 300 | padding: 15px 20px; 301 | } 302 | .comment-meta { 303 | position: relative; 304 | margin-bottom: 10px; 305 | } 306 | .comment-meta .author { 307 | vertical-align: middle; 308 | color: #444; 309 | } 310 | .comment-meta .vote { 311 | position: absolute; 312 | color: #b8b8b8; 313 | font-size: 12px; 314 | right: 0; 315 | } 316 | .night .comment-label { 317 | color: #b8b8b8; 318 | border-top: 1px solid #303030; 319 | border-bottom: 1px solid #303030; 320 | } 321 | .night .comment-item { 322 | color: #7f7f7f; 323 | border-bottom: 1px solid #303030; 324 | } 325 | .icon-vote, 326 | .icon-voted { 327 | background-repeat: no-repeat; 328 | display: inline-block; 329 | vertical-align: 0; 330 | width: 11px; 331 | height: 12px; 332 | margin-right: 4px; 333 | background-image: url(http://static.daily.zhihu.com/img/app/Comment_Vote.png) !important; 334 | } 335 | .icon-voted { 336 | background-image: url(http://static.daily.zhihu.com/img/app/Comment_Voted.png) !important; 337 | } 338 | .night .icon-vote { 339 | background-image: url(http://static.daily.zhihu.com/img/app/Dark_Comment_Vote.png) !important; 340 | } 341 | .img-wrap .headline-title { 342 | bottom: 5px; 343 | } 344 | .img-wrap .img-source { 345 | right: 10px!important; 346 | font-size: 9px; 347 | } 348 | .global-header { 349 | position: static; 350 | } 351 | .button { 352 | width: 60px; 353 | } 354 | .button i { 355 | margin-right: 0; 356 | } 357 | .headline .img-place-holder { 358 | height: 200px; 359 | } 360 | .from-column { 361 | width: 280px; 362 | line-height: 30px; 363 | height: 30px; 364 | padding-left: 90px; 365 | color: #2aacec; 366 | background-image: url(http://static.daily.zhihu.com/img/News_Column_Entrance.png); 367 | box-sizing: border-box; 368 | margin: 0 20px 20px; 369 | } 370 | .from-column:active { 371 | background-image: url(http://static.daily.zhihu.com/img/News_Column_Entrance_Highlight.png); 372 | } 373 | .night .headline { 374 | border-bottom: 4px solid #303030; 375 | } 376 | .night img { 377 | -webkit-mask-image: -webkit-gradient(linear, 0 0, 0 100%, from(rgba(0, 0, 0, 0.7)), to(rgba(0, 0, 0, 0.7))); 378 | } 379 | body.night, 380 | .night .content-wrap { 381 | background: #343434; 382 | } 383 | .night .answer + .answer { 384 | border-top: 2px solid #303030; 385 | } 386 | .night .question + .question { 387 | border-top: 4px solid #303030; 388 | } 389 | .night .view-more a { 390 | background: #292929; 391 | color: #666; 392 | } 393 | .night .icon-arrow-right { 394 | background-image: url(http://static.daily.zhihu.com/img/share-icons.png); 395 | background-repeat: no-repeat; 396 | display: inline-block; 397 | vertical-align: middle; 398 | background-position: -70px -35px; 399 | width: 10px; 400 | height: 15px; 401 | } 402 | .night blockquote, 403 | .night sup { 404 | border-left: 3px solid #666; 405 | } 406 | .night .content a { 407 | color: #698ebf; 408 | } 409 | .night .from-column { 410 | color: #2b82ac; 411 | background-image: url(http://static.daily.zhihu.com/img/Dark_News_Column_Entrance.png); 412 | } 413 | .night .from-column:active { 414 | background-image: url(http://static.daily.zhihu.com/img/Dark_News_Column_Entrance_Highlight.png); 415 | } 416 | .large .question-title { 417 | font-size: 24px; 418 | } 419 | .large .meta { 420 | font-size: 18px; 421 | } 422 | .large .content { 423 | font-size: 20px; 424 | } 425 | .large blockquote, 426 | .large sup { 427 | line-height: 1.6; 428 | } 429 | .meta .meta-item { 430 | -o-text-overflow: ellipsis; 431 | width: 39%; 432 | overflow: hidden; 433 | white-space: nowrap; 434 | text-overflow: ellipsis; 435 | display: inline-block; 436 | color: #929292; 437 | margin-right: 7px; 438 | } 439 | .headline .meta { 440 | white-space: nowrap; 441 | text-overflow: ellipsis; 442 | overflow: hidden; 443 | font-size: 11px; 444 | color: #b8b8b8; 445 | margin: 15px 0; 446 | padding: 0 20px; 447 | } 448 | .headline .meta a, 449 | .headline .meta a:hover { 450 | padding-left: 1em; 451 | margin-top: 2px; 452 | float: right; 453 | font-size: 11px; 454 | color: #0066cf; 455 | text-decoration: none; 456 | } 457 | .highlight { 458 | overflow: auto; 459 | word-wrap: normal; 460 | background: #fff; 461 | } 462 | .highlight pre { 463 | white-space: pre; 464 | } 465 | .highlight .hll { 466 | background-color: #ffc; 467 | } 468 | .highlight .err { 469 | color: #a61717; 470 | background-color: #e3d2d2; 471 | } 472 | .highlight .cp { 473 | color: #999; 474 | font-weight: 700; 475 | } 476 | .highlight .cs { 477 | color: #999; 478 | font-weight: 700; 479 | font-style: italic; 480 | } 481 | .highlight .gd { 482 | color: #000; 483 | background-color: #fdd; 484 | } 485 | .highlight .gi { 486 | color: #000; 487 | background-color: #dfd; 488 | } 489 | .highlight .gu { 490 | color: #aaa; 491 | } 492 | .highlight .ni { 493 | color: purple; 494 | } 495 | .highlight .nt { 496 | color: navy; 497 | } 498 | .highlight .w { 499 | color: #bbb; 500 | } 501 | .highlight .sr { 502 | color: olive; 503 | } 504 | [hidden], 505 | .button span { 506 | display: none; 507 | } 508 | b, 509 | strong, 510 | .highlight .k, 511 | .highlight .o, 512 | .highlight .gs, 513 | .highlight .kc, 514 | .highlight .kd, 515 | .highlight .kn, 516 | .highlight .kp, 517 | .highlight .kr, 518 | .highlight .ow { 519 | font-weight: 700; 520 | } 521 | dfn, 522 | .highlight .ge { 523 | font-style: italic; 524 | } 525 | .meta span, 526 | .meta .source { 527 | vertical-align: middle; 528 | } 529 | .meta .avatar, 530 | .comment-meta .avatar { 531 | width: 20px; 532 | height: 20px; 533 | border-radius: 2px; 534 | margin-right: 5px; 535 | } 536 | .meta .bio, 537 | .highlight .gh, 538 | .highlight .bp { 539 | color: #999; 540 | } 541 | .night .comment-meta .author, 542 | .night .content, 543 | .night .meta .author, 544 | .highlight .go { 545 | color: #888; 546 | } 547 | .night .headline-title, 548 | .night .headline-background .heading-content, 549 | .night .question-title { 550 | color: #B8B8B8; 551 | } 552 | .highlight .c, 553 | .highlight .cm, 554 | .highlight .c1 { 555 | color: #998; 556 | font-style: italic; 557 | } 558 | .highlight .gr, 559 | .highlight .gt { 560 | color: #a00; 561 | } 562 | .highlight .gp, 563 | .highlight .nn { 564 | color: #555; 565 | } 566 | .highlight .kt, 567 | .highlight .nc { 568 | color: #458; 569 | font-weight: 700; 570 | } 571 | .highlight .m, 572 | .highlight .mf, 573 | .highlight .mh, 574 | .highlight .mi, 575 | .highlight .mo, 576 | .highlight .il { 577 | color: #099; 578 | } 579 | .highlight .s, 580 | .highlight .sb, 581 | .highlight .sc, 582 | .highlight .sd, 583 | .highlight .s2, 584 | .highlight .se, 585 | .highlight .sh, 586 | .highlight .si, 587 | .highlight .sx, 588 | .highlight .s1, 589 | .highlight .ss { 590 | color: #d32; 591 | } 592 | .highlight .na, 593 | .highlight .nb, 594 | .highlight .no, 595 | .highlight .nv, 596 | .highlight .vc, 597 | .highlight .vg, 598 | .highlight .vi { 599 | color: teal; 600 | } 601 | .highlight .ne, 602 | .highlight .nf { 603 | color: #900; 604 | font-weight: 700; 605 | } 606 | .answer h1, 607 | .answer h2, 608 | .answer h3, 609 | .answer h4, 610 | .answer h5 { 611 | font-size: 19px; 612 | } 613 | @media only screen and (-webkit-min-device-pixel-ratio2), only screen and (min-device-pixel-ratio2) { 614 | .icon-arrow-right { 615 | background-image: url(http://static.daily.zhihu.com/img/share-icons@2x.png); 616 | -webkit-background-size: 82px 55px; 617 | background-size: 82px 55px; 618 | } 619 | .icon-vote, 620 | .icon-voted { 621 | background-image: url(http://static.daily.zhihu.com/img/app/Comment_Vote@2x.png) !important; 622 | background-size: 11px 12px; 623 | } 624 | .icon-voted { 625 | background-image: url(http://static.daily.zhihu.com/img/app/Comment_Voted@2x.png) !important; 626 | } 627 | .night .icon-vote { 628 | background-image: url(http://static.daily.zhihu.com/img/app/Dark_Comment_Vote@2x.png) !important; 629 | } 630 | .from-column { 631 | background-image: url(http://static.daily.zhihu.com/img/News_Column_Entrance@2x.png) !important; 632 | background-size: 280px 30px; 633 | } 634 | .from-column:active { 635 | background-image: url(http://static.daily.zhihu.com/img/News_Column_Entrance_Highlight@2x.png) !important; 636 | } 637 | .night .from-column { 638 | color: #2b82ac; 639 | background-image: url(http://static.daily.zhihu.com/img/Dark_News_Column_Entrance@2x.png) !important; 640 | } 641 | .night .from-column:active { 642 | background-image: url(http://static.daily.zhihu.com/img/Dark_News_Column_Entrance_Highlight@2x.png) !important; 643 | } 644 | } 645 | .meta .meta-item { 646 | width: 39%; 647 | overflow: hidden; 648 | white-space: nowrap; 649 | text-overflow: ellipsis; 650 | display: inline-block; 651 | color: #929292; 652 | margin-right: 7px; 653 | } 654 | .headline .meta { 655 | white-space: nowrap; 656 | text-overflow: ellipsis; 657 | overflow: hidden; 658 | font-size: 11px; 659 | color: #b8b8b8; 660 | margin: 20px 0; 661 | padding: 0 20px; 662 | } 663 | .headline .meta a, 664 | .headline .meta a:hover { 665 | margin-top: 2px; 666 | float: right; 667 | font-size: 11px; 668 | color: #0066cf; 669 | text-decoration: none; 670 | } 671 | .answer h1, 672 | .answer h2, 673 | .answer h3, 674 | .answer h4, 675 | .answer h5 { 676 | font-size: 19px; 677 | } 678 | .origin-source, 679 | a.origin-source:link { 680 | display: block; 681 | margin: 25px 0; 682 | height: 50px; 683 | overflow: hidden; 684 | background: #f0f0f0; 685 | color: #888; 686 | position: relative; 687 | -webkit-touch-callout: none; 688 | } 689 | .origin-source .source-logo, 690 | a.origin-source:link .source-logo { 691 | float: left; 692 | width: 50px; 693 | height: 50px; 694 | margin-right: 10px; 695 | } 696 | .origin-source .text, 697 | a.origin-source:link .text { 698 | line-height: 50px; 699 | height: 50px; 700 | font-size: 13px; 701 | } 702 | .origin-source.with-link .text { 703 | color: #333; 704 | } 705 | .origin-source.with-link:after { 706 | display: block; 707 | position: absolute; 708 | border-color: transparent transparent transparent #f0f0f0; 709 | border-width: 7px; 710 | border-style: solid; 711 | height: 0; 712 | width: 0; 713 | top: 18px; 714 | right: 4px; 715 | line-height: 0; 716 | content: ""; 717 | } 718 | .origin-source.with-link:before { 719 | display: block; 720 | height: 0; 721 | width: 0; 722 | position: absolute; 723 | top: 18px; 724 | right: 3px; 725 | border-color: transparent transparent transparent #000; 726 | border-width: 7px; 727 | border-style: solid; 728 | line-height: 0; 729 | content: ""; 730 | } 731 | .origin-source-wrap { 732 | position: relative; 733 | background: #f0f0f0; 734 | } 735 | .origin-source-wrap .focus-link { 736 | position: absolute; 737 | right: 0; 738 | top: 0; 739 | width: 45px; 740 | color: #00a2ed; 741 | height: 50px; 742 | display: none; 743 | text-align: center; 744 | font-size: 12px; 745 | -webkit-touch-callout: none; 746 | } 747 | .origin-source-wrap .focus-link .btn-label { 748 | text-align: center; 749 | display: block; 750 | margin-top: 8px; 751 | border-left: solid 1px #ccc; 752 | height: 34px; 753 | line-height: 34px; 754 | } 755 | .origin-source-wrap.unfocused .focus-link { 756 | display: block; 757 | } 758 | .origin-source-wrap.unfocused .origin-source:before, 759 | .origin-source-wrap.unfocused .origin-source:after { 760 | display: none; 761 | } 762 | .night .origin-source-wrap { 763 | background: #292929; 764 | } 765 | .night .origin-source-wrap .focus-link { 766 | color: #116f9e; 767 | } 768 | .night .origin-source-wrap .btn-label { 769 | border-left: solid 1px #3f3f3f; 770 | } 771 | .night .origin-source, 772 | .night .origin-source.with-link { 773 | background: #292929; 774 | color: #666; 775 | } 776 | .night .origin-source .text, 777 | .night .origin-source.with-link .text { 778 | color: #666; 779 | } 780 | .night .origin-source.with-link:after { 781 | border-color: transparent transparent transparent #292929; 782 | } 783 | .night .origin-source.with-link:before { 784 | border-color: transparent transparent transparent #666; 785 | } 786 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenyangcun/MaterialDesignExample/68438aa2ffa76520d9af35ad247e6414c49f9287/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/AboutFragment.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.app.Fragment; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | /** 11 | * Created by Chenyc on 15/6/27. 12 | */ 13 | public class AboutFragment extends Fragment { 14 | 15 | @Nullable 16 | @Override 17 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 18 | View view = inflater.inflate(R.layout.fragment_about, null); 19 | 20 | return view; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/BlogFragment.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import com.aswifter.material.widget.BackHandledFragment; 10 | import com.aswifter.material.widget.ProgressWebView; 11 | 12 | /** 13 | * Created by Chenyc on 15/6/27. 14 | */ 15 | public class BlogFragment extends BackHandledFragment { 16 | 17 | private ProgressWebView mWebView; 18 | 19 | @Nullable 20 | @Override 21 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 22 | View view = inflater.inflate(R.layout.fragment_blog, null); 23 | mWebView = (ProgressWebView) view.findViewById(R.id.webView); 24 | mWebView.getSettings().setJavaScriptEnabled(true); 25 | mWebView.loadUrl("http://www.aswifter.com/"); 26 | return view; 27 | } 28 | 29 | @Override 30 | public void onActivityCreated(Bundle savedInstanceState) { 31 | super.onActivityCreated(savedInstanceState); 32 | } 33 | 34 | @Override 35 | public String getTagText() { 36 | return null; 37 | } 38 | 39 | @Override 40 | public boolean onBackPressed() { 41 | if(mWebView.canGoBack()){ 42 | mWebView.goBack(); 43 | return true; 44 | } 45 | 46 | return false; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/ExampleFragment.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v4.app.Fragment; 8 | import android.support.v7.widget.DefaultItemAnimator; 9 | import android.support.v7.widget.LinearLayoutManager; 10 | import android.support.v7.widget.RecyclerView; 11 | import android.util.TypedValue; 12 | import android.view.LayoutInflater; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.widget.TextView; 16 | 17 | import com.aswifter.material.example.AppBarDetailActivity; 18 | import com.aswifter.material.example.BottomTabActivity; 19 | import com.aswifter.material.example.CardViewActivity; 20 | import com.aswifter.material.example.EditTextFLActivity; 21 | import com.aswifter.material.news.NewsListActivity; 22 | import com.aswifter.material.widget.DividerItemDecoration; 23 | import com.aswifter.material.widget.RecyclerItemClickListener; 24 | 25 | /** 26 | * Created by Chenyc on 15/6/27. 27 | */ 28 | public class ExampleFragment extends Fragment { 29 | 30 | private RecyclerView mRecyclerView; 31 | private LinearLayoutManager mLayoutManager; 32 | private String[] myDataset; 33 | private MyAdapter mAdapter; 34 | 35 | @Nullable 36 | @Override 37 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 38 | View view = inflater.inflate(R.layout.fragment_example, null); 39 | 40 | //RecyclerView 41 | mRecyclerView = (RecyclerView) view.findViewById(R.id.recyclerView); 42 | mRecyclerView.setHasFixedSize(true); 43 | mLayoutManager = new LinearLayoutManager(getActivity()); 44 | mRecyclerView.setLayoutManager(mLayoutManager); 45 | mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST)); 46 | mRecyclerView.addOnItemTouchListener(new RecyclerItemClickListener(getActivity(), onItemClickListener)); 47 | mRecyclerView.setItemAnimator(new DefaultItemAnimator()); 48 | 49 | // specify an adapter (see also next example) 50 | myDataset = new String[]{"RecycleView", 51 | "TextInputLayout", "CardView", "AppBar & TabLayout","Bottom Tab" 52 | }; 53 | mAdapter = new MyAdapter(getActivity(), myDataset); 54 | mRecyclerView.setAdapter(mAdapter); 55 | return view; 56 | } 57 | 58 | 59 | private RecyclerItemClickListener.OnItemClickListener onItemClickListener = new RecyclerItemClickListener.OnItemClickListener() { 60 | @Override 61 | public void onItemClick(View view, int position) { 62 | Intent intent = null; 63 | switch (position) { 64 | case 0: 65 | intent = new Intent(getActivity(), NewsListActivity.class); 66 | startActivity(intent); 67 | break; 68 | 69 | case 1: 70 | intent = new Intent(getActivity(), EditTextFLActivity.class); 71 | startActivity(intent); 72 | break; 73 | case 2: 74 | intent = new Intent(getActivity(), CardViewActivity.class); 75 | startActivity(intent); 76 | break; 77 | case 3: 78 | intent = new Intent(getActivity(), AppBarDetailActivity.class); 79 | startActivity(intent); 80 | break; 81 | 82 | case 4: 83 | intent = new Intent(getActivity(), BottomTabActivity.class); 84 | startActivity(intent); 85 | break; 86 | } 87 | 88 | 89 | } 90 | }; 91 | 92 | 93 | public class MyAdapter extends RecyclerView.Adapter { 94 | private final int mBackground; 95 | private String[] mDataset; 96 | private final TypedValue mTypedValue = new TypedValue(); 97 | 98 | // Provide a reference to the views for each data item 99 | // Complex data items may need more than one view per item, and 100 | // you provide access to all the views for a data item in a view holder 101 | public class ViewHolder extends RecyclerView.ViewHolder { 102 | // each data item is just a string in this case 103 | public TextView mTextView; 104 | 105 | public int position; 106 | 107 | public ViewHolder(View v) { 108 | super(v); 109 | mTextView = (TextView) v.findViewById(R.id.news_title); 110 | } 111 | } 112 | 113 | // Provide a suitable constructor (depends on the kind of dataset) 114 | public MyAdapter(Context context, String[] myDataset) { 115 | mDataset = myDataset; 116 | context.getTheme().resolveAttribute(R.attr.selectableItemBackground, mTypedValue, true); 117 | mBackground = mTypedValue.resourceId; 118 | } 119 | 120 | @Override 121 | public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, 122 | int viewType) { 123 | // create a new view 124 | View v = LayoutInflater.from(parent.getContext()) 125 | .inflate(R.layout.item_view, parent, false); 126 | v.setBackgroundResource(mBackground); 127 | // set the view's size, margins, paddings and layout parameters 128 | ViewHolder vh = new ViewHolder(v); 129 | return vh; 130 | } 131 | 132 | @Override 133 | public void onBindViewHolder(ViewHolder holder, int position) { 134 | // - get element from your dataset at this position 135 | // - replace the contents of the view with that element 136 | holder.mTextView.setText(mDataset[position]); 137 | 138 | } 139 | 140 | // Return the size of your dataset (invoked by the layout manager) 141 | @Override 142 | public int getItemCount() { 143 | return mDataset.length; 144 | } 145 | } 146 | 147 | 148 | } 149 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.NavigationView; 5 | import android.support.v4.widget.DrawerLayout; 6 | import android.support.v7.app.ActionBarDrawerToggle; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.support.v7.widget.Toolbar; 9 | import android.view.Gravity; 10 | import android.view.Menu; 11 | import android.view.MenuItem; 12 | import android.view.View; 13 | import android.widget.Toast; 14 | 15 | import com.aswifter.material.book.BooksFragment; 16 | import com.aswifter.material.widget.BackHandledFragment; 17 | 18 | public class MainActivity extends AppCompatActivity implements BackHandledFragment.BackHandlerInterface { 19 | 20 | private DrawerLayout mDrawerLayout; 21 | private ActionBarDrawerToggle mDrawerToggle; 22 | private Toolbar mToolbar; 23 | private BackHandledFragment selectedFragment; 24 | private NavigationView mNavigationView; 25 | 26 | private static final int ANIM_DURATION_TOOLBAR = 300; 27 | 28 | @Override 29 | protected void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | setContentView(R.layout.activity_main); 32 | mToolbar = (Toolbar) findViewById(R.id.toolbar); 33 | setSupportActionBar(mToolbar); 34 | 35 | mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); 36 | mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, mToolbar, R.string.drawer_open, 37 | R.string.drawer_close); 38 | mDrawerToggle.syncState(); 39 | mDrawerLayout.addDrawerListener(mDrawerToggle); 40 | mNavigationView = (NavigationView) findViewById(R.id.navigation_view); 41 | setupDrawerContent(mNavigationView); 42 | 43 | //profile Image 44 | setUpProfileImage(); 45 | 46 | 47 | switchToBook(); 48 | 49 | } 50 | 51 | 52 | private void switchToBook() { 53 | getSupportFragmentManager().beginTransaction().replace(R.id.frame_content, new BooksFragment()).commit(); 54 | mToolbar.setTitle(R.string.navigation_book); 55 | } 56 | 57 | private void switchToExample() { 58 | getSupportFragmentManager().beginTransaction().replace(R.id.frame_content, new ExampleFragment()).commit(); 59 | mToolbar.setTitle(R.string.navigation_example); 60 | } 61 | 62 | private void switchToBlog() { 63 | getSupportFragmentManager().beginTransaction().replace(R.id.frame_content, new BlogFragment()).commit(); 64 | mToolbar.setTitle(R.string.navigation_my_blog); 65 | } 66 | 67 | 68 | private void switchToAbout() { 69 | getSupportFragmentManager().beginTransaction().replace(R.id.frame_content, new AboutFragment()).commit(); 70 | mToolbar.setTitle(R.string.navigation_about); 71 | } 72 | 73 | 74 | private void setUpProfileImage() { 75 | View headerView= mNavigationView.inflateHeaderView(R.layout.navigation_header); 76 | View profileView = headerView.findViewById(R.id.profile_image); 77 | if (profileView != null) { 78 | profileView.setOnClickListener(new View.OnClickListener() { 79 | @Override 80 | public void onClick(View v) { 81 | switchToBlog(); 82 | mDrawerLayout.closeDrawers(); 83 | mNavigationView.getMenu().getItem(1).setChecked(true); 84 | } 85 | }); 86 | } 87 | 88 | } 89 | 90 | 91 | private void setupDrawerContent(NavigationView navigationView) { 92 | navigationView.setNavigationItemSelectedListener( 93 | new NavigationView.OnNavigationItemSelectedListener() { 94 | @Override 95 | public boolean onNavigationItemSelected(MenuItem menuItem) { 96 | switch (menuItem.getItemId()) { 97 | 98 | case R.id.navigation_item_book: 99 | switchToBook(); 100 | break; 101 | case R.id.navigation_item_example: 102 | switchToExample(); 103 | break; 104 | case R.id.navigation_item_blog: 105 | switchToBlog(); 106 | break; 107 | case R.id.navigation_item_about: 108 | switchToAbout(); 109 | break; 110 | 111 | } 112 | menuItem.setChecked(true); 113 | mDrawerLayout.closeDrawers(); 114 | return true; 115 | } 116 | }); 117 | } 118 | 119 | 120 | @Override 121 | public boolean onCreateOptionsMenu(Menu menu) { 122 | // Inflate the menu; this adds items to the action bar if it is present. 123 | getMenuInflater().inflate(R.menu.menu_main, menu); 124 | return true; 125 | } 126 | 127 | @Override 128 | public boolean onOptionsItemSelected(MenuItem item) { 129 | // Handle action bar item clicks here. The action bar will 130 | // automatically handle clicks on the Home/Up button, so long 131 | // as you specify a parent activity in AndroidManifest.xml. 132 | int id = item.getItemId(); 133 | 134 | //noinspection SimplifiableIfStatement 135 | if (id == R.id.action_settings) { 136 | return true; 137 | } 138 | 139 | return super.onOptionsItemSelected(item); 140 | } 141 | 142 | @Override 143 | public void setSelectedFragment(BackHandledFragment backHandledFragment) { 144 | this.selectedFragment = backHandledFragment; 145 | } 146 | 147 | 148 | private long exitTime = 0; 149 | 150 | public void doExitApp() { 151 | if ((System.currentTimeMillis() - exitTime) > 2000) { 152 | Toast.makeText(this, R.string.press_again_exit_app, Toast.LENGTH_SHORT).show(); 153 | exitTime = System.currentTimeMillis(); 154 | } else { 155 | finish(); 156 | } 157 | } 158 | 159 | @Override 160 | public void onBackPressed() { 161 | if (selectedFragment == null || !selectedFragment.onBackPressed()) { 162 | if (mDrawerLayout.isDrawerOpen(Gravity.LEFT)) { 163 | mDrawerLayout.closeDrawer(Gravity.LEFT); 164 | } else { 165 | doExitApp(); 166 | } 167 | } 168 | 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/MaterialApplication.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material; 2 | 3 | import android.app.Application; 4 | 5 | import com.aswifter.material.common.AppClient; 6 | import com.aswifter.material.utils.DisplayUtil; 7 | 8 | /** 9 | * Created by erfli on 6/14/16. 10 | */ 11 | public class MaterialApplication extends Application{ 12 | @Override 13 | public void onCreate() { 14 | super.onCreate(); 15 | AppClient.initAppClient(); 16 | DisplayUtil.init(this); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/book/Book.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.book; 2 | 3 | import java.io.Serializable; 4 | import java.util.Arrays; 5 | 6 | /** 7 | * Created by Chenyc on 15/6/30. 8 | */ 9 | public class Book implements Serializable { 10 | 11 | private String subtitle; 12 | private String[] author; 13 | private String pubdate; 14 | private String origin_title; 15 | private String image; 16 | private String catalog; 17 | private String alt; 18 | private String id; 19 | private String publisher; 20 | private String title; 21 | private String url; 22 | private String author_intro; 23 | private String summary; 24 | private String price; 25 | private String pages; 26 | private Images images; 27 | 28 | 29 | public String getSubtitle() { 30 | return subtitle; 31 | } 32 | 33 | public void setSubtitle(String subtitle) { 34 | this.subtitle = subtitle; 35 | } 36 | 37 | public String[] getAuthor() { 38 | return author; 39 | } 40 | 41 | public void setAuthor(String[] author) { 42 | this.author = author; 43 | } 44 | 45 | public String getPubdate() { 46 | return pubdate; 47 | } 48 | 49 | public void setPubdate(String pubdate) { 50 | this.pubdate = pubdate; 51 | } 52 | 53 | public String getOrigin_title() { 54 | return origin_title; 55 | } 56 | 57 | public void setOrigin_title(String origin_title) { 58 | this.origin_title = origin_title; 59 | } 60 | 61 | public String getImage() { 62 | return image; 63 | } 64 | 65 | public void setImage(String image) { 66 | this.image = image; 67 | } 68 | 69 | public String getCatalog() { 70 | return catalog; 71 | } 72 | 73 | public void setCatalog(String catalog) { 74 | this.catalog = catalog; 75 | } 76 | 77 | public String getAlt() { 78 | return alt; 79 | } 80 | 81 | public void setAlt(String alt) { 82 | this.alt = alt; 83 | } 84 | 85 | public String getId() { 86 | return id; 87 | } 88 | 89 | public void setId(String id) { 90 | this.id = id; 91 | } 92 | 93 | public String getPublisher() { 94 | return publisher; 95 | } 96 | 97 | public void setPublisher(String publisher) { 98 | this.publisher = publisher; 99 | } 100 | 101 | public String getTitle() { 102 | return title; 103 | } 104 | 105 | public void setTitle(String title) { 106 | this.title = title; 107 | } 108 | 109 | public String getUrl() { 110 | return url; 111 | } 112 | 113 | public void setUrl(String url) { 114 | this.url = url; 115 | } 116 | 117 | public String getAuthor_intro() { 118 | return author_intro; 119 | } 120 | 121 | public void setAuthor_intro(String author_intro) { 122 | this.author_intro = author_intro; 123 | } 124 | 125 | public String getSummary() { 126 | return summary; 127 | } 128 | 129 | public void setSummary(String summary) { 130 | this.summary = summary; 131 | } 132 | 133 | public String getPrice() { 134 | return price; 135 | } 136 | 137 | public void setPrice(String price) { 138 | this.price = price; 139 | } 140 | 141 | public String getPages() { 142 | return pages; 143 | } 144 | 145 | public void setPages(String pages) { 146 | this.pages = pages; 147 | } 148 | 149 | public Images getImages() { 150 | return images; 151 | } 152 | 153 | public void setImages(Images images) { 154 | this.images = images; 155 | } 156 | 157 | public class Images implements Serializable { 158 | private String small; 159 | private String large; 160 | private String medium; 161 | 162 | public String getSmall() { 163 | return small; 164 | } 165 | 166 | public void setSmall(String small) { 167 | this.small = small; 168 | } 169 | 170 | public String getLarge() { 171 | return large; 172 | } 173 | 174 | public void setLarge(String large) { 175 | this.large = large; 176 | } 177 | 178 | public String getMedium() { 179 | return medium; 180 | } 181 | 182 | public void setMedium(String medium) { 183 | this.medium = medium; 184 | } 185 | } 186 | 187 | @Override 188 | public String toString() { 189 | return "Book{" + 190 | "subtitle='" + subtitle + '\'' + 191 | ", author=" + Arrays.toString(author) + 192 | ", pubdate='" + pubdate + '\'' + 193 | ", origin_title='" + origin_title + '\'' + 194 | ", image='" + image + '\'' + 195 | ", catalog='" + catalog + '\'' + 196 | ", alt='" + alt + '\'' + 197 | ", id='" + id + '\'' + 198 | ", publisher='" + publisher + '\'' + 199 | ", title='" + title + '\'' + 200 | ", url='" + url + '\'' + 201 | ", author_intro='" + author_intro + '\'' + 202 | ", summary='" + summary + '\'' + 203 | ", price='" + price + '\'' + 204 | '}'; 205 | } 206 | 207 | 208 | 209 | 210 | } 211 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/book/BookDetailActivity.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.book; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.CollapsingToolbarLayout; 5 | import android.support.design.widget.TabLayout; 6 | import android.support.v4.app.Fragment; 7 | import android.support.v4.app.FragmentManager; 8 | import android.support.v4.app.FragmentPagerAdapter; 9 | import android.support.v4.view.ViewPager; 10 | import android.support.v7.app.AppCompatActivity; 11 | import android.support.v7.widget.Toolbar; 12 | import android.view.View; 13 | import android.widget.ImageView; 14 | 15 | import com.aswifter.material.R; 16 | import com.aswifter.material.example.DetailFragment; 17 | import com.bumptech.glide.Glide; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | /** 23 | * Created by Chenyc on 15/7/1. 24 | */ 25 | public class BookDetailActivity extends AppCompatActivity { 26 | 27 | 28 | private ViewPager mViewPager; 29 | private Book mBook; 30 | 31 | @Override 32 | protected void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.activity_appbar_detail); 35 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 36 | setSupportActionBar(toolbar); 37 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 38 | toolbar.setNavigationOnClickListener(new View.OnClickListener() { 39 | @Override 40 | public void onClick(View view) { 41 | onBackPressed(); 42 | } 43 | }); 44 | 45 | mBook = (Book) getIntent().getSerializableExtra("book"); 46 | CollapsingToolbarLayout collapsingToolbar = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar); 47 | collapsingToolbar.setTitle(mBook.getTitle()); 48 | 49 | ImageView ivImage = (ImageView) findViewById(R.id.ivImage); 50 | Glide.with(ivImage.getContext()) 51 | .load(mBook.getImages().getLarge()) 52 | .fitCenter() 53 | .into(ivImage); 54 | 55 | mViewPager = (ViewPager) findViewById(R.id.viewpager); 56 | setupViewPager(mViewPager); 57 | 58 | TabLayout tabLayout = (TabLayout) findViewById(R.id.sliding_tabs); 59 | tabLayout.addTab(tabLayout.newTab().setText("内容简介")); 60 | tabLayout.addTab(tabLayout.newTab().setText("作者简介")); 61 | tabLayout.addTab(tabLayout.newTab().setText("目录")); 62 | tabLayout.setupWithViewPager(mViewPager); 63 | } 64 | 65 | 66 | private void setupViewPager(ViewPager mViewPager) { 67 | MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager()); 68 | adapter.addFragment(DetailFragment.newInstance(mBook.getSummary()), "内容简介"); 69 | adapter.addFragment(DetailFragment.newInstance(mBook.getAuthor_intro()), "作者简介"); 70 | adapter.addFragment(DetailFragment.newInstance(mBook.getCatalog()), "目录"); 71 | mViewPager.setAdapter(adapter); 72 | } 73 | 74 | 75 | static class MyPagerAdapter extends FragmentPagerAdapter { 76 | private final List mFragments = new ArrayList<>(); 77 | private final List mFragmentTitles = new ArrayList<>(); 78 | 79 | public MyPagerAdapter(FragmentManager fm) { 80 | super(fm); 81 | } 82 | 83 | public void addFragment(Fragment fragment, String title) { 84 | mFragments.add(fragment); 85 | mFragmentTitles.add(title); 86 | } 87 | 88 | @Override 89 | public Fragment getItem(int position) { 90 | return mFragments.get(position); 91 | } 92 | 93 | @Override 94 | public int getCount() { 95 | return mFragments.size(); 96 | } 97 | 98 | @Override 99 | public CharSequence getPageTitle(int position) { 100 | return mFragmentTitles.get(position); 101 | } 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/book/BookResponse.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.book; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Created by erfli on 6/14/16. 8 | */ 9 | public class BookResponse { 10 | public int count; 11 | public int start; 12 | public int total; 13 | public ArrayList books; 14 | 15 | public int getCount() { 16 | return count; 17 | } 18 | 19 | public int getStart() { 20 | return start; 21 | } 22 | 23 | public int getTotal() { 24 | return total; 25 | } 26 | 27 | public ArrayList getBooks() { 28 | return books; 29 | } 30 | 31 | public void setCount(int count) { 32 | this.count = count; 33 | } 34 | 35 | public void setStart(int start) { 36 | this.start = start; 37 | } 38 | 39 | public void setTotal(int total) { 40 | this.total = total; 41 | } 42 | 43 | public void setBooks(ArrayList books) { 44 | this.books = books; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/book/BooksFragment.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.book; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.design.widget.FloatingActionButton; 7 | import android.support.v4.app.ActivityCompat; 8 | import android.support.v4.app.ActivityOptionsCompat; 9 | import android.support.v4.app.Fragment; 10 | import android.support.v7.widget.DefaultItemAnimator; 11 | import android.support.v7.widget.LinearLayoutManager; 12 | import android.support.v7.widget.RecyclerView; 13 | import android.text.TextUtils; 14 | import android.view.LayoutInflater; 15 | import android.view.View; 16 | import android.view.ViewGroup; 17 | import android.view.animation.OvershootInterpolator; 18 | import android.widget.ProgressBar; 19 | 20 | import com.afollestad.materialdialogs.MaterialDialog; 21 | import com.aswifter.material.R; 22 | import com.aswifter.material.common.ThreadPool; 23 | import com.aswifter.material.widget.RecyclerItemClickListener; 24 | import com.google.android.agera.BaseObservable; 25 | import com.google.android.agera.Repositories; 26 | import com.google.android.agera.Repository; 27 | import com.google.android.agera.Result; 28 | import com.google.android.agera.Updatable; 29 | 30 | import java.util.List; 31 | import java.util.concurrent.ExecutorService; 32 | 33 | /** 34 | * Created by Chenyc on 15/7/1. 35 | */ 36 | public class BooksFragment extends Fragment implements Updatable { 37 | 38 | private RecyclerView mRecyclerView; 39 | private MyAdapter mAdapter; 40 | private ProgressBar mProgressBar; 41 | private FloatingActionButton mFabButton; 42 | 43 | private static final int ANIM_DURATION_FAB = 400; 44 | private ExecutorService networkExecutor; 45 | private Repository>> booksRepository; 46 | private SearchObservable searchObservable; 47 | private BooksSupplier booksSupplier; 48 | 49 | @Nullable 50 | @Override 51 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 52 | View view = inflater.inflate(R.layout.fragment_books, null); 53 | mRecyclerView = (RecyclerView) view.findViewById(R.id.recyclerView); 54 | mRecyclerView.setHasFixedSize(true); 55 | LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity()); 56 | mRecyclerView.setLayoutManager(layoutManager); 57 | mRecyclerView.addOnItemTouchListener(new RecyclerItemClickListener(getActivity(), onItemClickListener)); 58 | mRecyclerView.setItemAnimator(new DefaultItemAnimator()); 59 | 60 | mProgressBar = (ProgressBar) view.findViewById(R.id.progressBar); 61 | 62 | mAdapter = new MyAdapter(this, getActivity()); 63 | mRecyclerView.setAdapter(mAdapter); 64 | 65 | setUpFAB(view); 66 | 67 | setUpRepository(); 68 | 69 | return view; 70 | } 71 | 72 | public class SearchObservable extends BaseObservable { 73 | 74 | public void doSearch(String key) { 75 | booksSupplier.setKey(key); 76 | dispatchUpdate(); 77 | } 78 | 79 | } 80 | 81 | 82 | private void setUpRepository() { 83 | searchObservable = new SearchObservable(); 84 | booksSupplier = new BooksSupplier(getString(R.string.default_search_keyword)); 85 | // Set up books repository 86 | booksRepository = Repositories 87 | .repositoryWithInitialValue(Result.>absent()) 88 | .observe(searchObservable) 89 | .onUpdatesPerLoop() 90 | .goTo(ThreadPool.executor) 91 | .thenGetFrom(booksSupplier) 92 | .compile(); 93 | } 94 | 95 | @Override 96 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 97 | super.onActivityCreated(savedInstanceState); 98 | mFabButton.setTranslationY(2 * getResources().getDimensionPixelOffset(R.dimen.btn_fab_size)); 99 | //doSearch(getString(R.string.default_search_keyword)); 100 | } 101 | 102 | 103 | @Override 104 | public void update() { 105 | mProgressBar.setVisibility(View.GONE); 106 | startFABAnimation(); 107 | if (booksRepository.get().isPresent()) { 108 | mAdapter.updateItems(booksRepository.get().get(), true); 109 | } 110 | } 111 | 112 | @Override 113 | public void onResume() { 114 | super.onResume(); 115 | booksRepository.addUpdatable(this); 116 | } 117 | 118 | 119 | @Override 120 | public void onPause() { 121 | super.onPause(); 122 | booksRepository.removeUpdatable(this); 123 | } 124 | 125 | private void doSearch(String keyword) { 126 | mProgressBar.setVisibility(View.VISIBLE); 127 | mAdapter.clearItems(); 128 | searchObservable.doSearch(keyword); 129 | } 130 | 131 | 132 | private void setUpFAB(View view) { 133 | mFabButton = (FloatingActionButton) view.findViewById(R.id.fab_normal); 134 | mFabButton.setOnClickListener(new View.OnClickListener() { 135 | @Override 136 | public void onClick(View view) { 137 | new MaterialDialog.Builder(getActivity()) 138 | .title(R.string.search) 139 | //.inputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD) 140 | .input(R.string.input_hint, R.string.input_prefill, new MaterialDialog.InputCallback() { 141 | @Override 142 | public void onInput(MaterialDialog dialog, CharSequence input) { 143 | // Do something 144 | if (!TextUtils.isEmpty(input)) { 145 | doSearch(input.toString()); 146 | } 147 | } 148 | }).show(); 149 | } 150 | }); 151 | } 152 | 153 | 154 | private void startFABAnimation() { 155 | mFabButton.animate() 156 | .translationY(0) 157 | .setInterpolator(new OvershootInterpolator(1.f)) 158 | .setStartDelay(500) 159 | .setDuration(ANIM_DURATION_FAB) 160 | .start(); 161 | } 162 | 163 | 164 | private RecyclerItemClickListener.OnItemClickListener onItemClickListener = new RecyclerItemClickListener.OnItemClickListener() { 165 | @Override 166 | public void onItemClick(View view, int position) { 167 | Book book = mAdapter.getBook(position); 168 | Intent intent = new Intent(getActivity(), BookDetailActivity.class); 169 | intent.putExtra("book", book); 170 | 171 | ActivityOptionsCompat options = 172 | ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), 173 | view.findViewById(R.id.ivBook), getString(R.string.transition_book_img)); 174 | 175 | ActivityCompat.startActivity(getActivity(), intent, options.toBundle()); 176 | 177 | } 178 | }; 179 | 180 | 181 | } 182 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/book/BooksSupplier.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.book; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import com.aswifter.material.common.AppClient; 6 | import com.google.android.agera.Result; 7 | import com.google.android.agera.Supplier; 8 | import com.google.gson.Gson; 9 | import com.google.gson.reflect.TypeToken; 10 | 11 | import org.json.JSONArray; 12 | import org.json.JSONObject; 13 | 14 | import java.io.IOException; 15 | import java.util.HashMap; 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | import okhttp3.HttpUrl; 20 | import okhttp3.OkHttpClient; 21 | import okhttp3.Request; 22 | import okhttp3.Response; 23 | import okhttp3.ResponseBody; 24 | 25 | /** 26 | * Created by chenyc on 16/4/27. 27 | */ 28 | public class BooksSupplier implements Supplier>> { 29 | 30 | public String key; 31 | 32 | public void setKey(String key) { 33 | this.key = key; 34 | } 35 | 36 | public BooksSupplier(String key) { 37 | this.key = key; 38 | } 39 | 40 | 41 | OkHttpClient client = new OkHttpClient(); 42 | 43 | private static final String BASE_URL = "https://api.douban.com/v2/"; 44 | 45 | private static String getAbsoluteUrl(String relativeUrl) { 46 | return BASE_URL + relativeUrl; 47 | } 48 | 49 | 50 | private List getBooks() { 51 | Mapparams = new HashMap<>(); 52 | params.put("q",key); 53 | params.put("start","0"); 54 | params.put("end","50"); 55 | try { 56 | BookResponse bookResponse = AppClient.httpService.getBooks(params).execute().body(); 57 | return bookResponse.getBooks(); 58 | } catch (IOException e) { 59 | e.printStackTrace(); 60 | return null; 61 | } 62 | } 63 | 64 | 65 | @NonNull 66 | @Override 67 | public Result> get() { 68 | List books = getBooks(); 69 | if (books == null) { 70 | return Result.failure(); 71 | } else { 72 | return Result.success(books); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/book/MyAdapter.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.book; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.util.TypedValue; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.view.animation.DecelerateInterpolator; 10 | 11 | import com.aswifter.material.R; 12 | import com.aswifter.material.common.Utils; 13 | import com.bumptech.glide.Glide; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | /** 19 | * Created by erfli on 6/15/16. 20 | */ 21 | public class MyAdapter extends RecyclerView.Adapter { 22 | private BooksFragment booksFragment; 23 | private final int mBackground; 24 | private List mBooks = new ArrayList(); 25 | private final TypedValue mTypedValue = new TypedValue(); 26 | 27 | private static final int ANIMATED_ITEMS_COUNT = 4; 28 | 29 | private boolean animateItems = false; 30 | private int lastAnimatedPosition = -1; 31 | 32 | // Provide a suitable constructor (depends on the kind of dataset) 33 | public MyAdapter(BooksFragment booksFragment, Context context) { 34 | this.booksFragment = booksFragment; 35 | context.getTheme().resolveAttribute(R.attr.selectableItemBackground, mTypedValue, true); 36 | mBackground = mTypedValue.resourceId; 37 | } 38 | 39 | 40 | private void runEnterAnimation(View view, int position) { 41 | if (!animateItems || position >= ANIMATED_ITEMS_COUNT - 1) { 42 | return; 43 | } 44 | 45 | if (position > lastAnimatedPosition) { 46 | lastAnimatedPosition = position; 47 | view.setTranslationY(Utils.getScreenHeight(booksFragment.getActivity())); 48 | view.animate() 49 | .translationY(0) 50 | .setStartDelay(100 * position) 51 | .setInterpolator(new DecelerateInterpolator(3.f)) 52 | .setDuration(700) 53 | .start(); 54 | } 55 | } 56 | 57 | 58 | public void updateItems(List books, boolean animated) { 59 | animateItems = animated; 60 | lastAnimatedPosition = -1; 61 | mBooks.addAll(books); 62 | notifyDataSetChanged(); 63 | } 64 | 65 | public void clearItems() { 66 | mBooks.clear(); 67 | notifyDataSetChanged(); 68 | } 69 | 70 | 71 | @Override 72 | public ViewHolder onCreateViewHolder(ViewGroup parent, 73 | int viewType) { 74 | // create a new view 75 | View v = LayoutInflater.from(parent.getContext()) 76 | .inflate(R.layout.book_item, parent, false); 77 | //v.setBackgroundResource(mBackground); 78 | // set the view's size, margins, paddings and layout parameters 79 | ViewHolder vh = new ViewHolder(v); 80 | return vh; 81 | } 82 | 83 | @Override 84 | public void onBindViewHolder(ViewHolder holder, int position) { 85 | runEnterAnimation(holder.itemView, position); 86 | Book book = mBooks.get(position); 87 | holder.tvTitle.setText(book.getTitle()); 88 | String desc = "作者: " + (book.getAuthor().length > 0 ? book.getAuthor()[0] : "") + "\n副标题: " + book.getSubtitle() 89 | + "\n出版年: " + book.getPubdate() + "\n页数: " + book.getPages() + "\n定价:" + book.getPrice(); 90 | holder.tvDesc.setText(desc); 91 | Glide.with(holder.ivBook.getContext()) 92 | .load(book.getImage()) 93 | .fitCenter() 94 | .into(holder.ivBook); 95 | } 96 | 97 | // Return the size of your dataset (invoked by the layout manager) 98 | @Override 99 | public int getItemCount() { 100 | return mBooks.size(); 101 | } 102 | 103 | 104 | public Book getBook(int pos) { 105 | return mBooks.get(pos); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/book/OnRefreshObservable.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.book; 2 | 3 | import android.support.v4.widget.SwipeRefreshLayout; 4 | 5 | import com.google.android.agera.BaseObservable; 6 | 7 | public class OnRefreshObservable extends BaseObservable 8 | implements SwipeRefreshLayout.OnRefreshListener { 9 | 10 | /** 11 | * Triggered when the associated {@link SwipeRefreshLayout} is refreshed by the user. The event 12 | * is passed on to the observers, using the {@link UpdateDispatcher} provided by {@link 13 | * BaseObservable}. 14 | */ 15 | @Override 16 | public void onRefresh() { 17 | dispatchUpdate(); 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/book/ViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.book; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | import android.widget.ImageView; 6 | import android.widget.TextView; 7 | 8 | import com.aswifter.material.R; 9 | 10 | /** 11 | * Created by erfli on 6/15/16. 12 | */ 13 | public class ViewHolder extends RecyclerView.ViewHolder { 14 | // each data item is just a string in this case 15 | public ImageView ivBook; 16 | public TextView tvTitle; 17 | public TextView tvDesc; 18 | 19 | public int position; 20 | 21 | public ViewHolder(View v) { 22 | super(v); 23 | ivBook = (ImageView) v.findViewById(R.id.ivBook); 24 | tvTitle = (TextView) v.findViewById(R.id.tvTitle); 25 | tvDesc = (TextView) v.findViewById(R.id.tvDesc); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/common/AppClient.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.common; 2 | 3 | import com.aswifter.material.book.BookResponse; 4 | import com.aswifter.material.news.ConverterName; 5 | import com.aswifter.material.news.NewsDetailResponse; 6 | import com.aswifter.material.news.NewsResponse; 7 | import com.google.gson.Gson; 8 | import com.google.gson.reflect.TypeToken; 9 | 10 | import java.io.IOException; 11 | import java.lang.annotation.Annotation; 12 | import java.lang.reflect.Type; 13 | import java.util.Map; 14 | 15 | import okhttp3.RequestBody; 16 | import okhttp3.ResponseBody; 17 | import retrofit2.Call; 18 | import retrofit2.Converter; 19 | import retrofit2.Retrofit; 20 | import retrofit2.http.GET; 21 | import retrofit2.http.Path; 22 | import retrofit2.http.Query; 23 | import retrofit2.http.QueryMap; 24 | 25 | /** 26 | * Created by erfli on 6/14/16. 27 | */ 28 | public class AppClient { 29 | public interface HttpService { 30 | @GET("https://api.douban.com/v2/book/search") 31 | Call getBooks(@QueryMap Map options); 32 | 33 | @GET("news/latest") 34 | Call getLatestNews(); 35 | 36 | @GET("news/before/{path}") 37 | Call getHistoryNews(@Path("path") String path); 38 | 39 | @GET("news/{key}") 40 | Call getNewsDetail(@Path("key") String key); 41 | 42 | @GET("http://news.at.zhihu.com/css/news_qa.auto.css") 43 | @ConverterName("string") 44 | Call getCSS(@Query("v") String key); 45 | } 46 | 47 | public static HttpService httpService; 48 | 49 | public static void initAppClient() { 50 | Retrofit retrofit = new Retrofit.Builder() 51 | .baseUrl("http://news-at.zhihu.com/api/4/") 52 | .addConverterFactory(new Converter.Factory() { 53 | Gson gson = new Gson(); 54 | 55 | @Override 56 | public Converter responseBodyConverter(final Type type, final Annotation[] annotations, Retrofit retrofit) { 57 | return new Converter() { 58 | @Override 59 | public Object convert(ResponseBody value) throws IOException { 60 | try { 61 | if (annotations.length > 1) { 62 | for (Annotation annotation : annotations) { 63 | if (annotation instanceof ConverterName && ((ConverterName) annotation).value().equals("string")) { 64 | return value.string(); 65 | } 66 | } 67 | } 68 | return gson.getAdapter(TypeToken.get(type)).fromJson(value.charStream()); 69 | } finally { 70 | value.close(); 71 | } 72 | } 73 | }; 74 | } 75 | 76 | @Override 77 | public Converter requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { 78 | return super.requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit); 79 | } 80 | 81 | @Override 82 | public Converter stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) { 83 | return super.stringConverter(type, annotations, retrofit); 84 | } 85 | }) 86 | .build(); 87 | httpService = retrofit.create(HttpService.class); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/common/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.common; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | 5 | /** 6 | * Created by erfli on 6/16/16. 7 | */ 8 | public abstract class BaseActivity extends AppCompatActivity { 9 | protected abstract void initView(); 10 | protected abstract void initRepository(); 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/common/ThreadPool.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.common; 2 | 3 | import java.util.concurrent.ScheduledThreadPoolExecutor; 4 | import java.util.concurrent.ThreadPoolExecutor; 5 | 6 | /** 7 | * Created by erfli on 6/14/16. 8 | */ 9 | public class ThreadPool { 10 | public static ThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(6); 11 | public static void clearExecut(){ 12 | executor.getQueue().clear(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/common/Utils.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.common; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.graphics.Point; 6 | import android.os.Build; 7 | import android.view.Display; 8 | import android.view.WindowManager; 9 | 10 | /** 11 | * Created by froger_mcs on 05.11.14. 12 | */ 13 | public class Utils { 14 | private static int screenWidth = 0; 15 | private static int screenHeight = 0; 16 | 17 | public static int dpToPx(int dp) { 18 | return (int) (dp * Resources.getSystem().getDisplayMetrics().density); 19 | } 20 | 21 | public static int getScreenHeight(Context c) { 22 | if (screenHeight == 0) { 23 | WindowManager wm = (WindowManager) c.getSystemService(Context.WINDOW_SERVICE); 24 | Display display = wm.getDefaultDisplay(); 25 | Point size = new Point(); 26 | display.getSize(size); 27 | screenHeight = size.y; 28 | } 29 | 30 | return screenHeight; 31 | } 32 | 33 | public static int getScreenWidth(Context c) { 34 | if (screenWidth == 0) { 35 | WindowManager wm = (WindowManager) c.getSystemService(Context.WINDOW_SERVICE); 36 | Display display = wm.getDefaultDisplay(); 37 | Point size = new Point(); 38 | display.getSize(size); 39 | screenWidth = size.x; 40 | } 41 | 42 | return screenWidth; 43 | } 44 | 45 | public static boolean isAndroid5() { 46 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/example/AppBarDetailActivity.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.example; 2 | 3 | import android.content.res.AssetManager; 4 | import android.os.Bundle; 5 | import android.support.design.widget.CollapsingToolbarLayout; 6 | import android.support.design.widget.TabLayout; 7 | import android.support.v4.app.Fragment; 8 | import android.support.v4.app.FragmentManager; 9 | import android.support.v4.app.FragmentPagerAdapter; 10 | import android.support.v4.view.ViewPager; 11 | import android.support.v7.app.AppCompatActivity; 12 | import android.support.v7.widget.Toolbar; 13 | import android.view.View; 14 | import android.widget.ImageView; 15 | 16 | import com.aswifter.material.R; 17 | 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.Scanner; 23 | 24 | /** 25 | * Created by Chenyc on 2015/6/29. 26 | */ 27 | public class AppBarDetailActivity extends AppCompatActivity { 28 | 29 | private ViewPager mViewPager; 30 | 31 | @Override 32 | protected void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.activity_appbar_detail); 35 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 36 | setSupportActionBar(toolbar); 37 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 38 | toolbar.setNavigationOnClickListener(new View.OnClickListener() { 39 | @Override 40 | public void onClick(View view) { 41 | onBackPressed(); 42 | } 43 | }); 44 | 45 | 46 | CollapsingToolbarLayout collapsingToolbar = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar); 47 | collapsingToolbar.setTitle("失控"); 48 | 49 | ImageView ivImage = (ImageView)findViewById(R.id.ivImage); 50 | ivImage.setImageResource(R.drawable.book1); 51 | 52 | mViewPager = (ViewPager) findViewById(R.id.viewpager); 53 | setupViewPager(mViewPager); 54 | 55 | TabLayout tabLayout = (TabLayout) findViewById(R.id.sliding_tabs); 56 | tabLayout.addTab(tabLayout.newTab().setText("内容简介")); 57 | tabLayout.addTab(tabLayout.newTab().setText("作者简介")); 58 | tabLayout.addTab(tabLayout.newTab().setText("目录")); 59 | tabLayout.setupWithViewPager(mViewPager); 60 | } 61 | 62 | private void setupViewPager(ViewPager mViewPager) { 63 | MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager()); 64 | adapter.addFragment(DetailFragment.newInstance(getAsset("book_content.txt")), "内容简介"); 65 | adapter.addFragment(DetailFragment.newInstance(getAsset("book_author.txt")), "作者简介"); 66 | adapter.addFragment(DetailFragment.newInstance(getAsset("book_menu.txt")), "目录"); 67 | mViewPager.setAdapter(adapter); 68 | } 69 | 70 | private String getAsset(String fileName) { 71 | AssetManager am = getResources().getAssets(); 72 | InputStream is = null; 73 | try { 74 | is = am.open(fileName, AssetManager.ACCESS_BUFFER); 75 | } catch (IOException e) { 76 | e.printStackTrace(); 77 | } 78 | return new Scanner(is).useDelimiter("\\Z").next(); 79 | } 80 | 81 | 82 | static class MyPagerAdapter extends FragmentPagerAdapter { 83 | private final List mFragments = new ArrayList<>(); 84 | private final List mFragmentTitles = new ArrayList<>(); 85 | 86 | public MyPagerAdapter(FragmentManager fm) { 87 | super(fm); 88 | } 89 | 90 | public void addFragment(Fragment fragment, String title) { 91 | mFragments.add(fragment); 92 | mFragmentTitles.add(title); 93 | } 94 | 95 | @Override 96 | public Fragment getItem(int position) { 97 | return mFragments.get(position); 98 | } 99 | 100 | @Override 101 | public int getCount() { 102 | return mFragments.size(); 103 | } 104 | 105 | @Override 106 | public CharSequence getPageTitle(int position) { 107 | return mFragmentTitles.get(position); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/example/BottomTabActivity.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.example; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.design.widget.TabLayout; 6 | import android.support.v4.app.Fragment; 7 | import android.support.v4.app.FragmentManager; 8 | import android.support.v4.app.FragmentPagerAdapter; 9 | import android.support.v4.view.ViewPager; 10 | import android.support.v7.app.AppCompatActivity; 11 | import android.support.v7.widget.Toolbar; 12 | import android.view.LayoutInflater; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.widget.ImageView; 16 | import android.widget.TextView; 17 | 18 | import com.aswifter.material.R; 19 | 20 | /** 21 | * Created by chenyc on 15/8/9. 22 | */ 23 | public class BottomTabActivity extends AppCompatActivity { 24 | 25 | private ViewPager viewPager; 26 | private TabLayout tabLayout; 27 | 28 | @Override 29 | protected void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | setContentView(R.layout.activity_bottom_tab); 32 | 33 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 34 | toolbar.setTitle(R.string.title_buttom_tab); 35 | setSupportActionBar(toolbar); 36 | getSupportActionBar().setHomeButtonEnabled(true); 37 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 38 | toolbar.setNavigationOnClickListener(new View.OnClickListener() { 39 | @Override 40 | public void onClick(View view) { 41 | onBackPressed(); 42 | } 43 | }); 44 | 45 | viewPager = (ViewPager)findViewById(R.id.viewPager); 46 | tabLayout = (TabLayout) findViewById(R.id.tabLayout); 47 | 48 | SampleFragmentPagerAdapter pagerAdapter = 49 | new SampleFragmentPagerAdapter(getSupportFragmentManager(), this); 50 | 51 | viewPager.setAdapter(pagerAdapter); 52 | 53 | 54 | tabLayout.setupWithViewPager(viewPager); 55 | 56 | for (int i = 0; i < tabLayout.getTabCount(); i++) { 57 | TabLayout.Tab tab = tabLayout.getTabAt(i); 58 | if (tab != null) { 59 | tab.setCustomView(pagerAdapter.getTabView(i)); 60 | } 61 | } 62 | 63 | // viewPager.setCurrentItem(1); 64 | tabLayout.getTabAt(0).getCustomView().setSelected(true); 65 | } 66 | 67 | 68 | public class SampleFragmentPagerAdapter extends FragmentPagerAdapter { 69 | final int PAGE_COUNT = 3; 70 | private String tabTitles[] = new String[]{"TAB1","TAB2","TAB3"}; 71 | private Context context; 72 | 73 | 74 | public View getTabView(int position) { 75 | View v = LayoutInflater.from(context).inflate(R.layout.custom_tab, null); 76 | TextView tv = (TextView) v.findViewById(R.id.news_title); 77 | tv.setText(tabTitles[position]); 78 | ImageView img = (ImageView) v.findViewById(R.id.imageView); 79 | //img.setImageResource(imageResId[position]); 80 | return v; 81 | } 82 | 83 | public SampleFragmentPagerAdapter(FragmentManager fm, Context context) { 84 | super(fm); 85 | this.context = context; 86 | } 87 | 88 | @Override 89 | public int getCount() { 90 | return PAGE_COUNT; 91 | } 92 | 93 | @Override 94 | public Fragment getItem(int position) { 95 | return PageFragment.newInstance(position + 1); 96 | } 97 | 98 | @Override 99 | public CharSequence getPageTitle(int position) { 100 | return tabTitles[position]; 101 | } 102 | } 103 | 104 | public static class PageFragment extends Fragment { 105 | public static final String ARG_PAGE = "ARG_PAGE"; 106 | 107 | private int mPage; 108 | 109 | public static PageFragment newInstance(int page) { 110 | Bundle args = new Bundle(); 111 | args.putInt(ARG_PAGE, page); 112 | PageFragment fragment = new PageFragment(); 113 | fragment.setArguments(args); 114 | return fragment; 115 | } 116 | 117 | @Override 118 | public void onCreate(Bundle savedInstanceState) { 119 | super.onCreate(savedInstanceState); 120 | mPage = getArguments().getInt(ARG_PAGE); 121 | } 122 | 123 | @Override 124 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 125 | Bundle savedInstanceState) { 126 | View view = inflater.inflate(R.layout.fragment_page, container, false); 127 | TextView textView = (TextView) view; 128 | textView.setText("Fragment #" + mPage); 129 | return view; 130 | } 131 | } 132 | 133 | } -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/example/CardViewActivity.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.example; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.Toolbar; 7 | import android.view.View; 8 | 9 | import com.aswifter.material.R; 10 | 11 | /** 12 | * Created by Chenyc on 15/6/28. 13 | */ 14 | public class CardViewActivity extends AppCompatActivity { 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.activity_cardview); 20 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 21 | toolbar.setTitle(R.string.title_book); 22 | setSupportActionBar(toolbar); 23 | getSupportActionBar().setHomeButtonEnabled(true); 24 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 25 | toolbar.setNavigationOnClickListener(new View.OnClickListener() { 26 | @Override 27 | public void onClick(View view) { 28 | onBackPressed(); 29 | } 30 | }); 31 | } 32 | 33 | 34 | public void goDetail(View view){ 35 | Intent intent = new Intent(this,AppBarDetailActivity.class); 36 | startActivity(intent); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/example/DetailFragment.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.example; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.app.Fragment; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.TextView; 10 | 11 | import com.aswifter.material.R; 12 | 13 | /** 14 | * Created by Chenyc on 2015/6/29. 15 | */ 16 | public class DetailFragment extends Fragment { 17 | 18 | public static DetailFragment newInstance(String info) { 19 | Bundle args = new Bundle(); 20 | DetailFragment fragment = new DetailFragment(); 21 | args.putString("info", info); 22 | fragment.setArguments(args); 23 | return fragment; 24 | } 25 | 26 | 27 | @Nullable 28 | @Override 29 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 30 | View view = inflater.inflate(R.layout.fragment_detail, null); 31 | TextView tvInfo = (TextView) view.findViewById(R.id.tvInfo); 32 | tvInfo.setText(getArguments().getString("info")); 33 | // tvInfo.setOnClickListener(new View.OnClickListener() { 34 | // @Override 35 | // public void onClick(View v) { 36 | // Snackbar.make(v,"hello",Snackbar.LENGTH_SHORT).show(); 37 | // } 38 | // }); 39 | return view; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/example/EditTextFLActivity.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.example; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.support.v7.widget.Toolbar; 6 | import android.view.View; 7 | 8 | import com.aswifter.material.R; 9 | 10 | /** 11 | * Created by chenyc on 2015/6/25. 12 | */ 13 | public class EditTextFLActivity extends AppCompatActivity { 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | setContentView(R.layout.activity_edittext_fl); 19 | 20 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 21 | toolbar.setTitle(R.string.title_login); 22 | setSupportActionBar(toolbar); 23 | getSupportActionBar().setHomeButtonEnabled(true); 24 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 25 | toolbar.setNavigationOnClickListener(new View.OnClickListener() { 26 | @Override 27 | public void onClick(View view) { 28 | onBackPressed(); 29 | } 30 | }); 31 | 32 | findViewById(R.id.btnLogin).setOnClickListener(new View.OnClickListener() { 33 | @Override 34 | public void onClick(View v) { 35 | finish(); 36 | } 37 | }); 38 | // TextInputLayout textInputEmail =(TextInputLayout)findViewById(R.id.textInputEmail); 39 | // textInputEmail.setErrorEnabled(true); 40 | // textInputEmail.setError("Error Message"); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/news/ConverterName.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.news; 2 | 3 | /** 4 | * Created by erfli on 6/18/16. 5 | */ 6 | 7 | import java.lang.annotation.Documented; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.Target; 10 | 11 | import static java.lang.annotation.ElementType.METHOD; 12 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 13 | @Documented 14 | @Target(METHOD) 15 | @Retention(RUNTIME) 16 | public @interface ConverterName 17 | { 18 | String value() default "gson"; 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/news/NewsAdapter.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.news; 2 | 3 | import android.content.Context; 4 | import android.support.design.widget.Snackbar; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.util.TypedValue; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.ImageView; 11 | import android.widget.TextView; 12 | 13 | import com.aswifter.material.R; 14 | import com.bumptech.glide.Glide; 15 | 16 | import java.util.List; 17 | import java.util.Objects; 18 | 19 | /** 20 | * Created by erfli on 6/15/16. 21 | */ 22 | public class NewsAdapter extends RecyclerView.Adapter { 23 | private Context context; 24 | private final int mBackground; 25 | private List mDataset; 26 | 27 | private final TypedValue mTypedValue = new TypedValue(); 28 | 29 | // Provide a reference to the views for each data item 30 | // Complex data items may need more than one view per item, and 31 | // you provide access to all the views for a data item in a view holder 32 | public class ViewHolder extends RecyclerView.ViewHolder{ 33 | // each data item is just a string in this case 34 | public TextView newsTitleTV; 35 | public ImageView newsIV; 36 | 37 | public ViewHolder(View v) { 38 | super(v); 39 | newsTitleTV = (TextView) v.findViewById(R.id.news_title); 40 | newsIV = (ImageView) v.findViewById(R.id.news_image); 41 | } 42 | } 43 | 44 | // Provide a suitable constructor (depends on the kind of dataset) 45 | public NewsAdapter(Context context, List myDataset) { 46 | this.mDataset = myDataset; 47 | context.getTheme().resolveAttribute(R.attr.selectableItemBackground, mTypedValue, true); 48 | mBackground = mTypedValue.resourceId; 49 | } 50 | 51 | @Override 52 | public ViewHolder onCreateViewHolder(ViewGroup parent, 53 | int viewType) { 54 | // create a new view 55 | View v = LayoutInflater.from(parent.getContext()) 56 | .inflate(R.layout.item_view, parent, false); 57 | // v.setBackgroundResource(mBackground); 58 | // set the view's size, margins, paddings and layout parameters 59 | ViewHolder vh = new ViewHolder(v); 60 | return vh; 61 | } 62 | 63 | // Replace the contents of a view (invoked by the layout manager) 64 | @Override 65 | public void onBindViewHolder(ViewHolder holder, int position) { 66 | // - get element from your dataset at this position 67 | // - replace the contents of the view with that element 68 | Story story = mDataset.get(position); 69 | holder.newsTitleTV.setText(mDataset.get(position).getTitle()); 70 | Glide.clear(holder.newsIV); 71 | Glide.with(holder.newsIV.getContext()) 72 | .load(story.getImages().get(0)) 73 | .fitCenter() 74 | .into(holder.newsIV); 75 | } 76 | 77 | // Return the size of your dataset (invoked by the layout manager) 78 | @Override 79 | public int getItemCount() { 80 | return mDataset.size(); 81 | } 82 | 83 | public Story getItem(int position){ 84 | return mDataset.get(position); 85 | } 86 | 87 | public void updateData(List stories) { 88 | mDataset.clear(); 89 | mDataset.addAll(stories); 90 | notifyDataSetChanged(); 91 | } 92 | 93 | public void addData(List stories){ 94 | mDataset.addAll(stories); 95 | notifyDataSetChanged(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/news/NewsDetailActivity.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.news; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.NonNull; 5 | import android.support.design.widget.CollapsingToolbarLayout; 6 | import android.support.v7.widget.Toolbar; 7 | import android.util.Log; 8 | import android.view.View; 9 | import android.webkit.WebSettings; 10 | import android.webkit.WebView; 11 | import android.widget.ImageView; 12 | import android.widget.Toast; 13 | 14 | import com.aswifter.material.R; 15 | import com.aswifter.material.common.AppClient; 16 | import com.aswifter.material.common.BaseActivity; 17 | import com.aswifter.material.common.ThreadPool; 18 | import com.bumptech.glide.Glide; 19 | import com.google.android.agera.Receiver; 20 | import com.google.android.agera.Repositories; 21 | import com.google.android.agera.Repository; 22 | import com.google.android.agera.Result; 23 | import com.google.android.agera.Supplier; 24 | import com.google.android.agera.Updatable; 25 | 26 | import java.io.IOException; 27 | 28 | public class NewsDetailActivity extends BaseActivity implements Updatable { 29 | private WebView webView; 30 | private ImageView titleImageView; 31 | private CollapsingToolbarLayout collapsingToolbarLayout; 32 | private Toolbar toolbar; 33 | private Repository> repository; 34 | private NewsDetailSupplier newsDetailSupplier; 35 | private Story story; 36 | public static final String NEWS = "news_key"; 37 | 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | setContentView(R.layout.activity_news_detail); 42 | initView(); 43 | initRepository(); 44 | } 45 | 46 | @Override 47 | protected void initView() { 48 | collapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar); 49 | toolbar = (Toolbar) findViewById(R.id.toolbar); 50 | setSupportActionBar(toolbar); 51 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 52 | toolbar.setNavigationOnClickListener(new View.OnClickListener() { 53 | @Override 54 | public void onClick(View view) { 55 | onBackPressed(); 56 | } 57 | }); 58 | titleImageView = (ImageView) findViewById(R.id.ivImage); 59 | webView = (WebView) findViewById(R.id.webView); 60 | WebSettings webSettings = webView.getSettings(); 61 | webSettings.setJavaScriptEnabled(true); 62 | webSettings.setDomStorageEnabled(true); 63 | story = getIntent().getParcelableExtra(NEWS); 64 | collapsingToolbarLayout.setTitle(story.getTitle()); 65 | } 66 | 67 | @Override 68 | protected void initRepository() { 69 | newsDetailSupplier = new NewsDetailSupplier(); 70 | repository = Repositories.repositoryWithInitialValue(Result.absent()) 71 | .observe() 72 | .onUpdatesPerLoop() 73 | .goTo(ThreadPool.executor) 74 | .thenGetFrom(newsDetailSupplier) 75 | .compile(); 76 | } 77 | 78 | @Override 79 | protected void onResume() { 80 | super.onResume(); 81 | newsDetailSupplier.setKey(String.valueOf(story.getId())); 82 | repository.addUpdatable(this); 83 | } 84 | 85 | @Override 86 | protected void onStop() { 87 | super.onStop(); 88 | repository.removeUpdatable(this); 89 | } 90 | 91 | @Override 92 | public void update() { 93 | if (repository.get().isPresent()) { 94 | repository.get().ifFailedSendTo(new Receiver() { 95 | @Override 96 | public void accept(@NonNull Throwable value) { 97 | Toast.makeText(NewsDetailActivity.this, "加载失败", Toast.LENGTH_SHORT).show(); 98 | } 99 | }).ifSucceededSendTo(new Receiver() { 100 | @Override 101 | public void accept(@NonNull final NewsDetailResponse value) { 102 | collapsingToolbarLayout.setTitle(value.getTitle()); 103 | Glide.with(NewsDetailActivity.this) 104 | .load(value.getImage()) 105 | .asBitmap() 106 | .into(titleImageView); 107 | if (value.getCss() != null && value.getCss().size() > 0) { 108 | final Repository> resultRepository = Repositories.repositoryWithInitialValue(Result.absent()) 109 | .observe() 110 | .onUpdatesPerLoop() 111 | .goTo(ThreadPool.executor) 112 | .thenGetFrom(new Supplier>() { 113 | @NonNull 114 | @Override 115 | public Result get() { 116 | String result = null; 117 | try { 118 | String url = value.getCss().get(0); 119 | url = url.substring(url.lastIndexOf('=') + 1); 120 | result = AppClient.httpService.getCSS(url).execute().body(); 121 | } catch (Exception e) { 122 | Log.d("NewsDetail", "WebViewError" + e); 123 | } 124 | if (result == null) { 125 | return Result.failure(); 126 | } else { 127 | return Result.success(result); 128 | } 129 | } 130 | }) 131 | .compile(); 132 | resultRepository.addUpdatable(new Updatable() { 133 | @Override 134 | public void update() { 135 | String css = ""; 136 | String html = "" + css + "" + value.getBody() + ""; 137 | html = html.replace("
", ""); 138 | webView.loadDataWithBaseURL("file:///android_asset/", html, "text/html", "UTF-8", null); 139 | resultRepository.removeUpdatable(this); 140 | } 141 | }); 142 | } else { 143 | webView.loadData(value.getBody(), "text/html", "utf-8"); 144 | } 145 | } 146 | }); 147 | } 148 | } 149 | 150 | class NewsDetailSupplier implements Supplier> { 151 | 152 | String key; 153 | 154 | public void setKey(String key) { 155 | this.key = key; 156 | 157 | } 158 | 159 | @NonNull 160 | @Override 161 | public Result get() { 162 | NewsDetailResponse newsDetailResponse = null; 163 | try { 164 | newsDetailResponse = AppClient.httpService.getNewsDetail(key).execute().body(); 165 | } catch (IOException e) { 166 | e.printStackTrace(); 167 | } 168 | if (newsDetailResponse == null) { 169 | return Result.failure(); 170 | } else { 171 | return Result.success(newsDetailResponse); 172 | } 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/news/NewsDetailResponse.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.news; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by erfli on 6/16/16. 7 | */ 8 | public class NewsDetailResponse { 9 | private String body; 10 | 11 | private String image_source; 12 | 13 | private String title; 14 | 15 | private String image; 16 | 17 | private String share_url; 18 | 19 | private List js ; 20 | 21 | private String ga_prefix; 22 | 23 | private List images ; 24 | 25 | private int type; 26 | 27 | private int id; 28 | 29 | private List css ; 30 | 31 | public void setBody(String body){ 32 | this.body = body; 33 | } 34 | public String getBody(){ 35 | return this.body; 36 | } 37 | public void setImage_source(String image_source){ 38 | this.image_source = image_source; 39 | } 40 | public String getImage_source(){ 41 | return this.image_source; 42 | } 43 | public void setTitle(String title){ 44 | this.title = title; 45 | } 46 | public String getTitle(){ 47 | return this.title; 48 | } 49 | public void setImage(String image){ 50 | this.image = image; 51 | } 52 | public String getImage(){ 53 | return this.image; 54 | } 55 | public void setShare_url(String share_url){ 56 | this.share_url = share_url; 57 | } 58 | public String getShare_url(){ 59 | return this.share_url; 60 | } 61 | public void setJs(List js){ 62 | this.js = js; 63 | } 64 | public List getJs(){ 65 | return this.js; 66 | } 67 | public void setGa_prefix(String ga_prefix){ 68 | this.ga_prefix = ga_prefix; 69 | } 70 | public String getGa_prefix(){ 71 | return this.ga_prefix; 72 | } 73 | public void setImages(List images){ 74 | this.images = images; 75 | } 76 | public List getImages(){ 77 | return this.images; 78 | } 79 | public void setType(int type){ 80 | this.type = type; 81 | } 82 | public int getType(){ 83 | return this.type; 84 | } 85 | public void setId(int id){ 86 | this.id = id; 87 | } 88 | public int getId(){ 89 | return this.id; 90 | } 91 | public void setCss(List css){ 92 | this.css = css; 93 | } 94 | public List getCss(){ 95 | return this.css; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/news/NewsListActivity.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.news; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.annotation.NonNull; 6 | import android.support.v4.app.ActivityCompat; 7 | import android.support.v4.app.ActivityOptionsCompat; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.support.v7.widget.DefaultItemAnimator; 10 | import android.support.v7.widget.LinearLayoutManager; 11 | import android.support.v7.widget.RecyclerView; 12 | import android.support.v7.widget.Toolbar; 13 | import android.view.View; 14 | 15 | import com.aswifter.material.R; 16 | import com.aswifter.material.common.ThreadPool; 17 | import com.aswifter.material.widget.DividerItemDecoration; 18 | import com.aswifter.material.widget.DividerOffsetDecoration; 19 | import com.aswifter.material.widget.PullToRefreshLayout; 20 | import com.aswifter.material.widget.RecyclerItemClickListener; 21 | import com.aswifter.material.widget.RefreshLayout; 22 | import com.google.android.agera.Receiver; 23 | import com.google.android.agera.Repositories; 24 | import com.google.android.agera.Repository; 25 | import com.google.android.agera.Result; 26 | import com.google.android.agera.Updatable; 27 | 28 | import java.util.ArrayList; 29 | import java.util.Date; 30 | import java.util.List; 31 | 32 | public class NewsListActivity extends AppCompatActivity implements Updatable { 33 | 34 | Repository>> repository; 35 | NewsObservable newsObservable; 36 | Receiver> receiver; 37 | Receiver throwableReceiver; 38 | private RecyclerView mRecyclerView; 39 | private LinearLayoutManager mLayoutManager; 40 | private List myDataset; 41 | private NewsAdapter mAdapter; 42 | RefreshLayout refreshLayout; 43 | 44 | private int page = 0; 45 | 46 | @Override 47 | protected void onCreate(Bundle savedInstanceState) { 48 | super.onCreate(savedInstanceState); 49 | setContentView(R.layout.activity_recycler_view); 50 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 51 | toolbar.setTitle(R.string.latest_news); 52 | setSupportActionBar(toolbar); 53 | getSupportActionBar().setHomeButtonEnabled(true); 54 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 55 | toolbar.setNavigationOnClickListener(new View.OnClickListener() { 56 | @Override 57 | public void onClick(View view) { 58 | onBackPressed(); 59 | } 60 | }); 61 | initRefreshView(); 62 | mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView); 63 | // use a linear layout manager 64 | mLayoutManager = new LinearLayoutManager(this); 65 | mRecyclerView.setLayoutManager(mLayoutManager); 66 | // mRecyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST).setmDivider(getResources().getDrawable(R.drawable.md_transparent))); 67 | mRecyclerView.addItemDecoration(new DividerOffsetDecoration()); 68 | mRecyclerView.setItemAnimator(new DefaultItemAnimator()); 69 | initData(); 70 | } 71 | 72 | private void initRefreshView(){ 73 | refreshLayout = (RefreshLayout) findViewById(R.id.refresh_layout); 74 | refreshLayout.setColorSchemeResources(R.color.google_blue, 75 | R.color.google_green, 76 | R.color.google_red, 77 | R.color.google_yellow); 78 | refreshLayout.setOnRefreshListener(new PullToRefreshLayout.OnRefreshListener() { 79 | @Override 80 | public void onRefresh() { 81 | getLatestData(); 82 | } 83 | }); 84 | refreshLayout.setOnLoadListener(new RefreshLayout.OnLoadListener() { 85 | @Override 86 | public void onLoad() { 87 | getHistoryData(); 88 | } 89 | }); 90 | } 91 | 92 | private void initData(){ 93 | NewsSupplier newsSupplier = new NewsSupplier(); 94 | newsObservable = new NewsObservable(newsSupplier); 95 | repository = Repositories.repositoryWithInitialValue(Result.>absent()) 96 | .observe(newsObservable) 97 | .onUpdatesPerLoop() 98 | .goTo(ThreadPool.executor) 99 | .thenGetFrom(newsSupplier) 100 | .compile(); 101 | 102 | receiver = new Receiver>() { 103 | 104 | @Override 105 | public void accept(@NonNull List value) { 106 | if(page > 1){ 107 | mAdapter.addData(value); 108 | }else{ 109 | mAdapter.updateData(value); 110 | } 111 | } 112 | }; 113 | 114 | throwableReceiver = new Receiver() { 115 | @Override 116 | public void accept(@NonNull Throwable value) { 117 | 118 | } 119 | }; 120 | myDataset = new ArrayList<>(); 121 | mAdapter = new NewsAdapter(this, myDataset); 122 | mRecyclerView.setAdapter(mAdapter); 123 | mRecyclerView.addOnItemTouchListener(new RecyclerItemClickListener(this, new RecyclerItemClickListener.OnItemClickListener() { 124 | @Override 125 | public void onItemClick(View view, int position) { 126 | Intent intent = new Intent(NewsListActivity.this,NewsDetailActivity.class); 127 | ActivityOptionsCompat options = 128 | ActivityOptionsCompat.makeSceneTransitionAnimation(NewsListActivity.this, 129 | view.findViewById(R.id.news_image), getString(R.string.transition_news_img)); 130 | intent.putExtra(NewsDetailActivity.NEWS, mAdapter.getItem(position)); 131 | ActivityCompat.startActivity(NewsListActivity.this,intent, options.toBundle()); 132 | } 133 | })); 134 | getLatestData(); 135 | } 136 | 137 | @Override 138 | public void onResume() { 139 | super.onResume(); 140 | repository.addUpdatable(this); 141 | } 142 | 143 | 144 | @Override 145 | public void onPause() { 146 | super.onPause(); 147 | repository.removeUpdatable(this); 148 | } 149 | 150 | private void getLatestData() { 151 | newsObservable.refreshNews(""); 152 | page = 0; 153 | } 154 | 155 | private void getHistoryData(){ 156 | String time = getDateString(new Date()); 157 | String key = String.valueOf(Long.valueOf(time) - page); 158 | newsObservable.refreshNews(key); 159 | page+=1; 160 | } 161 | 162 | @Override 163 | public void update() { 164 | refreshLayout.setRefreshing(false); 165 | repository.get().ifFailedSendTo(throwableReceiver).ifSucceededSendTo(receiver); 166 | } 167 | 168 | public static String getDateString(Date date){ 169 | String year =(date.getYear()+1900)+""; 170 | String mm = (date.getMonth()+1)+""; 171 | if(Integer.valueOf(mm).intValue()<10){ 172 | mm="0"+mm; 173 | } 174 | String day = date.getDate()+""; 175 | if(Integer.valueOf(day).intValue()<10)day="0"+day; 176 | return year+mm+day; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/news/NewsObservable.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.news; 2 | 3 | import com.google.android.agera.BaseObservable; 4 | 5 | /** 6 | * Created by erfli on 6/15/16. 7 | */ 8 | public class NewsObservable extends BaseObservable{ 9 | 10 | NewsSupplier supplier; 11 | public NewsObservable(NewsSupplier supplier){ 12 | this.supplier = supplier; 13 | } 14 | public void refreshNews(String key){ 15 | supplier.setNewsKey(key); 16 | dispatchUpdate(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/news/NewsResponse.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.news; 2 | 3 | import java.util.List; 4 | 5 | 6 | public class NewsResponse{ 7 | private List stories; 8 | private String date; 9 | public NewsResponse(){ 10 | 11 | } 12 | 13 | public void setStories(List stories) { 14 | this.stories = stories; 15 | } 16 | 17 | public List getStories() { 18 | return stories; 19 | } 20 | 21 | public void setDate(String date) { 22 | this.date = date; 23 | } 24 | 25 | public String getDate() { 26 | return date; 27 | } 28 | 29 | 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/news/NewsSupplier.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.news; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.text.TextUtils; 5 | 6 | import com.aswifter.material.common.AppClient; 7 | import com.google.android.agera.Result; 8 | import com.google.android.agera.Supplier; 9 | 10 | import java.io.IOException; 11 | import java.util.List; 12 | 13 | /** 14 | * Created by erfli on 6/15/16. 15 | */ 16 | public class NewsSupplier implements Supplier>> { 17 | 18 | private String newsKey = "latest"; 19 | @NonNull 20 | @Override 21 | public Result> get() { 22 | List stories = getStoryList(); 23 | if(stories == null){ 24 | return Result.failure(); 25 | }else{ 26 | return Result.success(stories); 27 | } 28 | } 29 | 30 | private ListgetStoryList(){ 31 | try { 32 | if(TextUtils.isEmpty(newsKey)){ 33 | return AppClient.httpService.getLatestNews().execute().body().getStories(); 34 | }else 35 | return AppClient.httpService.getHistoryNews(newsKey).execute().body().getStories(); 36 | } catch (IOException e) { 37 | e.printStackTrace(); 38 | return null; 39 | } 40 | } 41 | 42 | public void setNewsKey(@NonNull String key){ 43 | if(TextUtils.isEmpty(key)){ 44 | newsKey = ""; 45 | }else { 46 | newsKey = key; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/news/Story.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.news; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import java.util.List; 7 | 8 | 9 | public class Story implements Parcelable{ 10 | 11 | private static final String FIELD_TYPE = "type"; 12 | private static final String FIELD_ID = "id"; 13 | private static final String FIELD_GA_PREFIX = "ga_prefix"; 14 | private static final String FIELD_IMAGES = "images"; 15 | private static final String FIELD_TITLE = "title"; 16 | 17 | private int type; 18 | private long id; 19 | private int gaPrefix; 20 | private List images; 21 | private String title; 22 | 23 | protected Story(Parcel in) { 24 | type = in.readInt(); 25 | id = in.readLong(); 26 | gaPrefix = in.readInt(); 27 | images = in.createStringArrayList(); 28 | title = in.readString(); 29 | } 30 | 31 | public static final Creator CREATOR = new Creator() { 32 | @Override 33 | public Story createFromParcel(Parcel in) { 34 | return new Story(in); 35 | } 36 | 37 | @Override 38 | public Story[] newArray(int size) { 39 | return new Story[size]; 40 | } 41 | }; 42 | 43 | public void setType(int type) { 44 | this.type = type; 45 | } 46 | 47 | public int getType() { 48 | return type; 49 | } 50 | 51 | public void setId(long id) { 52 | this.id = id; 53 | } 54 | 55 | public long getId() { 56 | return id; 57 | } 58 | 59 | public void setGaPrefix(int gaPrefix) { 60 | this.gaPrefix = gaPrefix; 61 | } 62 | 63 | public int getGaPrefix() { 64 | return gaPrefix; 65 | } 66 | 67 | public void setImages(List images) { 68 | this.images = images; 69 | } 70 | 71 | public List getImages() { 72 | return images; 73 | } 74 | 75 | public void setTitle(String title) { 76 | this.title = title; 77 | } 78 | 79 | public String getTitle() { 80 | return title; 81 | } 82 | 83 | @Override 84 | public boolean equals(Object obj){ 85 | if(obj instanceof Story){ 86 | return ((Story) obj).getId() == id; 87 | } 88 | return false; 89 | } 90 | 91 | @Override 92 | public int hashCode(){ 93 | return ((Long) id).hashCode(); 94 | } 95 | 96 | 97 | @Override 98 | public int describeContents() { 99 | return 0; 100 | } 101 | 102 | @Override 103 | public void writeToParcel(Parcel dest, int flags) { 104 | dest.writeInt(type); 105 | dest.writeLong(id); 106 | dest.writeInt(gaPrefix); 107 | dest.writeStringList(images); 108 | dest.writeString(title); 109 | } 110 | } -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/news/TopStory.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.news; 2 | 3 | 4 | 5 | public class TopStory{ 6 | 7 | private static final String FIELD_TYPE = "type"; 8 | private static final String FIELD_ID = "id"; 9 | private static final String FIELD_GA_PREFIX = "ga_prefix"; 10 | private static final String FIELD_TITLE = "title"; 11 | private static final String FIELD_IMAGE = "image"; 12 | 13 | 14 | private int mType; 15 | private long mId; 16 | private int mGaPrefix; 17 | private String mTitle; 18 | private String mImage; 19 | 20 | 21 | public TopStory(){ 22 | 23 | } 24 | 25 | public void setType(int type) { 26 | mType = type; 27 | } 28 | 29 | public int getType() { 30 | return mType; 31 | } 32 | 33 | public void setId(long id) { 34 | mId = id; 35 | } 36 | 37 | public long getId() { 38 | return mId; 39 | } 40 | 41 | public void setGaPrefix(int gaPrefix) { 42 | mGaPrefix = gaPrefix; 43 | } 44 | 45 | public int getGaPrefix() { 46 | return mGaPrefix; 47 | } 48 | 49 | public void setTitle(String title) { 50 | mTitle = title; 51 | } 52 | 53 | public String getTitle() { 54 | return mTitle; 55 | } 56 | 57 | public void setImage(String image) { 58 | mImage = image; 59 | } 60 | 61 | public String getImage() { 62 | return mImage; 63 | } 64 | 65 | @Override 66 | public boolean equals(Object obj){ 67 | if(obj instanceof TopStory){ 68 | return ((TopStory) obj).getId() == mId; 69 | } 70 | return false; 71 | } 72 | 73 | @Override 74 | public int hashCode(){ 75 | return ((Long)mId).hashCode(); 76 | } 77 | 78 | 79 | } -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/utils/DisplayUtil.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.utils; 2 | 3 | import android.content.Context; 4 | import android.util.DisplayMetrics; 5 | import android.view.View; 6 | import android.view.WindowManager; 7 | 8 | public class DisplayUtil { 9 | 10 | public static int SCREEN_WIDTH_PIXELS; 11 | public static int SCREEN_HEIGHT_PIXELS; 12 | public static float SCREEN_DENSITY; 13 | public static int SCREEN_WIDTH_DP; 14 | public static int SCREEN_HEIGHT_DP; 15 | 16 | public static void init(Context context) { 17 | if (context == null) { 18 | return; 19 | } 20 | DisplayMetrics dm = new DisplayMetrics(); 21 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 22 | wm.getDefaultDisplay().getMetrics(dm); 23 | SCREEN_WIDTH_PIXELS = dm.widthPixels; 24 | SCREEN_HEIGHT_PIXELS = dm.heightPixels; 25 | SCREEN_DENSITY = dm.density; 26 | SCREEN_WIDTH_DP = (int) (SCREEN_WIDTH_PIXELS / dm.density); 27 | SCREEN_HEIGHT_DP = (int) (SCREEN_HEIGHT_PIXELS / dm.density); 28 | } 29 | 30 | public static int dp2px(float dp) { 31 | final float scale = SCREEN_DENSITY; 32 | return (int) (dp * scale + 0.5f); 33 | } 34 | 35 | public static int designedDP2px(float designedDp) { 36 | if (SCREEN_WIDTH_DP != 320) { 37 | designedDp = designedDp * SCREEN_WIDTH_DP / 320f; 38 | } 39 | return dp2px(designedDp); 40 | } 41 | 42 | public static void setPadding(final View view, float left, float top, float right, float bottom) { 43 | view.setPadding(designedDP2px(left), dp2px(top), designedDP2px(right), dp2px(bottom)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/widget/BackHandledFragment.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.widget; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.Fragment; 5 | 6 | public abstract class BackHandledFragment extends Fragment { 7 | protected BackHandlerInterface backHandlerInterface; 8 | 9 | public abstract String getTagText(); 10 | 11 | public abstract boolean onBackPressed(); 12 | 13 | @Override 14 | public void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | if (!(getActivity() instanceof BackHandlerInterface)) { 17 | throw new ClassCastException("Hosting activity must implement BackHandlerInterface"); 18 | } else { 19 | backHandlerInterface = (BackHandlerInterface) getActivity(); 20 | } 21 | } 22 | 23 | @Override 24 | public void onStart() { 25 | super.onStart(); 26 | 27 | // Mark this fragment as the selected Fragment. 28 | backHandlerInterface.setSelectedFragment(this); 29 | } 30 | 31 | public interface BackHandlerInterface { 32 | public void setSelectedFragment(BackHandledFragment backHandledFragment); 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/widget/CircleImageView.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.RadialGradient; 8 | import android.graphics.Shader; 9 | import android.graphics.drawable.ShapeDrawable; 10 | import android.graphics.drawable.shapes.OvalShape; 11 | import android.support.v4.view.ViewCompat; 12 | import android.view.animation.Animation; 13 | import android.widget.ImageView; 14 | 15 | /** 16 | * Private class created to work around issues with AnimationListeners being 17 | * called before the animation is actually complete and support shadows on older 18 | * platforms. 19 | * 20 | * @hide 21 | */ 22 | class CircleImageView extends ImageView { 23 | 24 | private static final int KEY_SHADOW_COLOR = 0x1E000000; 25 | private static final int FILL_SHADOW_COLOR = 0x3D000000; 26 | // PX 27 | private static final float X_OFFSET = 0f; 28 | private static final float Y_OFFSET = 1.75f; 29 | private static final float SHADOW_RADIUS = 3.5f; 30 | private static final int SHADOW_ELEVATION = 4; 31 | 32 | private Animation.AnimationListener mListener; 33 | private int mShadowRadius; 34 | 35 | public CircleImageView(Context context, int color, final float radius) { 36 | super(context); 37 | final float density = getContext().getResources().getDisplayMetrics().density; 38 | final int diameter = (int) (radius * density * 2); 39 | final int shadowYOffset = (int) (density * Y_OFFSET); 40 | final int shadowXOffset = (int) (density * X_OFFSET); 41 | 42 | mShadowRadius = (int) (density * SHADOW_RADIUS); 43 | 44 | ShapeDrawable circle; 45 | if (elevationSupported()) { 46 | circle = new ShapeDrawable(new OvalShape()); 47 | ViewCompat.setElevation(this, SHADOW_ELEVATION * density); 48 | } else { 49 | OvalShape oval = new OvalShadow(mShadowRadius, diameter); 50 | circle = new ShapeDrawable(oval); 51 | ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, circle.getPaint()); 52 | circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset, 53 | KEY_SHADOW_COLOR); 54 | final int padding = mShadowRadius; 55 | // set padding so the inner image sits correctly within the shadow. 56 | setPadding(padding, padding, padding, padding); 57 | } 58 | circle.getPaint().setColor(color); 59 | setBackgroundDrawable(circle); 60 | } 61 | 62 | private boolean elevationSupported() { 63 | return android.os.Build.VERSION.SDK_INT >= 21; 64 | } 65 | 66 | @Override 67 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 68 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 69 | if (!elevationSupported()) { 70 | setMeasuredDimension(getMeasuredWidth() + mShadowRadius*2, getMeasuredHeight() 71 | + mShadowRadius*2); 72 | } 73 | } 74 | 75 | public void setAnimationListener(Animation.AnimationListener listener) { 76 | mListener = listener; 77 | } 78 | 79 | @Override 80 | public void onAnimationStart() { 81 | super.onAnimationStart(); 82 | if (mListener != null) { 83 | mListener.onAnimationStart(getAnimation()); 84 | } 85 | } 86 | 87 | @Override 88 | public void onAnimationEnd() { 89 | super.onAnimationEnd(); 90 | if (mListener != null) { 91 | mListener.onAnimationEnd(getAnimation()); 92 | } 93 | } 94 | 95 | /** 96 | * Update the background color of the circle image view. 97 | * 98 | * @param colorRes Id of a color resource. 99 | */ 100 | public void setBackgroundColorRes(int colorRes) { 101 | setBackgroundColor(getContext().getResources().getColor(colorRes)); 102 | } 103 | 104 | @Override 105 | public void setBackgroundColor(int color) { 106 | if (getBackground() instanceof ShapeDrawable) { 107 | ((ShapeDrawable) getBackground()).getPaint().setColor(color); 108 | } 109 | } 110 | 111 | private class OvalShadow extends OvalShape { 112 | private RadialGradient mRadialGradient; 113 | private Paint mShadowPaint; 114 | private int mCircleDiameter; 115 | 116 | public OvalShadow(int shadowRadius, int circleDiameter) { 117 | super(); 118 | mShadowPaint = new Paint(); 119 | mShadowRadius = shadowRadius; 120 | mCircleDiameter = circleDiameter; 121 | mRadialGradient = new RadialGradient(mCircleDiameter / 2, mCircleDiameter / 2, 122 | mShadowRadius, new int[] { 123 | FILL_SHADOW_COLOR, Color.TRANSPARENT 124 | }, null, Shader.TileMode.CLAMP); 125 | mShadowPaint.setShader(mRadialGradient); 126 | } 127 | 128 | @Override 129 | public void draw(Canvas canvas, Paint paint) { 130 | final int viewWidth = CircleImageView.this.getWidth(); 131 | final int viewHeight = CircleImageView.this.getHeight(); 132 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2 + mShadowRadius), 133 | mShadowPaint); 134 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2), paint); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/widget/DividerItemDecoration.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Rect; 7 | import android.graphics.drawable.Drawable; 8 | import android.support.v7.widget.LinearLayoutManager; 9 | import android.support.v7.widget.RecyclerView; 10 | import android.view.View; 11 | 12 | /* 13 | * Copyright (C) 2014 The Android Open Source Project 14 | * 15 | * Licensed under the Apache License, Version 2.0 (the "License"); 16 | * you may not use this file except in compliance with the License. 17 | * You may obtain a copy of the License at 18 | * 19 | * http://www.apache.org/licenses/LICENSE-2.0 20 | * 21 | * Unless required by applicable law or agreed to in writing, software 22 | * distributed under the License is distributed on an " IS" BASIS, 23 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | * See the License for the specific language governing permissions and 25 | * limitations under the License. 26 | */ 27 | public class DividerItemDecoration extends RecyclerView.ItemDecoration { 28 | 29 | private static final int[] ATTRS = new int[]{ 30 | android.R.attr.listDivider 31 | }; 32 | 33 | public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; 34 | 35 | public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; 36 | 37 | private Drawable mDivider; 38 | 39 | private int mOrientation; 40 | 41 | public DividerItemDecoration(Context context, int orientation) { 42 | final TypedArray a = context.obtainStyledAttributes(ATTRS); 43 | mDivider = a.getDrawable(0); 44 | a.recycle(); 45 | setOrientation(orientation); 46 | } 47 | 48 | public void setOrientation(int orientation) { 49 | if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) { 50 | throw new IllegalArgumentException("invalid orientation"); 51 | } 52 | mOrientation = orientation; 53 | } 54 | 55 | public DividerItemDecoration setmDivider(Drawable mDivider) { 56 | this.mDivider = mDivider; 57 | return this; 58 | } 59 | 60 | @Override 61 | public void onDraw(Canvas c, RecyclerView parent) { 62 | if (mOrientation == VERTICAL_LIST) { 63 | drawVertical(c, parent); 64 | } else { 65 | drawHorizontal(c, parent); 66 | } 67 | } 68 | 69 | public void drawVertical(Canvas c, RecyclerView parent) { 70 | final int left = parent.getPaddingLeft(); 71 | final int right = parent.getWidth() - parent.getPaddingRight(); 72 | 73 | final int childCount = parent.getChildCount(); 74 | for (int i = 0; i < childCount; i++) { 75 | final View child = parent.getChildAt(i); 76 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child 77 | .getLayoutParams(); 78 | final int top = child.getBottom() + params.bottomMargin; 79 | final int bottom = top + mDivider.getIntrinsicHeight(); 80 | mDivider.setBounds(left, top, right, bottom); 81 | mDivider.draw(c); 82 | } 83 | } 84 | 85 | public void drawHorizontal(Canvas c, RecyclerView parent) { 86 | final int top = parent.getPaddingTop(); 87 | final int bottom = parent.getHeight() - parent.getPaddingBottom(); 88 | 89 | final int childCount = parent.getChildCount(); 90 | for (int i = 0; i < childCount; i++) { 91 | final View child = parent.getChildAt(i); 92 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child 93 | .getLayoutParams(); 94 | final int left = child.getRight() + params.rightMargin; 95 | final int right = left + mDivider.getIntrinsicHeight(); 96 | mDivider.setBounds(left, top, right, bottom); 97 | mDivider.draw(c); 98 | } 99 | } 100 | 101 | @Override 102 | public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { 103 | if (mOrientation == VERTICAL_LIST) { 104 | outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); 105 | } else { 106 | outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/widget/DividerOffsetDecoration.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Rect; 7 | import android.graphics.drawable.Drawable; 8 | import android.support.v7.widget.LinearLayoutManager; 9 | import android.support.v7.widget.RecyclerView; 10 | import android.view.View; 11 | 12 | /* 13 | * Copyright (C) 2014 The Android Open Source Project 14 | * 15 | * Licensed under the Apache License, Version 2.0 (the "License"); 16 | * you may not use this file except in compliance with the License. 17 | * You may obtain a copy of the License at 18 | * 19 | * http://www.apache.org/licenses/LICENSE-2.0 20 | * 21 | * Unless required by applicable law or agreed to in writing, software 22 | * distributed under the License is distributed on an " IS" BASIS, 23 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | * See the License for the specific language governing permissions and 25 | * limitations under the License. 26 | */ 27 | public class DividerOffsetDecoration extends RecyclerView.ItemDecoration { 28 | @Override 29 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 30 | outRect.bottom = 30; 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/widget/ProgressWebView.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.widget; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.webkit.WebChromeClient; 9 | import android.webkit.WebView; 10 | import android.webkit.WebViewClient; 11 | 12 | 13 | public class ProgressWebView extends WebView{ 14 | private WebViewProgressBar progressBar; 15 | private Handler handler; 16 | private WebView _this; 17 | public ProgressWebView(Context context, AttributeSet attrs) { 18 | super(context, attrs); 19 | progressBar = new WebViewProgressBar(context); 20 | progressBar.setLayoutParams(new ViewGroup.LayoutParams 21 | (ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 22 | progressBar.setVisibility(GONE); 23 | addView(progressBar); 24 | handler = new Handler(); 25 | _this = this; 26 | setWebChromeClient(new MyWebChromeClient()); 27 | setWebViewClient(new MyWebClient()); 28 | 29 | } 30 | 31 | private class MyWebChromeClient extends WebChromeClient { 32 | @Override 33 | public void onProgressChanged(WebView view, int newProgress) { 34 | if(newProgress == 100){ 35 | progressBar.setProgress(100); 36 | handler.postDelayed(runnable,200); 37 | }else if(progressBar.getVisibility() == GONE){ 38 | progressBar.setVisibility(VISIBLE); 39 | } 40 | if(newProgress < 5){ 41 | newProgress = 5; 42 | } 43 | progressBar.setProgress(newProgress); 44 | super.onProgressChanged(view, newProgress); 45 | } 46 | } 47 | private class MyWebClient extends WebViewClient { 48 | @Override 49 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 50 | _this.loadUrl(url); 51 | return true; 52 | } 53 | } 54 | private Runnable runnable = new Runnable() { 55 | @Override 56 | public void run() { 57 | progressBar.setVisibility(View.GONE); 58 | } 59 | }; 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/widget/RecyclerItemClickListener.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.widget; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.GestureDetector; 6 | import android.view.MotionEvent; 7 | import android.view.View; 8 | 9 | 10 | public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener { 11 | private OnItemClickListener mListener; 12 | 13 | public interface OnItemClickListener { 14 | void onItemClick(View view, int position); 15 | } 16 | 17 | GestureDetector mGestureDetector; 18 | 19 | public RecyclerItemClickListener(Context context, OnItemClickListener listener) { 20 | mListener = listener; 21 | mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { 22 | @Override public boolean onSingleTapUp(MotionEvent e) { 23 | return true; 24 | } 25 | }); 26 | } 27 | 28 | @Override public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) { 29 | View childView = view.findChildViewUnder(e.getX(), e.getY()); 30 | if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) { 31 | mListener.onItemClick(childView, view.getChildAdapterPosition(childView)); 32 | } 33 | return false; 34 | } 35 | 36 | @Override public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) { } 37 | 38 | @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { 39 | // do nothing 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/widget/RefreshLayout.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.widget; 2 | 3 | import android.content.Context; 4 | import android.support.v4.view.MotionEventCompat; 5 | import android.support.v4.view.ViewCompat; 6 | import android.util.AttributeSet; 7 | import android.view.MotionEvent; 8 | import android.view.ViewConfiguration; 9 | import android.view.animation.Animation; 10 | 11 | import com.aswifter.material.utils.DisplayUtil; 12 | 13 | public class RefreshLayout extends PullToRefreshLayout { 14 | 15 | private final int mTouchSlop; 16 | private OnLoadListener mOnLoadListener; 17 | private float mInitialDownY; 18 | private boolean mIsBeingDragged; 19 | private boolean isLoading = false; 20 | 21 | public RefreshLayout(Context context) { 22 | this(context, null); 23 | } 24 | 25 | public RefreshLayout(Context context, AttributeSet attrs) { 26 | super(context, attrs); 27 | mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 28 | initListener(); 29 | } 30 | 31 | private void initListener(){ 32 | mRefreshListener = new Animation.AnimationListener() { 33 | @Override 34 | public void onAnimationStart(Animation animation) { 35 | } 36 | 37 | @Override 38 | public void onAnimationRepeat(Animation animation) { 39 | } 40 | 41 | @Override 42 | public void onAnimationEnd(Animation animation) { 43 | if(!isLoading){ 44 | if (isRefreshing()) { 45 | // Make sure the progress view is fully visible 46 | mProgress.setAlpha(MAX_ALPHA); 47 | mProgress.start(); 48 | if (mNotify) { 49 | if (mListener != null) { 50 | mListener.onRefresh(); 51 | } 52 | } 53 | mCurrentTargetOffsetTop = mCircleView.getTop(); 54 | } else { 55 | reset(); 56 | } 57 | }else{ 58 | if(mOnLoadListener != null){ 59 | mOnLoadListener.onLoad(); 60 | } 61 | } 62 | } 63 | }; 64 | } 65 | @Override 66 | public boolean dispatchTouchEvent(MotionEvent ev) { 67 | final int action = MotionEventCompat.getActionMasked(ev); 68 | 69 | if (!isEnabled() || canChildScrollDown()) { 70 | // Fail fast if we're not in a state where a swipe is possible 71 | return super.dispatchTouchEvent(ev); 72 | } 73 | switch (action) { 74 | case MotionEvent.ACTION_DOWN: 75 | mIsBeingDragged = false; 76 | mInitialDownY = ev.getY(); 77 | break; 78 | 79 | case MotionEvent.ACTION_MOVE: { 80 | final float y = ev.getY(); 81 | final float overscrollTop = (mInitialDownY -y) * DRAG_RATE; 82 | if (overscrollTop > mTouchSlop) { 83 | mIsBeingDragged = true; 84 | if(!isLoading){ 85 | isLoading = true; 86 | setProgressViewOffset(true, getBottom() - mCircleView.getHeight(), (int)(getBottom() - mCircleView.getHeight() - mTotalDragDistance)); 87 | } 88 | moveSpinner(overscrollTop); 89 | return true; 90 | } 91 | break; 92 | } 93 | case MotionEventCompat.ACTION_POINTER_UP: 94 | case MotionEvent.ACTION_UP: { 95 | if (mIsBeingDragged) { 96 | final float y = ev.getY(); 97 | final float overscrollTop = (mInitialDownY - y) * DRAG_RATE; 98 | mIsBeingDragged = false; 99 | finishSpinner(overscrollTop); 100 | return true; 101 | } 102 | } 103 | } 104 | 105 | return super.dispatchTouchEvent(ev); 106 | } 107 | 108 | private boolean canChildScrollDown() { 109 | return ViewCompat.canScrollVertically(mTarget, 1); 110 | } 111 | 112 | public void setLoading(boolean loading) { 113 | if (mTarget == null) return; 114 | isLoading = loading; 115 | if (loading) { 116 | if (isRefreshing()) { 117 | super.setRefreshing(true); 118 | } 119 | mOnLoadListener.onLoad(); 120 | } else { 121 | super.setRefreshing(false); 122 | setProgressViewOffset(true,getTop(),(int)(DEFAULT_CIRCLE_TARGET * DisplayUtil.SCREEN_DENSITY)); 123 | mInitialDownY = 0; 124 | } 125 | } 126 | 127 | @Override 128 | public void setRefreshing(boolean refreshing) { 129 | if(isLoading){ 130 | setLoading(refreshing); 131 | }else{ 132 | super.setRefreshing(refreshing); 133 | } 134 | if(!refreshing){ 135 | setProgressViewOffset(true,-mCircleView.getHeight(), DEFAULT_CIRCLE_TARGET); 136 | } 137 | } 138 | 139 | public void setOnLoadListener(OnLoadListener loadListener) { 140 | mOnLoadListener = loadListener; 141 | } 142 | 143 | public interface OnLoadListener { 144 | public void onLoad(); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /app/src/main/java/com/aswifter/material/widget/WebViewProgressBar.java: -------------------------------------------------------------------------------- 1 | package com.aswifter.material.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.util.AttributeSet; 7 | import android.view.View; 8 | 9 | import com.aswifter.material.R; 10 | 11 | 12 | public class WebViewProgressBar extends View { 13 | private int progress = 1; 14 | private final static int HEIGHT = 5; 15 | private Paint paint; 16 | private final static int colors[] = new int[]{}; 17 | public WebViewProgressBar(Context context, AttributeSet attrs) { 18 | super(context, attrs); 19 | } 20 | 21 | public WebViewProgressBar(Context context) { 22 | super(context); 23 | // LinearGradient shader = new LinearGradient( 24 | // 0, 0, 25 | // 100, HEIGHT, 26 | // colors, 27 | // null, 28 | // Shader.TileMode.MIRROR); 29 | paint=new Paint(Paint.DITHER_FLAG); 30 | paint.setStyle(Paint.Style.STROKE); 31 | paint.setStrokeWidth(HEIGHT); 32 | paint.setAntiAlias(true); 33 | paint.setColor(context.getResources().getColor(R.color.primary_light)); 34 | // paint.setShader(shader); 35 | } 36 | public void setProgress(int progress){ 37 | this.progress = progress; 38 | invalidate(); 39 | } 40 | 41 | @Override 42 | protected void onDraw(Canvas canvas) { 43 | canvas.drawRect(0, 0, getWidth() * progress / 100, HEIGHT, paint); 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/res/color/tab_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/book1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenyangcun/MaterialDesignExample/68438aa2ffa76520d9af35ad247e6414c49f9287/app/src/main/res/drawable-xxhdpi/book1.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/book2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenyangcun/MaterialDesignExample/68438aa2ffa76520d9af35ad247e6414c49f9287/app/src/main/res/drawable-xxhdpi/book2.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/book3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenyangcun/MaterialDesignExample/68438aa2ffa76520d9af35ad247e6414c49f9287/app/src/main/res/drawable-xxhdpi/book3.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_favorite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenyangcun/MaterialDesignExample/68438aa2ffa76520d9af35ad247e6414c49f9287/app/src/main/res/drawable-xxhdpi/ic_favorite.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_find_in_page_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenyangcun/MaterialDesignExample/68438aa2ffa76520d9af35ad247e6414c49f9287/app/src/main/res/drawable-xxhdpi/ic_find_in_page_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/news_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_appbar_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 14 | 15 | 24 | 25 | 26 | 35 | 36 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 54 | 55 | 61 | 62 | 66 | 67 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_bottom_tab.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 17 | 18 | 19 | 25 | 26 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_cardview.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 21 | 22 | 31 | 32 | 37 | 38 | 42 | 43 | 48 | 49 | 55 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 79 | 80 | 85 | 86 | 90 | 91 | 96 | 97 | 103 | 104 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 126 | 127 | 132 | 133 | 137 | 138 | 143 | 144 | 150 | 151 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_edittext_fl.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 14 | 15 | 21 | 22 | 23 | 26 | 27 | 33 | 34 | 35 | 36 | 37 |