├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── jc │ │ └── mvvmrxjavaretrofitsample │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── jc │ │ │ └── mvvmrxjavaretrofitsample │ │ │ ├── model │ │ │ ├── data │ │ │ │ ├── DouBanMovieService.java │ │ │ │ └── RetrofitHelper.java │ │ │ └── entity │ │ │ │ ├── Movie.java │ │ │ │ └── Response.java │ │ │ ├── view │ │ │ ├── CompletedListener.java │ │ │ ├── MainActivity.java │ │ │ ├── MovieAdapter.java │ │ │ └── MovieFragment.java │ │ │ └── viewModel │ │ │ ├── MainViewModel.java │ │ │ └── MovieViewModel.java │ └── res │ │ ├── drawable │ │ ├── cover.jpg │ │ └── rating_bar_bg.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── movie_fragment.xml │ │ └── movie_item.xml │ │ ├── menu │ │ └── menu_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── rating_small_empty.png │ │ ├── rating_small_full.png │ │ └── rating_small_half.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── jc │ └── mvvmrxjavaretrofitsample │ └── ExampleUnitTest.java ├── build.gradle ├── demo.apk ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── image └── sample.gif └── 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/.name: -------------------------------------------------------------------------------- 1 | MVVMRxJavaRetrofitSample -------------------------------------------------------------------------------- /.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 | 23 | -------------------------------------------------------------------------------- /.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 | 12 | 13 | 14 | 26 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 53 | 54 | 55 | 56 | 57 | 1.8 58 | 59 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Sample 简介 2 | 3 | 一个简单的结合 Retrofit 和 RxJava 框架实现 MVVM 架构的例子。 4 | 5 | 最近在研究 [Kotlin](https://github.com/githubhaohao/JavaToKotlin) for Android,做了一个基于 Clean 架构以及 Retrofit , RxKotlin , Dagger 框架实现的 Kotlin for Android App ,更多详情请[戳这里](https://github.com/githubhaohao/DoubanBook)。 6 | 7 | ## 效果预览 8 | 9 | ![result](https://github.com/githubhaohao/MVVMRxJavaRetrofitSample/blob/master/image/sample.gif?raw=true) 10 | 11 | [Demo 下载](https://github.com/githubhaohao/MVVMRxJavaRetrofitSample/blob/master/demo.apk) 12 | 13 | ## 准备知识 14 | ### MVC 15 | ![mvc](https://github.com/githubhaohao/ImageRoom/blob/master/Images/mvvm/mvc.PNG?raw=true) 16 | 17 | - **视图(View)**:用户界面。 18 | - **控制器(Controller)**:业务逻辑 19 | - **模型(Model)**:数据保存 20 | 21 | --- 22 | 1. View 传送指令到 Controller 23 | 2. Controller 完成业务逻辑后,要求 Model 改变状态 24 | 3. Model 将新的数据发送到 View,使用户得到反馈 25 | 26 | **缺陷**:View 和 Model 是相互可知,耦合性大,像 Activity 或者 Fragment 既是 Controller 层,又是 View 层,造成工程的可扩展性可维护性非常差。 27 | 28 | ### MVP 29 | ![mvp](https://github.com/githubhaohao/ImageRoom/blob/master/Images/mvvm/mvp.png?raw=true) 30 | 31 | 在 MVP 设计架构中,Controller 变成了 Presenter。 32 | 33 | 1. 各层之间的通信,都是双向的。 34 | 2. View 与 Model 不直接发生联系,都通过 Presenter 进行间接通信。 35 | 3. Model 层与 Presenter 层,Presenter 层与 View 层之间通过接口建立联系。 36 | 37 | 采用 MVP 设计架构,Activity 与 Fragment 只位于 View 层。 38 | 39 | **MVP 的缺陷在于**:由于我们使用了接口的方式去连接 View 层和 Presenter 层,这样就导致了一个问题,当你的页面逻辑很复杂的时候,你的接口会有很多,如果你的 app 中有很多个这样复杂的页面,维护接口的成本就会变的非常的大。 40 | 41 | ### MVVM 42 | ![MVVM](https://github.com/githubhaohao/ImageRoom/blob/master/Images/mvvm/mvvp.PNG?raw=true) 43 | 44 | MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。 45 | **区别在于**: View 层与 ViewModel 层通过`DataBinding`相互绑定,View的变动,自动反映在 ViewModel,反之亦然。 46 | 47 | ### [RxJava](https://github.com/ReactiveX/RxJava ) 48 | 49 | RxJava 在 GitHub 主页上的自我介绍是 "a library for composing asynchronous and event-based programs using observable sequences for the Java VM"(一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库)。 50 | 51 | RxJava 本质上是一个异步操作库,是一个能让你用极其简洁的逻辑去处理繁琐复杂任务的异步事件库。 52 | 53 | 简而言之,RxJava 可以用几个关键字概括:**简洁**,**队列化**,**异步**。 54 | 55 | ### [Retrofit](https://github.com/square/retrofit) 56 | 57 | ![retrofit](https://github.com/githubhaohao/ImageRoom/blob/master/Images/mvvm/android-libs-retrofit-1-638.jpg?raw=true) 58 | 59 | 一个 Android 和 Java 上 HTTP 库(利用注解和 okhttp 来实现和服务器的数据交互)。 60 | 61 | [**Retrofit 官方文档:http://square.github.io/retrofit/**](http://square.github.io/retrofit/) 62 | 63 | ### [DataBinding](https://developer.android.com/topic/libraries/data-binding/index.html) 64 | 65 | ![data-binding](https://github.com/githubhaohao/ImageRoom/blob/master/Images/mvvm/data_binding.png?raw=true) 66 | 67 | 在今年的 Google IO 2015 中,Google 在 support-v7 中新增了 Data Binding,使用 Data Binding 可以直接在布局的 xml 中绑定布局与数据,从而简化代码,Android Data Binding 是Android 的 MVVM 框架。因为 Data Binding 是包含在 support-v7 包里面的,所以可以向下兼容到最低 Android 2.1 (API level 7+). 68 | 69 | ## 实践 70 | 71 | 嫌代码不够高亮?请移步博客[http://haohaochang.cn](http://haohaochang.cn/2017/02/12/MVVM%EF%BC%8CRxJava%E5%92%8CRetrofit%E7%9A%84%E4%B8%80%E6%AC%A1%E5%AE%9E%E8%B7%B5/) 72 | 73 | 直接上代码。 74 | 75 | ### 依赖的第三方类库 76 | 77 | ```gradle 78 | compile 'io.reactivex:rxjava:1.1.0' 79 | compile 'io.reactivex:rxandroid:1.1.0' 80 | compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4' 81 | compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4' 82 | compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0-beta4' 83 | compile 'com.github.bumptech.glide:glide:3.7.0' 84 | ``` 85 | 86 | ### API 87 | 88 | `https://api.douban.com/v2/movie/top250?start=0&count=20` 89 | 90 | ### 引入DataBinding 91 | 92 | ```gradle 93 | android { 94 | ...... 95 | 96 | dataBinding { 97 | enabled = true 98 | } 99 | } 100 | 101 | ``` 102 | 103 | ### 工程目录结构 104 | 105 | ![目录](https://github.com/githubhaohao/ImageRoom/blob/master/Images/mvvm/%E7%9B%AE%E5%BD%95.png?raw=true) 106 | 107 | ### MVVM 之 View 108 | 109 | **MainActivity.java** 110 | 111 | ```java 112 | getFragmentManager().beginTransaction().add(R.id.movie_fragment, MovieFragment.getInstance()).commit(); 113 | 114 | ``` 115 | 116 | **MovieFragment.java** 117 | 118 | ```java 119 | public class MovieFragment extends Fragment implements CompletedListener,SwipeRefreshLayout.OnRefreshListener{ 120 | 121 | private static String TAG = MovieFragment.class.getSimpleName(); 122 | private MainViewModel viewModel; 123 | private MovieFragmentBinding movieFragmentBinding; 124 | private MovieAdapter movieAdapter; 125 | 126 | public static MovieFragment getInstance() { 127 | return new MovieFragment(); 128 | } 129 | 130 | @Nullable 131 | @Override 132 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 133 | View contentView = inflater.inflate(R.layout.movie_fragment, container, false); 134 | movieFragmentBinding = MovieFragmentBinding.bind(contentView); 135 | initData(); 136 | return contentView; 137 | } 138 | 139 | private void initData() { 140 | movieAdapter = new MovieAdapter(); 141 | movieFragmentBinding.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false)); 142 | movieFragmentBinding.recyclerView.setItemAnimator(new DefaultItemAnimator()); 143 | movieFragmentBinding.recyclerView.setAdapter(movieAdapter); 144 | movieFragmentBinding.swipeRefreshLayout.setColorSchemeResources(R.color.colorAccent, R.color.colorPrimary, R.color.colorPrimaryDark); 145 | movieFragmentBinding.swipeRefreshLayout.setOnRefreshListener(this); 146 | viewModel = new MainViewModel(movieAdapter,this); 147 | movieFragmentBinding.setViewModel(viewModel); 148 | 149 | } 150 | 151 | @Override 152 | public void onRefresh() { 153 | movieAdapter.clearItems(); 154 | viewModel.refreshData(); 155 | } 156 | 157 | @Override 158 | public void onCompleted() { 159 | if (movieFragmentBinding.swipeRefreshLayout.isRefreshing()) { 160 | movieFragmentBinding.swipeRefreshLayout.setRefreshing(false); 161 | } 162 | } 163 | } 164 | 165 | ``` 166 | 167 | **activity_main.xml** 168 | 169 | ```xml 170 | 171 | 178 | 179 | 180 | 181 | 186 | 187 | 188 | 189 | 190 | 191 | ``` 192 | 193 | **movie_fragment.xml** 194 | 195 | ```xml 196 | 197 | 198 | 199 | 202 | 203 | 206 | 211 | 212 | 218 | 219 | 220 | 221 | 222 | 229 | 230 | 236 | 241 | 242 | 243 | 244 | 245 | ``` 246 | 247 | **movie_item.xml** 248 | 249 | ```xml 250 | 251 | 253 | 254 | 257 | 258 | 266 | 270 | 277 | 282 | 288 | 293 | 303 | 304 | 313 | 314 | 315 | 324 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | ``` 340 | 341 | **MovieAdapter.java** 342 | 343 | ```java 344 | public class MovieAdapter extends RecyclerView.Adapter { 345 | private List movies; 346 | 347 | public MovieAdapter() { 348 | movies = new ArrayList<>(); 349 | } 350 | 351 | @Override 352 | public BindingHolder onCreateViewHolder(ViewGroup parent, int viewType) { 353 | MovieItemBinding itemBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.movie_item, parent, false); 354 | return new BindingHolder(itemBinding); 355 | } 356 | 357 | @Override 358 | public void onBindViewHolder(BindingHolder holder, int position) { 359 | MovieViewModel movieViewModel = new MovieViewModel(movies.get(position)); 360 | holder.itemBinding.setViewModel(movieViewModel); 361 | } 362 | 363 | @Override 364 | public int getItemCount() { 365 | return movies.size(); 366 | } 367 | 368 | public void addItem(Movie movie) { 369 | movies.add(movie); 370 | notifyItemInserted(movies.size() - 1); 371 | } 372 | 373 | public void clearItems() { 374 | movies.clear(); 375 | notifyDataSetChanged(); 376 | } 377 | 378 | public static class BindingHolder extends RecyclerView.ViewHolder { 379 | private MovieItemBinding itemBinding; 380 | 381 | public BindingHolder(MovieItemBinding itemBinding) { 382 | super(itemBinding.cardView); 383 | this.itemBinding = itemBinding; 384 | } 385 | } 386 | } 387 | ``` 388 | 389 | 回调接口** CompletedListener.java** 390 | 391 | ```java 392 | public interface CompletedListener { 393 | void onCompleted(); 394 | } 395 | ``` 396 | ### MVVM 之 ViewModel 397 | 398 | **MainViewModel.java** 399 | 400 | ```java 401 | public class MainViewModel { 402 | public ObservableField contentViewVisibility; 403 | public ObservableField progressBarVisibility; 404 | public ObservableField errorInfoLayoutVisibility; 405 | public ObservableField exception; 406 | private Subscriber subscriber; 407 | private MovieAdapter movieAdapter; 408 | private CompletedListener completedListener; 409 | 410 | public MainViewModel(MovieAdapter movieAdapter,CompletedListener completedListener) { 411 | this.movieAdapter = movieAdapter; 412 | this.completedListener = completedListener; 413 | initData(); 414 | getMovies(); 415 | } 416 | 417 | private void getMovies() { 418 | subscriber = new Subscriber() { 419 | @Override 420 | public void onCompleted() { 421 | Log.d("[MainViewModel]", "onCompleted"); 422 | hideAll(); 423 | contentViewVisibility.set(View.VISIBLE); 424 | completedListener.onCompleted(); 425 | } 426 | 427 | @Override 428 | public void onError(Throwable e) { 429 | hideAll(); 430 | errorInfoLayoutVisibility.set(View.VISIBLE); 431 | exception.set(e.getMessage()); 432 | } 433 | 434 | @Override 435 | public void onNext(Movie movie) { 436 | movieAdapter.addItem(movie); 437 | } 438 | }; 439 | RetrofitHelper.getInstance().getMovies(subscriber, 0, 20); 440 | } 441 | 442 | public void refreshData() { 443 | getMovies(); 444 | } 445 | 446 | private void initData() { 447 | contentViewVisibility = new ObservableField<>(); 448 | progressBarVisibility = new ObservableField<>(); 449 | errorInfoLayoutVisibility = new ObservableField<>(); 450 | exception = new ObservableField<>(); 451 | contentViewVisibility.set(View.GONE); 452 | errorInfoLayoutVisibility.set(View.GONE); 453 | progressBarVisibility.set(View.VISIBLE); 454 | } 455 | 456 | private void hideAll(){ 457 | contentViewVisibility.set(View.GONE); 458 | errorInfoLayoutVisibility.set(View.GONE); 459 | progressBarVisibility.set(View.GONE); 460 | } 461 | } 462 | 463 | ``` 464 | 465 | **MovieViewModel.java** 466 | 467 | ```java 468 | public class MovieViewModel extends BaseObservable { 469 | private Movie movie; 470 | 471 | public MovieViewModel(Movie movie) { 472 | this.movie = movie; 473 | } 474 | 475 | public String getCoverUrl() { 476 | return movie.getImages().getSmall(); 477 | } 478 | 479 | public String getTitle() { 480 | return movie.getTitle(); 481 | } 482 | 483 | public float getRating() { 484 | return movie.getRating().getAverage(); 485 | } 486 | 487 | public String getRatingText(){ 488 | return String.valueOf(movie.getRating().getAverage()); 489 | } 490 | 491 | public String getYear() { 492 | return movie.getYear(); 493 | } 494 | 495 | public String getMovieType() { 496 | StringBuilder builder = new StringBuilder(); 497 | for (String s : movie.getGenres()) { 498 | builder.append(s + " "); 499 | } 500 | return builder.toString(); 501 | } 502 | 503 | public String getImageUrl() { 504 | return movie.getImages().getSmall(); 505 | } 506 | 507 | @BindingAdapter({"app:imageUrl"}) 508 | public static void loadImage(ImageView imageView,String url) { 509 | Glide.with(imageView.getContext()) 510 | .load(url) 511 | .placeholder(R.drawable.cover) 512 | .error(R.drawable.cover) 513 | .into(imageView); 514 | 515 | } 516 | } 517 | 518 | ``` 519 | 520 | ### MVVM 之 Model 521 | 522 | **DouBanMovieService.java** 523 | 524 | ```java 525 | public interface DouBanMovieService { 526 | String BASE_URL = "https://api.douban.com/v2/movie/"; 527 | 528 | @GET("top250") 529 | Observable>> getMovies(@Query("start") int start, @Query("count") int count); 530 | } 531 | ``` 532 | **RetrofitHelper.java** 533 | ```java 534 | public class RetrofitHelper { 535 | private static final int DEFAULT_TIMEOUT = 10; 536 | private Retrofit retrofit; 537 | private DouBanMovieService movieService; 538 | OkHttpClient.Builder builder; 539 | 540 | /** 541 | * 获取RetrofitHelper对象的单例 542 | * */ 543 | private static class Singleton { 544 | private static final RetrofitHelper INSTANCE = new RetrofitHelper(); 545 | } 546 | 547 | public static RetrofitHelper getInstance() { 548 | return Singleton.INSTANCE; 549 | } 550 | 551 | public RetrofitHelper() { 552 | builder = new OkHttpClient.Builder(); 553 | builder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS); 554 | 555 | retrofit = new Retrofit.Builder() 556 | .client(builder.build()) 557 | .addConverterFactory(GsonConverterFactory.create()) 558 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 559 | .baseUrl(DouBanMovieService.BASE_URL) 560 | .build(); 561 | movieService = retrofit.create(DouBanMovieService.class); 562 | } 563 | 564 | public void getMovies(Subscriber subscriber, int start, int count) { 565 | movieService.getMovies(start, count) 566 | .map(new Func1>, List>() { 567 | @Override 568 | public List call(Response> listResponse) { 569 | return listResponse.getSubjects(); 570 | } 571 | }) 572 | .flatMap(new Func1, Observable>() { 573 | @Override 574 | public Observable call(List movies) { 575 | return Observable.from(movies); 576 | } 577 | }) 578 | .subscribeOn(Schedulers.io()) 579 | .observeOn(AndroidSchedulers.mainThread()) 580 | .subscribe(subscriber); 581 | } 582 | } 583 | ``` 584 | 585 | 还有 entity 类,这里就不贴出来了。 586 | 587 | 588 | 589 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "24.0.3" 6 | 7 | defaultConfig { 8 | applicationId "com.jc.mvvmrxjavaretrofitsample" 9 | minSdkVersion 19 10 | targetSdkVersion 25 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 | dataBinding { 22 | enabled = true 23 | } 24 | } 25 | 26 | dependencies { 27 | compile fileTree(dir: 'libs', include: ['*.jar']) 28 | testCompile 'junit:junit:4.12' 29 | compile 'com.android.support:appcompat-v7:25.1.1' 30 | compile 'com.android.support:design:25.1.1' 31 | compile 'com.android.support:cardview-v7:25.1.1' 32 | compile 'io.reactivex:rxjava:1.1.0' 33 | compile 'io.reactivex:rxandroid:1.1.0' 34 | compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4' 35 | compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4' 36 | compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0-beta4' 37 | compile 'com.github.bumptech.glide:glide:3.7.0' 38 | 39 | } 40 | -------------------------------------------------------------------------------- /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 K:\AndroidSDK/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/jc/mvvmrxjavaretrofitsample/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.jc.mvvmrxjavaretrofitsample; 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 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/jc/mvvmrxjavaretrofitsample/model/data/DouBanMovieService.java: -------------------------------------------------------------------------------- 1 | package com.jc.mvvmrxjavaretrofitsample.model.data; 2 | 3 | import com.jc.mvvmrxjavaretrofitsample.model.entity.Movie; 4 | import com.jc.mvvmrxjavaretrofitsample.model.entity.Response; 5 | 6 | import java.util.List; 7 | 8 | import retrofit2.http.GET; 9 | import retrofit2.http.Query; 10 | import rx.Observable; 11 | 12 | /** 13 | * Created by HaohaoChang on 2017/2/11. 14 | */ 15 | public interface DouBanMovieService { 16 | String BASE_URL = "https://api.douban.com/v2/movie/"; 17 | 18 | @GET("top250") 19 | Observable>> getMovies(@Query("start") int start, @Query("count") int count); 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/jc/mvvmrxjavaretrofitsample/model/data/RetrofitHelper.java: -------------------------------------------------------------------------------- 1 | package com.jc.mvvmrxjavaretrofitsample.model.data; 2 | 3 | import com.jc.mvvmrxjavaretrofitsample.model.entity.Movie; 4 | import com.jc.mvvmrxjavaretrofitsample.model.entity.Response; 5 | 6 | import java.util.List; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | import okhttp3.OkHttpClient; 10 | import retrofit2.Retrofit; 11 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; 12 | import retrofit2.converter.gson.GsonConverterFactory; 13 | import rx.Observable; 14 | import rx.Subscriber; 15 | import rx.android.schedulers.AndroidSchedulers; 16 | import rx.functions.Func1; 17 | import rx.schedulers.Schedulers; 18 | 19 | /** 20 | * Created by HaohaoChang on 2017/2/11. 21 | */ 22 | public class RetrofitHelper { 23 | private static final int DEFAULT_TIMEOUT = 10; 24 | private Retrofit retrofit; 25 | private DouBanMovieService movieService; 26 | OkHttpClient.Builder builder; 27 | 28 | /** 29 | * 获取RetrofitHelper对象的单例 30 | * */ 31 | private static class Singleton { 32 | private static final RetrofitHelper INSTANCE = new RetrofitHelper(); 33 | } 34 | 35 | public static RetrofitHelper getInstance() { 36 | return Singleton.INSTANCE; 37 | } 38 | 39 | private RetrofitHelper() { 40 | builder = new OkHttpClient.Builder(); 41 | builder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS); 42 | 43 | retrofit = new Retrofit.Builder() 44 | .client(builder.build()) 45 | .addConverterFactory(GsonConverterFactory.create()) 46 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 47 | .baseUrl(DouBanMovieService.BASE_URL) 48 | .build(); 49 | movieService = retrofit.create(DouBanMovieService.class); 50 | } 51 | 52 | public void getMovies(Subscriber subscriber, int start, int count) { 53 | movieService.getMovies(start, count) 54 | .map(new Func1>, List>() { 55 | @Override 56 | public List call(Response> listResponse) { 57 | return listResponse.getSubjects(); 58 | } 59 | }) 60 | .flatMap(new Func1, Observable>() { 61 | @Override 62 | public Observable call(List movies) { 63 | return Observable.from(movies); 64 | } 65 | }) 66 | .subscribeOn(Schedulers.io()) 67 | .observeOn(AndroidSchedulers.mainThread()) 68 | .subscribe(subscriber); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/com/jc/mvvmrxjavaretrofitsample/model/entity/Movie.java: -------------------------------------------------------------------------------- 1 | package com.jc.mvvmrxjavaretrofitsample.model.entity; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by HaohaoChang on 2017/2/11. 7 | */ 8 | public class Movie { 9 | private String id; 10 | private String alt; 11 | private String year; 12 | private String title; 13 | private String original_title; 14 | private List genres; 15 | private List casts; 16 | private List directors; 17 | private Avatars images; 18 | private Rating rating; 19 | 20 | public Rating getRating() { 21 | return rating; 22 | } 23 | 24 | public class Rating { 25 | float average; 26 | 27 | public void setAverage(float average) { 28 | this.average = average; 29 | } 30 | 31 | public float getAverage() { 32 | return average; 33 | } 34 | } 35 | 36 | public String getId() { 37 | return id; 38 | } 39 | 40 | public void setId(String id) { 41 | this.id = id; 42 | } 43 | 44 | public String getAlt() { 45 | return alt; 46 | } 47 | 48 | public void setAlt(String alt) { 49 | this.alt = alt; 50 | } 51 | 52 | public String getYear() { 53 | return year; 54 | } 55 | 56 | public void setYear(String year) { 57 | this.year = year; 58 | } 59 | 60 | public String getTitle() { 61 | return title; 62 | } 63 | 64 | public void setTitle(String title) { 65 | this.title = title; 66 | } 67 | 68 | public String getOriginal_title() { 69 | return original_title; 70 | } 71 | 72 | public void setOriginal_title(String original_title) { 73 | this.original_title = original_title; 74 | } 75 | 76 | public List getGenres() { 77 | return genres; 78 | } 79 | 80 | public void setGenres(List genres) { 81 | this.genres = genres; 82 | } 83 | 84 | public List getCasts() { 85 | return casts; 86 | } 87 | 88 | public void setCasts(List casts) { 89 | this.casts = casts; 90 | } 91 | 92 | public List getDirectors() { 93 | return directors; 94 | } 95 | 96 | public void setDirectors(List directors) { 97 | this.directors = directors; 98 | } 99 | 100 | public Avatars getImages() { 101 | return images; 102 | } 103 | 104 | public void setImages(Avatars images) { 105 | this.images = images; 106 | } 107 | 108 | private class Cast{ 109 | private String id; 110 | private String name; 111 | private String alt; 112 | private Avatars avatars; 113 | 114 | public String getId() { 115 | return id; 116 | } 117 | 118 | public void setId(String id) { 119 | this.id = id; 120 | } 121 | 122 | public String getName() { 123 | return name; 124 | } 125 | 126 | public void setName(String name) { 127 | this.name = name; 128 | } 129 | 130 | public String getAlt() { 131 | return alt; 132 | } 133 | 134 | public void setAlt(String alt) { 135 | this.alt = alt; 136 | } 137 | 138 | public Avatars getAvatars() { 139 | return avatars; 140 | } 141 | 142 | public void setAvatars(Avatars avatars) { 143 | this.avatars = avatars; 144 | } 145 | 146 | @Override 147 | public String toString() { 148 | return "cast.id=" + id + " cast.name=" + name + " | "; 149 | } 150 | } 151 | 152 | public class Avatars{ 153 | private String small; 154 | private String medium; 155 | private String large; 156 | 157 | public String getSmall() { 158 | return small; 159 | } 160 | 161 | public void setSmall(String small) { 162 | this.small = small; 163 | } 164 | 165 | public String getMedium() { 166 | return medium; 167 | } 168 | 169 | public void setMedium(String medium) { 170 | this.medium = medium; 171 | } 172 | 173 | public String getLarge() { 174 | return large; 175 | } 176 | 177 | public void setLarge(String large) { 178 | this.large = large; 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /app/src/main/java/com/jc/mvvmrxjavaretrofitsample/model/entity/Response.java: -------------------------------------------------------------------------------- 1 | package com.jc.mvvmrxjavaretrofitsample.model.entity; 2 | 3 | /** 4 | * Created by HaohaoChang on 2017/2/11. 5 | */ 6 | public class Response { 7 | private T subjects; 8 | private int count; 9 | private int start; 10 | private int total; 11 | 12 | public void setSubjects(T subjects) { 13 | this.subjects = subjects; 14 | } 15 | 16 | public T getSubjects() { 17 | 18 | return subjects; 19 | } 20 | 21 | public int getCount() { 22 | return count; 23 | } 24 | 25 | public void setCount(int count) { 26 | this.count = count; 27 | } 28 | 29 | public int getStart() { 30 | return start; 31 | } 32 | 33 | public void setStart(int start) { 34 | this.start = start; 35 | } 36 | 37 | public int getTotal() { 38 | return total; 39 | } 40 | 41 | public void setTotal(int total) { 42 | this.total = total; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/jc/mvvmrxjavaretrofitsample/view/CompletedListener.java: -------------------------------------------------------------------------------- 1 | package com.jc.mvvmrxjavaretrofitsample.view; 2 | 3 | /** 4 | * Created by HaohaoChang on 2017/2/12. 5 | */ 6 | public interface CompletedListener { 7 | void onCompleted(); 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/jc/mvvmrxjavaretrofitsample/view/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.jc.mvvmrxjavaretrofitsample.view; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.FloatingActionButton; 5 | import android.support.design.widget.Snackbar; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.Toolbar; 8 | import android.view.View; 9 | import android.view.Menu; 10 | import android.view.MenuItem; 11 | 12 | import com.jc.mvvmrxjavaretrofitsample.R; 13 | 14 | public class MainActivity extends AppCompatActivity { 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.activity_main); 20 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 21 | setSupportActionBar(toolbar); 22 | 23 | FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); 24 | fab.setOnClickListener(new View.OnClickListener() { 25 | @Override 26 | public void onClick(View view) { 27 | Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) 28 | .setAction("Action", null).show(); 29 | } 30 | }); 31 | 32 | getFragmentManager().beginTransaction().add(R.id.movie_fragment, MovieFragment.getInstance()).commit(); 33 | } 34 | 35 | @Override 36 | public boolean onCreateOptionsMenu(Menu menu) { 37 | // Inflate the menu; this adds items to the action bar if it is present. 38 | getMenuInflater().inflate(R.menu.menu_main, menu); 39 | return true; 40 | } 41 | 42 | @Override 43 | public boolean onOptionsItemSelected(MenuItem item) { 44 | // Handle action bar item clicks here. The action bar will 45 | // automatically handle clicks on the Home/Up button, so long 46 | // as you specify a parent activity in AndroidManifest.xml. 47 | int id = item.getItemId(); 48 | 49 | //noinspection SimplifiableIfStatement 50 | if (id == R.id.action_settings) { 51 | return true; 52 | } 53 | 54 | return super.onOptionsItemSelected(item); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/jc/mvvmrxjavaretrofitsample/view/MovieAdapter.java: -------------------------------------------------------------------------------- 1 | package com.jc.mvvmrxjavaretrofitsample.view; 2 | 3 | import android.content.Context; 4 | import android.databinding.BindingAdapter; 5 | import android.databinding.DataBindingUtil; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.ImageView; 11 | 12 | import com.bumptech.glide.Glide; 13 | import com.jc.mvvmrxjavaretrofitsample.R; 14 | import com.jc.mvvmrxjavaretrofitsample.databinding.MovieItemBinding; 15 | import com.jc.mvvmrxjavaretrofitsample.model.entity.Movie; 16 | import com.jc.mvvmrxjavaretrofitsample.viewModel.MovieViewModel; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | /** 22 | * Created by HaohaoChang on 2017/2/11. 23 | */ 24 | public class MovieAdapter extends RecyclerView.Adapter { 25 | private List movies; 26 | 27 | public MovieAdapter() { 28 | movies = new ArrayList<>(); 29 | } 30 | 31 | @Override 32 | public BindingHolder onCreateViewHolder(ViewGroup parent, int viewType) { 33 | MovieItemBinding itemBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.movie_item, parent, false); 34 | return new BindingHolder(itemBinding); 35 | } 36 | 37 | @Override 38 | public void onBindViewHolder(BindingHolder holder, int position) { 39 | MovieViewModel movieViewModel = new MovieViewModel(movies.get(position)); 40 | holder.itemBinding.setViewModel(movieViewModel); 41 | } 42 | 43 | @Override 44 | public int getItemCount() { 45 | return movies.size(); 46 | } 47 | 48 | public void addItem(Movie movie) { 49 | movies.add(movie); 50 | notifyItemInserted(movies.size() - 1); 51 | } 52 | 53 | public void clearItems() { 54 | movies.clear(); 55 | notifyDataSetChanged(); 56 | } 57 | 58 | public static class BindingHolder extends RecyclerView.ViewHolder { 59 | private MovieItemBinding itemBinding; 60 | 61 | public BindingHolder(MovieItemBinding itemBinding) { 62 | super(itemBinding.cardView); 63 | this.itemBinding = itemBinding; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/jc/mvvmrxjavaretrofitsample/view/MovieFragment.java: -------------------------------------------------------------------------------- 1 | package com.jc.mvvmrxjavaretrofitsample.view; 2 | 3 | import android.app.Fragment; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.widget.SwipeRefreshLayout; 7 | import android.support.v7.widget.DefaultItemAnimator; 8 | import android.support.v7.widget.LinearLayoutManager; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | 13 | import com.jc.mvvmrxjavaretrofitsample.R; 14 | import com.jc.mvvmrxjavaretrofitsample.databinding.MovieFragmentBinding; 15 | import com.jc.mvvmrxjavaretrofitsample.viewModel.MainViewModel; 16 | 17 | /** 18 | * Created by HaohaoChang on 2017/2/11. 19 | */ 20 | public class MovieFragment extends Fragment implements CompletedListener,SwipeRefreshLayout.OnRefreshListener{ 21 | 22 | private static String TAG = MovieFragment.class.getSimpleName(); 23 | private MainViewModel viewModel; 24 | private MovieFragmentBinding movieFragmentBinding; 25 | private MovieAdapter movieAdapter; 26 | 27 | public static MovieFragment getInstance() { 28 | return new MovieFragment(); 29 | } 30 | 31 | @Nullable 32 | @Override 33 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 34 | View contentView = inflater.inflate(R.layout.movie_fragment, container, false); 35 | movieFragmentBinding = MovieFragmentBinding.bind(contentView); 36 | initData(); 37 | return contentView; 38 | } 39 | 40 | private void initData() { 41 | movieAdapter = new MovieAdapter(); 42 | movieFragmentBinding.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false)); 43 | movieFragmentBinding.recyclerView.setItemAnimator(new DefaultItemAnimator()); 44 | movieFragmentBinding.recyclerView.setAdapter(movieAdapter); 45 | movieFragmentBinding.swipeRefreshLayout.setColorSchemeResources(R.color.colorAccent, R.color.colorPrimary, R.color.colorPrimaryDark); 46 | movieFragmentBinding.swipeRefreshLayout.setOnRefreshListener(this); 47 | viewModel = new MainViewModel(movieAdapter,this); 48 | movieFragmentBinding.setViewModel(viewModel); 49 | 50 | } 51 | 52 | @Override 53 | public void onRefresh() { 54 | movieAdapter.clearItems(); 55 | viewModel.refreshData(); 56 | } 57 | 58 | @Override 59 | public void onCompleted() { 60 | if (movieFragmentBinding.swipeRefreshLayout.isRefreshing()) { 61 | movieFragmentBinding.swipeRefreshLayout.setRefreshing(false); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/jc/mvvmrxjavaretrofitsample/viewModel/MainViewModel.java: -------------------------------------------------------------------------------- 1 | package com.jc.mvvmrxjavaretrofitsample.viewModel; 2 | 3 | import android.databinding.ObservableField; 4 | import android.util.Log; 5 | import android.view.View; 6 | 7 | import com.jc.mvvmrxjavaretrofitsample.model.data.RetrofitHelper; 8 | import com.jc.mvvmrxjavaretrofitsample.model.entity.Movie; 9 | import com.jc.mvvmrxjavaretrofitsample.view.CompletedListener; 10 | import com.jc.mvvmrxjavaretrofitsample.view.MovieAdapter; 11 | 12 | import rx.Subscriber; 13 | 14 | /** 15 | * Created by HaohaoChang on 2017/2/11. 16 | */ 17 | public class MainViewModel { 18 | public ObservableField contentViewVisibility; 19 | public ObservableField progressBarVisibility; 20 | public ObservableField errorInfoLayoutVisibility; 21 | public ObservableField exception; 22 | private Subscriber subscriber; 23 | private MovieAdapter movieAdapter; 24 | private CompletedListener completedListener; 25 | 26 | public MainViewModel(MovieAdapter movieAdapter,CompletedListener completedListener) { 27 | this.movieAdapter = movieAdapter; 28 | this.completedListener = completedListener; 29 | initData(); 30 | getMovies(); 31 | } 32 | 33 | private void getMovies() { 34 | subscriber = new Subscriber() { 35 | @Override 36 | public void onCompleted() { 37 | Log.d("[MainViewModel]", "onCompleted"); 38 | hideAll(); 39 | contentViewVisibility.set(View.VISIBLE); 40 | completedListener.onCompleted(); 41 | } 42 | 43 | @Override 44 | public void onError(Throwable e) { 45 | hideAll(); 46 | errorInfoLayoutVisibility.set(View.VISIBLE); 47 | exception.set(e.getMessage()); 48 | } 49 | 50 | @Override 51 | public void onNext(Movie movie) { 52 | movieAdapter.addItem(movie); 53 | } 54 | }; 55 | RetrofitHelper.getInstance().getMovies(subscriber, 0, 20); 56 | } 57 | 58 | public void refreshData() { 59 | getMovies(); 60 | } 61 | 62 | private void initData() { 63 | contentViewVisibility = new ObservableField<>(); 64 | progressBarVisibility = new ObservableField<>(); 65 | errorInfoLayoutVisibility = new ObservableField<>(); 66 | exception = new ObservableField<>(); 67 | contentViewVisibility.set(View.GONE); 68 | errorInfoLayoutVisibility.set(View.GONE); 69 | progressBarVisibility.set(View.VISIBLE); 70 | } 71 | 72 | private void hideAll(){ 73 | contentViewVisibility.set(View.GONE); 74 | errorInfoLayoutVisibility.set(View.GONE); 75 | progressBarVisibility.set(View.GONE); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/jc/mvvmrxjavaretrofitsample/viewModel/MovieViewModel.java: -------------------------------------------------------------------------------- 1 | package com.jc.mvvmrxjavaretrofitsample.viewModel; 2 | 3 | import android.databinding.BaseObservable; 4 | import android.databinding.BindingAdapter; 5 | import android.widget.ImageView; 6 | 7 | import com.bumptech.glide.Glide; 8 | import com.jc.mvvmrxjavaretrofitsample.R; 9 | import com.jc.mvvmrxjavaretrofitsample.model.entity.Movie; 10 | 11 | /** 12 | * Created by HaohaoChang on 2017/2/11. 13 | */ 14 | public class MovieViewModel extends BaseObservable { 15 | private Movie movie; 16 | 17 | public MovieViewModel(Movie movie) { 18 | this.movie = movie; 19 | } 20 | 21 | public String getCoverUrl() { 22 | return movie.getImages().getSmall(); 23 | } 24 | 25 | public String getTitle() { 26 | return movie.getTitle(); 27 | } 28 | 29 | public float getRating() { 30 | return movie.getRating().getAverage(); 31 | } 32 | 33 | public String getRatingText(){ 34 | return String.valueOf(movie.getRating().getAverage()); 35 | } 36 | 37 | public String getYear() { 38 | return movie.getYear(); 39 | } 40 | 41 | public String getMovieType() { 42 | StringBuilder builder = new StringBuilder(); 43 | for (String s : movie.getGenres()) { 44 | builder.append(s + " "); 45 | } 46 | return builder.toString(); 47 | } 48 | 49 | public String getImageUrl() { 50 | return movie.getImages().getSmall(); 51 | } 52 | 53 | @BindingAdapter({"app:imageUrl"}) 54 | public static void loadImage(ImageView imageView,String url) { 55 | Glide.with(imageView.getContext()) 56 | .load(url) 57 | .placeholder(R.drawable.cover) 58 | .error(R.drawable.cover) 59 | .into(imageView); 60 | 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubhaohao/MVVMRxJavaRetrofitSample/5f3a19d7f18646e2b0953bfe770561ec51d5971c/app/src/main/res/drawable/cover.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/rating_bar_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 21 | 22 | 23 | 24 | 29 | 30 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/movie_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 11 | 16 | 22 | 23 | 24 | 25 | 26 | 33 | 39 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/res/layout/movie_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 17 | 21 | 28 | 33 | 39 | 44 | 54 | 55 | 64 | 65 | 66 | 75 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubhaohao/MVVMRxJavaRetrofitSample/5f3a19d7f18646e2b0953bfe770561ec51d5971c/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubhaohao/MVVMRxJavaRetrofitSample/5f3a19d7f18646e2b0953bfe770561ec51d5971c/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubhaohao/MVVMRxJavaRetrofitSample/5f3a19d7f18646e2b0953bfe770561ec51d5971c/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/rating_small_empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubhaohao/MVVMRxJavaRetrofitSample/5f3a19d7f18646e2b0953bfe770561ec51d5971c/app/src/main/res/mipmap-xhdpi/rating_small_empty.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/rating_small_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubhaohao/MVVMRxJavaRetrofitSample/5f3a19d7f18646e2b0953bfe770561ec51d5971c/app/src/main/res/mipmap-xhdpi/rating_small_full.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/rating_small_half.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubhaohao/MVVMRxJavaRetrofitSample/5f3a19d7f18646e2b0953bfe770561ec51d5971c/app/src/main/res/mipmap-xhdpi/rating_small_half.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubhaohao/MVVMRxJavaRetrofitSample/5f3a19d7f18646e2b0953bfe770561ec51d5971c/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubhaohao/MVVMRxJavaRetrofitSample/5f3a19d7f18646e2b0953bfe770561ec51d5971c/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | > 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #36506C 4 | #233142 5 | #A5DEF1 6 | #EBF7FD 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 | MVVMRxJavaRetrofitSample 3 | Settings 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 |