diff --git a/.gitignore b/.gitignore index ccf2efe..494f7aa 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,9 @@ proguard/ # Log Files *.log + +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/captures diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..5af12df --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +AndroidBlog \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..9a8b7e5 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..c2bae49 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..8d2df47 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..a8009e8 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + Android + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..5b7860a --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/AndroidBlog.iml b/AndroidBlog.iml new file mode 100644 index 0000000..dc2c2df --- /dev/null +++ b/AndroidBlog.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 48826f8..619f157 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,28 @@ -# AndroidBlog -A simple blog reader app +#Android博客客户端 +--- +这是一个简单的android博客阅读应用,遵循 Android 设计风格,UI借鉴我个人非常喜欢的一款应用[Etsy](https://www.etsy.com/mobile/?ref=ftr). + +##构建与支持 +项目使用 Android Studio (v1.2) + Gradle (v2.2.1) 构建 +该项目支持 Android 2.3+ ( api level 9) + +## 效果图 +image1 image2 +image3image4 + +##使用到的开源项目 +[AndroidStaggeredGrid](https://github.com/etsy/AndroidStaggeredGrid) Esty 开源的瀑布流布局库 +[Sugar](https://github.com/satyan/sugar) 一个简单的ORM框架 +[Volley](https://android.googlesource.com/platform/frameworks/volley) Google官方发布的网络请求库 +[ButterKnife](http://jakewharton.github.io/butterknife/) 一个专注于Android系统的View注入框架 +[NineOldAndroid](http://nineoldandroids.com/) 动画支持库 +[Jackson](https://github.com/FasterXML/jackson) json解析库 +... + +## 意见与建议 +欢迎通过Pull Requests 方式向本项目贡献代码 +如果发现程序的任何BUG,或对本项目有任何意见,可以直接创建 [Issue](https://github.com/likebamboo/AndroidBlog/issues) 告知我。 + +## 关于 +本项目的前端和后端均由我一人开发,后端代码暂不开源,主要是因为本人不太会写后端,后端代码太烂了(其实前端也不怎么样)。 +本项目的Apk安装包赞不会发布到任何应用市场,请直接在[release](https://github.com/likebamboo/AndroidBlog/releases)下载。 diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..d2e0e68 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,3 @@ +/build +gradle.properties +osa.key.jks \ No newline at end of file diff --git a/app/app.iml b/app/app.iml new file mode 100644 index 0000000..2b666ef --- /dev/null +++ b/app/app.iml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..33b8c41 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,70 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 22 + buildToolsVersion '19.1.0' + + defaultConfig { + applicationId 'com.likebamboo.osa.android' + minSdkVersion 9 + targetSdkVersion 22 + versionCode 1 + versionName '0.1' + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + packagingOptions { + exclude 'META-INF/NOTICE' + exclude 'META-INF/LICENSE' + } + + lintOptions { + abortOnError false + } + + signingConfigs { + release { + try { + storeFile file("osa.key.jks") + storePassword KEYSTORE_PASSWORD + keyAlias "androidblog" + keyPassword KEY_PASSWORD + } + catch (ex) { + throw new InvalidUserDataException("You should define KEYSTORE_PASSWORD and KEY_PASSWORD in gradle.properties.") + } + } + } + + buildTypes{ + release { + signingConfig signingConfigs.release + } + } + +} + +dependencies { + compile fileTree(include: ['*.jar'], dir: 'libs') + compile 'com.android.support:appcompat-v7:22.+' + // recyclerview + compile 'com.android.support:recyclerview-v7:22.+' + // volley 网络请求库 + compile 'com.mcxiaoke.volley:library:1.0.+@aar' + // jackson,json解析包 https://github.com/FasterXML/jackson + compile 'com.fasterxml.jackson.core:jackson-databind:2.3.1' + // etsy 瀑布流 https://github.com/etsy/AndroidStaggeredGrid + compile 'com.etsy.android.grid:library:1.0.5' + // 依赖注入框架 https://github.com/JakeWharton/butterknife + compile 'com.jakewharton:butterknife:6.1.0' + // orm 框架 + compile 'com.github.satyan:sugar:1.3.1' + // nineold 动画支持库 + compile 'com.nineoldandroids:library:2.4.0' + // + compile 'com.github.ozodrukh:CircularReveal:1.0.6@aar' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..a21fea5 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,29 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in E:\android-sdk/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 *; +#} + +-keep class butterknife.** { *; } +-dontwarn butterknife.internal.** +-keep class **$$ViewInjector { *; } + +-keepclasseswithmembernames class * { + @butterknife.* ; +} + +-keepclasseswithmembernames class * { + @butterknife.* ; +} diff --git a/app/src/androidTest/java/com/likebamboo/osa/android/ApplicationTest.java b/app/src/androidTest/java/com/likebamboo/osa/android/ApplicationTest.java new file mode 100644 index 0000000..daeba31 --- /dev/null +++ b/app/src/androidTest/java/com/likebamboo/osa/android/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.likebamboo.osa.android; + +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/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..31591a5 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/assets/fontawesome-webfont.ttf b/app/src/main/assets/fontawesome-webfont.ttf new file mode 100644 index 0000000..96a3639 Binary files /dev/null and b/app/src/main/assets/fontawesome-webfont.ttf differ diff --git a/app/src/main/java/com/likebamboo/osa/android/OsaApplication.java b/app/src/main/java/com/likebamboo/osa/android/OsaApplication.java new file mode 100644 index 0000000..59fdb9b --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/OsaApplication.java @@ -0,0 +1,36 @@ +package com.likebamboo.osa.android; + +import com.likebamboo.osa.android.request.RequestManager; +import com.orm.SugarApp; + +/** + * Created by likebamboo on 2015/5/10. + */ +public class OsaApplication extends SugarApp { + + @Override + public void onCreate() { + super.onCreate(); + init(); + } + + + /** + * 初始化 + */ + private void init() { + // 初始化volley请求管理 + RequestManager.init(this); + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + } + + + @Override + public void onTerminate() { + super.onTerminate(); + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/cache/BitmapLruCache.java b/app/src/main/java/com/likebamboo/osa/android/cache/BitmapLruCache.java new file mode 100644 index 0000000..6d1977a --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/cache/BitmapLruCache.java @@ -0,0 +1,38 @@ +package com.likebamboo.osa.android.cache; + +import android.graphics.Bitmap; +import android.support.v4.util.LruCache; +import android.util.Log; + +import com.android.volley.toolbox.ImageLoader; + +/** + * Basic LRU Memory cache. + * + * @author Trey Robinson + */ +public class BitmapLruCache extends LruCache implements ImageLoader.ImageCache { + + private final String TAG = this.getClass().getSimpleName(); + + public BitmapLruCache(int maxSize) { + super(maxSize); + } + + @Override + protected int sizeOf(String key, Bitmap value) { + return value.getRowBytes() * value.getHeight(); + } + + @Override + public Bitmap getBitmap(String url) { + Log.v(TAG, "Retrieved item from Mem Cache"); + return get(url); + } + + @Override + public void putBitmap(String url, Bitmap bitmap) { + Log.v(TAG, "Added item to Mem Cache"); + put(url, bitmap); + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/entity/AuthorList.java b/app/src/main/java/com/likebamboo/osa/android/entity/AuthorList.java new file mode 100644 index 0000000..d23140b --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/entity/AuthorList.java @@ -0,0 +1,215 @@ +package com.likebamboo.osa.android.entity; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.ArrayList; + +/** + * 作者 + * Created by likebamboo 2015/05/14 + * + * @author likebamboo + */ +public class AuthorList extends BaseRsp { + + @JsonProperty("result") + private ArrayList mList = null; + + public ArrayList getList() { + return mList; + } + + public void setList(ArrayList list) { + this.mList = list; + } + + // 忽略未知属性 + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Author implements Parcelable { + /** + * 名称 * + */ + private String name = ""; + + /** + * 头像 * + */ + private String avatar = ""; + + /** + * 简介 * + */ + private String introduction = ""; + + /** + * 博客地址 * + */ + private String blog = ""; + + /** + * github 地址 * + */ + private String github = ""; + + /** + * weibo 地址 * + */ + private String weibo = ""; + + /** + * twitter 地址 * + */ + private String twitter = ""; + + /** + * facebook 地址 * + */ + private String facebook = ""; + + /** + * 添加时间 + */ + private String addTime = ""; + + /** + * 索引 + */ + @JsonProperty("idx") + private String index = ""; + + public String getIndex() { + return index; + } + + public void setIndex(String index) { + this.index = index; + } + + public String getAddTime() { + return addTime; + } + + public void setAddTime(String addTime) { + this.addTime = addTime; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAvatar() { + return avatar; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getIntroduction() { + return introduction; + } + + public void setIntroduction(String introduction) { + this.introduction = introduction; + } + + public String getBlog() { + return blog; + } + + public void setBlog(String blog) { + this.blog = blog; + } + + public String getGithub() { + return github; + } + + public void setGithub(String github) { + this.github = github; + } + + public String getWeibo() { + return weibo; + } + + public void setWeibo(String weibo) { + this.weibo = weibo; + } + + public String getTwitter() { + return twitter; + } + + public void setTwitter(String twitter) { + this.twitter = twitter; + } + + public String getFacebook() { + return facebook; + } + + public void setFacebook(String facebook) { + this.facebook = facebook; + } + + @Override + public String toString() { + return "AuthorList [name=" + name + ", avatar=" + avatar + ", introduction=" + introduction + ", blog=" + blog + + ", github=" + github + ", weibo=" + weibo + ", twitter=" + twitter + ", facebook=" + facebook + "]"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.name); + dest.writeString(this.avatar); + dest.writeString(this.introduction); + dest.writeString(this.blog); + dest.writeString(this.github); + dest.writeString(this.weibo); + dest.writeString(this.twitter); + dest.writeString(this.facebook); + dest.writeString(this.addTime); + dest.writeString(this.index); + } + + public Author() { + } + + private Author(Parcel in) { + this.name = in.readString(); + this.avatar = in.readString(); + this.introduction = in.readString(); + this.blog = in.readString(); + this.github = in.readString(); + this.weibo = in.readString(); + this.twitter = in.readString(); + this.facebook = in.readString(); + this.addTime = in.readString(); + this.index = in.readString(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public Author createFromParcel(Parcel source) { + return new Author(source); + } + + public Author[] newArray(int size) { + return new Author[size]; + } + }; + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/entity/BaseRsp.java b/app/src/main/java/com/likebamboo/osa/android/entity/BaseRsp.java new file mode 100644 index 0000000..7b5eda9 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/entity/BaseRsp.java @@ -0,0 +1,41 @@ +package com.likebamboo.osa.android.entity; + +/** + * Created by wentaoli on 2015/5/12. + */ +public class BaseRsp { + /** + * 返回信息 + */ + private String message = ""; + + /** + * 错误码 + */ + private int errorCode = -1; + + @Override + public String toString() { + return "BaseRsp{" + + "errorCode=" + errorCode + + ", message='" + message + '\'' + + '}'; + } + + public int getErrorCode() { + return errorCode; + } + + public void setErrorCode(int errorCode) { + this.errorCode = errorCode; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/app/src/main/java/com/likebamboo/osa/android/entity/BlogList.java b/app/src/main/java/com/likebamboo/osa/android/entity/BlogList.java new file mode 100644 index 0000000..34441ed --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/entity/BlogList.java @@ -0,0 +1,268 @@ +package com.likebamboo.osa.android.entity; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.ArrayList; + +/** + * Created by wentaoli on 2015/5/12. + */ +public class BlogList extends BaseRsp { + + @JsonProperty("result") + private ArrayList mList = null; + + public ArrayList getList() { + return mList; + } + + public void setList(ArrayList list) { + this.mList = list; + } + + /** + * 博客基本信息 + * + * @author likebamboo + */ + // 忽略未知属性 + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Blog implements Parcelable { + + /** + * id + */ + private long id = 0L; + + /** + * 标题 + */ + private String title = ""; + + /** + * 摘要 + */ + private String abstracts = ""; + + /** + * 类别 + */ + private String categorys = ""; + + /** + * 作者名称 + */ + private String author = ""; + + /** + * URL + */ + private String url = ""; + + /** + * 博客发布时间, + */ + private String postTime = ""; + + private boolean isTrans = false; + + /** + * 博客原作者(针对翻译) + */ + private String oAuthor = ""; + + /** + * 源博客发布时间 + */ + private String oPostTime = ""; + + /** + * 博客来源URL + */ + private String fromUrl = ""; + + /** + * 原文来源URL(针对翻译) + */ + private String oFromUrl = ""; + + /** + * 博客添加时间 + */ + private String addTime = ""; + + public String getAbstracts() { + return abstracts; + } + + public void setAbstracts(String abstracts) { + this.abstracts = abstracts; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getCategorys() { + return categorys; + } + + public void setCategorys(String categorys) { + this.categorys = categorys; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public boolean isTrans() { + return isTrans; + } + + public void setIsTrans(boolean isTrans) { + this.isTrans = isTrans; + } + + public String getPostTime() { + return postTime; + } + + public void setPostTime(String postTime) { + this.postTime = postTime; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getoAuthor() { + return oAuthor; + } + + public void setoAuthor(String oAuthor) { + this.oAuthor = oAuthor; + } + + public String getoPostTime() { + return oPostTime; + } + + public void setoPostTime(String oPostTime) { + this.oPostTime = oPostTime; + } + + public String getoFromUrl() { + return oFromUrl; + } + + public void setoFromUrl(String oFromUrl) { + this.oFromUrl = oFromUrl; + } + + public String getFromUrl() { + return fromUrl; + } + + public void setFromUrl(String fromUrl) { + this.fromUrl = fromUrl; + } + + public String getAddTime() { + return addTime; + } + + public void setAddTime(String addTime) { + this.addTime = addTime; + } + + @Override + public String toString() { + return "Blog {" + + "abstracts='" + abstracts + '\'' + + ", id=" + id + + ", title='" + title + '\'' + + ", categorys='" + categorys + '\'' + + ", authorName='" + author + '\'' + + ", url='" + url + '\'' + + ", postTime='" + postTime + '\'' + + ", isTrans=" + isTrans + + '}'; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(this.id); + dest.writeString(this.title); + dest.writeString(this.abstracts); + dest.writeString(this.categorys); + dest.writeString(this.author); + dest.writeString(this.url); + dest.writeString(this.postTime); + dest.writeByte(isTrans ? (byte) 1 : (byte) 0); + dest.writeString(oAuthor); + dest.writeString(oPostTime); + dest.writeString(fromUrl); + dest.writeString(oFromUrl); + dest.writeString(addTime); + } + + public Blog() { + } + + private Blog(Parcel in) { + this.id = in.readLong(); + this.title = in.readString(); + this.abstracts = in.readString(); + this.categorys = in.readString(); + this.author = in.readString(); + this.url = in.readString(); + this.postTime = in.readString(); + this.isTrans = in.readByte() != 0; + this.oAuthor = in.readString(); + this.oPostTime = in.readString(); + this.fromUrl = in.readString(); + this.oFromUrl = in.readString(); + this.addTime = in.readString(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public Blog createFromParcel(Parcel source) { + return new Blog(source); + } + + public Blog[] newArray(int size) { + return new Blog[size]; + } + }; + } + +} diff --git a/app/src/main/java/com/likebamboo/osa/android/entity/CategoryList.java b/app/src/main/java/com/likebamboo/osa/android/entity/CategoryList.java new file mode 100644 index 0000000..3cd3eca --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/entity/CategoryList.java @@ -0,0 +1,137 @@ +package com.likebamboo.osa.android.entity; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.ArrayList; + +/** + * 类别 + * Created by likebamboo 2015/05/14 + * + * @author likebamboo + */ +public class CategoryList extends BaseRsp { + + @JsonProperty("result") + private ArrayList mList = null; + + public ArrayList getList() { + return mList; + } + + public void setList(ArrayList mList) { + this.mList = mList; + } + + // 忽略未知属性 + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Category implements Parcelable { + /** + * 类别id + */ + private int id = 0; + + /** + * 类别名称 + */ + private String name = ""; + + /** + * 类别描述 + */ + private String description = ""; + + /** + * 添加时间 + */ + @JsonProperty("add_time") + private String addTime = ""; + + /** + * 封面图 + */ + private String cover = ""; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getAddTime() { + return addTime; + } + + public void setAddTime(String addTime) { + this.addTime = addTime; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getCover() { + return cover; + } + + public void setCover(String cover) { + this.cover = cover; + } + + @Override + public String toString() { + return "CategoryList [name=" + name + "]"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.name); + dest.writeString(this.description); + dest.writeString(this.addTime); + dest.writeString(this.cover); + } + + public Category() { + } + + private Category(Parcel in) { + this.name = in.readString(); + this.description = in.readString(); + this.addTime = in.readString(); + this.cover = in.readString(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public Category createFromParcel(Parcel source) { + return new Category(source); + } + + public Category[] newArray(int size) { + return new Category[size]; + } + }; + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/entity/TagList.java b/app/src/main/java/com/likebamboo/osa/android/entity/TagList.java new file mode 100644 index 0000000..fd964b6 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/entity/TagList.java @@ -0,0 +1,163 @@ +package com.likebamboo.osa.android.entity; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.orm.SugarRecord; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by likebamboo on 2015/5/19. + */ +public class TagList extends BaseRsp { + + @JsonProperty("result") + private ArrayList mList = null; + + public ArrayList getList() { + return mList; + } + + public void setList(ArrayList list) { + this.mList = list; + } + + /** + * 标签 + * + * @author likebamboo + */ + // 忽略未知属性 + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Tag extends SugarRecord implements Parcelable { + + /** + * id + */ + @JsonProperty("id") + private long _id = 0L; + + /** + * 标题 + */ + private String name = ""; + + /** + * 摘要 + */ + private String desc = ""; + + /** + * 添加时间 + */ + @JsonIgnoreProperties + private long addTime = 0; + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getTagId() { + return _id; + } + + public void setTagId(long id) { + this._id = id; + } + + public long getAddTime() { + return addTime; + } + + public void setAddTime(long addTime) { + this.addTime = addTime; + } + + /** + * 删除标签 + * + * @param name + */ + public static void delete(String name) { + if (TextUtils.isEmpty(name)) { + return; + } + Tag tag = findTagByName(name); + if (tag != null) { + tag.delete(); + } + } + + /** + * 查找标签 + * + * @param name + */ + public static Tag findTagByName(String name) { + if (TextUtils.isEmpty(name)) { + return null; + } + List tags = find(Tag.class, " name like ? ", name); + if (tags == null || tags.isEmpty()) { + return null; + } + for (Tag t : tags) { + if (t != null && name.equals(t.getName())) { + return t; + } + } + return null; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(this.id); + dest.writeString(this.name); + dest.writeString(this.desc); + dest.writeLong(this.addTime); + } + + public Tag() { + } + + private Tag(Parcel in) { + this.id = in.readLong(); + this.name = in.readString(); + this.desc = in.readString(); + this.addTime = in.readLong(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public Tag createFromParcel(Parcel source) { + return new Tag(source); + } + + public Tag[] newArray(int size) { + return new Tag[size]; + } + }; + } + +} diff --git a/app/src/main/java/com/likebamboo/osa/android/exception/ErrorTrans.java b/app/src/main/java/com/likebamboo/osa/android/exception/ErrorTrans.java new file mode 100644 index 0000000..9618ccc --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/exception/ErrorTrans.java @@ -0,0 +1,41 @@ +package com.likebamboo.osa.android.exception; + +import com.android.volley.AuthFailureError; +import com.android.volley.NetworkError; +import com.android.volley.NoConnectionError; +import com.android.volley.ParseError; +import com.android.volley.ServerError; +import com.android.volley.TimeoutError; + + +/** + * Created by wentaoli on 2015/5/14. + */ +public class ErrorTrans { + + public static OsaException transToOsaException(Throwable a) { + if (a == null) { + return new OsaException("未知错误"); + } + if (a instanceof ParseError) { + return new OsaException("数据解析错误", a); + } + if (a instanceof TimeoutError) { + return new OsaException("请求超时", a); + } + if (a instanceof ServerError) { + return new OsaException("服务器错误", a); + } + if (a instanceof AuthFailureError) { + return new OsaException("请求认证错误", a); + } + if (a instanceof NoConnectionError) { + return new OsaException("网络未连接,请检查网络状态", a); + } + if (a instanceof NetworkError) { + return new OsaException("网络连接异常", a); + } + return new OsaException("未知错误"); + } + +} diff --git a/app/src/main/java/com/likebamboo/osa/android/exception/OsaException.java b/app/src/main/java/com/likebamboo/osa/android/exception/OsaException.java new file mode 100644 index 0000000..da30e87 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/exception/OsaException.java @@ -0,0 +1,21 @@ +package com.likebamboo.osa.android.exception; + +/** + * Created by wentaoli on 2015/5/14. + */ +public class OsaException extends Exception { + public OsaException() { + } + + public OsaException(String detailMessage) { + super(detailMessage); + } + + public OsaException(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + } + + public OsaException(Throwable throwable) { + super(throwable); + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/impl/ImageLoaderListener.java b/app/src/main/java/com/likebamboo/osa/android/impl/ImageLoaderListener.java new file mode 100644 index 0000000..5fe9a33 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/impl/ImageLoaderListener.java @@ -0,0 +1,42 @@ +package com.likebamboo.osa.android.impl; + +import android.widget.ImageView; + +import com.android.volley.VolleyError; +import com.android.volley.toolbox.ImageLoader; + +/** + * volley 图片下载回调 + * Created by wentaoli on 2015/6/4. + */ +public class ImageLoaderListener implements ImageLoader.ImageListener { + + private ImageView imageView = null; + + private int defaultResId = 0; + + public ImageLoaderListener(final ImageView imageView, final String imageUrl, final int defaultResId) { + this.defaultResId = defaultResId; + this.imageView = imageView; + if (imageView != null) { + imageView.setTag(imageUrl); + imageView.setImageResource(defaultResId); + } + } + + @Override + public void onResponse(ImageLoader.ImageContainer imageContainer, boolean b) { + if (imageContainer == null || imageContainer.getBitmap() == null) { + return; + } + if (("" + imageView.getTag()).equals(imageContainer.getRequestUrl())) { + imageView.setImageBitmap(imageContainer.getBitmap()); + } + } + + @Override + public void onErrorResponse(VolleyError volleyError) { + imageView.setImageResource(defaultResId); + } + +} diff --git a/app/src/main/java/com/likebamboo/osa/android/interfaces/IOnItemClickListener.java b/app/src/main/java/com/likebamboo/osa/android/interfaces/IOnItemClickListener.java new file mode 100644 index 0000000..fc5d134 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/interfaces/IOnItemClickListener.java @@ -0,0 +1,15 @@ +package com.likebamboo.osa.android.interfaces; + +/** + * Created by wentaoli on 2015/5/14. + */ +public interface IOnItemClickListener { + + /** + * listView 点击事件回调 + * + * @param postion + * @param item + */ + void onItemClick(int postion, T item); +} diff --git a/app/src/main/java/com/likebamboo/osa/android/request/BaseRequest.java b/app/src/main/java/com/likebamboo/osa/android/request/BaseRequest.java new file mode 100644 index 0000000..acb2b52 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/request/BaseRequest.java @@ -0,0 +1,87 @@ +package com.likebamboo.osa.android.request; + +import android.text.TextUtils; + +import com.android.volley.Request; +import com.android.volley.Response; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Map; + +/** + * Created by likebamboo on 2015/5/12. + */ +public abstract class BaseRequest extends Request { + + public BaseRequest(int method, String url, Response.ErrorListener listener) { + super(method, url, listener); + } + + /** + * 返回json数据中的result字段 + * + * @param data + * @return + */ + protected String getResult(String data) { + if (TextUtils.isEmpty(data)) { + return null; + } + JSONObject json = null; + try { + json = new JSONObject(data); + return json.getString("result"); + } catch (JSONException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + return data; + } + + /** + * 拼接URL(仅针对get方法) + * + * @param method + * @param baseUrl + * @param params + * @return + */ + public static String formatUrl(int method, String baseUrl, Map params) { + if (method != Method.GET) { + return baseUrl; + } + return formatUrl(baseUrl, params); + } + + /** + * 拼接URL(仅针对get方法) + * + * @param baseUrl + * @param params + * @return + */ + public static String formatUrl(String baseUrl, Map params) { + if (TextUtils.isEmpty(baseUrl) || params == null || params.isEmpty()) { + return baseUrl; + } + StringBuilder sb = new StringBuilder(baseUrl); + if (sb.toString().contains("?")) { + // 不以问号结尾 + if (!sb.toString().endsWith("?")) { + sb.append("&"); + } + } else {// 没有问号,添加问号 + sb.append("?"); + } + String split = ""; + for (String key : params.keySet()) { + sb.append(split).append(key).append("=").append(params.get(key)); + split = "&"; + } + + return sb.toString(); + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/request/JsonRequest.java b/app/src/main/java/com/likebamboo/osa/android/request/JsonRequest.java new file mode 100644 index 0000000..18e9a6d --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/request/JsonRequest.java @@ -0,0 +1,104 @@ +/* + * Created by Storm Zhang, Feb 11, 2014. + */ + +package com.likebamboo.osa.android.request; + +import android.text.TextUtils; + +import com.android.volley.AuthFailureError; +import com.android.volley.NetworkResponse; +import com.android.volley.ParseError; +import com.android.volley.Response; +import com.android.volley.Response.ErrorListener; +import com.android.volley.Response.Listener; +import com.android.volley.VolleyError; +import com.android.volley.toolbox.HttpHeaderParser; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Map; + +/** + * JSON请求 + * + * @param + */ +public class JsonRequest extends BaseRequest { + protected final ObjectMapper mapper = new ObjectMapper(); + protected final Class mClazz; + private final Listener mListener; + protected final Map mParams; + + /** + * 是否仅仅解析返回数据中 result 字段的数据 + */ + private boolean justResult = false; + + public JsonRequest(String url, Class clazz, Listener listener, ErrorListener errorListener) { + this(url, clazz, null, listener, errorListener); + } + + public JsonRequest(String url, Class clazz, Map params, Listener listener, ErrorListener errorListener) { + super(Method.GET, formatUrl(url, params), errorListener); + this.mClazz = clazz; + this.mParams = params; + this.mListener = listener; + } + + public JsonRequest(int method, String url, Class clazz, Map params, Listener listener, ErrorListener errorListener) { + super(method, formatUrl(method, url, params), errorListener); + this.mClazz = clazz; + this.mParams = params; + this.mListener = listener; + } + + @Override + public Map getParams() throws AuthFailureError { + return mParams != null ? mParams : super.getParams(); + } + + public boolean isJustResult() { + return justResult; + } + + public void setJustResult(boolean justResult) { + this.justResult = justResult; + } + + @Override + protected void deliverResponse(T response) { + if (mListener == null) { + return; + } + mListener.onResponse(response); + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + try { + String json = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); + if (TextUtils.isEmpty(json)) { + return Response.error(new VolleyError("返回数据为空")); + } + if (justResult) { + json = getResult(json); + } + return Response.success(mapper.readValue(json, mClazz), HttpHeaderParser.parseCacheHeaders(response)); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + return Response.error(new ParseError(e)); + } catch (JsonParseException e) { + e.printStackTrace(); + return Response.error(new ParseError(e)); + } catch (IOException e) { + e.printStackTrace(); + return Response.error(new ParseError(e)); + } catch (Exception e) { + e.printStackTrace(); + return Response.error(new ParseError(e)); + } + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/request/RequestManager.java b/app/src/main/java/com/likebamboo/osa/android/request/RequestManager.java new file mode 100644 index 0000000..051e3b8 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/request/RequestManager.java @@ -0,0 +1,89 @@ +/* + * Created by Storm Zhang, Feb 11, 2014. + */ + +package com.likebamboo.osa.android.request; + +import android.app.ActivityManager; +import android.content.Context; + +import com.android.volley.Request; +import com.android.volley.RequestQueue; +import com.android.volley.toolbox.ImageLoader; +import com.android.volley.toolbox.Volley; +import com.likebamboo.osa.android.cache.BitmapLruCache; + +/** + * volley请求管理 + */ +public class RequestManager { + private static RequestQueue mRequestQueue; + private static ImageLoader mImageLoader; + + private RequestManager() { + // no instances + } + + /** + * 初始化 + * + * @param context + */ + public static void init(Context context) { + mRequestQueue = Volley.newRequestQueue(context); + int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass(); + // Use 1/8th of the available memory for this memory cache. + int cacheSize = 1024 * 1024 * memClass / 8; + mImageLoader = new ImageLoader(mRequestQueue, new BitmapLruCache(cacheSize)); + } + + /** + * 获取请求队列 + * + * @return + */ + public static RequestQueue getRequestQueue() { + if (mRequestQueue != null) { + return mRequestQueue; + } else { + throw new IllegalStateException("RequestQueue not initialized"); + } + } + + /** + * 添加请求 + * + * @param request + * @param tag + */ + public static void addRequest(Request request, Object... tag) { + if (tag != null && tag.length > 0) { + request.setTag(tag[0]); + } + mRequestQueue.add(request); + } + + /** + * 取消请求 + * + * @param tag + */ + public static void cancelAll(Object tag) { + mRequestQueue.cancelAll(tag); + } + + /** + * Returns instance of ImageLoader initialized with {@see FakeImageCache} + * which effectively means that no memory caching is used. This is useful + * for images that you know that will be show only once. + * + * @return + */ + public static ImageLoader getImageLoader() { + if (mImageLoader != null) { + return mImageLoader; + } else { + throw new IllegalStateException("ImageLoader not initialized"); + } + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/request/RequestParams.java b/app/src/main/java/com/likebamboo/osa/android/request/RequestParams.java new file mode 100644 index 0000000..ce5b3be --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/request/RequestParams.java @@ -0,0 +1,16 @@ +/* + * Created by Storm Zhang, Feb 13, 2014. + */ + +package com.likebamboo.osa.android.request; + +import java.util.HashMap; + +public class RequestParams extends HashMap { + private static final long serialVersionUID = 8112047472727256876L; + + public RequestParams add(String key, String value) { + put(key, value); + return this; + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/request/RequestUrl.java b/app/src/main/java/com/likebamboo/osa/android/request/RequestUrl.java new file mode 100644 index 0000000..b8083ce --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/request/RequestUrl.java @@ -0,0 +1,74 @@ +package com.likebamboo.osa.android.request; + +/** + * Created by wentaoli on 2015/5/12. + */ +public class RequestUrl { + + public static final boolean isTest = false; + + /** + * BaseUrl + */ + public static final String BASE_URL = isTest ? "http://192.168.45.38:8080/osa-mobile/" : "http://120.24.93.248/"; + + /** + * BLOG,博客 + */ + public static final String BLOG_URL = BASE_URL + "blog"; + + /** + * BLOG搜索,博客搜索 + */ + public static final String BLOG_SEARCH_URL = BLOG_URL + "/s"; + + /** + * category,分类 + */ + public static final String CATEGORY_URL = BASE_URL + "category"; + + /** + * autor,作者 + */ + public static final String AUTHOR_URL = BASE_URL + "author"; + + /** + * 分类 BLOG, 参数: 类别id + */ + public static final String CATEGORY_BLOG_URL = BLOG_URL + "/category/%s"; + + /** + * 作者的 BLOG, 参数:作者名称 (why名称,不是id? 具体看代码) + */ + public static final String AUTHOR_BLOG_URL = BLOG_URL + "/author/%s/"; + + /** + * BLOG detail展示URL + */ + public static final String BLOG_VIEW_URL = BLOG_URL + "/%s"; + + /** + * BLOG detail info + */ + public static final String BLOG_INFO_URL = BLOG_URL + "/info/%s"; + + /** + * 标签 BLOG, 参数: 标签名称 + */ + public static final String TAG_BLOG_URL = BLOG_URL + "/tag/%s/"; + + /** + * 关于我 + */ + public static final String ABOUT_ME_URL = "http://likebamboo.com/about.html"; + + /** + * issues + */ + public static final String ISSUES_URL = "https://github.com/likebamboo/AndroidBlog/issues"; + + /** + * 关于app + */ + public static final String ABOUT_APP_URL = "https://github.com/likebamboo/AndroidBlog"; +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/AboutActivity.java b/app/src/main/java/com/likebamboo/osa/android/ui/AboutActivity.java new file mode 100644 index 0000000..810bb61 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/AboutActivity.java @@ -0,0 +1,56 @@ +package com.likebamboo.osa.android.ui; + +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.text.Html; +import android.text.method.LinkMovementMethod; +import android.view.View; +import android.widget.TextView; + +import com.likebamboo.osa.android.R; +import com.likebamboo.osa.android.request.RequestUrl; +import com.likebamboo.osa.android.ui.nav.ActivityNavigator; +import com.likebamboo.osa.android.utils.DeviceUtil; + +import butterknife.ButterKnife; +import butterknife.InjectView; + +/** + * 关于界面 + * + * @author likebamboo + */ +public class AboutActivity extends BaseActivity { + + @InjectView(R.id.app_version_tv) + TextView mVersionTv; + + @InjectView(R.id.app_desc_tv) + TextView mDescTv; + + @InjectView(R.id.app_detail_tv) + TextView mDetailTv; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_about); + ButterKnife.inject(this); + + ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_TITLE); + actionBar.setHomeAsUpIndicator(R.drawable.ic_up); + + mVersionTv.setText(getString(R.string.version, DeviceUtil.getVersionName(this))); + mDescTv.setMovementMethod(LinkMovementMethod.getInstance()); + mDescTv.setText(Html.fromHtml(getString(R.string.about_app))); + + mDetailTv.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ActivityNavigator.openWebView(AboutActivity.this, null, RequestUrl.ABOUT_APP_URL); + } + }); + } + +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/AuthorActivity.java b/app/src/main/java/com/likebamboo/osa/android/ui/AuthorActivity.java new file mode 100644 index 0000000..d5ca44b --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/AuthorActivity.java @@ -0,0 +1,171 @@ +package com.likebamboo.osa.android.ui; + +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.View; + +import com.likebamboo.osa.android.R; +import com.likebamboo.osa.android.entity.AuthorList; +import com.likebamboo.osa.android.interfaces.IOnItemClickListener; +import com.likebamboo.osa.android.request.JsonRequest; +import com.likebamboo.osa.android.request.RequestManager; +import com.likebamboo.osa.android.request.RequestParams; +import com.likebamboo.osa.android.request.RequestUrl; +import com.likebamboo.osa.android.ui.adapter.AuthorAdapter; +import com.likebamboo.osa.android.ui.fragments.AuthorInfoFragment; +import com.likebamboo.osa.android.ui.nav.ActivityNavigator; +import com.likebamboo.osa.android.ui.view.fastscroll.FastScroller; + +import butterknife.InjectView; + +/** + * 作者界面 + */ +public class AuthorActivity extends EndlessActivity { + + @InjectView(R.id.recycler_view) + RecyclerView mAuthorList = null; + + /** + * 适配器 + */ + private AuthorAdapter mAuthorAdapter = null; + + /** + * 布局管理器 + */ + private LinearLayoutManager mLayoutManager; + + /** + * 加载更多,状态记录 + */ + private int pastVisiblesItems, visibleItemCount, totalItemCount; + + @Override + protected int getLayoutId() { + return R.layout.activity_author; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // 初始化 + initView(); + addListener(); + + // 加载数据 + loadDatas(); + } + + /** + * 初始化控件 + */ + private void initView() { + // 初始化适配器 + mAuthorAdapter = new AuthorAdapter(this); + // 添加footer + mAuthorAdapter.setFooter(mFooterView); + // 设置适配器 + mAuthorList.setAdapter(mAuthorAdapter); + + // recyclerView布局管理器 + mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); + mAuthorList.setLayoutManager(mLayoutManager); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + // fastScroller + FastScroller fastScroller = (FastScroller) findViewById(R.id.fastscroller); + fastScroller.setRecyclerView(mAuthorList); + } else { + findViewById(R.id.fastscroller).setVisibility(View.GONE); + } + } + + /** + * 添加监听器 + */ + private void addListener() { + // item Onclick + mAuthorAdapter.setOnItemClickListener(new IOnItemClickListener() { + @Override + public void onItemClick(int postion, AuthorList.Author item) { + if (item == null) { + return; + } + // 开始搜索 + Intent i = new Intent(AuthorActivity.this, AuthorBlogActivity.class); + // 搜索关键字 + i.putExtra(AuthorBlogActivity.EXTRA_AUTHOR_NAME, item.getName()); + // 设置不显示抽屉导航 + i.putExtra(NavigationActivity.EXTRA_SHOULD_DISABLE_DRAWER, true); + // 设置标题 + i.putExtra(EXTRA_TITLE, item.getName()); + ActivityNavigator.withAnim(i, ActivityNavigator.AnimationMode.DEFAULT).startActivity(AuthorActivity.this, i); + } + }); + + // item info click + mAuthorAdapter.setOnInfoClickListner(new AuthorAdapter.IOnAuthorInfoClickListener() { + @Override + public void onAuthorInfoClick(int position, AuthorList.Author author) { + if (author == null) { + return; + } + AuthorInfoFragment fragment = AuthorInfoFragment.newInstance(author); + fragment.show(getSupportFragmentManager(), "dialog"); + } + + @Override + public void onAuthorBlogLinkClick(int position, String blogLink) { + if (TextUtils.isEmpty(blogLink)) { + return; + } + // 跳转到Web页面 + ActivityNavigator.openWebView(AuthorActivity.this, null, blogLink); + } + }); + + // 滚动加载更多 + mAuthorList.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + visibleItemCount = mLayoutManager.getChildCount(); + totalItemCount = mLayoutManager.getItemCount(); + pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition(); + + // 加载更多 + if ((visibleItemCount + pastVisiblesItems) >= totalItemCount) { + loadDatas(); + } + } + }); + } + + @Override + protected void loadDatas(RequestParams params) { + // 加载数据 + RequestManager.addRequest(new JsonRequest(RequestUrl.AUTHOR_URL, AuthorList.class, params, responseListener(), errorListener()), + this.getClass().getName()); + } + + @Override + protected void doOnSuccess(AuthorList data) { + if (data == null || data.getList() == null || data.getList().isEmpty()) { + showMessage(data); + return; + } + ++mPageIndex; + if (data.getList().size() < mPageSize) { + mHasMore = false; + } else { + mHasMore = true; + } + mAuthorAdapter.addDatas(data.getList()); + } + +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/AuthorBlogActivity.java b/app/src/main/java/com/likebamboo/osa/android/ui/AuthorBlogActivity.java new file mode 100644 index 0000000..00e9c59 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/AuthorBlogActivity.java @@ -0,0 +1,49 @@ +package com.likebamboo.osa.android.ui; + +import android.os.Bundle; +import android.text.TextUtils; + +import com.likebamboo.osa.android.request.RequestParams; +import com.likebamboo.osa.android.request.RequestUrl; + +import java.net.URLEncoder; + +/** + * 作者博客列表界面 + */ +public class AuthorBlogActivity extends BlogListActivity { + + /** + * 作者名称 + */ + public static final String EXTRA_AUTHOR_NAME = "extra_author_name"; + + /** + * 作者名称 + */ + private String mAuthorName = ""; + + @Override + protected void onCreate(Bundle savedInstanceState) { + if (getIntent() != null && getIntent().hasExtra(EXTRA_AUTHOR_NAME)) { + mAuthorName = getIntent().getStringExtra(EXTRA_AUTHOR_NAME); + } + super.onCreate(savedInstanceState); + } + + @Override + public void addParams(RequestParams params) { + } + + @Override + public String getRequestUrl() { + if (!TextUtils.isEmpty(mAuthorName)) { + try { + mAuthorName = URLEncoder.encode(mAuthorName, "UTF-8"); + } catch (Exception e) { + } + } + // 改为搜索url + return String.format(RequestUrl.AUTHOR_BLOG_URL, mAuthorName + ""); + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/BaseActivity.java b/app/src/main/java/com/likebamboo/osa/android/ui/BaseActivity.java new file mode 100644 index 0000000..685547c --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/BaseActivity.java @@ -0,0 +1,57 @@ +package com.likebamboo.osa.android.ui; + +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.view.MenuItem; + +/** + * Created by likebamboo on 2015/5/11. + */ +public class BaseActivity extends AppCompatActivity { + + /** + * 标题 + */ + public static final String EXTRA_TITLE = "extra_title"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getIntent().hasExtra(EXTRA_TITLE)) { + setTitle(getIntent().getStringExtra(EXTRA_TITLE)); + } + ActionBar actionBar = getSupportActionBar(); + actionBar.setTitle(getTitle()); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + } + + @Override + protected void onPause() { + super.onPause(); + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + finish(); + break; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/BlogActivity.java b/app/src/main/java/com/likebamboo/osa/android/ui/BlogActivity.java new file mode 100644 index 0000000..b4782fd --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/BlogActivity.java @@ -0,0 +1,286 @@ +package com.likebamboo.osa.android.ui; + +import android.content.Intent; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.text.TextUtils; +import android.view.View; +import android.widget.Toast; + +import com.android.volley.Response; +import com.android.volley.VolleyError; +import com.likebamboo.osa.android.R; +import com.likebamboo.osa.android.entity.BlogList; +import com.likebamboo.osa.android.request.JsonRequest; +import com.likebamboo.osa.android.request.RequestManager; +import com.likebamboo.osa.android.request.RequestUrl; +import com.likebamboo.osa.android.ui.fragments.BlogInfoFragment; +import com.likebamboo.osa.android.ui.nav.ActivityNavigator; +import com.likebamboo.osa.android.ui.view.CommonWebView; +import com.likebamboo.osa.android.ui.view.LoadingLayout; +import com.likebamboo.osa.android.ui.view.fa.TextAwesome; +import com.likebamboo.osa.android.ui.view.fab.FabToolbar; +import com.likebamboo.osa.android.utils.NetworkUtil; +import com.likebamboo.osa.android.utils.UrlDetect; + +import java.net.URLDecoder; + +import butterknife.ButterKnife; +import butterknife.InjectView; + +/** + * 博客界面 + */ +public class BlogActivity extends BaseActivity { + + /** + * 博客URL + */ + public static final String EXTRA_BLOG_URL = "extra_blog_url"; + + /** + * 博客信息 + */ + private BlogList.Blog mBlogInfo = null; + + /** + * actionbar + */ + private ActionBar mActionBar; + + @InjectView(R.id.blog_webview) + CommonWebView mWebView = null; + + @InjectView(R.id.blog_loading_layout) + LoadingLayout mLoadingLayout = null; + + @InjectView(R.id.fab_tool_bar) + FabToolbar mFabToolbar = null; + + @InjectView(R.id.blog_favorite_tv) + TextAwesome mFavTv = null; + + @InjectView(R.id.blog_issue_tv) + TextAwesome mIssueTv = null; + + @InjectView(R.id.blog_info_tv) + TextAwesome mInfoTv = null; + + /** + * 打开web页面的URL + */ + private String mBlogUrl = ""; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_blog); + ButterKnife.inject(this); + + // 初始化actionBar + initActionBar(); + + // 初始化View + initView(); + + // 添加监听器 + addListener(); + + mBlogUrl = getIntent().getStringExtra(EXTRA_BLOG_URL); + if (TextUtils.isEmpty(mBlogUrl)) { + // 容错处理 + finish(); + return; + } + // 开始加载页面 + startLoading(mBlogUrl); + } + + /** + * 初始化WebView + */ + private void initView() { + // webView 不显示状态栏 + mWebView.setToolBarVisibility(View.GONE); + + // fab监听mWebView的滚动 + mFabToolbar.attachTo(mWebView.getWebView()); + + // 问题 + mIssueTv.setText(R.string.fa_exclamation_circle, getString(R.string.issue)); + // 收藏 + mFavTv.setText(R.string.fa_heart_o, getString(R.string.favorite)); + // 博客信息 + mInfoTv.setText(R.string.fa_info, getString(R.string.detail)); + } + + /** + * 添加监听器 + */ + private void addListener() { + mWebView.setStatusListener(new CommonWebView.IWebViewStatusListener() { + @Override + public void onPageStarted(String url) { + } + + @Override + public void onPageFinished(String url) { + } + + @Override + public void onReceiveTitle(String title) { + if (mActionBar != null) { + mActionBar.setTitle(title); + } + } + + @Override + public boolean shouldOverrideUrl(String url) { + // 如果是博客URL, 转到博客界面(BlogActivity) + String formatUrl = UrlDetect.isBlogUrl(url); + if (!TextUtils.isEmpty(formatUrl)) { + // 进入博客详情界面 + Intent i = new Intent(BlogActivity.this, BlogActivity.class); + i.putExtra(BlogActivity.EXTRA_BLOG_URL, formatUrl); + ActivityNavigator.startActivity(BlogActivity.this, i); + return true; + } + // 如果是 tag 博客界面, + formatUrl = UrlDetect.isTagBlogUrl(url); + if (!TextUtils.isEmpty(formatUrl)) { + // 进入标签博客列表界面 + Intent i = new Intent(BlogActivity.this, TagBlogActivity.class); + try { + String title = URLDecoder.decode(formatUrl, "UTF-8"); + i.putExtra(EXTRA_TITLE, title); + } catch (Exception e) { + e.printStackTrace(); + } + i.putExtra(TagBlogActivity.EXTRA_TAG_NAME, formatUrl); + i.putExtra(NavigationActivity.EXTRA_SHOULD_DISABLE_DRAWER, true); + ActivityNavigator.startActivity(BlogActivity.this, i); + return true; + } + // 如果是其他url + if (UrlDetect.isValidURL(url)) { + // 跳转到Web页面 + ActivityNavigator.openWebView(BlogActivity.this, null, url); + return true; + } + return false; + } + }); + + mInfoTv.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (mBlogInfo != null) { + // 显示博客详情信息 + showBlogInfo(); + return; + } + // 加载博客信息 + loadBlogInfo(); + } + }); + } + + /** + * 加载博客信息 + */ + private void loadBlogInfo() { + mLoadingLayout.showLoading(true); + JsonRequest request = new JsonRequest( + String.format(RequestUrl.BLOG_INFO_URL, mBlogUrl), + BlogList.Blog.class, new Response.Listener() { + @Override + public void onResponse(BlogList.Blog blog) { + mLoadingLayout.showLoading(false); + if (blog != null) { + mBlogInfo = blog; + showBlogInfo(); + } + } + }, new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError volleyError) { + mLoadingLayout.showLoading(false); + // 提示错误信息 + Toast.makeText(getApplicationContext(), getString(R.string.get_blog_info_error), Toast.LENGTH_SHORT).show(); + } + }); + request.setJustResult(true); + + RequestManager.addRequest(request, "getBlogInfo"); + } + + /** + * 显示博客信息 + */ + private void showBlogInfo() { + if (isFinishing()) { + return; + } + BlogInfoFragment fragment = BlogInfoFragment.getInstance(mBlogInfo); + fragment.show(getSupportFragmentManager(), "dialog"); + } + + /** + * 初始化actionBar布局 + */ + private void initActionBar() { + mActionBar = getSupportActionBar(); + mActionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_TITLE); + mActionBar.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + mActionBar.setHomeAsUpIndicator(R.drawable.ic_up); + } + + /** + * 加载url + * + * @see [类、类#方法、类#成员] + */ + private void startLoading(String url) { + if (TextUtils.isEmpty(url)) { + return; + } + if (!NetworkUtil.isNetworkAvailable(this)) { + mLoadingLayout.showError(getString(R.string.network_error)); + return; + } + url = url.startsWith("/") ? url.substring(1) : url; + if (!url.contains(RequestUrl.BASE_URL)) { + url = String.format(RequestUrl.BLOG_VIEW_URL, url); + } + // 开始loading web页面 + mWebView.loadUrl(url); + } + + @Override + public void onBackPressed() { + if (mWebView != null && mWebView.goBack()) { + return; + } + super.onBackPressed(); + } + + @Override + public void finish() { + super.finish(); + overridePendingTransition(0, R.anim.fade_out); + } + + @Override + protected void onResume() { + super.onResume(); + mWebView.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + mWebView.onPause(); + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/BlogListActivity.java b/app/src/main/java/com/likebamboo/osa/android/ui/BlogListActivity.java new file mode 100644 index 0000000..dfabb31 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/BlogListActivity.java @@ -0,0 +1,141 @@ +package com.likebamboo.osa.android.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.text.TextUtils; + +import com.etsy.android.grid.StaggeredGridView; +import com.likebamboo.osa.android.R; +import com.likebamboo.osa.android.entity.BlogList; +import com.likebamboo.osa.android.interfaces.IOnItemClickListener; +import com.likebamboo.osa.android.request.JsonRequest; +import com.likebamboo.osa.android.request.RequestManager; +import com.likebamboo.osa.android.request.RequestParams; +import com.likebamboo.osa.android.request.RequestUrl; +import com.likebamboo.osa.android.ui.adapter.BlogAdapter; +import com.likebamboo.osa.android.ui.nav.ActivityNavigator; + +import butterknife.InjectView; + +/** + * 博客界面 + */ +public abstract class BlogListActivity extends EndlessActivity { + + /** + * 排序 + */ + public static final String EXTRA_SORT_KEY = "extra_sort_key"; + + @InjectView(R.id.list_view) + StaggeredGridView mBlogListView; + + /** + * 排序 + */ + protected String mSort = ""; + + @Override + protected int getLayoutId() { + return R.layout.activity_blog_list; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getIntent().hasExtra(EXTRA_TITLE)) { + setTitle(getIntent().getStringExtra(EXTRA_TITLE)); + } else { + setTitle(R.string.app_name); + } + + if (getIntent().hasExtra(EXTRA_SORT_KEY)) { + mSort = getIntent().getStringExtra(EXTRA_SORT_KEY); + } + + // 设置空态页面 + mBlogListView.setEmptyView(mLoadingLayout); + // 添加footer + mBlogListView.addFooterView(mFooterView); + + // 设置适配器 + mAdapter = new BlogAdapter(this); + mBlogListView.setAdapter(mAdapter); + ((BlogAdapter) mAdapter).setOnItemClickListener(new IOnItemClickListener() { + @Override + public void onItemClick(int postion, BlogList.Blog item) { + if (item == null) { + return; + } + // 进入博客详情界面 + Intent i = new Intent(BlogListActivity.this, BlogActivity.class); + i.putExtra(BlogActivity.EXTRA_BLOG_URL, item.getUrl()); + ActivityNavigator.startActivity(BlogListActivity.this, i); + } + }); + // 设置加载更多 + mBlogListView.setOnScrollListener(this); + + // 加载数据 + loadDatas(); + } + + /** + * 加载数据 + */ + @Override + protected void loadDatas(RequestParams params) { + String url = getRequestUrl(); + if (TextUtils.isEmpty(url)) { + url = RequestUrl.BLOG_URL; + } + + // 排序 + if (!TextUtils.isEmpty(mSort)) { + params.add("sort", mSort); + } + + // 添加其他请求参数 + addParams(params); + + RequestManager.addRequest(new JsonRequest(url, BlogList.class, params, responseListener(), errorListener()), + getClass().getName()); + } + + @Override + protected void doOnSuccess(BlogList data) { + if (data == null || data.getList() == null || data.getList().isEmpty()) { + showMessage(data); + return; + } + ++mPageIndex; + if (data.getList().size() < mPageSize) { + mHasMore = false; + } else { + mHasMore = true; + } + mAdapter.addDatas(data.getList()); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + if (intent.hasExtra(EXTRA_TITLE)) { + setTitle(intent.getStringExtra(EXTRA_TITLE)); + } + } + + /** + * 获取请求的URL + * + * @return + */ + public abstract String getRequestUrl(); + + /** + * 添加其他请求参数 + * + * @param params + */ + public abstract void addParams(RequestParams params); +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/CategoryActivity.java b/app/src/main/java/com/likebamboo/osa/android/ui/CategoryActivity.java new file mode 100644 index 0000000..7af36dc --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/CategoryActivity.java @@ -0,0 +1,92 @@ +package com.likebamboo.osa.android.ui; + +import android.content.Intent; +import android.os.Bundle; + +import com.etsy.android.grid.StaggeredGridView; +import com.likebamboo.osa.android.R; +import com.likebamboo.osa.android.entity.CategoryList; +import com.likebamboo.osa.android.interfaces.IOnItemClickListener; +import com.likebamboo.osa.android.request.JsonRequest; +import com.likebamboo.osa.android.request.RequestManager; +import com.likebamboo.osa.android.request.RequestParams; +import com.likebamboo.osa.android.request.RequestUrl; +import com.likebamboo.osa.android.ui.adapter.CategoryAdapter; +import com.likebamboo.osa.android.ui.nav.ActivityNavigator; + +import butterknife.InjectView; + +/** + * 分类界面 + */ +public class CategoryActivity extends EndlessActivity { + + @InjectView(R.id.list_view) + StaggeredGridView mListView; + + @Override + protected int getLayoutId() { + return R.layout.activity_category; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // 设置空态页面 + mListView.setEmptyView(mLoadingLayout); + // 添加footer + mListView.addFooterView(mFooterView); + + // 设置适配器 + mAdapter = new CategoryAdapter(this); + mListView.setAdapter(mAdapter); + ((CategoryAdapter) mAdapter).setOnItemClickListener(new IOnItemClickListener() { + @Override + public void onItemClick(int postion, CategoryList.Category item) { + if (item == null) { + return; + } + // 开始搜索 + Intent i = new Intent(CategoryActivity.this, CategoryBlogActivity.class); + // 搜索关键字 + i.putExtra(CategoryBlogActivity.EXTRA_CATEGORY_ID, item.getId()); + // 设置不显示抽屉导航 + i.putExtra(NavigationActivity.EXTRA_SHOULD_DISABLE_DRAWER, true); + // 设置标题 + i.putExtra(EXTRA_TITLE, item.getName()); + ActivityNavigator.withAnim(i, ActivityNavigator.AnimationMode.DEFAULT).startActivity(CategoryActivity.this, i); + } + }); + // 设置加载更多 + mListView.setOnScrollListener(this); + + // 加载数据 + loadDatas(); + } + + /** + * 加载数据 + */ + @Override + protected void loadDatas(RequestParams params) { + // 加载数据 + RequestManager.addRequest(new JsonRequest(RequestUrl.CATEGORY_URL, CategoryList.class, params, responseListener(), errorListener()), + this.getClass().getName()); + } + + @Override + protected void doOnSuccess(CategoryList data) { + if (data == null || data.getList() == null || data.getList().isEmpty()) { + showMessage(data); + return; + } + ++mPageIndex; + if (data.getList().size() < mPageSize) { + mHasMore = false; + } else { + mHasMore = true; + } + mAdapter.addDatas(data.getList()); + } + +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/CategoryBlogActivity.java b/app/src/main/java/com/likebamboo/osa/android/ui/CategoryBlogActivity.java new file mode 100644 index 0000000..07d2fb6 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/CategoryBlogActivity.java @@ -0,0 +1,40 @@ +package com.likebamboo.osa.android.ui; + +import android.os.Bundle; + +import com.likebamboo.osa.android.request.RequestParams; +import com.likebamboo.osa.android.request.RequestUrl; + +/** + * 分类博客列表界面 + */ +public class CategoryBlogActivity extends BlogListActivity { + + /** + * 类别id + */ + public static final String EXTRA_CATEGORY_ID = "extra_category_id"; + + /** + * 博客类别 + */ + private int mCategoryId = 0; + + @Override + protected void onCreate(Bundle savedInstanceState) { + if (getIntent() != null && getIntent().hasExtra(EXTRA_CATEGORY_ID)) { + mCategoryId = getIntent().getIntExtra(EXTRA_CATEGORY_ID, 0); + } + super.onCreate(savedInstanceState); + } + + @Override + public void addParams(RequestParams params) { + } + + @Override + public String getRequestUrl() { + // 改为搜索url + return String.format(RequestUrl.CATEGORY_BLOG_URL, mCategoryId + ""); + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/EndlessActivity.java b/app/src/main/java/com/likebamboo/osa/android/ui/EndlessActivity.java new file mode 100644 index 0000000..c4749f4 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/EndlessActivity.java @@ -0,0 +1,221 @@ +package com.likebamboo.osa.android.ui; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.widget.AbsListView; + +import com.android.volley.Response; +import com.android.volley.VolleyError; +import com.likebamboo.osa.android.R; +import com.likebamboo.osa.android.entity.BaseRsp; +import com.likebamboo.osa.android.exception.ErrorTrans; +import com.likebamboo.osa.android.exception.OsaException; +import com.likebamboo.osa.android.request.RequestManager; +import com.likebamboo.osa.android.request.RequestParams; +import com.likebamboo.osa.android.ui.adapter.BaseAdapter; +import com.likebamboo.osa.android.ui.view.LoadingLayout; + +import butterknife.ButterKnife; +import butterknife.InjectView; + +/** + * 分页加载数据Activity基类 + * Created by likebamboo on 2015/5/14. + */ +public abstract class EndlessActivity extends NavigationActivity implements AbsListView.OnScrollListener { + + /** + * 适配器 + */ + protected BaseAdapter mAdapter = null; + + /** + * footer + */ + protected LoadingLayout mFooterView = null; + + /** + * loading + */ + @InjectView(android.R.id.empty) + protected LoadingLayout mLoadingLayout = null; + + /** + * 页码 + */ + protected int mPageIndex = 0; + + /** + * 页容量 + */ + protected int mPageSize = 20; + + /** + * 是否还有更多 + */ + protected boolean mHasMore = true; + + /** + * 是否正在加载数据 + */ + protected boolean isLoading = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(getLayoutId()); + ButterKnife.inject(this); + // 添加footer + mFooterView = (LoadingLayout) LayoutInflater.from(this).inflate(R.layout.footer_loading_layout, null); + // 重试 + mLoadingLayout.setRetryListener(new LoadingLayout.IRetryListener() { + @Override + public void onRetry() { + reloadDatas(); + } + }); + mFooterView.setRetryListener(new LoadingLayout.IRetryListener() { + @Override + public void onRetry() { + mFooterView.showLoading(true); + reloadDatas(); + } + }); + } + + @Override + public final void setContentView(int layoutId) { + super.setContentView(layoutId); + } + + /** + * 重试 + */ + public void reloadDatas() { + if (mPageIndex == 0) { + // 显示加载中的布局 + mLoadingLayout.showLoading(true); + } + // 加载数据 + loadDatas(); + } + + /** + * 网络请求出错时回调 + * + * @return + */ + protected final Response.ErrorListener errorListener() { + return new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + isLoading = false; + doOnError(ErrorTrans.transToOsaException(error)); + } + }; + } + + + /** + * 加载数据成功时回调 + * + * @return + */ + protected final Response.Listener responseListener() { + return new Response.Listener() { + @Override + public void onResponse(T data) { + isLoading = false; + mLoadingLayout.showLoading(false); + doOnSuccess(data); + } + }; + } + + + @Override + public void onScrollStateChanged(final AbsListView view, final int scrollState) { + } + + @Override + public void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount, final int totalItemCount) { + // our handling + int lastInScreen = firstVisibleItem + visibleItemCount; + if (lastInScreen >= totalItemCount) { + // 加载更多数据。 + loadDatas(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + // 取消请求 + RequestManager.cancelAll(this.getClass().getName()); + isLoading = false; + } + + /** + * 加载数据 + */ + protected final void loadDatas() { + // 如果正在加载数据,或者没有更多数据了, + if (isLoading) { + return; + } + if (!mHasMore) { + mFooterView.showEmpty(getString(R.string.has_not_more_data)); + return; + } + isLoading = true; + if (mPageIndex > 0) { + mFooterView.showLoading(true); + } + // 加载数据 + RequestParams params = new RequestParams(); + params.add("pageNo", (mPageIndex + 1) + "").add("pageSize", "" + mPageSize); + loadDatas(params); + } + + /** + * 加载失败,显示错误信息 + * + * @param error + */ + protected void doOnError(OsaException error) { + // 如果加载第一页的数据出错,显示错误信息 + if (mPageIndex == 0) { + mLoadingLayout.showError(error.getMessage() + "\n" + getString(R.string.retry)); + mFooterView.showLoading(false); + return; + } + mFooterView.showError(error.getMessage() + "\n" + getString(R.string.retry)); + } + + /** + * 显示服务器返回的信息 + * + * @param data + */ + protected void showMessage(T data) { + // 如果加载第一页的数据为空 + if (mPageIndex == 0) { + mLoadingLayout.showEmpty(data == null ? getString(R.string.can_not_find_data) : data.getMessage()); + } else { + mFooterView.showEmpty(getString(R.string.has_not_more_data)); + } + } + + protected abstract void loadDatas(RequestParams params); + + /** + * 加载数据出错回调 + */ + protected abstract void doOnSuccess(T data); + + /** + * layoutId + */ + protected abstract int getLayoutId(); + +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/MainActivity.java b/app/src/main/java/com/likebamboo/osa/android/ui/MainActivity.java new file mode 100644 index 0000000..8afa815 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/MainActivity.java @@ -0,0 +1,26 @@ +package com.likebamboo.osa.android.ui; + +import android.os.Bundle; + +import com.likebamboo.osa.android.request.RequestParams; +import com.likebamboo.osa.android.request.RequestUrl; + +/** + * 主界面 + */ +public class MainActivity extends BlogListActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public void addParams(RequestParams params) { + } + + @Override + public String getRequestUrl() { + return RequestUrl.BLOG_URL; + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/NavigationActivity.java b/app/src/main/java/com/likebamboo/osa/android/ui/NavigationActivity.java new file mode 100644 index 0000000..900bf6c --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/NavigationActivity.java @@ -0,0 +1,201 @@ +package com.likebamboo.osa.android.ui; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBar; +import android.util.AttributeSet; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.FrameLayout; + +import com.likebamboo.osa.android.R; +import com.likebamboo.osa.android.ui.fragments.NavigationDrawerFragment; +import com.likebamboo.osa.android.ui.fragments.SettingsFragment; +import com.likebamboo.osa.android.ui.nav.ActivityNavigator; +import com.likebamboo.osa.android.ui.view.blur.BlurBehind; +import com.likebamboo.osa.android.ui.view.blur.OnBlurCompleteListener; + +/** + * drawer导航界面基类 + */ +public class NavigationActivity extends BaseActivity implements NavigationDrawerFragment.NavigationDrawerCallbacks { + + /** + * 是否隐藏drawer + */ + public static final String EXTRA_SHOULD_DISABLE_DRAWER = "extra_should_disable_drawer"; + + /** + * drawer导航fragment + */ + private NavigationDrawerFragment mNavigationDrawerFragment; + + /** + * 内容区域 + */ + private FrameLayout mContainerView = null; + + private boolean hiddenDrawer = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + super.setContentView(R.layout.activity_navigation); + hiddenDrawer = getIntent().getBooleanExtra(EXTRA_SHOULD_DISABLE_DRAWER, false); + + mContainerView = (FrameLayout) findViewById(R.id.container); + DrawerLayout drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); + // 关闭DrawerLayout,且不让打开 + if (hiddenDrawer) { + drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); + } + + mNavigationDrawerFragment = (NavigationDrawerFragment) getSupportFragmentManager().findFragmentById(R.id.navigation_drawer); + // Set up the drawer. + mNavigationDrawerFragment.setUp(R.id.navigation_drawer, drawerLayout, (hiddenDrawer ? R.drawable.ic_up : 0)); + } + + /** + * 通过layout名称构建视图 + * + * @param layoutId + * @see [类、类#方法、类#成员] + */ + @Override + public void setContentView(int layoutId) { + getLayoutInflater().inflate(layoutId, mContainerView); + } + + /** + * 通过view构建视图 + * + * @param view + * @see [类、类#方法、类#成员] + */ + @Override + public void setContentView(View view) { + mContainerView.addView(view); + } + + @Override + public boolean onNavigationDrawerItemSelected(int position, String text) { + //update the main content by start Activity + boolean close = true; + Intent i = new Intent(); + i.putExtra(EXTRA_TITLE, text); + switch (position) { + case 0:// 首页 + i.setClass(this, MainActivity.class); + break; + case 1:// 分类 + i.setClass(this, CategoryActivity.class); + break; + case 2: //作者 + i.setClass(this, AuthorActivity.class); + break; + case 3:// 设置 + SettingsFragment fragment = SettingsFragment.newInstance(); + fragment.show(getSupportFragmentManager(), "dialog"); + close = false; + break; + default: + break; + } + ActivityNavigator.withAnim(i, ActivityNavigator.AnimationMode.DEFAULT).clearTop(i).startActivity(this, i); + return close; + } + + @Nullable + @Override + public View onCreateView(String name, Context context, AttributeSet attrs) { + return super.onCreateView(name, context, attrs); + } + + public void restoreActionBar() { + ActionBar actionBar = getSupportActionBar(); + actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); + actionBar.setDisplayShowTitleEnabled(true); + actionBar.setTitle(getTitle()); + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + if (!mNavigationDrawerFragment.isDrawerOpen()) { + // Only show items in the action bar relevant to this screen + // if the drawer is not showing. Otherwise, let the drawer + // decide what to show in the action bar. + getMenuInflater().inflate(R.menu.main, menu); + restoreActionBar(); + return true; + } + return super.onCreateOptionsMenu(menu); + } + + @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(); + + if (item.getItemId() == android.R.id.home) { + // 如果不显示drawer,直接finish + if (hiddenDrawer) { + finish(); + return true; + } + // 如果显示drawer + if (mNavigationDrawerFragment != null) { + if (mNavigationDrawerFragment.isDrawerOpen()) { + mNavigationDrawerFragment.closeDrawer(); + } else { + mNavigationDrawerFragment.openDrawer(); + } + } + return true; + } + + if (id == R.id.action_search) { + // 如果当前是搜索结果界面 + if (this instanceof SearchResultActivity) { + finish(); + return true; + } + BlurBehind.getInstance().execute(this, new OnBlurCompleteListener() { + @Override + public void onBlurComplete() { + // 搜索 + Intent i = new Intent(NavigationActivity.this, SearchActivity.class); + ActivityNavigator.withAnim(i, ActivityNavigator.AnimationMode.FADE_IN_OUT).startActivity(NavigationActivity.this, i); + } + }); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + } + + @Override + public void onBackPressed() { + if (mNavigationDrawerFragment != null && mNavigationDrawerFragment.isDrawerOpen()) { + mNavigationDrawerFragment.closeDrawer(); + return; + } + super.onBackPressed(); + } + + @Override + public void finish() { + super.finish(); + overridePendingTransition(0, 0); + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/SearchActivity.java b/app/src/main/java/com/likebamboo/osa/android/ui/SearchActivity.java new file mode 100644 index 0000000..e5ae0b2 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/SearchActivity.java @@ -0,0 +1,159 @@ +package com.likebamboo.osa.android.ui; + +import android.content.Intent; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.support.v7.widget.SearchView; +import android.text.TextUtils; +import android.widget.Toast; + +import com.likebamboo.osa.android.R; +import com.likebamboo.osa.android.entity.TagList; +import com.likebamboo.osa.android.ui.nav.ActivityNavigator; +import com.likebamboo.osa.android.ui.view.TagGroup; +import com.likebamboo.osa.android.ui.view.blur.BlurBehind; +import com.orm.StringUtil; + +import java.util.ArrayList; +import java.util.List; + +import butterknife.ButterKnife; +import butterknife.InjectView; + +/** + * 搜索界面 + */ +public class SearchActivity extends BaseActivity { + + @InjectView(R.id.search_history_tags) + TagGroup mHistoryTags; + + /** + * ActionBar搜索布局 + */ + private SearchView searchView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_search); + ButterKnife.inject(this); + + // 背景 + BlurBehind.getInstance().withFilterColor(getResources().getColor(R.color.bg_blur)).setBackground(this); + + // 初始化搜索布局 + initSearchView(); + // 设置历史数据 + setHistoryDatas(); + // 添加监听器 + addListener(); + } + + /** + * 添加监听器 + */ + private void addListener() { + // 监听标签点击事件 + mHistoryTags.setOnTagClickListener(new TagGroup.IOnTagClickListener() { + @Override + public void onTagClick(String tag) { + // 执行搜索 + doSearch(tag); + } + + @Override + public void onTagLongClick(String tag) { + // 删除历史记录 + mHistoryTags.deleteTag(tag); + TagList.Tag.delete(tag); + // 显示toast + Toast.makeText(getApplicationContext(), tag + " 标签已删除", Toast.LENGTH_SHORT).show(); + } + }); + + // 设置搜索监听 + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + public boolean onQueryTextSubmit(String query) { + return doSearch(query); + } + + public boolean onQueryTextChange(String newText) { + return true; + } + }); + } + + /** + * 开始搜索 + * + * @param key + * @return + */ + private boolean doSearch(String key) { + if (TextUtils.isEmpty(key)) { + return false; + } + + // 保存历史记录 + mHistoryTags.addTag(0, key); + TagList.Tag t = TagList.Tag.findTagByName(key); + if (t == null) { + t = new TagList.Tag(); + t.setName(key); + } + t.setAddTime(System.currentTimeMillis()); + t.save(); + + // 清空输入框 + searchView.setQuery("", false); + + // 开始搜索 + Intent i = new Intent(SearchActivity.this, SearchResultActivity.class); + // 搜索关键字 + i.putExtra(SearchResultActivity.EXTRA_SEARCH_KEY, key); + // 设置不显示抽屉导航 + i.putExtra(NavigationActivity.EXTRA_SHOULD_DISABLE_DRAWER, true); + // 设置标题 + i.putExtra(EXTRA_TITLE, key); + ActivityNavigator.withAnim(i, ActivityNavigator.AnimationMode.DEFAULT).startActivity(SearchActivity.this, i); + return true; + } + + /** + * 设置历史数据 + */ + private void setHistoryDatas() { + List tags = new ArrayList<>(); + List datas = TagList.Tag.find(TagList.Tag.class, null, null, null, StringUtil.toSQLName("addTime") + " desc ", null); + if (datas != null) { + for (TagList.Tag tag : datas) { + tags.add(tag.getName()); + } + } + mHistoryTags.setTags(tags); + } + + /** + * 初始化搜索布局 + */ + private void initSearchView() { + ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_CUSTOM); + searchView = new SearchView(this); + searchView.onActionViewExpanded(); + searchView.setIconifiedByDefault(true); + actionBar.setHomeAsUpIndicator(R.drawable.ic_up); + + actionBar.setCustomView(searchView); + actionBar.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + } + + @Override + public void finish() { + super.finish(); + overridePendingTransition(0, R.anim.fade_out); + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/SearchResultActivity.java b/app/src/main/java/com/likebamboo/osa/android/ui/SearchResultActivity.java new file mode 100644 index 0000000..a57d46d --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/SearchResultActivity.java @@ -0,0 +1,51 @@ +package com.likebamboo.osa.android.ui; + +import android.os.Bundle; +import android.text.TextUtils; + +import com.likebamboo.osa.android.request.RequestParams; +import com.likebamboo.osa.android.request.RequestUrl; + +import java.net.URLEncoder; + +/** + * 搜索结果界面 + */ +public class SearchResultActivity extends BlogListActivity { + + /** + * 搜索关键字 + */ + public static final String EXTRA_SEARCH_KEY = "extra_search_key"; + + /** + * 搜索博客的关键字 + */ + private String mSearchKey = ""; + + @Override + protected void onCreate(Bundle savedInstanceState) { + if (getIntent() != null && getIntent().hasExtra(EXTRA_SEARCH_KEY)) { + mSearchKey = getIntent().getStringExtra(EXTRA_SEARCH_KEY); + } + super.onCreate(savedInstanceState); + } + + @Override + public void addParams(RequestParams params) { + // 关键字 + if (!TextUtils.isEmpty(mSearchKey)) { + try { + mSearchKey = URLEncoder.encode(mSearchKey, "UTF-8"); + } catch (Exception e) { + } + params.add("key", mSearchKey); + } + } + + @Override + public String getRequestUrl() { + // 改为搜索url + return RequestUrl.BLOG_SEARCH_URL; + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/TagBlogActivity.java b/app/src/main/java/com/likebamboo/osa/android/ui/TagBlogActivity.java new file mode 100644 index 0000000..c8bf8d6 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/TagBlogActivity.java @@ -0,0 +1,40 @@ +package com.likebamboo.osa.android.ui; + +import android.os.Bundle; + +import com.likebamboo.osa.android.request.RequestParams; +import com.likebamboo.osa.android.request.RequestUrl; + +/** + * 标签博客列表界面 + */ +public class TagBlogActivity extends BlogListActivity { + + /** + * 类别id + */ + public static final String EXTRA_TAG_NAME = "extra_tag_name"; + + /** + * tag标签名称 + */ + private String mTagName = ""; + + @Override + protected void onCreate(Bundle savedInstanceState) { + if (getIntent() != null && getIntent().hasExtra(EXTRA_TAG_NAME)) { + mTagName = getIntent().getStringExtra(EXTRA_TAG_NAME); + } + super.onCreate(savedInstanceState); + } + + @Override + public void addParams(RequestParams params) { + } + + @Override + public String getRequestUrl() { + // 改为搜索url + return String.format(RequestUrl.TAG_BLOG_URL, mTagName); + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/WebViewActivity.java b/app/src/main/java/com/likebamboo/osa/android/ui/WebViewActivity.java new file mode 100644 index 0000000..89bbf80 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/WebViewActivity.java @@ -0,0 +1,144 @@ +package com.likebamboo.osa.android.ui; + +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.text.TextUtils; + +import com.likebamboo.osa.android.R; +import com.likebamboo.osa.android.ui.view.CommonWebView; + +import butterknife.ButterKnife; +import butterknife.InjectView; + +/** + * 普通WebView界面 + */ +public class WebViewActivity extends BaseActivity { + + /** + * 博客URL + */ + public static final String EXTRA_URL = "extra_url"; + + /** + * actionbar + */ + private ActionBar mActionBar; + + @InjectView(R.id.webview) + CommonWebView mWebView = null; + + /** + * 打开web页面的URL + */ + private String mUrl = ""; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_webview); + ButterKnife.inject(this); + + // 初始化actionBar + initActionBar(); + + // 初始化View + initView(); + + // 添加监听器 + addListener(); + + mUrl = getIntent().getStringExtra(EXTRA_URL); + if (TextUtils.isEmpty(mUrl)) { + // 容错处理 + finish(); + return; + } + // 开始加载页面 + startLoading(mUrl); + } + + /** + * 初始化控件 + */ + private void initView() { + } + + /** + * 添加监听器 + */ + private void addListener() { + mWebView.setStatusListener(new CommonWebView.IWebViewStatusListener() { + @Override + public void onPageStarted(String url) { + } + + @Override + public void onPageFinished(String url) { + } + + @Override + public void onReceiveTitle(String title) { + if (mActionBar != null) { + mActionBar.setTitle(title); + } + } + + @Override + public boolean shouldOverrideUrl(String url) { + return false; + } + }); + } + + /** + * 初始化actionBar布局 + */ + private void initActionBar() { + mActionBar = getSupportActionBar(); + mActionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_TITLE); + mActionBar.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + mActionBar.setHomeAsUpIndicator(R.drawable.ic_up); + } + + /** + * 加载url + * + * @see [类、类#方法、类#成员] + */ + private void startLoading(String url) { + if (TextUtils.isEmpty(url)) { + return; + } + // 开始loading web页面 + mWebView.loadUrl(url); + } + + @Override + public void onBackPressed() { + if (mWebView != null && mWebView.goBack()) { + return; + } + super.onBackPressed(); + } + + @Override + public void finish() { + super.finish(); + overridePendingTransition(0, R.anim.fade_out); + } + + @Override + protected void onResume() { + super.onResume(); + mWebView.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + mWebView.onPause(); + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/adapter/AuthorAdapter.java b/app/src/main/java/com/likebamboo/osa/android/ui/adapter/AuthorAdapter.java new file mode 100644 index 0000000..2c7b63a --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/adapter/AuthorAdapter.java @@ -0,0 +1,189 @@ +package com.likebamboo.osa.android.ui.adapter; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.android.volley.toolbox.ImageLoader; +import com.likebamboo.osa.android.R; +import com.likebamboo.osa.android.entity.AuthorList; +import com.likebamboo.osa.android.impl.ImageLoaderListener; +import com.likebamboo.osa.android.interfaces.IOnItemClickListener; +import com.likebamboo.osa.android.request.RequestManager; +import com.likebamboo.osa.android.request.RequestUrl; +import com.likebamboo.osa.android.ui.view.CircleImageView; +import com.likebamboo.osa.android.ui.view.fa.TextAwesome; +import com.likebamboo.osa.android.ui.view.fastscroll.BubbleTextGetter; + +import butterknife.ButterKnife; +import butterknife.InjectView; + +/** + * Created by wentaoli on 2015/5/14. + */ +public class AuthorAdapter extends BaseRecycleAdapter implements BubbleTextGetter { + private IOnItemClickListener mItemClickListener = null; + private IOnAuthorInfoClickListener mInfoClickListener = null; + + /** + * 设置回调 + * + * @param l + */ + public void setOnInfoClickListner(IOnAuthorInfoClickListener l) { + this.mInfoClickListener = l; + } + + /** + * 设置回调 + * + * @param l + */ + public void setOnItemClickListener(IOnItemClickListener l) { + this.mItemClickListener = l; + } + + public AuthorAdapter(Context mContext) { + super(mContext); + } + + @Override + public RecyclerView.ViewHolder onCreateViewItemHolder(ViewGroup viewGroup, int viewType) { + // 创建一个View + View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_author, viewGroup, false); + // 创建一个ViewHolder + ItemViewHolder holder = new ItemViewHolder(view); + return holder; + } + + @Override + public void onBindItemViewHolder(RecyclerView.ViewHolder viewHolder, int position) { + if (viewHolder == null || !(viewHolder instanceof ItemViewHolder)) { + return; + } + + AuthorList.Author item = getItem(position); + if (item == null) { + return; + } + + final ItemViewHolder holder = (ItemViewHolder) viewHolder; + + holder.avatarIv.setImageResource(R.drawable.default_avatar); + if (!TextUtils.isEmpty(item.getAvatar())) { + String url = RequestUrl.BASE_URL + item.getAvatar(); + // 加载图片 + ImageLoader imageLoader = RequestManager.getImageLoader(); + imageLoader.get(url, new ImageLoaderListener(holder.avatarIv, url, R.drawable.default_avatar)); + } + // 作者名称 + holder.nameTv.setText(item.getName()); + + // 博客 + holder.blogTv.setText(R.string.fa_link, item.getBlog()); + holder.blogTv.setTag(position); + holder.blogTv.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (mInfoClickListener == null) { + return; + } + Object obj = view.getTag(); + try { + int pos = Integer.parseInt(obj + ""); + AuthorList.Author item = getItem(pos); + if (item == null) { + return; + } + mInfoClickListener.onAuthorBlogLinkClick(pos, item.getBlog()); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + + // item点击事件 + holder.rootView.setTag(position); + holder.rootView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (mItemClickListener == null) { + return; + } + Object obj = view.getTag(); + try { + int pos = Integer.parseInt(obj + ""); + mItemClickListener.onItemClick(pos, getItem(pos)); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + + holder.infoTv.setTag(position); + holder.infoTv.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (mInfoClickListener == null) { + return; + } + Object obj = view.getTag(); + try { + int pos = Integer.parseInt(obj + ""); + mInfoClickListener.onAuthorInfoClick(pos, getItem(pos)); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + @Override + public String getTextToShowInBubble(int pos) { + AuthorList.Author item = getItem(pos); + if (item != null) { + return item.getIndex(); + } + return "#"; + } + + public static class ItemViewHolder extends RecyclerView.ViewHolder { + @InjectView(R.id.author_item_layout) + public View rootView; + @InjectView(R.id.author_blog_tv) + public TextAwesome blogTv; + @InjectView(R.id.author_name_tv) + public TextView nameTv; + @InjectView(R.id.author_info_tv) + public TextView infoTv; + @InjectView(R.id.author_avatar_iv) + public CircleImageView avatarIv; + + public ItemViewHolder(View view) { + super(view); + ButterKnife.inject(this, view); + } + } + + public interface IOnAuthorInfoClickListener { + /** + * 点击作者信息 + * + * @param position + * @param author + */ + void onAuthorInfoClick(int position, AuthorList.Author author); + + /** + * 作者博客链接点击事件 + * + * @param position + * @param blogLink + */ + void onAuthorBlogLinkClick(int position, String blogLink); + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/adapter/BaseAdapter.java b/app/src/main/java/com/likebamboo/osa/android/ui/adapter/BaseAdapter.java new file mode 100644 index 0000000..9a165dc --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/adapter/BaseAdapter.java @@ -0,0 +1,139 @@ +package com.likebamboo.osa.android.ui.adapter; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; + +/** + * ListView 适配器基类 + * + * @author wentaoli + * @version [版本号, 2015年5月12日] + * @see [相关类/方法] + * @since [产品/模块版本] + */ +public abstract class BaseAdapter extends android.widget.BaseAdapter { + + /** + * 上下文对象 + */ + protected Context mContext = null; + + /** + * 数据源 + */ + protected ArrayList mDatas = new ArrayList(); + + public BaseAdapter(final Context ctx) { + super(); + this.mContext = ctx; + } + + public BaseAdapter(final Context ctx, final ArrayList datas) { + super(); + this.mContext = ctx; + this.mDatas = datas; + } + + /** + * 获取数据源 + * + * @return + */ + public ArrayList getDatas() { + return mDatas; + } + + /** + * 添加数据 + * + * @param datas + */ + public void addDatas(final ArrayList datas) { + if (mDatas == null) { + mDatas = new ArrayList(); + } + mDatas.addAll(datas); + notifyDataSetChanged(); + } + + /** + * 添加数据 + * + * @param datas + * @param index + */ + public void addDatas(final ArrayList datas, int index) { + if (mDatas == null) { + mDatas = new ArrayList(); + } + mDatas.addAll(index, datas); + notifyDataSetChanged(); + } + + /** + * 添加数据 + * + * @param datas + */ + public void addData(T datas) { + if (mDatas == null) { + mDatas = new ArrayList(); + } + mDatas.add(datas); + } + + /** + * 添加数据 + * + * @param datas + * @param index + */ + public void addData(T datas, int index) { + if (mDatas == null) { + mDatas = new ArrayList(); + } + mDatas.add(index, datas); + } + + /** + * 清空数据 + */ + public void clear() { + if (mDatas != null) { + mDatas.clear(); + } + } + + @Override + public int getCount() { + if (mDatas == null) { + return 0; + } + int count = mDatas.size(); + return count; + } + + @Override + public T getItem(int arg0) { + if (mDatas == null) { + return null; + } + if (arg0 < 0 || arg0 >= mDatas.size()) { + return null; + } + return mDatas.get(arg0); + } + + @Override + public long getItemId(int arg0) { + return arg0; + } + + + @Override + public abstract View getView(int position, View v, ViewGroup parent); + +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/adapter/BaseRecycleAdapter.java b/app/src/main/java/com/likebamboo/osa/android/ui/adapter/BaseRecycleAdapter.java new file mode 100644 index 0000000..d00c822 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/adapter/BaseRecycleAdapter.java @@ -0,0 +1,242 @@ +package com.likebamboo.osa.android.ui.adapter; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; + +/** + * Created by wentaoli on 2015/5/14. + */ +public abstract class BaseRecycleAdapter extends RecyclerView.Adapter { + + /** + * 头部 + */ + protected static final int TYPE_HEADER = 0x1001; + /** + * item + */ + protected static final int TYPE_ITEM = 0x1002; + /** + * footer + */ + protected static final int TYPE_FOOTRE = 0x1003; + + /** + * 上下文对象 + */ + protected Context mContext = null; + + /** + * 数据源 + */ + protected ArrayList mDatas = new ArrayList(); + + /** + * header + */ + private View mHeaderView = null; + + /** + * footer + */ + private View mFooterView = null; + + public BaseRecycleAdapter(Context mContext) { + this.mContext = mContext; + } + + @Override + public final int getItemCount() { + return getItemViewCount() + (mHeaderView == null ? 0 : 1) + (mFooterView == null ? 0 : 1); + } + + /** + * 获取中间View的count + * + * @return + */ + public int getItemViewCount() { + if (mDatas != null) { + return mDatas.size(); + } + return 0; + } + + /** + * 获取position所对应的item的类型 + * + * @param position + * @return + */ + @Override + public final int getItemViewType(int position) { + int headerCount = mHeaderView == null ? 0 : 1; + if (position < headerCount) { + return TYPE_HEADER; + } + if (isItemView(position)) { + return TYPE_ITEM; + } + return TYPE_FOOTRE; + } + + /** + * 判断position是否是中间的数据区域的View + * + * @param position + * @return + */ + public boolean isItemView(int position) { + if (mDatas == null) { + return false; + } + int headerCount = mHeaderView == null ? 0 : 1; + if (position < (headerCount + mDatas.size())) { + return true; + } + return false; + } + + @Override + public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { + // header + if (viewType == TYPE_HEADER) { + // footer + viewGroup.addView(mHeaderView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + return new VHHeader(mHeaderView); + } + + // item + if (viewType == TYPE_ITEM) { + return onCreateViewItemHolder(viewGroup, viewType); + } + // footer + viewGroup.addView(mFooterView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + return new VHFooter(mFooterView); + } + + @Override + public final void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { + if (viewHolder == null) { + return; + } + if (viewHolder instanceof VHHeader) { + // do nothing + return; + } + if (viewHolder instanceof VHFooter) { + // do nothing + return; + } + onBindItemViewHolder(viewHolder, position); + } + + /** + * 获取数据 + * + * @param postion + * @return + */ + public T getItem(int postion) { + if (postion < 0 || postion > getItemCount() || mDatas == null) { + return null; + } + int headerCount = mHeaderView == null ? 0 : 1; + if (postion < headerCount) { + return null; + } + postion -= headerCount; + if (postion >= mDatas.size()) { + return null; + } + return mDatas.get(postion); + } + + /** + * 添加数据并刷新adapter + * + * @param datas + */ + public void addDatas(final ArrayList datas) { + if (mDatas == null) { + mDatas = new ArrayList(); + } + mDatas.addAll(datas); + notifyDataSetChanged(); + } + + /** + * 添加数据并刷新adapter + * + * @param datas + * @param index + */ + public void addDatas(final ArrayList datas, int index) { + if (mDatas == null) { + mDatas = new ArrayList(); + } + mDatas.addAll(index, datas); + notifyDataSetChanged(); + } + + /** + * 清空数据 + */ + public void clear() { + if (mDatas != null) { + mDatas.clear(); + } + } + + /** + * 添加header + * + * @param header + */ + public void addHeader(View header) { + mHeaderView = header; + } + + /** + * 添加footer + * + * @param footer + */ + public void setFooter(View footer) { + mFooterView = footer; + } + + public static class VHHeader extends RecyclerView.ViewHolder { + public VHHeader(View itemView) { + super(itemView); + } + } + + public static class VHFooter extends RecyclerView.ViewHolder { + public VHFooter(View itemView) { + super(itemView); + } + } + + /** + * 创建itemViewHolder + * + * @param viewGroup + * @param i + * @return + */ + public abstract RecyclerView.ViewHolder onCreateViewItemHolder(ViewGroup viewGroup, int i); + + /** + * 将ViewHolder绑定到具体的View上 + * + * @param viewHolder + * @param i + * @return + */ + public abstract void onBindItemViewHolder(RecyclerView.ViewHolder viewHolder, int i); +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/adapter/BlogAdapter.java b/app/src/main/java/com/likebamboo/osa/android/ui/adapter/BlogAdapter.java new file mode 100644 index 0000000..0d0e0b8 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/adapter/BlogAdapter.java @@ -0,0 +1,104 @@ +package com.likebamboo.osa.android.ui.adapter; + +import android.content.Context; +import android.text.Html; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.likebamboo.osa.android.R; +import com.likebamboo.osa.android.entity.BlogList; +import com.likebamboo.osa.android.entity.BlogList.Blog; +import com.likebamboo.osa.android.interfaces.IOnItemClickListener; +import com.likebamboo.osa.android.ui.view.fa.TextAwesome; +import com.likebamboo.osa.android.utils.DateUtil; + +import java.util.ArrayList; + +import butterknife.ButterKnife; +import butterknife.InjectView; + +/** + * Created by wentaoli on 2015/5/12. + */ +public class BlogAdapter extends BaseAdapter { + + private IOnItemClickListener mItemClickListener = null; + + /** + * 设置回调 + * + * @param l + */ + public void setOnItemClickListener(IOnItemClickListener l) { + this.mItemClickListener = l; + } + + public BlogAdapter(Context ctx) { + super(ctx); + } + + public BlogAdapter(Context ctx, ArrayList datas) { + super(ctx, datas); + } + + @Override + public View getView(int position, View view, ViewGroup parent) { + ViewHolder holder; + if (view != null) { + holder = (ViewHolder) view.getTag(); + } else { + view = LayoutInflater.from(mContext).inflate(R.layout.item_blog, parent, false); + holder = new ViewHolder(view); + view.setTag(holder); + } + + Blog item = getItem(position); + if (item == null) { + return view; + } + + view.setId(position); + view.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + int pos = view.getId(); + if (mItemClickListener != null) { + mItemClickListener.onItemClick(pos, getItem(pos)); + } + } + }); + /** + * PS:为什么在Adapter中设置View的OnClick事件,而不是直接使用 + * @see android.widget.AdapterView#setOnItemClickListener(AdapterView.OnItemClickListener) + * 的方式来实现? + * 因为用 setOnItemClickListener 的方式 设置 ListView 的item 的background没有效果,不知道什么鬼, + * 各位大神如果有解决方案,望告知~ + */ + + holder.titleTv.setText(item.getTitle()); + holder.abstractsTv.setText(Html.fromHtml(item.getAbstracts().replace("\n", "
"))); + holder.tagTv.setText(R.string.fa_filter, item.getCategorys()); + holder.timeTv.setText(R.string.fa_calendar, DateUtil.parseDate(item.getAddTime())); + return view; + } + + public static class ViewHolder { + @InjectView(R.id.blog_title_tv) + public TextView titleTv; + @InjectView(R.id.blog_author_tv) + public TextView authorTv; + @InjectView(R.id.blog_abstracts_tv) + public TextView abstractsTv; + @InjectView(R.id.blog_time_tv) + public TextAwesome timeTv; + @InjectView(R.id.blog_tag_tv) + public TextAwesome tagTv; + + public ViewHolder(View view) { + ButterKnife.inject(this, view); + } + + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/adapter/CategoryAdapter.java b/app/src/main/java/com/likebamboo/osa/android/ui/adapter/CategoryAdapter.java new file mode 100644 index 0000000..33d1857 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/adapter/CategoryAdapter.java @@ -0,0 +1,122 @@ +package com.likebamboo.osa.android.ui.adapter; + +import android.content.Context; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.android.volley.VolleyError; +import com.android.volley.toolbox.ImageLoader; +import com.etsy.android.grid.util.DynamicHeightImageView; +import com.likebamboo.osa.android.R; +import com.likebamboo.osa.android.entity.CategoryList; +import com.likebamboo.osa.android.interfaces.IOnItemClickListener; +import com.likebamboo.osa.android.request.RequestManager; +import com.likebamboo.osa.android.request.RequestUrl; + +import java.util.ArrayList; + +import butterknife.ButterKnife; +import butterknife.InjectView; + +/** + * Created by wentaoli on 2015/5/12. + */ +public class CategoryAdapter extends BaseAdapter { + + private IOnItemClickListener mItemClickListener = null; + + /** + * 设置回调 + * + * @param l + */ + public void setOnItemClickListener(IOnItemClickListener l) { + this.mItemClickListener = l; + } + + public CategoryAdapter(Context ctx) { + super(ctx); + } + + public CategoryAdapter(Context ctx, ArrayList datas) { + super(ctx, datas); + } + + @Override + public View getView(int position, View view, ViewGroup parent) { + final ViewHolder holder; + if (view != null) { + holder = (ViewHolder) view.getTag(); + } else { + view = LayoutInflater.from(mContext).inflate(R.layout.item_category, parent, false); + holder = new ViewHolder(view); + view.setTag(holder); + } + + CategoryList.Category item = getItem(position); + if (item == null) { + return view; + } + + // 图片 + holder.coverIv.setImageResource(R.drawable.ic_launcher); + // 加载图片 + ImageLoader imageLoader = RequestManager.getImageLoader(); + holder.coverIv.setHeightRatio(0.8); + if (!TextUtils.isEmpty(item.getCover())) { + String url = RequestUrl.BASE_URL + item.getCover(); + holder.coverIv.setTag(url); + imageLoader.get(url, new ImageLoader.ImageListener() { + @Override + public void onResponse(ImageLoader.ImageContainer imageContainer, boolean b) { + if (imageContainer == null || imageContainer.getBitmap() == null) { + onErrorResponse(null); + return; + } + if (("" + holder.coverIv.getTag()).equals(imageContainer.getRequestUrl())) { + holder.coverIv.setHeightRatio(imageContainer.getBitmap().getHeight() / (double) imageContainer.getBitmap().getWidth()); + holder.coverIv.setImageBitmap(imageContainer.getBitmap()); + } + } + + @Override + public void onErrorResponse(VolleyError volleyError) { + holder.coverIv.setImageResource(R.drawable.ic_launcher); + } + }); + } + + view.setId(position); + view.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + int pos = view.getId(); + if (mItemClickListener != null) { + mItemClickListener.onItemClick(pos, getItem(pos)); + } + } + }); + // set data + holder.titleTv.setText(item.getName()); + holder.descTv.setText(item.getDescription()); + + return view; + } + + public static class ViewHolder { + @InjectView(R.id.category_title_tv) + public TextView titleTv; + @InjectView(R.id.category_desc_tv) + public TextView descTv; + @InjectView(R.id.category_cover_iv) + public DynamicHeightImageView coverIv; + + public ViewHolder(View view) { + ButterKnife.inject(this, view); + } + + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/fragments/AuthorInfoFragment.java b/app/src/main/java/com/likebamboo/osa/android/ui/fragments/AuthorInfoFragment.java new file mode 100644 index 0000000..8cda919 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/fragments/AuthorInfoFragment.java @@ -0,0 +1,157 @@ +package com.likebamboo.osa.android.ui.fragments; + +import android.annotation.SuppressLint; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.android.volley.VolleyError; +import com.android.volley.toolbox.ImageLoader; +import com.likebamboo.osa.android.R; +import com.likebamboo.osa.android.entity.AuthorList; +import com.likebamboo.osa.android.request.RequestManager; +import com.likebamboo.osa.android.request.RequestUrl; +import com.likebamboo.osa.android.ui.nav.ActivityNavigator; +import com.likebamboo.osa.android.ui.view.CircleImageView; +import com.likebamboo.osa.android.ui.view.blur.BlurDialogFragmentHelper; + +import butterknife.ButterKnife; +import butterknife.InjectView; + +/** + * 作者信息Fragment + * + * @author likebamboo + */ +public class AuthorInfoFragment extends DialogFragment { + + private BlurDialogFragmentHelper mHelper; + + /** + * 作者 + */ + private AuthorList.Author mAuthor = null; + + @InjectView(R.id.author_avatar_iv) + CircleImageView mAvatarIv; + + @InjectView(R.id.author_name_tv) + TextView mNameTv; + + @InjectView(R.id.author_blog_tv) + TextView mBlogTv; + + @InjectView(R.id.author_github_tv) + TextView mGithubTv; + + @InjectView(R.id.author_info_tv) + TextView mInfoTv; + + public AuthorInfoFragment() { + } + + @SuppressLint("ValidFragment") + public AuthorInfoFragment(AuthorList.Author author) { + this.mAuthor = author; + } + + /** + * instance + * + * @param author + * @return + */ + public static AuthorInfoFragment newInstance(AuthorList.Author author) { + AuthorInfoFragment fragment = new AuthorInfoFragment(author); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mHelper = new BlurDialogFragmentHelper(this); + mHelper.onCreate(); + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.fragment_author_info, container, false); + + if (mAuthor == null) { + return v; + } + ButterKnife.inject(this, v); + + // 加载图片 + ImageLoader imageLoader = RequestManager.getImageLoader(); + if (!TextUtils.isEmpty(mAuthor.getAvatar())) { + imageLoader.get(RequestUrl.BASE_URL + mAuthor.getAvatar(), new ImageLoader.ImageListener() { + @Override + public void onResponse(ImageLoader.ImageContainer imageContainer, boolean b) { + mAvatarIv.setImageBitmap(imageContainer.getBitmap()); + } + + @Override + public void onErrorResponse(VolleyError volleyError) { + mAvatarIv.setImageResource(R.drawable.default_avatar); + } + }); + } + // set info + mNameTv.setText(mAuthor.getName()); + mBlogTv.setText(mAuthor.getBlog()); + // 链接点击跳转 + mBlogTv.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + openWebView(mAuthor.getBlog()); + } + }); + mGithubTv.setText(mAuthor.getGithub()); + // 链接点击跳转 + mGithubTv.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + openWebView(mAuthor.getGithub()); + } + }); + mInfoTv.setText(mAuthor.getIntroduction()); + return v; + } + + /** + * 打开web界面 + * + * @param url + */ + private void openWebView(String url) { + if (getActivity() == null) { + return; + } + // 跳转到Web页面 + ActivityNavigator.openWebView(getActivity(), null, url); + } + + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mHelper.onActivityCreated(); + } + + @Override + public void onStart() { + super.onStart(); + mHelper.onStart(); + } + + @Override + public void onDismiss(DialogInterface dialog) { + mHelper.onDismiss(); + super.onDismiss(dialog); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/fragments/BlogInfoFragment.java b/app/src/main/java/com/likebamboo/osa/android/ui/fragments/BlogInfoFragment.java new file mode 100644 index 0000000..3d1f39c --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/fragments/BlogInfoFragment.java @@ -0,0 +1,170 @@ +package com.likebamboo.osa.android.ui.fragments; + +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.likebamboo.osa.android.R; +import com.likebamboo.osa.android.entity.BlogList; +import com.likebamboo.osa.android.ui.nav.ActivityNavigator; +import com.likebamboo.osa.android.ui.view.blur.BlurDialogFragmentHelper; + +import java.net.URLDecoder; + +import butterknife.ButterKnife; +import butterknife.InjectView; + +/** + * 博客信息Fragment + * + * @author likebamboo + */ +public class BlogInfoFragment extends DialogFragment { + public static final String EXTRA_BLOG = "extra_blog"; + + private BlurDialogFragmentHelper mHelper; + + /** + * 博客信息 + */ + private BlogList.Blog mBlog = null; + + @InjectView(R.id.blog_title_tv) + TextView mTitleTv; + + @InjectView(R.id.blog_author_title_tv) + TextView mAuthorTitleTv; + + @InjectView(R.id.blog_author_tv) + TextView mAuthorTv; + + @InjectView(R.id.blog_link_tv) + TextView mUrlTv; + + @InjectView(R.id.blog_time_tv) + TextView mPostTimeTv; + + @InjectView(R.id.blog_category_tv) + TextView mCategoryTv; + + // for 翻译的文章 + @InjectView(R.id.blog_o_author_tv) + TextView mOAuthorTv; + + @InjectView(R.id.blog_o_link_tv) + TextView mOUrlTv; + + @InjectView(R.id.blog_o_time_tv) + TextView mOPostTimeTv; + + /** + * instance + * + * @param blog + * @return + */ + public static BlogInfoFragment getInstance(BlogList.Blog blog) { + BlogInfoFragment fragment = new BlogInfoFragment(); + Bundle bundle = new Bundle(); + bundle.putParcelable(EXTRA_BLOG, blog); + fragment.setArguments(bundle); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mHelper = new BlurDialogFragmentHelper(this); + mHelper.onCreate(); + if (getArguments() != null) { + mBlog = getArguments().getParcelable(EXTRA_BLOG); + } + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.fragment_blog_info, container, false); + + if (mBlog == null) { + return v; + } + ButterKnife.inject(this, v); + + // 标题 + mTitleTv.setText(mBlog.getTitle()); + // 作者 + mAuthorTv.setText(mBlog.getAuthor()); + // 链接 + try { + final String url = URLDecoder.decode(mBlog.getFromUrl(), "UTF-8"); + mUrlTv.setText(url); + mUrlTv.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (getActivity() == null) { + return; + } + ActivityNavigator.openWebView(getActivity(), null, url); + } + }); + } catch (Exception e) { + e.printStackTrace(); + } + // 发表时间 + mPostTimeTv.setText(mBlog.getPostTime()); + // set info + mCategoryTv.setText(mBlog.getCategorys()); + + // 是否为翻译 + if (mBlog.isTrans()) { + // 译者 + mAuthorTitleTv.setText(getString(R.string.translator)); + mOAuthorTv.setText(mBlog.getoAuthor()); + mOPostTimeTv.setText(mBlog.getoPostTime()); + try { + final String oUrl = URLDecoder.decode(mBlog.getoFromUrl(), "UTF-8"); + mOUrlTv.setText(oUrl); + mOUrlTv.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (getActivity() == null) { + return; + } + ActivityNavigator.openWebView(getActivity(), null, oUrl); + } + }); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + // 作者 + mAuthorTitleTv.setText(getString(R.string.author)); + v.findViewById(R.id.blog_o_author_layout).setVisibility(View.GONE); + v.findViewById(R.id.blog_o_time_layout).setVisibility(View.GONE); + v.findViewById(R.id.blog_o_link_layout).setVisibility(View.GONE); + } + return v; + } + + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mHelper.onActivityCreated(); + } + + @Override + public void onStart() { + super.onStart(); + mHelper.onStart(); + } + + @Override + public void onDismiss(DialogInterface dialog) { + mHelper.onDismiss(); + super.onDismiss(dialog); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/fragments/NavigationDrawerFragment.java b/app/src/main/java/com/likebamboo/osa/android/ui/fragments/NavigationDrawerFragment.java new file mode 100644 index 0000000..4aa7a17 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/fragments/NavigationDrawerFragment.java @@ -0,0 +1,272 @@ +package com.likebamboo.osa.android.ui.fragments; + +import android.app.Activity; +import android.content.res.Configuration; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBar; +import android.support.v7.app.ActionBarDrawerToggle; +import android.support.v7.app.AppCompatActivity; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +import com.likebamboo.osa.android.R; +import com.likebamboo.osa.android.ui.view.blur.EtsyActionBarDrawerToggle; + +/** + * Fragment used for managing interactions for and presentation of a navigation drawer. + * See the + * design guidelines for a complete explanation of the behaviors implemented here. + */ +public class NavigationDrawerFragment extends Fragment { + /** + * A pointer to the current callbacks instance (the Activity). + */ + private NavigationDrawerCallbacks mCallbacks; + + /** + * Helper component that ties the action bar to the navigation drawer. + */ + private ActionBarDrawerToggle mDrawerToggle; + + private DrawerLayout mDrawerLayout; + private ListView mDrawerListView; + private View mFragmentContainerView; + + public NavigationDrawerFragment() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + // Indicate that this fragment would like to influence the set of actions in the action bar. + setHasOptionsMenu(true); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + mDrawerListView = (ListView) inflater.inflate(R.layout.fragment_navigation_drawer, container, false); + mDrawerListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + Object item = parent.getAdapter().getItem(position); + if (item == null) { + return; + } + // 去掉 fa 图标文字 + String[] split = ("" + item).split(" "); + if (split != null && split.length > 0) { + selectItem(position, split[split.length - 1]); + } + } + }); + + String[] menuDatas = getResources().getStringArray(R.array.nav_menu_list); + mDrawerListView.setAdapter(new ArrayAdapter( + getActionBar().getThemedContext(), + R.layout.item_nav_menu, + R.id.nav_menu_item_tv, + menuDatas)); + mDrawerListView.setItemChecked(0, true); + return mDrawerListView; + } + + public boolean isDrawerOpen() { + return mDrawerLayout != null && mDrawerLayout.isDrawerOpen(mFragmentContainerView); + } + + public void closeDrawer() { + if (mDrawerLayout != null) { + mDrawerLayout.closeDrawer(mFragmentContainerView); + } + } + + public void openDrawer() { + if (mDrawerLayout != null) { + mDrawerLayout.openDrawer(mFragmentContainerView); + } + } + + /** + * Users of this fragment must call this method to set up the navigation drawer interactions. + * + * @param fragmentId The android:id of this fragment in its activity's layout. + * @param drawerLayout The DrawerLayout containing this fragment's UI. + * @param upIcon the icon of homeAsUp + */ + public void setUp(int fragmentId, DrawerLayout drawerLayout, int upIcon) { + mFragmentContainerView = getActivity().findViewById(fragmentId); + mDrawerLayout = drawerLayout; + + // Set the custom scrim color (the overlay color) for the etsy like blurred image + mDrawerLayout.setScrimColor(getResources().getColor(R.color.bg_glass)); + + // set a custom shadow that overlays the main content when the drawer opens + //mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); + // set up the drawer's list view with items and click listener + + ActionBar actionBar = getActionBar(); + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setHomeButtonEnabled(true); + + // ActionBarDrawerToggle ties together the the proper interactions + // between the navigation drawer and the action bar app icon. + mDrawerToggle = new EtsyActionBarDrawerToggle( + getActivity(), /* host Activity */ + mDrawerLayout, /* DrawerLayout object */ + R.string.navigation_drawer_open, /* "open drawer" description for accessibility */ + R.string.navigation_drawer_close /* "close drawer" description for accessibility */ + ) { + @Override + public void onDrawerClosed(View drawerView) { + super.onDrawerClosed(drawerView); + if (!isAdded()) { + return; + } + getActivity().supportInvalidateOptionsMenu(); // calls onPrepareOptionsMenu() + } + + @Override + public void onDrawerOpened(View drawerView) { + super.onDrawerOpened(drawerView); + if (!isAdded()) { + return; + } + + getActivity().supportInvalidateOptionsMenu(); // calls onPrepareOptionsMenu() + } + }; + // 设置返回图标 + if (upIcon > 0) { + actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP); + actionBar.setHomeAsUpIndicator(upIcon); + mDrawerToggle.setHomeAsUpIndicator(upIcon); + } else { + // Defer code dependent on restoration of previous instance state. + mDrawerLayout.post(new Runnable() { + @Override + public void run() { + mDrawerToggle.syncState(); + } + }); + } + mDrawerLayout.setDrawerListener(mDrawerToggle); + } + + /** + * 选中菜单项目 + * + * @param position + * @param menuText + */ + private void selectItem(int position, String menuText) { + if (mDrawerListView != null) { + mDrawerListView.setItemChecked(position, true); + } + if (mCallbacks != null) { + boolean close = mCallbacks.onNavigationDrawerItemSelected(position, menuText); + if (close && mDrawerLayout != null) { + mDrawerLayout.closeDrawer(mFragmentContainerView); + } + } + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + mCallbacks = (NavigationDrawerCallbacks) activity; + } catch (ClassCastException e) { + throw new ClassCastException("Activity must implement NavigationDrawerCallbacks."); + } + } + + @Override + public void onDetach() { + super.onDetach(); + mCallbacks = null; + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + // Forward the new configuration the drawer toggle component. + mDrawerToggle.onConfigurationChanged(newConfig); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + // If the drawer is open, show the global app actions in the action bar. See also + // showGlobalContextActionBar, which controls the top-left area of the action bar. + if (mDrawerLayout != null && isDrawerOpen()) { + inflater.inflate(R.menu.global, menu); + showGlobalContextActionBar(); + } + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (mDrawerToggle.onOptionsItemSelected(item)) { + return true; + } + + return super.onOptionsItemSelected(item); + } + + /** + * Per the navigation drawer design guidelines, updates the action bar to show the global app + * 'context', rather than just what's in the current screen. + */ + private void showGlobalContextActionBar() { + ActionBar actionBar = getActionBar(); + if (actionBar == null) { + return; + } + actionBar.setDisplayShowTitleEnabled(true); + actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); + actionBar.setTitle(getActivity().getTitle()); + } + + private ActionBar getActionBar() { + if (!(getActivity() instanceof AppCompatActivity)) { + return null; + } + return ((AppCompatActivity) getActivity()).getSupportActionBar(); + } + + /** + * Callbacks interface that all activities using this fragment must implement. + */ + public interface NavigationDrawerCallbacks { + /** + * Called when an item in the navigation drawer is selected. + * + * @return should close the navigation drawer + */ + boolean onNavigationDrawerItemSelected(int position, String menuText); + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/fragments/SettingsFragment.java b/app/src/main/java/com/likebamboo/osa/android/ui/fragments/SettingsFragment.java new file mode 100644 index 0000000..7252246 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/fragments/SettingsFragment.java @@ -0,0 +1,92 @@ +package com.likebamboo.osa.android.ui.fragments; + +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +import com.likebamboo.osa.android.R; +import com.likebamboo.osa.android.request.RequestUrl; +import com.likebamboo.osa.android.ui.AboutActivity; +import com.likebamboo.osa.android.ui.BaseActivity; +import com.likebamboo.osa.android.ui.nav.ActivityNavigator; +import com.likebamboo.osa.android.ui.view.blur.BlurDialogFragmentHelper; + +/** + * 关于Fragment + * + * @author likebamboo + */ +public class SettingsFragment extends DialogFragment { + + private BlurDialogFragmentHelper mHelper; + + public static SettingsFragment newInstance() { + SettingsFragment fragment = new SettingsFragment(); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mHelper = new BlurDialogFragmentHelper(this); + mHelper.onCreate(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.fragment_settings, container, false); + + ListView listView = (ListView) v.findViewById(R.id.dialog_content); + listView.setAdapter(new ArrayAdapter<>( + getActivity(), R.layout.simple_text, + android.R.id.text1, + getResources().getStringArray(R.array.about_list) + )); + listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView adapterView, View view, int i, long l) { + if (getActivity() == null) { + return; + } + switch (i) { + case 0:// 意见反馈 + ActivityNavigator.openWebView(getActivity(), null, RequestUrl.ISSUES_URL); + break; + case 1:// 关于作者 + ActivityNavigator.openWebView(getActivity(), null, RequestUrl.ABOUT_ME_URL); + break; + case 2:// 关于app + Intent intent = new Intent(getActivity(), AboutActivity.class); + intent.putExtra(BaseActivity.EXTRA_TITLE, getString(R.string.about)); + ActivityNavigator.startActivity(getActivity(), intent); + break; + } + } + }); + return v; + } + + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mHelper.onActivityCreated(); + } + + @Override + public void onStart() { + super.onStart(); + mHelper.onStart(); + } + + @Override + public void onDismiss(DialogInterface dialog) { + mHelper.onDismiss(); + super.onDismiss(dialog); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/nav/ActivityNavigator.java b/app/src/main/java/com/likebamboo/osa/android/ui/nav/ActivityNavigator.java new file mode 100644 index 0000000..7fdee79 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/nav/ActivityNavigator.java @@ -0,0 +1,170 @@ +package com.likebamboo.osa.android.ui.nav; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.text.TextUtils; + +import com.likebamboo.osa.android.R; +import com.likebamboo.osa.android.ui.NavigationActivity; +import com.likebamboo.osa.android.ui.WebViewActivity; + +import java.util.List; + +/** + * Activity导航 + * Created by wentaoli on 2015/5/11. + */ +public class ActivityNavigator { + + public static final ActivityNavigator nav = new ActivityNavigator(); + + private static final String NAV_ANIM_EXIT = "NAV_ANIM_EXIT"; + private static final String NAV_ANIM_IN = "NAV_ANIM_EXIT"; + + + /** + * 动画模式 + */ + public enum AnimationMode { + SLIDE_RIGHT, + SLIDE_BOTTOM, + FADE_SLOW, + FADE_IN_OUT, + POP, + DEFAULT, + DEFAULT_OUT, + ZOOM_IN_OUT, + NONE + } + + + /** + * 添加动画效果 + * + * @param intent + * @param animationMode + * @return + */ + public static ActivityNavigator withAnim(Intent intent, AnimationMode animationMode) { + if (intent == null) { + return nav; + } + int anim = 0; + switch (animationMode) { + case SLIDE_RIGHT: + break; + case SLIDE_BOTTOM: + break; + case FADE_SLOW: + break; + case FADE_IN_OUT: + intent.putExtra(NAV_ANIM_IN, R.anim.fade_in); + //intent.putExtra(NAV_ANIM_EXIT, R.anim.fade_out); + break; + case POP: + break; + case DEFAULT: + break; + case DEFAULT_OUT: + break; + case ZOOM_IN_OUT: + break; + case NONE: + default: + break; + } + return nav; + } + + /** + * 将Activity带到最顶层[有BUG] + * + * @param i + * @return + */ + public static ActivityNavigator reorderToTop(Intent i) { + if (i == null) { + return nav; + } + i.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + return nav; + } + + /** + * 将Activity清除某个Activity上边的Activity + * + * @param i + * @return + */ + public static ActivityNavigator clearTop(Intent i) { + if (i == null) { + return nav; + } + // 清除目标Activity上方的Activity(同时会清掉目标activity,并重新建立一个目标activity) + i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + // 加SingleTop标志,防止同时清除目标Activity + i.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + return nav; + } + + /** + * 打开webview界面 + * + * @param activity + * @param intent + * @param url + */ + public static void openWebView(Activity activity, Intent intent, String url) { + if (TextUtils.isEmpty(url)) { + return; + } + if (intent == null) { + intent = new Intent(); + } + // 跳转到Web页面 + intent.setClass(activity, WebViewActivity.class); + intent.putExtra(WebViewActivity.EXTRA_URL, url); + intent.putExtra(NavigationActivity.EXTRA_SHOULD_DISABLE_DRAWER, true); + ActivityNavigator.startActivity(activity, intent); + } + + /** + * 启动Activity + * + * @param activity + * @param intent + */ + public static void startActivity(Activity activity, Intent intent) { + if (intent == null || !isIntentAvailable(activity, intent)) { + return; + } + int animIn = 0, animOut = 0; + if (intent.hasExtra(NAV_ANIM_EXIT)) { + animOut = intent.getIntExtra(NAV_ANIM_EXIT, 0); + } + if (intent.hasExtra(NAV_ANIM_IN)) { + animIn = intent.getIntExtra(NAV_ANIM_IN, 0); + } + activity.startActivity(intent); + activity.overridePendingTransition(animIn, animOut); + } + + /** + * 检验Intent是否有效 + * + * @param context + * @param i + * @return + */ + public static boolean isIntentAvailable(Context context, Intent i) { + if (i == null) { + return false; + } + final PackageManager packageManager = context.getPackageManager(); + List list = packageManager.queryIntentActivities(i, PackageManager.MATCH_DEFAULT_ONLY); + return list.size() > 0; + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/CircleImageView.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/CircleImageView.java new file mode 100644 index 0000000..a2d80f8 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/CircleImageView.java @@ -0,0 +1,270 @@ +package com.likebamboo.osa.android.ui.view; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.util.AttributeSet; +import android.widget.ImageView; + +/** + *
+ * 描述:圆形ImageView
+ * @author  likebamboo
+ * @version  [版本号, 2015年05月14日]
+ * @see  [相关类/方法]
+ * @since  [产品/模块版本]
+ */
+public class CircleImageView extends ImageView {
+    private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
+
+    private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
+
+    private static final int COLORDRAWABLE_DIMENSION = 2;
+
+    private static final int DEFAULT_BORDER_WIDTH = 2;
+
+    private static final int DEFAULT_BORDER_COLOR = Color.WHITE;
+
+    private final RectF mDrawableRect = new RectF();
+
+    private final RectF mBorderRect = new RectF();
+
+    private final Matrix mShaderMatrix = new Matrix();
+
+    private final Paint mBitmapPaint = new Paint();
+
+    private final Paint mBorderPaint = new Paint();
+
+    private int mBorderColor = DEFAULT_BORDER_COLOR;
+
+    private int mBorderWidth = DEFAULT_BORDER_WIDTH;
+
+    private Bitmap mBitmap;
+
+    private BitmapShader mBitmapShader;
+
+    private int mBitmapWidth;
+
+    private int mBitmapHeight;
+
+    private float mDrawableRadius;
+
+    private float mBorderRadius;
+
+    private boolean mReady;
+
+    private boolean mSetupPending;
+
+    public CircleImageView(Context context) {
+        super(context);
+
+        init();
+    }
+
+    public CircleImageView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        mBorderWidth = DEFAULT_BORDER_WIDTH;
+        mBorderColor = DEFAULT_BORDER_COLOR;
+
+        init();
+    }
+
+    private void init() {
+        super.setScaleType(SCALE_TYPE);
+        mReady = true;
+
+        if (mSetupPending) {
+            setup();
+            mSetupPending = false;
+        }
+    }
+
+    @Override
+    public ScaleType getScaleType() {
+        return SCALE_TYPE;
+    }
+
+    @Override
+    public void setScaleType(ScaleType scaleType) {
+        if (scaleType != SCALE_TYPE) {
+            throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));
+        }
+    }
+
+    @Override
+    public void setAdjustViewBounds(boolean adjustViewBounds) {
+        if (adjustViewBounds) {
+            throw new IllegalArgumentException("adjustViewBounds not supported.");
+        }
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (getDrawable() == null) {
+            return;
+        }
+
+        canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius, mBitmapPaint);
+        if (mBorderWidth != 0) {
+            canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius, mBorderPaint);
+        }
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+        setup();
+    }
+
+    public int getBorderColor() {
+        return mBorderColor;
+    }
+
+    public void setBorderColor(int borderColor) {
+        if (borderColor == mBorderColor) {
+            return;
+        }
+
+        mBorderColor = borderColor;
+        mBorderPaint.setColor(mBorderColor);
+        invalidate();
+    }
+
+    public int getBorderWidth() {
+        return mBorderWidth;
+    }
+
+    public void setBorderWidth(int borderWidth) {
+        if (borderWidth == mBorderWidth) {
+            return;
+        }
+
+        mBorderWidth = borderWidth;
+        setup();
+    }
+
+    @Override
+    public void setImageBitmap(Bitmap bm) {
+        super.setImageBitmap(bm);
+        mBitmap = bm;
+        setup();
+    }
+
+    @Override
+    public void setImageDrawable(Drawable drawable) {
+        super.setImageDrawable(drawable);
+        mBitmap = getBitmapFromDrawable(drawable);
+        setup();
+    }
+
+    @Override
+    public void setImageResource(int resId) {
+        super.setImageResource(resId);
+        mBitmap = getBitmapFromDrawable(getDrawable());
+        setup();
+    }
+
+    @Override
+    public void setImageURI(Uri uri) {
+        super.setImageURI(uri);
+        mBitmap = getBitmapFromDrawable(getDrawable());
+        setup();
+    }
+
+    private Bitmap getBitmapFromDrawable(Drawable drawable) {
+        if (drawable == null) {
+            return null;
+        }
+
+        if (drawable instanceof BitmapDrawable) {
+            return ((BitmapDrawable) drawable).getBitmap();
+        }
+
+        try {
+            Bitmap bitmap;
+
+            if (drawable instanceof ColorDrawable) {
+                bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
+            } else {
+                bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);
+            }
+
+            Canvas canvas = new Canvas(bitmap);
+            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+            drawable.draw(canvas);
+            return bitmap;
+        } catch (OutOfMemoryError e) {
+            return null;
+        }
+    }
+
+    private void setup() {
+        if (!mReady) {
+            mSetupPending = true;
+            return;
+        }
+
+        if (mBitmap == null) {
+            return;
+        }
+
+        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+
+        mBitmapPaint.setAntiAlias(true);
+        mBitmapPaint.setShader(mBitmapShader);
+
+        mBorderPaint.setStyle(Paint.Style.STROKE);
+        mBorderPaint.setAntiAlias(true);
+        mBorderPaint.setColor(mBorderColor);
+        mBorderPaint.setStrokeWidth(mBorderWidth);
+
+        mBitmapHeight = mBitmap.getHeight();
+        mBitmapWidth = mBitmap.getWidth();
+
+        mBorderRect.set(0, 0, getWidth(), getHeight());
+        mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2, (mBorderRect.width() - mBorderWidth) / 2);
+
+        mDrawableRect.set(mBorderWidth, mBorderWidth, mBorderRect.width() - mBorderWidth, mBorderRect.height()
+                - mBorderWidth);
+        mDrawableRadius = Math.min(mDrawableRect.height() / 2, mDrawableRect.width() / 2);
+
+        updateShaderMatrix();
+        invalidate();
+    }
+
+    private void updateShaderMatrix() {
+        float scale;
+        float dx = 0;
+        float dy = 0;
+
+        mShaderMatrix.set(null);
+
+        if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
+            scale = mDrawableRect.height() / (float) mBitmapHeight;
+            dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
+        } else {
+            scale = mDrawableRect.width() / (float) mBitmapWidth;
+            dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
+        }
+
+        mShaderMatrix.setScale(scale, scale);
+        mShaderMatrix.postTranslate((int) (dx + 0.5f) + mBorderWidth, (int) (dy + 0.5f) + mBorderWidth);
+
+        mBitmapShader.setLocalMatrix(mShaderMatrix);
+    }
+}
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/CommonWebView.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/CommonWebView.java
new file mode 100644
index 0000000..b7c5a43
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/CommonWebView.java
@@ -0,0 +1,249 @@
+package com.likebamboo.osa.android.ui.view;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.http.SslError;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.webkit.SslErrorHandler;
+import android.webkit.WebChromeClient;
+import android.webkit.WebSettings;
+import android.webkit.WebSettings.PluginState;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.FrameLayout;
+
+import com.likebamboo.osa.android.R;
+
+/**
+ * 通用webview,包装webview,向外暴露webview部分接口,统一封装了native功能,提供给页面使用
+ *
+ * @version [版本号, 2015-5-24]
+ * @see [相关类/方法]
+ * @since [产品/模块版本]
+ */
+public class CommonWebView extends FrameLayout {
+    /**
+     * 上下文对象
+     */
+    private Context mContext = null;
+
+    /**
+     * webView
+     */
+    private ObservedWebView mWebview = null;
+
+    /**
+     * loading
+     */
+    private LoadingLayout mLoadingLayout = null;
+
+    /**
+     * 工具栏
+     */
+    private WebViewToolBar mToolBar = null;
+
+    /**
+     * 状态回调
+     */
+    private IWebViewStatusListener mListener = null;
+
+    /**
+     * webView client
+     */
+    private WebViewClient mWebViewClient = new WebViewClient() {
+
+        public void onPageStarted(WebView view, String url, Bitmap favicon) {
+            mLoadingLayout.showLoading(true);
+            if (mListener != null) {
+                mListener.onPageStarted(url);
+            }
+        }
+
+        public void onPageFinished(WebView view, String url) {
+            mLoadingLayout.showLoading(false);
+            if (mListener != null) {
+                mListener.onPageFinished(url);
+            }
+            // 标题
+            if (!TextUtils.isEmpty(view.getTitle())) {
+                if (mListener != null) {
+                    mListener.onReceiveTitle(view.getTitle());
+                }
+            }
+
+            if (mToolBar != null) {
+                // 更新工具栏状态
+                mToolBar.updateStatus();
+            }
+        }
+
+        @Override
+        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
+            // Android webview访问HTTPS web page忽略验证 .
+            handler.proceed(); // 接受所有网站的证书
+        }
+
+        @Override
+        public boolean shouldOverrideUrlLoading(WebView view, String url) {
+            if (url == null) {
+                return true;
+            }
+            if (mListener != null && mListener.shouldOverrideUrl(url)) {
+                return true;
+            }
+            return false;
+        }
+    };
+
+    /**
+     * WebChromeClient
+     */
+    private WebChromeClient mWebChromeClient = new WebChromeClient() {
+        @Override
+        public void onProgressChanged(WebView view, int newProgress) {
+            super.onProgressChanged(view, newProgress);
+        }
+
+        @Override
+        public void onReceivedTitle(WebView view, String title) {
+            super.onReceivedTitle(view, title);
+            if (!TextUtils.isEmpty(title)) {
+                if (mListener != null) {
+                    mListener.onReceiveTitle(view.getTitle());
+                }
+            }
+        }
+    };
+
+    public CommonWebView(Context context) {
+        this(context, null);
+    }
+
+    public CommonWebView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public CommonWebView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        initView(context);
+    }
+
+    /**
+     * 初始化view
+     */
+    private void initView(Context context) {
+        mContext = context;
+        LayoutInflater.from(context).inflate(R.layout.common_webview, this, true);
+        mWebview = (ObservedWebView) findViewById(R.id.common_webview);
+        mLoadingLayout = (LoadingLayout) findViewById(R.id.webview_loading_layout);
+        mToolBar = (WebViewToolBar) findViewById(R.id.webview_tool_bar);
+
+        initWebViewSettings();
+        mToolBar.attachToWebView(mWebview);
+    }
+
+    /**
+     * 初始化webview配置
+     */
+    private void initWebViewSettings() {
+        WebSettings webSettings = mWebview.getSettings();
+        // 打开h5 localstorage
+        webSettings.setDomStorageEnabled(true);
+        // 不设置setDatabasePath,html5数据只会保存在内存
+        webSettings.setDatabaseEnabled(true);
+        webSettings.setJavaScriptEnabled(true);
+        webSettings.setPluginState(PluginState.ON);
+        // 禁止window open
+        webSettings.setJavaScriptCanOpenWindowsAutomatically(false);
+        // 支持viewport meta tag
+        webSettings.setUseWideViewPort(true);
+        webSettings.setLoadWithOverviewMode(true);
+        webSettings.setSupportZoom(false);
+        mWebview.setVerticalScrollBarEnabled(false);
+        mWebview.setHorizontalScrollBarEnabled(false);
+        // 滚动条风格,为0就是不给滚动条留空间,滚动条覆盖在网页上
+        mWebview.setScrollBarStyle(WebView.SCROLLBARS_INSIDE_OVERLAY);
+        mWebview.setWebChromeClient(mWebChromeClient);
+        mWebview.setWebViewClient(mWebViewClient);
+    }
+
+    // ==============api method ===========
+
+    /**
+     * 加载指定url
+     */
+    public void loadUrl(String url) {
+        mWebview.loadUrl(url);
+    }
+
+    /**
+     * onpause
+     */
+    public void onPause() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+            mWebview.onPause();
+        }
+    }
+
+    /**
+     * onResume
+     */
+    public void onResume() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+            mWebview.onResume();
+        }
+    }
+
+    /**
+     * 设置toolbar是否显示
+     *
+     * @param visibility
+     */
+    public void setToolBarVisibility(int visibility) {
+        mToolBar.setVisibility(visibility);
+    }
+
+    /**
+     * 设置状态监听回调
+     *
+     * @param mListener
+     */
+    public void setStatusListener(IWebViewStatusListener mListener) {
+        this.mListener = mListener;
+    }
+
+    /**
+     * 返回
+     *
+     * @return
+     */
+    public boolean goBack() {
+        if (mWebview.canGoBack()) {
+            mWebview.goBack();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * getwebView
+     *
+     * @return
+     */
+    public ObservedWebView getWebView() {
+        return mWebview;
+    }
+
+    public interface IWebViewStatusListener {
+        void onPageStarted(String url);
+
+        void onPageFinished(String url);
+
+        void onReceiveTitle(String title);
+
+        boolean shouldOverrideUrl(String url);
+    }
+}
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/LoadingLayout.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/LoadingLayout.java
new file mode 100644
index 0000000..70e6e5b
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/LoadingLayout.java
@@ -0,0 +1,159 @@
+package com.likebamboo.osa.android.ui.view;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.likebamboo.osa.android.R;
+
+import butterknife.ButterKnife;
+import butterknife.InjectView;
+
+/**
+ * @author likebamboo
+ * @date 2015/5/13.
+ * @desc 
描述: Loading(整块区域或者加载更多时loading)
+ */
+public class LoadingLayout extends LinearLayout {
+    /**
+     * 正在加载view
+     */
+    @InjectView(R.id.loading_layout)
+    View mLoadingView = null;
+
+    /**
+     * Loading 提示TextView
+     */
+    @InjectView(R.id.loading_tv)
+    TextView mLoadingTv = null;
+
+    /**
+     * 重试布局
+     */
+    @InjectView(R.id.loading_fail_layout)
+    View mRetryLayout = null;
+
+    /**
+     * 错误提示TextView
+     */
+    @InjectView(R.id.loading_fail_tv)
+    TextView mErrorTv = null;
+
+    /**
+     * 重试接口
+     */
+    private IRetryListener mRetryListener = null;
+
+    /**
+     * 是否可以重试
+     */
+    private boolean canRetry = true;
+
+    public interface IRetryListener {
+        void onRetry();
+    }
+
+    public LoadingLayout(Context context) {
+        this(context, null);
+    }
+
+    public LoadingLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @SuppressLint("NewApi")
+    public LoadingLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        if (isInEditMode()) {
+            return;
+        }
+        ButterKnife.inject(this);
+        mRetryLayout.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                if (!canRetry) {
+                    return;
+                }
+                if (mRetryListener != null) {
+                    mRetryListener.onRetry();
+                }
+            }
+        });
+    }
+
+    /**
+     * 显示/隐藏正在加载中。。。
+     *
+     * @param show
+     * @see [类、类#方法、类#成员]
+     */
+    public void showLoading(boolean show) {
+        showLoading(show, null);
+    }
+
+    /**
+     * 显示/隐藏正在加载中。。。
+     *
+     * @param show
+     * @param text
+     * @see [类、类#方法、类#成员]
+     */
+    public void showLoading(boolean show, String text) {
+        if (show) {
+            setVisibility(View.VISIBLE);
+            mLoadingView.setVisibility(View.VISIBLE);
+            mRetryLayout.setVisibility(View.GONE);
+            if (!TextUtils.isEmpty(text)) {
+                mLoadingTv.setText(text);
+            }
+        } else {
+            setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * 显示加载失败信息
+     *
+     * @param msg
+     * @see [类、类#方法、类#成员]
+     */
+    public void showError(String msg) {
+        setVisibility(View.VISIBLE);
+        mLoadingView.setVisibility(View.GONE);
+        mRetryLayout.setVisibility(View.VISIBLE);
+        if (!TextUtils.isEmpty(msg)) {
+            mErrorTv.setText(msg);
+        }
+        canRetry = true;
+    }
+
+    /**
+     * 显示"空"信息
+     *
+     * @param msg
+     */
+    public void showEmpty(String msg) {
+        showError(msg);
+        // 不允许重试
+        canRetry = false;
+    }
+
+    /**
+     * 设置重试监听器。
+     *
+     * @param listener
+     */
+    public void setRetryListener(IRetryListener listener) {
+        this.mRetryListener = listener;
+    }
+
+}
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/ObservedWebView.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/ObservedWebView.java
new file mode 100644
index 0000000..6bfc16e
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/ObservedWebView.java
@@ -0,0 +1,49 @@
+package com.likebamboo.osa.android.ui.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.webkit.WebView;
+
+/**
+ * 可监听滚动的WebView
+ * 

+ * Created by wentaoli on 2015/5/22. + */ +public class ObservedWebView extends WebView { + private OnScrollChangedCallback mOnScrollChangedCallback; + + public ObservedWebView(final Context context) { + super(context); + } + + public ObservedWebView(final Context context, final AttributeSet attrs) { + super(context, attrs); + } + + public ObservedWebView(final Context context, final AttributeSet attrs, final int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onScrollChanged(final int l, final int t, final int oldl, final int oldt) { + super.onScrollChanged(l, t, oldl, oldt); + if (mOnScrollChangedCallback != null) { + mOnScrollChangedCallback.onScroll(l, t); + } + } + + public OnScrollChangedCallback getOnScrollChangedCallback() { + return mOnScrollChangedCallback; + } + + public void setOnScrollChangedCallback(final OnScrollChangedCallback onScrollChangedCallback) { + mOnScrollChangedCallback = onScrollChangedCallback; + } + + /** + * Impliment in the activity/fragment/view that you want to listen to the webview + */ + public interface OnScrollChangedCallback { + void onScroll(int l, int t); + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/TagGroup.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/TagGroup.java new file mode 100644 index 0000000..80de813 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/TagGroup.java @@ -0,0 +1,508 @@ +package com.likebamboo.osa.android.ui.view; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.HapticFeedbackConstants; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.likebamboo.osa.android.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * 参考https://github.com/2dxgujun/AndroidTagGroup + * + * @author likebamboo + * @version 1.0 + * @since 2015-05-18 + */ +public class TagGroup extends ViewGroup { + + /** + * tag 字体大小 + */ + private float mTagTextSize; + + /** + * 字体背景 + */ + private int mTagBackgroundColor; + + /** + * 字体背景 + */ + private int mTagBackgroundResId; + + /** + * 字体颜色 + */ + private int mTagTextColor; + + /** + * The horizontal tag spacing, + */ + private int mHorizontalSpacing; + + /** + * The vertical tag spacing + */ + private int mVerticalSpacing; + + /** + * 标签内边距 + */ + private int mHorizontalPadding; + + /** + * 标签内边距 + */ + private int mVerticalPadding; + + /** + * Listener used to dispatch tag click event. + */ + private IOnTagClickListener mOnTagClickListener; + + public TagGroup(Context context) { + this(context, null); + } + + public TagGroup(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public TagGroup(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + // Load styled attributes. + final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TagGroup, defStyleAttr, R.style.TagGroup); + try { + mTagTextColor = a.getColor(R.styleable.TagGroup_tagTextColor, Color.WHITE); + try { + mTagBackgroundColor = a.getColor(R.styleable.TagGroup_tagBackground, 0); + } catch (Exception e) { + mTagBackgroundResId = a.getResourceId(R.styleable.TagGroup_tagBackground, 0); + } + mTagTextSize = a.getDimensionPixelOffset(R.styleable.TagGroup_tagTextSize, (int) dp2px(14)); + mHorizontalSpacing = (int) a.getDimension(R.styleable.TagGroup_horizontalSpacing, dp2px(8)); + mVerticalSpacing = (int) a.getDimension(R.styleable.TagGroup_verticalSpacing, dp2px(4)); + + mHorizontalPadding = (int) a.getDimension(R.styleable.TagGroup_horizontalPadding, dp2px(6)); + mVerticalPadding = (int) a.getDimension(R.styleable.TagGroup_verticalPadding, dp2px(1)); + } catch (Exception e) { + e.printStackTrace(); + } finally { + a.recycle(); + } + + } + + public void setTextSize(float textSize) { + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + TagView tagView = getTagViewAt(i); + tagView.setTextSize(textSize); + } + requestLayout(); + } + + /** + * @param resId + */ + public void setTagColor(int resId) { + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + TagView tagView = getTagViewAt(i); + tagView.setTextColor(resId); + } + } + + /** + * @param resId + */ + public void setTagBackgroundResource(int resId) { + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + TagView tagView = getTagViewAt(i); + tagView.setBackgroundResource(resId); + } + } + + /** + * @param color + */ + public void setTagBackgroundColor(int color) { + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + TagView tagView = getTagViewAt(i); + tagView.setBackgroundColor(color); + } + } + + public int getHorizontalSpacing() { + return mHorizontalSpacing; + } + + public void setHorizontalSpacing(int horizontalSpacing) { + mHorizontalSpacing = horizontalSpacing; + requestLayout(); + } + + public int getVerticalSpacing() { + return mVerticalSpacing; + } + + public void setVerticalSpacing(int verticalSpacing) { + mVerticalSpacing = verticalSpacing; + requestLayout(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + final int heightMode = MeasureSpec.getMode(heightMeasureSpec); + final int widthSize = MeasureSpec.getSize(widthMeasureSpec); + final int heightSize = MeasureSpec.getSize(heightMeasureSpec); + + measureChildren(widthMeasureSpec, heightMeasureSpec); + + int width = 0; + int height = 0; + + int row = 0; // The row counter. + int rowWidth = 0; // Calc the current row width. + int rowMaxHeight = 0; // Calc the max tag height, in current row. + + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + final int childWidth = child.getMeasuredWidth(); + final int childHeight = child.getMeasuredHeight(); + + if (child.getVisibility() != GONE) { + rowWidth += childWidth; + if (rowWidth > widthSize) { // Next line. + rowWidth = childWidth; // The next row width. + height += rowMaxHeight + mVerticalSpacing; + rowMaxHeight = childHeight; // The next row max height. + row++; + } else { // This line. + rowMaxHeight = Math.max(rowMaxHeight, childHeight); + } + rowWidth += mHorizontalSpacing; + } + } + // Account for the last row height. + height += rowMaxHeight; + + // Account for the padding too. + height += getPaddingTop() + getPaddingBottom(); + + // If the tags grouped in one row, set the width to wrap the tags. + if (row == 0) { + width = rowWidth; + width += getPaddingLeft() + getPaddingRight(); + } else {// If the tags grouped exceed one line, set the width to match the parent. + width = widthSize; + } + + setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width, + heightMode == MeasureSpec.EXACTLY ? heightSize : height); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + final int parentLeft = getPaddingLeft(); + final int parentRight = r - l - getPaddingRight(); + final int parentTop = getPaddingTop(); + final int parentBottom = b - t - getPaddingBottom(); + + int childLeft = parentLeft; + int childTop = parentTop; + + int rowMaxHeight = 0; + + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + final int width = child.getMeasuredWidth(); + final int height = child.getMeasuredHeight(); + + if (child.getVisibility() != GONE) { + if (childLeft + width > parentRight) { // Next line + childLeft = parentLeft; + childTop += rowMaxHeight + mVerticalSpacing; + rowMaxHeight = height; + } else { + rowMaxHeight = Math.max(rowMaxHeight, height); + } + child.layout(childLeft, childTop, childLeft + width, childTop + height); + + childLeft += width + mHorizontalSpacing; + } + } + } + + @Override + public Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + SavedState ss = new SavedState(superState); + ss.tags = getTags().toArray(new String[]{}); + return ss; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + if (!(state instanceof SavedState)) { + super.onRestoreInstanceState(state); + return; + } + + SavedState ss = (SavedState) state; + super.onRestoreInstanceState(ss.getSuperState()); + + setTags(ss.tags); + } + + + /** + * Returns the tags array in group. + * + * @return the tag array + */ + public List getTags() { + final int count = getChildCount(); + final List tagList = new ArrayList<>(); + for (int i = 0; i < count; i++) { + final TagView tagView = getTagViewAt(i); + tagList.add(tagView.getText().toString()); + } + + return tagList; + } + + /** + * Returns the tag view at the specified position in the group. + * + * @param index the position at which to get the tag view from + * @return the tag view at the specified position or null if the position + * does not exists within this group + */ + protected TagView getTagViewAt(int index) { + return (TagView) getChildAt(index); + } + + public void setOnTagClickListener(IOnTagClickListener l) { + mOnTagClickListener = l; + } + + /** + * @see #setTags(String...) + */ + public void setTags(List tagList) { + setTags(tagList.toArray(new String[]{})); + } + + /** + * Set the tag to this group. It will remove all tags first. + * + * @param tags the tag list to set + */ + public void setTags(String... tags) { + removeAllViews(); + for (final String tag : tags) { + if (TextUtils.isEmpty(tag)) { + continue; + } + addTag(tag); + } + } + + /** + * 删除标签 + * + * @param tag + */ + public void deleteTag(CharSequence tag) { + if (TextUtils.isEmpty(tag)) { + return; + } + int index = 0; + for (; index < getChildCount(); index++) { + if (getTagViewAt(index).getText().equals(tag)) { + break; + } + } + if (index < getChildCount()) { + removeViewAt(index); + } + } + + /** + * 判断是否已经有标签 + * + * @param tag + */ + public boolean hasTag(CharSequence tag) { + if (TextUtils.isEmpty(tag)) { + return true; + } + return getTags().contains(tag + ""); + } + + /** + * 添加标签 + * + * @param tag + */ + public void addTag(CharSequence tag) { + addTag(getChildCount(), tag); + } + + /** + * 添加标签 + * + * @param index + * @param tag the tag to append + */ + public void addTag(int index, CharSequence tag) { + // 如果已经有该标签,将标签提到最前面 + if (hasTag(tag)) { + deleteTag(tag); + index = 0; + } + final TagView tagView = new TagView(getContext()); + tagView.setText(tag); + tagView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTagTextSize); + tagView.setTextColor(mTagTextColor); + if (mTagBackgroundColor != 0) { + tagView.setBackgroundColor(mTagBackgroundColor); + } else if (mTagBackgroundResId != 0) { + tagView.setBackgroundResource(mTagBackgroundResId); + } + + tagView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + if (view instanceof TagView) { + TagView v = (TagView) view; + if (mOnTagClickListener != null) { + mOnTagClickListener.onTagClick(v.getText().toString().trim()); + } + } + } + }); + tagView.setOnLongClickListener(new OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + // 长按震动反馈 + view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + if (view instanceof TagView) { + TagView v = (TagView) view; + if (mOnTagClickListener != null) { + mOnTagClickListener.onTagLongClick(v.getText().toString().trim()); + } + return true; + } + return false; + } + }); + + // 设置标签内边距 + tagView.setPadding(mHorizontalPadding, mVerticalPadding, mHorizontalPadding, mVerticalPadding); + addView(tagView, index, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); + } + + public float dp2px(float dp) { + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, + getResources().getDisplayMetrics()); + } + + @Override + public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { + return new LayoutParams(getContext(), attrs); + } + + public interface IOnTagClickListener { + /** + * @param tag tagName + */ + void onTagClick(String tag); + + /** + * tagLongClick + * + * @param tag tagName + */ + void onTagLongClick(String tag); + } + + /** + * For {@link TagGroup} save and restore state. + */ + static class SavedState extends BaseSavedState { + int tagCount; + String[] tags; + int checkedPosition; + String input; + + public SavedState(Parcel source) { + super(source); + tagCount = source.readInt(); + tags = new String[tagCount]; + source.readStringArray(tags); + checkedPosition = source.readInt(); + input = source.readString(); + } + + public SavedState(Parcelable superState) { + super(superState); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + tagCount = tags.length; + dest.writeInt(tagCount); + dest.writeStringArray(tags); + dest.writeInt(checkedPosition); + dest.writeString(input); + } + + public static final Creator CREATOR = + new Creator() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + /** + * The tag view + */ + class TagView extends TextView { + public TagView(Context context) { + super(context); + } + + public TagView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public TagView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/WebViewToolBar.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/WebViewToolBar.java new file mode 100644 index 0000000..a4071e3 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/WebViewToolBar.java @@ -0,0 +1,142 @@ +package com.likebamboo.osa.android.ui.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.webkit.WebView; +import android.widget.RelativeLayout; + +import com.likebamboo.osa.android.R; + +/** + * WebView 操作栏封装,提供 back ,refresh ,forward 功能 + * + * @author likebamboo + * @version [版本号, 2015年5月20日] + * @see [相关类/方法] + * @since [产品/模块版本] + */ +public class WebViewToolBar extends RelativeLayout implements View.OnClickListener +{ + + /** + * 后退 + */ + private View mWebviewGoBack; + + /** + * 刷新 + */ + private View mWebviewRefresh; + + /** + * 前进 + */ + private View mWebviewGoForward; + + /** + * 目标WebView + */ + private WebView mTargetView = null; + + public WebViewToolBar(Context context) + { + this(context, null); + } + + public WebViewToolBar(Context context, AttributeSet attrs) + { + this(context, attrs, 0); + } + + public WebViewToolBar(Context context, AttributeSet attrs, int defStyleAttr) + { + super(context, attrs, defStyleAttr); + LayoutInflater.from(context).inflate(R.layout.webview_toolbar, this, true); + if (!isInEditMode()) + { + // 初始化 + initView(); + // 添加监听器 + addListener(); + } + } + + /** + * 初始化界面元素 + * + * @see [类、类#方法、类#成员] + */ + private void initView() + { + mWebviewGoBack = findViewById(R.id.webviewGoBack); + mWebviewGoBack.setEnabled(false); + mWebviewRefresh = findViewById(R.id.webviewRefresh); + mWebviewRefresh.setEnabled(false); + mWebviewGoForward = findViewById(R.id.webviewGoForward); + mWebviewGoForward.setEnabled(false); + } + + /** + * 添加监听器 + * + * @see [类、类#方法、类#成员] + */ + private void addListener() + { + mWebviewGoBack.setOnClickListener(this); + mWebviewRefresh.setOnClickListener(this); + mWebviewGoForward.setOnClickListener(this); + } + + @Override + public void onClick(View v) + { + if (v == null || mTargetView == null) + { + return; + } + switch (v.getId()) + { + case R.id.webviewGoBack:// 后退 + mTargetView.goBack(); + break; + case R.id.webviewRefresh:// 刷新 + mTargetView.reload(); + break; + case R.id.webviewGoForward:// 向前 + mTargetView.goForward(); + break; + default: + break; + } + } + + /** + * 将本控件绑定到具体的webView 上 + * + * @see [类、类#方法、类#成员] + */ + public void attachToWebView(WebView target) + { + mTargetView = target; + } + + /** + * 更新状态 + * + * @see [类、类#方法、类#成员] + */ + public void updateStatus() + { + if (mTargetView == null) + { + return; + } + mWebviewGoBack.setEnabled(mTargetView.canGoBack()); + mWebviewRefresh.setEnabled(true); + mWebviewGoForward.setEnabled(mTargetView.canGoForward()); + } + +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/Blur.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/Blur.java new file mode 100644 index 0000000..180d371 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/Blur.java @@ -0,0 +1,276 @@ +package com.likebamboo.osa.android.ui.view.blur; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +import android.os.Build.VERSION; +import android.renderscript.Allocation; +import android.renderscript.Element; +import android.renderscript.RenderScript; +import android.renderscript.ScriptIntrinsicBlur; + +// Slightly modified source from Nicolas Pomepuy +// https://github.com/PomepuyN/BlurEffectForAndroidDesign + +public class Blur { + + public static Bitmap apply(Context context, Bitmap sentBitmap) { + return apply(context, sentBitmap, 10); + } + + public static Bitmap apply(Context context, Bitmap sentBitmap, int radius) { + return apply(context, sentBitmap, 1, radius); + } + + @SuppressLint("NewApi") + public static Bitmap apply(Context context, Bitmap sentBitmap, float scale, int radius) { + if (sentBitmap == null) { + return null; + } + if (scale < 0.5) { + scale = 0.5F; + } + if (scale > 1) { + scale = 1; + } + + Bitmap bitmap = Bitmap.createScaledBitmap(sentBitmap, (int) (sentBitmap.getWidth() * scale), (int) (sentBitmap.getHeight() * scale), false); + + if (VERSION.SDK_INT > 16) { + final RenderScript rs = RenderScript.create(context); + final Allocation input = Allocation.createFromBitmap(rs, bitmap, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); + final Allocation output = Allocation.createTyped(rs, input.getType()); + final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); + script.setRadius(radius); + script.setInput(input); + script.forEach(output); + output.copyTo(bitmap); + + return bitmap; + } + + // Stack Blur v1.0 from + // http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html + // + // Java Author: Mario Klingemann + // http://incubator.quasimondo.com + // created Feburary 29, 2004 + // Android port : Yahel Bouaziz + // http://www.kayenko.com + // ported april 5th, 2012 + + // This is a compromise between Gaussian Blur and Box blur + // It creates much better looking blurs than Box Blur, but is + // 7x faster than my Gaussian Blur implementation. + // + // I called it Stack Blur because this describes best how this + // filter works internally: it creates a kind of moving stack + // of colors whilst scanning through the image. Thereby it + // just has to add one new block of color to the right side + // of the stack and remove the leftmost color. The remaining + // colors on the topmost layer of the stack are either added on + // or reduced by one, depending on if they are on the right or + // on the left side of the stack. + // + // If you are using this algorithm in your code please add + // the following line: + // + // Stack Blur Algorithm by Mario Klingemann + + if (radius < 1) { + return (null); + } + + int w = bitmap.getWidth(); + int h = bitmap.getHeight(); + + int[] pix = new int[w * h]; + bitmap.getPixels(pix, 0, w, 0, 0, w, h); + + int wm = w - 1; + int hm = h - 1; + int wh = w * h; + int div = radius + radius + 1; + + int r[] = new int[wh]; + int g[] = new int[wh]; + int b[] = new int[wh]; + int rsum, gsum, bsum, x, y, i, p, yp, yi, yw; + int vmin[] = new int[Math.max(w, h)]; + + int divsum = (div + 1) >> 1; + divsum *= divsum; + int dv[] = new int[256 * divsum]; + for (i = 0; i < 256 * divsum; i++) { + dv[i] = (i / divsum); + } + + yw = yi = 0; + + int[][] stack = new int[div][3]; + int stackpointer; + int stackstart; + int[] sir; + int rbs; + int r1 = radius + 1; + int routsum, goutsum, boutsum; + int rinsum, ginsum, binsum; + + for (y = 0; y < h; y++) { + rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; + for (i = -radius; i <= radius; i++) { + p = pix[yi + Math.min(wm, Math.max(i, 0))]; + sir = stack[i + radius]; + sir[0] = (p & 0xff0000) >> 16; + sir[1] = (p & 0x00ff00) >> 8; + sir[2] = (p & 0x0000ff); + rbs = r1 - Math.abs(i); + rsum += sir[0] * rbs; + gsum += sir[1] * rbs; + bsum += sir[2] * rbs; + if (i > 0) { + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + } else { + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + } + } + stackpointer = radius; + + for (x = 0; x < w; x++) { + + r[yi] = dv[rsum]; + g[yi] = dv[gsum]; + b[yi] = dv[bsum]; + + rsum -= routsum; + gsum -= goutsum; + bsum -= boutsum; + + stackstart = stackpointer - radius + div; + sir = stack[stackstart % div]; + + routsum -= sir[0]; + goutsum -= sir[1]; + boutsum -= sir[2]; + + if (y == 0) { + vmin[x] = Math.min(x + radius + 1, wm); + } + p = pix[yw + vmin[x]]; + + sir[0] = (p & 0xff0000) >> 16; + sir[1] = (p & 0x00ff00) >> 8; + sir[2] = (p & 0x0000ff); + + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + + rsum += rinsum; + gsum += ginsum; + bsum += binsum; + + stackpointer = (stackpointer + 1) % div; + sir = stack[(stackpointer) % div]; + + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + + rinsum -= sir[0]; + ginsum -= sir[1]; + binsum -= sir[2]; + + yi++; + } + yw += w; + } + for (x = 0; x < w; x++) { + rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; + yp = -radius * w; + for (i = -radius; i <= radius; i++) { + yi = Math.max(0, yp) + x; + + sir = stack[i + radius]; + + sir[0] = r[yi]; + sir[1] = g[yi]; + sir[2] = b[yi]; + + rbs = r1 - Math.abs(i); + + rsum += r[yi] * rbs; + gsum += g[yi] * rbs; + bsum += b[yi] * rbs; + + if (i > 0) { + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + } else { + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + } + + if (i < hm) { + yp += w; + } + } + yi = x; + stackpointer = radius; + for (y = 0; y < h; y++) { + // Preserve alpha channel: ( 0xff000000 & pix[yi] ) + pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum]; + + rsum -= routsum; + gsum -= goutsum; + bsum -= boutsum; + + stackstart = stackpointer - radius + div; + sir = stack[stackstart % div]; + + routsum -= sir[0]; + goutsum -= sir[1]; + boutsum -= sir[2]; + + if (x == 0) { + vmin[y] = Math.min(y + r1, hm) * w; + } + p = x + vmin[y]; + + sir[0] = r[p]; + sir[1] = g[p]; + sir[2] = b[p]; + + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + + rsum += rinsum; + gsum += ginsum; + bsum += binsum; + + stackpointer = (stackpointer + 1) % div; + sir = stack[stackpointer]; + + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + + rinsum -= sir[0]; + ginsum -= sir[1]; + binsum -= sir[2]; + + yi += w; + } + } + + bitmap.setPixels(pix, 0, w, 0, 0, w, h); + return (bitmap); + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/BlurBehind.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/BlurBehind.java new file mode 100644 index 0000000..f67c807 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/BlurBehind.java @@ -0,0 +1,121 @@ +package com.likebamboo.osa.android.ui.view.blur; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.PorterDuff; +import android.graphics.drawable.BitmapDrawable; +import android.os.AsyncTask; +import android.support.v4.util.LruCache; +import android.view.View; + +/** + * READ ME + *

+ * Created by wentaoli on 2015/5/18. + */ +public class BlurBehind { + + private static final String KEY_CACHE_BLURRED_BACKGROUND_IMAGE = "KEY_CACHE_BLURRED_BACKGROUND_IMAGE"; + private static final int CONSTANT_BLUR_RADIUS = 10; + private static final int CONSTANT_DEFAULT_ALPHA = 100; + + private static final LruCache mImageCache = new LruCache(1); + private static CacheBlurBehindAndExecuteTask cacheBlurBehindAndExecuteTask; + + private int mAlpha = CONSTANT_DEFAULT_ALPHA; + private int mFilterColor = -1; + + private enum State { + READY, + EXECUTING + } + + private State mState = State.READY; + + private static BlurBehind mInstance; + + public static BlurBehind getInstance() { + if (mInstance == null) { + mInstance = new BlurBehind(); + } + return mInstance; + } + + public void execute(Activity activity, OnBlurCompleteListener onBlurCompleteListener) { + if (mState.equals(State.READY)) { + mState = State.EXECUTING; + cacheBlurBehindAndExecuteTask = new CacheBlurBehindAndExecuteTask(activity, onBlurCompleteListener); + cacheBlurBehindAndExecuteTask.execute(); + } + } + + public BlurBehind withAlpha(int alpha) { + this.mAlpha = alpha; + return this; + } + + public BlurBehind withFilterColor(int filterColor) { + this.mFilterColor = filterColor; + return this; + } + + public void setBackground(Activity activity) { + if (mImageCache.size() != 0) { + BitmapDrawable bd = new BitmapDrawable(activity.getResources(), mImageCache.get(KEY_CACHE_BLURRED_BACKGROUND_IMAGE)); + bd.setAlpha(mAlpha); + if (mFilterColor != -1) { + bd.setColorFilter(mFilterColor, PorterDuff.Mode.DST_ATOP); + } + activity.getWindow().setBackgroundDrawable(bd); + mImageCache.remove(KEY_CACHE_BLURRED_BACKGROUND_IMAGE); + cacheBlurBehindAndExecuteTask = null; + } + } + + private class CacheBlurBehindAndExecuteTask extends AsyncTask { + private Activity activity; + private OnBlurCompleteListener onBlurCompleteListener; + + private View decorView; + private Bitmap image; + + public CacheBlurBehindAndExecuteTask(Activity activity, OnBlurCompleteListener onBlurCompleteListener) { + this.activity = activity; + this.onBlurCompleteListener = onBlurCompleteListener; + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + + decorView = activity.getWindow().getDecorView(); + decorView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_LOW); + decorView.setDrawingCacheEnabled(true); + decorView.buildDrawingCache(); + + image = decorView.getDrawingCache(); + } + + @Override + protected Void doInBackground(Void... params) { + Bitmap blurredBitmap = Blur.apply(activity, image, 0.5F, CONSTANT_BLUR_RADIUS); + mImageCache.put(KEY_CACHE_BLURRED_BACKGROUND_IMAGE, blurredBitmap); + + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + super.onPostExecute(aVoid); + + decorView.destroyDrawingCache(); + decorView.setDrawingCacheEnabled(false); + + activity = null; + + onBlurCompleteListener.onBlurComplete(); + + mState = State.READY; + } + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/BlurDialogFragmentHelper.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/BlurDialogFragmentHelper.java new file mode 100644 index 0000000..d89e80f --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/BlurDialogFragmentHelper.java @@ -0,0 +1,140 @@ +package com.likebamboo.osa.android.ui.view.blur; + +import android.graphics.Bitmap; +import android.graphics.Rect; +import android.support.annotation.ColorRes; +import android.support.annotation.NonNull; +import android.support.annotation.StyleRes; +import android.support.v4.app.DialogFragment; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.likebamboo.osa.android.R; + +/** + * BlurDialogFragmentHelper.java + * + * @author Manabu-GT on 6/12/14. + */ +public class BlurDialogFragmentHelper { + + private final DialogFragment mFragment; + + private ViewGroup mRoot; + + private ViewGroup mBlurContainer; + + private View mBlurBgView; + + private ImageView mBlurImgView; + + private int mAnimDuration; + + private int mWindowAnimStyle; + + private int mBgColorResId; + + public BlurDialogFragmentHelper(@NonNull DialogFragment fragment) { + mFragment = fragment; + mAnimDuration = fragment.getActivity().getResources().getInteger(android.R.integer.config_mediumAnimTime); + mWindowAnimStyle = R.style.DialogSlideAnimation; + mBgColorResId = R.color.bg_glass; + } + + /** + * Duration of the alpha animation. + * @param animDuration The length of ensuing property animations, in milliseconds. + * The value cannot be negative. + */ + public void setAnimDuration(int animDuration) { + mAnimDuration = animDuration; + } + + public void setWindowAnimStyle(@StyleRes int windowAnimStyle) { + mWindowAnimStyle = windowAnimStyle; + } + + public void setBgColorResId(@ColorRes int bgColorResId) { + mBgColorResId = bgColorResId; + } + + public void onCreate() { + mFragment.setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Translucent_NoTitleBar); + } + + public void onActivityCreated() { + Window window = mFragment.getDialog().getWindow(); + window.setWindowAnimations(mWindowAnimStyle); + + mRoot = (ViewGroup) mFragment.getActivity().getWindow().getDecorView(); + Rect visibleFrame = new Rect(); + mRoot.getWindowVisibleDisplayFrame(visibleFrame); + + mBlurContainer = new FrameLayout(mFragment.getActivity()); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + params.topMargin = visibleFrame.top; + params.bottomMargin = mRoot.getHeight() - visibleFrame.bottom; + mBlurContainer.setLayoutParams(params); + + mBlurBgView = new View(mFragment.getActivity()); + mBlurBgView.setBackgroundColor(mFragment.getResources().getColor(mBgColorResId)); + Util.setAlpha(mBlurBgView, 0f); + + mBlurImgView = new ImageView(mFragment.getActivity()); + Util.setAlpha(mBlurImgView, 0f); + + mBlurContainer.addView(mBlurImgView); + mBlurContainer.addView(mBlurBgView); + + mRoot.addView(mBlurContainer); + + Bitmap bitmap = Util.drawViewToBitmap(mRoot, mRoot.getWidth(), + visibleFrame.bottom, 0, visibleFrame.top, 3); + Bitmap blurred = Blur.apply(mFragment.getActivity(), bitmap); + if (blurred == null) { + return; + } + mBlurImgView.setScaleType(ImageView.ScaleType.FIT_XY); + mBlurImgView.setImageBitmap(blurred); + bitmap.recycle(); + + View view = mFragment.getView(); + if (view != null) { + view.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + mFragment.dismiss(); + return true; + } + }); + } + } + + public void onStart() { + startEnterAnimation(); + } + + public void onDismiss() { + startExitAnimation(); + } + + private void startEnterAnimation() { + Util.animateAlpha(mBlurBgView, 0f, 1f, mAnimDuration, null); + Util.animateAlpha(mBlurImgView, 0f, 1f, mAnimDuration, null); + } + + private void startExitAnimation() { + Util.animateAlpha(mBlurBgView, 1f, 0f, mAnimDuration, null); + Util.animateAlpha(mBlurImgView, 1f, 0f, mAnimDuration, new Runnable() { + @Override + public void run() { + mRoot.removeView(mBlurContainer); + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/EtsyActionBarDrawerToggle.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/EtsyActionBarDrawerToggle.java new file mode 100644 index 0000000..f673585 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/EtsyActionBarDrawerToggle.java @@ -0,0 +1,107 @@ +package com.likebamboo.osa.android.ui.view.blur; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBarDrawerToggle; +import android.support.v7.widget.Toolbar; +import android.view.View; +import android.widget.ImageView; + +import com.likebamboo.osa.android.R; + +/** + * EtsyActionBarDrawerToggle.java + * + * @author Manabu-GT on 6/12/14. + */ +public class EtsyActionBarDrawerToggle extends ActionBarDrawerToggle { + + private static final int DEFAULT_RADIUS = 10; + private static final int DEFAULT_DOWN_SAMPLING = 3; + + private Activity mActivity; + + private View mContainer; + private ImageView mBlurImage; + + private int mBlurRadius; + private int mDownSampling; + + public EtsyActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout, + int openDrawerContentDescRes, int closeDrawerContentDescRes) { + super(activity, drawerLayout, openDrawerContentDescRes, closeDrawerContentDescRes); + init(activity); + } + + public EtsyActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout, Toolbar toolbar, + int openDrawerContentDescRes, int closeDrawerContentDescRes) { + super(activity, drawerLayout, toolbar, openDrawerContentDescRes, closeDrawerContentDescRes); + init(activity); + } + + public void setBlurImage() { + mBlurImage.setImageBitmap(null); + mBlurImage.setVisibility(View.VISIBLE); + // do the downscaling for faster processing + Bitmap downScaled = Util.drawViewToBitmap(mContainer, + mContainer.getWidth(), mContainer.getHeight(), mDownSampling); + if (downScaled == null) { + return; + } + // apply the blur using the renderscript + Bitmap blurred = Blur.apply(mActivity, downScaled, mBlurRadius); + if (blurred == null) { + return; + } + mBlurImage.setImageBitmap(blurred); + downScaled.recycle(); + } + + public void clearBlurImage() { + mBlurImage.setVisibility(View.GONE); + mBlurImage.setImageBitmap(null); + } + + public void setBlurRadius(int blurRadius) { + if (0 < blurRadius && blurRadius <= 25) { + mBlurRadius = blurRadius; + } + } + + public void setDownSampling(int downSampling) { + mDownSampling = downSampling; + } + + private void init(Activity activity) { + mActivity = activity; + mBlurRadius = DEFAULT_RADIUS; + mDownSampling = DEFAULT_DOWN_SAMPLING; + + mContainer = activity.findViewById(R.id.container); + mBlurImage = (ImageView) activity.findViewById(R.id.blur_view); + } + + private void setBlurAlpha(float slideOffset) { + if (mBlurImage.getVisibility() != View.VISIBLE) { + setBlurImage(); + } + Util.setAlpha(mBlurImage, slideOffset); + } + + @Override + public void onDrawerSlide(final View drawerView, final float slideOffset) { + super.onDrawerSlide(drawerView, slideOffset); + if (slideOffset > 0f) { + setBlurAlpha(slideOffset); + } else { + clearBlurImage(); + } + } + + @Override + public void onDrawerClosed(View drawerView) { + super.onDrawerClosed(drawerView); + clearBlurImage(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/OnBlurCompleteListener.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/OnBlurCompleteListener.java new file mode 100644 index 0000000..09bf86c --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/OnBlurCompleteListener.java @@ -0,0 +1,8 @@ +package com.likebamboo.osa.android.ui.view.blur; + +/** + * Created by wentaoli on 2015/5/18. + */ +public interface OnBlurCompleteListener { + void onBlurComplete(); +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/Util.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/Util.java new file mode 100644 index 0000000..6ccc9c8 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/Util.java @@ -0,0 +1,94 @@ +package com.likebamboo.osa.android.ui.view.blur; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.annotation.TargetApi; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.os.Build; +import android.os.Handler; +import android.view.View; +import android.view.ViewPropertyAnimator; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; + +/** + * READ ME + * Util.java + * + * @author Manabu-GT on 6/12/14. + */ +public class Util { + + public static Bitmap drawViewToBitmap(View view, int width, int height, int downSampling) { + return drawViewToBitmap(view, width, height, 0f, 0f, downSampling); + } + + public static Bitmap drawViewToBitmap(View view, int width, int height, float translateX, + float translateY, int downSampling) { + if (width * height == 0) { + return null; + } + float scale = 1f / downSampling; + int bmpWidth = (int) (width * scale - translateX / downSampling); + int bmpHeight = (int) (height * scale - translateY / downSampling); + Bitmap dest = Bitmap.createBitmap(bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(dest); + c.translate(-translateX / downSampling, -translateY / downSampling); + if (downSampling > 1) { + c.scale(scale, scale); + } + view.draw(c); + return dest; + } + + public static boolean isPostHoneycomb() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1; + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) + public static void setAlpha(View view, float alpha) { + if (isPostHoneycomb()) { + view.setAlpha(alpha); + } else { + AlphaAnimation alphaAnimation = new AlphaAnimation(alpha, alpha); + // make it instant + alphaAnimation.setDuration(0); + alphaAnimation.setFillAfter(true); + view.startAnimation(alphaAnimation); + } + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) + public static void animateAlpha(final View view, float fromAlpha, float toAlpha, int duration, final Runnable endAction) { + if (isPostHoneycomb()) { + ViewPropertyAnimator animator = view.animate().alpha(toAlpha).setDuration(duration); + if (endAction != null) { + animator.setListener(new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animation) { + endAction.run(); + } + }); + } + } else { + AlphaAnimation alphaAnimation = new AlphaAnimation(fromAlpha, toAlpha); + alphaAnimation.setDuration(duration); + alphaAnimation.setFillAfter(true); + if (endAction != null) { + alphaAnimation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationEnd(Animation animation) { + // fixes the crash bug while removing views + Handler handler = new Handler(); + handler.post(endAction); + } + @Override + public void onAnimationStart(Animation animation) { } + @Override + public void onAnimationRepeat(Animation animation) { } + }); + } + view.startAnimation(alphaAnimation); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/fa/ButtonAwesome.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/fa/ButtonAwesome.java new file mode 100644 index 0000000..59014e0 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/fa/ButtonAwesome.java @@ -0,0 +1,45 @@ +package com.likebamboo.osa.android.ui.view.fa; + +import android.content.Context; +import android.graphics.Typeface; +import android.support.v4.util.LruCache; +import android.util.AttributeSet; +import android.widget.Button; + +/** + * ButtonAwesome + * + * @see FontAwesomeAndroid + */ +public class ButtonAwesome extends Button { + private final static String NAME = "FONTAWESOME"; + private static LruCache sTypefaceCache = new LruCache(12); + + public ButtonAwesome(Context context) { + super(context); + init(); + + } + + public ButtonAwesome(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public ButtonAwesome(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + public synchronized void init() { + Typeface typeface = sTypefaceCache.get(NAME); + if (typeface == null) { + typeface = Typeface.createFromAsset(getContext().getAssets(), "fontawesome-webfont.ttf"); + sTypefaceCache.put(NAME, typeface); + } + + setTypeface(typeface); + } +} + + diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/fa/DrawableAwesome.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/fa/DrawableAwesome.java new file mode 100644 index 0000000..d7c66fc --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/fa/DrawableAwesome.java @@ -0,0 +1,148 @@ +package com.likebamboo.osa.android.ui.view.fa; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.util.DisplayMetrics; + +/** + * DrawableAwesome + * + * @see FontAwesomeAndroid + */ +public class DrawableAwesome extends Drawable { + + private static final float PADDING_RATIO = 0.88f; + + private final Context context; + private final int icon; + private final Paint paint; + private final int width; + private final int height; + private final float size; + private final int color; + private final boolean antiAliased; + private final boolean fakeBold; + private final float shadowRadius; + private final float shadowDx; + private final float shadowDy; + private final int shadowColor; + + public DrawableAwesome(int icon, int sizeDpi, int color, + boolean antiAliased, boolean fakeBold, float shadowRadius, + float shadowDx, float shadowDy, int shadowColor, Context context) { + super(); + this.context = context; + this.icon = icon; + this.size = dpToPx(sizeDpi) * PADDING_RATIO; + this.height = dpToPx(sizeDpi); + this.width = dpToPx(sizeDpi); + this.color = color; + this.antiAliased = antiAliased; + this.fakeBold = fakeBold; + this.shadowRadius = shadowRadius; + this.shadowDx = shadowDx; + this.shadowDy = shadowDy; + this.shadowColor = shadowColor; + this.paint = new Paint(); + + paint.setStyle(Paint.Style.FILL); + paint.setTextAlign(Paint.Align.CENTER); + this.paint.setColor(this.color); + this.paint.setTextSize(this.size); + Typeface font = Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf"); + this.paint.setTypeface(font); + this.paint.setAntiAlias(this.antiAliased); + this.paint.setFakeBoldText(this.fakeBold); + this.paint.setShadowLayer(this.shadowRadius, this.shadowDx, this.shadowDy, this.shadowColor); + } + + @Override + public int getIntrinsicHeight() { + return height; + } + + @Override + public int getIntrinsicWidth() { + return width; + } + + @Override + public void draw(Canvas canvas) { + float xDiff = (width / 2.0f); + String stringIcon = this.context.getResources().getString(icon); + canvas.drawText(stringIcon, xDiff, size, paint); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + @Override + public void setAlpha(int alpha) { + paint.setAlpha(alpha); + } + + @Override + public void setColorFilter(ColorFilter cf) { + paint.setColorFilter(cf); + } + + private int dpToPx(int dp) { + DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); + int px = Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT)); + return px; + } + + public static class DrawableAwesomeBuilder { + private Context context; + private int icon; + private int sizeDpi = 32; + private int color = Color.GRAY; + private boolean antiAliased = true; + private boolean fakeBold = true; + private float shadowRadius = 0; + private float shadowDx = 0; + private float shadowDy = 0; + private int shadowColor = Color.WHITE; + + public DrawableAwesomeBuilder(Context context, int icon) { + this.context = context; + this.icon = icon; + } + + public void setSize(int size) { + this.sizeDpi = size; + } + + public void setColor(int color) { + this.color = color; + } + + public void setAntiAliased(boolean antiAliased) { + this.antiAliased = antiAliased; + } + + public void setFakeBold(boolean fakeBold) { + this.fakeBold = fakeBold; + } + + public void setShadow(float radius, float dx, float dy, int color) { + this.shadowRadius = radius; + this.shadowDx = dx; + this.shadowDy = dy; + this.shadowColor = color; + } + + public DrawableAwesome build() { + return new DrawableAwesome(icon, sizeDpi, color, antiAliased, fakeBold, + shadowRadius, shadowDx, shadowDy, shadowColor, context); + } + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/fa/TextAwesome.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/fa/TextAwesome.java new file mode 100644 index 0000000..08b0d57 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/fa/TextAwesome.java @@ -0,0 +1,51 @@ +package com.likebamboo.osa.android.ui.view.fa; + +import android.content.Context; +import android.graphics.Typeface; +import android.support.v4.util.LruCache; +import android.util.AttributeSet; +import android.widget.TextView; + +/** + * DrawableAwesome + * + * @see FontAwesomeAndroid + */ +public class TextAwesome extends TextView { + + private final static String NAME = "FONTAWESOME"; + private static LruCache sTypefaceCache = new LruCache(12); + + public TextAwesome(Context context) { + super(context); + init(); + } + + public TextAwesome(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public synchronized void init() { + Typeface typeface = sTypefaceCache.get(NAME); + if (typeface == null) { + typeface = Typeface.createFromAsset(getContext().getAssets(), "fontawesome-webfont.ttf"); + sTypefaceCache.put(NAME, typeface); + } + setTypeface(typeface); + } + + /** + * 设置文字 + * + * @param icon + * @param text + */ + public void setText(int icon, String text) { + String iconStr = getResources().getString(icon); + setText(iconStr + " " + text); + } + +} + + diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/fab/DirectionScrollListener.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/fab/DirectionScrollListener.java new file mode 100644 index 0000000..de978f9 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/fab/DirectionScrollListener.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2014 SBG Apps + * + * 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. + */ + +package com.likebamboo.osa.android.ui.view.fab; + +import android.view.View; +import android.widget.AbsListView; + +/** + * Created by Stéphane on 09/07/2014. + */ +class DirectionScrollListener implements AbsListView.OnScrollListener { + + private static final int DIRECTION_CHANGE_THRESHOLD = 1; + private final FloatingActionButton mFloatingActionButton; + private int mPrevPosition; + private int mPrevTop; + private boolean mUpdated; + + DirectionScrollListener(FloatingActionButton floatingActionButton) { + mFloatingActionButton = floatingActionButton; + } + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { + final View topChild = view.getChildAt(0); + int firstViewTop = 0; + if (topChild != null) { + firstViewTop = topChild.getTop(); + } + boolean goingDown; + boolean changed = true; + if (mPrevPosition == firstVisibleItem) { + final int topDelta = mPrevTop - firstViewTop; + goingDown = firstViewTop < mPrevTop; + changed = Math.abs(topDelta) > DIRECTION_CHANGE_THRESHOLD; + } else { + goingDown = firstVisibleItem > mPrevPosition; + } + if (changed && mUpdated) { + mFloatingActionButton.hide(goingDown); + } + mPrevPosition = firstVisibleItem; + mPrevTop = firstViewTop; + mUpdated = true; + } + + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + } +} \ No newline at end of file diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/fab/FabToolbar.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/fab/FabToolbar.java new file mode 100644 index 0000000..e4c2997 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/fab/FabToolbar.java @@ -0,0 +1,269 @@ +package com.likebamboo.osa.android.ui.view.fab; + +import android.content.Context; +import android.content.res.TypedArray; +import android.support.annotation.NonNull; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.widget.LinearLayout; + +import com.likebamboo.osa.android.R; +import com.likebamboo.osa.android.ui.view.ObservedWebView; + +import butterknife.ButterKnife; +import butterknife.InjectView; +import io.codetail.animation.SupportAnimator; +import io.codetail.animation.ViewAnimationUtils; +import io.codetail.widget.RevealFrameLayout; + +/** + * fab 对应的工具栏 + */ +public class FabToolbar extends RevealFrameLayout { + /** + * 内容区 + */ + @InjectView(R.id.container) + LinearLayout mContainer = null; + + @InjectView(R.id.fabbutton) + FloatingActionButton mFAB = null; + + /** + * 屏幕宽度 + */ + private float mScreenWidth = 0; + /** + * 动画执行时间 + */ + private int mDuration = 500; + + /** + * 是否处于动画执行状态 + */ + private boolean isAniming = false; + + public FabToolbar(Context context) { + this(context, null); + } + + public FabToolbar(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public FabToolbar(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + if (attrs != null) { + loadAttributes(attrs); + } + } + + + /** + * 初始化 + */ + private void init() { + mScreenWidth = getResources().getDisplayMetrics().widthPixels; + View v = inflate(getContext(), R.layout.fab_tool_bar, this); + ButterKnife.inject(this, v); + mContainer.setVisibility(View.GONE); + + mFAB.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + // 正执行动画 + if (isAniming) { + return; + } + + // 如果是在展示状态 + if (mContainer.getVisibility() == View.VISIBLE) { + hideMenu(); + return; + } + showMenu(); + } + }); + } + + /** + * 加载布局属性 + * + * @param attrs + */ + private void loadAttributes(AttributeSet attrs) { + TypedArray a = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.FabToolbar, 0, 0); + + try { + mContainer.setBackgroundColor(a.getColor(R.styleable.FabToolbar_tb_color, getResources().getColor(R.color.bg_drawer))); + mDuration = a.getInteger(R.styleable.FabToolbar_tb_anim_duration, 500); + } finally { + a.recycle(); + } + } + + /** + * @param startRadius + * @param endRadius + * @param listener + */ + private void animateCircle(float startRadius, float endRadius, SupportAnimator.AnimatorListener listener) { + int cX = (mFAB.getLeft() + mFAB.getRight()) / 2; + int cY = (mFAB.getTop() + mFAB.getBottom()) / 2; + SupportAnimator animator = ViewAnimationUtils.createCircularReveal(mContainer, cX, cY, startRadius, endRadius); + animator.setInterpolator(new AccelerateDecelerateInterpolator()); + animator.setDuration(mDuration); + if (listener != null) { + animator.addListener(listener); + } + animator.start(); + } + + @Override + public void addView(@NonNull View child) { + if (canAddViewToContainer(child)) { + mContainer.addView(child); + } else { + super.addView(child); + } + } + + @Override + public void addView(@NonNull View child, int width, int height) { + if (canAddViewToContainer(child)) { + mContainer.addView(child, width, height); + } else { + super.addView(child, width, height); + } + } + + @Override + public void addView(@NonNull View child, ViewGroup.LayoutParams params) { + if (canAddViewToContainer(child)) { + mContainer.addView(child, params); + } else { + super.addView(child, params); + } + } + + @Override + public void addView(@NonNull View child, int index, ViewGroup.LayoutParams params) { + if (canAddViewToContainer(child)) { + mContainer.addView(child, index, params); + } else { + super.addView(child, index, params); + } + } + + /** + * 判断View是否可以添加 + * + * @param child + * @return + */ + private boolean canAddViewToContainer(View child) { + return mContainer != null && !(child instanceof FloatingActionButton); + } + + private class ToolbarCollapseListener implements SupportAnimator.AnimatorListener { + + private boolean show = false; + + public ToolbarCollapseListener(boolean show) { + this.show = show; + } + + @Override + public void onAnimationEnd() { + if (show) { + mContainer.setVisibility(VISIBLE); + } else { + mContainer.setVisibility(GONE); + } + isAniming = false; + } + + @Override + public void onAnimationStart() { + isAniming = true; + } + + @Override + public void onAnimationCancel() { + } + + @Override + public void onAnimationRepeat() { + } + } + + + //==========api method================== + + /** + * 显示toolbar + */ + public void showMenu() { + mContainer.setVisibility(VISIBLE); + animateCircle(0, mScreenWidth, new ToolbarCollapseListener(true)); + } + + /** + * 隐藏toolbar + */ + public void hideMenu() { + animateCircle(mScreenWidth, 0, new ToolbarCollapseListener(false)); + } + + /** + * 设置动画执行事件 + * + * @param duration + */ + public void setAnimationDuration(int duration) { + mDuration = duration; + } + + /** + * 将fabtoolbar attach到webView上 + * + * @param webView + */ + public void attachTo(ObservedWebView webView) { + webView.setOnScrollChangedCallback(new DirectionWebViweScrollListener()); + } + + /** + * 滚动监听 + */ + public class DirectionWebViweScrollListener implements ObservedWebView.OnScrollChangedCallback { + + private static final int DIRECTION_CHANGE_THRESHOLD = 8; + private int mPrevTop; + private boolean mUpdated; + + @Override + public void onScroll(int l, int t) { + if (isAniming) { + return; + } + if (mContainer.getVisibility() == View.VISIBLE) { + hideMenu(); + return; + } + if (mFAB == null) { + return; + } + boolean goingDown = t > mPrevTop; + boolean changed = Math.abs(t - mPrevTop) > DIRECTION_CHANGE_THRESHOLD; + if (changed && mUpdated) { + mFAB.hide(goingDown); + } + mPrevTop = t; + mUpdated = true; + } + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/fab/FloatingActionButton.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/fab/FloatingActionButton.java new file mode 100644 index 0000000..a841d86 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/fab/FloatingActionButton.java @@ -0,0 +1,183 @@ +package com.likebamboo.osa.android.ui.view.fab; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.AttributeSet; +import android.view.Display; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.Interpolator; +import android.widget.AbsListView; + +import com.likebamboo.osa.android.R; +import com.nineoldandroids.animation.ObjectAnimator; +import com.nineoldandroids.view.ViewHelper; + +/** + * READ ME + * fab + */ +public class FloatingActionButton extends View { + + private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator(); + private final Paint mButtonPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint mDrawablePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private Bitmap mBitmap; + private int mColor; + private boolean mHidden = false; + private Rect rect; + private int mLeftDisplayed = -1; + private int mRightDisplayed = -1; + private int mTopDisplayed = -1; + private int mBottomDisplayed = -1; + /** + * The FAB button's Y position when it is displayed. + */ + private float mYDisplayed = -1; + /** + * The FAB button's Y position when it is hidden. + */ + private float mYHidden = -1; + + public FloatingActionButton(Context context) { + this(context, null); + } + + public FloatingActionButton(Context context, AttributeSet attributeSet) { + this(context, attributeSet, 0); + } + + + public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.FloatingActionButton); + mColor = a.getColor(R.styleable.FloatingActionButton_fab_color, Color.WHITE); + mButtonPaint.setStyle(Paint.Style.FILL); + mButtonPaint.setColor(mColor); + float radius, dx, dy; + radius = a.getFloat(R.styleable.FloatingActionButton_fab_shadowRadius, 10.0f); + dx = a.getFloat(R.styleable.FloatingActionButton_fab_shadowDx, 0.0f); + dy = a.getFloat(R.styleable.FloatingActionButton_fab_shadowDy, 3.5f); + int color = a.getInteger(R.styleable.FloatingActionButton_fab_shadowColor, Color.argb(100, 0, 0, 0)); + mButtonPaint.setShadowLayer(radius, dx, dy, color); + + Drawable drawable = a.getDrawable(R.styleable.FloatingActionButton_fab_drawable); + if (null != drawable) { + mBitmap = ((BitmapDrawable) drawable).getBitmap(); + } + setWillNotDraw(false); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + setLayerType(View.LAYER_TYPE_SOFTWARE, null); + } + + WindowManager mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + Display display = mWindowManager.getDefaultDisplay(); + Point size = new Point(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { + display.getSize(size); + mYHidden = size.y; + } else { + mYHidden = display.getHeight(); + } + } + + public static int darkenColor(int color) { + float[] hsv = new float[3]; + Color.colorToHSV(color, hsv); + hsv[2] *= 0.8f; + return Color.HSVToColor(hsv); + } + + public void setColor(int color) { + mColor = color; + mButtonPaint.setColor(mColor); + invalidate(); + } + + public void setDrawable(Drawable drawable) { + mBitmap = ((BitmapDrawable) drawable).getBitmap(); + invalidate(); + } + + public void setShadow(float radius, float dx, float dy, int color) { + mButtonPaint.setShadowLayer(radius, dx, dy, color); + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + canvas.drawCircle(getWidth() / 2, getHeight() / 2, (float) (getWidth() / 2.6), mButtonPaint); + if (null != mBitmap) { + canvas.drawBitmap(mBitmap, (getWidth() - mBitmap.getWidth()) / 2, + (getHeight() - mBitmap.getHeight()) / 2, mDrawablePaint); + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + // Perform the default behavior + super.onLayout(changed, left, top, right, bottom); + if (mLeftDisplayed == -1) { + mLeftDisplayed = left; + mRightDisplayed = right; + mTopDisplayed = top; + mBottomDisplayed = bottom; + } + + // Store the FAB button's displayed Y position if we are not already aware of it + if (mYDisplayed == -1) { + mYDisplayed = ViewHelper.getY(this); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + int color; + if (event.getAction() == MotionEvent.ACTION_UP) { + color = mColor; + } else { + color = darkenColor(mColor); + rect = new Rect(mLeftDisplayed, mTopDisplayed, mRightDisplayed, mBottomDisplayed); + } + if (event.getAction() == MotionEvent.ACTION_MOVE) { + if (!rect.contains(mLeftDisplayed + (int) event.getX(), mTopDisplayed + (int) event.getY())) { + color = mColor; + } + } + mButtonPaint.setColor(color); + invalidate(); + return super.onTouchEvent(event); + } + + public void hide(boolean hide) { + // If the hidden state is being updated + if (mHidden != hide) { + // Store the new hidden state + mHidden = hide; + + // Animate the FAB to it's new Y position + ObjectAnimator animator = ObjectAnimator.ofFloat(this, "y", mHidden ? mYHidden : mYDisplayed).setDuration(500); + animator.setInterpolator(mInterpolator); + animator.start(); + } + } + + public void listenTo(AbsListView listView) { + if (null != listView) { + listView.setOnScrollListener(new DirectionScrollListener(this)); + } + } + +} diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/fastscroll/BubbleTextGetter.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/fastscroll/BubbleTextGetter.java new file mode 100644 index 0000000..cb0b06e --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/fastscroll/BubbleTextGetter.java @@ -0,0 +1,8 @@ +package com.likebamboo.osa.android.ui.view.fastscroll; + +/** + * + */ +public interface BubbleTextGetter { + String getTextToShowInBubble(int pos); +} \ No newline at end of file diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/fastscroll/FastScroller.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/fastscroll/FastScroller.java new file mode 100644 index 0000000..465817c --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/fastscroll/FastScroller.java @@ -0,0 +1,192 @@ +package com.likebamboo.osa.android.ui.view.fastscroll; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.likebamboo.osa.android.R; + +import static android.support.v7.widget.RecyclerView.OnScrollListener; + +/** + * + */ +@TargetApi(Build.VERSION_CODES.HONEYCOMB) +public class FastScroller extends LinearLayout { + private static final int BUBBLE_ANIMATION_DURATION = 500; + private static final int TRACK_SNAP_RANGE = 5; + + private TextView bubble; + private View handle; + private RecyclerView recyclerView; + private final ScrollListener scrollListener = new ScrollListener(); + private int height; + + private boolean isMoving = false; + + private ObjectAnimator currentAnimator = null; + + public FastScroller(final Context context, final AttributeSet attrs, final int defStyleAttr) { + super(context, attrs, defStyleAttr); + initialise(context); + } + + public FastScroller(final Context context) { + super(context); + initialise(context); + } + + public FastScroller(final Context context, final AttributeSet attrs) { + super(context, attrs); + initialise(context); + } + + private void initialise(Context context) { + setOrientation(HORIZONTAL); + setClipChildren(false); + LayoutInflater inflater = LayoutInflater.from(context); + inflater.inflate(R.layout.recycler_view_fast_scroller, this, true); + bubble = (TextView) findViewById(R.id.fastscroller_bubble); + handle = findViewById(R.id.fastscroller_handle); + bubble.setVisibility(INVISIBLE); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + height = h; + } + + @Override + public boolean onTouchEvent(@NonNull MotionEvent event) { + final int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_DOWN: + isMoving = false; + if (event.getX() < handle.getX()) + return false; + if (currentAnimator != null) + currentAnimator.cancel(); + if (bubble.getVisibility() == INVISIBLE) + showBubble(); + handle.setSelected(true); + case MotionEvent.ACTION_MOVE: + isMoving = true; + final float y = event.getY(); + setBubbleAndHandlePosition(y); + setRecyclerViewPosition(y); + return true; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + isMoving = false; + handle.setSelected(false); + hideBubble(); + return true; + } + return super.onTouchEvent(event); + } + + public void setRecyclerView(RecyclerView recyclerView) { + this.recyclerView = recyclerView; + recyclerView.addOnScrollListener(scrollListener); + } + + private void setRecyclerViewPosition(float y) { + if (recyclerView != null) { + int itemCount = recyclerView.getAdapter().getItemCount(); + float proportion; + if (handle.getY() == 0) + proportion = 0f; + else if (handle.getY() + handle.getHeight() >= height - TRACK_SNAP_RANGE) + proportion = 1f; + else + proportion = y / (float) height; + int targetPos = getValueInRange(0, itemCount - 1, (int) (proportion * (float) itemCount)); + Log.d("AppLog", "targetPos:" + targetPos); + ((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(targetPos, 0); + // recyclerView.oPositionWithOffset(targetPos); + String bubbleText = ((BubbleTextGetter) recyclerView.getAdapter()).getTextToShowInBubble(targetPos); + bubble.setText(bubbleText); + } + } + + private int getValueInRange(int min, int max, int value) { + int minimum = Math.max(min, value); + return Math.min(minimum, max); + } + + private void setBubbleAndHandlePosition(float y) { + int bubbleHeight = bubble.getHeight(); + int handleHeight = handle.getHeight(); + handle.setY(getValueInRange(0, height - handleHeight, (int) (y - handleHeight / 2))); + bubble.setY(getValueInRange(0, height - bubbleHeight - handleHeight / 2, (int) (y - bubbleHeight))); + } + + private void showBubble() { + AnimatorSet animatorSet = new AnimatorSet(); + bubble.setVisibility(VISIBLE); + if (currentAnimator != null) + currentAnimator.cancel(); + currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 0f, 1f).setDuration(BUBBLE_ANIMATION_DURATION); + currentAnimator.start(); + } + + private void hideBubble() { + if (currentAnimator != null) + currentAnimator.cancel(); + currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 1f, 0f).setDuration(BUBBLE_ANIMATION_DURATION); + currentAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + bubble.setVisibility(INVISIBLE); + currentAnimator = null; + } + + @Override + public void onAnimationCancel(Animator animation) { + super.onAnimationCancel(animation); + bubble.setVisibility(INVISIBLE); + currentAnimator = null; + } + }); + currentAnimator.start(); + } + + private class ScrollListener extends OnScrollListener { + @Override + public void onScrolled(RecyclerView rv, int dx, int dy) { + if (isMoving) { + return; + } + View firstVisibleView = recyclerView.getChildAt(0); + int firstVisiblePosition = recyclerView.getChildPosition(firstVisibleView); + int visibleRange = recyclerView.getChildCount(); + int lastVisiblePosition = firstVisiblePosition + visibleRange; + int itemCount = recyclerView.getAdapter().getItemCount(); + int position; + if (firstVisiblePosition == 0) + position = 0; + else if (lastVisiblePosition == itemCount) + position = itemCount; + else + position = (int) (((float) firstVisiblePosition / (((float) itemCount - (float) visibleRange))) * (float) itemCount); + float proportion = (float) position / (float) itemCount; + setBubbleAndHandlePosition(height * proportion); + } + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/utils/DateUtil.java b/app/src/main/java/com/likebamboo/osa/android/utils/DateUtil.java new file mode 100644 index 0000000..265100a --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/utils/DateUtil.java @@ -0,0 +1,76 @@ +package com.likebamboo.osa.android.utils; + +import android.text.TextUtils; + +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * + */ +public class DateUtil { + + /** + * 格式化服务器端给的时间字符串 + * + * @param timeStr + * @return + */ + public static String parseDate(String timeStr) { + return parseDate(timeStr, "yyyy-MM-dd"); + } + + /** + * 将时间字符串转为本地显示的时间形式 + * + * @param timeStr + * @param pattern + * @return + */ + public static String parseDate(String timeStr, String pattern) { + if (TextUtils.isEmpty(timeStr) || TextUtils.isEmpty(pattern)) { + return ""; + } + + try { + SimpleDateFormat sdf = new SimpleDateFormat(pattern); + // 将字符串转为日期 + Date d = sdf.parse(timeStr); + return parseDate(d.getTime()); + } catch (Exception e) { + e.printStackTrace(); + } + return timeStr; + } + + /** + * 将时间戳转为本地显示的时间形式 + * + * @param timeStamp + * @return + */ + public static String parseDate(long timeStamp) { + long now = System.currentTimeMillis(); + SimpleDateFormat sdf = null; + try { + // 将字符串转为日期 + Date d = new Date(timeStamp); + Date dd = new Date(); + // 如果是同一天 + if ((now - timeStamp < ONE_DAY) && (d.getDate() == dd.getDate())) { + // sdf = new SimpleDateFormat("HH:mm"); + return "今天"; + } else if (dd.getYear() != d.getYear()) {// 如果是不同年份 + sdf = new SimpleDateFormat("yyyy-MM-dd"); + } else { + sdf = new SimpleDateFormat("MM-dd"); + } + return sdf.format(d); + } catch (Exception e) { + e.printStackTrace(); + } + return ""; + } + + public static final long ONE_DAY = 24 * 60 * 60 * 1000L; +} diff --git a/app/src/main/java/com/likebamboo/osa/android/utils/DeviceUtil.java b/app/src/main/java/com/likebamboo/osa/android/utils/DeviceUtil.java new file mode 100644 index 0000000..4dc5f0c --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/utils/DeviceUtil.java @@ -0,0 +1,53 @@ +package com.likebamboo.osa.android.utils; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; + +/** + * Created by likebamboo on 2015/5/30. + */ +public class DeviceUtil { + + /** + * 获取软件版本名称 + * + * @param ctx + * @return + */ + public static String getVersionName(Context ctx) { + if (ctx == null) { + return ""; + } + + try { + PackageManager pm = ctx.getPackageManager(); + PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), 0); + return pi.versionName; + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return ""; + } + + /** + * 获取软件版本号 + * + * @param ctx + * @return + */ + public static int getVersionCode(Context ctx) { + if (ctx == null) { + return 0; + } + + try { + PackageManager pm = ctx.getPackageManager(); + PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), 0); + return pi.versionCode; + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return 0; + } +} diff --git a/app/src/main/java/com/likebamboo/osa/android/utils/NetworkUtil.java b/app/src/main/java/com/likebamboo/osa/android/utils/NetworkUtil.java new file mode 100644 index 0000000..f858440 --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/utils/NetworkUtil.java @@ -0,0 +1,82 @@ + +package com.likebamboo.osa.android.utils; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.util.Log; + +/** + * @author likebamboo + * @date 2015年5月17日 + */ +public class NetworkUtil { + + /** + * Returns whether the network is available + * + * @param context Context + * @return 网络是否可用 + * @see [类、类#方法、类#成员] + */ + public static boolean isNetworkAvailable(Context context) { + return getConnectedNetworkInfo(context) != null; + } + + /** + * 获取网络类型 + * + * @param context Context + * @return 网络类型 + * @see [类、类#方法、类#成员] + */ + public static int getNetworkType(Context context) { + NetworkInfo networkInfo = getConnectedNetworkInfo(context); + if (networkInfo != null) { + return networkInfo.getType(); + } + + return -1; + } + + public static NetworkInfo getConnectedNetworkInfo(Context context) { + try { + ConnectivityManager connectivity = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivity == null) { + Log.e("network", "couldn't get connectivity manager"); + } else { + NetworkInfo info = connectivity.getActiveNetworkInfo(); + if (info != null) { + return info; + } + } + } catch (Exception e) { + Log.e("network", e.toString(), e); + } + return null; + } + + /** + * 判断网络是不是手机网络,非wifi + * + * @param context Context + * @return boolean + * @see [类、类#方法、类#成员] + */ + public static boolean isMobileNetwork(Context context) { + return (ConnectivityManager.TYPE_MOBILE == getNetworkType(context)); + } + + /** + * 判断网络是不是wifi + * + * @param context Context + * @return boolean + * @see [类、类#方法、类#成员] + */ + public static boolean isWifiNetwork(Context context) { + return (ConnectivityManager.TYPE_WIFI == getNetworkType(context)); + } + +} diff --git a/app/src/main/java/com/likebamboo/osa/android/utils/UrlDetect.java b/app/src/main/java/com/likebamboo/osa/android/utils/UrlDetect.java new file mode 100644 index 0000000..b17bf7a --- /dev/null +++ b/app/src/main/java/com/likebamboo/osa/android/utils/UrlDetect.java @@ -0,0 +1,111 @@ +package com.likebamboo.osa.android.utils; + +import android.text.TextUtils; +import android.webkit.URLUtil; + +import com.likebamboo.osa.android.request.RequestUrl; + +/** + * URL 检测 + * Created by wentaoli on 2015/5/27. + */ +public class UrlDetect { + + /** + * 是否为正确的url + * + * @param url + * @return + */ + public static boolean isValidURL(String url) { + if (TextUtils.isEmpty(url)) { + return false; + } + return URLUtil.isValidUrl(url); + } + + /** + * 是否为本站URL + * + * @param url + */ + public static boolean isOurselvesURL(String url) { + if (!isValidURL(url)) { + return false; + } + if (!url.startsWith("http")) { + url = "http://" + url; + } + System.out.println(url); + if (url.startsWith(RequestUrl.BASE_URL)) { + return true; + } + return false; + } + + /** + * 是否是博客链接 + * + * @param url + * @return + */ + public static String isBlogUrl(String url) { + if (!isOurselvesURL(url)) { + return ""; + } + if (!url.startsWith("http")) { + url = "http://" + url; + } + // 查看该链接是否以博客url开头 + if (!url.startsWith(RequestUrl.BLOG_URL)) { + return ""; + } + // 去掉链接前面的内容 + url = url.substring((RequestUrl.BLOG_URL + "/").length()); + // 如果处理后的 url 不含"/",或者只有最后一个字符是"/",说明是blog链接 + if (!url.contains("/") || url.indexOf("/") == url.length() - 1) { + if (url.endsWith("/")) { + url = url.substring(0, url.length() - 1); + } + return url; + } + return ""; + } + + + /** + * 是否是标签blog列表链接 + * + * @param url + * @return + */ + public static String isTagBlogUrl(String url) { + if (!isOurselvesURL(url)) { + return ""; + } + if (!url.startsWith("http")) { + url = "http://" + url; + } + // 查看该链接是否以博客url开头 + if (!url.startsWith(RequestUrl.BLOG_URL)) { + return ""; + } + // 去掉链接前面的内容 + url = url.substring((RequestUrl.BLOG_URL + "/").length()); + // 如果 url 不是以 "tag/" 开头 + if (!url.toLowerCase().startsWith("tag/")) { + return ""; + } + // 截取 "tag/" 后边的内容 + url = url.substring(4); + // 如果处理后的 url 不含"/",或者只有最后一个字符是"/",说明是 tag blog链接 + if (!url.contains("/") || url.indexOf("/") == url.length() - 1) { + if (url.endsWith("/")) { + url = url.substring(0, url.length() - 1); + } + return url; + } + return ""; + } + +} diff --git a/app/src/main/res/anim/fade_in.xml b/app/src/main/res/anim/fade_in.xml new file mode 100644 index 0000000..26d3d2f --- /dev/null +++ b/app/src/main/res/anim/fade_in.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/fade_out.xml b/app/src/main/res/anim/fade_out.xml new file mode 100644 index 0000000..2811a30 --- /dev/null +++ b/app/src/main/res/anim/fade_out.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_down_dialog.xml b/app/src/main/res/anim/slide_down_dialog.xml new file mode 100644 index 0000000..7e78c0c --- /dev/null +++ b/app/src/main/res/anim/slide_down_dialog.xml @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_up_dialog.xml b/app/src/main/res/anim/slide_up_dialog.xml new file mode 100644 index 0000000..584b591 --- /dev/null +++ b/app/src/main/res/anim/slide_up_dialog.xml @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/bg_card.9.png b/app/src/main/res/drawable-hdpi/bg_card.9.png new file mode 100644 index 0000000..5986e50 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/bg_card.9.png differ diff --git a/app/src/main/res/drawable-hdpi/bg_card_active.9.png b/app/src/main/res/drawable-hdpi/bg_card_active.9.png new file mode 100644 index 0000000..3221b9a Binary files /dev/null and b/app/src/main/res/drawable-hdpi/bg_card_active.9.png differ diff --git a/app/src/main/res/drawable-hdpi/default_avatar.png b/app/src/main/res/drawable-hdpi/default_avatar.png new file mode 100644 index 0000000..7501748 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/default_avatar.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_add_white.png b/app/src/main/res/drawable-hdpi/ic_add_white.png new file mode 100644 index 0000000..481643e Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_add_white.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_launcher.png b/app/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..e208081 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_up.png b/app/src/main/res/drawable-hdpi/ic_up.png new file mode 100644 index 0000000..817cea9 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_up.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_launcher.png b/app/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..5499aca Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_launcher.png b/app/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 0000000..ec84e34 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_up.png b/app/src/main/res/drawable-xhdpi/ic_up.png new file mode 100644 index 0000000..b1b6793 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_up.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/app/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..e9b39a1 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable/bg_card_selector.xml b/app/src/main/res/drawable/bg_card_selector.xml new file mode 100644 index 0000000..a248210 --- /dev/null +++ b/app/src/main/res/drawable/bg_card_selector.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_list_item_selector.xml b/app/src/main/res/drawable/bg_list_item_selector.xml new file mode 100644 index 0000000..c8a926e --- /dev/null +++ b/app/src/main/res/drawable/bg_list_item_selector.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_tag_selector.xml b/app/src/main/res/drawable/bg_tag_selector.xml new file mode 100644 index 0000000..dc4f31e --- /dev/null +++ b/app/src/main/res/drawable/bg_tag_selector.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_translucent_gradient.xml b/app/src/main/res/drawable/bg_translucent_gradient.xml new file mode 100644 index 0000000..bd57b9d --- /dev/null +++ b/app/src/main/res/drawable/bg_translucent_gradient.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/recycler_view_fast_scroller__bubble.xml b/app/src/main/res/drawable/recycler_view_fast_scroller__bubble.xml new file mode 100644 index 0000000..0d6f2be --- /dev/null +++ b/app/src/main/res/drawable/recycler_view_fast_scroller__bubble.xml @@ -0,0 +1,15 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/recycler_view_fast_scroller__handle.xml b/app/src/main/res/drawable/recycler_view_fast_scroller__handle.xml new file mode 100644 index 0000000..d802119 --- /dev/null +++ b/app/src/main/res/drawable/recycler_view_fast_scroller__handle.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml new file mode 100644 index 0000000..5c58b86 --- /dev/null +++ b/app/src/main/res/layout/activity_about.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_author.xml b/app/src/main/res/layout/activity_author.xml new file mode 100644 index 0000000..e2d8d74 --- /dev/null +++ b/app/src/main/res/layout/activity_author.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_blog.xml b/app/src/main/res/layout/activity_blog.xml new file mode 100644 index 0000000..d362aeb --- /dev/null +++ b/app/src/main/res/layout/activity_blog.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_blog_list.xml b/app/src/main/res/layout/activity_blog_list.xml new file mode 100644 index 0000000..f6e7059 --- /dev/null +++ b/app/src/main/res/layout/activity_blog_list.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_category.xml b/app/src/main/res/layout/activity_category.xml new file mode 100644 index 0000000..04b6716 --- /dev/null +++ b/app/src/main/res/layout/activity_category.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_navigation.xml b/app/src/main/res/layout/activity_navigation.xml new file mode 100644 index 0000000..b061b22 --- /dev/null +++ b/app/src/main/res/layout/activity_navigation.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_search.xml b/app/src/main/res/layout/activity_search.xml new file mode 100644 index 0000000..c0d6291 --- /dev/null +++ b/app/src/main/res/layout/activity_search.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_webview.xml b/app/src/main/res/layout/activity_webview.xml new file mode 100644 index 0000000..523502d --- /dev/null +++ b/app/src/main/res/layout/activity_webview.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/common_webview.xml b/app/src/main/res/layout/common_webview.xml new file mode 100644 index 0000000..969abb4 --- /dev/null +++ b/app/src/main/res/layout/common_webview.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fab_tool_bar.xml b/app/src/main/res/layout/fab_tool_bar.xml new file mode 100644 index 0000000..8e19eb2 --- /dev/null +++ b/app/src/main/res/layout/fab_tool_bar.xml @@ -0,0 +1,26 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/footer_loading_layout.xml b/app/src/main/res/layout/footer_loading_layout.xml new file mode 100644 index 0000000..0ddddf3 --- /dev/null +++ b/app/src/main/res/layout/footer_loading_layout.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_author_info.xml b/app/src/main/res/layout/fragment_author_info.xml new file mode 100644 index 0000000..558f8f5 --- /dev/null +++ b/app/src/main/res/layout/fragment_author_info.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_blog_info.xml b/app/src/main/res/layout/fragment_blog_info.xml new file mode 100644 index 0000000..713a4e7 --- /dev/null +++ b/app/src/main/res/layout/fragment_blog_info.xml @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_navigation_drawer.xml b/app/src/main/res/layout/fragment_navigation_drawer.xml new file mode 100644 index 0000000..cce7ade --- /dev/null +++ b/app/src/main/res/layout/fragment_navigation_drawer.xml @@ -0,0 +1,10 @@ + diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml new file mode 100644 index 0000000..1a7deec --- /dev/null +++ b/app/src/main/res/layout/fragment_settings.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_author.xml b/app/src/main/res/layout/item_author.xml new file mode 100644 index 0000000..fe7d3e7 --- /dev/null +++ b/app/src/main/res/layout/item_author.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_blog.xml b/app/src/main/res/layout/item_blog.xml new file mode 100644 index 0000000..726dd48 --- /dev/null +++ b/app/src/main/res/layout/item_blog.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_category.xml b/app/src/main/res/layout/item_category.xml new file mode 100644 index 0000000..8a7d239 --- /dev/null +++ b/app/src/main/res/layout/item_category.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_nav_menu.xml b/app/src/main/res/layout/item_nav_menu.xml new file mode 100644 index 0000000..59989e9 --- /dev/null +++ b/app/src/main/res/layout/item_nav_menu.xml @@ -0,0 +1,12 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/recycler_view_fast_scroller.xml b/app/src/main/res/layout/recycler_view_fast_scroller.xml new file mode 100644 index 0000000..1e3a7b5 --- /dev/null +++ b/app/src/main/res/layout/recycler_view_fast_scroller.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/simple_text.xml b/app/src/main/res/layout/simple_text.xml new file mode 100644 index 0000000..8753903 --- /dev/null +++ b/app/src/main/res/layout/simple_text.xml @@ -0,0 +1,10 @@ + + diff --git a/app/src/main/res/layout/space_loading_layout.xml b/app/src/main/res/layout/space_loading_layout.xml new file mode 100644 index 0000000..d876b8d --- /dev/null +++ b/app/src/main/res/layout/space_loading_layout.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/webview_toolbar.xml b/app/src/main/res/layout/webview_toolbar.xml new file mode 100644 index 0000000..4fdc577 --- /dev/null +++ b/app/src/main/res/layout/webview_toolbar.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/global.xml b/app/src/main/res/menu/global.xml new file mode 100644 index 0000000..517c7de --- /dev/null +++ b/app/src/main/res/menu/global.xml @@ -0,0 +1,3 @@ +

+ diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml new file mode 100644 index 0000000..99bd67e --- /dev/null +++ b/app/src/main/res/menu/main.xml @@ -0,0 +1,11 @@ + + + + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml new file mode 100644 index 0000000..33a526a --- /dev/null +++ b/app/src/main/res/values/arrays.xml @@ -0,0 +1,17 @@ + + + + +     发现 +     分类 +     作者 +     设置 + + + + +     意见反馈 +     关于作者 +     关于APP + + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 0000000..68cb7fc --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/attrs_fab.xml b/app/src/main/res/values/attrs_fab.xml new file mode 100644 index 0000000..7faba5c --- /dev/null +++ b/app/src/main/res/values/attrs_fab.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f125d65 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,25 @@ + + + + #eeeeee + + #34322d + + #e25600 + + #88ffffff + + #faffffff + + + #55000000 + + + #1a000000 + + #ff202020 + + #ffe0e0e0 + #ffe9e9e9 + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..5f965f6 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,45 @@ + + + + 16dp + 16dp + + 240dp + + 16.0dip + 24.0dip + 8.0dip + 12.0dip + 4.0dip + 6.0dip + 2.0dip + 1.0dip + 32.0dip + 48.0dip + + 16.0dip + 24.0dip + 8.0dip + 12.0dip + 4.0dip + 2.0dip + 32.0dip + 36.0dip + 48.0dip + + + 16.0dip + 18.0dip + 14.0dip + 12.0dip + 10.0dip + 20.0dip + 8.0dip + 22.0dip + 24.0dip + 28.0dip + + 56dp + 16dp + diff --git a/app/src/main/res/values/font_awesome.xml b/app/src/main/res/values/font_awesome.xml new file mode 100644 index 0000000..c96808e --- /dev/null +++ b/app/src/main/res/values/font_awesome.xml @@ -0,0 +1,554 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml new file mode 100644 index 0000000..1a5abbf --- /dev/null +++ b/app/src/main/res/values/ids.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/integers.xml b/app/src/main/res/values/integers.xml new file mode 100644 index 0000000..eb15c86 --- /dev/null +++ b/app/src/main/res/values/integers.xml @@ -0,0 +1,43 @@ + + + 2 + 3 + 1000 + 2000 + 3 + 2 + 2000 + 0 + 2 + 0 + 300 + 400 + 350 + 200 + 3 + 2 + 60 + 1800 + 6587000 + 2 + 3 + 2 + 2 + 2 + 2 + 1 + 2 + 300 + 600 + 5 + 200 + 5 + 3 + 4 + 1 + 3 + 2 + 20 + 200 + 3 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..141da29 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,39 @@ + + + Android博客 + + Open navigation drawer + Close navigation drawer + 数据加载中... + 没有啦... + 没有找到想要的数据 + 请重试 + + 请输入关键字 + + 历史记录 + 网络连接错误,请检查网络状态 + + 反馈 + 详情 + issue + 收藏 + + 译者 + 作者 + 分类 + 原文作者 + 发表时间 + 原文发表时间 + 来源链接 + 原文链接 + + 获取博客信息失败 + + 关于 + 版本:%1$s + [Android博客]是一个简约、直观、漂亮并且开源的博客浏览应用,遵循Android设计风格。 + UI借鉴国外应用Etsy 。]]> + + 更多... + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..30dd22d --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/art/author.png b/art/author.png new file mode 100644 index 0000000..ad2a91f Binary files /dev/null and b/art/author.png differ diff --git a/art/blog.png b/art/blog.png new file mode 100644 index 0000000..c5a58c9 Binary files /dev/null and b/art/blog.png differ diff --git a/art/home.png b/art/home.png new file mode 100644 index 0000000..a4f0b1d Binary files /dev/null and b/art/home.png differ diff --git a/art/info.png b/art/info.png new file mode 100644 index 0000000..de91729 Binary files /dev/null and b/art/info.png differ diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..ee1ddfe --- /dev/null +++ b/build.gradle @@ -0,0 +1,20 @@ +// 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:1.2.2' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + maven {url "https://jitpack.io"} + } +} 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/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..0c71e76 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Apr 10 15:27:10 PDT 2013 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..91a7e26 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/osa-android.iml b/osa-android.iml new file mode 100644 index 0000000..efab83a --- /dev/null +++ b/osa-android.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':app'