├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── ganhuo │ │ └── app │ │ ├── AboutActivity.java │ │ ├── ApplicationPoint.java │ │ ├── MainActivity.java │ │ ├── WebViewActivity.java │ │ ├── actions │ │ └── DataController.java │ │ ├── adapters │ │ ├── ContentAdapterBase.java │ │ └── EntryListAdapter.java │ │ ├── fragments │ │ ├── CommonFragment.java │ │ ├── ContentFragmentBase.java │ │ └── EntryListFragment.java │ │ ├── holders │ │ ├── EntryViewHolder.java │ │ └── FooterViewHolder.java │ │ ├── interfaces │ │ ├── AdapterRespondent.java │ │ ├── DataProvider.java │ │ ├── SimpleUIRespondent.java │ │ └── UIRespondent.java │ │ ├── models │ │ └── Entry.java │ │ └── providers │ │ └── EntryListProvider.java │ └── res │ ├── layout │ ├── activity_about.xml │ ├── activity_main.xml │ ├── entry_item.xml │ ├── fragment_content.xml │ ├── recyclerview_footer.xml │ ├── title.xml │ └── webview.xml │ ├── menu │ └── menu_main.xml │ ├── mipmap-hdpi │ └── app_logo.png │ ├── mipmap-mdpi │ └── app_logo.png │ ├── mipmap-xhdpi │ └── app_logo.png │ ├── mipmap-xxhdpi │ └── app_logo.png │ ├── mipmap-xxxhdpi │ └── app_logo.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | .DS_Store 5 | /build 6 | # built application files 7 | *.apk 8 | *.ap_ 9 | 10 | # files for the dex VM 11 | *.dex 12 | 13 | # Java class files 14 | *.class 15 | .DS_Store 16 | 17 | # generated files 18 | bin/ 19 | gen/ 20 | Wiki/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Eclipse project files 26 | .classpath 27 | .project 28 | .settings/ 29 | 30 | # Proguard folder generated by Eclipse 31 | proguard/ 32 | 33 | #Android Studio 34 | build/ 35 | 36 | # Intellij project files 37 | *.iml 38 | *.ipr 39 | *.iws 40 | .idea/ 41 | 42 | #gradle 43 | .gradle/ 44 | captures -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 干货集中营 2 | 3 | ## 这是什么? 4 | 5 | 有一天,我发现了好多开发上的干货,我觉得这些干货好有用,我好想让别人知道,这个干货好有用。所以我就建了个邮件列表, 6 | 开始给大家推荐每日开发干货。订阅地址是: http://tinyletter.com/daimajia 7 | 8 | ## 为什么要做这个? 9 | 10 | 又有一天,我发现很多 QQ 订阅用户收不到干货,我说,这不行,我不答应,每个人都应该有收到干货的权利!所以我觉得是时候写个 App 了。 11 | 就这样,这个仓储就被创建了! 12 | 13 | ## 为什么要开源? 14 | 15 | 我的一贯作风。 16 | 17 | ## 我要怎么贡献? 18 | 19 | Fork ---> Pull Request. 20 | 21 | ## 我有意见和建议怎么办? 22 | 23 | 发我邮箱: daimajia@gmail.com 24 | 25 | 或者 26 | 27 | 加我微信:daimajia 记着加备注 28 | 29 | ## iOS 版本有么? 30 | 31 | 有的,点[这里](https://github.com/ganhuo/iOS-Ganhuo)! 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 22 5 | buildToolsVersion "22.0.1" 6 | 7 | defaultConfig { 8 | applicationId "com.ganhuo.app" 9 | minSdkVersion 14 10 | targetSdkVersion 22 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | 21 | packagingOptions { 22 | exclude 'META-INF/LICENSE.txt' 23 | exclude 'META-INF/NOTICE.txt' 24 | } 25 | 26 | lintOptions { 27 | abortOnError false 28 | } 29 | 30 | sourceSets { 31 | main { 32 | jniLibs.srcDirs = ['libs'] 33 | } 34 | } 35 | } 36 | 37 | dependencies { 38 | compile fileTree(dir: 'libs', include: ['*.jar']) 39 | compile 'com.android.support:appcompat-v7:22.1.1' 40 | compile 'com.android.support:recyclerview-v7:21.0.3' 41 | compile 'com.android.support:cardview-v7:21.0.3' 42 | compile 'com.github.bumptech.glide:glide:3.5.1' 43 | compile 'com.daimajia.numberprogressbar:library:1.2@aar' 44 | compile 'com.daimajia.easing:library:1.0.1@aar' 45 | compile 'com.daimajia.androidanimations:library:1.1.3@aar' 46 | compile 'im.fir:sdk:1.1.1' 47 | compile 'com.nineoldandroids:library:2.4.0' 48 | compile 'cn.leancloud.android:avoscloud-sdk:v3.1.1' 49 | compile 'cn.leancloud.android:Java-WebSocket:1.2.0-leancloud' 50 | compile 'cn.leancloud.android:avoscloud-statistics:v3.1@aar' 51 | compile 'cn.leancloud.android:avoscloud-push:v3.1.5@aar' 52 | compile('com.mikepenz.aboutlibraries:library:4.6.6@aar') { 53 | transitive = true 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/daimajia/Development/Environment/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/ganhuo/app/AboutActivity.java: -------------------------------------------------------------------------------- 1 | package com.ganhuo.app; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | public class AboutActivity extends AppCompatActivity { 7 | 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | setContentView(R.layout.activity_about); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/ganhuo/app/ApplicationPoint.java: -------------------------------------------------------------------------------- 1 | package com.ganhuo.app; 2 | 3 | import android.app.Application; 4 | 5 | import com.avos.avoscloud.AVInstallation; 6 | import com.avos.avoscloud.AVOSCloud; 7 | import com.avos.avoscloud.AVObject; 8 | import com.avos.avoscloud.PushService; 9 | import com.ganhuo.app.models.Entry; 10 | 11 | import im.fir.sdk.FIR; 12 | 13 | public class ApplicationPoint extends Application { 14 | @Override 15 | public void onCreate() { 16 | super.onCreate(); 17 | FIR.init(this); 18 | AVObject.registerSubclass(Entry.class); 19 | AVOSCloud.initialize(getApplicationContext(), "id77coqgtrm74f6esc617hhqxe4bo8icg17al5998lcdxswy", "qoj2a3fo8icot1qigiemnd36zjdfir45d9qapxna6ky7l0gs"); 20 | AVInstallation.getCurrentInstallation().saveInBackground(); 21 | PushService.setDefaultPushCallback(this, MainActivity.class); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/ganhuo/app/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.ganhuo.app; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.Menu; 7 | import android.view.MenuItem; 8 | 9 | import com.avos.avoscloud.AVAnalytics; 10 | import com.ganhuo.app.fragments.EntryListFragment; 11 | 12 | 13 | public class MainActivity extends AppCompatActivity { 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | setContentView(R.layout.activity_main); 19 | getSupportFragmentManager().beginTransaction().replace(R.id.fragment, new EntryListFragment()).commit(); 20 | Intent intent = getIntent(); 21 | AVAnalytics.trackAppOpened(intent); 22 | } 23 | 24 | @Override 25 | public boolean onCreateOptionsMenu(Menu menu) { 26 | getMenuInflater().inflate(R.menu.menu_main, menu); 27 | return true; 28 | } 29 | 30 | @Override 31 | public boolean onOptionsItemSelected(MenuItem item) { 32 | int id = item.getItemId(); 33 | if (id == R.id.about) { 34 | startActivity(new Intent(this, AboutActivity.class)); 35 | return true; 36 | } 37 | 38 | return super.onOptionsItemSelected(item); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/ganhuo/app/WebViewActivity.java: -------------------------------------------------------------------------------- 1 | package com.ganhuo.app; 2 | 3 | import android.graphics.Bitmap; 4 | import android.os.Bundle; 5 | import android.support.v7.app.ActionBarActivity; 6 | import android.view.KeyEvent; 7 | import android.view.MenuItem; 8 | import android.webkit.WebChromeClient; 9 | import android.webkit.WebView; 10 | import android.webkit.WebViewClient; 11 | 12 | import com.daimajia.androidanimations.library.Techniques; 13 | import com.daimajia.androidanimations.library.YoYo; 14 | import com.daimajia.numberprogressbar.NumberProgressBar; 15 | 16 | public class WebViewActivity extends ActionBarActivity { 17 | 18 | private WebView mWebView; 19 | private WebClient mWebClient; 20 | private NumberProgressBar mProgressbar; 21 | 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.webview); 27 | String url = getIntent().getStringExtra("url"); 28 | mWebClient = new WebClient(); 29 | mWebView = (WebView) findViewById(R.id.webview); 30 | mProgressbar = (NumberProgressBar) findViewById(R.id.progressbar); 31 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 32 | mWebView.setWebViewClient(mWebClient); 33 | mWebView.getSettings().setJavaScriptEnabled(true); 34 | mWebView.setWebChromeClient(new WebChrome()); 35 | mWebView.getSettings().setBuiltInZoomControls(true); 36 | mWebView.getSettings().setDisplayZoomControls(false); 37 | mWebView.loadUrl(url); 38 | } 39 | 40 | @Override 41 | public boolean onKeyDown(int keyCode, KeyEvent event) { 42 | if (event.getAction() == KeyEvent.ACTION_DOWN) { 43 | switch (keyCode) { 44 | case KeyEvent.KEYCODE_BACK: 45 | if (mWebView.canGoBack()) { 46 | mWebView.goBack(); 47 | } else { 48 | finish(); 49 | } 50 | return true; 51 | } 52 | 53 | } 54 | return super.onKeyDown(keyCode, event); 55 | } 56 | 57 | 58 | @Override 59 | public boolean onOptionsItemSelected(MenuItem item) { 60 | finish(); 61 | return super.onOptionsItemSelected(item); 62 | } 63 | 64 | @Override 65 | protected void onDestroy() { 66 | super.onDestroy(); 67 | if (mWebView != null) 68 | mWebView.destroy(); 69 | } 70 | 71 | @Override 72 | protected void onPause() { 73 | super.onPause(); 74 | if (mWebView != null) 75 | mWebView.onPause(); 76 | } 77 | 78 | @Override 79 | protected void onResume() { 80 | super.onResume(); 81 | mWebView.onResume(); 82 | } 83 | 84 | private class WebClient extends WebViewClient { 85 | @Override 86 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 87 | return false; 88 | } 89 | 90 | @Override 91 | public void onPageFinished(WebView view, String url) { 92 | 93 | } 94 | 95 | @Override 96 | public void onPageStarted(WebView view, String url, Bitmap favicon) { 97 | super.onPageStarted(view, url, favicon); 98 | YoYo.with(Techniques.FadeIn).playOn(mProgressbar); 99 | } 100 | 101 | @Override 102 | public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { 103 | super.onReceivedError(view, errorCode, description, failingUrl); 104 | } 105 | 106 | } 107 | 108 | private class WebChrome extends WebChromeClient { 109 | @Override 110 | public void onProgressChanged(WebView view, int newProgress) { 111 | super.onProgressChanged(view, newProgress); 112 | mProgressbar.setProgress(newProgress); 113 | } 114 | 115 | @Override 116 | public void onReceivedTitle(WebView view, String title) { 117 | super.onReceivedTitle(view, title); 118 | } 119 | } 120 | 121 | 122 | } 123 | -------------------------------------------------------------------------------- /app/src/main/java/com/ganhuo/app/actions/DataController.java: -------------------------------------------------------------------------------- 1 | package com.ganhuo.app.actions; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.os.Message; 6 | 7 | import com.ganhuo.app.interfaces.AdapterRespondent; 8 | import com.ganhuo.app.interfaces.DataProvider; 9 | import com.ganhuo.app.interfaces.UIRespondent; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public abstract class DataController implements DataProvider { 15 | 16 | private List mRepository; 17 | 18 | private boolean isBusy = false; 19 | private boolean isEnd = false; 20 | private int mPageSize = 10; 21 | private int mNextTimeOffset = 0; 22 | private int mRequestTimes = 0; 23 | 24 | private List> mUIRespondents; 25 | private List mAdapterRespondents; 26 | 27 | public boolean isBusy() { 28 | return isBusy; 29 | } 30 | 31 | public boolean isEmpty() { 32 | return mRepository == null || mRepository.size() == 0; 33 | } 34 | 35 | public boolean isEnd() { 36 | return isEnd; 37 | } 38 | 39 | public int getSize() { 40 | if (mRepository == null) { 41 | return 0; 42 | } 43 | return mRepository.size(); 44 | } 45 | 46 | public int getRequestOffset() { 47 | return mNextTimeOffset; 48 | } 49 | 50 | public int getPageSize() { 51 | return mPageSize; 52 | } 53 | 54 | public void setPageSize(int pageSize) { 55 | mPageSize = pageSize; 56 | } 57 | 58 | public List getData() throws Exception { 59 | return mRepository; 60 | } 61 | 62 | public void clear() { 63 | if (mRepository != null) { 64 | mRepository.clear(); 65 | mNextTimeOffset = 0; 66 | mRequestTimes = 0; 67 | } 68 | } 69 | 70 | public void add(T data) { 71 | if (data != null) { 72 | mRepository.add(0, data); 73 | dispatchAdapterMessage(AdapterMessageType.INSERT, 0); 74 | } 75 | } 76 | 77 | public void append(T data) { 78 | if (data != null) { 79 | mRepository.add(data); 80 | dispatchAdapterMessage(AdapterMessageType.INSERT, mRepository.size() - 1); 81 | } 82 | } 83 | 84 | public void add(List data) { 85 | if (data == null) { 86 | isEnd = true; 87 | return; 88 | } 89 | if (mRepository == null) { 90 | mRepository = new ArrayList<>(); 91 | } 92 | if (data.size() < mPageSize) { 93 | isEnd = true; 94 | } 95 | mRepository.addAll(0, data); 96 | } 97 | 98 | public void append(List data) { 99 | if (data == null) { 100 | isEnd = true; 101 | return; 102 | } 103 | if (mRepository == null) { 104 | mRepository = new ArrayList<>(); 105 | } 106 | if (data.size() < mPageSize) { 107 | isEnd = true; 108 | } 109 | mRepository.addAll(data); 110 | } 111 | 112 | public void addUIRespondent(UIRespondent respondent) { 113 | if (mUIRespondents == null) { 114 | mUIRespondents = new ArrayList<>(); 115 | } 116 | mUIRespondents.add(respondent); 117 | } 118 | 119 | public void addAdapterRespondent(AdapterRespondent respondent) { 120 | if (mAdapterRespondents == null) { 121 | mAdapterRespondents = new ArrayList<>(); 122 | } 123 | mAdapterRespondents.add(respondent); 124 | } 125 | 126 | public final void initialize() { 127 | doAction(ActionType.Initialize); 128 | } 129 | 130 | public final void refresh() { 131 | doAction(ActionType.Refresh); 132 | } 133 | 134 | public final void more() { 135 | doAction(ActionType.More); 136 | } 137 | 138 | protected void doAction(final ActionType type) { 139 | if (!type.equals(ActionType.Refresh)) { 140 | if ((isEnd || isBusy)) { 141 | return; 142 | } 143 | } 144 | isBusy = true; 145 | switch (type) { 146 | case Initialize: 147 | dispatchUIMessage(UIMessageType.InitializeStart, null, null); 148 | break; 149 | case More: 150 | dispatchUIMessage(UIMessageType.LoadingMoreStart, null, null); 151 | break; 152 | case Refresh: 153 | dispatchUIMessage(UIMessageType.RefreshingStart, null, null); 154 | break; 155 | } 156 | 157 | 158 | new Thread(new Runnable() { 159 | @Override 160 | public void run() { 161 | List data = null; 162 | Exception exception = null; 163 | try { 164 | switch (type) { 165 | case Initialize: 166 | data = doInitialize(); 167 | add(data); 168 | break; 169 | case More: 170 | data = doMore(); 171 | append(data); 172 | break; 173 | case Refresh: 174 | data = doRefresh(); 175 | add(data); 176 | break; 177 | } 178 | mRequestTimes++; 179 | mNextTimeOffset = mPageSize * mRequestTimes; 180 | } catch (Exception e) { 181 | exception = e; 182 | e.printStackTrace(); 183 | } finally { 184 | if (exception == null) 185 | dispatchAdapterMessage(AdapterMessageType.CHANGE, 0); 186 | 187 | switch (type) { 188 | case Initialize: 189 | dispatchUIMessage(UIMessageType.InitializeDone, exception, data); 190 | break; 191 | case More: 192 | dispatchUIMessage(UIMessageType.LoadMoreDone, exception, data); 193 | break; 194 | case Refresh: 195 | dispatchUIMessage(UIMessageType.RefreshingDone, exception, data); 196 | break; 197 | } 198 | if (isEnd) { 199 | dispatchUIMessage(UIMessageType.End, null, null); 200 | } 201 | isBusy = false; 202 | } 203 | } 204 | }).start(); 205 | } 206 | 207 | 208 | private void dispatchAdapterMessage(AdapterMessageType type, int position) { 209 | Handler handler = new AdapterPosterHandler(Looper.getMainLooper()); 210 | Message msg = Message.obtain(handler); 211 | msg.arg1 = type.ordinal(); 212 | msg.arg2 = position; 213 | msg.sendToTarget(); 214 | } 215 | 216 | private void dispatchUIMessage(UIMessageType type, Exception e, List data) { 217 | if (mUIRespondents == null || mUIRespondents.size() == 0) { 218 | return; 219 | } 220 | Handler handler = new UIPosterHandler(Looper.getMainLooper()); 221 | Message msg = Message.obtain(handler); 222 | msg.arg1 = type.ordinal(); 223 | msg.obj = data != null ? data : e; 224 | msg.sendToTarget(); 225 | } 226 | 227 | public T getData(int position) { 228 | return mRepository.get(position); 229 | } 230 | 231 | 232 | protected enum ActionType { 233 | Initialize, 234 | More, 235 | Refresh 236 | } 237 | 238 | public enum AdapterMessageType { 239 | INSERT, 240 | CHANGE, REMOVE 241 | } 242 | 243 | public enum UIMessageType { 244 | OnDataSetChanged, 245 | InitializeStart, 246 | InitializeDone, 247 | LoadingMoreStart, 248 | LoadMoreDone, 249 | RefreshingStart, 250 | RefreshingDone, 251 | End 252 | } 253 | 254 | public class AdapterPosterHandler extends Handler { 255 | 256 | public AdapterPosterHandler(Looper looper) { 257 | super(looper); 258 | } 259 | 260 | @Override 261 | public void handleMessage(Message msg) { 262 | for (AdapterRespondent adapterRespondent : mAdapterRespondents) { 263 | AdapterMessageType type = AdapterMessageType.values()[msg.arg1]; 264 | switch (type) { 265 | case INSERT: 266 | adapterRespondent.onDataInsert(msg.arg1); 267 | break; 268 | case REMOVE: 269 | adapterRespondent.onDataRemove(msg.arg2); 270 | break; 271 | case CHANGE: 272 | adapterRespondent.onDataChanged(); 273 | break; 274 | } 275 | 276 | } 277 | } 278 | 279 | } 280 | 281 | 282 | public class UIPosterHandler extends Handler { 283 | 284 | private UIPosterHandler(Looper looper) { 285 | super(looper); 286 | } 287 | 288 | 289 | @Override 290 | public void handleMessage(Message msg) { 291 | if (getLooper() != Looper.getMainLooper()) { 292 | throw new RuntimeException("Can not post UI update in non-main thread"); 293 | } 294 | for (UIRespondent ui : mUIRespondents) { 295 | UIMessageType type = UIMessageType.values()[msg.arg1]; 296 | Object o = msg.obj; 297 | List data = o instanceof List ? (List) o : null; 298 | Exception e = o instanceof Exception ? (Exception) o : null; 299 | switch (type) { 300 | case InitializeStart: 301 | ui.onInitializeStart(); 302 | break; 303 | case InitializeDone: 304 | ui.onInitializeDone(e, data); 305 | break; 306 | case LoadingMoreStart: 307 | ui.onLoadingMoreStart(); 308 | break; 309 | case LoadMoreDone: 310 | ui.onLoadMoreDone(e, data); 311 | break; 312 | case RefreshingStart: 313 | ui.onRefreshingStart(); 314 | break; 315 | case RefreshingDone: 316 | ui.onRefreshDone(e, data); 317 | break; 318 | case End: 319 | ui.onEnd(); 320 | break; 321 | } 322 | } 323 | } 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /app/src/main/java/com/ganhuo/app/adapters/ContentAdapterBase.java: -------------------------------------------------------------------------------- 1 | package com.ganhuo.app.adapters; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.Button; 9 | import android.widget.ProgressBar; 10 | import android.widget.TextView; 11 | 12 | 13 | import com.ganhuo.app.R; 14 | import com.ganhuo.app.actions.DataController; 15 | import com.ganhuo.app.holders.FooterViewHolder; 16 | import com.ganhuo.app.interfaces.AdapterRespondent; 17 | import com.ganhuo.app.interfaces.SimpleUIRespondent; 18 | 19 | import java.util.List; 20 | 21 | public abstract class ContentAdapterBase extends RecyclerView.Adapter { 22 | 23 | protected Context mContext; 24 | protected LayoutInflater mInflater; 25 | protected DataController mDataController; 26 | protected View mLoadingMoreView; 27 | 28 | public ContentAdapterBase(Context mContext, final DataController mDataController) { 29 | this.mContext = mContext; 30 | this.mDataController = mDataController; 31 | this.mInflater = LayoutInflater.from(mContext); 32 | this.mDataController.addAdapterRespondent(new AdapterRespondent() { 33 | @Override 34 | public void onDataChanged() { 35 | notifyDataSetChanged(); 36 | } 37 | 38 | @Override 39 | public void onDataInsert(int position) { 40 | notifyDataSetChanged(); 41 | } 42 | 43 | @Override 44 | public void onDataRemove(int position) { 45 | notifyItemRemoved(position); 46 | } 47 | }); 48 | this.mDataController.addUIRespondent(new DataObserver()); 49 | } 50 | 51 | @Override 52 | public int getItemViewType(int position) { 53 | if (needHeader() && position == 0) { 54 | return CommonFeature.HEADER.ordinal(); 55 | } 56 | 57 | if (position == getItemCount() - 1) { 58 | return CommonFeature.FOOTER.ordinal(); 59 | } 60 | 61 | return CommonFeature.COMMON.ordinal(); 62 | } 63 | 64 | @Override 65 | public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 66 | if (viewType == CommonFeature.FOOTER.ordinal()) { 67 | mLoadingMoreView = mInflater.inflate(R.layout.recyclerview_footer, parent, false); 68 | return new FooterViewHolder(mLoadingMoreView); 69 | } else if (viewType == CommonFeature.HEADER.ordinal()) { 70 | return onCreateCustomHeaderHolder(parent); 71 | } else { 72 | return onCreateCustomContentHolder(parent, viewType); 73 | } 74 | } 75 | 76 | @Override 77 | public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 78 | if (needHeader() && position == 0) { 79 | onBindCustomHeaderHolder(holder); 80 | return; 81 | } 82 | 83 | if (needHeader()) { 84 | position -= 1; 85 | } 86 | 87 | if (holder instanceof FooterViewHolder) { 88 | FooterViewHolder footerViewHolder = (FooterViewHolder) holder; 89 | if (mDataController.isEnd()) { 90 | footerViewHolder.onBindViewHolder(); 91 | } else { 92 | mDataController.more(); 93 | } 94 | } else { 95 | onBindCustomViewHolder(holder, position); 96 | } 97 | 98 | } 99 | 100 | protected RecyclerView.ViewHolder onCreateCustomHeaderHolder(ViewGroup parent) { 101 | return null; 102 | } 103 | 104 | protected void onBindCustomHeaderHolder(RecyclerView.ViewHolder holder) { 105 | 106 | } 107 | 108 | protected abstract RecyclerView.ViewHolder onCreateCustomContentHolder(ViewGroup parent, int viewType); 109 | 110 | 111 | protected abstract void onBindCustomViewHolder(RecyclerView.ViewHolder holder, int position); 112 | 113 | protected boolean needHeader() { 114 | return false; 115 | } 116 | 117 | 118 | @Override 119 | public void onAttachedToRecyclerView(RecyclerView recyclerView) { 120 | super.onAttachedToRecyclerView(recyclerView); 121 | mDataController.initialize(); 122 | } 123 | 124 | @Override 125 | public int getItemCount() { 126 | if (mDataController.getSize() == 0) { 127 | if (needHeader()) { 128 | return 1; 129 | } else { 130 | return 0; 131 | } 132 | } 133 | if (needHeader()) { 134 | return mDataController.getSize() + 2; 135 | } 136 | 137 | return mDataController.getSize() + 1; 138 | } 139 | 140 | public enum CommonFeature{ 141 | HEADER, 142 | COMMON, 143 | FOOTER 144 | } 145 | 146 | private class DataObserver extends SimpleUIRespondent { 147 | @Override 148 | public void onLoadMoreDone(Exception e, List data) { 149 | if (e != null && mLoadingMoreView != null) { 150 | final Button refresh = (Button) mLoadingMoreView.findViewById(R.id.loading_more_retry); 151 | final TextView tips = (TextView) mLoadingMoreView.findViewById(R.id.loading_more_tips); 152 | final ProgressBar progressBar = (ProgressBar) mLoadingMoreView.findViewById(R.id.loading_more_progress); 153 | progressBar.setVisibility(View.INVISIBLE); 154 | refresh.setVisibility(View.VISIBLE); 155 | tips.setVisibility(View.VISIBLE); 156 | refresh.setOnClickListener(new View.OnClickListener() { 157 | @Override 158 | public void onClick(View v) { 159 | refresh.setVisibility(View.INVISIBLE); 160 | tips.setVisibility(View.INVISIBLE); 161 | progressBar.setVisibility(View.VISIBLE); 162 | mDataController.more(); 163 | } 164 | }); 165 | } 166 | } 167 | } 168 | } 169 | 170 | -------------------------------------------------------------------------------- /app/src/main/java/com/ganhuo/app/adapters/EntryListAdapter.java: -------------------------------------------------------------------------------- 1 | package com.ganhuo.app.adapters; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.ViewGroup; 6 | 7 | import com.ganhuo.app.actions.DataController; 8 | import com.ganhuo.app.holders.EntryViewHolder; 9 | import com.ganhuo.app.models.Entry; 10 | 11 | public class EntryListAdapter extends ContentAdapterBase { 12 | 13 | private Context mContext; 14 | 15 | public EntryListAdapter(Context context, DataController dataController) { 16 | super(context, dataController); 17 | this.mContext = context; 18 | } 19 | 20 | 21 | @Override 22 | protected RecyclerView.ViewHolder onCreateCustomContentHolder(ViewGroup parent, int viewType) { 23 | return EntryViewHolder.create(mContext, parent); 24 | } 25 | 26 | @Override 27 | protected void onBindCustomViewHolder(RecyclerView.ViewHolder holder, int position) { 28 | ((EntryViewHolder) holder).onBindViewHolder(mDataController.getData(position)); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/ganhuo/app/fragments/CommonFragment.java: -------------------------------------------------------------------------------- 1 | package com.ganhuo.app.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.widget.ContentLoadingProgressBar; 6 | import android.support.v4.widget.SwipeRefreshLayout; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.Button; 13 | import android.widget.TextView; 14 | import android.widget.Toast; 15 | 16 | import com.ganhuo.app.R; 17 | import com.ganhuo.app.actions.DataController; 18 | 19 | import java.util.List; 20 | 21 | public abstract class CommonFragment extends ContentFragmentBase { 22 | protected RecyclerView mRecyclerView; 23 | protected ViewGroup mRoot, mPagerPage, mLoadingPage; 24 | protected ContentLoadingProgressBar mLoadingBar; 25 | 26 | protected TextView mLoadingTips; 27 | protected Button mLoadingRetry; 28 | 29 | protected SwipeRefreshLayout mSwipeRefresh; 30 | protected RecyclerView.Adapter mAdapter; 31 | protected DataController mDataController; 32 | 33 | 34 | @Override 35 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 36 | ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_content, container, false); 37 | mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview_content); 38 | mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); 39 | mPagerPage = (ViewGroup) rootView.findViewById(R.id.pager_page); 40 | mLoadingPage = (ViewGroup) rootView.findViewById(R.id.loading_page); 41 | mLoadingBar = (ContentLoadingProgressBar) mLoadingPage.findViewById(R.id.loading_bar); 42 | mLoadingRetry = (Button) mLoadingPage.findViewById(R.id.loading_refresh); 43 | mLoadingTips = (TextView) mLoadingPage.findViewById(R.id.loading_tips); 44 | mSwipeRefresh = (SwipeRefreshLayout) mPagerPage; 45 | mRoot = rootView; 46 | if (enableRefresh()) { 47 | mSwipeRefresh.setOnRefreshListener(this); 48 | } else { 49 | mSwipeRefresh.setEnabled(false); 50 | } 51 | return rootView; 52 | } 53 | 54 | @Override 55 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 56 | mDataController = onGenerateDataController(); 57 | mAdapter = onGenerateAdapter(mDataController); 58 | mDataController.addUIRespondent(this); 59 | mDataController.addAdapterRespondent(this); 60 | mRecyclerView.setAdapter(mAdapter); 61 | } 62 | 63 | protected abstract DataController onGenerateDataController(); 64 | 65 | protected abstract RecyclerView.Adapter onGenerateAdapter(DataController controller); 66 | 67 | protected abstract boolean enableRefresh(); 68 | 69 | @Override 70 | public void onDataChanged() { 71 | mAdapter.notifyDataSetChanged(); 72 | } 73 | 74 | @Override 75 | public void onDataInsert(int position) { 76 | mAdapter.notifyItemInserted(position); 77 | } 78 | 79 | @Override 80 | public void onDataRemove(int position) { 81 | mAdapter.notifyItemRemoved(position); 82 | } 83 | 84 | @Override 85 | public void onRefreshingStart() { 86 | super.onRefreshingStart(); 87 | mSwipeRefresh.setRefreshing(true); 88 | } 89 | 90 | @Override 91 | public void onRefreshDone(Exception e, List data) { 92 | if (e == null) { 93 | if (data == null || data.size() == 0) { 94 | Toast.makeText(getActivity(), "没啦,干货正在准备中呢!", Toast.LENGTH_SHORT).show(); 95 | } else { 96 | Toast.makeText(getActivity(), "成功挖掘 " + data.size() + " 条新干货", Toast.LENGTH_SHORT).show(); 97 | } 98 | } else { 99 | e.printStackTrace(); 100 | Toast.makeText(getActivity(), "出现了些小问题", Toast.LENGTH_SHORT).show(); 101 | } 102 | mSwipeRefresh.setRefreshing(false); 103 | } 104 | 105 | @Override 106 | public void onInitializeDone(Exception e, List data) { 107 | super.onInitializeDone(e, data); 108 | mLoadingBar.hide(); 109 | if (e == null) { 110 | mRoot.bringChildToFront(mPagerPage); 111 | } else { 112 | mLoadingRetry.setVisibility(View.VISIBLE); 113 | mLoadingTips.setVisibility(View.VISIBLE); 114 | mLoadingRetry.setOnClickListener(new View.OnClickListener() { 115 | @Override 116 | public void onClick(View v) { 117 | mLoadingRetry.setVisibility(View.GONE); 118 | mLoadingTips.setVisibility(View.GONE); 119 | mDataController.initialize(); 120 | } 121 | }); 122 | mLoadingTips.setVisibility(View.VISIBLE); 123 | e.printStackTrace(); 124 | } 125 | } 126 | 127 | @Override 128 | public void onRefresh() { 129 | mDataController.refresh(); 130 | } 131 | 132 | } 133 | 134 | 135 | -------------------------------------------------------------------------------- /app/src/main/java/com/ganhuo/app/fragments/ContentFragmentBase.java: -------------------------------------------------------------------------------- 1 | package com.ganhuo.app.fragments; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.support.v4.widget.SwipeRefreshLayout; 5 | 6 | import com.ganhuo.app.interfaces.AdapterRespondent; 7 | import com.ganhuo.app.interfaces.UIRespondent; 8 | 9 | import java.util.List; 10 | 11 | public abstract class ContentFragmentBase extends Fragment implements UIRespondent, AdapterRespondent, SwipeRefreshLayout.OnRefreshListener { 12 | 13 | 14 | @Override 15 | public void onInitializeStart() { 16 | 17 | } 18 | 19 | @Override 20 | public void onInitializeDone(Exception e, List data) { 21 | 22 | } 23 | 24 | @Override 25 | public void onLoadingMoreStart() { 26 | 27 | } 28 | 29 | @Override 30 | public void onLoadMoreDone(Exception e, List data) { 31 | 32 | } 33 | 34 | @Override 35 | public void onRefreshingStart() { 36 | 37 | } 38 | 39 | @Override 40 | public void onRefreshDone(Exception e, List data) { 41 | 42 | } 43 | 44 | @Override 45 | public void onEnd() { 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/ganhuo/app/fragments/EntryListFragment.java: -------------------------------------------------------------------------------- 1 | package com.ganhuo.app.fragments; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | 5 | import com.ganhuo.app.actions.DataController; 6 | import com.ganhuo.app.adapters.EntryListAdapter; 7 | import com.ganhuo.app.models.Entry; 8 | import com.ganhuo.app.providers.EntryListProvider; 9 | 10 | public class EntryListFragment extends CommonFragment { 11 | @Override 12 | protected DataController onGenerateDataController() { 13 | return new EntryListProvider(); 14 | } 15 | 16 | @Override 17 | protected RecyclerView.Adapter onGenerateAdapter(DataController controller) { 18 | return new EntryListAdapter(getActivity(), controller); 19 | } 20 | 21 | @Override 22 | protected boolean enableRefresh() { 23 | return true; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/ganhuo/app/holders/EntryViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.ganhuo.app.holders; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.ImageView; 10 | import android.widget.TextView; 11 | 12 | import com.bumptech.glide.Glide; 13 | import com.ganhuo.app.R; 14 | import com.ganhuo.app.WebViewActivity; 15 | import com.ganhuo.app.models.Entry; 16 | 17 | public class EntryViewHolder extends RecyclerView.ViewHolder { 18 | public final ImageView thumb; 19 | public final TextView title, content, time; 20 | public final View parent; 21 | 22 | private EntryViewHolder(View itemView) { 23 | super(itemView); 24 | thumb = (ImageView) itemView.findViewById(R.id.thumb); 25 | title = (TextView) itemView.findViewById(R.id.title); 26 | content = (TextView) itemView.findViewById(R.id.content); 27 | time = (TextView) itemView.findViewById(R.id.time); 28 | parent = itemView; 29 | } 30 | 31 | public static EntryViewHolder create(final Context context, ViewGroup parent) { 32 | View v = LayoutInflater.from(context).inflate(R.layout.entry_item, parent, false); 33 | v.setOnClickListener(new View.OnClickListener() { 34 | @Override 35 | public void onClick(View v) { 36 | Entry entry = (Entry) v.getTag(); 37 | if (entry != null) { 38 | Intent intent = new Intent(context, WebViewActivity.class); 39 | intent.putExtra("url", entry.getUrl()); 40 | context.startActivity(intent); 41 | } 42 | } 43 | }); 44 | return new EntryViewHolder(v); 45 | } 46 | 47 | public void onBindViewHolder(Entry entry) { 48 | parent.setTag(entry); 49 | title.setText(entry.getTitle()); 50 | content.setText(entry.getContent()); 51 | time.setText(entry.getBeautyPublishDate()); 52 | Glide.with(thumb.getContext()).load(entry.getThumbUrl()).into(thumb); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/ganhuo/app/holders/FooterViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.ganhuo.app.holders; 2 | 3 | import android.support.v4.widget.ContentLoadingProgressBar; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.View; 6 | import android.widget.TextView; 7 | 8 | import com.ganhuo.app.R; 9 | 10 | 11 | public class FooterViewHolder extends RecyclerView.ViewHolder { 12 | public final ContentLoadingProgressBar progressBar; 13 | public final TextView tips; 14 | 15 | public FooterViewHolder(View itemView) { 16 | super(itemView); 17 | progressBar = (ContentLoadingProgressBar) itemView.findViewById(R.id.loading_more_progress); 18 | tips = (TextView) itemView.findViewById(R.id.loading_more_tips); 19 | } 20 | 21 | public void onBindViewHolder() { 22 | tips.setText("到头了"); 23 | tips.setVisibility(View.VISIBLE); 24 | progressBar.hide(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/ganhuo/app/interfaces/AdapterRespondent.java: -------------------------------------------------------------------------------- 1 | package com.ganhuo.app.interfaces; 2 | 3 | public interface AdapterRespondent { 4 | void onDataChanged(); 5 | 6 | void onDataInsert(int position); 7 | 8 | void onDataRemove(int position); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/ganhuo/app/interfaces/DataProvider.java: -------------------------------------------------------------------------------- 1 | package com.ganhuo.app.interfaces; 2 | 3 | import java.util.List; 4 | 5 | public interface DataProvider { 6 | List doInitialize() throws Exception; 7 | 8 | List doRefresh() throws Exception; 9 | 10 | List doMore() throws Exception; 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/ganhuo/app/interfaces/SimpleUIRespondent.java: -------------------------------------------------------------------------------- 1 | package com.ganhuo.app.interfaces; 2 | 3 | import java.util.List; 4 | 5 | public class SimpleUIRespondent implements UIRespondent { 6 | @Override 7 | public void onInitializeStart() { 8 | 9 | } 10 | 11 | @Override 12 | public void onInitializeDone(Exception e, List data) { 13 | 14 | } 15 | 16 | @Override 17 | public void onLoadingMoreStart() { 18 | 19 | } 20 | 21 | @Override 22 | public void onLoadMoreDone(Exception e, List data) { 23 | 24 | } 25 | 26 | @Override 27 | public void onRefreshingStart() { 28 | 29 | } 30 | 31 | @Override 32 | public void onRefreshDone(Exception e, List data) { 33 | 34 | } 35 | 36 | @Override 37 | public void onEnd() { 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/ganhuo/app/interfaces/UIRespondent.java: -------------------------------------------------------------------------------- 1 | package com.ganhuo.app.interfaces; 2 | 3 | import java.util.List; 4 | 5 | public interface UIRespondent { 6 | 7 | void onInitializeStart(); 8 | 9 | void onInitializeDone(Exception e, List data); 10 | 11 | void onLoadingMoreStart(); 12 | 13 | void onLoadMoreDone(Exception e, List data); 14 | 15 | void onRefreshingStart(); 16 | 17 | void onRefreshDone(Exception e, List data); 18 | 19 | void onEnd(); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/ganhuo/app/models/Entry.java: -------------------------------------------------------------------------------- 1 | package com.ganhuo.app.models; 2 | 3 | import android.text.format.DateFormat; 4 | 5 | import com.avos.avoscloud.AVClassName; 6 | import com.avos.avoscloud.AVFile; 7 | import com.avos.avoscloud.AVObject; 8 | 9 | import java.util.Date; 10 | 11 | @AVClassName("Entry") 12 | public class Entry extends AVObject { 13 | public String getTitle() { 14 | return getString("title"); 15 | } 16 | 17 | public String getContent() { 18 | return getString("content"); 19 | } 20 | 21 | public String getUrl() { 22 | return getString("url"); 23 | } 24 | 25 | public AVFile getThumb() { 26 | return getAVFile("thumb"); 27 | } 28 | 29 | 30 | public Date getPublishDate() { 31 | return getDate("publishDate"); 32 | } 33 | 34 | public String getBeautyPublishDate() { 35 | return DateFormat.format("yyyy-MM-dd", getPublishDate()).toString(); 36 | } 37 | 38 | public String getThumbUrl() { 39 | return getThumb().getUrl(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/ganhuo/app/providers/EntryListProvider.java: -------------------------------------------------------------------------------- 1 | package com.ganhuo.app.providers; 2 | 3 | import com.avos.avoscloud.AVQuery; 4 | import com.ganhuo.app.actions.DataController; 5 | import com.ganhuo.app.models.Entry; 6 | 7 | import java.util.List; 8 | 9 | public class EntryListProvider extends DataController { 10 | 11 | 12 | public List getData() throws Exception { 13 | AVQuery query = new AVQuery<>("Entry"); 14 | query.setSkip(getRequestOffset()); 15 | query.limit(getPageSize()); 16 | query.orderByDescending("createdAt"); 17 | return query.find(); 18 | } 19 | 20 | public List getLatest() throws Exception { 21 | if (getData() == null || getData().size() == 0) { 22 | return null; 23 | } 24 | AVQuery query = new AVQuery<>("Entry"); 25 | query.whereGreaterThan("createdAt", getData().get(0).getCreatedAt()); 26 | return query.find(); 27 | } 28 | 29 | @Override 30 | public List doInitialize() throws Exception { 31 | return getData(); 32 | } 33 | 34 | @Override 35 | public List doRefresh() throws Exception { 36 | return getLatest(); 37 | } 38 | 39 | @Override 40 | public List doMore() throws Exception { 41 | return getData(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 18 | 19 | 27 | 28 | 35 | 36 | 39 | 40 | 43 | 44 | 45 | 48 | 49 | 52 | 53 | 56 | 57 | 60 | 61 | 64 | 65 | 68 | 69 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/entry_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 22 | 23 | 30 | 31 | 41 | 42 | 51 | 52 | 60 | 61 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | 24 | 25 | 33 | 34 | 43 | 44 |