diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0fe3258
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,28 @@
+release
+target
+build
+.settings
+.project
+.classpath
+.idea
+.DS_Store
+bin
+gen
+proguard
+.pmd
+*~
+*.iml
+tmp
+gen-external-apklibs
+out
+tmp
+MeituanUri
+coverage
+build/
+.gradle/
+local.properties
+gradle/
+gradlew
+gradlew.bat
+.gradletasknamecache
+keystore
diff --git a/README.md b/README.md
index 1c16448..0090a69 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,325 @@
-# MVVMLight
-A toolkit help to build Android MVVM Application
+# MVVM Light Toolkit
+A toolkit help to build Android MVVM Application,We have more
+attributes for Data Binding of View(like Uri for ImageView) ,we create some command for deal with event( like click of Button),also have a global message pipe to communicate with other ViewModel.
+##Download##
+
+```groovy
+ compile 'com.kelin.mvvmlight:library:0.6.0'
+```
+
+requires at least android gradle plugin 1.5.0.
+
+##Usage##
+---
+###Data Binding###
+
+
+Binding URI to the ImageView with bind:uri will make it loading bitmap from URI and render to ImageView automatically.
+
+ ```xml
+
+ ```
+
+ ---
+
+ **Example**
+
+ 
+
+ ---
+
+AdapterView like ListView、RecyclerView 、ViewPager is convenient, bind it to the collection view with app:items and app:itemView,You should use an ObservableList to automatically update your view based on list changes.
+
+ ```xml
+ itemViewModel = new ObservableArrayList<>();
+ public final ItemView itemView = ItemView.of(BR.viewModel, R.layout.layoutitem_list_view);
+ ```
+
+ Adapter,ViewHolder ..is Not Required:
+
+ ---
+
+ **Example**
+
+ 
+
+ ---
+ **Other attributes supported:**
+
+ - *ImageView*
+
+ ```xml
+
+
+
+
+
+
+ ```
+
+ - *ListView*、*ViewPager*、*RecyclerView*
+
+ ```xml
+
+
+
+
+
+
+
+
+
+
+
+ ```
+
+ - *ViewGroup*
+
+ ```xml
+
+
+
+
+ ```
+
+ - *EditText*
+
+ ```xml
+
+
+ ```
+
+ - *SimpleDraweeView*
+
+ ```xml
+
+
+ ```
+
+ - *WebView*
+
+ ```xml
+
+
+ ```
+
+
+###Command Binding###
+---
+
+When RecyclerView scroll to end of list,we have onLoadMoreCommand to deal with event.
+
+ ```xml
+
+ ```
+
+In ViewModel define a ReplyCommand field to deal with this event.
+
+ ```java
+ public final ReplyCommand loadMoreCommand = new ReplyCommand<>(
+ (count) -> {
+ /*count: count of list items*/
+ int page=count / LIMIT +1;
+ loadData(page)
+ });
+ ```
+
+ ---
+
+ **Example**
+
+ 
+
+ ---
+
+ Deal with click event of View is more convenient:
+
+ ```xml
+
+ ```
+In ViewModel define a ReplyCommand btnClickCommand will be call when click event occur.
+
+ ```java
+ public ReplyCommand btnClickCommand = new ReplyCommand(() -> {
+ do something...
+ });
+ ```
+
+ ---
+
+ **Example**
+
+ 
+
+ ---
+
+ **onRefreshCommand to SwipeRefreshLayout**
+
+ 
+
+ ---
+
+More command binding is supported:
+
+ - *View*
+
+ ```xml
+
+
+
+
+
+
+ ```
+
+ - *ListView*、*RecyclerView*
+
+ ```xml
+
+
+
+
+
+
+ ```
+ - *ViewPager*
+
+ ```xml
+
+
+
+
+
+
+ ```
+
+ - *EditText*
+
+ ```xml
+
+
+
+
+
+
+ ```
+
+ - *ImageView*
+
+ ```xml
+
+
+
+
+ ```
+
+ - *ScrollView*、*NestedScrollView*
+
+ ```xml
+
+
+
+
+ ```
+
+ - *SwipeRefreshLayout*
+
+ ```xml
+
+
+ ```
+
+###Messenger###
+---
+**simplifies the communication between ViewModel(major) or any components**
+
+ ---
+
+ **Example**
+
+ 
+
+ ---
+
+- global message broadcast without deliver data
+
+ ```java
+ /* TOKEN: like Action of broadcast with who register this token will be notified when event occur.*/
+ Messenger.Default().sendNoMsg(TOKEN);
+ /*context: it usually to be a activity ,this parameter is represent to
+ a receiver which is mean for convenient when unregister message.
+ TOKEN: like Action of broadcast with who register this token will be notified when event occur.
+ (data)->{ }:Action to deal with event. */
+ Messenger.Default().register(context, TOKEN, () -> { });
+ ```
+- global message broadcast (carry data to receiver)
+
+ ```java
+ Messenger.getDefault().send(data, TOKEN)
+ /*context:
+ TOKEN:
+ Data.class: type of deliver data.
+ (data)->{ }: function to deal with event which has data is deliver by sender.*/
+ Messenger.getDefault().register(context, TOKEN, Data.class, (data) -> { });
+ ```
+- send to specify target (inactive)
+
+ ```java
+ Messenger.getDefault().sendToTarget(T message, R target)
+ Messenger.getDefault().sendNoMsgToTargetWithToken(Object token,R target)
+ Messenger.getDefault().sendNoMsgToTarget(Object target)
+ ```
+
+- cancel register
+
+ ```java
+ Messenger.getDefault().unregister(Object recipient)"
+ /* Usually Usage*/
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ Messenger.getDefault().unregister(this);
+ }
+ ```
+
+
+## License
+ ```
+ Copyright 2016 Kelin Hong
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ```
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..108a81c
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,25 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.1.0'
+ classpath 'me.tatarka:gradle-retrolambda:3.2.5'
+ classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
+ classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6'
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..1d3591c
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/library/.gitignore b/library/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/library/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/library/bintrayUpload.gradle b/library/bintrayUpload.gradle
new file mode 100644
index 0000000..614b069
--- /dev/null
+++ b/library/bintrayUpload.gradle
@@ -0,0 +1,81 @@
+apply plugin: 'com.github.dcendents.android-maven'
+apply plugin: 'com.jfrog.bintray'
+// This is the library version used when deploying the artifact
+version = "1.0.0"
+
+def siteUrl = 'https://github.com/Kelin-Hong/MVVMLight' // 项目的主页
+def gitUrl = 'https://github.com/Kelin-Hong/MVVMLight.git' // Git仓库的url
+group = "com.kelin.mvvmlight" // Maven Group ID for the artifact,一般填你唯一的包名
+install {
+ repositories.mavenInstaller {
+ // This generates POM.xml with proper parameters
+ pom {
+ project {
+ packaging 'aar'
+ // Add your description here
+ name 'A toolkit help to build Android MVVM Application.'
+ url siteUrl
+ // Set your license
+ licenses {
+ license {
+ name 'The Apache Software License, Version 2.0'
+ url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+ }
+ }
+ developers {
+ developer {
+ id 'KelinHong' //填写的一些基本信息
+ name 'KelinHong'
+ email 'kelinhong1218@gmail.com'
+ }
+ }
+ scm {
+ connection gitUrl
+ developerConnection gitUrl
+ url siteUrl
+ }
+ }
+ }
+ }
+}
+task sourcesJar(type: Jar) {
+ from android.sourceSets.main.java.srcDirs
+ classifier = 'sources'
+}
+task javadoc(type: Javadoc) {
+ source = android.sourceSets.main.java.srcDirs
+ classpath += configurations.compile
+ classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
+// source = android.sourceSets.main.java.srcDirs
+// classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
+}
+task javadocJar(type: Jar, dependsOn: javadoc) {
+ classifier = 'javadoc'
+ from javadoc.destinationDir
+}
+artifacts {
+ archives javadocJar
+ archives sourcesJar
+}
+
+Properties properties = new Properties()
+properties.load(project.rootProject.file('local.properties').newDataInputStream())
+bintray {
+ user = properties.getProperty("bintray.user")
+ key = properties.getProperty("bintray.apikey")
+ configurations = ['archives']
+ pkg {
+ repo = "maven"
+ name = "MVVMLight" //发布到JCenter上的项目名字
+ websiteUrl = siteUrl
+ vcsUrl = gitUrl
+ licenses = ["Apache-2.0"]
+ publish = true
+ }
+}
+javadoc { //jav doc采用utf-8编码否则会报“GBK的不可映射字符”错误
+ options{
+ encoding "UTF-8"
+ charSet 'UTF-8'
+ }
+}
\ No newline at end of file
diff --git a/library/build.gradle b/library/build.gradle
new file mode 100644
index 0000000..3fd2f2e
--- /dev/null
+++ b/library/build.gradle
@@ -0,0 +1,40 @@
+apply plugin: 'com.android.library'
+apply plugin: 'me.tatarka.retrolambda'
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion "23.0.2"
+
+ defaultConfig {
+ minSdkVersion 15
+ targetSdkVersion 23
+ versionCode 3
+ versionName "1.0"
+ }
+
+ dataBinding {
+ enabled true
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility 1.8
+ targetCompatibility 1.8
+ }
+}
+
+dependencies {
+ compile 'com.android.support:appcompat-v7:23.+'
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile 'io.reactivex:rxandroid:1.1.0'
+ compile 'me.tatarka.bindingcollectionadapter:bindingcollectionadapter:1.1.0'
+ compile 'me.tatarka.bindingcollectionadapter:bindingcollectionadapter-recyclerview:1.1.0'
+ compile 'com.facebook.fresco:fresco:0.9.0+'
+ testCompile 'junit:junit:4.12'
+}
+
+apply from: "bintrayUpload.gradle"
diff --git a/library/proguard-rules.pro b/library/proguard-rules.pro
new file mode 100644
index 0000000..faf9f17
--- /dev/null
+++ b/library/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/kelin/android-sdk-macosx/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/library/src/androidTest/java/com/kelin/mvvmlight/ApplicationTest.java b/library/src/androidTest/java/com/kelin/mvvmlight/ApplicationTest.java
new file mode 100644
index 0000000..5d01d38
--- /dev/null
+++ b/library/src/androidTest/java/com/kelin/mvvmlight/ApplicationTest.java
@@ -0,0 +1,13 @@
+package com.kelin.mvvmlight;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..3a256dd
--- /dev/null
+++ b/library/src/main/AndroidManifest.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/library/src/main/java/com/kelin/mvvmlight/base/ViewModel.java b/library/src/main/java/com/kelin/mvvmlight/base/ViewModel.java
new file mode 100644
index 0000000..c6fbc4f
--- /dev/null
+++ b/library/src/main/java/com/kelin/mvvmlight/base/ViewModel.java
@@ -0,0 +1,10 @@
+package com.kelin.mvvmlight.base;
+
+import android.view.View;
+
+/**
+ * Created by kelin on 16-3-15.
+ */
+public interface ViewModel {
+
+}
diff --git a/library/src/main/java/com/kelin/mvvmlight/bindingadapter/edittext/ViewBindingAdapter.java b/library/src/main/java/com/kelin/mvvmlight/bindingadapter/edittext/ViewBindingAdapter.java
new file mode 100644
index 0000000..ff03869
--- /dev/null
+++ b/library/src/main/java/com/kelin/mvvmlight/bindingadapter/edittext/ViewBindingAdapter.java
@@ -0,0 +1,78 @@
+package com.kelin.mvvmlight.bindingadapter.edittext;
+
+import android.content.Context;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+import com.kelin.mvvmlight.command.ReplyCommand;
+
+/**
+ * Created by kelin on 16-3-24.
+ */
+public final class ViewBindingAdapter {
+
+
+ @android.databinding.BindingAdapter({"requestFocus"})
+ public static void requestFocusCommand(EditText editText, final Boolean needRequestFocus) {
+ if (needRequestFocus) {
+ editText.setFocusableInTouchMode(true);
+ editText.setSelection(editText.getText().length());
+ editText.requestFocus();
+ InputMethodManager imm = (InputMethodManager) editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
+ } else {
+ editText.setEnabled(false);
+ editText.setEnabled(true);
+ }
+
+ }
+
+
+ @android.databinding.BindingAdapter(value = {"beforeTextChangedCommand", "onTextChangedCommand", "afterTextChangedCommand"}, requireAll = false)
+ public static void editTextCommand(EditText editText,
+ final ReplyCommand beforeTextChangedCommand,
+ final ReplyCommand onTextChangedCommand,
+ final ReplyCommand afterTextChangedCommand) {
+ editText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ if (beforeTextChangedCommand != null) {
+ beforeTextChangedCommand.execute(new TextChangeDataWrapper(s, start, count, count));
+ }
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ if (onTextChangedCommand != null) {
+ onTextChangedCommand.execute(new TextChangeDataWrapper(s, start, before, count));
+ }
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (afterTextChangedCommand != null) {
+ afterTextChangedCommand.execute(s.toString());
+ }
+ }
+ });
+ }
+
+ public static class TextChangeDataWrapper {
+ public CharSequence s;
+ public int start;
+ public int before;
+ public int count;
+
+ public TextChangeDataWrapper(CharSequence s, int start, int before, int count) {
+ this.s = s;
+ this.start = start;
+ this.before = before;
+ this.count = count;
+ }
+ }
+
+
+}
+
diff --git a/library/src/main/java/com/kelin/mvvmlight/bindingadapter/image/ViewBindingAdapter.java b/library/src/main/java/com/kelin/mvvmlight/bindingadapter/image/ViewBindingAdapter.java
new file mode 100644
index 0000000..2d5a5e0
--- /dev/null
+++ b/library/src/main/java/com/kelin/mvvmlight/bindingadapter/image/ViewBindingAdapter.java
@@ -0,0 +1,72 @@
+package com.kelin.mvvmlight.bindingadapter.image;
+
+import android.databinding.BindingAdapter;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.support.annotation.DrawableRes;
+import android.text.TextUtils;
+import android.widget.ImageView;
+
+import com.facebook.common.executors.UiThreadImmediateExecutorService;
+import com.facebook.common.references.CloseableReference;
+import com.facebook.datasource.DataSource;
+import com.facebook.drawee.backends.pipeline.Fresco;
+import com.facebook.drawee.view.SimpleDraweeView;
+import com.facebook.imagepipeline.common.ResizeOptions;
+import com.facebook.imagepipeline.core.ImagePipeline;
+import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
+import com.facebook.imagepipeline.image.CloseableImage;
+import com.facebook.imagepipeline.request.ImageRequest;
+import com.facebook.imagepipeline.request.ImageRequestBuilder;
+import com.kelin.mvvmlight.command.ReplyCommand;
+
+/**
+ * Created by kelin on 16-3-24.
+ */
+public final class ViewBindingAdapter {
+
+ @BindingAdapter({"uri"})
+ public static void setImageUri(SimpleDraweeView simpleDraweeView, String uri) {
+ if (!TextUtils.isEmpty(uri)) {
+ simpleDraweeView.setImageURI(Uri.parse(uri));
+ }
+ }
+
+
+ @BindingAdapter(value = {"uri", "placeholderImageRes", "request_width", "request_height", "onSuccessCommand", "onFailureCommand"}, requireAll = false)
+ public static void loadImage(final ImageView imageView, String uri,
+ @DrawableRes int placeholderImageRes,
+ int width, int height,
+ final ReplyCommand onSuccessCommand,
+ final ReplyCommand>> onFailureCommand) {
+ imageView.setImageResource(placeholderImageRes);
+ if (!TextUtils.isEmpty(uri)) {
+ ImagePipeline imagePipeline = Fresco.getImagePipeline();
+ ImageRequestBuilder builder = ImageRequestBuilder.newBuilderWithSource(Uri.parse(uri));
+ if (width > 0 && height > 0) {
+ builder.setResizeOptions(new ResizeOptions(width, height));
+ }
+ ImageRequest request = builder.build();
+ DataSource>
+ dataSource = imagePipeline.fetchDecodedImage(request, imageView.getContext());
+ dataSource.subscribe(new BaseBitmapDataSubscriber() {
+ @Override
+ protected void onFailureImpl(DataSource> dataSource) {
+ if (onFailureCommand != null) {
+ onFailureCommand.execute(dataSource);
+ }
+ }
+
+ @Override
+ protected void onNewResultImpl(Bitmap bitmap) {
+ imageView.setImageBitmap(bitmap);
+ if (onSuccessCommand != null) {
+ onSuccessCommand.execute(bitmap);
+ }
+ }
+ }, UiThreadImmediateExecutorService.getInstance());
+ }
+ }
+
+}
+
diff --git a/library/src/main/java/com/kelin/mvvmlight/bindingadapter/listview/ViewBindingAdapter.java b/library/src/main/java/com/kelin/mvvmlight/bindingadapter/listview/ViewBindingAdapter.java
new file mode 100644
index 0000000..f1805f2
--- /dev/null
+++ b/library/src/main/java/com/kelin/mvvmlight/bindingadapter/listview/ViewBindingAdapter.java
@@ -0,0 +1,109 @@
+package com.kelin.mvvmlight.bindingadapter.listview;
+
+import android.databinding.BindingAdapter;
+import android.view.View;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+import com.kelin.mvvmlight.command.ReplyCommand;
+
+import java.util.concurrent.TimeUnit;
+
+import rx.subjects.PublishSubject;
+
+/**
+ * Created by kelin on 16-3-24.
+ */
+public final class ViewBindingAdapter {
+
+ @SuppressWarnings("unchecked")
+ @BindingAdapter(value = {"onScrollChangeCommand", "onScrollStateChangedCommand"}, requireAll = false)
+ public static void onScrollChangeCommand(final ListView listView,
+ final ReplyCommand onScrollChangeCommand,
+ final ReplyCommand onScrollStateChangedCommand) {
+ listView.setOnScrollListener(new AbsListView.OnScrollListener() {
+ private int scrollState;
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ this.scrollState = scrollState;
+ if (onScrollStateChangedCommand != null) {
+ onScrollChangeCommand.equals(scrollState);
+ }
+ }
+
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+ if (onScrollChangeCommand != null) {
+ onScrollChangeCommand.execute(new ListViewScrollDataWrapper(scrollState, firstVisibleItem, visibleItemCount, totalItemCount));
+ }
+ }
+ });
+
+ }
+
+
+ @BindingAdapter(value = {"onItemClickCommand"}, requireAll = false)
+ public static void onItemClickCommand(final ListView listView, final ReplyCommand onItemClickCommand) {
+ listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ if (onItemClickCommand != null) {
+ onItemClickCommand.execute(position);
+ }
+ }
+ });
+ }
+
+
+ @BindingAdapter({"onLoadMoreCommand"})
+ public static void onLoadMoreCommand(final ListView listView, final ReplyCommand onLoadMoreCommand) {
+ listView.setOnScrollListener(new OnScrollListener(listView, onLoadMoreCommand));
+
+ }
+
+ public static class OnScrollListener implements AbsListView.OnScrollListener {
+ private PublishSubject methodInvoke = PublishSubject.create();
+ private ReplyCommand onLoadMoreCommand;
+ private ListView listView;
+
+ public OnScrollListener(ListView listView, ReplyCommand onLoadMoreCommand) {
+ this.onLoadMoreCommand = onLoadMoreCommand;
+ this.listView = listView;
+ methodInvoke.throttleFirst(1, TimeUnit.SECONDS)
+ .subscribe(c -> this.onLoadMoreCommand.execute(c));
+ }
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+
+ }
+
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+ if (firstVisibleItem + visibleItemCount >= totalItemCount
+ && totalItemCount != 0
+ && totalItemCount != listView.getHeaderViewsCount()
+ + listView.getFooterViewsCount()) {
+ if (onLoadMoreCommand != null) {
+ methodInvoke.onNext(totalItemCount);
+ }
+ }
+ }
+ }
+
+ public static class ListViewScrollDataWrapper {
+ public int firstVisibleItem;
+ public int visibleItemCount;
+ public int totalItemCount;
+ public int scrollState;
+
+ public ListViewScrollDataWrapper(int scrollState, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+ this.firstVisibleItem = firstVisibleItem;
+ this.visibleItemCount = visibleItemCount;
+ this.totalItemCount = totalItemCount;
+ this.scrollState = scrollState;
+ }
+ }
+}
diff --git a/library/src/main/java/com/kelin/mvvmlight/bindingadapter/recyclerview/ViewBindingAdapter.java b/library/src/main/java/com/kelin/mvvmlight/bindingadapter/recyclerview/ViewBindingAdapter.java
new file mode 100644
index 0000000..b62495e
--- /dev/null
+++ b/library/src/main/java/com/kelin/mvvmlight/bindingadapter/recyclerview/ViewBindingAdapter.java
@@ -0,0 +1,97 @@
+package com.kelin.mvvmlight.bindingadapter.recyclerview;
+
+import android.databinding.BindingAdapter;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+
+import com.kelin.mvvmlight.command.ReplyCommand;
+
+import java.util.concurrent.TimeUnit;
+
+import rx.subjects.PublishSubject;
+
+/**
+ * Created by kelin on 16-4-26.
+ */
+public class ViewBindingAdapter {
+
+ @BindingAdapter(value = {"onScrollChangeCommand", "onScrollStateChangedCommand"}, requireAll = false)
+ public static void onScrollChangeCommand(final RecyclerView recyclerView,
+ final ReplyCommand onScrollChangeCommand,
+ final ReplyCommand onScrollStateChangedCommand) {
+ recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+ private int state;
+
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ super.onScrolled(recyclerView, dx, dy);
+ if (onScrollChangeCommand != null) {
+ onScrollChangeCommand.execute(new ScrollDataWrapper(dx, dy, state));
+ }
+ }
+
+ @Override
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+ super.onScrollStateChanged(recyclerView, newState);
+ state = newState;
+ if (onScrollStateChangedCommand != null) {
+ onScrollChangeCommand.equals(newState);
+ }
+ }
+ });
+
+ }
+
+ @SuppressWarnings("unchecked")
+ @BindingAdapter({"onLoadMoreCommand"})
+ public static void onLoadMoreCommand(final RecyclerView recyclerView, final ReplyCommand onLoadMoreCommand) {
+ RecyclerView.OnScrollListener listener = new OnScrollListener(onLoadMoreCommand);
+ recyclerView.addOnScrollListener(listener);
+
+ }
+
+ public static class OnScrollListener extends RecyclerView.OnScrollListener {
+
+ private PublishSubject methodInvoke = PublishSubject.create();
+
+ private ReplyCommand onLoadMoreCommand;
+
+ public OnScrollListener(ReplyCommand onLoadMoreCommand) {
+ this.onLoadMoreCommand = onLoadMoreCommand;
+ methodInvoke.throttleFirst(1, TimeUnit.SECONDS)
+ .subscribe(c -> onLoadMoreCommand.execute(c));
+ }
+
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
+ int visibleItemCount = layoutManager.getChildCount();
+ int totalItemCount = layoutManager.getItemCount();
+ int pastVisiblesItems = layoutManager.findFirstVisibleItemPosition();
+ if ((visibleItemCount + pastVisiblesItems) >= totalItemCount) {
+ if (onLoadMoreCommand != null) {
+ methodInvoke.onNext(recyclerView.getAdapter().getItemCount());
+ }
+ }
+ }
+
+ @Override
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+ super.onScrollStateChanged(recyclerView, newState);
+ }
+
+
+ }
+
+ public static class ScrollDataWrapper {
+ public float scrollX;
+ public float scrollY;
+ public int state;
+
+ public ScrollDataWrapper(float scrollX, float scrollY, int state) {
+ this.scrollX = scrollX;
+ this.scrollY = scrollY;
+ this.state = state;
+ }
+ }
+}
diff --git a/library/src/main/java/com/kelin/mvvmlight/bindingadapter/scrollview/ViewBindingAdapter.java b/library/src/main/java/com/kelin/mvvmlight/bindingadapter/scrollview/ViewBindingAdapter.java
new file mode 100644
index 0000000..5fc1f53
--- /dev/null
+++ b/library/src/main/java/com/kelin/mvvmlight/bindingadapter/scrollview/ViewBindingAdapter.java
@@ -0,0 +1,65 @@
+package com.kelin.mvvmlight.bindingadapter.scrollview;
+
+import android.databinding.BindingAdapter;
+import android.support.v4.widget.NestedScrollView;
+import android.view.ViewTreeObserver;
+import android.widget.ScrollView;
+
+import com.kelin.mvvmlight.command.ReplyCommand;
+
+/**
+ * Created by kelin on 16-3-24.
+ */
+public final class ViewBindingAdapter {
+
+ @SuppressWarnings("unchecked")
+ @BindingAdapter({"onScrollChangeCommand"})
+ public static void onScrollChangeCommand(final NestedScrollView nestedScrollView, final ReplyCommand onScrollChangeCommand) {
+ nestedScrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
+ @Override
+ public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
+ if (onScrollChangeCommand != null) {
+ onScrollChangeCommand.execute(new NestScrollDataWrapper(scrollX, scrollY, oldScrollX, oldScrollY));
+ }
+ }
+ });
+ }
+
+ @SuppressWarnings("unchecked")
+ @BindingAdapter({"onScrollChangeCommand"})
+ public static void onScrollChangeCommand(final ScrollView scrollView, final ReplyCommand onScrollChangeCommand) {
+ scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
+ @Override
+ public void onScrollChanged() {
+ if (onScrollChangeCommand != null) {
+ onScrollChangeCommand.execute(new ScrollDataWrapper(scrollView.getScaleX(), scrollView.getScrollY()));
+ }
+ }
+ });
+
+ }
+
+ public static class ScrollDataWrapper {
+ public float scrollX;
+ public float scrollY;
+
+ public ScrollDataWrapper(float scrollX, float scrollY) {
+ this.scrollX = scrollX;
+ this.scrollY = scrollY;
+ }
+ }
+
+ public static class NestScrollDataWrapper {
+ public int scrollX;
+ public int scrollY;
+ public int oldScrollX;
+ public int oldScrollY;
+
+ public NestScrollDataWrapper(int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
+ this.scrollX = scrollX;
+ this.scrollY = scrollY;
+ this.oldScrollX = oldScrollX;
+ this.oldScrollY = oldScrollY;
+ }
+ }
+}
diff --git a/library/src/main/java/com/kelin/mvvmlight/bindingadapter/swiperefresh/ViewBindingAdapter.java b/library/src/main/java/com/kelin/mvvmlight/bindingadapter/swiperefresh/ViewBindingAdapter.java
new file mode 100644
index 0000000..c9ad4ba
--- /dev/null
+++ b/library/src/main/java/com/kelin/mvvmlight/bindingadapter/swiperefresh/ViewBindingAdapter.java
@@ -0,0 +1,24 @@
+package com.kelin.mvvmlight.bindingadapter.swiperefresh;
+
+import android.databinding.BindingAdapter;
+import android.support.v4.widget.SwipeRefreshLayout;
+
+import com.kelin.mvvmlight.command.ReplyCommand;
+
+/**
+ * Created by kelin on 16-4-26.
+ */
+public class ViewBindingAdapter {
+ @BindingAdapter({"onRefreshCommand"})
+ public static void onRefreshCommand(SwipeRefreshLayout swipeRefreshLayout, final ReplyCommand onRefreshCommand) {
+ swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
+ @Override
+ public void onRefresh() {
+ if (onRefreshCommand != null) {
+ onRefreshCommand.execute();
+ }
+ }
+ });
+ }
+
+}
diff --git a/library/src/main/java/com/kelin/mvvmlight/bindingadapter/view/ViewBindingAdapter.java b/library/src/main/java/com/kelin/mvvmlight/bindingadapter/view/ViewBindingAdapter.java
new file mode 100644
index 0000000..dc45816
--- /dev/null
+++ b/library/src/main/java/com/kelin/mvvmlight/bindingadapter/view/ViewBindingAdapter.java
@@ -0,0 +1,62 @@
+package com.kelin.mvvmlight.bindingadapter.view;
+
+import android.databinding.BindingAdapter;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.kelin.mvvmlight.command.ReplyCommand;
+import com.kelin.mvvmlight.command.ResponseCommand;
+
+/**
+ * Created by kelin on 16-3-24.
+ */
+public final class ViewBindingAdapter {
+
+ @BindingAdapter({"clickCommand"})
+ public static void clickCommand(View view, final ReplyCommand clickCommand) {
+ view.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (clickCommand != null) {
+ clickCommand.execute();
+ }
+ }
+ });
+ }
+
+ @BindingAdapter({"requestFocus"})
+ public static void requestFocusCommand(View view, final Boolean needRequestFocus) {
+ if (needRequestFocus) {
+ view.setFocusableInTouchMode(true);
+ view.requestFocus();
+ } else {
+ view.clearFocus();
+ }
+ }
+
+ @BindingAdapter({"onFocusChangeCommand"})
+ public static void onFocusChangeCommand(View view, final ReplyCommand onFocusChangeCommand) {
+ view.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (onFocusChangeCommand != null) {
+ onFocusChangeCommand.execute(hasFocus);
+ }
+ }
+ });
+ }
+
+ @BindingAdapter({"onTouchCommand"})
+ public static void onTouchCommand(View view, final ResponseCommand onTouchCommand) {
+ view.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (onTouchCommand != null) {
+ return onTouchCommand.execute(event);
+ }
+ return false;
+ }
+ });
+ }
+}
+
diff --git a/library/src/main/java/com/kelin/mvvmlight/bindingadapter/viewgroup/ViewBindingAdapter.java b/library/src/main/java/com/kelin/mvvmlight/bindingadapter/viewgroup/ViewBindingAdapter.java
new file mode 100644
index 0000000..51453c1
--- /dev/null
+++ b/library/src/main/java/com/kelin/mvvmlight/bindingadapter/viewgroup/ViewBindingAdapter.java
@@ -0,0 +1,31 @@
+package com.kelin.mvvmlight.bindingadapter.viewgroup;
+
+import android.databinding.BindingAdapter;
+import android.databinding.DataBindingUtil;
+import android.databinding.ObservableList;
+import android.databinding.ViewDataBinding;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import com.kelin.mvvmlight.base.ViewModel;
+
+import me.tatarka.bindingcollectionadapter.ItemView;
+
+/**
+ * Created by kelin on 16-3-24.
+ */
+public final class ViewBindingAdapter {
+
+ @BindingAdapter({"itemView", "viewModels"})
+ public static void addViews(ViewGroup viewGroup, final ItemView itemView, final ObservableList viewModelList) {
+ if (viewModelList != null && !viewModelList.isEmpty()) {
+ viewGroup.removeAllViews();
+ for (ViewModel viewModel : viewModelList) {
+ ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()),
+ itemView.layoutRes(), viewGroup, true);
+ binding.setVariable(itemView.bindingVariable(), viewModel);
+ }
+ }
+ }
+}
+
diff --git a/library/src/main/java/com/kelin/mvvmlight/bindingadapter/viewpager/ViewBindingAdapter.java b/library/src/main/java/com/kelin/mvvmlight/bindingadapter/viewpager/ViewBindingAdapter.java
new file mode 100644
index 0000000..eb6a464
--- /dev/null
+++ b/library/src/main/java/com/kelin/mvvmlight/bindingadapter/viewpager/ViewBindingAdapter.java
@@ -0,0 +1,58 @@
+package com.kelin.mvvmlight.bindingadapter.viewpager;
+
+import android.databinding.BindingAdapter;
+import android.support.v4.view.ViewPager;
+
+import com.kelin.mvvmlight.command.ReplyCommand;
+
+/**
+ * Created by kelin on 16-6-1.
+ */
+public class ViewBindingAdapter {
+ @BindingAdapter(value = {"onPageScrolledCommand", "onPageSelectedCommand", "onPageScrollStateChangedCommand"}, requireAll = false)
+ public static void onScrollChangeCommand(final ViewPager viewPager,
+ final ReplyCommand onPageScrolledCommand,
+ final ReplyCommand onPageSelectedCommand,
+ final ReplyCommand onPageScrollStateChangedCommand) {
+ viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
+ private int state;
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ if (onPageScrolledCommand != null) {
+ onPageScrolledCommand.execute(new ViewPagerDataWrapper(position, positionOffset, positionOffsetPixels, state));
+ }
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ if (onPageSelectedCommand != null) {
+ onPageSelectedCommand.execute(position);
+ }
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ this.state = state;
+ if (onPageScrollStateChangedCommand != null) {
+ onPageScrollStateChangedCommand.execute(state);
+ }
+ }
+ });
+
+ }
+
+ public static class ViewPagerDataWrapper {
+ public float positionOffset;
+ public float position;
+ public int positionOffsetPixels;
+ public int state;
+
+ public ViewPagerDataWrapper(float position, float positionOffset, int positionOffsetPixels, int state) {
+ this.positionOffset = positionOffset;
+ this.position = position;
+ this.positionOffsetPixels = positionOffsetPixels;
+ this.state = state;
+ }
+ }
+}
diff --git a/library/src/main/java/com/kelin/mvvmlight/bindingadapter/webview/ViewBindingAdapter.java b/library/src/main/java/com/kelin/mvvmlight/bindingadapter/webview/ViewBindingAdapter.java
new file mode 100644
index 0000000..c21507b
--- /dev/null
+++ b/library/src/main/java/com/kelin/mvvmlight/bindingadapter/webview/ViewBindingAdapter.java
@@ -0,0 +1,17 @@
+package com.kelin.mvvmlight.bindingadapter.webview;
+
+import android.databinding.BindingAdapter;
+import android.text.TextUtils;
+import android.webkit.WebView;
+
+/**
+ * Created by kelin on 16-4-29.
+ */
+public class ViewBindingAdapter {
+ @BindingAdapter({"render"})
+ public static void loadHtml(WebView webView, final String html) {
+ if (!TextUtils.isEmpty(html)) {
+ webView.loadDataWithBaseURL(null, html, "text/html", "UTF-8", null);
+ }
+ }
+}
diff --git a/library/src/main/java/com/kelin/mvvmlight/command/ReplyCommand.java b/library/src/main/java/com/kelin/mvvmlight/command/ReplyCommand.java
new file mode 100644
index 0000000..4078765
--- /dev/null
+++ b/library/src/main/java/com/kelin/mvvmlight/command/ReplyCommand.java
@@ -0,0 +1,67 @@
+package com.kelin.mvvmlight.command;
+
+import rx.functions.Action0;
+import rx.functions.Action1;
+import rx.functions.Func0;
+
+/**
+ * Created by kelin on 15-8-4.
+ */
+public class ReplyCommand {
+
+ private Action0 execute0;
+ private Action1 execute1;
+
+ private Func0 canExecute0;
+
+ public ReplyCommand(Action0 execute) {
+ this.execute0 = execute;
+ }
+
+
+ public ReplyCommand(Action1 execute) {
+ this.execute1 = execute;
+ }
+
+ /**
+ *
+ * @param execute callback for event
+ * @param canExecute0 if this function return true the action execute would be invoked! otherwise would't invoked!
+ */
+ public ReplyCommand(Action0 execute, Func0 canExecute0) {
+ this.execute0 = execute;
+ this.canExecute0 = canExecute0;
+ }
+
+ /**
+ *
+ * @param execute callback for event,this callback need a params
+ * @param canExecute0 if this function return true the action execute would be invoked! otherwise would't invoked!
+ */
+ public ReplyCommand(Action1 execute, Func0 canExecute0) {
+ this.execute1 = execute;
+ this.canExecute0 = canExecute0;
+ }
+
+
+ public void execute() {
+ if (execute0 != null && canExecute0()) {
+ execute0.call();
+ }
+ }
+
+ private boolean canExecute0() {
+ if (canExecute0 == null) {
+ return true;
+ }
+ return canExecute0.call();
+ }
+
+
+ public void execute(T parameter) {
+ if (execute1 != null && canExecute0()) {
+ execute1.call(parameter);
+ }
+ }
+
+}
diff --git a/library/src/main/java/com/kelin/mvvmlight/command/ResponseCommand.java b/library/src/main/java/com/kelin/mvvmlight/command/ResponseCommand.java
new file mode 100644
index 0000000..6773849
--- /dev/null
+++ b/library/src/main/java/com/kelin/mvvmlight/command/ResponseCommand.java
@@ -0,0 +1,66 @@
+package com.kelin.mvvmlight.command;
+
+import com.kelin.mvvmlight.command.ReplyCommand;
+
+import rx.functions.Func0;
+import rx.functions.Func1;
+
+/**
+ * Created by kelin on 15-8-4.
+ */
+public class ResponseCommand {
+
+ private Func0 execute0;
+ private Func1 execute1;
+
+ private Func0 canExecute0;
+
+ /**
+ * like {@link ReplyCommand},but ResponseCommand can return result when command has executed!
+ * @param execute function to execute when event occur.
+ */
+ public ResponseCommand(Func0 execute) {
+ this.execute0 = execute;
+ }
+
+
+ public ResponseCommand(Func1 execute) {
+ this.execute1 = execute;
+ }
+
+
+ public ResponseCommand(Func0 execute, Func0 canExecute0) {
+ this.execute0 = execute;
+ this.canExecute0 = canExecute0;
+ }
+
+
+ public ResponseCommand(Func1 execute, Func0 canExecute0) {
+ this.execute1 = execute;
+ this.canExecute0 = canExecute0;
+ }
+
+
+ public R execute() {
+ if (execute0 != null && canExecute0()) {
+ return execute0.call();
+ }
+ return null;
+ }
+
+ private boolean canExecute0() {
+ if (canExecute0 == null) {
+ return true;
+ }
+ return canExecute0.call();
+ }
+
+
+ public R execute(T parameter) {
+ if (execute1 != null && canExecute0()) {
+ return execute1.call(parameter);
+ }
+ return null;
+ }
+
+}
diff --git a/library/src/main/java/com/kelin/mvvmlight/messenger/Messenger.java b/library/src/main/java/com/kelin/mvvmlight/messenger/Messenger.java
new file mode 100644
index 0000000..2c324b2
--- /dev/null
+++ b/library/src/main/java/com/kelin/mvvmlight/messenger/Messenger.java
@@ -0,0 +1,608 @@
+package com.kelin.mvvmlight.messenger;
+
+
+import com.google.repacked.antlr.v4.runtime.misc.Nullable;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+import rx.functions.Action0;
+import rx.functions.Action1;
+
+/**
+ * Created by kelin on 15-8-14.
+ */
+public class Messenger {
+
+ private static Messenger defaultInstance;
+
+ private HashMap> recipientsOfSubclassesAction;
+
+ private HashMap> recipientsStrictAction;
+
+ public static Messenger getDefault() {
+ if (defaultInstance == null) {
+ defaultInstance = new Messenger();
+ }
+ return defaultInstance;
+ }
+
+
+ public static void overrideDefault(Messenger newMessenger) {
+ defaultInstance = newMessenger;
+ }
+
+ public static void reset() {
+ defaultInstance = null;
+ }
+
+ /**
+ *
+ * @param recipient the receiver,if register in activity the recipient always set "this",
+ * and "Messenger.getDefault().unregister(this)" in onDestroy,if in ViewModel,
+ * you can also register with Activity context and also in onDestroy to unregister.
+ * @param action do something on message received
+ */
+ public void register(Object recipient, Action0 action) {
+ register(recipient, null, false, action);
+ }
+
+ /**
+ *
+ * @param recipient the receiver,if register in activity the recipient always set "this",
+ * and "Messenger.getDefault().unregister(this)" in onDestroy,if in ViewModel,
+ * you can also register with Activity context and also in onDestroy to unregister.
+ * @param receiveDerivedMessagesToo whether Derived class of recipient can receive the message
+ * @param action do something on message received
+ */
+ public void register(Object recipient, boolean receiveDerivedMessagesToo, Action0 action) {
+ register(recipient, null, receiveDerivedMessagesToo, action);
+ }
+
+ /**
+ *
+ * @param recipient the receiver,if register in activity the recipient always set "this",
+ * and "Messenger.getDefault().unregister(this)" in onDestroy,if in ViewModel,
+ * you can also register with Activity context and also in onDestroy to unregister.
+ * @param token register with a unique token,when a messenger send a msg with same token,it will receive this msg
+ * @param action do something on message received
+ */
+ public void register(Object recipient, Object token, Action0 action) {
+ register(recipient, token, false, action);
+ }
+ /**
+ *
+ * @param recipient the receiver,if register in activity the recipient always set "this",
+ * and "Messenger.getDefault().unregister(this)" in onDestroy,if in ViewModel,
+ * you can also register with Activity context and also in onDestroy to unregister.
+ * @param token register with a unique token,when a messenger send a msg with same token,it will receive this msg
+ * @param receiveDerivedMessagesToo whether Derived class of recipient can receive the message
+ * @param action do something on message received
+ */
+ public void register(Object recipient, Object token, boolean receiveDerivedMessagesToo, Action0 action) {
+
+ Type messageType = NotMsgType.class;
+
+ HashMap> recipients;
+
+ if (receiveDerivedMessagesToo) {
+ if (recipientsOfSubclassesAction == null) {
+ recipientsOfSubclassesAction = new HashMap>();
+ }
+
+ recipients = recipientsOfSubclassesAction;
+ } else {
+ if (recipientsStrictAction == null) {
+ recipientsStrictAction = new HashMap>();
+ }
+
+ recipients = recipientsStrictAction;
+ }
+
+ List list;
+
+ if (!recipients.containsKey(messageType)) {
+ list = new ArrayList();
+ recipients.put(messageType, list);
+ } else {
+ list = recipients.get(messageType);
+ }
+
+ WeakAction weakAction = new WeakAction(recipient, action);
+
+ WeakActionAndToken item = new WeakActionAndToken(weakAction, token);
+ list.add(item);
+ cleanup();
+ }
+
+ /**
+ *
+ * @param recipient {@link com.kelin.mvvmlight.messenger.Messenger#register(Object, Action0)}
+ * @param tClass class of T
+ * @param action this action has one params that type of tClass
+ * @param message data type
+ */
+ public void register(Object recipient, Class tClass, Action1 action) {
+ register(recipient, null, false, action, tClass);
+ }
+
+ /**
+ * see {@link com.kelin.mvvmlight.messenger.Messenger#register(Object, Class, Action1)}
+ * @param recipient receiver of message
+ * @param receiveDerivedMessagesToo whether derived class of recipient can receive the message
+ * @param tClass class of T
+ * @param action this action has one params that type of tClass
+ * @param message data type
+ */
+ public void register(Object recipient, boolean receiveDerivedMessagesToo, Class tClass, Action1 action) {
+ register(recipient, null, receiveDerivedMessagesToo, action, tClass);
+ }
+
+ /**
+ * see {@link com.kelin.mvvmlight.messenger.Messenger#register(Object, Object, Action0)}
+ * @param recipient receiver of message
+ * @param token register with a unique token,when a messenger send a msg with same token,it will receive this msg
+ * @param tClass class of T for Action1
+ * @param action this action has one params that type of tClass
+ * @param message data type
+ */
+ public void register(Object recipient, Object token, Class tClass, Action1 action) {
+ register(recipient, token, false, action, tClass);
+ }
+
+ /**
+ * see {@link com.kelin.mvvmlight.messenger.Messenger#register(Object, Object, Class, Action1)}
+ * @param recipient receiver of message
+ * @param token register with a unique token,when a messenger send a msg with same token,it will receive this msg
+ * @param receiveDerivedMessagesToo whether derived class of recipient can receive the message
+ * @param action this action has one params that type of tClass
+ * @param tClass class of T for Action1
+ * @param message data type
+ */
+ public void register(Object recipient, Object token, boolean receiveDerivedMessagesToo, Action1 action, Class tClass) {
+
+ Type messageType = tClass;
+
+ HashMap> recipients;
+
+ if (receiveDerivedMessagesToo) {
+ if (recipientsOfSubclassesAction == null) {
+ recipientsOfSubclassesAction = new HashMap>();
+ }
+
+ recipients = recipientsOfSubclassesAction;
+ } else {
+ if (recipientsStrictAction == null) {
+ recipientsStrictAction = new HashMap>();
+ }
+
+ recipients = recipientsStrictAction;
+ }
+
+ List list;
+
+ if (!recipients.containsKey(messageType)) {
+ list = new ArrayList();
+ recipients.put(messageType, list);
+ } else {
+ list = recipients.get(messageType);
+ }
+
+ WeakAction weakAction = new WeakAction(recipient, action);
+
+ WeakActionAndToken item = new WeakActionAndToken(weakAction, token);
+ list.add(item);
+ cleanup();
+ }
+
+
+ private void cleanup() {
+ cleanupList(recipientsOfSubclassesAction);
+ cleanupList(recipientsStrictAction);
+ }
+
+ /**
+ *
+ * @param token send with a unique token,when a receiver has register with same token,it will receive this msg
+ */
+ public void sendNoMsg(Object token) {
+ sendToTargetOrType(null, token);
+ }
+
+ /**
+ * send to recipient directly with has not any message
+ * @param target Messenger.getDefault().register(this, ..) in a activity,if target set this activity
+ * it will receive the message
+ */
+ public void sendNoMsgToTarget(Object target) {
+ sendToTargetOrType(target.getClass(), null);
+ }
+
+ /**
+ * send message to target with token,when a receiver has register with same token,it will receive this msg
+ * @param token send with a unique token,when a receiver has register with same token,it will receive this msg
+ * @param target send to recipient directly with has not any message,
+ * Messenger.getDefault().register(this, ..) in a activity,if target set this activity
+ * it will receive the message
+ */
+ public void sendNoMsgToTargetWithToken(Object token, Object target) {
+ sendToTargetOrType(target.getClass(), token);
+ }
+
+ /**
+ * send the message type of T, all receiver can receive the message
+ * @param message any object can to be a message
+ * @param message data type
+ */
+ public void send(T message) {
+ sendToTargetOrType(message, null, null);
+ }
+
+ /**
+ * send the message type of T, all receiver can receive the message
+ * @param message any object can to be a message
+ * @param token send with a unique token,when a receiver has register with same token,it will receive this message
+ * @param message data type
+ */
+ public void send(T message, Object token) {
+ sendToTargetOrType(message, null, token);
+ }
+
+ /**
+ * send message to recipient directly
+ * @param message any object can to be a message
+ * @param target send to recipient directly with has not any message,
+ * Messenger.getDefault().register(this, ..) in a activity,if target set this activity
+ * it will receive the message
+ * @param message data type
+ * @param target
+ */
+ public void sendToTarget(T message, R target) {
+ sendToTargetOrType(message, target.getClass(), null);
+ }
+
+ /**
+ * Unregister the receiver such as:
+ * Messenger.getDefault().unregister(this)" in onDestroy in the Activity is required avoid to memory leak!
+ * @param recipient receiver of message
+ */
+ public void unregister(Object recipient) {
+ unregisterFromLists(recipient, recipientsOfSubclassesAction);
+ unregisterFromLists(recipient, recipientsStrictAction);
+ cleanup();
+ }
+
+
+ public void unregister(Object recipient, Object token) {
+ unregisterFromLists(recipient, token, null, recipientsStrictAction);
+ unregisterFromLists(recipient, token, null, recipientsOfSubclassesAction);
+ cleanup();
+ }
+
+
+
+ private static void sendToList(
+ T message,
+ Collection list,
+ Type messageTargetType,
+ Object token) {
+ if (list != null) {
+ // Clone to protect from people registering in a "receive message" method
+ // Bug correction Messaging BL0004.007
+ ArrayList listClone = new ArrayList<>();
+ listClone.addAll(list);
+
+ for (WeakActionAndToken item : listClone) {
+ WeakAction executeAction = item.getAction();
+ if (executeAction != null
+ && item.getAction().isLive()
+ && item.getAction().getTarget() != null
+ && (messageTargetType == null
+ || item.getAction().getTarget().getClass() == messageTargetType
+ || classImplements(item.getAction().getTarget().getClass(), messageTargetType))
+ && ((item.getToken() == null && token == null)
+ || item.getToken() != null && item.getToken().equals(token))) {
+ executeAction.execute(message);
+ }
+ }
+ }
+ }
+
+ private static void unregisterFromLists(Object recipient, HashMap> lists) {
+ if (recipient == null
+ || lists == null
+ || lists.size() == 0) {
+ return;
+ }
+ synchronized (lists) {
+ for (Type messageType : lists.keySet()) {
+ for (WeakActionAndToken item : lists.get(messageType)) {
+ WeakAction weakAction = item.getAction();
+
+ if (weakAction != null
+ && recipient == weakAction.getTarget()) {
+ weakAction.markForDeletion();
+ }
+ }
+ }
+ }
+ cleanupList(lists);
+ }
+
+ private static void unregisterFromLists(
+ Object recipient,
+ Action1 action,
+ HashMap> lists,
+ Class tClass) {
+ Type messageType = tClass;
+
+ if (recipient == null
+ || lists == null
+ || lists.size() == 0
+ || !lists.containsKey(messageType)) {
+ return;
+ }
+
+ synchronized (lists) {
+ for (WeakActionAndToken item : lists.get(messageType)) {
+ WeakAction weakActionCasted = (WeakAction) item.getAction();
+
+ if (weakActionCasted != null
+ && recipient == weakActionCasted.getTarget()
+ && (action == null
+ || action == weakActionCasted.getAction1())) {
+ item.getAction().markForDeletion();
+ }
+ }
+ }
+ }
+
+ private static void unregisterFromLists(
+ Object recipient,
+ Action0 action,
+ HashMap> lists
+ ) {
+ Type messageType = NotMsgType.class;
+
+ if (recipient == null
+ || lists == null
+ || lists.size() == 0
+ || !lists.containsKey(messageType)) {
+ return;
+ }
+
+ synchronized (lists) {
+ for (WeakActionAndToken item : lists.get(messageType)) {
+ WeakAction weakActionCasted = (WeakAction) item.getAction();
+
+ if (weakActionCasted != null
+ && recipient == weakActionCasted.getTarget()
+ && (action == null
+ || action == weakActionCasted.getAction())) {
+ item.getAction().markForDeletion();
+ }
+ }
+ }
+ }
+
+
+ private static void unregisterFromLists(
+ Object recipient,
+ Object token,
+ Action1 action,
+ HashMap> lists, Class tClass) {
+ Type messageType = tClass;
+
+ if (recipient == null
+ || lists == null
+ || lists.size() == 0
+ || !lists.containsKey(messageType)) {
+ return;
+ }
+
+ synchronized (lists) {
+ for (WeakActionAndToken item : lists.get(messageType)) {
+ WeakAction weakActionCasted = (WeakAction) item.getAction();
+
+ if (weakActionCasted != null
+ && recipient == weakActionCasted.getTarget()
+ && (action == null
+ || action == weakActionCasted.getAction1())
+ && (token == null
+ || token.equals(item.getToken()))) {
+ item.getAction().markForDeletion();
+ }
+ }
+ }
+ }
+
+ private static void unregisterFromLists(
+ Object recipient,
+ Object token,
+ Action0 action,
+ HashMap> lists) {
+ Type messageType = NotMsgType.class;
+
+ if (recipient == null
+ || lists == null
+ || lists.size() == 0
+ || !lists.containsKey(messageType)) {
+ return;
+ }
+
+ synchronized (lists) {
+ for (WeakActionAndToken item : lists.get(messageType)) {
+ WeakAction weakActionCasted = (WeakAction) item.getAction();
+
+ if (weakActionCasted != null
+ && recipient == weakActionCasted.getTarget()
+ && (action == null
+ || action == weakActionCasted.getAction())
+ && (token == null
+ || token.equals(item.getToken()))) {
+ item.getAction().markForDeletion();
+ }
+ }
+ }
+ }
+
+ private static boolean classImplements(Type instanceType, Type interfaceType) {
+ if (interfaceType == null
+ || instanceType == null) {
+ return false;
+ }
+ Class[] interfaces = ((Class) instanceType).getInterfaces();
+ for (Class currentInterface : interfaces) {
+ if (currentInterface == interfaceType) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static void cleanupList(HashMap> lists) {
+ if (lists == null) {
+ return;
+ }
+ for (Iterator it = lists.entrySet().iterator(); it.hasNext(); ) {
+ Object key = it.next();
+ List itemList = lists.get(key);
+ if (itemList != null) {
+ for (WeakActionAndToken item : itemList) {
+ if (item.getAction() == null
+ || !item.getAction().isLive()) {
+ itemList.remove(item);
+ }
+ }
+ if (itemList.size() == 0) {
+ lists.remove(key);
+ }
+ }
+ }
+ }
+
+ private void sendToTargetOrType(Type messageTargetType, Object token) {
+ Class messageType = NotMsgType.class;
+ if (recipientsOfSubclassesAction != null) {
+ // Clone to protect from people registering in a "receive message" method
+ // Bug correction Messaging BL0008.002
+// var listClone = recipientsOfSubclassesAction.Keys.Take(_recipientsOfSubclassesAction.Count()).ToList();
+ List listClone = new ArrayList<>();
+ listClone.addAll(recipientsOfSubclassesAction.keySet());
+ for (Type type : listClone) {
+ List list = null;
+
+ if (messageType == type
+ || ((Class) type).isAssignableFrom(messageType)
+ || classImplements(messageType, type)) {
+ list = recipientsOfSubclassesAction.get(type);
+ }
+
+ sendToList(list, messageTargetType, token);
+ }
+ }
+
+ if (recipientsStrictAction != null) {
+ if (recipientsStrictAction.containsKey(messageType)) {
+ List list = recipientsStrictAction.get(messageType);
+ sendToList(list, messageTargetType, token);
+ }
+ }
+
+ cleanup();
+ }
+
+ private static void sendToList(
+ Collection list,
+ Type messageTargetType,
+ Object token) {
+ if (list != null) {
+ // Clone to protect from people registering in a "receive message" method
+ // Bug correction Messaging BL0004.007
+ ArrayList listClone = new ArrayList<>();
+ listClone.addAll(list);
+
+ for (WeakActionAndToken item : listClone) {
+ WeakAction executeAction = item.getAction();
+ if (executeAction != null
+ && item.getAction().isLive()
+ && item.getAction().getTarget() != null
+ && (messageTargetType == null
+ || item.getAction().getTarget().getClass() == messageTargetType
+ || classImplements(item.getAction().getTarget().getClass(), messageTargetType))
+ && ((item.getToken() == null && token == null)
+ || item.getToken() != null && item.getToken().equals(token))) {
+ executeAction.execute();
+ }
+ }
+ }
+ }
+
+ private void sendToTargetOrType(T message, Type messageTargetType, Object token) {
+ Class messageType = message.getClass();
+
+
+ if (recipientsOfSubclassesAction != null) {
+ // Clone to protect from people registering in a "receive message" method
+ // Bug correction Messaging BL0008.002
+// var listClone = recipientsOfSubclassesAction.Keys.Take(_recipientsOfSubclassesAction.Count()).ToList();
+ List listClone = new ArrayList<>();
+ listClone.addAll(recipientsOfSubclassesAction.keySet());
+ for (Type type : listClone) {
+ List list = null;
+
+ if (messageType == type
+ || ((Class) type).isAssignableFrom(messageType)
+ || classImplements(messageType, type)) {
+ list = recipientsOfSubclassesAction.get(type);
+ }
+
+ sendToList(message, list, messageTargetType, token);
+ }
+ }
+
+ if (recipientsStrictAction != null) {
+ if (recipientsStrictAction.containsKey(messageType)) {
+ List list = recipientsStrictAction.get(messageType);
+ sendToList(message, list, messageTargetType, token);
+ }
+ }
+
+ cleanup();
+ }
+
+ private class WeakActionAndToken {
+ private WeakAction action;
+ private Object token;
+
+ public WeakActionAndToken(WeakAction action, Object token) {
+ this.action = action;
+ this.token = token;
+ }
+
+ public WeakAction getAction() {
+ return action;
+ }
+
+ public void setAction(WeakAction action) {
+ this.action = action;
+ }
+
+ public Object getToken() {
+ return token;
+ }
+
+ public void setToken(Object token) {
+ this.token = token;
+ }
+ }
+
+ public static class NotMsgType {
+
+ }
+}
diff --git a/library/src/main/java/com/kelin/mvvmlight/messenger/WeakAction.java b/library/src/main/java/com/kelin/mvvmlight/messenger/WeakAction.java
new file mode 100644
index 0000000..9667d7a
--- /dev/null
+++ b/library/src/main/java/com/kelin/mvvmlight/messenger/WeakAction.java
@@ -0,0 +1,74 @@
+package com.kelin.mvvmlight.messenger;
+
+import java.lang.ref.WeakReference;
+
+import rx.functions.Action0;
+import rx.functions.Action1;
+
+/**
+ * Created by kelin on 15-8-14.
+ */
+public class WeakAction {
+ private Action0 action;
+ private Action1 action1;
+ private boolean isLive;
+ private Object target;
+ private WeakReference reference;
+
+ public WeakAction(Object target, Action0 action) {
+ reference = new WeakReference(target);
+ this.action = action;
+
+ }
+
+ public WeakAction(Object target, Action1 action1) {
+ reference = new WeakReference(target);
+ this.action1 = action1;
+ }
+
+ public void execute() {
+ if (action != null && isLive()) {
+ action.call();
+ }
+ }
+
+ public void execute(T parameter) {
+ if (action1 != null
+ && isLive()) {
+ action1.call(parameter);
+ }
+ }
+
+ public void markForDeletion() {
+ reference.clear();
+ reference = null;
+ action = null;
+ action1 = null;
+ }
+
+ public Action0 getAction() {
+ return action;
+ }
+
+ public Action1 getAction1() {
+ return action1;
+ }
+
+ public boolean isLive() {
+ if (reference == null) {
+ return false;
+ }
+ if (reference.get() == null) {
+ return false;
+ }
+ return true;
+ }
+
+
+ public Object getTarget() {
+ if (reference != null) {
+ return reference.get();
+ }
+ return null;
+ }
+}
diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..c4d3073
--- /dev/null
+++ b/library/src/main/res/values/attrs.xml
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library/src/main/res/values/strings.xml b/library/src/main/res/values/strings.xml
new file mode 100644
index 0000000..416d3ea
--- /dev/null
+++ b/library/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ MVVMLight
+
diff --git a/library/src/test/java/com/kelin/mvvmlight/ExampleUnitTest.java b/library/src/test/java/com/kelin/mvvmlight/ExampleUnitTest.java
new file mode 100644
index 0000000..f346cb5
--- /dev/null
+++ b/library/src/test/java/com/kelin/mvvmlight/ExampleUnitTest.java
@@ -0,0 +1,15 @@
+package com.kelin.mvvmlight;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * To work on unit tests, switch the Test Artifact in the Build Variants view.
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() throws Exception {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/sample/.gitignore b/sample/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/sample/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/sample/build.gradle b/sample/build.gradle
new file mode 100644
index 0000000..a5b7f8d
--- /dev/null
+++ b/sample/build.gradle
@@ -0,0 +1,44 @@
+apply plugin: 'com.android.application'
+apply plugin: 'me.tatarka.retrolambda'
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion "23.0.2"
+
+ defaultConfig {
+ applicationId "com.kelin.mvvmlight.zhihu"
+ minSdkVersion 15
+ targetSdkVersion 23
+ versionCode 1
+ versionName "1.0"
+ }
+ dataBinding {
+ enabled true
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility 1.8
+ targetCompatibility 1.8
+ }
+}
+
+dependencies {
+ compile project(':library')
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ testCompile 'junit:junit:4.12'
+ compile 'com.android.support:appcompat-v7:23.3.0'
+ compile 'com.android.support:design:23.3.0'
+ compile 'com.trello:rxlifecycle:0.5.0'
+ // If you want pre-written Activities and Fragments you can subclass as providers
+ compile 'com.trello:rxlifecycle-components:0.5.0'
+ compile 'com.squareup.retrofit2:retrofit:2.0.1'
+ compile 'com.squareup.retrofit2:converter-gson:2.0.1'
+ compile 'com.squareup.retrofit2:adapter-rxjava:2.0.1'
+ compile 'com.facebook.fresco:fresco:0.9.0+'
+ compile 'com.mcxiaoke.viewpagerindicator:library:2.4.1'
+}
diff --git a/sample/proguard-rules.pro b/sample/proguard-rules.pro
new file mode 100644
index 0000000..faf9f17
--- /dev/null
+++ b/sample/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/kelin/android-sdk-macosx/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/sample/src/androidTest/java/com/kelin/mvvmlight/zhihu/ApplicationTest.java b/sample/src/androidTest/java/com/kelin/mvvmlight/zhihu/ApplicationTest.java
new file mode 100644
index 0000000..fbc9f78
--- /dev/null
+++ b/sample/src/androidTest/java/com/kelin/mvvmlight/zhihu/ApplicationTest.java
@@ -0,0 +1,13 @@
+package com.kelin.mvvmlight.zhihu;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..bbddf4f
--- /dev/null
+++ b/sample/src/main/AndroidManifest.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/java/com/kelin/mvvmlight/zhihu/MainActivity.java b/sample/src/main/java/com/kelin/mvvmlight/zhihu/MainActivity.java
new file mode 100644
index 0000000..c500744
--- /dev/null
+++ b/sample/src/main/java/com/kelin/mvvmlight/zhihu/MainActivity.java
@@ -0,0 +1,130 @@
+package com.kelin.mvvmlight.zhihu;
+
+import android.databinding.DataBindingUtil;
+import android.databinding.ViewDataBinding;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.support.design.widget.AppBarLayout;
+import android.support.design.widget.CollapsingToolbarLayout;
+import android.support.design.widget.NavigationView;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.view.ViewPager;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.widget.Toolbar;
+import android.text.SpannableString;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.kelin.mvvmlight.messenger.Messenger;
+import com.kelin.mvvmlight.zhihu.news.NewsListFragment;
+import com.kelin.mvvmlight.zhihu.utils.AlphaForegroundColorSpan;
+import com.kelin.mvvmlight.zhihu.utils.ViewUtils;
+import com.trello.rxlifecycle.components.support.RxAppCompatActivity;
+import com.viewpagerindicator.CirclePageIndicator;
+
+public class MainActivity extends RxAppCompatActivity
+ implements NavigationView.OnNavigationItemSelectedListener {
+ private AlphaForegroundColorSpan alphaForegroundColorSpan;
+ private SpannableString actionBarTitleSpan;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ViewDataBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
+ binding.setVariable(com.kelin.mvvmlight.zhihu.BR.viewModel, new MainViewModel(this));
+ CollapsingToolbarLayout collapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ getSupportActionBar().setDisplayShowTitleEnabled(true);
+
+ ((AppBarLayout) findViewById(R.id.appBarLayout)).addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
+ @Override
+ public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
+ int height = appBarLayout.getHeight() - getSupportActionBar().getHeight() - ViewUtils.getStatusBarHeight(MainActivity.this);
+ int alpha = 255 * (0 - verticalOffset) / height;
+ collapsingToolbarLayout.setExpandedTitleColor(Color.argb(0, 255, 255, 255));
+ collapsingToolbarLayout.setCollapsedTitleTextColor(Color.argb(alpha, 255, 255, 255));
+ }
+ });
+
+ CirclePageIndicator circlePageIndicator = (CirclePageIndicator) findViewById(R.id.indicator);
+ ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
+
+ // Indicator must setViewPager after setAdapter,but data for ViewPager is load in other ViewModel
+ Messenger.getDefault().register(this, MainViewModel.TOKEN_UPDATE_INDICATOR, () ->
+ circlePageIndicator.setViewPager(viewPager));
+
+ DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
+ ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
+ this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
+ drawer.setDrawerListener(toggle);
+ toggle.syncState();
+
+ NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
+ navigationView.setNavigationItemSelectedListener(this);
+
+ NewsListFragment fragment = new NewsListFragment();
+ getFragmentManager().beginTransaction()
+ .replace(R.id.content, fragment)
+ .commit();
+ }
+
+ @Override
+ public void onBackPressed() {
+ DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
+ if (drawer.isDrawerOpen(GravityCompat.START)) {
+ drawer.closeDrawer(GravityCompat.START);
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_settings) {
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ @SuppressWarnings("StatementWithEmptyBody")
+ @Override
+ public boolean onNavigationItemSelected(MenuItem item) {
+ // Handle navigation view item clicks here.
+ int id = item.getItemId();
+
+ if (id == R.id.nav_gallery) {
+ NewsListFragment fragment = new NewsListFragment();
+ getFragmentManager().beginTransaction()
+ .replace(R.id.content, fragment)
+ .commit();
+
+ }
+
+ DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
+ drawer.closeDrawer(GravityCompat.START);
+ return true;
+ }
+
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ Messenger.getDefault().unregister(this);
+ }
+}
diff --git a/sample/src/main/java/com/kelin/mvvmlight/zhihu/MainViewModel.java b/sample/src/main/java/com/kelin/mvvmlight/zhihu/MainViewModel.java
new file mode 100644
index 0000000..4540649
--- /dev/null
+++ b/sample/src/main/java/com/kelin/mvvmlight/zhihu/MainViewModel.java
@@ -0,0 +1,45 @@
+package com.kelin.mvvmlight.zhihu;
+
+import android.app.Activity;
+import android.content.Context;
+import android.databinding.ObservableArrayList;
+import android.databinding.ObservableList;
+
+import com.kelin.mvvmlight.base.ViewModel;
+import com.kelin.mvvmlight.messenger.Messenger;
+import com.kelin.mvvmlight.zhihu.news.NewsViewModel;
+import com.kelin.mvvmlight.zhihu.news.TopNewsService;
+
+import java.util.concurrent.TimeUnit;
+
+import me.tatarka.bindingcollectionadapter.ItemView;
+import rx.Observable;
+
+/**
+ * Created by kelin on 16-4-28.
+ */
+public class MainViewModel implements ViewModel {
+ // Token to Messenger append package name to be unique
+ public static final String TOKEN_UPDATE_INDICATOR = "token_update_indicator" + ZhiHuApp.sPackageName;
+
+ //context
+ private Context context;
+
+ // viewModel for recycler header viewPager
+ public final ItemView topItemView = ItemView.of(com.kelin.mvvmlight.zhihu.BR.viewModel, R.layout.viewpager_item_top_news);
+ public final ObservableList topItemViewModel = new ObservableArrayList<>();
+
+
+ public MainViewModel(Activity activity) {
+ context=activity;
+ Messenger.getDefault().register(activity, NewsViewModel.TOKEN_TOP_NEWS_FINISH, TopNewsService.News.class, (news) -> {
+ Observable.just(news)
+ .doOnNext(m -> topItemViewModel.clear())
+ .flatMap(n -> Observable.from(n.getTop_stories()))
+ .doOnNext(m -> topItemViewModel.add(new TopItemViewModel(context,m)))
+ .toList()
+ .subscribe((l) -> Messenger.getDefault().sendNoMsgToTargetWithToken(TOKEN_UPDATE_INDICATOR, activity));
+ });
+
+ }
+}
diff --git a/sample/src/main/java/com/kelin/mvvmlight/zhihu/TopItemViewModel.java b/sample/src/main/java/com/kelin/mvvmlight/zhihu/TopItemViewModel.java
new file mode 100644
index 0000000..46bb4ee
--- /dev/null
+++ b/sample/src/main/java/com/kelin/mvvmlight/zhihu/TopItemViewModel.java
@@ -0,0 +1,38 @@
+package com.kelin.mvvmlight.zhihu;
+
+import android.content.Context;
+import android.content.Intent;
+import android.databinding.ObservableField;
+
+import com.kelin.mvvmlight.base.ViewModel;
+import com.kelin.mvvmlight.command.ReplyCommand;
+import com.kelin.mvvmlight.zhihu.news.TopNewsService;
+import com.kelin.mvvmlight.zhihu.newsdetail.NewsDetailActivity;
+
+/**
+ * Created by kelin on 16-4-26.
+ */
+public class TopItemViewModel implements ViewModel {
+ //context
+ private Context context;
+
+ //model
+ public TopNewsService.News.TopStoriesBean topStoriesBean;
+
+ //field to presenter
+ public final ObservableField title = new ObservableField<>();
+ public final ObservableField imageUrl = new ObservableField<>();
+
+ public final ReplyCommand topItemClickCommand = new ReplyCommand(() -> {
+ Intent intent = new Intent(context, NewsDetailActivity.class);
+ intent.putExtra(NewsDetailActivity.EXTRA_KEY_NEWS_ID, topStoriesBean.getId());
+ context.startActivity(intent);
+ });
+
+ public TopItemViewModel(Context context, TopNewsService.News.TopStoriesBean topStoriesBean) {
+ this.context = context;
+ this.topStoriesBean = topStoriesBean;
+ title.set(topStoriesBean.getTitle());
+ imageUrl.set(topStoriesBean.getImage());
+ }
+}
diff --git a/sample/src/main/java/com/kelin/mvvmlight/zhihu/ZhiHuApp.java b/sample/src/main/java/com/kelin/mvvmlight/zhihu/ZhiHuApp.java
new file mode 100644
index 0000000..ea47b30
--- /dev/null
+++ b/sample/src/main/java/com/kelin/mvvmlight/zhihu/ZhiHuApp.java
@@ -0,0 +1,31 @@
+package com.kelin.mvvmlight.zhihu;
+
+import android.app.Application;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+
+import com.facebook.drawee.backends.pipeline.Fresco;
+
+/**
+ * Created by kelin on 16-4-12.
+ */
+public class ZhiHuApp extends Application {
+ public static String sPackageName;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Fresco.initialize(this);
+ initPackageName();
+ }
+
+ private void initPackageName() {
+ PackageInfo info;
+ try {
+ info = getApplicationContext().getPackageManager().getPackageInfo(this.getPackageName(), 0);
+ sPackageName = info.packageName;
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/sample/src/main/java/com/kelin/mvvmlight/zhihu/news/NewItemViewModel.java b/sample/src/main/java/com/kelin/mvvmlight/zhihu/news/NewItemViewModel.java
new file mode 100644
index 0000000..fa1d2a2
--- /dev/null
+++ b/sample/src/main/java/com/kelin/mvvmlight/zhihu/news/NewItemViewModel.java
@@ -0,0 +1,54 @@
+package com.kelin.mvvmlight.zhihu.news;
+
+import android.content.Context;
+import android.content.Intent;
+import android.databinding.ObservableField;
+import android.databinding.ObservableInt;
+
+import com.kelin.mvvmlight.base.ViewModel;
+import com.kelin.mvvmlight.command.ReplyCommand;
+import com.kelin.mvvmlight.zhihu.newsdetail.NewsDetailActivity;
+
+/**
+ * Created by kelin on 16-4-26.
+ */
+public class NewItemViewModel implements ViewModel {
+ //context
+ private Context context;
+
+ //model
+ public NewsService.News.StoriesBean storiesBean;
+
+ //field to presenter
+ public final ObservableField title = new ObservableField<>();
+ public final ObservableField imageUrl = new ObservableField<>();
+ public final ObservableField date = new ObservableField<>();
+ public ViewStyle viewStyle = new ViewStyle();
+
+ //Use class viewStyle to wrap field which is binding to style of view
+ public static class ViewStyle {
+ public final ObservableInt titleTextColor = new ObservableInt();
+ }
+
+
+ //command
+ public ReplyCommand itemClickCommand = new ReplyCommand(() -> {
+ this.viewStyle.titleTextColor.set(context.getResources().getColor(android.R.color.darker_gray));
+ Intent intent = new Intent(context, NewsDetailActivity.class);
+ intent.putExtra(NewsDetailActivity.EXTRA_KEY_NEWS_ID, storiesBean.getId());
+ context.startActivity(intent);
+ });
+
+ public NewItemViewModel(Context context, NewsService.News.StoriesBean storiesBean) {
+ this.context = context;
+ this.storiesBean = storiesBean;
+ this.viewStyle.titleTextColor.set(context.getResources().getColor(android.R.color.black));
+ if (storiesBean.getExtraField() != null) {
+ date.set(NewsListHelper.changeDateFormat(storiesBean.getExtraField().getDate(), NewsListHelper.DAY_FORMAT, NewsListHelper.DAY_UI_FORMAT));
+ } else {
+ title.set(storiesBean.getTitle());
+ imageUrl.set(storiesBean.getImages().get(0));
+ }
+ }
+
+}
diff --git a/sample/src/main/java/com/kelin/mvvmlight/zhihu/news/NewsListFragment.java b/sample/src/main/java/com/kelin/mvvmlight/zhihu/news/NewsListFragment.java
new file mode 100644
index 0000000..c3136bb
--- /dev/null
+++ b/sample/src/main/java/com/kelin/mvvmlight/zhihu/news/NewsListFragment.java
@@ -0,0 +1,60 @@
+package com.kelin.mvvmlight.zhihu.news;
+
+import android.content.Context;
+import android.databinding.DataBindingUtil;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.kelin.mvvmlight.zhihu.R;
+import com.kelin.mvvmlight.zhihu.databinding.FragmentNewListBinding;
+import com.trello.rxlifecycle.components.RxFragment;
+
+/**
+ * Created by kelin on 16-4-25.
+ */
+public class NewsListFragment extends RxFragment {
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ FragmentNewListBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_new_list, container, false);
+ binding.setViewModel(new NewsViewModel(this));
+ initView(binding);
+ return binding.getRoot();
+ }
+
+ private void initView(FragmentNewListBinding binding) {
+ binding.recyclerView.addItemDecoration(new DividerItemDecoration(this.getActivity()));
+ }
+
+ public static class DividerItemDecoration extends RecyclerView.ItemDecoration {
+ private Drawable mDivider;
+
+ public DividerItemDecoration(Context context) {
+ mDivider = context.getResources().getDrawable(R.drawable.divider);
+ }
+
+ @Override
+ public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ int left = parent.getPaddingLeft();
+ int right = parent.getWidth() - parent.getPaddingRight();
+
+ int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = parent.getChildAt(i);
+
+ RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
+
+ int top = child.getBottom() + params.bottomMargin;
+ int bottom = top + mDivider.getIntrinsicHeight();
+
+ mDivider.setBounds(left, top, right, bottom);
+ mDivider.draw(c);
+ }
+ }
+ }
+}
diff --git a/sample/src/main/java/com/kelin/mvvmlight/zhihu/news/NewsListHelper.java b/sample/src/main/java/com/kelin/mvvmlight/zhihu/news/NewsListHelper.java
new file mode 100644
index 0000000..291af56
--- /dev/null
+++ b/sample/src/main/java/com/kelin/mvvmlight/zhihu/news/NewsListHelper.java
@@ -0,0 +1,60 @@
+package com.kelin.mvvmlight.zhihu.news;
+
+import android.util.Log;
+
+import com.kelin.mvvmlight.zhihu.retrofit.ApiException;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+import rx.Observable;
+import rx.subjects.ReplaySubject;
+
+/**
+ * Created by kelin on 16-4-26.
+ */
+public class NewsListHelper {
+ public static final SimpleDateFormat DAY_FORMAT = new SimpleDateFormat("yyyyMMdd");
+ public static final SimpleDateFormat DAY_UI_FORMAT = new SimpleDateFormat("yyyy年MM月dd日");
+
+ public static void dealWithResponseError(Observable throwableObservable) {
+
+ ReplaySubject throwableReplaySubject = ReplaySubject.create();
+
+ throwableObservable.subscribe(throwableReplaySubject);
+
+ throwableReplaySubject
+ .repeat(5)
+ .scan((n, c) -> n.getCause())
+ .takeUntil(n -> n.getCause() == null)
+ .filter(n -> n instanceof ApiException)
+ .cast(ApiException.class)
+ .subscribe(e -> Log.v("error", e.msg));
+
+
+ }
+
+ public static boolean isToday(String date) {
+ return new SimpleDateFormat("yyyyMMdd").format(Calendar.getInstance().getTime()).equals(date);
+ }
+
+ public static boolean isTomorrow(String date) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.add(Calendar.DAY_OF_MONTH, 1);
+ return new SimpleDateFormat("yyyyMMdd").format(calendar.getTime()).equals(date);
+ }
+
+ public static String changeDateFormat(String oldDate, SimpleDateFormat oldFormat, SimpleDateFormat newFormat) {
+ Date date;
+ try {
+ date = oldFormat.parse(oldDate);
+ } catch (ParseException e) {
+ e.printStackTrace();
+ return null;
+ }
+ return newFormat.format(date);
+ }
+
+}
diff --git a/sample/src/main/java/com/kelin/mvvmlight/zhihu/news/NewsService.java b/sample/src/main/java/com/kelin/mvvmlight/zhihu/news/NewsService.java
new file mode 100644
index 0000000..af8a3ce
--- /dev/null
+++ b/sample/src/main/java/com/kelin/mvvmlight/zhihu/news/NewsService.java
@@ -0,0 +1,138 @@
+package com.kelin.mvvmlight.zhihu.news;
+
+import java.io.Serializable;
+import java.util.List;
+
+import retrofit2.http.GET;
+import retrofit2.http.Path;
+import rx.Observable;
+
+/**
+ * Created by kelin on 16-4-26.
+ */
+public interface NewsService {
+ @GET("/api/4/news/before/{date}")
+ public Observable getNewsList(@Path("date") String date);
+
+ public class News {
+
+ private String date;
+
+ private List stories;
+
+ public String getDate() {
+ return date;
+ }
+
+ public void setDate(String date) {
+ this.date = date;
+ }
+
+ public List getStories() {
+ return stories;
+ }
+
+ public void setStories(List stories) {
+ this.stories = stories;
+ }
+
+
+ public static class StoriesBean implements Serializable{
+ private ExtraField extraField;
+ private String title;
+ private String ga_prefix;
+ private boolean multipic;
+ private int type;
+ private long id;
+ private List images;
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getGa_prefix() {
+ return ga_prefix;
+ }
+
+ public void setGa_prefix(String ga_prefix) {
+ this.ga_prefix = ga_prefix;
+ }
+
+ public boolean isMultipic() {
+ return multipic;
+ }
+
+ public void setMultipic(boolean multipic) {
+ this.multipic = multipic;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public void setType(int type) {
+ this.type = type;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public List getImages() {
+ return images;
+ }
+
+ public void setImages(List images) {
+ this.images = images;
+ }
+
+ public ExtraField getExtraField() {
+ return extraField;
+ }
+
+ public void setExtraField(ExtraField extraField) {
+ this.extraField = extraField;
+ }
+
+ public static class ExtraField implements Serializable {
+ private boolean isHeader;
+ private String date;
+
+ public ExtraField(boolean isHeader, String date) {
+ this.isHeader = isHeader;
+ this.date = date;
+ }
+
+ public boolean isHeader() {
+ return isHeader;
+ }
+
+ public void setHeader(boolean header) {
+ isHeader = header;
+ }
+
+ public String getDate() {
+ return date;
+ }
+
+ public void setDate(String date) {
+ this.date = date;
+ }
+ }
+
+ public StoriesBean(ExtraField extraField) {
+ this.extraField = extraField;
+ }
+ }
+
+
+ }
+}
diff --git a/sample/src/main/java/com/kelin/mvvmlight/zhihu/news/NewsViewModel.java b/sample/src/main/java/com/kelin/mvvmlight/zhihu/news/NewsViewModel.java
new file mode 100644
index 0000000..7ed6325
--- /dev/null
+++ b/sample/src/main/java/com/kelin/mvvmlight/zhihu/news/NewsViewModel.java
@@ -0,0 +1,178 @@
+package com.kelin.mvvmlight.zhihu.news;
+
+import android.app.Fragment;
+import android.databinding.ObservableArrayList;
+import android.databinding.ObservableBoolean;
+import android.databinding.ObservableList;
+import android.support.v4.util.Pair;
+import android.widget.Toast;
+
+import com.kelin.mvvmlight.BR;
+import com.kelin.mvvmlight.base.ViewModel;
+import com.kelin.mvvmlight.command.ReplyCommand;
+import com.kelin.mvvmlight.messenger.Messenger;
+import com.kelin.mvvmlight.zhihu.R;
+import com.kelin.mvvmlight.zhihu.ZhiHuApp;
+import com.kelin.mvvmlight.zhihu.retrofit.RetrofitProvider;
+import com.trello.rxlifecycle.FragmentLifecycleProvider;
+
+import java.util.Calendar;
+
+import me.tatarka.bindingcollectionadapter.BaseItemViewSelector;
+import me.tatarka.bindingcollectionadapter.ItemView;
+import me.tatarka.bindingcollectionadapter.ItemViewSelector;
+import rx.Notification;
+import rx.Observable;
+import rx.android.schedulers.AndroidSchedulers;
+import rx.schedulers.Schedulers;
+import rx.subjects.BehaviorSubject;
+
+/**
+ * Created by kelin on 16-4-25.
+ */
+public class NewsViewModel implements ViewModel {
+ public static final String TOKEN_TOP_NEWS_FINISH = "token_top_news_finish" + ZhiHuApp.sPackageName;
+
+ //context
+ private Fragment fragment;
+
+ /**
+ * model
+ */
+ private NewsService.News news;
+ private TopNewsService.News topNews;
+
+ /*
+ data for presenter
+ */
+
+ // viewModel for RecyclerView
+ public final ObservableList itemViewModel = new ObservableArrayList<>();
+ // view layout for RecyclerView
+ public final ItemViewSelector itemView = new BaseItemViewSelector() {
+ @Override
+ public void select(ItemView itemView, int position, NewItemViewModel itemViewModel) {
+ itemView.set(BR.viewModel, itemViewModel.storiesBean.getExtraField() != null ? R.layout.listitem_news_header : R.layout.listitem_news);
+ }
+
+ @Override
+ public int viewTypeCount() {
+ return 2;
+ }
+
+ };
+ //collection of view style,wrap to a class to manage conveniently!
+ public final ViewStyle viewStyle = new ViewStyle();
+
+ public class ViewStyle {
+ public final ObservableBoolean isRefreshing = new ObservableBoolean(true);
+ public final ObservableBoolean progressRefreshing = new ObservableBoolean(true);
+ }
+
+ /**
+ * command
+ */
+
+ public final ReplyCommand onRefreshCommand = new ReplyCommand<>(() -> {
+ Observable.just(Calendar.getInstance())
+ .doOnNext(c -> c.add(Calendar.DAY_OF_MONTH, 1))
+ .map(c -> NewsListHelper.DAY_FORMAT.format(c.getTime()))
+ .subscribe(d -> loadTopNews(d));
+ });
+ /**
+ * @param p count of listview items,is unused here!
+ * @params,funciton when return true,the callback just can be invoked!
+ */
+ public final ReplyCommand onLoadMoreCommand = new ReplyCommand<>((p) -> {
+ loadNewsList(news.getDate());
+ });
+
+
+ public NewsViewModel(Fragment fragment) {
+ this.fragment = fragment;
+
+ BehaviorSubject> subject = BehaviorSubject.create();
+ subject.filter(Notification::isOnNext)
+ .subscribe(n -> Toast.makeText(fragment.getActivity(), "load finish!", Toast.LENGTH_SHORT).show());
+
+ Observable.just(Calendar.getInstance())
+ .doOnNext(c -> c.add(Calendar.DAY_OF_MONTH, 1))
+ .map(c -> NewsListHelper.DAY_FORMAT.format(c.getTime()))
+ .subscribe(d -> loadTopNews(d));
+ }
+
+
+ private void loadNewsList(String date) {
+ viewStyle.isRefreshing.set(true);
+
+ Observable> newsListOb =
+ RetrofitProvider.getInstance().create(NewsService.class)
+ .getNewsList(date)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .compose(((FragmentLifecycleProvider) fragment).bindToLifecycle())
+ .materialize().share();
+
+ newsListOb.filter(Notification::isOnNext)
+ .map(n -> n.getValue())
+ .filter(m -> !m.getStories().isEmpty())
+ .doOnNext(m -> Observable.just(m.getDate()).map(d -> new NewsService.News.StoriesBean.ExtraField(true, d))
+ .map(d -> new NewsService.News.StoriesBean(d))
+ .subscribe(d -> itemViewModel.add(new NewItemViewModel(fragment.getActivity(), d))))
+ .doOnNext(m -> news = m)
+ .doAfterTerminate(()-> viewStyle.isRefreshing.set(false))
+ .flatMap(m -> Observable.from(m.getStories()))
+ .subscribe(i -> itemViewModel.add(new NewItemViewModel(fragment.getActivity(), i)));
+
+
+ NewsListHelper.dealWithResponseError(newsListOb.filter(Notification::isOnError)
+ .map(n -> n.getThrowable()));
+
+
+ }
+
+ private void loadTopNews(String date) {
+ viewStyle.isRefreshing.set(true);
+
+ Observable topNewsOb =
+ RetrofitProvider.getInstance().create(TopNewsService.class)
+ .getTopNewsList()
+ .compose(((FragmentLifecycleProvider) fragment).bindToLifecycle());
+
+ Observable newsListOb =
+ RetrofitProvider.getInstance().create(NewsService.class)
+ .getNewsList(date)
+ .compose(((FragmentLifecycleProvider) fragment).bindToLifecycle());
+
+
+ Observable>> combineRequestOb = Observable.combineLatest(topNewsOb, newsListOb, Pair::new)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .materialize().share();
+
+
+ combineRequestOb.filter(Notification::isOnNext)
+ .map(n -> n.getValue())
+ .map(p -> p.first)
+ .filter(m -> !m.getTop_stories().isEmpty())
+ .doOnNext(m -> Observable.just(NewsListHelper.isTomorrow(date)).filter(b -> b).subscribe(b -> itemViewModel.clear()))
+ .subscribe(m -> Messenger.getDefault().send(m, TOKEN_TOP_NEWS_FINISH));
+
+ combineRequestOb.filter(Notification::isOnNext)
+ .map(n -> n.getValue())
+ .map(p -> p.second).filter(m -> !m.getStories().isEmpty())
+ .doOnNext(m -> news = m)
+ .flatMap(m -> Observable.from(m.getStories()))
+ .subscribe(i -> itemViewModel.add(new NewItemViewModel(fragment.getActivity(), i)));
+
+ combineRequestOb.subscribe((n) -> {
+ viewStyle.isRefreshing.set(false);
+ viewStyle.progressRefreshing.set(false);
+ });
+
+ NewsListHelper.dealWithResponseError(combineRequestOb.filter(Notification::isOnError)
+ .map(n -> n.getThrowable()));
+
+ }
+
+}
diff --git a/sample/src/main/java/com/kelin/mvvmlight/zhihu/news/TopNewsService.java b/sample/src/main/java/com/kelin/mvvmlight/zhihu/news/TopNewsService.java
new file mode 100644
index 0000000..792a32c
--- /dev/null
+++ b/sample/src/main/java/com/kelin/mvvmlight/zhihu/news/TopNewsService.java
@@ -0,0 +1,191 @@
+package com.kelin.mvvmlight.zhihu.news;
+
+import java.util.List;
+
+import retrofit2.http.GET;
+import rx.Observable;
+
+/**
+ * Created by kelin on 16-4-26.
+ */
+public interface TopNewsService {
+ @GET("/api/4/news/latest")
+ public Observable getTopNewsList();
+
+ public class News {
+
+ private String date;
+
+ private List stories;
+
+ private List top_stories;
+
+ public String getDate() {
+ return date;
+ }
+
+ public void setDate(String date) {
+ this.date = date;
+ }
+
+ public List getStories() {
+ return stories;
+ }
+
+ public void setStories(List stories) {
+ this.stories = stories;
+ }
+
+ public List getTop_stories() {
+ return top_stories;
+ }
+
+ public void setTop_stories(List top_stories) {
+ this.top_stories = top_stories;
+ }
+
+ public static class StoriesBean {
+ private ExtraField extraField;
+ private String title;
+ private String ga_prefix;
+ private boolean multipic;
+ private int type;
+ private long id;
+ private List images;
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getGa_prefix() {
+ return ga_prefix;
+ }
+
+ public void setGa_prefix(String ga_prefix) {
+ this.ga_prefix = ga_prefix;
+ }
+
+ public boolean isMultipic() {
+ return multipic;
+ }
+
+ public void setMultipic(boolean multipic) {
+ this.multipic = multipic;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public void setType(int type) {
+ this.type = type;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public List getImages() {
+ return images;
+ }
+
+ public void setImages(List images) {
+ this.images = images;
+ }
+
+ public ExtraField getExtraField() {
+ return extraField;
+ }
+
+ public void setExtraField(ExtraField extraField) {
+ this.extraField = extraField;
+ }
+
+ public static class ExtraField {
+ private boolean isHeader;
+ private String date;
+
+ public ExtraField(boolean isHeader, String date) {
+ this.isHeader = isHeader;
+ this.date = date;
+ }
+
+ public boolean isHeader() {
+ return isHeader;
+ }
+
+ public void setHeader(boolean header) {
+ isHeader = header;
+ }
+
+ public String getDate() {
+ return date;
+ }
+
+ public void setDate(String date) {
+ this.date = date;
+ }
+ }
+
+ public StoriesBean(ExtraField extraField) {
+ this.extraField = extraField;
+ }
+ }
+
+ public static class TopStoriesBean {
+ private String image;
+ private int type;
+ private long id;
+ private String ga_prefix;
+ private String title;
+
+ public String getImage() {
+ return image;
+ }
+
+ public void setImage(String image) {
+ this.image = image;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public void setType(int type) {
+ this.type = type;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getGa_prefix() {
+ return ga_prefix;
+ }
+
+ public void setGa_prefix(String ga_prefix) {
+ this.ga_prefix = ga_prefix;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+ }
+ }
+}
diff --git a/sample/src/main/java/com/kelin/mvvmlight/zhihu/newsdetail/NewsDetailActivity.java b/sample/src/main/java/com/kelin/mvvmlight/zhihu/newsdetail/NewsDetailActivity.java
new file mode 100644
index 0000000..8de1054
--- /dev/null
+++ b/sample/src/main/java/com/kelin/mvvmlight/zhihu/newsdetail/NewsDetailActivity.java
@@ -0,0 +1,45 @@
+package com.kelin.mvvmlight.zhihu.newsdetail;
+
+import android.databinding.DataBindingUtil;
+import android.databinding.ViewDataBinding;
+import android.os.Bundle;
+import android.support.design.widget.CollapsingToolbarLayout;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuItem;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import com.kelin.mvvmlight.zhihu.R;
+import com.trello.rxlifecycle.components.support.RxAppCompatActivity;
+
+public class NewsDetailActivity extends RxAppCompatActivity {
+ public static final String EXTRA_KEY_NEWS_ID = "key_news_id";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ long id = getIntent().getLongExtra(EXTRA_KEY_NEWS_ID, -1);
+ ViewDataBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_news_detail);
+ binding.setVariable(com.kelin.mvvmlight.zhihu.BR.viewModel, new NewsDetailViewModel(this, id));
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ CollapsingToolbarLayout collapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
+ collapsingToolbarLayout.setExpandedTitleTextAppearance(R.style.ExpandedText);
+ collapsingToolbarLayout.setCollapsedTitleTextAppearance(R.style.CollapsedTitleText);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ WebView webView = (WebView) findViewById(R.id.webview);
+ webView.getSettings().setJavaScriptEnabled(true);
+ webView.setWebViewClient(new WebViewClient());
+
+
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/sample/src/main/java/com/kelin/mvvmlight/zhihu/newsdetail/NewsDetailCssService.java b/sample/src/main/java/com/kelin/mvvmlight/zhihu/newsdetail/NewsDetailCssService.java
new file mode 100644
index 0000000..a780460
--- /dev/null
+++ b/sample/src/main/java/com/kelin/mvvmlight/zhihu/newsdetail/NewsDetailCssService.java
@@ -0,0 +1,13 @@
+package com.kelin.mvvmlight.zhihu.newsdetail;
+
+import retrofit2.http.GET;
+import retrofit2.http.Url;
+import rx.Observable;
+
+/**
+ * Created by kelin on 16-4-29.
+ */
+public interface NewsDetailCssService {
+ @GET
+ Observable getNewsDetailCss(@Url String url);
+}
diff --git a/sample/src/main/java/com/kelin/mvvmlight/zhihu/newsdetail/NewsDetailService.java b/sample/src/main/java/com/kelin/mvvmlight/zhihu/newsdetail/NewsDetailService.java
new file mode 100644
index 0000000..32a197e
--- /dev/null
+++ b/sample/src/main/java/com/kelin/mvvmlight/zhihu/newsdetail/NewsDetailService.java
@@ -0,0 +1,141 @@
+package com.kelin.mvvmlight.zhihu.newsdetail;
+
+import java.util.List;
+
+import retrofit2.http.GET;
+import retrofit2.http.Path;
+import rx.Observable;
+
+/**
+ * Created by kelin on 16-4-29.
+ */
+public interface NewsDetailService {
+ @GET("/api/4/news/{id}")
+ public Observable getNewsDetail(@Path("id") long id);
+
+ public class NewsDetail{
+
+ /**
+ * body : T
+ * image_source : Angel Abril Ruiz / CC BY
+ * title : 卖衣服的新手段:把耐用品变成「不停买新的」
+ * image : http://p4.zhimg.com/30/59/30594279d368534c6c2f91b2c00c7806.jpg
+ * share_url : http://daily.zhihu.com/story/3892357
+ * js : []
+ * ga_prefix : 050615
+ * images : ["http://p3.zhimg.com/69/d0/69d0ab1bde1988bd475bc7e0a25b713e.jpg"]
+ * type : 0
+ * id : 3892357
+ * css : ["http://news-at.zhihu.com/css/news_qa.auto.css?v=4b3e3"]
+ */
+
+ private String body;
+ private String image_source;
+ private String title;
+ private String image;
+ private String share_url;
+ private String ga_prefix;
+ private int type;
+ private int id;
+ private List> js;
+ private List images;
+ private List css;
+ private String cssStr;
+
+ public String getCssStr() {
+ return cssStr;
+ }
+
+ public void setCssStr(String cssStr) {
+ this.cssStr = cssStr;
+ }
+
+ public String getBody() {
+ return body;
+ }
+
+ public void setBody(String body) {
+ this.body = body;
+ }
+
+ public String getImage_source() {
+ return image_source;
+ }
+
+ public void setImage_source(String image_source) {
+ this.image_source = image_source;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getImage() {
+ return image;
+ }
+
+ public void setImage(String image) {
+ this.image = image;
+ }
+
+ public String getShare_url() {
+ return share_url;
+ }
+
+ public void setShare_url(String share_url) {
+ this.share_url = share_url;
+ }
+
+ public String getGa_prefix() {
+ return ga_prefix;
+ }
+
+ public void setGa_prefix(String ga_prefix) {
+ this.ga_prefix = ga_prefix;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public void setType(int type) {
+ this.type = type;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public List> getJs() {
+ return js;
+ }
+
+ public void setJs(List> js) {
+ this.js = js;
+ }
+
+ public List getImages() {
+ return images;
+ }
+
+ public void setImages(List images) {
+ this.images = images;
+ }
+
+ public List getCss() {
+ return css;
+ }
+
+ public void setCss(List css) {
+ this.css = css;
+ }
+ }
+}
diff --git a/sample/src/main/java/com/kelin/mvvmlight/zhihu/newsdetail/NewsDetailViewModel.java b/sample/src/main/java/com/kelin/mvvmlight/zhihu/newsdetail/NewsDetailViewModel.java
new file mode 100644
index 0000000..801a09f
--- /dev/null
+++ b/sample/src/main/java/com/kelin/mvvmlight/zhihu/newsdetail/NewsDetailViewModel.java
@@ -0,0 +1,118 @@
+package com.kelin.mvvmlight.zhihu.newsdetail;
+
+import android.app.Activity;
+import android.databinding.ObservableBoolean;
+import android.databinding.ObservableField;
+
+import com.kelin.mvvmlight.base.ViewModel;
+import com.kelin.mvvmlight.command.ReplyCommand;
+import com.kelin.mvvmlight.zhihu.retrofit.RetrofitProvider;
+import com.kelin.mvvmlight.zhihu.retrofit.ToStringConverter;
+import com.trello.rxlifecycle.ActivityLifecycleProvider;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.List;
+
+import okhttp3.ResponseBody;
+import retrofit2.Converter;
+import retrofit2.Retrofit;
+import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
+import rx.Notification;
+import rx.Observable;
+import rx.android.schedulers.AndroidSchedulers;
+import rx.schedulers.Schedulers;
+
+/**
+ * Created by kelin on 16-4-28.
+ */
+public class NewsDetailViewModel implements ViewModel {
+ //context
+ private Activity activity;
+
+ /**
+ * Model
+ * data source for ViewModel
+ */
+ private NewsDetailService.NewsDetail newsDetail;
+
+ /**
+ * ViewStyle
+ * collection of view style
+ */
+ public class ViewStyle {
+ public final ObservableBoolean isRefreshing = new ObservableBoolean(true);
+ public final ObservableBoolean progressRefreshing = new ObservableBoolean(true);
+ }
+
+ //data
+ public final ObservableField imageUrl = new ObservableField<>();
+ public final ObservableField html = new ObservableField<>();
+ public final ObservableField title = new ObservableField<>();
+ public final ViewStyle viewStyle = new ViewStyle();
+
+
+ //command
+ public final ReplyCommand onRefreshCommand = new ReplyCommand<>(() -> {
+ viewStyle.isRefreshing.set(true);
+ viewStyle.progressRefreshing.set(false);
+ loadData(newsDetail.getId());
+ });
+
+ public NewsDetailViewModel(Activity activity, long id) {
+ this.activity = activity;
+ loadData(id);
+ }
+
+ private void loadData(long id) {
+ Observable> newsDetailOb =
+ RetrofitProvider.getInstance().create(NewsDetailService.class)
+ .getNewsDetail(id)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .compose(((ActivityLifecycleProvider) activity).bindToLifecycle())
+ .materialize().share();
+
+ newsDetailOb.filter(Notification::isOnNext)
+ .map(n -> n.getValue())
+ .doOnNext(m -> newsDetail = m)
+ .subscribe(m -> loadHtmlCss(m.getCss()));
+ }
+
+ private void loadHtmlCss(List urls) {
+ Retrofit retrofit = new Retrofit.Builder()
+ .baseUrl("http://news-at.zhihu.com/")
+ .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
+ .addConverterFactory(new Converter.Factory() {
+ @Override
+ public Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
+ return new ToStringConverter();
+ }
+ }).build();
+
+ Observable.from(urls)
+ .flatMap(s -> retrofit
+ .create(NewsDetailCssService.class)
+ .getNewsDetailCss(s)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .compose(((ActivityLifecycleProvider) activity).bindToLifecycle())
+ .materialize().share().filter(Notification::isOnNext).map(n -> n.getValue()))
+ .scan((s1, s2) -> s1 + s2)
+ .last()
+ .doOnNext(s -> newsDetail.setCssStr(s))
+ .doAfterTerminate(() -> viewStyle.progressRefreshing.set(false))
+ .subscribe(s -> initViewModelField());
+ }
+
+
+ private void initViewModelField() {
+ viewStyle.isRefreshing.set(false);
+ imageUrl.set(newsDetail.getImage());
+ Observable.just(newsDetail.getBody())
+ .map(s -> s + "")
+ .subscribe(s -> html.set(s));
+ title.set(newsDetail.getTitle());
+ }
+}
diff --git a/sample/src/main/java/com/kelin/mvvmlight/zhihu/retrofit/ApiException.java b/sample/src/main/java/com/kelin/mvvmlight/zhihu/retrofit/ApiException.java
new file mode 100644
index 0000000..fb875c2
--- /dev/null
+++ b/sample/src/main/java/com/kelin/mvvmlight/zhihu/retrofit/ApiException.java
@@ -0,0 +1,21 @@
+package com.kelin.mvvmlight.zhihu.retrofit;
+
+import java.io.IOException;
+
+/**
+ * Created by liupei on 15/11/10.
+ */
+public class ApiException extends IOException {
+ public final int code;
+ public final String msg;
+
+ public ApiException(int code) {
+ this.code = code;
+ this.msg = null;
+ }
+
+ public ApiException(int code, String msg) {
+ this.code = code;
+ this.msg = msg;
+ }
+}
diff --git a/sample/src/main/java/com/kelin/mvvmlight/zhihu/retrofit/ApiTypeAdapterFactory.java b/sample/src/main/java/com/kelin/mvvmlight/zhihu/retrofit/ApiTypeAdapterFactory.java
new file mode 100644
index 0000000..7f15da9
--- /dev/null
+++ b/sample/src/main/java/com/kelin/mvvmlight/zhihu/retrofit/ApiTypeAdapterFactory.java
@@ -0,0 +1,59 @@
+package com.kelin.mvvmlight.zhihu.retrofit;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.IOException;
+
+/**
+ * Created by dingzhihu on 15/5/7.
+ */
+public class ApiTypeAdapterFactory implements TypeAdapterFactory {
+ private String dataElementName;
+
+ public ApiTypeAdapterFactory(String dataElementName) {
+ this.dataElementName = dataElementName;
+ }
+
+ @Override
+ public TypeAdapter create(Gson gson, TypeToken type) {
+ final TypeAdapter delegate = gson.getDelegateAdapter(this, type);
+ final TypeAdapter elementTypeAdapter = gson.getAdapter(JsonElement.class);
+
+
+ return new TypeAdapter() {
+ @Override
+ public void write(JsonWriter out, T value) throws IOException {
+ delegate.write(out, value);
+ }
+
+ @Override
+ public T read(JsonReader in) throws IOException {
+ JsonElement jsonElement = elementTypeAdapter.read(in);
+ if (jsonElement.isJsonObject()) {
+ JsonObject jsonObject = jsonElement.getAsJsonObject();
+ if (jsonObject.has("status")) {
+ int status = jsonObject.get("status").getAsInt();
+ String message = jsonObject.get("message").getAsString();
+ if (status == 0) {
+ //do nothing
+ } else {
+ throw new ApiException(status, message);
+ }
+ }
+ if (jsonObject.has(dataElementName)) {
+ jsonElement = jsonObject.get(dataElementName);
+ }
+ }
+ return delegate.fromJsonTree(jsonElement);
+ }
+
+ }.nullSafe();
+ }
+}
diff --git a/sample/src/main/java/com/kelin/mvvmlight/zhihu/retrofit/RetrofitProvider.java b/sample/src/main/java/com/kelin/mvvmlight/zhihu/retrofit/RetrofitProvider.java
new file mode 100644
index 0000000..2713b3e
--- /dev/null
+++ b/sample/src/main/java/com/kelin/mvvmlight/zhihu/retrofit/RetrofitProvider.java
@@ -0,0 +1,34 @@
+package com.kelin.mvvmlight.zhihu.retrofit;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import retrofit2.Retrofit;
+import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+/**
+ * Created by dingzhihu on 15/5/7.
+ */
+public class RetrofitProvider {
+
+ private static Retrofit retrofit;
+
+ private RetrofitProvider() {
+ }
+
+ public static Retrofit getInstance() {
+ if (retrofit == null) {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapterFactory(new ApiTypeAdapterFactory("data"))
+ .create();
+ retrofit = new Retrofit.Builder()
+ .baseUrl("http://news-at.zhihu.com/")
+ .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
+ .addConverterFactory(GsonConverterFactory.create(gson))
+ .build();
+ }
+ return retrofit;
+
+ }
+}
diff --git a/sample/src/main/java/com/kelin/mvvmlight/zhihu/retrofit/ToStringConverter.java b/sample/src/main/java/com/kelin/mvvmlight/zhihu/retrofit/ToStringConverter.java
new file mode 100644
index 0000000..e876823
--- /dev/null
+++ b/sample/src/main/java/com/kelin/mvvmlight/zhihu/retrofit/ToStringConverter.java
@@ -0,0 +1,16 @@
+package com.kelin.mvvmlight.zhihu.retrofit;
+
+import java.io.IOException;
+
+import okhttp3.ResponseBody;
+import retrofit2.Converter;
+
+/**
+ * Created by kelin on 16-5-3.
+ */
+public final class ToStringConverter implements Converter {
+ @Override
+ public String convert(ResponseBody value) throws IOException {
+ return value.string();
+ }
+}
\ No newline at end of file
diff --git a/sample/src/main/java/com/kelin/mvvmlight/zhihu/utils/AlphaForegroundColorSpan.java b/sample/src/main/java/com/kelin/mvvmlight/zhihu/utils/AlphaForegroundColorSpan.java
new file mode 100644
index 0000000..673f425
--- /dev/null
+++ b/sample/src/main/java/com/kelin/mvvmlight/zhihu/utils/AlphaForegroundColorSpan.java
@@ -0,0 +1,45 @@
+package com.kelin.mvvmlight.zhihu.utils;
+
+import android.graphics.Color;
+import android.os.Parcel;
+import android.text.TextPaint;
+import android.text.style.ForegroundColorSpan;
+
+/**
+ * Created by kelin on 16-4-28.
+ */
+public class AlphaForegroundColorSpan extends ForegroundColorSpan {
+ private float mAlpha;
+
+ public AlphaForegroundColorSpan(int color) {
+ super(color);
+ }
+
+ public AlphaForegroundColorSpan(Parcel src) {
+ super(src);
+ mAlpha = src.readFloat();
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeFloat(mAlpha);
+ }
+
+ @Override
+ public void updateDrawState(TextPaint ds) {
+ ds.setColor(getAlphaColor());
+ }
+
+ public void setAlpha(float alpha) {
+ mAlpha = alpha;
+ }
+
+ public float getAlpha() {
+ return mAlpha;
+ }
+
+ private int getAlphaColor() {
+ int foregroundColor = getForegroundColor();
+ return Color.argb((int) (mAlpha), Color.red(foregroundColor), Color.green(foregroundColor), Color.blue(foregroundColor));
+ }
+}
diff --git a/sample/src/main/java/com/kelin/mvvmlight/zhihu/utils/ViewUtils.java b/sample/src/main/java/com/kelin/mvvmlight/zhihu/utils/ViewUtils.java
new file mode 100644
index 0000000..783bddc
--- /dev/null
+++ b/sample/src/main/java/com/kelin/mvvmlight/zhihu/utils/ViewUtils.java
@@ -0,0 +1,27 @@
+package com.kelin.mvvmlight.zhihu.utils;
+
+import android.content.Context;
+
+/**
+ * Created by kelin on 16-5-3.
+ */
+public class ViewUtils {
+ public static int getStatusBarHeight(Context context) {
+ Class> c = null;
+ Object obj = null;
+ java.lang.reflect.Field field = null;
+ int x = 0;
+ int statusBarHeight = 0;
+ try {
+ c = Class.forName("com.android.internal.R$dimen");
+ obj = c.newInstance();
+ field = c.getField("status_bar_height");
+ x = Integer.parseInt(field.get(obj).toString());
+ statusBarHeight = context.getResources().getDimensionPixelSize(x);
+ return statusBarHeight;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return statusBarHeight;
+ }
+}
diff --git a/sample/src/main/res/drawable-v21/ic_menu_camera.xml b/sample/src/main/res/drawable-v21/ic_menu_camera.xml
new file mode 100644
index 0000000..0d9ea10
--- /dev/null
+++ b/sample/src/main/res/drawable-v21/ic_menu_camera.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/sample/src/main/res/drawable-v21/ic_menu_gallery.xml b/sample/src/main/res/drawable-v21/ic_menu_gallery.xml
new file mode 100644
index 0000000..f6872c4
--- /dev/null
+++ b/sample/src/main/res/drawable-v21/ic_menu_gallery.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/sample/src/main/res/drawable-v21/ic_menu_manage.xml b/sample/src/main/res/drawable-v21/ic_menu_manage.xml
new file mode 100644
index 0000000..c1be60b
--- /dev/null
+++ b/sample/src/main/res/drawable-v21/ic_menu_manage.xml
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/drawable-v21/ic_menu_send.xml b/sample/src/main/res/drawable-v21/ic_menu_send.xml
new file mode 100644
index 0000000..00c668c
--- /dev/null
+++ b/sample/src/main/res/drawable-v21/ic_menu_send.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/sample/src/main/res/drawable-v21/ic_menu_share.xml b/sample/src/main/res/drawable-v21/ic_menu_share.xml
new file mode 100644
index 0000000..a28fb9e
--- /dev/null
+++ b/sample/src/main/res/drawable-v21/ic_menu_share.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/sample/src/main/res/drawable-v21/ic_menu_slideshow.xml b/sample/src/main/res/drawable-v21/ic_menu_slideshow.xml
new file mode 100644
index 0000000..209aa64
--- /dev/null
+++ b/sample/src/main/res/drawable-v21/ic_menu_slideshow.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/sample/src/main/res/drawable/divider.xml b/sample/src/main/res/drawable/divider.xml
new file mode 100644
index 0000000..b15621d
--- /dev/null
+++ b/sample/src/main/res/drawable/divider.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/drawable/side_nav_bar.xml b/sample/src/main/res/drawable/side_nav_bar.xml
new file mode 100644
index 0000000..458b4b0
--- /dev/null
+++ b/sample/src/main/res/drawable/side_nav_bar.xml
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..849481b
--- /dev/null
+++ b/sample/src/main/res/layout/activity_main.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/layout/activity_news_detail.xml b/sample/src/main/res/layout/activity_news_detail.xml
new file mode 100644
index 0000000..2f55999
--- /dev/null
+++ b/sample/src/main/res/layout/activity_news_detail.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/res/layout/activity_news_detail_content.xml b/sample/src/main/res/layout/activity_news_detail_content.xml
new file mode 100644
index 0000000..112cfcc
--- /dev/null
+++ b/sample/src/main/res/layout/activity_news_detail_content.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/res/layout/app_bar_main.xml b/sample/src/main/res/layout/app_bar_main.xml
new file mode 100644
index 0000000..08f8273
--- /dev/null
+++ b/sample/src/main/res/layout/app_bar_main.xml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/layout/content_main.xml b/sample/src/main/res/layout/content_main.xml
new file mode 100644
index 0000000..51545ea
--- /dev/null
+++ b/sample/src/main/res/layout/content_main.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/sample/src/main/res/layout/fragment_new_list.xml b/sample/src/main/res/layout/fragment_new_list.xml
new file mode 100644
index 0000000..989681c
--- /dev/null
+++ b/sample/src/main/res/layout/fragment_new_list.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/layout/listitem_news.xml b/sample/src/main/res/layout/listitem_news.xml
new file mode 100644
index 0000000..623126d
--- /dev/null
+++ b/sample/src/main/res/layout/listitem_news.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/res/layout/listitem_news_header.xml b/sample/src/main/res/layout/listitem_news_header.xml
new file mode 100644
index 0000000..7ca8107
--- /dev/null
+++ b/sample/src/main/res/layout/listitem_news_header.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/res/layout/listtem_header_top_news.xml b/sample/src/main/res/layout/listtem_header_top_news.xml
new file mode 100644
index 0000000..ab9d2e3
--- /dev/null
+++ b/sample/src/main/res/layout/listtem_header_top_news.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/layout/nav_header_main.xml b/sample/src/main/res/layout/nav_header_main.xml
new file mode 100644
index 0000000..cf41aec
--- /dev/null
+++ b/sample/src/main/res/layout/nav_header_main.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/res/layout/viewpager_item_top_news.xml b/sample/src/main/res/layout/viewpager_item_top_news.xml
new file mode 100644
index 0000000..f2931fa
--- /dev/null
+++ b/sample/src/main/res/layout/viewpager_item_top_news.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/res/menu/activity_main_drawer.xml b/sample/src/main/res/menu/activity_main_drawer.xml
new file mode 100644
index 0000000..d097498
--- /dev/null
+++ b/sample/src/main/res/menu/activity_main_drawer.xml
@@ -0,0 +1,15 @@
+
+
diff --git a/sample/src/main/res/menu/main.xml b/sample/src/main/res/menu/main.xml
new file mode 100644
index 0000000..a2411e3
--- /dev/null
+++ b/sample/src/main/res/menu/main.xml
@@ -0,0 +1,9 @@
+
+
diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher.png b/sample/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
Binary files /dev/null and b/sample/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher.png b/sample/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
Binary files /dev/null and b/sample/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
Binary files /dev/null and b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
Binary files /dev/null and b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
Binary files /dev/null and b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/values-v21/styles.xml b/sample/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..dbbdd40
--- /dev/null
+++ b/sample/src/main/res/values-v21/styles.xml
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/sample/src/main/res/values-w820dp/dimens.xml b/sample/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/sample/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/sample/src/main/res/values/colors.xml b/sample/src/main/res/values/colors.xml
new file mode 100644
index 0000000..3ab3e9c
--- /dev/null
+++ b/sample/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+
diff --git a/sample/src/main/res/values/dimens.xml b/sample/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..cfc61a9
--- /dev/null
+++ b/sample/src/main/res/values/dimens.xml
@@ -0,0 +1,9 @@
+
+
+ 16dp
+ 160dp
+
+ 16dp
+ 16dp
+ 16dp
+
diff --git a/sample/src/main/res/values/drawables.xml b/sample/src/main/res/values/drawables.xml
new file mode 100644
index 0000000..52c6a6c
--- /dev/null
+++ b/sample/src/main/res/values/drawables.xml
@@ -0,0 +1,8 @@
+
+ - @android:drawable/ic_menu_camera
+ - @android:drawable/ic_menu_gallery
+ - @android:drawable/ic_menu_slideshow
+ - @android:drawable/ic_menu_manage
+ - @android:drawable/ic_menu_share
+ - @android:drawable/ic_menu_send
+
diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml
new file mode 100644
index 0000000..23beff4
--- /dev/null
+++ b/sample/src/main/res/values/strings.xml
@@ -0,0 +1,9 @@
+
+ 知乎
+
+ Open navigation drawer
+ Close navigation drawer
+
+ Settings
+ 今日日报
+
diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml
new file mode 100644
index 0000000..035fe80
--- /dev/null
+++ b/sample/src/main/res/values/styles.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/test/java/com/kelin/mvvmlight/zhihu/ExampleUnitTest.java b/sample/src/test/java/com/kelin/mvvmlight/zhihu/ExampleUnitTest.java
new file mode 100644
index 0000000..e0e637b
--- /dev/null
+++ b/sample/src/test/java/com/kelin/mvvmlight/zhihu/ExampleUnitTest.java
@@ -0,0 +1,15 @@
+package com.kelin.mvvmlight.zhihu;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * To work on unit tests, switch the Test Artifact in the Build Variants view.
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() throws Exception {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..f4f4615
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':library', ':sample'
\ No newline at end of file