├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── wu │ │ └── allen │ │ └── zhuanlan │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── wu │ │ │ └── allen │ │ │ └── zhuanlan │ │ │ ├── App.java │ │ │ ├── adapter │ │ │ ├── ArticleAdapter.java │ │ │ ├── ArticleViewHolder.java │ │ │ ├── GirlAdapter.java │ │ │ ├── GirlViewHolder.java │ │ │ ├── ZhuanLanAdapter.java │ │ │ └── ZhuanLanViewHolder.java │ │ │ ├── cache │ │ │ ├── Data.java │ │ │ └── Database.java │ │ │ ├── model │ │ │ ├── Article.java │ │ │ ├── ArticleDetail.java │ │ │ ├── Girl.java │ │ │ ├── Item.java │ │ │ └── ZhuanLan.java │ │ │ ├── net │ │ │ ├── Network.java │ │ │ └── api │ │ │ │ ├── ArticleDetailApi.java │ │ │ │ ├── GankApi.java │ │ │ │ ├── ZhLanArticleApi.java │ │ │ │ └── ZhuanLanApi.java │ │ │ ├── util │ │ │ ├── Dp2PxUtil.java │ │ │ ├── ImgSaveUtil.java │ │ │ ├── ImgShareUtil.java │ │ │ ├── NetWorkUtil.java │ │ │ ├── ToastUtil.java │ │ │ └── TopicData.java │ │ │ ├── view │ │ │ ├── activity │ │ │ │ ├── AboutActivity.java │ │ │ │ ├── ArticleDetailActivity.java │ │ │ │ ├── BaseActivity.java │ │ │ │ ├── GirlActivity.java │ │ │ │ ├── MainActivity.java │ │ │ │ └── ZhlanDetailActivity.java │ │ │ └── fragment │ │ │ │ ├── BaseFragment.java │ │ │ │ ├── GirlFragment.java │ │ │ │ └── ZhihuFragment.java │ │ │ └── widget │ │ │ └── ScrollAwareFABBehavior.java │ └── res │ │ ├── anim │ │ ├── fab_in.xml │ │ ├── fab_out.xml │ │ └── rotate.xml │ │ ├── animator │ │ └── animator_rotation.xml │ │ ├── drawable │ │ ├── about_logo.png │ │ ├── aboutbg.jpg │ │ ├── change.png │ │ ├── error.jpg │ │ ├── ic_arrow_white_24dp.xml │ │ ├── ic_find_replace_black_24dp.jpg │ │ ├── logo.jpg │ │ └── read.png │ │ ├── layout │ │ ├── activity_about.xml │ │ ├── activity_articledetail.xml │ │ ├── activity_main.xml │ │ ├── content_main.xml │ │ ├── error_layout.xml │ │ ├── fragment_girl_layout.xml │ │ ├── fragment_zhuanlan_layout.xml │ │ ├── girl_item.xml │ │ ├── girldetail_layout.xml │ │ ├── load_more_layout.xml │ │ ├── no_more_layout.xml │ │ ├── progress_layout.xml │ │ ├── zhuanlan_item.xml │ │ ├── zhuanlan_layout.xml │ │ └── zhuanlanpost_item.xml │ │ ├── menu │ │ ├── articledetail_menu_.xml │ │ ├── girl_menu.xml │ │ └── menu_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-v19 │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ ├── dimens.xml │ │ └── strings.xml │ │ └── values │ │ ├── array.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── wu │ └── allen │ └── zhuanlan │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle ├── .idea │ ├── .name │ ├── compiler.xml │ ├── copyright │ │ └── profiles_settings.xml │ ├── encodings.xml │ ├── gradle.xml │ ├── libraries │ │ ├── PhotoView_1_2_4.xml │ │ ├── adapter_rxjava_2_0_2.xml │ │ ├── android_android_23.xml │ │ ├── animated_vector_drawable_23_4_0.xml │ │ ├── appcompat_v7_23_4_0.xml │ │ ├── cardview_v7_23_3_0.xml │ │ ├── circleimageview_2_0_0.xml │ │ ├── converter_gson_2_0_2.xml │ │ ├── core_0_8_5_9.xml │ │ ├── design_23_4_0.xml │ │ ├── easyrecyclerview_4_0_2.xml │ │ ├── glide_3_7_0.xml │ │ ├── gson_2_6_1.xml │ │ ├── hamcrest_core_1_3.xml │ │ ├── junit_4_12.xml │ │ ├── library_1_0_5.xml │ │ ├── library_1_1_5.xml │ │ ├── library_2_4_0.xml │ │ ├── logging_interceptor_3_0_1.xml │ │ ├── okhttp_3_2_0.xml │ │ ├── okio_1_6_0.xml │ │ ├── recyclerview_v7_23_4_0.xml │ │ ├── retrofit_2_0_2.xml │ │ ├── rxandroid_1_2_0.xml │ │ ├── rxjava_1_1_5.xml │ │ ├── snackbar_2_11_0.xml │ │ ├── support_annotations_23_4_0.xml │ │ ├── support_v13_23_2_1.xml │ │ ├── support_v4_23_4_0.xml │ │ └── support_vector_drawable_23_4_0.xml │ ├── misc.xml │ ├── modules.xml │ ├── runConfigurations.xml │ ├── vcs.xml │ └── workspace.xml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZhuanLan & Meizi 2 | 3 | ## About 4 | 5 | 近期有空打算重构这个项目,只做知乎专栏的内容。以前的代码写的很烂,大家随便看看就好。 6 | 7 | ## Sample 8 | 9 | [戳这里下载 APK](http://fir.im/ZhuanLan) 10 | 11 | ## Change Log 12 | 13 | * 1.0.0 first publish 14 | * 1.0.1 fix bug & add todo list 15 | * 1.0.2 add profile_url & name in ZhlanDetailActivity & add statement in Aboutactivity & change desc in GirlActivity 16 | * 1.0.5 all changes you can see in the below picture. 17 | * 1.0.6 change something in app structe and navigation bar is trunslate. 18 | 19 | ## ScreenShot 20 | 21 | **截图不是最新版本,最新版本会及时在 Smaple 中发布** 22 | 23 | ![zhuanlan6.png](http://7xrl8j.com1.z0.glb.clouddn.com/zhuanlan3.png) 24 | ![zhuanlan5.png](http://7xrl8j.com1.z0.glb.clouddn.com/zhuanlan5.png) 25 | ![zhuanlan1.png](http://7xrl8j.com1.z0.glb.clouddn.com/zhuanlan4.png) 26 | ![zhuanlan4.png](http://7xrl8j.com1.z0.glb.clouddn.com/zhuanlan1.png) 27 | ![zhuanlan3.png](http://7xrl8j.com1.z0.glb.clouddn.com/zhuanlan2.png) 28 | ![zhuanlan2.png](http://7xrl8j.com1.z0.glb.clouddn.com/zhuanlan6.png) 29 | 30 | 31 | 32 | 33 | ## Thanks 34 | 35 | * [GankGirl](https://github.com/gaolonglong/GankGirl) 36 | 37 | * [知乎专栏 API 分析](https://marktony.github.io/2016/05/14/%E7%9F%A5%E4%B9%8E%E4%B8%93%E6%A0%8FAPI%E5%88%86%E6%9E%90/) 38 | 39 | * [给 Android 开发者的 RxJava 详解以及相关 Demo](https://gank.io/post/560e15be2dca930e00da1083) 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | ## License 48 | MIT 49 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | applicationId "com.wu.allen.zhuanlan" 9 | minSdkVersion 21 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.4.0' 26 | compile 'com.android.support:design:23.4.0' 27 | compile 'com.android.support:support-v4:23.2.1' 28 | compile 'com.android.support:support-v13:23.2.1' 29 | compile 'com.jude:easyrecyclerview:4.0.2' 30 | compile 'com.jude:fitsystemwindowlayout:2.1.5' 31 | compile 'com.android.support:cardview-v7:23.3.0' 32 | compile 'com.nispok:snackbar:2.11.+' 33 | compile 'com.afollestad.material-dialogs:core:0.8.5.9' 34 | 35 | compile 'de.hdodenhof:circleimageview:2.0.0' 36 | compile 'com.github.bumptech.glide:glide:3.7.0' 37 | compile 'com.commit451:PhotoView:1.2.4' 38 | compile 'com.wang.avi:library:1.0.5' 39 | compile 'com.nineoldandroids:library:2.4.0' 40 | 41 | compile 'io.reactivex:rxjava:1.1.5' 42 | compile 'io.reactivex:rxandroid:1.2.0' 43 | compile 'com.squareup.retrofit2:retrofit:2.0.2' 44 | compile 'com.squareup.retrofit2:converter-gson:2.0.2' 45 | compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2' 46 | compile 'com.squareup.okhttp3:logging-interceptor:3.0.1' 47 | compile 'com.squareup.okhttp3:okhttp:3.0.1' 48 | 49 | } -------------------------------------------------------------------------------- /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:\Users\allen\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/wu/allen/zhuanlan/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan; 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 | 8 | 9 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/App.java: -------------------------------------------------------------------------------- 1 | // (c)2016 Flipboard Inc, All Rights Reserved. 2 | 3 | package com.wu.allen.zhuanlan; 4 | 5 | import android.app.Application; 6 | 7 | public class App extends Application { 8 | private static App INSTANCE; 9 | 10 | public static App getInstance() { 11 | return INSTANCE; 12 | } 13 | 14 | @Override 15 | public void onCreate() { 16 | super.onCreate(); 17 | INSTANCE = this; 18 | } 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/adapter/ArticleAdapter.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.adapter; 2 | 3 | import android.content.Context; 4 | import android.view.ViewGroup; 5 | 6 | import com.jude.easyrecyclerview.adapter.BaseViewHolder; 7 | import com.jude.easyrecyclerview.adapter.RecyclerArrayAdapter; 8 | import com.wu.allen.zhuanlan.model.Article; 9 | 10 | /** 11 | * Created by allen on 2016/6/15. 12 | */ 13 | public class ArticleAdapter extends RecyclerArrayAdapter
{ 14 | 15 | public ArticleAdapter(Context context) { 16 | super(context); 17 | } 18 | 19 | @Override 20 | public BaseViewHolder OnCreateViewHolder(ViewGroup parent, int viewType) { 21 | return new ArticleViewHolder(parent); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/adapter/ArticleViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.adapter; 2 | 3 | import android.util.Log; 4 | import android.view.ViewGroup; 5 | import android.widget.ImageView; 6 | import android.widget.TextView; 7 | 8 | import com.bumptech.glide.Glide; 9 | import com.bumptech.glide.load.engine.DiskCacheStrategy; 10 | import com.jude.easyrecyclerview.adapter.BaseViewHolder; 11 | import com.wu.allen.zhuanlan.R; 12 | import com.wu.allen.zhuanlan.model.Article; 13 | 14 | import de.hdodenhof.circleimageview.CircleImageView; 15 | 16 | /** 17 | * Created by allen on 2016/6/15. 18 | */ 19 | public class ArticleViewHolder extends BaseViewHolder
{ 20 | private ImageView imageview; 21 | private CircleImageView circleImageView; 22 | private TextView tvLike,tvAuthor,tvComment,tvTitle; 23 | 24 | private static final String TAG = "ArticleViewHolder"; 25 | 26 | public ArticleViewHolder(ViewGroup view) { 27 | super(view, R.layout.zhuanlanpost_item); 28 | 29 | imageview = $(R.id.iv_articlebg); 30 | circleImageView = $(R.id.profile_image); 31 | tvAuthor = $(R.id.tv_author); 32 | tvLike = $(R.id.tv_fans_count); 33 | tvComment = $(R.id.tv_comment_count); 34 | tvTitle = $(R.id.tv_title); 35 | } 36 | 37 | @Override 38 | public void setData(Article data) { 39 | super.setData(data); 40 | tvAuthor.setText(data.getSlug() +" "+"发布了文章" ); 41 | tvComment.setText(data.getCommentsCount() +" "+"评论"); 42 | tvTitle.setText(data.getTitle()); 43 | Log.e(TAG,data.getProfileUrl()); 44 | // Because of zhihu url API logo is the default img 45 | Glide.with(getContext()) 46 | .load(data.getProfileUrl()) 47 | .centerCrop() 48 | .error(R.drawable.logo) 49 | .placeholder(R.drawable.logo)//占位图 50 | .diskCacheStrategy(DiskCacheStrategy.SOURCE) 51 | .into(circleImageView); 52 | 53 | Glide.with(getContext()) 54 | .load(data.getTitleImage()) 55 | .centerCrop() 56 | .error(R.drawable.error) 57 | .placeholder(R.drawable.error)//占位图 58 | .diskCacheStrategy(DiskCacheStrategy.SOURCE) 59 | .into(imageview); 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/adapter/GirlAdapter.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.adapter; 2 | 3 | import android.content.Context; 4 | import android.view.ViewGroup; 5 | 6 | import com.jude.easyrecyclerview.adapter.BaseViewHolder; 7 | import com.jude.easyrecyclerview.adapter.RecyclerArrayAdapter; 8 | import com.wu.allen.zhuanlan.model.Item; 9 | 10 | /** 11 | * Created by allen on 2016/6/17. 12 | */ 13 | public class GirlAdapter extends RecyclerArrayAdapter { 14 | 15 | 16 | public GirlAdapter(Context context) { 17 | super(context); 18 | } 19 | 20 | @Override 21 | public BaseViewHolder OnCreateViewHolder(ViewGroup parent, int viewType) { 22 | return new GirlViewHolder(parent); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/adapter/GirlViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.adapter; 2 | 3 | import android.util.Log; 4 | import android.view.ViewGroup; 5 | import android.widget.ImageView; 6 | 7 | import com.bumptech.glide.Glide; 8 | import com.bumptech.glide.load.engine.DiskCacheStrategy; 9 | import com.jude.easyrecyclerview.adapter.BaseViewHolder; 10 | import com.wu.allen.zhuanlan.R; 11 | import com.wu.allen.zhuanlan.model.Item; 12 | 13 | /** 14 | * Created by allen on 2016/6/17. 15 | */ 16 | public class GirlViewHolder extends BaseViewHolder { 17 | private ImageView image; 18 | public GirlViewHolder(ViewGroup parent) { 19 | super(parent, R.layout.girl_item); 20 | image = $(R.id.image); 21 | } 22 | 23 | @Override 24 | public void setData(Item data) { 25 | super.setData(data); 26 | Log.e("GirlViewHolder",data.imageUrl); 27 | Glide.with(getContext()) 28 | .load(data.imageUrl) 29 | .diskCacheStrategy(DiskCacheStrategy.SOURCE) 30 | .into(image); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/adapter/ZhuanLanAdapter.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.adapter; 2 | 3 | import android.content.Context; 4 | import android.view.ViewGroup; 5 | 6 | import com.jude.easyrecyclerview.adapter.BaseViewHolder; 7 | import com.jude.easyrecyclerview.adapter.RecyclerArrayAdapter; 8 | import com.wu.allen.zhuanlan.model.ZhuanLan; 9 | 10 | /** 11 | * Created by allen on 2016/6/14. 12 | */ 13 | public class ZhuanLanAdapter extends RecyclerArrayAdapter { 14 | 15 | 16 | public ZhuanLanAdapter(Context context) { 17 | super(context); 18 | } 19 | 20 | @Override 21 | public BaseViewHolder OnCreateViewHolder(ViewGroup parent, int viewType) { 22 | return new ZhuanLanViewHolder(parent); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/adapter/ZhuanLanViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.adapter; 2 | 3 | import android.text.TextPaint; 4 | import android.view.ViewGroup; 5 | import android.widget.TextView; 6 | 7 | import com.bumptech.glide.Glide; 8 | import com.bumptech.glide.load.engine.DiskCacheStrategy; 9 | import com.jude.easyrecyclerview.adapter.BaseViewHolder; 10 | import com.wu.allen.zhuanlan.R; 11 | import com.wu.allen.zhuanlan.model.ZhuanLan; 12 | 13 | import de.hdodenhof.circleimageview.CircleImageView; 14 | 15 | /** 16 | * Created by allen on 2016/6/14. 17 | */ 18 | public class ZhuanLanViewHolder extends BaseViewHolder { 19 | 20 | private CircleImageView imageView; 21 | private TextView tvName; 22 | private TextView tvIntro; 23 | private TextView tvArticleCount; 24 | private TextView tvFansCount; 25 | 26 | 27 | public ZhuanLanViewHolder(ViewGroup parent) { 28 | super(parent, R.layout.zhuanlan_item); 29 | imageView = $(R.id.profile_image); 30 | tvName = $(R.id.name); 31 | tvArticleCount = $(R.id.tv_article_count); 32 | tvFansCount = $(R.id.tv_fans_count); 33 | tvIntro = $(R.id.intro); 34 | // Bold for textView 35 | TextPaint tp = tvIntro.getPaint(); 36 | tp.setFakeBoldText(true); 37 | } 38 | 39 | @Override 40 | public void setData(ZhuanLan data) { 41 | super.setData(data); 42 | Glide.with(getContext()) 43 | .load("https://pic2.zhimg.com/" + data.getAvatar().getId() + "_m.jpg") 44 | .centerCrop() 45 | .error(R.drawable.logo) 46 | .diskCacheStrategy(DiskCacheStrategy.SOURCE) 47 | .into(imageView); 48 | tvName.setText(data.getName()); 49 | tvIntro.setText(data.getDescription()); 50 | tvFansCount.setText(data.getFollowersCount() + "人关注"); 51 | tvArticleCount.setText(data.getPostsCount() + "篇文章"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/cache/Data.java: -------------------------------------------------------------------------------- 1 | // (c)2016 Flipboard Inc, All Rights Reserved. 2 | 3 | 4 | // here reference is https://github.com/rengwuxian/RxJavaSamples/tree/master/app/src/main/java/com/rengwuxian/rxjavasamples/module/cache_6/data 5 | 6 | package com.wu.allen.zhuanlan.cache; 7 | 8 | import android.support.annotation.NonNull; 9 | import android.util.Log; 10 | 11 | import com.wu.allen.zhuanlan.model.Girl; 12 | import com.wu.allen.zhuanlan.model.Item; 13 | import com.wu.allen.zhuanlan.net.Network; 14 | 15 | import java.text.ParseException; 16 | import java.text.SimpleDateFormat; 17 | import java.util.ArrayList; 18 | import java.util.Date; 19 | import java.util.List; 20 | 21 | import rx.Observable; 22 | import rx.Observer; 23 | import rx.Subscriber; 24 | import rx.Subscription; 25 | import rx.android.schedulers.AndroidSchedulers; 26 | import rx.functions.Action1; 27 | import rx.functions.Func1; 28 | import rx.schedulers.Schedulers; 29 | import rx.subjects.BehaviorSubject; 30 | public class Data { 31 | private static final String TAG = "MeiziData"; 32 | private static Data instance; 33 | // BehaviorSubject 只打印出最后一个数据 34 | BehaviorSubject> cache; 35 | private Data() { 36 | } 37 | // 单例 38 | public static Data getInstance() { 39 | if (instance == null) { 40 | instance = new Data(); 41 | } 42 | return instance; 43 | } 44 | // 从网络加载数据 45 | public void loadFromNetwork(final int page) { 46 | Log.e(TAG,page + ""); 47 | Network.getGankApi() 48 | .getBeauties(10, page) 49 | .subscribeOn(Schedulers.io()) 50 | // Observable 返回的类型 GankBeautyResult Map 转换成 List 51 | .map(new Func1>() { 52 | @Override 53 | public List call(Girl girl) { 54 | List gankBeauties = girl.girlResults; 55 | List items = new ArrayList<>(gankBeauties.size()); 56 | for (Girl.GirlResult gankBeauty : gankBeauties) { 57 | Item item = new Item(); 58 | item.description = gankBeauty.desc; 59 | item.imageUrl = gankBeauty.url; 60 | items.add(item); 61 | } 62 | return items; 63 | } 64 | }) 65 | // 在 doOnNext() 之前先处理一下 Action1> 里面的数据就是输入的数据 66 | .doOnNext(new Action1>() { 67 | @Override 68 | public void call(List items) { 69 | // 写入缓存 70 | Log.e("Data","data write in disk cache"); 71 | Database.getInstance().writeItems(items); 72 | } 73 | }) 74 | .subscribe(new Action1>() { 75 | @Override 76 | public void call(List items) { 77 | Log.e("Data","data pass to subscribe"); 78 | cache.onNext(items);// 自动回调 cache.onNext(items); 79 | } 80 | }, new Action1() { 81 | @Override 82 | public void call(Throwable throwable) { 83 | throwable.printStackTrace(); 84 | } 85 | }); 86 | } 87 | // 获取数据 88 | public Subscription subscribeData(@NonNull Observer> observer,final int number) { 89 | 90 | if (cache == null ) { 91 | cache = BehaviorSubject.create(); 92 | Observable.create(new Observable.OnSubscribe>() { 93 | @Override 94 | public void call(Subscriber> subscriber) { 95 | List items = Database.getInstance().readItems(); 96 | 97 | if (items == null) { 98 | Log.e("Data","no data in disk and load data from net"); 99 | loadFromNetwork(number); 100 | } else { 101 | Log.e("Data","disk has data"); 102 | subscriber.onNext(items); 103 | } 104 | } 105 | }) 106 | .subscribeOn(Schedulers.io()) 107 | .subscribe(cache);// 观察者与被观察着通过订阅联系起来 108 | } else { 109 | Log.e("Data","memory has data just read from memory"); 110 | } 111 | return cache.observeOn(AndroidSchedulers.mainThread()) 112 | .subscribe(observer); 113 | } 114 | 115 | public void clearMemoryCache() { 116 | cache = null; 117 | } 118 | // 内存和磁盘缓存 119 | public void clearMemoryAndDiskCache() { 120 | clearMemoryCache(); 121 | // 删除磁盘缓存 122 | Database.getInstance().delete(); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/cache/Database.java: -------------------------------------------------------------------------------- 1 | // (c)2016 Flipboard Inc, All Rights Reserved. 2 | // here reference is https://github.com/rengwuxian/RxJavaSamples/tree/master/app/src/main/java/com/rengwuxian/rxjavasamples/module/cache_6/data 3 | package com.wu.allen.zhuanlan.cache; 4 | 5 | import android.util.Log; 6 | 7 | import com.google.gson.Gson; 8 | import com.google.gson.reflect.TypeToken; 9 | import com.wu.allen.zhuanlan.App; 10 | import com.wu.allen.zhuanlan.model.Item; 11 | 12 | import java.io.File; 13 | import java.io.FileNotFoundException; 14 | import java.io.FileReader; 15 | import java.io.FileWriter; 16 | import java.io.IOException; 17 | import java.io.Reader; 18 | import java.io.Writer; 19 | import java.util.List; 20 | 21 | public class Database { 22 | private static String DATA_FILE_NAME = "data.db"; 23 | 24 | private static Database INSTANCE; 25 | File dataFile = new File(App.getInstance().getFilesDir(), DATA_FILE_NAME); 26 | Gson gson = new Gson(); 27 | //System.out.println() 28 | 29 | private Database() { 30 | 31 | } 32 | 33 | public static Database getInstance() { 34 | if (INSTANCE == null) { 35 | INSTANCE = new Database(); 36 | 37 | } 38 | 39 | return INSTANCE; 40 | } 41 | 42 | public List readItems() { 43 | // Hard code adding some delay, to distinguish reading from memory and reading disk clearly 44 | try { 45 | Thread.sleep(100); 46 | } catch (InterruptedException e) { 47 | e.printStackTrace(); 48 | } 49 | 50 | try { 51 | 52 | Reader reader = new FileReader(dataFile); 53 | return gson.fromJson(reader, new TypeToken>(){}.getType()); 54 | } catch (FileNotFoundException e) { 55 | e.printStackTrace(); 56 | return null; 57 | } 58 | } 59 | 60 | public void writeItems(List items) { 61 | String json = gson.toJson(items); 62 | try { 63 | if (!dataFile.exists()) { 64 | try { 65 | dataFile.createNewFile(); 66 | } catch (IOException e) { 67 | e.printStackTrace(); 68 | } 69 | } 70 | Writer writer = new FileWriter(dataFile); 71 | writer.write(json); 72 | writer.flush(); 73 | } catch (IOException e) { 74 | e.printStackTrace(); 75 | } 76 | } 77 | 78 | public void delete() { 79 | dataFile.delete(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/model/Article.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.model; 2 | 3 | /** 4 | * Created by allen on 2016/6/15. 5 | */ 6 | public class Article { 7 | 8 | private String title; 9 | private String titleImage; 10 | private String summary; 11 | private String content; 12 | private String profileUrl; 13 | private int commentsCount; 14 | private int likeCount; 15 | private String slug; 16 | private String articleSlug; 17 | 18 | 19 | 20 | public Article(String title, String titleImage, String summary, String content, int commentsCount, 21 | int likeCount, String slug,String articleSlug,String profileUrl) { 22 | this.title = title; 23 | this.titleImage = titleImage; 24 | this.summary = summary; 25 | this.content = content; 26 | this.commentsCount = commentsCount; 27 | this.likeCount = likeCount; 28 | this.slug = slug; 29 | this.articleSlug = articleSlug; 30 | this.profileUrl = profileUrl; 31 | } 32 | 33 | public String getArticleSlug() { 34 | return articleSlug; 35 | } 36 | 37 | public void setArticleSlug(String articleSlug) { 38 | this.articleSlug = articleSlug; 39 | } 40 | 41 | public String getSummary() { 42 | return summary; 43 | } 44 | 45 | public void setSummary(String summary) { 46 | this.summary = summary; 47 | } 48 | 49 | public String getTitle() { 50 | return title; 51 | } 52 | 53 | public void setTitle(String title) { 54 | this.title = title; 55 | } 56 | 57 | public String getTitleImage() { 58 | return titleImage; 59 | } 60 | 61 | public void setTitleImage(String titleImage) { 62 | this.titleImage = titleImage; 63 | } 64 | 65 | public String getContent() { 66 | return content; 67 | } 68 | 69 | public void setContent(String content) { 70 | this.content = content; 71 | } 72 | 73 | public int getCommentsCount() { 74 | return commentsCount; 75 | } 76 | 77 | public void setCommentsCount(int commentsCount) { 78 | this.commentsCount = commentsCount; 79 | } 80 | 81 | public int getLikeCount() { 82 | return likeCount; 83 | } 84 | 85 | public void setLikeCount(int likeCount) { 86 | this.likeCount = likeCount; 87 | } 88 | public String getSlug() { 89 | return slug; 90 | } 91 | 92 | public void setSlug(String slug) { 93 | this.slug = slug; 94 | } 95 | 96 | public String getProfileUrl() { 97 | return profileUrl; 98 | } 99 | 100 | public void setProfileUrl(String profileUrl) { 101 | this.profileUrl = profileUrl; 102 | } 103 | 104 | 105 | } 106 | 107 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/model/ArticleDetail.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.model; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by allen on 2016/6/19. 7 | */ 8 | public class ArticleDetail { 9 | 10 | 11 | /** 12 | * isTitleImageFullScreen : false 13 | * rating : none 14 | * titleImage : https://pic1.zhimg.com/aa0da1510a8d64c96bf811072a35addc_r.jpg 15 | * links : {"comments":"/api/posts/20460225/comments"} 16 | * reviewers : [] 17 | * topics : [] 18 | * titleImageSize : {"width":719,"height":393} 19 | * href : /api/posts/20460225 20 | * author : {"profileUrl":"https://www.zhihu.com/people/fenggou","bio":"信息安全,鸡尾酒,电吉他,摄影。","hash":"1e373c5c6d9af3c8beaeb9aadc0890ac","name":"fenggou","isOrg":false,"slug":"fenggou","avatar":{"id":"939c20127","template":"https://pic4.zhimg.com/{id}_{size}.jpg"},"description":"写文字对于我来说挺痛苦的。"} 21 | * tipjarState : closed 22 | * content : 前几天写了个iPhone手机丢失后骗子是如何忽悠你交出AppleID账号密码的,一些名词解释和详细内容在这里 用iPhone的,丢过或者要丢的赶紧进来瞅瞅,如果没看过的话要先点过去补补课,因为今天的内容更惊悚,更要加以防范!

这个攻击叫做“GodLike攻击”。

上一篇内容提到的骗子手段还算文艺的,用各种各样“文案”忽悠你交出密码,但你以为不上道就没事儿了?流氓会武术,谁都拦不住。现在骗子们开始用上“黑科技”,通过漏洞直接盗取你的AppleID邮箱,自己给手机iCloud安全绑定取消掉!

首先,各种软磨硬泡钓鱼你不上钩后,会收到这样的邮件:

或者这样的

哼哼,你以为骗子服软了?给点小钱就把手机还给你?图样啊……当你点开那张“照片”后,一段隐藏的恶意代码就在你的浏览器上欢快的开始干活了,“悄悄地进村,打枪的不要”。

隐藏的恶意代码如下,算了,反正你们也看不懂,我就不放图了。总之,骗子所谓的照片或者店铺地址隐藏了一个名叫“GodLike”的html页面,这个页面中的恶意代码利用了国内某大型邮件服务商的漏洞,通过这个漏洞可以直接盗取你的邮箱控制权。

主要是获取XX邮箱sid、skey等关键认证信息。

还是放图吧,看看骗子的专业性。。。这张“图片”点开后是酱婶儿的,其实是个网页,藏着Godlike攻击代码

具体的GodLike攻击代码,窃取你邮箱的认证信息

将窃取到的认证信息发送到骗子的远程服务器上记录与利用,你邮箱就这么丢的,现在造了不?感觉骗子专业吧,会用漏洞了。

PS:上图来自于 js可以跨域得到cookie?qq邮箱被一封邮件黑了? 中提供的线索,感谢。另外该邮箱漏洞目前已经修复(12月31日更新)

试想下,你用了XX厂的邮箱,然后还注册成了AppleID,那么当你点开这封邮件的时候,骗子通过漏洞窃取了你XX邮箱的控制权,然后上了你的邮箱翻些好玩的,顺便再给你AppleID的绑定给取消掉,好么,这次丢了邮箱又彻底赔了手机。。。

乌云君目前接到了多起漏洞攻击举报,文件命名都是“GodLike”,所以将这种攻击行为命名为GodLike攻击,这种漏洞攻击力极强,不知骗子的音箱是否正回响着

First Blood !

Double Kill !!

Triple Kill !!!

M-m-m-m....Monster Kill !!!!!

God Like !!!!!

[骗子某] 已经接近神了。。。拜托谁杀了他把(350额外金钱)

注:dota玩家都懂得。

再注:很多人遇到这种钓鱼邮件后,喜欢打开链接进行嘲讽(如安全技术人员),但切记在浏览器隐身模式下打开,否则你正登录着的XX邮箱也会被盗走(误伤啊,放过我)。。

最后注:该漏洞乌云君已经报告给XX邮箱官方,预计很快就会修复。目前防范手段就是不要在电脑或手机端直接打开邮件中的链接,在“隐身模式”中查看。

额外参考内容:

黑产godlike攻击: 邮箱 XSS 窃取 appleID 的案例分析

js可以跨域得到cookie?qq邮箱被一封邮件黑了? - iPhone


--------------------------------------------

网站:乌云漏洞报告平台


微博: 乌云君

微信: wooyun_org

知乎专栏: 乌云君 - 知乎专栏

联系邮箱: help@wooyun.org

23 | * state : published 24 | * sourceUrl : 25 | * commentPermission : anyone 26 | * canComment : true 27 | * snapshotUrl : 28 | * slug : 20460225 29 | * publishedTime : 2015-12-30T19:54:14+08:00 30 | * url : /p/20460225 31 | * title : 用iPhone的,丢过或者要丢的赶紧进来瞅瞅(二) 32 | * lastestLikers : [{"profileUrl":"https://www.zhihu.com/people/lu-xiao-feng-75-10","bio":"敏而好学","hash":"9c650de7d6970e725125bedde7b0ed4f","name":"陆小凤","isOrg":false,"slug":"lu-xiao-feng-75-10","avatar":{"id":"7495193c3","template":"https://pic4.zhimg.com/{id}_{size}.jpg"},"description":""},{"profileUrl":"https://www.zhihu.com/people/superman666","bio":"前端开发 / 摄影爱好者","hash":"90ca7906d37d0cab516dc81baba87bad","name":"超人不会飞","isOrg":false,"slug":"superman666","avatar":{"id":"c3368f968ea383ce15b0c5e6be612cbd","template":"https://pic2.zhimg.com/{id}_{size}.jpg"},"description":"博客:superman66.github.io"},{"profileUrl":"https://www.zhihu.com/people/inkdie","bio":"我不姓年,名字是我的生日。","hash":"6b6bb6b1aeb36a85cbd88455d7038662","name":"A小年","isOrg":false,"slug":"inkdie","avatar":{"id":"e31be3f17","template":"https://pic4.zhimg.com/{id}_{size}.jpg"},"description":"唉,你视奸我!"},{"profileUrl":"https://www.zhihu.com/people/e-na-come","bio":"","hash":"c0d389562ed3720275c3ee0b1a51e4f0","name":"哦那come","isOrg":false,"slug":"e-na-come","avatar":{"id":"da8e974dc","template":"https://pic1.zhimg.com/{id}_{size}.jpg"},"description":""},{"profileUrl":"https://www.zhihu.com/people/auras-power","bio":"无尽循环甲虫","hash":"85ab1f72157e16c63428f2e6cf2976cb","name":"Auras Power","isOrg":false,"slug":"auras-power","avatar":{"id":"095297c22371d02563801a1a3f380042","template":"https://pic3.zhimg.com/{id}_{size}.jpg"},"description":""}] 33 | * summary : 前几天写了个iPhone手机丢失后骗子是如何忽悠你交出AppleID账号密码的,一些名词解释和详细内容在这里 用iPhone的,丢过或者要丢的赶紧进来瞅瞅,如果没看过的话要先点过去补补课,因为今天的内容更惊悚,更要加以防范!这个攻击叫做“GodLike攻击”。上一… 34 | * column : {"slug":"wooyun","name":"乌云君"} 35 | * meta : {"previous":{"isTitleImageFullScreen":false,"rating":"none","titleImage":"https://pic3.zhimg.com/a30ea3ed0b85fb52baaae17f24691a12_r.jpg","links":{"comments":"/api/posts/20416511/comments"},"topics":[],"href":"/api/posts/20416511","author":{"profileUrl":"https://www.zhihu.com/people/fenggou","bio":"信息安全,鸡尾酒,电吉他,摄影。","hash":"1e373c5c6d9af3c8beaeb9aadc0890ac","name":"fenggou","isOrg":false,"slug":"fenggou","avatar":{"id":"939c20127","template":"https://pic4.zhimg.com/{id}_{size}.jpg"},"description":"写文字对于我来说挺痛苦的。"},"content":"如果你是iPhone用户,那今天的内容对你来说挺重要<\/b>。这个事儿也许你经历过,也许你的朋友遇到过,但你没遇到过不知所以然,不过无需担心,乌云君马上给你娓娓道来,再重复一次:今天这事儿对iPhone用户很重要!<\/b>

忘记从iOS哪个版本开始,苹果对iPhone上登录的AppleID(iCloud账号,就是你同步你通讯录、照片需要的那个账号)进行了严格的绑定措施。在没有iCloud密码的情况下,其他人没法把你账号注销换成自己的。换句话说,你的这部iPhone登录iCloud账号后,除非你主动解除绑定,否则控制权将一直属于你。<\/p>

挺好的不是么,买了个属于自己的东西,比那啥只能使用70年强多了。但是,但是!有个行业不爽了,那就是传说中的 \u201c\u2018偷\u2019机倒手\u201d 族,专注于偷手机、然后销赃的利益链。因苹果的这个绑定机制,偷来的手机不好卖了,买家绑定不了自己账号还面临随时被原主人锁机的风险谁干那?但做这个买卖的没有傻子,他们很快就想到了完美的应对方案:骗<\/b>(别笑,这个方案在中国真的可以说完美)!<\/p>

于是在丢失iPhone后,我们开始收到这样的短信/邮件:<\/p>

手机丢了本来就心慌,里面存的那些照片、视频让别人看着可咋整啊\u2026\u2026结果收到这种信息后,一下又充满了希望。原来可以拒绝其他人激活看到俺的信息,还能定位?赶紧看看偷手机的小兔崽子在哪!结果头一晕,手一快,手机彻底找不回来了不说,还将自己的iCloud账号拱手送给了骗子。<\/p>

等等,刚刚剧情发展有点快,没看懂,这中间到底都发生了啥?点开明明就是iCloud登录页面么,那么酷炫屌炸天的Apple style我不会认错的。那现在乌云君镜头慢回放,仔细看下点开的网站:<\/p>

看看Apple官方的样子<\/p>

https还算是一个识别依据吧,但也不是100%的,比如乌云社区的这个案例,骗子竟然也上了合法的https证书(发现一个仿真度极高的Paypal钓鱼网站还支持SSL<\/i><\/a>),最靠谱的还是做到百分之百识别官方域名 http://<\/span>icloud.com<\/span><\/span><\/i><\/a> <\/b>吧,要啥奇技淫巧啊这么简单的事儿 :(<\/p>

--------- 牛逼闪闪的分割线 ----------<\/b><\/p>

其实上面的内容大家多少都知道点哈\u2026就这么结束显然不够逼格,所以下面要放出重磅炸弹,白帽子对这种钓鱼平台的后台进行数据揭秘,看看到底有多少人中招,骗子怎么收密码的,以下内容来自乌云白帽子报告:利用漏洞揭秘iCloud钓鱼网站(涉及20余个钓鱼站点,最近1个月1.3万Apple受害者)<\/i><\/a><\/p>

首先白帽子对自己收到的这个iCloud骗子网站进行了深入的分析(骗错人了hiahia),不出意料发现了一个漏洞,这个漏洞不一般,感觉更像是钓鱼行业中\u201c黑吃黑\u201d现象故意留的后门(你买我的钓鱼程序,但后期我能悄悄的拿走你骗来的成果,对用户账号造成二次泄露影响),这个牛逼哄哄的漏洞是酱婶儿的<\/p>

白帽子在这个钓鱼站上找到这个链接,打开后直接就是发件箱配置,里面一个163邮箱,密码星号看不到。不急,右键查看源代码,找到了星号下的明文邮箱密码,登录之,duang!原来受骗者填写的iCloud账号密码是通过这个邮箱发给后面的骗子。<\/b><\/p>

随便点开一封邮件瞅瞅,老板请核实资料?<\/p>

有了这个信息,就能解除iCloud绑定了,白帽子在这几分钟的时间内,发现这个邮箱仍然在对外发新的受骗者信息,可见其活跃。通过这个邮箱不完全统计,该骗子在一个月内竟成功骗到了一万三千多个iCloud账号和密码<\/b>!<\/p>

飞蛾扑火,前赴后继。明显感觉到这骗子已经累惨,平均每天要去验证433个iCloud账号,然后解绑手机点钱,这活儿还真有点吸引力呢。关于iPhone手机的倒卖行业,乌云君也做了些了解,丢了想找回来几乎不可能,在各种钓鱼手段狂轰滥炸一个月后没中招的话,骗子会将你的手机买配件,大概2k左右的价格也很可观。所以iPhone丢失后,不要抱有太多的幻想能够找回手机,期望越大,你越容易落入陷阱。<\/p>

所以乌云君的建议是平时的锁屏密码设置复杂些,毕竟都指纹解锁了,麻烦不到哪去,同步备份也勤快些省的丢失数据;其次丢失手机第一时间登录iCloud锁定手机,并在接下来的一段时间内警惕任何收到的短信、邮件(有的骗子直接跳出伪装跟你聊QQ,中间威逼利诱,比如你给500机器就还给你之类的,大家好自为之)。<\/p>

哦对了,这事儿有破案的先例:刚丢 iPhone 就收到 Apple ID 异常邮件?小心被钓鱼(已有近 3000 人中招)<\/i><\/a><\/p>

最后微博上收集了一些热心网友讨论,分享一下,今天就酱:<\/p>

***Angeles:一看短信就知道假的,擦 人家官方几时写iPhone 为 iphone 过啊,大小写很注重的好吧 (细节党,赞)
<\/p>

三不知大师:故意输错密码账号试试能不能通过 (这还真是个小技巧,但不准)
<\/p>

余博伦是讨厌鬼:这前端仿得不错哈 (是的,好的钓鱼前端能迷倒千千万)
<\/p>

Leona***:小时候玩剑侠情缘网络版,被骗子的免费点卡充值引诱,在山寨官网上填写账号从而被盗号以后,对于任何需要填写密码的网页我都是检查了又检查。已坚持了十二年。 (一朝被蛇咬,十二年怕井绳,不过谨慎是个好事儿少年)
<\/p>

亦半亦*:手机丢后也收到过 如果不是苹果客服提醒骗子会想方设法的套取我的用户名和密码就信了 还好自己也上假冒网站上去过F12看了下就一张图片和一个表单提交控件。。 (技术流,但看域名更简单一些)
<\/p>

iTo***: 手机丢啦,苹果也不会主动发短信给你的!所以这样的短信只能是骗子想获取你的id。稍后的每日一技中会提到~不过大家还是要注意下啦!<\/p><\/blockquote>

--------------------------------------------<\/p>

网站:乌云漏洞报告平台<\/i><\/a><\/p>

微博: 乌云君<\/i><\/a><\/p>

微信: wooyun_org<\/p>

知乎专栏: 乌云君 - 知乎专栏<\/a><\/p>

联系邮箱: help@wooyun.org<\/p>","state":"published","sourceUrl":"","commentPermission":"anyone","canComment":true,"snapshotUrl":"","slug":20416511,"publishedTime":"2015-12-14T19:13:38+08:00","url":"/p/20416511","title":"用iPhone的,丢过或者要丢的赶紧进来瞅瞅","summary":"如果你是iPhone用户,那今天的内容对你来说挺重要<\/b>。这个事儿也许你经历过,也许你的朋友遇到过,但你没遇到过不知所以然,不过无需担心,乌云君马上给你娓娓道来,再重复一次:今天这事儿对iPhone用户很重要!<\/b>忘记从iOS哪个版本开始,苹果对iPhone上登录的A\u2026","column":{"slug":"wooyun","name":"乌云君"},"meta":{"previous":null,"next":null},"reviewingCommentsCount":0,"commentsCount":0,"likesCount":0},"next":{"isTitleImageFullScreen":false,"rating":"none","titleImage":"https://pic4.zhimg.com/04129f190219918c6f2c597daed1a6ab_r.jpg","links":{"comments":"/api/posts/20581814/comments"},"topics":[],"href":"/api/posts/20581814","author":{"profileUrl":"https://www.zhihu.com/people/fenggou","bio":"信息安全,鸡尾酒,电吉他,摄影。","hash":"1e373c5c6d9af3c8beaeb9aadc0890ac","name":"fenggou","isOrg":false,"slug":"fenggou","avatar":{"id":"939c20127","template":"https://pic4.zhimg.com/{id}_{size}.jpg"},"description":"写文字对于我来说挺痛苦的。"},"content":"

一提到车牌,就忍不住放这个梗。。。<\/p>

去年乌云君曝光了一些车主信息被非法交易买卖的事情,这些数据会从汽车销售商、总部客户系统中被泄露,然后用来做一些广告推销或违章诈骗等危害用户权益的行为,比如这样(apk文件为android手机木马程序)<\/p>

或这样<\/p>

车辆信息除了汽车经销商,车辆管理等机构,更大的一个信息聚集地就是保险公司,因为车险是每个车主都必须要购买的,并且需要超详细的个人信息记录。那保险公司是否也会给用户造成不必要的泄密困扰呢?<\/p>

有乌云白帽之前发现了阳光保险公司一个惊呆的漏洞 阳光保险某安全漏洞可查询海量车主信息(车架号、发动机号、姓名、身份证号等)<\/i><\/a> ,只要你知道对方的车牌号码,就能够查询车辆以及车主信息,甚至还能查到其最近的违章记录!<\/p>

首先进入该保险公司车险报价查询页面,输入你想查询的车牌号码与地区,然后个人信息随便填(对就是随便填写,不用与真实车主信息吻合),选个牛逼的号码<\/p>

然后点击下一步,没挑战,车架号、发动机号、车主姓名、身份证号码都出来了<\/p>

不同地区查询的信息结果不同,还可以更详细一些<\/p>

车主信息指哪打哪,利用发动机号等信息还可以查询车主近期的违章记录,这用来诈骗不妥妥的?比如另个北京土豪号码近期的违章记录。。。<\/p>

车牌信息的定向查询,加上如今各种\u201c社工库\u201d的配合,能给车主带来不小的麻烦。随便上个路被黑客盯上了,一系列的麻烦可能会接踵而来。同样,各类的保险也是更多行业间信息泄露的另类途径。<\/p>

--------------------------------------------<\/p>

网站:乌云漏洞报告平台<\/a><\/p>

微博: 乌云君<\/a><\/p>

微信: wooyun_org<\/p>

知乎专栏: 乌云君 - 知乎专栏<\/a><\/p>

联系邮箱: help@wooyun.org<\/p>","state":"published","sourceUrl":"","commentPermission":"anyone","canComment":true,"snapshotUrl":"","slug":20581814,"publishedTime":"2016-02-18T15:18:42+08:00","url":"/p/20581814","title":"汽车号牌已变成新的信息泄露入口?!","summary":"一提到车牌,就忍不住放这个梗。。。去年乌云君曝光了一些车主信息被非法交易买卖的事情,这些数据会从汽车销售商、总部客户系统中被泄露,然后用来做一些广告推销或违章诈骗等危害用户权益的行为,比如这样(apk文件为android手机木马程序)或这样车辆信息\u2026","column":{"slug":"wooyun","name":"乌云君"},"meta":{"previous":null,"next":null},"reviewingCommentsCount":0,"commentsCount":0,"likesCount":0}} 36 | * reviewingCommentsCount : 0 37 | * commentsCount : 26 38 | * likesCount : 245 39 | */ 40 | 41 | private String titleImage; 42 | /** 43 | * comments : /api/posts/20460225/comments 44 | */ 45 | 46 | private LinksBean links; 47 | private String content; 48 | private int slug; 49 | private String publishedTime; 50 | private String title; 51 | private String summary; 52 | private int commentsCount; 53 | private int likesCount; 54 | private List reviewers; 55 | private List topics; 56 | 57 | public String getTitleImage() { 58 | return titleImage; 59 | } 60 | 61 | public void setTitleImage(String titleImage) { 62 | this.titleImage = titleImage; 63 | } 64 | 65 | public LinksBean getLinks() { 66 | return links; 67 | } 68 | 69 | public void setLinks(LinksBean links) { 70 | this.links = links; 71 | } 72 | 73 | public String getContent() { 74 | return content; 75 | } 76 | 77 | public void setContent(String content) { 78 | this.content = content; 79 | } 80 | 81 | public int getSlug() { 82 | return slug; 83 | } 84 | 85 | public void setSlug(int slug) { 86 | this.slug = slug; 87 | } 88 | 89 | public String getPublishedTime() { 90 | return publishedTime; 91 | } 92 | 93 | public void setPublishedTime(String publishedTime) { 94 | this.publishedTime = publishedTime; 95 | } 96 | 97 | public String getTitle() { 98 | return title; 99 | } 100 | 101 | public void setTitle(String title) { 102 | this.title = title; 103 | } 104 | 105 | public String getSummary() { 106 | return summary; 107 | } 108 | 109 | public void setSummary(String summary) { 110 | this.summary = summary; 111 | } 112 | 113 | public int getCommentsCount() { 114 | return commentsCount; 115 | } 116 | 117 | public void setCommentsCount(int commentsCount) { 118 | this.commentsCount = commentsCount; 119 | } 120 | 121 | public int getLikesCount() { 122 | return likesCount; 123 | } 124 | 125 | public void setLikesCount(int likesCount) { 126 | this.likesCount = likesCount; 127 | } 128 | 129 | public List getReviewers() { 130 | return reviewers; 131 | } 132 | 133 | public void setReviewers(List reviewers) { 134 | this.reviewers = reviewers; 135 | } 136 | 137 | public List getTopics() { 138 | return topics; 139 | } 140 | 141 | public void setTopics(List topics) { 142 | this.topics = topics; 143 | } 144 | 145 | public static class LinksBean { 146 | private String comments; 147 | 148 | public String getComments() { 149 | return comments; 150 | } 151 | 152 | public void setComments(String comments) { 153 | this.comments = comments; 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/model/Girl.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by allen on 2016/6/17. 9 | */ 10 | public class Girl { 11 | public boolean error; 12 | // @SerializedName 定义序列化之后的名称 13 | public @SerializedName("results") 14 | List girlResults; 15 | 16 | public static class GirlResult{ 17 | public String desc; 18 | public String url; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/model/Item.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.model; 2 | 3 | /** 4 | * Created by allen on 2016/6/17. 5 | * 6 | * Cache Item for girl pic 7 | */ 8 | public class Item { 9 | public String description; 10 | public String imageUrl; 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/model/ZhuanLan.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.model; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by allen on 2016/6/14. 7 | */ 8 | public class ZhuanLan { 9 | 10 | private int followersCount; 11 | private String description; 12 | private CreatorBean creator; 13 | private String href; 14 | private boolean acceptSubmission; 15 | private String slug; 16 | private String name; 17 | private String url; 18 | private AvatarBean avatar; 19 | private String commentPermission; 20 | private boolean following; 21 | private int postsCount; 22 | private boolean canPost; 23 | private boolean activateAuthorRequested; 24 | 25 | private List topics; 26 | 27 | public int getFollowersCount() { 28 | return followersCount; 29 | } 30 | 31 | public void setFollowersCount(int followersCount) { 32 | this.followersCount = followersCount; 33 | } 34 | 35 | public String getDescription() { 36 | return description; 37 | } 38 | 39 | public void setDescription(String description) { 40 | this.description = description; 41 | } 42 | 43 | public CreatorBean getCreator() { 44 | return creator; 45 | } 46 | 47 | public void setCreator(CreatorBean creator) { 48 | this.creator = creator; 49 | } 50 | 51 | public String getHref() { 52 | return href; 53 | } 54 | 55 | public void setHref(String href) { 56 | this.href = href; 57 | } 58 | 59 | public boolean isAcceptSubmission() { 60 | return acceptSubmission; 61 | } 62 | 63 | public void setAcceptSubmission(boolean acceptSubmission) { 64 | this.acceptSubmission = acceptSubmission; 65 | } 66 | 67 | public String getSlug() { 68 | return slug; 69 | } 70 | 71 | public void setSlug(String slug) { 72 | this.slug = slug; 73 | } 74 | 75 | public String getName() { 76 | return name; 77 | } 78 | 79 | public void setName(String name) { 80 | this.name = name; 81 | } 82 | 83 | public String getUrl() { 84 | return url; 85 | } 86 | 87 | public void setUrl(String url) { 88 | this.url = url; 89 | } 90 | 91 | public AvatarBean getAvatar() { 92 | return avatar; 93 | } 94 | 95 | public void setAvatar(AvatarBean avatar) { 96 | this.avatar = avatar; 97 | } 98 | 99 | public String getCommentPermission() { 100 | return commentPermission; 101 | } 102 | 103 | public void setCommentPermission(String commentPermission) { 104 | this.commentPermission = commentPermission; 105 | } 106 | 107 | public boolean isFollowing() { 108 | return following; 109 | } 110 | 111 | public void setFollowing(boolean following) { 112 | this.following = following; 113 | } 114 | 115 | public int getPostsCount() { 116 | return postsCount; 117 | } 118 | 119 | public void setPostsCount(int postsCount) { 120 | this.postsCount = postsCount; 121 | } 122 | 123 | public boolean isCanPost() { 124 | return canPost; 125 | } 126 | 127 | public void setCanPost(boolean canPost) { 128 | this.canPost = canPost; 129 | } 130 | 131 | public boolean isActivateAuthorRequested() { 132 | return activateAuthorRequested; 133 | } 134 | 135 | public void setActivateAuthorRequested(boolean activateAuthorRequested) { 136 | this.activateAuthorRequested = activateAuthorRequested; 137 | } 138 | 139 | public List getTopics() { 140 | return topics; 141 | } 142 | 143 | public void setTopics(List topics) { 144 | this.topics = topics; 145 | } 146 | 147 | public static class CreatorBean { 148 | private String bio; 149 | private String hash; 150 | private String description; 151 | private String profileUrl; 152 | /** 153 | * id : 6c83b4b34 154 | * template : http://pic1.zhimg.com/{id}_{size}.jpg 155 | */ 156 | 157 | private AvatarBean avatar; 158 | private String slug; 159 | private String name; 160 | 161 | public String getBio() { 162 | return bio; 163 | } 164 | 165 | public void setBio(String bio) { 166 | this.bio = bio; 167 | } 168 | 169 | public String getHash() { 170 | return hash; 171 | } 172 | 173 | public void setHash(String hash) { 174 | this.hash = hash; 175 | } 176 | 177 | public String getDescription() { 178 | return description; 179 | } 180 | 181 | public void setDescription(String description) { 182 | this.description = description; 183 | } 184 | 185 | public String getProfileUrl() { 186 | return profileUrl; 187 | } 188 | 189 | public void setProfileUrl(String profileUrl) { 190 | this.profileUrl = profileUrl; 191 | } 192 | 193 | public AvatarBean getAvatar() { 194 | return avatar; 195 | } 196 | 197 | public void setAvatar(AvatarBean avatar) { 198 | this.avatar = avatar; 199 | } 200 | 201 | public String getSlug() { 202 | return slug; 203 | } 204 | 205 | public void setSlug(String slug) { 206 | this.slug = slug; 207 | } 208 | 209 | public String getName() { 210 | return name; 211 | } 212 | 213 | public void setName(String name) { 214 | this.name = name; 215 | } 216 | 217 | public static class AvatarBean { 218 | private String id; 219 | private String template; 220 | 221 | public String getId() { 222 | return id; 223 | } 224 | 225 | public void setId(String id) { 226 | this.id = id; 227 | } 228 | 229 | public String getTemplate() { 230 | return template; 231 | } 232 | 233 | public void setTemplate(String template) { 234 | this.template = template; 235 | } 236 | } 237 | } 238 | 239 | public static class AvatarBean { 240 | private String id; 241 | private String template; 242 | 243 | public String getId() { 244 | return id; 245 | } 246 | 247 | public void setId(String id) { 248 | this.id = id; 249 | } 250 | 251 | public String getTemplate() { 252 | return template; 253 | } 254 | 255 | public void setTemplate(String template) { 256 | this.template = template; 257 | } 258 | } 259 | 260 | public static class TopicsBean { 261 | private String url; 262 | private String id; 263 | private String name; 264 | 265 | public String getUrl() { 266 | return url; 267 | } 268 | 269 | public void setUrl(String url) { 270 | this.url = url; 271 | } 272 | 273 | public String getId() { 274 | return id; 275 | } 276 | 277 | public void setId(String id) { 278 | this.id = id; 279 | } 280 | 281 | public String getName() { 282 | return name; 283 | } 284 | 285 | public void setName(String name) { 286 | this.name = name; 287 | } 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/net/Network.java: -------------------------------------------------------------------------------- 1 | // (c)2016 Flipboard Inc, All Rights Reserved. 2 | 3 | package com.wu.allen.zhuanlan.net; 4 | 5 | 6 | import com.wu.allen.zhuanlan.net.api.ArticleDetailApi; 7 | import com.wu.allen.zhuanlan.net.api.GankApi; 8 | import com.wu.allen.zhuanlan.net.api.ZhLanArticleApi; 9 | import com.wu.allen.zhuanlan.net.api.ZhuanLanApi; 10 | 11 | import java.util.concurrent.TimeUnit; 12 | 13 | import okhttp3.OkHttpClient; 14 | import okhttp3.logging.HttpLoggingInterceptor; 15 | import retrofit2.CallAdapter; 16 | import retrofit2.Converter; 17 | import retrofit2.Retrofit; 18 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; 19 | import retrofit2.converter.gson.GsonConverterFactory; 20 | 21 | public class Network { 22 | private static GankApi gankApi; 23 | private static ZhuanLanApi zhuanLanApi; 24 | private static ZhLanArticleApi zhLanArticleApi; 25 | private static ArticleDetailApi articleDetailApi; 26 | private static final String GANHUO_API = "http://gank.io/api/"; 27 | private static final String ZhuanLanList_API = "https://zhuanlan.zhihu.com/api/columns/"; 28 | private static final String ArticleList_API = "https://zhuanlan.zhihu.com/api/columns/"; 29 | private static final String ARTICLEDETAIL_API = "https://zhuanlan.zhihu.com/api/posts/"; 30 | private static Converter.Factory gsonConverterFactory = GsonConverterFactory.create(); 31 | private static CallAdapter.Factory rxJavaCallAdapterFactory = RxJavaCallAdapterFactory.create(); 32 | private static OkHttpClient client; 33 | 34 | public static OkHttpClient initOkHttp(){ 35 | HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); 36 | interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); 37 | client = new OkHttpClient.Builder() 38 | .addInterceptor(interceptor) 39 | .retryOnConnectionFailure(true) 40 | .connectTimeout(15, TimeUnit.SECONDS) 41 | .build(); 42 | return client; 43 | } 44 | // Gank meizi api 45 | public static GankApi getGankApi() { 46 | if (gankApi == null) { 47 | Retrofit retrofit = new Retrofit.Builder() 48 | .client(initOkHttp()) 49 | .baseUrl(GANHUO_API) 50 | .addConverterFactory(gsonConverterFactory) 51 | .addCallAdapterFactory(rxJavaCallAdapterFactory) 52 | .build(); 53 | gankApi = retrofit.create(GankApi.class); 54 | } 55 | return gankApi; 56 | } 57 | // Zhihu zhuanlan api 58 | public static ZhuanLanApi getZhuanLanApi() { 59 | if (zhuanLanApi == null) { 60 | Retrofit retrofit = new Retrofit.Builder() 61 | .client(initOkHttp()) 62 | .baseUrl(ZhuanLanList_API) 63 | .addConverterFactory(gsonConverterFactory) 64 | .addCallAdapterFactory(rxJavaCallAdapterFactory) 65 | .build(); 66 | zhuanLanApi = retrofit.create(ZhuanLanApi.class); 67 | } 68 | return zhuanLanApi; 69 | } 70 | // Zhihu zhuanlanarticle api 71 | public static ZhLanArticleApi getZhLanArticleApi() { 72 | if (zhLanArticleApi == null) { 73 | Retrofit retrofit = new Retrofit.Builder() 74 | .client(initOkHttp()) 75 | .baseUrl(ArticleList_API) 76 | .addConverterFactory(gsonConverterFactory) 77 | .addCallAdapterFactory(rxJavaCallAdapterFactory) 78 | .build(); 79 | zhLanArticleApi= retrofit.create(ZhLanArticleApi.class); 80 | } 81 | return zhLanArticleApi; 82 | } 83 | // Zhihu articleDetail api 84 | public static ArticleDetailApi getArticleDetailApi() { 85 | if (articleDetailApi == null) { 86 | Retrofit retrofit = new Retrofit.Builder() 87 | .client(initOkHttp()) 88 | .baseUrl(ARTICLEDETAIL_API) 89 | .addConverterFactory(gsonConverterFactory) 90 | .addCallAdapterFactory(rxJavaCallAdapterFactory) 91 | .build(); 92 | articleDetailApi = retrofit.create(ArticleDetailApi.class); 93 | } 94 | return articleDetailApi; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/net/api/ArticleDetailApi.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.net.api; 2 | 3 | import com.wu.allen.zhuanlan.model.ArticleDetail; 4 | import retrofit2.http.GET; 5 | import retrofit2.http.Path; 6 | import rx.Observable; 7 | 8 | /** 9 | * Created by allen on 2016/6/19. 10 | */ 11 | public interface ArticleDetailApi { 12 | @GET("{articleSlug}") 13 | Observable getArticleDetail( 14 | @Path("articleSlug") int articleSlug 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/net/api/GankApi.java: -------------------------------------------------------------------------------- 1 | // (c)2016 Flipboard Inc, All Rights Reserved. 2 | 3 | package com.wu.allen.zhuanlan.net.api; 4 | 5 | 6 | import com.wu.allen.zhuanlan.model.Girl; 7 | 8 | import retrofit2.http.GET; 9 | import retrofit2.http.Path; 10 | import rx.Observable; 11 | 12 | public interface GankApi { 13 | @GET("data/福利/{number}/{page}") 14 | Observable getBeauties(@Path("number") int number, @Path("page") int page); 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/net/api/ZhLanArticleApi.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.net.api; 2 | 3 | import com.wu.allen.zhuanlan.model.Article; 4 | 5 | import java.util.List; 6 | 7 | import retrofit2.http.GET; 8 | import retrofit2.http.Path; 9 | import retrofit2.http.Query; 10 | import rx.Observable; 11 | 12 | /** 13 | * Created by allen on 2016/6/18. 14 | */ 15 | public interface ZhLanArticleApi { 16 | @GET("{slug}/posts") 17 | Observable> getArticle( 18 | @Path("slug") String slug, 19 | @Query("limit") int limit, 20 | @Query("offest") int offest 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/net/api/ZhuanLanApi.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.net.api; 2 | 3 | import com.wu.allen.zhuanlan.model.ZhuanLan; 4 | 5 | import retrofit2.http.GET; 6 | import retrofit2.http.Path; 7 | import rx.Observable; 8 | 9 | /** 10 | * Created by allen on 2016/6/18. 11 | */ 12 | public interface ZhuanLanApi { 13 | @GET("{zhuanlanname}") 14 | Observable getZhuanLan( 15 | @Path("zhuanlanname") String zhuanlanname 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/util/Dp2PxUtil.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.util; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.util.DisplayMetrics; 6 | 7 | /** 8 | * Created by allen on 2016/6/14. 9 | */ 10 | public class Dp2PxUtil { 11 | /** 12 | * This method converts dp unit to equivalent pixels, depending on device density. 13 | * 14 | * @param dp A value in dp (density independent pixels) unit. Which we need to convert into pixels 15 | * @param context Context to get resources and device specific display metrics 16 | * @return A float value to represent px equivalent to dp depending on device density 17 | */ 18 | public static float convertDpToPixel(float dp, Context context){ 19 | Resources resources = context.getResources(); 20 | DisplayMetrics metrics = resources.getDisplayMetrics(); 21 | float px = dp * (metrics.densityDpi / 160f); 22 | return px; 23 | } 24 | 25 | /** 26 | * This method converts device specific pixels to density independent pixels. 27 | * 28 | * @param px A value in px (pixels) unit. Which we need to convert into db 29 | * @param context Context to get resources and device specific display metrics 30 | * @return A float value to represent dp equivalent to px value 31 | */ 32 | public static float convertPixelsToDp(float px, Context context){ 33 | Resources resources = context.getResources(); 34 | DisplayMetrics metrics = resources.getDisplayMetrics(); 35 | float dp = px / (metrics.densityDpi / 160f); 36 | return dp; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/util/ImgSaveUtil.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.util; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.graphics.Bitmap; 6 | import android.net.Uri; 7 | import android.os.Environment; 8 | import android.support.design.widget.Snackbar; 9 | import android.util.Log; 10 | import android.widget.ImageView; 11 | 12 | import java.io.File; 13 | import java.io.FileNotFoundException; 14 | import java.io.FileOutputStream; 15 | import java.io.IOException; 16 | 17 | /** 18 | * Created by allen on 2016/6/19. 19 | * 20 | * Here reference is https://github.com/gaolonglong/GankGirl/blob/master/app/src/main/java/com/app/gaolonglong/gankgirl/util/ImageUtil.java 21 | */ 22 | public class ImgSaveUtil { 23 | 24 | public static Uri saveImage(Context context, String url, Bitmap bitmap, ImageView imageView, String tag){ 25 | //妹纸保存路径 26 | String imgDir = Environment.getExternalStorageDirectory().getPath() + "/GankGirl"; 27 | //图片名称处理 28 | String[] fileNameArr = url.substring(url.lastIndexOf("/") + 1).split("\\."); 29 | String fileName = fileNameArr[0] + ".png"; 30 | //创建文件路径 31 | File fileDir = new File(imgDir); 32 | if (!fileDir.exists()){ 33 | fileDir.mkdir(); 34 | } 35 | //创建文件 36 | File imageFile = new File(fileDir,fileName); 37 | try { 38 | FileOutputStream fos = new FileOutputStream(imageFile); 39 | boolean compress = bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos); 40 | Snackbar.make(imageView,"妹纸已经躺在你的图库里啦.. ( >ω<)",Snackbar.LENGTH_SHORT).show(); 41 | 42 | fos.flush(); 43 | fos.close(); 44 | } catch (FileNotFoundException e) { 45 | e.printStackTrace(); 46 | } catch (IOException e) { 47 | e.printStackTrace(); 48 | } 49 | 50 | Uri uri = Uri.fromFile(imageFile); 51 | //发送广播,通知图库更新 52 | context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,uri)); 53 | return uri; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/util/ImgShareUtil.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.util; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.net.Uri; 6 | 7 | /** 8 | * Created by allen on 2016/6/21. 9 | */ 10 | public class ImgShareUtil { 11 | 12 | public static void shareImage(Context context, Uri uri){ 13 | Intent intent = new Intent(Intent.ACTION_SEND); 14 | intent.setType("image/jpeg");//图片 15 | intent.putExtra(Intent.EXTRA_STREAM, uri); 16 | context.startActivity(Intent.createChooser(intent,"分享妹纸")); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/util/NetWorkUtil.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.util; 2 | 3 | import android.content.Context; 4 | import android.net.ConnectivityManager; 5 | import android.net.NetworkInfo; 6 | 7 | /** 8 | * Created by allen on 2016/6/19. 9 | */ 10 | public class NetWorkUtil { 11 | // NetWork is ok? 12 | public static boolean isNetworkConnected(Context context) { 13 | if (context != null) { 14 | ConnectivityManager mConnectivityManager = (ConnectivityManager) context 15 | .getSystemService(Context.CONNECTIVITY_SERVICE); 16 | NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo(); 17 | if (mNetworkInfo != null) { 18 | return mNetworkInfo.isAvailable(); 19 | } 20 | } 21 | return false; 22 | } 23 | // WIFI is ok? 24 | public static boolean isWifiConnected(Context context) { 25 | if (context != null) { 26 | ConnectivityManager mConnectivityManager = (ConnectivityManager) context 27 | .getSystemService(Context.CONNECTIVITY_SERVICE); 28 | NetworkInfo mWiFiNetworkInfo = mConnectivityManager 29 | .getNetworkInfo(ConnectivityManager.TYPE_WIFI); 30 | if (mWiFiNetworkInfo != null) { 31 | return mWiFiNetworkInfo.isAvailable(); 32 | } 33 | } 34 | return false; 35 | } 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/util/ToastUtil.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.util; 2 | 3 | import android.content.Context; 4 | import android.widget.Toast; 5 | 6 | /** 7 | * Created by allen on 2016/6/14. 8 | */ 9 | 10 | 11 | public class ToastUtil { 12 | 13 | // shortTime 14 | public static void showShort(Context context, String info) { 15 | Toast.makeText(context, info, Toast.LENGTH_SHORT).show(); 16 | } 17 | 18 | // LongTime 19 | public static void showLong(Context context, String info) { 20 | Toast.makeText(context, info, Toast.LENGTH_LONG).show(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/util/TopicData.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.util; 2 | 3 | /** 4 | * Created by allen on 2016/6/18. 5 | */ 6 | // Beacause of ZHihu API,some constant write here 7 | public class TopicData { 8 | 9 | public static String default_ids[] = {"beasleynation","Battlestar","bigertech","48zhen", 10 | "nordenbox","android-weekly","kaede","klexraxmusic","grapeot","dingj"}; 11 | 12 | public static String psychology_ids[] = {"gooseeker","happy","psychology","jiandanxinli", 13 | "lengai","career","aiqing","knowyourself"}; 14 | 15 | public static String life_talks_ids[] = {"zhangjiawei","hibetterme","maboyong","vczh-nichijou", 16 | "xiepanda","niceliving","uglypeoplefuckbooks","doubtsinreading","Wisdom","oh-hard"}; 17 | 18 | public static String photography_ids[] = {"kotandroid","Photography","he110world","ciciatc", 19 | "japan-talk","limiao","hikarifromtokyo","ethanlam","1chivnjapan"}; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/view/activity/AboutActivity.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.view.activity; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.design.widget.CollapsingToolbarLayout; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.Toolbar; 8 | import android.view.Menu; 9 | import android.view.MenuItem; 10 | 11 | import com.wu.allen.zhuanlan.R; 12 | 13 | public class AboutActivity extends BaseActivity { 14 | @Override 15 | protected void onCreate(@Nullable Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.activity_about); 18 | 19 | //Toolbar 20 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 21 | setSupportActionBar(toolbar); 22 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 23 | 24 | //CollapsingToolbarLayout 25 | CollapsingToolbarLayout collapsingToolbar = 26 | (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar); 27 | //collapsingToolbar.setTitle("关于"); 28 | 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/view/activity/ArticleDetailActivity.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.view.activity; 2 | 3 | import android.content.Intent; 4 | import android.graphics.Color; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.design.widget.CollapsingToolbarLayout; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.support.v7.widget.Toolbar; 10 | import android.util.Log; 11 | import android.view.Menu; 12 | import android.view.MenuItem; 13 | import android.view.View; 14 | import android.webkit.WebSettings; 15 | import android.webkit.WebView; 16 | import android.webkit.WebViewClient; 17 | import android.widget.ImageView; 18 | 19 | import com.afollestad.materialdialogs.MaterialDialog; 20 | import com.bumptech.glide.Glide; 21 | import com.wu.allen.zhuanlan.R; 22 | import com.wu.allen.zhuanlan.model.ArticleDetail; 23 | import com.wu.allen.zhuanlan.model.ZhuanLan; 24 | import com.wu.allen.zhuanlan.net.Network; 25 | import com.wu.allen.zhuanlan.util.ToastUtil; 26 | 27 | import rx.Subscriber; 28 | import rx.android.schedulers.AndroidSchedulers; 29 | import rx.schedulers.Schedulers; 30 | 31 | /** 32 | * Created by allen on 2016/6/18. 33 | */ 34 | public class ArticleDetailActivity extends BaseActivity { 35 | private static final String TAG = "ArticleDetailActivity"; 36 | private String titleImage,articleTitle; 37 | private int articleSlug; 38 | private ImageView imageView; 39 | private WebView webView; 40 | @Override 41 | protected void onCreate(@Nullable Bundle savedInstanceState) { 42 | super.onCreate(savedInstanceState); 43 | setContentView(R.layout.activity_articledetail); 44 | getData(); 45 | initView(); 46 | loadView(); 47 | } 48 | // InitView 49 | public void initView(){ 50 | webView = (WebView) findViewById(R.id.wb_main); 51 | imageView = (ImageView) findViewById(R.id.iv_article); 52 | Glide.with(ArticleDetailActivity.this) 53 | .load(titleImage) 54 | .centerCrop() 55 | .into(imageView); 56 | //Toolbar 57 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 58 | setSupportActionBar(toolbar); 59 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 60 | 61 | CollapsingToolbarLayout mCollapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.toolbar_layout); 62 | mCollapsingToolbarLayout.setTitle(articleTitle); 63 | mCollapsingToolbarLayout.setExpandedTitleColor(Color.WHITE);//设置还没收缩时状态下字体颜色 64 | mCollapsingToolbarLayout.setCollapsedTitleTextColor(Color.GREEN);//设置收缩后Toolbar上字体的颜色 65 | } 66 | // Get data from ZhanlanDetailActivity 67 | public void getData() { 68 | Intent intent = getIntent(); 69 | articleSlug = Integer.parseInt(intent.getStringExtra("slug")); 70 | articleTitle = intent.getStringExtra("title"); 71 | titleImage = intent.getStringExtra("image"); 72 | 73 | getArticleDetailData(articleSlug); 74 | } 75 | // reference is https://github.com/marktony/zhuanlan/blob/master/app/src/main/java/com/marktony/zhuanlan/ui/ReadActivity.java 76 | private void loadView() { 77 | webView.getSettings().setJavaScriptEnabled(true); 78 | webView.getSettings().setBuiltInZoomControls(false); 79 | webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); 80 | webView.getSettings().setDomStorageEnabled(true); 81 | webView.getSettings().setAppCacheEnabled(false); 82 | webView.setWebViewClient(new WebViewClient() { 83 | @Override 84 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 85 | webView.loadUrl(url); 86 | return true; 87 | } 88 | 89 | @Override 90 | public void onPageFinished(WebView view, String url) { 91 | super.onPageFinished(view, url); 92 | } 93 | }); 94 | } 95 | // Get Article Detail 96 | private void getArticleDetailData(int articleSlug) { 97 | Network.getArticleDetailApi() 98 | .getArticleDetail(articleSlug) 99 | .subscribeOn(Schedulers.io()) 100 | .observeOn(AndroidSchedulers.mainThread()) 101 | .subscribe(new Subscriber() { 102 | @Override 103 | public void onCompleted() { 104 | } 105 | 106 | @Override 107 | public void onError(Throwable e) { 108 | Log.e(TAG,e.getMessage()); 109 | 110 | } 111 | @Override 112 | public void onNext(ArticleDetail articleDetail) { 113 | Log.e(TAG,articleDetail.getTitle()); 114 | processData(articleDetail); 115 | } 116 | }); 117 | } 118 | // Return data of Content is HTML ,Process here and show with webView 119 | private void processData(ArticleDetail articleDetail) { 120 | String content = articleDetail.getContent(); 121 | String css = ""; 122 | String html = "\n" 123 | + "\n" 124 | + "\n" 125 | + "\t\n\n" 126 | + css 127 | + "\n" 128 | + content 129 | + "\n"; 130 | webView.loadData(html,"text/html; charset=UTF-8", null); 131 | } 132 | 133 | @Override 134 | public boolean onCreateOptionsMenu(Menu menu) { 135 | // Inflate the menu; this adds items to the action bar if it is present. 136 | getMenuInflater().inflate(R.menu.articledetail_menu_, menu); 137 | return true; 138 | } 139 | 140 | @Override 141 | public boolean onOptionsItemSelected(MenuItem item) { 142 | // Handle action bar item clicks here. The action bar will 143 | // automatically handle clicks on the Home/Up button, so long 144 | // as you specify a parent activity in AndroidManifest.xml. 145 | int id = item.getItemId(); 146 | 147 | //noinspection SimplifiableIfStatement 148 | if (id == R.id.action_post) { 149 | ToastUtil.showLong(this,"Post"); 150 | }else if (id == R.id.action_share){ 151 | //Intent intent = new Intent(getApplicationContext(),AboutActivity.class); 152 | //startActivity(intent); 153 | ToastUtil.showLong(this,"Share"); 154 | }else if (id == android.R.id.home){ 155 | onBackPressed(); 156 | } 157 | 158 | return super.onOptionsItemSelected(item); 159 | } 160 | 161 | 162 | 163 | } 164 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/view/activity/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.view.activity; 2 | 3 | import android.content.Intent; 4 | import android.os.Build; 5 | import android.os.Bundle; 6 | import android.os.PersistableBundle; 7 | import android.support.v4.content.ContextCompat; 8 | import android.support.v4.widget.SwipeRefreshLayout; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.view.Menu; 11 | import android.view.MenuItem; 12 | import android.view.View; 13 | import android.view.Window; 14 | import android.view.WindowManager; 15 | 16 | import com.afollestad.materialdialogs.MaterialDialog; 17 | import com.jude.easyrecyclerview.adapter.RecyclerArrayAdapter; 18 | import com.wu.allen.zhuanlan.R; 19 | import com.wu.allen.zhuanlan.util.ToastUtil; 20 | 21 | /** 22 | * Created by allen on 2016/6/14. 23 | */ 24 | public class BaseActivity extends AppCompatActivity implements SwipeRefreshLayout 25 | .OnRefreshListener,RecyclerArrayAdapter.OnLoadMoreListener{ 26 | 27 | @Override 28 | public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) { 29 | super.onCreate(savedInstanceState, persistentState); 30 | //initView(); 31 | } 32 | 33 | 34 | @Override 35 | public boolean onCreateOptionsMenu(Menu menu) { 36 | // Inflate the menu; this adds items to the action bar if it is present. 37 | getMenuInflater().inflate(R.menu.menu_main, menu); 38 | return true; 39 | } 40 | 41 | @Override 42 | public boolean onOptionsItemSelected(MenuItem item) { 43 | // Handle action bar item clicks here. The action bar will 44 | // automatically handle clicks on the Home/Up button, so long 45 | // as you specify a parent activity in AndroidManifest.xml. 46 | int id = item.getItemId(); 47 | 48 | //noinspection SimplifiableIfStatement 49 | if (id == R.id.action_settings) { 50 | new MaterialDialog.Builder(this) 51 | .title(R.string.dialog_title) 52 | .items(R.array.todo_item) 53 | .itemsCallback(new MaterialDialog.ListCallback() { 54 | @Override 55 | public void onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { 56 | } 57 | }) 58 | .show(); 59 | }else if (id == R.id.action_about){ 60 | Intent intent = new Intent(getApplicationContext(),AboutActivity.class); 61 | startActivity(intent); 62 | }else if (id == android.R.id.home){ 63 | onBackPressed(); 64 | } 65 | 66 | return super.onOptionsItemSelected(item); 67 | } 68 | 69 | 70 | @Override 71 | public void onRefresh() { 72 | 73 | } 74 | 75 | @Override 76 | public void onLoadMore() { 77 | 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/view/activity/GirlActivity.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.view.activity; 2 | 3 | import android.content.Intent; 4 | import android.graphics.Bitmap; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.design.widget.Snackbar; 8 | import android.support.v7.widget.Toolbar; 9 | import android.transition.Explode; 10 | import android.view.Menu; 11 | import android.view.MenuItem; 12 | import android.view.Window; 13 | import android.widget.ImageView; 14 | 15 | import com.bumptech.glide.Glide; 16 | import com.bumptech.glide.load.engine.DiskCacheStrategy; 17 | import com.bumptech.glide.request.animation.GlideAnimation; 18 | import com.bumptech.glide.request.target.SimpleTarget; 19 | import com.wu.allen.zhuanlan.R; 20 | import com.wu.allen.zhuanlan.model.Girl; 21 | import com.wu.allen.zhuanlan.util.ImgSaveUtil; 22 | import com.wu.allen.zhuanlan.util.ImgShareUtil; 23 | import com.wu.allen.zhuanlan.util.ToastUtil; 24 | 25 | import uk.co.senab.photoview.PhotoViewAttacher; 26 | 27 | /** 28 | * Created by allen on 2016/6/14. 29 | */ 30 | public class GirlActivity extends BaseActivity { 31 | 32 | private static final String TAG = "GirlActivity"; 33 | private Toolbar toolbar; 34 | private ImageView image; 35 | private String desc; 36 | private String url; 37 | private PhotoViewAttacher attacher; 38 | private Bitmap bitmap; 39 | 40 | @Override 41 | protected void onCreate(@Nullable Bundle savedInstanceState) { 42 | super.onCreate(savedInstanceState); 43 | beforeInitView(); 44 | setContentView(R.layout.girldetail_layout); 45 | getData(); 46 | initView(); 47 | } 48 | // Animated transitions 49 | public void beforeInitView(){ 50 | getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS); 51 | getWindow().setEnterTransition(new Explode()); 52 | getWindow().setExitTransition(new Explode()); 53 | } 54 | // Get data from MainFragment 55 | public void getData() { 56 | Intent intent = getIntent(); 57 | desc = intent.getStringExtra("desc"); 58 | url = intent.getStringExtra("url"); 59 | } 60 | // initView 61 | public void initView() { 62 | toolbar = (Toolbar) findViewById(R.id.toolbar); 63 | setSupportActionBar(toolbar); 64 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 65 | getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_arrow_white_24dp); 66 | getSupportActionBar().setTitle(desc); 67 | image = (ImageView) findViewById(R.id.image_meizhi); 68 | attacher = new PhotoViewAttacher(image); 69 | // Glide 70 | Glide.with(this) 71 | .load(url) 72 | .asBitmap() 73 | .diskCacheStrategy(DiskCacheStrategy.SOURCE) 74 | .into(new SimpleTarget() {// 这样写有什么好处? 75 | @Override 76 | public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) { 77 | image.setImageBitmap(resource); 78 | attacher.update(); 79 | bitmap = resource; 80 | } 81 | }); 82 | 83 | } 84 | 85 | @Override 86 | public boolean onCreateOptionsMenu(Menu menu) { 87 | // Inflate the menu; this adds items to the action bar if it is present. 88 | getMenuInflater().inflate(R.menu.girl_menu, menu); 89 | return true; 90 | } 91 | 92 | @Override 93 | public boolean onOptionsItemSelected(MenuItem item) { 94 | int id = item.getItemId(); 95 | 96 | //noinspection SimplifiableIfStatement 97 | if (id == R.id.action_save) { 98 | //ImgSaveUtil.saveImage(GirlActivity.this,url,bitmap,image,"saveImg"); 99 | //Snackbar.make(GirlActivity.this,"bug here in android 6.0",Snackbar.LENGTH_SHORT).show(); 100 | ToastUtil.showLong(this,"bug here in android 6.0"); 101 | }else if (id == R.id.action_share){ 102 | ImgShareUtil.shareImage(this,ImgSaveUtil.saveImage(this,url,bitmap,image,"share")); 103 | }else if (id == android.R.id.home){ 104 | onBackPressed(); 105 | } 106 | 107 | return super.onOptionsItemSelected(item); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/view/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.view.activity; 2 | 3 | 4 | import android.app.Fragment; 5 | import android.os.Bundle; 6 | import android.support.design.widget.TabLayout; 7 | import android.support.v13.app.FragmentPagerAdapter; 8 | import android.support.v4.view.ViewPager; 9 | import android.support.v7.widget.Toolbar; 10 | 11 | import com.nispok.snackbar.Snackbar; 12 | import com.nispok.snackbar.SnackbarManager; 13 | import com.nispok.snackbar.listeners.ActionClickListener; 14 | import com.wu.allen.zhuanlan.R; 15 | import com.wu.allen.zhuanlan.util.NetWorkUtil; 16 | import com.wu.allen.zhuanlan.view.fragment.GirlFragment; 17 | import com.wu.allen.zhuanlan.view.fragment.ZhihuFragment; 18 | 19 | import java.util.List; 20 | 21 | public class MainActivity extends BaseActivity { 22 | private static final String TAG = "MainActivity"; 23 | private TabLayout tabLayout; 24 | private ViewPager viewPager; 25 | private List fragments; 26 | private String[] titles = {"专栏","妹纸"}; 27 | 28 | @Override 29 | protected void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | setContentView(R.layout.activity_main); 32 | initView(); 33 | switchToFragmnent(); 34 | isNetOk(); 35 | } 36 | // Judge network is ok 37 | public void isNetOk(){ 38 | if(!NetWorkUtil.isNetworkConnected(getApplicationContext())){ 39 | SnackbarManager.show( 40 | Snackbar.with(getApplicationContext()) // context 41 | .text("没有网络==") // text to display 42 | .actionLabel("重试?") // action button label 43 | .actionListener(new ActionClickListener() { 44 | @Override 45 | public void onActionClicked(Snackbar snackbar) { 46 | isNetOk(); 47 | } 48 | }) // action button's ActionClickListener 49 | , this); 50 | } 51 | } 52 | // initView 53 | public void initView(){ 54 | //Toolbar 55 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 56 | tabLayout = (TabLayout) findViewById(R.id.tab_layout); 57 | setSupportActionBar(toolbar); 58 | } 59 | // switch fragment 60 | public void switchToFragmnent() { 61 | 62 | viewPager = (ViewPager) findViewById(R.id.view_pager); 63 | viewPager.setAdapter(new FragmentPagerAdapter(getFragmentManager()) { 64 | @Override 65 | public Fragment getItem(int position) { 66 | switch (position) { 67 | case 0: 68 | return new ZhihuFragment(); 69 | case 1: 70 | return new GirlFragment(); 71 | default: 72 | return new ZhihuFragment(); 73 | } 74 | } 75 | 76 | @Override 77 | public int getCount() { 78 | return 2; 79 | } 80 | 81 | @Override 82 | public CharSequence getPageTitle(int position) { 83 | switch (position) { 84 | case 0: 85 | return getString(R.string.zhihu); 86 | case 1: 87 | return getString(R.string.girl); 88 | default: 89 | return getString(R.string.zhihu); 90 | } 91 | } 92 | }); 93 | 94 | tabLayout.setupWithViewPager(viewPager); 95 | } 96 | 97 | @Override 98 | protected void onDestroy() { 99 | super.onDestroy(); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/view/activity/ZhlanDetailActivity.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.view.activity; 2 | 3 | import android.app.ActivityOptions; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.os.Handler; 7 | import android.support.annotation.Nullable; 8 | import android.support.design.widget.CollapsingToolbarLayout; 9 | import android.support.v4.widget.SwipeRefreshLayout; 10 | import android.support.v7.widget.LinearLayoutManager; 11 | import android.support.v7.widget.Toolbar; 12 | import android.transition.Explode; 13 | import android.transition.Fade; 14 | import android.util.Log; 15 | import android.view.MenuItem; 16 | import android.view.View; 17 | import android.view.Window; 18 | 19 | import com.afollestad.materialdialogs.MaterialDialog; 20 | import com.jude.easyrecyclerview.EasyRecyclerView; 21 | import com.jude.easyrecyclerview.adapter.RecyclerArrayAdapter; 22 | import com.wu.allen.zhuanlan.R; 23 | import com.wu.allen.zhuanlan.adapter.ArticleAdapter; 24 | import com.wu.allen.zhuanlan.model.Article; 25 | import com.wu.allen.zhuanlan.net.Network; 26 | import com.wu.allen.zhuanlan.util.ToastUtil; 27 | 28 | import java.util.ArrayList; 29 | import java.util.List; 30 | 31 | import rx.Subscriber; 32 | import rx.android.schedulers.AndroidSchedulers; 33 | import rx.schedulers.Schedulers; 34 | 35 | /** 36 | * Created by allen on 2016/6/14. 37 | */ 38 | public class ZhlanDetailActivity extends BaseActivity { 39 | 40 | private static final String TAG = "ZhlanDetailActivity"; 41 | private Toolbar toolbar; 42 | private String slug,title,name,articeSlug,titleImage,profileUrl; 43 | private EasyRecyclerView recyclerView; 44 | private List

articleList; 45 | private ArticleAdapter articleAdapter; 46 | private Handler handler = new Handler(); 47 | private int page = 1; 48 | private CollapsingToolbarLayout toolbarLayout; 49 | 50 | @Override 51 | protected void onCreate(@Nullable Bundle savedInstanceState) { 52 | super.onCreate(savedInstanceState); 53 | beforeInitView(); 54 | setContentView(R.layout.zhuanlan_layout); 55 | getData(); 56 | initView(); 57 | } 58 | // Animated transitions 59 | public void beforeInitView(){ 60 | getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS); 61 | getWindow().setEnterTransition(new Explode()); 62 | getWindow().setExitTransition(new Explode()); 63 | } 64 | // Get data from MainFragment 65 | public void getData() { 66 | Intent intent = getIntent(); 67 | slug = intent.getStringExtra("slug"); 68 | title = intent.getStringExtra("title"); 69 | profileUrl = intent.getStringExtra("profile_url"); 70 | } 71 | // initView 72 | public void initView() { 73 | articleList = new ArrayList<>(); 74 | recyclerView = (EasyRecyclerView) findViewById(R.id.recycler_view); 75 | toolbar = (Toolbar) findViewById(R.id.toolbar); 76 | setSupportActionBar(toolbar); 77 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 78 | getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_arrow_white_24dp); 79 | getSupportActionBar().setTitle(slug); 80 | LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getApplicationContext()); 81 | recyclerView.setLayoutManager(linearLayoutManager); 82 | articleAdapter = new ArticleAdapter(getApplicationContext()); 83 | doWithArticle(articleAdapter); 84 | recyclerView.setRefreshListener(ZhlanDetailActivity.this); 85 | onRefresh(); 86 | } 87 | // here can intent to ArticleDetailActivity 88 | private void doWithArticle(final RecyclerArrayAdapter
adapter) { 89 | recyclerView.setAdapterWithProgress(adapter); 90 | adapter.setMore(R.layout.load_more_layout,this); 91 | adapter.setNoMore(R.layout.no_more_layout); 92 | adapter.setError(R.layout.error_layout); 93 | adapter.setOnItemClickListener(new RecyclerArrayAdapter.OnItemClickListener() { 94 | @Override 95 | public void onItemClick(int position) { 96 | Intent intent = new Intent(ZhlanDetailActivity.this,ArticleDetailActivity.class); 97 | intent.putExtra("slug",adapter.getItem(position).getArticleSlug()); 98 | intent.putExtra("image",adapter.getItem(position).getTitleImage()); 99 | intent.putExtra("title",adapter.getItem(position).getTitle()); 100 | startActivity(intent); 101 | } 102 | }); 103 | } 104 | // Get Article List with the zhuanlan 105 | private void getArticleData(String slug,int limit,int offest) { 106 | Network.getZhLanArticleApi() 107 | .getArticle(slug,limit,offest) 108 | .subscribeOn(Schedulers.io()) 109 | .observeOn(AndroidSchedulers.mainThread()) 110 | .subscribe(new Subscriber>() { 111 | @Override 112 | public void onCompleted() { 113 | } 114 | 115 | @Override 116 | public void onError(Throwable e) { 117 | Log.e(TAG,e.getMessage()); 118 | } 119 | @Override 120 | public void onNext(List
article) { 121 | ProcessData(article); 122 | articleAdapter.addAll(articleList); 123 | } 124 | }); 125 | } 126 | // process data so that can be posted to ArticleAdapter 127 | private List
ProcessData(List
article) { 128 | for (int i = 0; i < article.size(); i++) { 129 | String title = article.get(i).getTitle(); 130 | String content = article.get(i).getContent(); 131 | titleImage = article.get(i).getTitleImage(); 132 | // no data why? 133 | String sumary = article.get(i).getSummary(); 134 | int commentsCount = article.get(i).getCommentsCount(); 135 | // no use 136 | int likesCount =article.get(i).getLikeCount(); 137 | articeSlug = article.get(i).getSlug(); 138 | Article articleItem = new Article(title,titleImage,content 139 | ,sumary,commentsCount,likesCount,slug,articeSlug,profileUrl); 140 | articleList.add(articleItem); 141 | } 142 | return articleList; 143 | } 144 | 145 | @Override 146 | public void onLoadMore() { 147 | handler.postDelayed(new Runnable() { 148 | @Override 149 | public void run() { 150 | // bug here 151 | getArticleData(slug,page*5,0); 152 | page++; 153 | } 154 | }, 1000); 155 | } 156 | 157 | @Override 158 | public void onRefresh() { 159 | articleAdapter.clear(); 160 | getArticleData(slug,5,0); 161 | } 162 | 163 | // override 164 | 165 | @Override 166 | public boolean onOptionsItemSelected(MenuItem item) { 167 | // Handle action bar item clicks here. The action bar will 168 | // automatically handle clicks on the Home/Up button, so long 169 | // as you specify a parent activity in AndroidManifest.xml. 170 | int id = item.getItemId(); 171 | 172 | //noinspection SimplifiableIfStatement 173 | if (id == R.id.action_share) { 174 | ToastUtil.showLong(getApplicationContext(),"wait me"); 175 | }else if (id == android.R.id.home){ 176 | onBackPressed(); 177 | } 178 | 179 | return super.onOptionsItemSelected(item); 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/view/fragment/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.view.fragment; 2 | 3 | import android.app.Fragment; 4 | import android.support.v4.widget.SwipeRefreshLayout; 5 | 6 | import com.jude.easyrecyclerview.adapter.RecyclerArrayAdapter; 7 | 8 | import rx.Subscription; 9 | 10 | /** 11 | * Created by allen on 2016/6/17. 12 | */ 13 | public class BaseFragment extends Fragment implements SwipeRefreshLayout 14 | .OnRefreshListener,RecyclerArrayAdapter.OnLoadMoreListener{ 15 | protected Subscription subscription; 16 | 17 | 18 | 19 | @Override 20 | public void onDestroyView() { 21 | super.onDestroyView(); 22 | unsubscribe(); 23 | } 24 | 25 | protected void unsubscribe() { 26 | if (subscription != null && !subscription.isUnsubscribed()) { 27 | subscription.unsubscribe(); 28 | } 29 | } 30 | 31 | @Override 32 | public void onRefresh() { 33 | 34 | } 35 | 36 | @Override 37 | public void onLoadMore() { 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/view/fragment/GirlFragment.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.view.fragment; 2 | 3 | import android.app.ActivityOptions; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.os.Handler; 7 | import android.support.annotation.Nullable; 8 | import android.support.v7.widget.DefaultItemAnimator; 9 | import android.support.v7.widget.StaggeredGridLayoutManager; 10 | import android.util.Log; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | 15 | import com.jude.easyrecyclerview.EasyRecyclerView; 16 | import com.jude.easyrecyclerview.adapter.RecyclerArrayAdapter; 17 | import com.jude.easyrecyclerview.decoration.SpaceDecoration; 18 | import com.wu.allen.zhuanlan.R; 19 | import com.wu.allen.zhuanlan.adapter.GirlAdapter; 20 | import com.wu.allen.zhuanlan.cache.Data; 21 | import com.wu.allen.zhuanlan.model.Item; 22 | import com.wu.allen.zhuanlan.util.Dp2PxUtil; 23 | import com.wu.allen.zhuanlan.view.activity.GirlActivity; 24 | 25 | import java.util.List; 26 | 27 | import rx.Observer; 28 | import rx.Subscription; 29 | 30 | /** 31 | * Created by allen on 2016/6/17. 32 | */ 33 | public class GirlFragment extends BaseFragment { 34 | 35 | private static final String TAG = "GirlFragment"; 36 | protected Subscription subscription; 37 | private EasyRecyclerView recyclerView; 38 | private GirlAdapter girlAdapter; 39 | private String title; 40 | private int page = 1; 41 | private Handler handler = new Handler(); 42 | private String[] ids; 43 | private String msgTopic; 44 | 45 | @Nullable 46 | @Override 47 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 48 | View view = inflater.inflate(R.layout.fragment_girl_layout, container, false); 49 | initView(view); 50 | return view; 51 | } 52 | // initView 53 | public void initView(View view){ 54 | recyclerView = (EasyRecyclerView) view.findViewById(R.id.recycler_view); 55 | StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL); 56 | recyclerView.setLayoutManager(staggeredGridLayoutManager); 57 | recyclerView.setItemAnimator(new DefaultItemAnimator()); 58 | girlAdapter = new GirlAdapter(getActivity()); 59 | doWithGirl(girlAdapter); 60 | // for itemDecoration 61 | SpaceDecoration itemDecoration = new SpaceDecoration((int) Dp2PxUtil.convertDpToPixel(8,getActivity())); 62 | itemDecoration.setPaddingEdgeSide(true); 63 | itemDecoration.setPaddingStart(true); 64 | itemDecoration.setPaddingHeaderFooter(false); 65 | recyclerView.addItemDecoration(itemDecoration); 66 | recyclerView.setRefreshListener(this); 67 | // load data while init 68 | getGirlData(page); 69 | } 70 | // can intent to GirlActivity here 71 | private void doWithGirl(final RecyclerArrayAdapter adapter) { 72 | recyclerView.setAdapterWithProgress(adapter); 73 | adapter.setMore(R.layout.load_more_layout,this); 74 | adapter.setNoMore(R.layout.no_more_layout); 75 | adapter.setError(R.layout.error_layout); 76 | adapter.setOnItemClickListener(new RecyclerArrayAdapter.OnItemClickListener() { 77 | @Override 78 | public void onItemClick(int position) { 79 | 80 | Intent intent = new Intent(getActivity(), GirlActivity.class); 81 | intent.putExtra("desc",adapter.getItem(position).description); 82 | intent.putExtra("url",adapter.getItem(position).imageUrl); 83 | startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(getActivity()).toBundle()); 84 | } 85 | }); 86 | } 87 | // Here get data from cache or net 88 | private void getGirlData(int page) { 89 | unsubscribe(); 90 | subscription = Data.getInstance() 91 | .subscribeData(new Observer>() { 92 | @Override 93 | public void onCompleted() {} 94 | 95 | @Override 96 | public void onError(Throwable e) { 97 | Log.e(TAG,e.getMessage()); 98 | } 99 | 100 | @Override 101 | public void onNext(List items) { 102 | girlAdapter.addAll(items); 103 | } 104 | },page); 105 | } 106 | // Load and Refresh data 107 | @Override 108 | public void onRefresh() { 109 | handler.postDelayed(new Runnable() { 110 | @Override 111 | public void run() { 112 | // before refresh clean cache 113 | girlAdapter.clear(); 114 | Data.getInstance().clearMemoryAndDiskCache(); 115 | getGirlData(page); 116 | 117 | } 118 | }, 1000); 119 | } 120 | 121 | @Override 122 | public void onLoadMore() { 123 | handler.postDelayed(new Runnable() { 124 | @Override 125 | public void run() { 126 | // before load more ,clean cache 127 | Data.getInstance().clearMemoryAndDiskCache(); 128 | page ++; 129 | getGirlData(page); 130 | } 131 | }, 1000); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/view/fragment/ZhihuFragment.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.view.fragment; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorInflater; 5 | import android.app.ActivityOptions; 6 | import android.content.Intent; 7 | import android.os.Bundle; 8 | import android.os.Handler; 9 | import android.support.annotation.Nullable; 10 | import android.support.design.widget.FloatingActionButton; 11 | import android.support.v7.widget.DefaultItemAnimator; 12 | import android.support.v7.widget.LinearLayoutManager; 13 | import android.util.Log; 14 | import android.view.LayoutInflater; 15 | import android.view.View; 16 | import android.view.ViewGroup; 17 | import android.view.animation.CycleInterpolator; 18 | import android.widget.LinearLayout; 19 | 20 | import com.jude.easyrecyclerview.EasyRecyclerView; 21 | import com.jude.easyrecyclerview.adapter.RecyclerArrayAdapter; 22 | import com.wu.allen.zhuanlan.R; 23 | import com.wu.allen.zhuanlan.adapter.ZhuanLanAdapter; 24 | import com.wu.allen.zhuanlan.model.ZhuanLan; 25 | import com.wu.allen.zhuanlan.net.Network; 26 | import com.wu.allen.zhuanlan.util.NetWorkUtil; 27 | import com.wu.allen.zhuanlan.util.TopicData; 28 | import com.wu.allen.zhuanlan.view.activity.ZhlanDetailActivity; 29 | import java.util.List; 30 | import java.util.Random; 31 | 32 | import rx.Subscriber; 33 | import rx.android.schedulers.AndroidSchedulers; 34 | import rx.schedulers.Schedulers; 35 | 36 | /** 37 | * Created by allen on 2016/6/17. 38 | */ 39 | public class ZhihuFragment extends BaseFragment { 40 | 41 | private static final String TAG = "ZhihuFragment"; 42 | private EasyRecyclerView recyclerView; 43 | private List zhuanLanList; 44 | private LinearLayout noNetLayout; 45 | private ZhuanLanAdapter zhuanLanAdapter; 46 | private String title; 47 | private int page = 1; 48 | private Handler handler = new Handler(); 49 | private String[] ids; 50 | private String[] TopicIds = {"TopicData.music_film_ids","TopicData.life_talks_ids", 51 | "TopicData.photography_ids","TopicData.photography_ids"}; 52 | private String msgTopic; 53 | 54 | @Override 55 | public void onCreate(Bundle savedInstanceState) { 56 | super.onCreate(savedInstanceState); 57 | ids = TopicData.default_ids; 58 | 59 | } 60 | 61 | @Nullable 62 | @Override 63 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 64 | View view = inflater.inflate(R.layout.fragment_zhuanlan_layout, container, false); 65 | initView(view); 66 | if(!NetWorkUtil.isNetworkConnected(getActivity())){ 67 | noNetLayout.setVisibility(View.VISIBLE); 68 | } 69 | 70 | return view; 71 | } 72 | // initView 73 | public void initView(View view){ 74 | 75 | final FloatingActionButton fab = (FloatingActionButton)view.findViewById(R.id.fab); 76 | fab.setOnClickListener(new View.OnClickListener() { 77 | @Override 78 | public void onClick(View view) { 79 | Animator animator = AnimatorInflater.loadAnimator(getActivity(), R.animator.animator_rotation); 80 | animator.setTarget(fab); 81 | animator.setInterpolator(new CycleInterpolator(2)); 82 | animator.start(); 83 | // bug here still 84 | changeTopic(); 85 | onRefresh(); 86 | } 87 | }); 88 | 89 | noNetLayout = (LinearLayout) view.findViewById(R.id.no_network); 90 | recyclerView = (EasyRecyclerView) view.findViewById(R.id.recycler_view); 91 | LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity()); 92 | recyclerView.setItemAnimator(new DefaultItemAnimator()); 93 | recyclerView.setLayoutManager(linearLayoutManager); 94 | zhuanLanAdapter = new ZhuanLanAdapter(getActivity()); 95 | doWithZhuanLan(zhuanLanAdapter); 96 | recyclerView.setRefreshListener(this); 97 | onRefresh(); 98 | } 99 | // Because of Api , there is a Skill 100 | private void changeTopic() { 101 | int max=3; 102 | int min=0; 103 | Random random = new Random(); 104 | int s = random.nextInt(max)%(max-min+1) + min; 105 | 106 | ids = TopicData.life_talks_ids; 107 | } 108 | // Intent to ZhlanDetailActivity 109 | private void doWithZhuanLan(final RecyclerArrayAdapter adapter) { 110 | recyclerView.setAdapterWithProgress(adapter); 111 | adapter.setMore(R.layout.load_more_layout,this); 112 | adapter.setNoMore(R.layout.no_more_layout); 113 | adapter.setError(R.layout.error_layout); 114 | adapter.setOnItemClickListener(new RecyclerArrayAdapter.OnItemClickListener() { 115 | @Override 116 | public void onItemClick(int position) { 117 | Intent intent = new Intent(getActivity(),ZhlanDetailActivity.class); 118 | intent.putExtra("slug",adapter.getItem(position).getSlug()); 119 | intent.putExtra("title",adapter.getItem(position).getName()); 120 | intent.putExtra("profile_url",adapter.getItem(position).getCreator().getProfileUrl()); 121 | startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(getActivity()).toBundle()); 122 | } 123 | }); 124 | } 125 | 126 | // Get Zhuanlan Author List 127 | private void getZhuanLanData(String zhuanlanname) { 128 | Network.getZhuanLanApi() 129 | .getZhuanLan(zhuanlanname) 130 | .subscribeOn(Schedulers.io()) 131 | .observeOn(AndroidSchedulers.mainThread()) 132 | .subscribe(new Subscriber() { 133 | @Override 134 | public void onCompleted() { 135 | Log.e(TAG,"onCompleted"); 136 | } 137 | 138 | @Override 139 | public void onError(Throwable e) { 140 | Log.e(TAG,e.getMessage()); 141 | 142 | } 143 | @Override 144 | public void onNext(ZhuanLan zhuanLan) { 145 | Log.e(TAG,"OnNext"); 146 | zhuanLanAdapter.add(zhuanLan); 147 | } 148 | }); 149 | } 150 | 151 | @Override 152 | public void onRefresh() { 153 | handler.postDelayed(new Runnable() { 154 | @Override 155 | public void run() { 156 | zhuanLanAdapter.clear(); 157 | for (int i = 0;i < ids.length; i++) { 158 | getZhuanLanData(ids[i]); 159 | } 160 | } 161 | }, 1000); 162 | } 163 | 164 | @Override 165 | public void onLoadMore() { 166 | handler.postDelayed(new Runnable() { 167 | @Override 168 | public void run() { 169 | ids = TopicData.photography_ids; 170 | //zhuanLanAdapter.clear(); 171 | for (int i = 0;i < ids.length; i++) { 172 | getZhuanLanData(ids[i]); 173 | } 174 | } 175 | }, 1000); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /app/src/main/java/com/wu/allen/zhuanlan/widget/ScrollAwareFABBehavior.java: -------------------------------------------------------------------------------- 1 | package com.wu.allen.zhuanlan.widget; 2 | 3 | import android.content.Context; 4 | import android.os.Build; 5 | import android.support.design.widget.CoordinatorLayout; 6 | import android.support.design.widget.FloatingActionButton; 7 | import android.support.v4.view.ViewCompat; 8 | import android.support.v4.view.ViewPropertyAnimatorListener; 9 | import android.support.v4.view.animation.FastOutSlowInInterpolator; 10 | import android.util.AttributeSet; 11 | import android.view.View; 12 | import android.view.animation.Animation; 13 | import android.view.animation.AnimationUtils; 14 | import android.view.animation.Interpolator; 15 | 16 | import com.wu.allen.zhuanlan.R; 17 | 18 | /** 19 | * Created by allen on 2016/6/15. 20 | */ 21 | public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior { 22 | private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator(); 23 | private boolean mIsAnimatingOut = false; 24 | 25 | public ScrollAwareFABBehavior(Context context, AttributeSet attrs) { 26 | super(); 27 | } 28 | 29 | @Override 30 | public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child, 31 | final View directTargetChild, final View target, final int nestedScrollAxes) { 32 | // Ensure we react to vertical scrolling 33 | return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL 34 | || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); 35 | } 36 | 37 | @Override 38 | public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child, 39 | final View target, final int dxConsumed, final int dyConsumed, 40 | final int dxUnconsumed, final int dyUnconsumed) { 41 | super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); 42 | if (dyConsumed > 0 && !this.mIsAnimatingOut && child.getVisibility() == View.VISIBLE) { 43 | // User scrolled down and the FAB is currently visible -> hide the FAB 44 | animateOut(child); 45 | } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) { 46 | // User scrolled up and the FAB is currently not visible -> show the FAB 47 | animateIn(child); 48 | } 49 | } 50 | 51 | // Same animation that FloatingActionButton.Behavior uses to hide the FAB when the AppBarLayout exits 52 | private void animateOut(final FloatingActionButton button) { 53 | if (Build.VERSION.SDK_INT >= 14) { 54 | ViewCompat.animate(button).scaleX(0.0F).scaleY(0.0F).alpha(0.0F).setInterpolator(INTERPOLATOR).withLayer() 55 | .setListener(new ViewPropertyAnimatorListener() { 56 | public void onAnimationStart(View view) { 57 | ScrollAwareFABBehavior.this.mIsAnimatingOut = true; 58 | } 59 | 60 | public void onAnimationCancel(View view) { 61 | ScrollAwareFABBehavior.this.mIsAnimatingOut = false; 62 | } 63 | 64 | public void onAnimationEnd(View view) { 65 | ScrollAwareFABBehavior.this.mIsAnimatingOut = false; 66 | view.setVisibility(View.GONE); 67 | } 68 | }).start(); 69 | } else { 70 | Animation anim = AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_out); 71 | anim.setInterpolator(INTERPOLATOR); 72 | anim.setDuration(200L); 73 | anim.setAnimationListener(new Animation.AnimationListener() { 74 | public void onAnimationStart(Animation animation) { 75 | ScrollAwareFABBehavior.this.mIsAnimatingOut = true; 76 | } 77 | 78 | public void onAnimationEnd(Animation animation) { 79 | ScrollAwareFABBehavior.this.mIsAnimatingOut = false; 80 | button.setVisibility(View.GONE); 81 | } 82 | 83 | @Override 84 | public void onAnimationRepeat(final Animation animation) { 85 | } 86 | }); 87 | button.startAnimation(anim); 88 | } 89 | } 90 | 91 | // Same animation that FloatingActionButton.Behavior uses to show the FAB when the AppBarLayout enters 92 | private void animateIn(FloatingActionButton button) { 93 | button.setVisibility(View.VISIBLE); 94 | if (Build.VERSION.SDK_INT >= 14) { 95 | ViewCompat.animate(button).scaleX(1.0F).scaleY(1.0F).alpha(1.0F) 96 | .setInterpolator(INTERPOLATOR).withLayer().setListener(null) 97 | .start(); 98 | } else { 99 | Animation anim = AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_in); 100 | anim.setDuration(200L); 101 | anim.setInterpolator(INTERPOLATOR); 102 | button.startAnimation(anim); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fab_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 22 | 23 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fab_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 22 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/anim/rotate.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/animator/animator_rotation.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/about_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuchangfeng/zhuanlan/62eac2d1af5a25e5df5fe448d9f13ab2f11a59f0/app/src/main/res/drawable/about_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/aboutbg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuchangfeng/zhuanlan/62eac2d1af5a25e5df5fe448d9f13ab2f11a59f0/app/src/main/res/drawable/aboutbg.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/change.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuchangfeng/zhuanlan/62eac2d1af5a25e5df5fe448d9f13ab2f11a59f0/app/src/main/res/drawable/change.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/error.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuchangfeng/zhuanlan/62eac2d1af5a25e5df5fe448d9f13ab2f11a59f0/app/src/main/res/drawable/error.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_find_replace_black_24dp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuchangfeng/zhuanlan/62eac2d1af5a25e5df5fe448d9f13ab2f11a59f0/app/src/main/res/drawable/ic_find_replace_black_24dp.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuchangfeng/zhuanlan/62eac2d1af5a25e5df5fe448d9f13ab2f11a59f0/app/src/main/res/drawable/logo.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/read.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuchangfeng/zhuanlan/62eac2d1af5a25e5df5fe448d9f13ab2f11a59f0/app/src/main/res/drawable/read.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 25 | 26 | 32 | 33 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 58 | 59 | 65 | 66 | 77 | 78 | 86 | 87 | 88 | 96 | 97 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_articledetail.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 24 | 25 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 48 | 49 | 53 | 54 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 15 | 16 | 23 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/error_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_girl_layout.xml: -------------------------------------------------------------------------------- 1 | 8 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_zhuanlan_layout.xml: -------------------------------------------------------------------------------- 1 | 8 | 15 | 16 | 23 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 53 | 54 | -------------------------------------------------------------------------------- /app/src/main/res/layout/girl_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/girldetail_layout.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 21 | 22 | 23 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/load_more_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/no_more_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/progress_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/zhuanlan_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 24 | 25 | 33 | 41 | 49 | 50 | 55 | 56 | 61 | 62 | 68 | 69 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /app/src/main/res/layout/zhuanlan_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/zhuanlanpost_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 17 | 18 | 22 | 23 | 33 | 34 | 43 | 44 | 45 | 50 | 51 | 58 | 59 | 67 | 68 | 69 | 70 | 71 | 72 | 78 | 79 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /app/src/main/res/menu/articledetail_menu_.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/menu/girl_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuchangfeng/zhuanlan/62eac2d1af5a25e5df5fe448d9f13ab2f11a59f0/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuchangfeng/zhuanlan/62eac2d1af5a25e5df5fe448d9f13ab2f11a59f0/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuchangfeng/zhuanlan/62eac2d1af5a25e5df5fe448d9f13ab2f11a59f0/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuchangfeng/zhuanlan/62eac2d1af5a25e5df5fe448d9f13ab2f11a59f0/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuchangfeng/zhuanlan/62eac2d1af5a25e5df5fe448d9f13ab2f11a59f0/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-v19: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuchangfeng/zhuanlan/62eac2d1af5a25e5df5fe448d9f13ab2f11a59f0/app/src/main/res/values-v19 -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 专栏 3 | 设置 4 | 关于 5 | 专栏 6 | 妹子 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/array.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 模式切换 5 | 缓存清理 6 | 专栏内容加上缓存 7 | 专栏文章评论接口的实现 8 | 专栏文章作者圆角头像的实现 9 | 专栏文章分享功能的实现 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #202529 4 | #202529 5 | #202529 6 | #202529 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 16dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 专栏 3 | ToDo 4 | About 5 | 专栏 6 | 妹子 7 | 8 | 保存 9 | 分享 10 | 评论、 11 | ToDo 12 | 13 | ZhuanLan Meizhi\nGitHub:https://github.com/wuchangfeng/ZhuanLan 14 | 个人一个练手 App,主要学习 RxJava 和 Retrofit 以及基于 RxJava 的缓存技术以及一些常用开源库的使用。\n 15 | App 中的知乎专栏 API 为并非由知乎官方提供,侵权删。妹子图的 API 来自 Gank.io。\n感谢。 16 | 开源库:\nrxjava\nretrofit\nokhttp\nPhotoView\nglide\neasyrecyclerview\ncom.nispok:snackbar\nom.afollestad.material-dialogs 17 | 18 | 专栏内容的缓存还在进行中ing Orz 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 11 | 12 | 13 | 19 | 20 | 28 | 29 |