├── .gitignore
├── .idea
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── gradle.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── desmond
│ │ └── citypicker
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ └── city.sqlite
│ ├── java
│ │ └── com
│ │ │ └── desmond
│ │ │ └── citypicker
│ │ │ ├── bean
│ │ │ ├── BaseCity.java
│ │ │ ├── GpsCityEvent.java
│ │ │ ├── OnDestoryEvent.java
│ │ │ └── Options.java
│ │ │ ├── bin
│ │ │ └── CityPicker.java
│ │ │ ├── callback
│ │ │ └── IOnCityPickerCheckedCallBack.java
│ │ │ ├── dao
│ │ │ └── AddressDBHelper.java
│ │ │ ├── finals
│ │ │ └── KEYS.java
│ │ │ ├── presenter
│ │ │ └── CityPickerPresenter.java
│ │ │ ├── tools
│ │ │ ├── PxConvertUtil.java
│ │ │ ├── Res.java
│ │ │ └── SysUtil.java
│ │ │ ├── ui
│ │ │ ├── CityPickerActivity.java
│ │ │ ├── CityPickerAdapter.java
│ │ │ └── SearchAdapter.java
│ │ │ └── views
│ │ │ └── pull2refresh
│ │ │ ├── BaseViewHolder.java
│ │ │ ├── RecyclerViewDivider.java
│ │ │ ├── RefreshRecyclerView.java
│ │ │ ├── SimpleBaseAdapter.java
│ │ │ └── callback
│ │ │ ├── IOnItemClickListener.java
│ │ │ ├── IOnItemLongClickListener.java
│ │ │ └── IOnRefreshListener.java
│ └── res
│ │ ├── drawable-xxhdpi
│ │ ├── back_normal.png
│ │ └── def_btn_press_bg.9.png
│ │ ├── drawable
│ │ ├── button_selector.xml
│ │ ├── header_city_bg.xml
│ │ ├── header_city_bg_press.xml
│ │ ├── press_def_bg.xml
│ │ └── white_selector.xml
│ │ ├── layout
│ │ ├── city_item.xml
│ │ ├── city_picker.xml
│ │ ├── city_picker_header.xml
│ │ ├── citypicker_title_bar.xml
│ │ └── pull2refresh_recyclerview.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── ids.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── desmond
│ └── citypicker
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screenshot
├── Screenshot_2017-05-22-11-22-45.png
├── Screenshot_2017-05-22-11-22-58.png
└── Screenshot_2017-05-22-11-23-08.png
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | CityPicker
2 | ===
3 |
4 | [](https://android-arsenal.com/api?level=14)
5 | 一个仿大众点评的城市快速选择器,
6 | 最少只需 **一行** 代码即可启动城市选择器,
7 | 支持页面样式修改,多元化自定义
8 |
9 | ScreenShot
10 | ---
11 |
12 | |  |  |  |
13 | |---|----|:---:|
14 |
15 |
16 | Version Log
17 | ---
18 | * ``V0.4.6``
19 | * 优化地理位置设置时有时会设置不成功问题
20 | * 修复其他若干问题
21 | * 修改UI默认主题色
22 | * ``V0.4.5``
23 | * 修改设置位置信息方式,由之前必须在打开页面之前获取位置信息改为允许用户在打开页面后设置位置信息,具体使用方式见 [Step3](#step3)
24 | * 简化配置项,不需要在AndroidManifest中再注册Activity,并默认隐藏titlebar
25 | * ``V0.4.3``
26 | * 修复更新数据库表结构后第一次进入会闪退问题
27 | * ``V0.4.0``
28 | * 数据库表结构修改,增加了高德地图citycode
29 | * 设置gps城市的api略有改动见 [Step3](#step3)
30 | * ``V0.3.3``
31 | * 紧急修复一个可能导致内存泄漏问题
32 | * 优化提高滑动检索效率
33 | * 隐藏下拉刷新label
34 | * ``V0.3.1``
35 | * 在搜索框后面添加一个清空搜索框按钮
36 | * 修复搜索框中输入空格会搜索出全部城市问题
37 | * 修复搜索结果弹出框中文字在不同theme下显示不同颜色问题,现在已统一为黑色
38 | * 其他调用时参数合法性校验
39 | * ``V0.3.0``
40 | * 简化api调用形式,修改为Rx形式,见[操作步骤](#use)
41 | * ``V0.2.2``
42 | * 修复进入页面会闪退问题
43 | * 修复修改右边滑动索引栏颜色时左边拼音标签颜色未修改问题
44 | * 启动城市选择页面时增加一个步骤见 [Step3](#step3)
45 | * ``V0.1.0``
46 | * 初始导入
47 |
48 | Import
49 | ---
50 | ###### Maven
51 | ``` xml
52 |
53 | com.desmond
54 | CityPicker
55 | xxx
56 | pom
57 |
58 | ```
59 | ###### Gradle
60 | ``` gradle
61 | compile 'com.desmond:CityPicker:xxx'
62 | ```
63 | Wiki
64 | ---
65 | ### Functions
66 | * 支持自定义基础城市列表(beta)
67 | * 支持历史点击城市查询
68 | * 支持自定义热门城市列表
69 | * 支持选择城市返回对象(目前已经囊括:城市名称,城市code(baidu、高德)等)
70 | * 提供方法支持页面样式轻度自定义。或继承CityPickerActivity重写部分UI样式
71 | * 基础数据依赖sqlite提供高效的查询效率
72 | * 与三方定位库解耦
73 | * 支持沉浸式状态栏
74 |
75 | ### Use
76 | ##### Step1
77 |
78 | 对于Android 6.0需要配置动态权限
79 | ``` java
80 | Manifest.permission.WRITE_EXTERNAL_STORAGE
81 | ```
82 |
83 | ##### Step2
84 | 启动城市选择页面及相关自定义配置
85 | ``` java
86 | CityPicker.with(getContext())
87 |
88 | //是否需要显示当前城市,如果为false那么就隐藏当前城市,并且调用setGpsCityByBaidu()或setGpsCityByAMap()都不会生效,非必选项,默认为true
89 | .setUseGpsCity(true)
90 |
91 | //自定义热门城市,输入数据库中的城市id(_id),非必选项,默认为数据库中的热门城市
92 | .setHotCitiesId("2", "9", "18", "11", "66", "1", "80", "49", "100");
93 |
94 | //设置最多显示历史点击城市数量,0为不显示历史城市
95 | .setMaxHistory(6);
96 |
97 | // 自定义城市基础数据列表,必须放在项目的assets文件夹下,并且表结构同citypicker项目下的assets中的数据库表结构相同
98 | // 该方法当前为beta版本,不推荐使用
99 | .setCustomDBName("xx.sqlite");
100 |
101 | // 设置标题栏背景,非必选项
102 | .setTitleBarDrawable(...);
103 |
104 | // 设置返回按钮图片,非必选项
105 | .setTitleBarBackBtnDrawable(...);
106 |
107 | // 设置搜索框背景,非必选项
108 | .setSearchViewDrawable(...);
109 |
110 | // 设置搜索框字体颜色,非必选项
111 | .setSearchViewTextColor(...);
112 |
113 | // 设置搜索框字体大小,非必选项
114 | .setSearchViewTextSize(...);
115 |
116 | // 设置右边检索栏字体颜色,非必选项
117 | .setIndexBarTextColor(...);
118 |
119 | // 设置右边检索栏字体大小,非必选项
120 | .setIndexBarTextSize(...);
121 |
122 | // 是否使用沉浸式状态栏,默认使用,非必选项
123 | .setUseImmerseBar(true);
124 |
125 | // 回调
126 | .setOnCityPickerCallBack(new IOnCityPickerCheckedCallBack()
127 | {
128 | @Override
129 | public void onCityPickerChecked(BaseCity baseCity)
130 | {
131 | //获取选择城市编码
132 | baseCity.getCodeByBaidu(); //baseCity.getCodeByAMap();//高德code
133 |
134 | //获取选择城市名称
135 | baseCity.getCityName();
136 |
137 | // 获取选择城市拼音全拼
138 | baseCity.getCityPinYin();
139 |
140 | //获取选择城市拼音首字母
141 | baseCity.getCityPYFirst();
142 | }
143 | })
144 |
145 | .open();
146 |
147 | ```
148 | ##### Step3
149 | 获取到位置信息后调用该静态方法可以在页面打开后设置当前城市
150 | 百度定位或高德定位需要自行配置,在这个库中没有集成任何与定位相关的模块
151 | ``` java
152 | //使用百度定位
153 | CityPicker.setGpsCityByBaidu("南京市","315");
154 |
155 | //高德定位
156 | CityPicker.setGpsCityByAMap("南京市","025");
157 | ```
158 | ### Be careful
159 | * 基础数据库名称定义为:**city.sqlite**。在引入的工程中千万不可创建**同名的数据库**,否则可能会发生异常!
160 |
161 | * 自定义基础数据库必须重写``city.sqlite``中的``tb_city`` 和 ``tb_history``, 允许增加字段,但不可删除或修改字段
162 |
163 | 项目中使用了如下三方库,如与你项目中的库冲突,请及时排除
164 | ```gradle
165 | def supportLibraryVersion = '24.2.1'
166 | dependencies {
167 | compile fileTree(dir: 'libs', include: ['*.jar'])
168 | testCompile 'junit:junit:4.12'
169 | compile "com.android.support:support-v4:$supportLibraryVersion"
170 | compile "com.android.support:appcompat-v7:$supportLibraryVersion"
171 | compile "com.android.support:recyclerview-v7:$supportLibraryVersion"
172 | compile "com.android.support:design:$supportLibraryVersion"
173 | compile "com.android.support:gridlayout-v7:$supportLibraryVersion"
174 | compile 'com.gjiazhe:wavesidebar:1.3'
175 | compile 'com.squareup.sqlbrite:sqlbrite:1.1.1'
176 | compile 'io.reactivex:rxjava:1.2.0'
177 | //rx系列
178 | compile 'io.reactivex:rxandroid:1.2.1'
179 |
180 | compile 'org.greenrobot:eventbus:3.0.0'
181 |
182 | }
183 | ```
184 | 排除示例:
185 | ```gradle
186 | compile ('com.desmond:CityPicker:0.3.0' ){
187 | exclude group: 'com.android.support'
188 | exclude group:'com.squareup.sqlbrite'
189 | exclude group:'io.reactivex'
190 | exclude group:'io.rxandroid'
191 | exclude group:'org.greenrobot'
192 | }
193 | ```
194 |
195 | Demo
196 | ---
197 | 手机扫描下方二维码下载demo尝鲜
198 | 
199 |
200 | Thanks
201 | ---
202 | * https://github.com/gjiazhe/WaveSideBar
203 | * https://github.com/square/sqlbrite
204 |
205 | Contact author
206 | ---
207 | QQ 350248823 添加时注明github-citypicker
208 | 欢迎issues,作者看到后会第一时间回复
209 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.github.dcendents.android-maven'
3 | apply plugin: 'com.jfrog.bintray'
4 | android {
5 | compileSdkVersion 24
6 | buildToolsVersion "25.0.0"
7 | defaultConfig {
8 | // applicationId "com.desmond.citypicker"
9 | resourcePrefix "CityPicker"
10 | minSdkVersion 14
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | android {
23 | lintOptions {
24 | abortOnError false
25 | }
26 | }
27 |
28 |
29 | def supportLibraryVersion = '24.2.1'
30 | dependencies {
31 | compile fileTree(dir: 'libs', include: ['*.jar'])
32 | testCompile 'junit:junit:4.12'
33 | compile "com.android.support:support-v4:$supportLibraryVersion"
34 | compile "com.android.support:appcompat-v7:$supportLibraryVersion"
35 | compile "com.android.support:recyclerview-v7:$supportLibraryVersion"
36 | compile "com.android.support:design:$supportLibraryVersion"
37 | compile "com.android.support:gridlayout-v7:$supportLibraryVersion"
38 | compile 'com.gjiazhe:wavesidebar:1.3'
39 | compile 'com.squareup.sqlbrite:sqlbrite:1.1.1'
40 | compile 'io.reactivex:rxjava:1.2.0'
41 | //rx系列
42 | compile 'io.reactivex:rxandroid:1.2.1'
43 |
44 | compile 'org.greenrobot:eventbus:3.0.0'
45 |
46 | }
47 |
48 | def siteUrl = 'https://github.com/yuruizhe/CityPicker' // 项目的主页
49 | def gitUrl = 'https://github.com/yuruizhe/CityPicker.git' // Git仓库的url
50 | group = "com.desmond" // Maven Group ID for the artifact,一般填你唯一的包名
51 | version = "0.4.5"
52 | project.archivesBaseName ='CityPicker'
53 | install {
54 | repositories.mavenInstaller {
55 | // This generates POM.xml with proper parameters
56 | pom {
57 | project {
58 | packaging 'aar'
59 | // Add your description here
60 | name 'CityPicker' //项目描述
61 | url siteUrl
62 | // Set your license
63 | licenses {
64 | license {
65 | name 'The Apache Software License, Version 2.0'
66 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
67 | }
68 | }
69 | developers {
70 | developer {
71 | id 'desmond' //填写的一些基本信息
72 | name 'desmond'
73 | email 'yuruizhe@chinasoftinc.com'
74 | }
75 | }
76 | scm {
77 | connection gitUrl
78 | developerConnection gitUrl
79 | url siteUrl
80 | }
81 | }
82 | }
83 | }
84 | }
85 | task sourcesJar(type: Jar) {
86 | from android.sourceSets.main.java.srcDirs
87 | classifier = 'sources'
88 | }
89 | task javadoc(type: Javadoc) {
90 | source = android.sourceSets.main.java.srcDirs
91 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
92 | }
93 | task javadocJar(type: Jar, dependsOn: javadoc) {
94 | classifier = 'javadoc'
95 | from javadoc.destinationDir
96 | }
97 | artifacts {
98 | // archives javadocJar
99 | archives sourcesJar
100 | }
101 | Properties properties = new Properties()
102 | properties.load(project.rootProject.file('local.properties').newDataInputStream())
103 | bintray {
104 | user = properties.getProperty("bintray.user")
105 | key = properties.getProperty("bintray.apikey")
106 | configurations = ['archives']
107 | pkg {
108 | repo = "maven"
109 | name = "CityPicker" //发布到JCenter上的项目名字
110 | websiteUrl = siteUrl
111 | vcsUrl = gitUrl
112 | licenses = ["Apache-2.0"]
113 | publish = true
114 | }
115 | }
116 |
117 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\Android\android-sdk_20160827/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/desmond/citypicker/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker;
2 |
3 | /**
4 | * Instrumentation test, which will execute on an Android device.
5 | *
6 | * @see Testing documentation
7 | */
8 | public class ExampleInstrumentedTest
9 | {
10 | public void useAppContext() throws Exception
11 | {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
13 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/assets/city.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuruizhe/CityPicker/f7829d49eb173f8049841ee84e46cdd2862d3861/app/src/main/assets/city.sqlite
--------------------------------------------------------------------------------
/app/src/main/java/com/desmond/citypicker/bean/BaseCity.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker.bean;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | /**
7 | * 城市基础对象,如果需要自定义可以继承此对象,并为所有字段赋值
8 | * @Todo
9 | * @Author desmond
10 | * @Date 2017/5/17
11 | * @Pacakge com.desmond.citypicker
12 | */
13 | public class BaseCity implements Parcelable
14 | {
15 | /**
16 | * 城市code(baidu)
17 | */
18 | private String codeByBaidu;
19 | /**
20 | * 城市code(高德)
21 | */
22 | private String codeByAMap;
23 | /**
24 | * 城市名称
25 | */
26 | private String cityName;
27 | /**
28 | * 城市拼音全称
29 | */
30 | private String cityPinYin;
31 | /**
32 | * 城市拼音首字母
33 | */
34 | private String cityPYFirst;
35 | /**
36 | * 主键id
37 | */
38 | private String id;
39 | /**
40 | * 是否为热门城市
41 | */
42 | private boolean isHot;
43 |
44 | public String getCodeByBaidu()
45 | {
46 | return codeByBaidu;
47 | }
48 |
49 | public void setCodeByBaidu(String codeByBaidu)
50 | {
51 | this.codeByBaidu = codeByBaidu;
52 | }
53 |
54 | public String getCodeByAMap()
55 | {
56 | return codeByAMap;
57 | }
58 |
59 | public void setCodeByAMap(String codeByAMap)
60 | {
61 | this.codeByAMap = codeByAMap;
62 | }
63 |
64 | public boolean isHot()
65 | {
66 | return isHot;
67 | }
68 |
69 | public void setHot(boolean hot)
70 | {
71 | isHot = hot;
72 | }
73 |
74 | public String getId()
75 | {
76 | return id;
77 | }
78 |
79 | public void setId(String id)
80 | {
81 | this.id = id;
82 | }
83 |
84 | public String getCityPYFirst()
85 | {
86 | return cityPYFirst;
87 | }
88 |
89 | public void setCityPYFirst(String cityPYFirst)
90 | {
91 | this.cityPYFirst = cityPYFirst;
92 | }
93 |
94 |
95 |
96 | public String getCityName()
97 | {
98 | return cityName;
99 | }
100 |
101 | public void setCityName(String cityName)
102 | {
103 | this.cityName = cityName;
104 | }
105 |
106 | public String getCityPinYin()
107 | {
108 | return cityPinYin;
109 | }
110 |
111 | public void setCityPinYin(String cityPinYin)
112 | {
113 | this.cityPinYin = cityPinYin;
114 | }
115 |
116 | @Override
117 | public int describeContents()
118 | {
119 | return 0;
120 | }
121 |
122 | @Override
123 | public void writeToParcel(Parcel dest, int flags)
124 | {
125 | dest.writeString(this.codeByBaidu);
126 | dest.writeString(this.codeByAMap);
127 | dest.writeString(this.cityName);
128 | dest.writeString(this.cityPinYin);
129 | dest.writeString(this.cityPYFirst);
130 | dest.writeString(this.id);
131 | dest.writeByte(this.isHot ? (byte) 1 : (byte) 0);
132 | }
133 |
134 | public BaseCity()
135 | {
136 | }
137 |
138 | protected BaseCity(Parcel in)
139 | {
140 | this.codeByBaidu = in.readString();
141 | this.codeByAMap = in.readString();
142 | this.cityName = in.readString();
143 | this.cityPinYin = in.readString();
144 | this.cityPYFirst = in.readString();
145 | this.id = in.readString();
146 | this.isHot = in.readByte() != 0;
147 | }
148 |
149 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator()
150 | {
151 | @Override
152 | public BaseCity createFromParcel(Parcel source)
153 | {
154 | return new BaseCity(source);
155 | }
156 |
157 | @Override
158 | public BaseCity[] newArray(int size)
159 | {
160 | return new BaseCity[size];
161 | }
162 | };
163 | }
164 |
--------------------------------------------------------------------------------
/app/src/main/java/com/desmond/citypicker/bean/GpsCityEvent.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker.bean;
2 |
3 | /**
4 | * @Todo
5 | * @Author desmond
6 | * @Date 2017/8/2
7 | * @Pacakge com.desmond.citypicker.bean
8 | */
9 |
10 | public class GpsCityEvent
11 | {
12 | public BaseCity gpsCity;
13 |
14 | public GpsCityEvent(BaseCity gpsCity)
15 | {
16 | this.gpsCity = gpsCity;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/desmond/citypicker/bean/OnDestoryEvent.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker.bean;
2 |
3 | /**
4 | * @Todo
5 | * @Author desmond
6 | * @Date 2017/5/27
7 | * @Pacakge com.desmond.citypicker.bean
8 | */
9 |
10 | public class OnDestoryEvent
11 | {
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/desmond/citypicker/bean/Options.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker.bean;
2 |
3 | import android.content.Context;
4 | import android.graphics.drawable.Drawable;
5 | import android.os.Parcel;
6 | import android.os.Parcelable;
7 | import android.support.annotation.ColorRes;
8 | import android.support.annotation.DrawableRes;
9 |
10 | import com.desmond.citypicker.R;
11 | import com.desmond.citypicker.tools.PxConvertUtil;
12 | import com.desmond.citypicker.tools.Res;
13 |
14 | /**
15 | * @Todo
16 | * @Author desmond
17 | * @Date 2017/5/21
18 | * @Pacakge com.desmond.citypicker.bean
19 | */
20 |
21 | public class Options implements Parcelable
22 | {
23 | /**
24 | * 是否需要显示当前城市
25 | */
26 | protected boolean useGpsCity;
27 |
28 | /**
29 | * 热门城市列表
30 | */
31 | protected String[] hotCitiesId;
32 | /**
33 | * 自定义的数据库名称,sqlite必须放在项目的assets
34 | */
35 | protected String customDBName;
36 | /**
37 | * 最大历史城市数量
38 | */
39 | protected int maxHistory;
40 |
41 | /**
42 | * 标题栏背景
43 | */
44 | protected int titleBarDrawable;
45 |
46 | /**
47 | * 搜索框字体大小(sp)
48 | */
49 | protected int searchViewTextSize;
50 |
51 | /**
52 | * 搜索框字体颜色
53 | */
54 | protected int searchViewTextColor;
55 |
56 | /**
57 | * 搜索框背景
58 | */
59 | protected int searchViewDrawable;
60 |
61 | /**
62 | * 返回按钮背景
63 | */
64 | protected int titleBarBackBtnDrawable;
65 |
66 | /**
67 | * 标题栏高度(dp)
68 | */
69 | // protected float titleBarHeight;
70 |
71 | /**
72 | * 检索栏字体大小(sp)
73 | */
74 | protected float indexBarTextSize;
75 |
76 | /**
77 | * 检索栏字体颜色
78 | */
79 | protected int indexBarTextColor;
80 |
81 |
82 | /**
83 | * 是否使用沉浸式状态栏
84 | */
85 | protected boolean useImmerseBar;
86 |
87 | private Context context;
88 |
89 | public boolean isUseGpsCity()
90 | {
91 | return useGpsCity;
92 | }
93 |
94 | public void setUseGpsCity(boolean useGpsCity)
95 | {
96 | this.useGpsCity = useGpsCity;
97 | }
98 |
99 | public String[] getHotCitiesId()
100 | {
101 | return hotCitiesId;
102 | }
103 |
104 | public void setHotCitiesId(String[] hotCitiesId)
105 | {
106 | this.hotCitiesId = hotCitiesId;
107 | }
108 |
109 | public String getCustomDBName()
110 | {
111 | return customDBName;
112 | }
113 |
114 | public void setCustomDBName(String customDBName)
115 | {
116 | this.customDBName = customDBName;
117 | }
118 |
119 | public int getMaxHistory()
120 | {
121 | return maxHistory;
122 | }
123 |
124 | public void setMaxHistory(int maxHistory)
125 | {
126 | this.maxHistory = maxHistory;
127 | }
128 |
129 | public Drawable getTitleBarDrawable()
130 | {
131 | return Res.drawable(context,titleBarDrawable);
132 | }
133 |
134 |
135 | public void setTitleBarDrawable(@DrawableRes int titleBarDrawable)
136 | {
137 | this.titleBarDrawable = titleBarDrawable;
138 | }
139 |
140 | public int getSearchViewTextSize()
141 | {
142 | return searchViewTextSize;
143 | }
144 |
145 | public void setSearchViewTextSize(int searchViewTextSize)
146 | {
147 | this.searchViewTextSize = searchViewTextSize;
148 | }
149 |
150 | public int getSearchViewTextColor()
151 | {
152 | return searchViewTextColor;
153 | }
154 |
155 | public void setSearchViewTextColor(@ColorRes int searchViewTextColor)
156 | {
157 | this.searchViewTextColor = Res.color(context,searchViewTextColor);
158 | }
159 |
160 |
161 | public Drawable getSearchViewDrawable()
162 | {
163 | return Res.drawable(context,searchViewDrawable);
164 | }
165 |
166 | public void setSearchViewDrawable(@DrawableRes int searchViewDrawable)
167 | {
168 | this.searchViewDrawable = searchViewDrawable;
169 | }
170 |
171 | public Drawable getTitleBarBackBtnDrawable()
172 | {
173 | return Res.drawable(context,titleBarBackBtnDrawable);
174 | }
175 |
176 |
177 | public void setTitleBarBackBtnDrawable(@DrawableRes int titleBarBackBtnDrawable)
178 | {
179 | this.titleBarBackBtnDrawable = titleBarBackBtnDrawable;
180 | }
181 |
182 | public float getIndexBarTextSize()
183 | {
184 | return indexBarTextSize;
185 | }
186 |
187 | public void setIndexBarTextSize(float indexBarTextSize)
188 | {
189 | this.indexBarTextSize = PxConvertUtil.sp2px(context,indexBarTextSize);
190 | }
191 |
192 | public int getIndexBarTextColor()
193 | {
194 | return indexBarTextColor;
195 | }
196 |
197 | public void setIndexBarTextColor(@ColorRes int indexBarTextColor)
198 | {
199 | this.indexBarTextColor = Res.color(context,indexBarTextColor);
200 | }
201 |
202 | public boolean isUseImmerseBar()
203 | {
204 | return useImmerseBar;
205 | }
206 |
207 | public void setUseImmerseBar(boolean useImmerseBar)
208 | {
209 | this.useImmerseBar = useImmerseBar;
210 | }
211 |
212 | public Context getContext()
213 | {
214 | return context;
215 | }
216 |
217 | public void setContext(Context context)
218 | {
219 | this.context = context;
220 | }
221 |
222 | public Options(Context context)
223 | {
224 | setContext(context);
225 | setUseGpsCity(true);
226 | setHotCitiesId(null);
227 | setCustomDBName("city.sqlite");
228 | setMaxHistory(12);
229 |
230 | setTitleBarDrawable(R.color.theme_main_color);
231 | setSearchViewTextSize(15);
232 |
233 | setSearchViewTextColor(R.color.black);
234 | setSearchViewDrawable(R.drawable.header_city_bg);
235 | setTitleBarBackBtnDrawable(R.drawable.back_normal);
236 |
237 | setIndexBarTextSize(14);
238 | setIndexBarTextColor(R.color.theme_vice2_color);
239 | setUseImmerseBar(true);
240 | }
241 |
242 |
243 | @Override
244 | public int describeContents()
245 | {
246 | return 0;
247 | }
248 |
249 | @Override
250 | public void writeToParcel(Parcel dest, int flags)
251 | {
252 | dest.writeByte(this.useGpsCity ? (byte) 1 : (byte) 0);
253 | dest.writeStringArray(this.hotCitiesId);
254 | dest.writeString(this.customDBName);
255 | dest.writeInt(this.maxHistory);
256 | dest.writeInt(this.titleBarDrawable);
257 | dest.writeInt(this.searchViewTextSize);
258 | dest.writeInt(this.searchViewTextColor);
259 | dest.writeInt(this.searchViewDrawable);
260 | dest.writeInt(this.titleBarBackBtnDrawable);
261 | dest.writeFloat(this.indexBarTextSize);
262 | dest.writeInt(this.indexBarTextColor);
263 | dest.writeByte(this.useImmerseBar ? (byte) 1 : (byte) 0);
264 | }
265 |
266 | protected Options(Parcel in)
267 | {
268 | this.useGpsCity = in.readByte() != 0;
269 | this.hotCitiesId = in.createStringArray();
270 | this.customDBName = in.readString();
271 | this.maxHistory = in.readInt();
272 | this.titleBarDrawable = in.readInt();
273 | this.searchViewTextSize = in.readInt();
274 | this.searchViewTextColor = in.readInt();
275 | this.searchViewDrawable = in.readInt();
276 | this.titleBarBackBtnDrawable = in.readInt();
277 | this.indexBarTextSize = in.readFloat();
278 | this.indexBarTextColor = in.readInt();
279 | this.useImmerseBar = in.readByte() != 0;
280 | this.context = in.readParcelable(Context.class.getClassLoader());
281 | }
282 |
283 | public static final Creator CREATOR = new Creator()
284 | {
285 | @Override
286 | public Options createFromParcel(Parcel source)
287 | {
288 | return new Options(source);
289 | }
290 |
291 | @Override
292 | public Options[] newArray(int size)
293 | {
294 | return new Options[size];
295 | }
296 | };
297 | }
298 |
--------------------------------------------------------------------------------
/app/src/main/java/com/desmond/citypicker/bin/CityPicker.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker.bin;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.support.annotation.ColorRes;
6 | import android.support.annotation.DrawableRes;
7 | import android.support.annotation.IntRange;
8 | import android.support.annotation.MainThread;
9 | import android.support.annotation.Nullable;
10 |
11 | import com.desmond.citypicker.bean.BaseCity;
12 | import com.desmond.citypicker.bean.GpsCityEvent;
13 | import com.desmond.citypicker.bean.OnDestoryEvent;
14 | import com.desmond.citypicker.bean.Options;
15 | import com.desmond.citypicker.callback.IOnCityPickerCheckedCallBack;
16 | import com.desmond.citypicker.finals.KEYS;
17 | import com.desmond.citypicker.ui.CityPickerActivity;
18 |
19 | import org.greenrobot.eventbus.EventBus;
20 | import org.greenrobot.eventbus.Subscribe;
21 | import org.greenrobot.eventbus.ThreadMode;
22 |
23 | /**
24 | * @Todo
25 | * @Author desmond
26 | * @Date 2017/5/25
27 | * @Pacakge com.desmond.citypicker.bin
28 | */
29 |
30 | public class CityPicker
31 | {
32 | Options options;
33 | static CityPicker instance;
34 | IOnCityPickerCheckedCallBack callback;
35 |
36 | public static BaseCity gpsCity ;
37 |
38 | private CityPicker()
39 | {
40 | EventBus.getDefault().register(this);
41 | }
42 |
43 | @MainThread
44 | public static CityPicker with(Context context)
45 | {
46 | if (instance == null)
47 | {
48 | synchronized (CityPicker.class)
49 | {
50 | if (instance == null)
51 | instance = new CityPicker();
52 | }
53 | }
54 | instance.options = new Options(context.getApplicationContext());
55 | return instance;
56 | }
57 |
58 | /**
59 | * 是否需要显示当前城市
60 | * @param useGpsCity 如果为false那么就隐藏当前城市,并且调用setGpsCityByBaidu()或setGpsCityByAMap()都不会生效
61 | * @return
62 | */
63 | public CityPicker setUseGpsCity(boolean useGpsCity)
64 | {
65 | this.options.setUseGpsCity(useGpsCity);
66 | return this;
67 | }
68 | /**
69 | * 设置定位城市(百度定位)
70 | *
71 | * @param name 城市中文名称
72 | * @param code 百度城市code
73 | * @return
74 | */
75 | @MainThread
76 | public static void setGpsCityByBaidu(@Nullable String name, @Nullable String code)
77 | {
78 | BaseCity baseCity = new BaseCity();
79 | baseCity.setCityName(name);
80 | baseCity.setCodeByBaidu(code);
81 | setGpsCity(baseCity);
82 | }
83 |
84 | /**
85 | * 设置定位城市(高德定位)
86 | * @param name 城市中文名称
87 | * @param code 高德城市code
88 | * @return
89 | */
90 | public static void setGpsCityByAMap(@Nullable String name, @Nullable String code)
91 | {
92 | BaseCity baseCity = new BaseCity();
93 | baseCity.setCityName(name);
94 | baseCity.setCodeByAMap(code);
95 | setGpsCity(baseCity);
96 | }
97 |
98 | /**
99 | * 设置定位城市
100 | *
101 | * @param baseCity
102 | * @return
103 | */
104 | @MainThread
105 | public static void setGpsCity(BaseCity baseCity)
106 | {
107 | gpsCity = baseCity;
108 | EventBus.getDefault().post(new GpsCityEvent(baseCity));
109 | }
110 |
111 | /**
112 | * 自定义热门城市,输入数据库中的城市id(_id)
113 | *
114 | * @param ids
115 | * @return
116 | */
117 | @MainThread
118 | public CityPicker setHotCitiesId(String... ids)
119 | {
120 | instance.options.setHotCitiesId(ids);
121 | return this;
122 | }
123 |
124 | /**
125 | * 设置最多显示历史点击城市数量,0为不显示历史城市
126 | *
127 | * @param max
128 | * @return
129 | */
130 | @MainThread
131 | public CityPicker setMaxHistory(@IntRange(from = 0) int max)
132 | {
133 | instance.options.setMaxHistory(max);
134 | return this;
135 | }
136 |
137 | /**
138 | * 自定义城市基础数据列表,必须放在项目的assets文件夹下,并且表结构同citypicker项目下的assets中的数据库表结构相同
139 | *
140 | * @param name
141 | * @return
142 | * @deprecated 该方法当前为beta版本,不推荐使用
143 | */
144 | @MainThread
145 | @Deprecated
146 | public CityPicker setCustomDBName(@Nullable String name)
147 | {
148 | instance.options.setCustomDBName(name);
149 | return this;
150 | }
151 |
152 | /**
153 | * 设置标题栏背景
154 | *
155 | * @param res
156 | * @return
157 | */
158 | @MainThread
159 | public CityPicker setTitleBarDrawable(@DrawableRes int res)
160 | {
161 | instance.options.setTitleBarDrawable(res);
162 | return this;
163 | }
164 |
165 | /**
166 | * 设置返回按钮图片
167 | *
168 | * @param res
169 | * @return
170 | */
171 | @MainThread
172 | public CityPicker setTitleBarBackBtnDrawable(@DrawableRes int res)
173 | {
174 | instance.options.setTitleBarBackBtnDrawable(res);
175 | return this;
176 | }
177 |
178 | /**
179 | * 设置搜索框背景
180 | *
181 | * @param res
182 | * @return
183 | */
184 | @MainThread
185 | public CityPicker setSearchViewDrawable(@DrawableRes int res)
186 | {
187 | instance.options.setSearchViewDrawable(res);
188 | return this;
189 | }
190 |
191 | /**
192 | * 设置搜索框字体颜色
193 | *
194 | * @param res
195 | * @return
196 | */
197 | @MainThread
198 | public CityPicker setSearchViewTextColor(@ColorRes int res)
199 | {
200 | instance.options.setSearchViewTextColor(res);
201 | return this;
202 | }
203 |
204 | /**
205 | * 设置搜索框字体大小(sp)
206 | *
207 | * @param size
208 | * @return
209 | */
210 | @MainThread
211 | public CityPicker setSearchViewTextSize(int size)
212 | {
213 | instance.options.setSearchViewTextSize(size);
214 | return this;
215 | }
216 |
217 |
218 | /**
219 | * 设置右边检索栏字体颜色
220 | *
221 | * @param res
222 | * @return
223 | */
224 | @MainThread
225 | public CityPicker setIndexBarTextColor(@ColorRes int res)
226 | {
227 | instance.options.setIndexBarTextColor(res);
228 | return this;
229 | }
230 |
231 | /**
232 | * 设置右边检索栏字体大小(sp)
233 | *
234 | * @param size
235 | * @return
236 | */
237 | @MainThread
238 | public CityPicker setIndexBarTextSize(int size)
239 | {
240 | instance.options.setIndexBarTextSize(size);
241 | return this;
242 | }
243 |
244 | /**
245 | * 是否使用沉浸式状态栏,默认使用
246 | *
247 | * @param arg0
248 | * @return
249 | */
250 | @MainThread
251 | public CityPicker setUseImmerseBar(boolean arg0)
252 | {
253 | instance.options.setUseImmerseBar(arg0);
254 | return this;
255 | }
256 |
257 |
258 | @MainThread
259 | public void open()
260 | {
261 | Intent intent = new Intent(instance.options.getContext(), CityPickerActivity.class);
262 | intent.putExtra(KEYS.OPTIONS, options);
263 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
264 | instance.options.getContext().startActivity(intent);
265 | }
266 |
267 |
268 | /**
269 | * 注册选择结果回调
270 | *
271 | * @param callback
272 | * @return
273 | */
274 | public CityPicker setOnCityPickerCallBack(IOnCityPickerCheckedCallBack callback)
275 | {
276 | this.callback = callback;
277 | return this;
278 | }
279 |
280 | @Subscribe(threadMode = ThreadMode.MAIN)
281 | public void whenCityPickerChecked(BaseCity baseCity)
282 | {
283 | if (this.callback != null)
284 | this.callback.onCityPickerChecked(baseCity);
285 | destroy();
286 | }
287 |
288 | @Subscribe(threadMode = ThreadMode.MAIN)
289 | public void whenCityPickerClosed(OnDestoryEvent event)
290 | {
291 | destroy();
292 | }
293 |
294 | public void destroy()
295 | {
296 | EventBus.getDefault().unregister(this);
297 | instance = null;
298 | options = null;
299 | gpsCity = null;
300 | }
301 |
302 | }
303 |
--------------------------------------------------------------------------------
/app/src/main/java/com/desmond/citypicker/callback/IOnCityPickerCheckedCallBack.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker.callback;
2 |
3 | import com.desmond.citypicker.bean.BaseCity;
4 |
5 | /**
6 | * @Todo
7 | * @Author desmond
8 | * @Date 2017/5/25
9 | * @Pacakge com.desmond.citypicker.callback
10 | */
11 |
12 | public interface IOnCityPickerCheckedCallBack
13 | {
14 | void onCityPickerChecked(BaseCity baseCity);
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/desmond/citypicker/dao/AddressDBHelper.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker.dao;
2 |
3 | import android.content.Context;
4 | import android.database.sqlite.SQLiteDatabase;
5 | import android.database.sqlite.SQLiteOpenHelper;
6 |
7 | import com.desmond.citypicker.tools.SysUtil;
8 |
9 | import java.io.File;
10 | import java.io.FileOutputStream;
11 | import java.io.InputStream;
12 | import java.io.OutputStream;
13 |
14 | /**
15 | * @Todo
16 | * @Author desmond
17 | * @Date 2017/4/28
18 | * @Pacakge com.chinasoftinc.support_library.db
19 | */
20 |
21 | public class AddressDBHelper extends SQLiteOpenHelper
22 | {
23 |
24 | private SQLiteDatabase mDataBase;
25 | private Context mContext;
26 |
27 |
28 | private static String DATABASE_PATH ;
29 |
30 |
31 | public AddressDBHelper(Context context,String name,int viersion)
32 | {
33 | super(context, name, null, viersion);
34 | this.mContext = context;
35 | DATABASE_PATH = "/data/data/" + SysUtil.getAppId(mContext) + "/databases/";
36 | try
37 | {
38 |
39 | createDataBase();
40 |
41 | this.getReadableDatabase();
42 | } catch (Exception e)
43 | {
44 | e.printStackTrace();
45 | }
46 | }
47 |
48 | /**
49 | * Creates a empty database on the system and rewrites it with your own
50 | * database.
51 | */
52 | public void createDataBase() throws Exception
53 | {
54 |
55 | boolean dbExist = checkDataBase();
56 |
57 | if (dbExist) return;
58 |
59 |
60 | copyDataBase();
61 |
62 | }
63 |
64 | /**
65 | * Check if the database already exist to avoid re-copying the file each
66 | * time you open the application.
67 | *
68 | * @return true if it exists, false if it doesn't
69 | */
70 | private boolean checkDataBase()
71 | {
72 | SQLiteDatabase checkDB = null;
73 |
74 | try
75 | {
76 | String myPath = DATABASE_PATH + getDatabaseName();
77 | checkDB = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);
78 | } catch (Exception e)
79 | {
80 | e.printStackTrace();
81 | }
82 |
83 | if (checkDB != null)
84 | {
85 | checkDB.close();
86 | }
87 | return checkDB != null;
88 | }
89 |
90 | /**
91 | * Copies your database from your local assets-folder to the just created
92 | * empty database in the system folder, from where it can be accessed and
93 | * handled. This is done by transfering bytestream.
94 | */
95 | private void copyDataBase() throws Exception
96 | {
97 | // Open your local db as the input stream
98 | InputStream myInput = mContext.getAssets().open(getDatabaseName());
99 |
100 | // Path to the just created empty db
101 | String outFileName = DATABASE_PATH + getDatabaseName();
102 |
103 | File f = new File(DATABASE_PATH);
104 | if(!f.exists())
105 | f.mkdirs();
106 |
107 |
108 | // Open the empty db as the output stream
109 | OutputStream myOutput = new FileOutputStream(outFileName);
110 |
111 |
112 |
113 | // transfer bytes from the inputfile to the outputfile
114 | byte[] buffer = new byte[1024];
115 | int length;
116 | while ((length = myInput.read(buffer)) > 0)
117 | {
118 | myOutput.write(buffer, 0, length);
119 | }
120 |
121 | // Close the streams
122 | myOutput.flush();
123 | myOutput.close();
124 | myInput.close();
125 |
126 | }
127 |
128 | public void openDataBase()
129 | {
130 | // Open the database
131 | String myPath = DATABASE_PATH + getDatabaseName();
132 | mDataBase = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READWRITE);
133 |
134 | }
135 |
136 | @Override
137 | public synchronized void close()
138 | {
139 | if (mDataBase != null)
140 | mDataBase.close();
141 | super.close();
142 |
143 | }
144 |
145 | @Override
146 | public void onCreate(SQLiteDatabase db)
147 | {
148 | }
149 |
150 | @Override
151 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
152 | {
153 | if(oldVersion == 1 && newVersion == 2)
154 | {
155 | try
156 | {
157 | copyDataBase();
158 | } catch (Exception e)
159 | {
160 | e.printStackTrace();
161 | }
162 | }
163 | this.getReadableDatabase();
164 | }
165 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/desmond/citypicker/finals/KEYS.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker.finals;
2 |
3 | /**
4 | * @Todo
5 | * @Author desmond
6 | * @Date 2017/5/19
7 | * @Pacakge com.desmond.citypicker.finals
8 | */
9 |
10 | public class KEYS
11 | {
12 |
13 | /**
14 | * 从intent中接收选择城市结果的key
15 | */
16 | public static final String SELECTED_RESULT = "SELECTED_RESULT";
17 |
18 | /**
19 | *
20 | */
21 | public static final String OPTIONS= "OPTIONS";
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/desmond/citypicker/presenter/CityPickerPresenter.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker.presenter;
2 |
3 | import android.content.ContentValues;
4 | import android.content.Context;
5 | import android.database.Cursor;
6 | import android.text.TextUtils;
7 |
8 | import com.desmond.citypicker.bean.BaseCity;
9 | import com.desmond.citypicker.dao.AddressDBHelper;
10 | import com.squareup.sqlbrite.BriteDatabase;
11 | import com.squareup.sqlbrite.SqlBrite;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 | import rx.android.schedulers.AndroidSchedulers;
17 |
18 | /**
19 | * @Todo
20 | * @Author desmond
21 | * @Date 2017/5/18
22 | * @Pacakge com.desmond.citypicker.presenter
23 | */
24 |
25 | public class CityPickerPresenter
26 | {
27 | private Context context;
28 | private BriteDatabase db;
29 |
30 | public static final String LISHI_REMEN = "#";
31 |
32 | public String name;
33 | public final int VERSION = 2;
34 | public static final int MAX_HEADER_CITY_SIZE = 12;
35 |
36 | public CityPickerPresenter(Context context, String dbName)
37 | {
38 | this.context = context;
39 | if (!TextUtils.isEmpty(dbName))
40 | this.name = dbName;
41 | }
42 |
43 |
44 | /**
45 | * 获取城市列表,并按照首字母排序
46 | *
47 | * @return
48 | */
49 | public List getCitysSort()
50 | {
51 | Cursor c = getDatabase().query("select * from tb_city order by city_py_first");
52 | List datas = getCityFromDb(c);
53 | c.close();
54 | return datas;
55 | }
56 |
57 | /**
58 | * 获取索引列表
59 | *
60 | * @return
61 | */
62 | public List getIndex()
63 | {
64 | Cursor c = getDatabase().query("select distinct city_py_first from tb_city c order by city_py_first");
65 | List datas = new ArrayList<>(c.getCount() + 1);
66 | datas.add(LISHI_REMEN);
67 | while (c.moveToNext())
68 | {
69 | String pyFirst = c.getString(c.getColumnIndex("city_py_first"));
70 | datas.add(pyFirst);
71 | }
72 | c.close();
73 | return datas;
74 | }
75 |
76 | /**
77 | * 获取数据库
78 | *
79 | * @return
80 | */
81 | private BriteDatabase getDatabase()
82 | {
83 | if (db != null) return db;
84 | SqlBrite sqlBrite = new SqlBrite.Builder().build();
85 | AddressDBHelper dbHelper = new AddressDBHelper(context, this.name, this.VERSION);
86 | db = sqlBrite.wrapDatabaseHelper(dbHelper, AndroidSchedulers.mainThread());
87 | return db;
88 | }
89 |
90 |
91 | /**
92 | * 获取历史城市
93 | *
94 | * @param max 返回数据条数
95 | * @return
96 | */
97 | public List getHistoryCity(int max)
98 | {
99 | Cursor c = getDatabase().query("select c._id,c.city_name,c.city_py,c.city_py_first,c.city_code_baidu,c.city_code_amap,c.is_hot from tb_history h left join tb_city c on h.city_id=c._id order by time desc limit ?", max + "");
100 | List datas = getCityFromDb(c);
101 | c.close();
102 | return datas;
103 | }
104 |
105 | /**
106 | * 记录历史城市
107 | *
108 | * @param city
109 | */
110 | public void saveHistoryCity(BaseCity city)
111 | {
112 | String cityId = getCityId(city);
113 | if(cityId == null)
114 | return;
115 |
116 | ContentValues cv = new ContentValues(2);
117 | cv.put("time", System.currentTimeMillis() + "");
118 |
119 | //如果之前没有记录过就会插入数据,否则修改时间戳为当前时间
120 | boolean has = getDatabase().update("tb_history", cv, "city_id=?", cityId) > 0;
121 | if (!has)
122 | {
123 | cv.put("city_id", cityId);
124 | getDatabase().insert("tb_history", cv);
125 | }
126 | }
127 |
128 |
129 | /**
130 | * 获取城市表的主键id
131 | * @param city
132 | * @return
133 | */
134 | protected String getCityId(BaseCity city)
135 | {
136 | String cityId = null;
137 |
138 | // 如果bean中就带有id就直接返回
139 | // 除了点击定位城市应该都进入这个分支
140 | if (!TextUtils.isEmpty(city.getId()))
141 | return city.getId();
142 |
143 |
144 | // 根据城市名称查询id
145 | Cursor c = getDatabase().query("select _id from tb_city where city_name=?", city.getCityName());
146 | if (c.moveToNext())
147 | {
148 | cityId = c.getString(c.getColumnIndex("_id"));
149 | c.close();
150 | return cityId;
151 | }
152 |
153 | // 考虑到城市名称叫法不同。如“北京市”和“北京” 表达的是同一个意思,但是数据库会匹配不到
154 | // 在这里还会根据百度code或高德code查询一次。
155 | // 这里之所以没有在一条语句中用or查询,一方面是有可能输入的code为null会报错,另一方面是出于效率的考虑
156 | if(!TextUtils.isEmpty(city.getCodeByBaidu()))
157 | {
158 | c = getDatabase().query("select _id from tb_city where city_code_baidu=?", city.getCodeByBaidu());
159 | if (c.moveToNext())
160 | {
161 | cityId = c.getString(c.getColumnIndex("_id"));
162 | c.close();
163 | return cityId;
164 | }
165 | }else
166 | {
167 | c = getDatabase().query("select _id from tb_city where city_code_amap=?", city.getCodeByAMap());
168 | if (c.moveToNext())
169 | {
170 | cityId = c.getString(c.getColumnIndex("_id"));
171 | c.close();
172 | return cityId;
173 | }
174 | }
175 |
176 | return cityId;
177 | }
178 |
179 |
180 | /**
181 | * 获取热门城市
182 | *
183 | * @param max 最大条数
184 | * @return
185 | */
186 | public List getHotCity(int max)
187 | {
188 | Cursor c = getDatabase().query("select * from tb_city where is_hot='T' limit ?", max + "");
189 | List datas = getCityFromDb(c);
190 | c.close();
191 | return datas;
192 | }
193 |
194 |
195 | /**
196 | * 获取热门城市
197 | *
198 | * @param ids
199 | * @return
200 | */
201 | public List getHotCityById(String... ids)
202 | {
203 | int max = ids.length <= CityPickerPresenter.MAX_HEADER_CITY_SIZE ? ids.length : CityPickerPresenter.MAX_HEADER_CITY_SIZE;
204 | StringBuffer sb = new StringBuffer();
205 | for (int i = 0; i < max; i++)
206 | sb.append(",?");
207 |
208 | Cursor c = getDatabase().query("select * from tb_city where _id in(" + sb.substring(1, sb.length()) + ")", ids);
209 | List datas = getCityFromDb(c);
210 | c.close();
211 | return datas;
212 | }
213 |
214 | /**
215 | * 搜索城市(模糊查询 like key%)
216 | *
217 | * @param key
218 | * @return
219 | */
220 | public List searchCity(String key)
221 | {
222 | key = key + "%";
223 | Cursor c = getDatabase().query("select * from tb_city where city_name like ? or city_py like ? order by city_py_first", key, key);
224 | List datas = getCityFromDb(c);
225 | c.close();
226 | return datas;
227 | }
228 |
229 | /**
230 | * 查询的结果统一转为对象
231 | *
232 | * @param c
233 | * @return
234 | */
235 | private List getCityFromDb(Cursor c)
236 | {
237 | List datas = new ArrayList<>(c.getCount());
238 | while (c.moveToNext())
239 | {
240 | String name = c.getString(c.getColumnIndex("city_name"));
241 | String py = c.getString(c.getColumnIndex("city_py"));
242 | String pyFrist = c.getString(c.getColumnIndex("city_py_first"));
243 | String codeBD = c.getString(c.getColumnIndex("city_code_baidu"));
244 | String codeAMap = c.getString(c.getColumnIndex("city_code_amap"));
245 | String id = c.getString(c.getColumnIndex("_id"));
246 | String isHot = c.getString(c.getColumnIndex("is_hot"));
247 |
248 | BaseCity baseCity = new BaseCity();
249 | baseCity.setCityName(name);
250 | baseCity.setCityPinYin(py);
251 | baseCity.setCityPYFirst(pyFrist);
252 | baseCity.setCodeByBaidu(codeBD);
253 | baseCity.setCodeByAMap(codeAMap);
254 | baseCity.setId(id);
255 | baseCity.setHot("T".equals(isHot));
256 | datas.add(baseCity);
257 | }
258 | return datas;
259 | }
260 |
261 | }
262 |
--------------------------------------------------------------------------------
/app/src/main/java/com/desmond/citypicker/tools/PxConvertUtil.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker.tools;
2 |
3 | import android.content.Context;
4 |
5 |
6 | /**
7 | * 转换工具
8 | *
9 | * @author yujinmin
10 | * @date 2014年7月8日 下午2:23:39
11 | */
12 | public class PxConvertUtil
13 | {
14 |
15 | /**
16 | * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
17 | */
18 | public static int dip2px(Context context,float dpValue)
19 | {
20 | final float scale = context.getResources().getDisplayMetrics().density;
21 | return (int) (dpValue * scale + 0.5f);
22 | }
23 |
24 | /**
25 | * 将px值转换为dip或dp值,保证尺寸大小不变
26 | *
27 | * @param pxValue
28 | * @return
29 | */
30 | public static int px2dip(Context context,float pxValue)
31 | {
32 | final float scale = context.getResources().getDisplayMetrics().density;
33 | return (int) (pxValue / scale + 0.5f);
34 | }
35 |
36 |
37 | /**
38 | * 将sp值转换为px值,保证文字大小不变
39 | *
40 | * @param spValue
41 | * @return
42 | */
43 | public static int sp2px(Context context,float spValue)
44 | {
45 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
46 | return (int) (spValue * fontScale + 0.5f);
47 | }
48 |
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/java/com/desmond/citypicker/tools/Res.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker.tools;
2 |
3 | import android.content.Context;
4 | import android.graphics.drawable.Drawable;
5 |
6 |
7 | /**
8 | * 获取资源文件帮助
9 | *
10 | * @Todo
11 | * @Author desmond
12 | * @Date 2017/4/25
13 | */
14 |
15 | public class Res
16 | {
17 | /**
18 | * 获取资源文件中的文本
19 | *
20 | * @param string 资源id
21 | * @return
22 | */
23 | public static String string(Context context,int string)
24 | {
25 | return context.getResources().getString(string);
26 | }
27 |
28 | /**
29 | * 获取资源文件中的文本
30 | *
31 | * @param string 资源id
32 | * @param args 填充占位符的参数
33 | * @return
34 | */
35 | public static String string(Context context,int string, Object... args)
36 | {
37 | return context.getResources().getString(string, args);
38 | }
39 |
40 | /**
41 | * 获取资源文件中的尺寸配置
42 | *
43 | * @param dimen 资源id
44 | * @return
45 | */
46 | public static float dimen(Context context,int dimen)
47 | {
48 | return PxConvertUtil.px2dip(context,dimenPx(context,dimen));
49 | }
50 |
51 | /**
52 | * 获取资源文件中尺寸配置,返回的是将dp转换为px的数值
53 | *
54 | * @param dimen 资源id
55 | * @return
56 | */
57 | public static int dimenPx(Context context,int dimen)
58 | {
59 | return context.getResources().getDimensionPixelSize(dimen);
60 | }
61 |
62 | /**
63 | * 获取颜色
64 | *
65 | * @param color 资源id
66 | * @return
67 | */
68 | public static int color(Context context,int color)
69 | {
70 | return context.getResources().getColor(color);
71 | }
72 |
73 |
74 | public static Drawable drawable(Context context,int drawable)
75 | {
76 | return context.getResources().getDrawable(drawable);
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/app/src/main/java/com/desmond/citypicker/tools/SysUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * 文 件 名: SysUtil.java
3 | * 版 权: Copyright (c) 2006-2014 ICS&S Inc, All rights reserved
4 | * 描 述: <描述>
5 | * 修 改 人: Desmond
6 | * 修改时间: 2014-10-16
7 | * 修改版本号: <版本编号>
8 | * 修改履历: <修改内容>
9 | */
10 | package com.desmond.citypicker.tools;
11 |
12 | import android.app.Activity;
13 | import android.content.Context;
14 | import android.view.WindowManager;
15 | import android.view.inputmethod.InputMethodManager;
16 |
17 | import java.lang.reflect.Field;
18 |
19 | /**
20 | * 系统工具类
21 | *
22 | * @author Desmond
23 | * @version [版本号, 2014-10-16]
24 | */
25 | public class SysUtil
26 | {
27 |
28 | private static int statusBarHeight;
29 |
30 | private static int screenWidth;
31 |
32 | public static String getAppId(Context context)
33 | {
34 | return context.getApplicationInfo().packageName;
35 | }
36 |
37 |
38 | /**
39 | * 获取屏幕宽度
40 | *
41 | * @return int
42 | * @author Desmond 2014-11-10 上午10:34:51
43 | */
44 | public static int getScreenWidth(Context context)
45 | {
46 | if (screenWidth > 0) return screenWidth;
47 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
48 | screenWidth = wm.getDefaultDisplay().getWidth();
49 | return screenWidth;
50 | }
51 |
52 |
53 | /**
54 | * 隐藏键盘
55 | *
56 | * @author Desmond 2015-11-25 下午10:21:47
57 | */
58 | public static void hideInput(Activity activity)
59 | {
60 | InputMethodManager manager = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
61 | if (manager == null || activity.getCurrentFocus() == null)
62 | return;
63 | manager.hideSoftInputFromWindow(activity.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
64 | }
65 |
66 |
67 | /**
68 | * 获取状态栏高度(px)
69 | *
70 | * @return
71 | */
72 | public static int getStatusBarHeight(Context context)
73 | {
74 | if (statusBarHeight <= 0)
75 | {
76 | Class> c = null;
77 | Object obj = null;
78 | Field field = null;
79 | int x = 0, sbar = 0;
80 | try
81 | {
82 | c = Class.forName("com.android.internal.R$dimen");
83 | obj = c.newInstance();
84 | field = c.getField("status_bar_height");
85 | x = Integer.parseInt(field.get(obj).toString());
86 | statusBarHeight = context.getResources().getDimensionPixelSize(x);
87 | } catch (Exception e1)
88 | {
89 | e1.printStackTrace();
90 | }
91 |
92 | }
93 | return statusBarHeight;
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/app/src/main/java/com/desmond/citypicker/ui/CityPickerActivity.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker.ui;
2 |
3 | import android.content.Intent;
4 | import android.os.Build;
5 | import android.os.Bundle;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.support.v7.widget.GridLayout;
8 | import android.support.v7.widget.LinearLayoutManager;
9 | import android.text.Editable;
10 | import android.text.TextUtils;
11 | import android.text.TextWatcher;
12 | import android.view.Gravity;
13 | import android.view.LayoutInflater;
14 | import android.view.View;
15 | import android.view.WindowManager;
16 | import android.widget.AdapterView;
17 | import android.widget.AutoCompleteTextView;
18 | import android.widget.Button;
19 | import android.widget.ImageButton;
20 | import android.widget.RelativeLayout;
21 | import android.widget.TextView;
22 |
23 | import com.desmond.citypicker.R;
24 | import com.desmond.citypicker.bean.BaseCity;
25 | import com.desmond.citypicker.bean.GpsCityEvent;
26 | import com.desmond.citypicker.bean.OnDestoryEvent;
27 | import com.desmond.citypicker.bean.Options;
28 | import com.desmond.citypicker.bin.CityPicker;
29 | import com.desmond.citypicker.finals.KEYS;
30 | import com.desmond.citypicker.presenter.CityPickerPresenter;
31 | import com.desmond.citypicker.tools.PxConvertUtil;
32 | import com.desmond.citypicker.tools.Res;
33 | import com.desmond.citypicker.tools.SysUtil;
34 | import com.desmond.citypicker.views.pull2refresh.RefreshRecyclerView;
35 | import com.desmond.citypicker.views.pull2refresh.callback.IOnItemClickListener;
36 | import com.gjiazhe.wavesidebar.WaveSideBar;
37 |
38 | import org.greenrobot.eventbus.EventBus;
39 | import org.greenrobot.eventbus.Subscribe;
40 | import org.greenrobot.eventbus.ThreadMode;
41 |
42 | import java.util.HashMap;
43 | import java.util.List;
44 |
45 | import static com.desmond.citypicker.presenter.CityPickerPresenter.LISHI_REMEN;
46 |
47 | /**
48 | *
49 | */
50 | public class CityPickerActivity extends AppCompatActivity implements View.OnClickListener,
51 | IOnItemClickListener,
52 | WaveSideBar.OnSelectIndexItemListener,
53 | AdapterView.OnItemClickListener,
54 | TextWatcher
55 | {
56 | protected View title;
57 | /**
58 | * 返回按钮
59 | */
60 | protected ImageButton titleBackIb;
61 | /**
62 | * 搜索框
63 | */
64 | protected AutoCompleteTextView titleSearchEt;
65 |
66 |
67 | /**
68 | * 搜索框清空按钮
69 | */
70 | protected ImageButton searchClearIb;
71 |
72 | /**
73 | * 列表
74 | */
75 | protected RefreshRecyclerView contentRrv;
76 | /**
77 | * 检索栏
78 | */
79 | protected WaveSideBar contentWsb;
80 |
81 | /**
82 | * 定位城市+历史城市+热门城市布局
83 | */
84 | protected View headerView;
85 | /**
86 | * 历史城市标题
87 | */
88 | protected TextView historyTitleTv;
89 | /**
90 | * 历史城市容器
91 | */
92 | protected GridLayout historyGroupGl;
93 | /**
94 | * 热门城市标题
95 | */
96 | protected TextView hotTitleTv;
97 | /**
98 | * 热门城市容器
99 | */
100 | protected GridLayout hotGroupGl;
101 | /**
102 | * 自动定位view
103 | */
104 | protected TextView gpsTv;
105 |
106 |
107 | protected CityPickerAdapter adapter;
108 | protected CityPickerPresenter cityPickerPresenter;
109 | protected SearchAdapter searchAdapter;
110 |
111 | /**
112 | * 自定义城市列表数据源
113 | */
114 | protected List datas;
115 |
116 | /**
117 | * 右边拼音首字母检索列表
118 | */
119 | protected List pyIndex;
120 |
121 | /**
122 | * 城市定位
123 | */
124 | protected BaseCity gpsCity;
125 |
126 | /**
127 | * 是否需要显示城市定位
128 | */
129 | protected boolean useGpsCity;
130 |
131 | /**
132 | * 热门城市列表
133 | */
134 | protected List hotCities;
135 |
136 | /**
137 | * 自定义热门城市ids
138 | */
139 | protected String[] hotCitiesId;
140 |
141 |
142 | protected List historyCitys;
143 |
144 | /**
145 | * 最大历史城市数量
146 | */
147 | protected int maxHistory;
148 |
149 |
150 | protected int headerCityWidth;
151 |
152 | protected Options options;
153 |
154 | /**
155 | * 索引缓存
156 | */
157 | protected HashMap indexPosMap;
158 |
159 |
160 | @Override
161 | protected void onCreate(Bundle savedInstanceState)
162 | {
163 | super.onCreate(savedInstanceState);
164 | setContentView(R.layout.city_picker);
165 | init(savedInstanceState);
166 | }
167 |
168 | /**
169 | * 获取传入的参数
170 | */
171 | protected void receiveDatas()
172 | {
173 | options = getIntent().getParcelableExtra(KEYS.OPTIONS);
174 | if (options == null)
175 | options = new Options(this.getApplicationContext());
176 | options.setContext(this.getApplicationContext());
177 | useGpsCity = options.isUseGpsCity();
178 | gpsCity = CityPicker.gpsCity;
179 | hotCitiesId = options.getHotCitiesId();
180 | maxHistory = options.getMaxHistory();
181 | }
182 |
183 | protected void init(Bundle savedInstanceState)
184 | {
185 | receiveDatas();
186 |
187 | registerViews();
188 |
189 | setViewStyle();
190 |
191 | EventBus.getDefault().register(this);
192 |
193 | cityPickerPresenter = new CityPickerPresenter(this.getApplicationContext(), options.getCustomDBName());
194 |
195 | datas = cityPickerPresenter.getCitysSort();
196 | pyIndex = cityPickerPresenter.getIndex();
197 |
198 |
199 | //城市列表适配
200 | adapter = new CityPickerAdapter(this.getApplicationContext(), options.getIndexBarTextColor());
201 | contentRrv.setLayoutManager(new LinearLayoutManager(this.getApplicationContext()));
202 | contentRrv.setAdapter(adapter);
203 | contentRrv.addHeaderView(getHeaderView());
204 | contentRrv.setOnItemClickListener(CityPickerActivity.this);
205 | contentRrv.disablePullLable();
206 | adapter.setData(datas);
207 | adapter.notifyDataSetChanged();
208 |
209 | //设置索引
210 | indexPosMap = new HashMap<>(pyIndex.size());
211 | contentWsb.setIndexItems(pyIndex.toArray(new String[pyIndex.size()]));
212 | contentWsb.setOnSelectIndexItemListener(this);
213 |
214 | //搜索结果适配
215 | searchAdapter = new SearchAdapter(this.getApplicationContext(), datas, cityPickerPresenter);
216 | titleSearchEt.setAdapter(searchAdapter);
217 | titleSearchEt.setOnItemClickListener(this);
218 | }
219 |
220 |
221 | /**
222 | * 修改页面样式
223 | */
224 | protected void setViewStyle()
225 | {
226 | title.setBackgroundDrawable(options.getTitleBarDrawable());
227 |
228 | titleSearchEt.setTextSize(options.getSearchViewTextSize());
229 | titleSearchEt.setTextColor(options.getSearchViewTextColor());
230 | titleSearchEt.setBackgroundDrawable(options.getSearchViewDrawable());
231 |
232 | titleBackIb.setImageDrawable(options.getTitleBarBackBtnDrawable());
233 |
234 | contentWsb.setTextColor(options.getIndexBarTextColor());
235 | contentWsb.setTextSize(options.getIndexBarTextSize());
236 |
237 |
238 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && options.isUseImmerseBar())
239 | {
240 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
241 | int statusBarHeight = SysUtil.getStatusBarHeight(this.getApplicationContext());
242 | RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, statusBarHeight + Res.dimenPx(this.getApplicationContext(), R.dimen.title_bar_height));
243 | title.setPadding(0, statusBarHeight + title.getPaddingTop(), 0, 0);
244 | title.setLayoutParams(params);
245 | }
246 |
247 | }
248 |
249 |
250 | /**
251 | * 设置列表的头部view
252 | *
253 | * @return
254 | */
255 | protected View getHeaderView()
256 | {
257 | if (headerView != null) return headerView;
258 |
259 | headerView = LayoutInflater.from(this.getApplicationContext()).inflate(R.layout.city_picker_header, contentRrv.getRecyclerView(), false);
260 | gpsTv = findById(headerView, R.id.c_p_header_gps_tv);
261 | historyTitleTv = findById(headerView, R.id.c_p_header_historytitle_tv);
262 | historyGroupGl = findById(headerView, R.id.c_p_header_historygroup_gl);
263 | hotTitleTv = findById(headerView, R.id.c_p_header_hottitle_tv);
264 | hotGroupGl = findById(headerView, R.id.c_p_header_hotgroup_gl);
265 |
266 | //动态计算每个按钮的宽度:(屏幕宽度-右边距-左边距)/每行按钮个数-单个按钮的右边距
267 | headerCityWidth = (SysUtil.getScreenWidth(this.getApplicationContext()) - historyGroupGl.getPaddingRight() - historyGroupGl.getPaddingLeft()) / historyGroupGl.getColumnCount() - PxConvertUtil.dip2px(this, 10);
268 |
269 | setHeaderViewValue();
270 | return headerView;
271 | }
272 |
273 | /**
274 | * 为头部布局填充内容及值
275 | */
276 | protected void setHeaderViewValue()
277 | {
278 | setGpsCityStatus(gpsCity);
279 |
280 | // 填充历史城市
281 | maxHistory = maxHistory > CityPickerPresenter.MAX_HEADER_CITY_SIZE ? CityPickerPresenter.MAX_HEADER_CITY_SIZE : maxHistory;
282 | historyCitys = cityPickerPresenter.getHistoryCity(maxHistory);
283 | if (historyCitys != null && historyCitys.size() > 0)
284 | {
285 | historyTitleTv.setVisibility(View.VISIBLE);
286 | historyGroupGl.setVisibility(View.VISIBLE);
287 |
288 | //动态创建button填充到布局中
289 | historyGroupGl.removeAllViews();
290 | for (int i = 0; i < historyCitys.size(); i++)
291 | {
292 | BaseCity city = historyCitys.get(i);
293 | Button btn = getNewButton();
294 | btn.setText(city.getCityName());
295 | btn.setOnClickListener(this);
296 | btn.setTag(city);
297 | btn.setId(R.id.header_city_button);
298 | historyGroupGl.addView(btn);
299 | }
300 | } else
301 | {
302 | historyTitleTv.setVisibility(View.GONE);
303 | historyGroupGl.setVisibility(View.GONE);
304 | }
305 |
306 | // 填充热门城市
307 | //如果用户没有输入自定义热门城市就从本地数据库中获取默认的热门城市
308 | if (hotCitiesId == null || hotCitiesId.length == 0)
309 | hotCities = cityPickerPresenter.getHotCity(CityPickerPresenter.MAX_HEADER_CITY_SIZE);
310 | else
311 | hotCities = cityPickerPresenter.getHotCityById(hotCitiesId);
312 |
313 | if (hotCities != null && !hotCities.isEmpty())
314 | {
315 | hotTitleTv.setVisibility(View.VISIBLE);
316 | hotGroupGl.setVisibility(View.VISIBLE);
317 |
318 | //动态创建button填充到布局中
319 | hotGroupGl.removeAllViews();
320 | for (int i = 0; i < hotCities.size(); i++)
321 | {
322 | BaseCity city = hotCities.get(i);
323 | Button btn = getNewButton();
324 | btn.setText(city.getCityName());
325 | btn.setOnClickListener(this);
326 | btn.setTag(city);
327 | btn.setId(R.id.header_city_button);
328 | hotGroupGl.addView(btn);
329 | }
330 | } else
331 | {
332 | hotTitleTv.setVisibility(View.GONE);
333 | hotGroupGl.setVisibility(View.GONE);
334 | }
335 | }
336 |
337 | /**
338 | * 动态创建一个空的Button
339 | *
340 | * @return
341 | */
342 | protected Button getNewButton()
343 | {
344 | int dp10 = PxConvertUtil.dip2px(this.getApplicationContext(), 10);
345 | int dp3 = PxConvertUtil.dip2px(this.getApplicationContext(), 3);
346 | Button btn = new Button(this);
347 | GridLayout.LayoutParams params = new GridLayout.LayoutParams();
348 | params.height = PxConvertUtil.dip2px(this.getApplicationContext(), 40);
349 | //没有使用权重的原因是当只有一个button的时候宽度会充满全屏
350 | //这里根据屏幕宽度动态计算button的宽度
351 | params.width = headerCityWidth;
352 | params.bottomMargin = dp10;
353 | params.rightMargin = dp10;
354 | btn.setLayoutParams(params);
355 |
356 | btn.setPadding(dp3, dp3, dp3, dp3);
357 | btn.setGravity(Gravity.CENTER);
358 | btn.setBackgroundResource(R.drawable.button_selector);
359 | btn.setEllipsize(TextUtils.TruncateAt.END);
360 | btn.setMaxLines(1);
361 | btn.setTextColor(getResources().getColor(R.color.black));
362 | btn.setTextSize(14);
363 | return btn;
364 | }
365 |
366 |
367 | protected void registerViews()
368 | {
369 | title = findById(R.id.title_root_rl);
370 | titleBackIb = findById(R.id.title_back_ib);
371 | searchClearIb = findById(R.id.title_searchclear_ib);
372 | contentRrv = findById(R.id.c_p_content_rrv);
373 | contentWsb = findById(R.id.c_p_content_wsb);
374 | titleSearchEt = findById(R.id.title_txt_et);
375 | titleBackIb.setOnClickListener(this);
376 | searchClearIb.setOnClickListener(this);
377 | titleSearchEt.addTextChangedListener(this);
378 | }
379 |
380 | @Override
381 | public void onClick(View v)
382 | {
383 | int i = v.getId();
384 | if (i == R.id.title_back_ib)// 返回按钮
385 | {
386 | onBackPressed();
387 |
388 | } else if (i == R.id.title_searchclear_ib)//搜索框清空按钮
389 | {
390 | titleSearchEt.setText("");
391 | } else if (i == R.id.c_p_header_gps_tv)// 点击gps定位
392 | {
393 | whenCitySelected(gpsCity);
394 |
395 | } else if (i == R.id.header_city_button)// 点击热门城市或历史城市
396 | {
397 | whenCitySelected((BaseCity) v.getTag());
398 | }
399 |
400 | }
401 |
402 | protected T findById(int id)
403 | {
404 | return (T) findViewById(id);
405 | }
406 |
407 | protected T findById(View view, int id)
408 | {
409 | return (T) view.findViewById(id);
410 | }
411 |
412 | @Override
413 | public void onItemClick(Object obj, int position)
414 | {
415 | whenCitySelected((BaseCity) obj);
416 | }
417 |
418 | /**
419 | * 城市选择完成后执行的操作
420 | *
421 | * @param city
422 | */
423 | protected void whenCitySelected(BaseCity city)
424 | {
425 | cityPickerPresenter.saveHistoryCity(city);
426 | EventBus.getDefault().post(city);
427 | Intent intent = new Intent();
428 | intent.putExtra(KEYS.SELECTED_RESULT, city);
429 | setResult(RESULT_OK, intent);
430 | finish();
431 | }
432 |
433 |
434 | /**
435 | * 手指在右边索引上滑动的回调
436 | *
437 | * @param index
438 | */
439 | @Override
440 | public void onSelectIndexItem(String index)
441 | {
442 | if (LISHI_REMEN.equals(index))
443 | {
444 | scrollTo(0);
445 | return;
446 | }
447 |
448 | // 行号先从索引缓存中获取,如果缓存中没有再遍历基础数据List
449 | Integer pos = indexPosMap.get(index);
450 | if (pos != null)
451 | {
452 | scrollTo(pos.intValue() + adapter.getHeaderSize());
453 | return;
454 | }
455 |
456 | for (int i = 0; i < datas.size(); i++)
457 | {
458 | if (!datas.get(i).getCityPYFirst().equals(index)) continue;
459 | indexPosMap.put(index, i);
460 | scrollTo(i + adapter.getHeaderSize());
461 | return;
462 | }
463 | }
464 |
465 | protected void scrollTo(int line)
466 | {
467 | ((LinearLayoutManager) contentRrv.getRecyclerView().getLayoutManager()).scrollToPositionWithOffset(line, 0);
468 | }
469 |
470 | /**
471 | * 搜索结果的listview点击
472 | *
473 | * @param parent
474 | * @param view
475 | * @param position
476 | * @param id
477 | */
478 | @Override
479 | public void onItemClick(AdapterView> parent, View view, int position, long id)
480 | {
481 | BaseCity baseCity = (BaseCity) parent.getAdapter().getItem(position);
482 | titleSearchEt.setText(baseCity.getCityName());
483 | whenCitySelected(baseCity);
484 | }
485 |
486 | @Override
487 | public void beforeTextChanged(CharSequence s, int start, int count, int after)
488 | {
489 |
490 | }
491 |
492 | @Override
493 | public void onTextChanged(CharSequence s, int start, int before, int count)
494 | {
495 |
496 | }
497 |
498 | @Override
499 | public void afterTextChanged(Editable s)
500 | {
501 | searchClearIb.setVisibility(s.length() <= 0 ? View.INVISIBLE : View.VISIBLE);
502 | }
503 |
504 | /**
505 | * 城市定位成功
506 | *
507 | * @param event
508 | */
509 | @Subscribe(threadMode = ThreadMode.MAIN)
510 | public void whenLocationSucc(GpsCityEvent event)
511 | {
512 | setGpsCityStatus(event.gpsCity);
513 | }
514 |
515 | private void setGpsCityStatus(BaseCity gpsCity)
516 | {
517 | if (gpsTv == null) return;
518 | if (!useGpsCity)
519 | {
520 | gpsTv.setVisibility(View.GONE);
521 | return;
522 | } else
523 | gpsTv.setVisibility(View.VISIBLE);
524 |
525 | this.gpsCity = gpsCity;
526 | //设置自动定位城市
527 | if (gpsCity != null)
528 | {
529 | gpsTv.setText(gpsCity.getCityName());
530 | gpsTv.setOnClickListener(this);
531 | } else
532 | {
533 | gpsTv.setText(Res.string(this, R.string.location_city_lodding));
534 | gpsTv.setOnClickListener(null);
535 | }
536 | }
537 |
538 | @Override
539 | protected void onDestroy()
540 | {
541 | super.onDestroy();
542 | EventBus.getDefault().post(new OnDestoryEvent());
543 | EventBus.getDefault().unregister(this);
544 | }
545 | }
546 |
--------------------------------------------------------------------------------
/app/src/main/java/com/desmond/citypicker/ui/CityPickerAdapter.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker.ui;
2 |
3 | import android.content.Context;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.text.TextUtils;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.TextView;
9 |
10 | import com.desmond.citypicker.R;
11 | import com.desmond.citypicker.bean.BaseCity;
12 | import com.desmond.citypicker.views.pull2refresh.BaseViewHolder;
13 | import com.desmond.citypicker.views.pull2refresh.SimpleBaseAdapter;
14 |
15 | /**
16 | * @Todo
17 | * @Author desmond
18 | * @Date 2017/5/17
19 | * @Pacakge com.desmond.citypicker
20 | */
21 |
22 | public class CityPickerAdapter extends SimpleBaseAdapter
23 | {
24 | protected int pyTextColor ;
25 | public CityPickerAdapter(Context context,int pyTextColor)
26 | {
27 | super(context);
28 | this.pyTextColor = pyTextColor;
29 | }
30 |
31 | @Override
32 | protected RecyclerView.ViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType)
33 | {
34 | return new BaseViewHolder(parent, R.layout.city_item)
35 | {
36 | private TextView pyTv, nameTv;
37 |
38 | @Override
39 | public void onInitializeView()
40 | {
41 | super.onInitializeView();
42 | pyTv = findViewById(R.id.c_item_py_tv);
43 | nameTv = findViewById(R.id.c_item_name_tv);
44 | }
45 |
46 | @Override
47 | public void setData(BaseCity object, int pos)
48 | {
49 | super.setData(object, pos);
50 | nameTv.setText(object.getCityName());
51 |
52 | boolean isFirst;
53 | if (pos > 0)
54 | isFirst = !TextUtils.equals(getData().get(pos - 1).getCityPYFirst(), object.getCityPYFirst());
55 | else
56 | isFirst = true;
57 |
58 | if (isFirst)
59 | {
60 | pyTv.setTextColor(pyTextColor);
61 | pyTv.setVisibility(View.VISIBLE);
62 | pyTv.setText(object.getCityPYFirst());
63 | } else
64 | pyTv.setVisibility(View.GONE);
65 |
66 | }
67 |
68 | };
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/app/src/main/java/com/desmond/citypicker/ui/SearchAdapter.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker.ui;
2 |
3 | import android.content.Context;
4 | import android.view.View;
5 | import android.view.ViewGroup;
6 | import android.widget.BaseAdapter;
7 | import android.widget.Filter;
8 | import android.widget.Filterable;
9 | import android.widget.TextView;
10 |
11 | import com.desmond.citypicker.R;
12 | import com.desmond.citypicker.bean.BaseCity;
13 | import com.desmond.citypicker.presenter.CityPickerPresenter;
14 | import com.desmond.citypicker.tools.Res;
15 |
16 | import java.util.List;
17 |
18 | /**
19 | * @Todo
20 | * @Author desmond
21 | * @Date 2017/5/19
22 | * @Pacakge com.desmond.citypicker.ui
23 | */
24 |
25 | public class SearchAdapter extends BaseAdapter implements Filterable
26 | {
27 | Context context;
28 | List list;
29 | CityPickerPresenter cityPickerPresenter;
30 |
31 | public SearchAdapter(Context context, List list, CityPickerPresenter cityPickerPresenter)
32 | {
33 | super();
34 | this.context = context;
35 | this.list = list;
36 | this.cityPickerPresenter = cityPickerPresenter;
37 | }
38 |
39 | @Override
40 | public int getCount()
41 | {
42 | return list.size();
43 | }
44 |
45 | @Override
46 | public Object getItem(int position)
47 | {
48 | return list.get(position);
49 | }
50 |
51 | @Override
52 | public long getItemId(int position)
53 | {
54 | return position;
55 | }
56 |
57 | @Override
58 | public View getView(int position, View convertView, ViewGroup parent)
59 | {
60 | ViewHolder viewHolder;
61 | if (convertView == null)
62 | {
63 | viewHolder = new ViewHolder();
64 | convertView = View.inflate(context, android.R.layout.simple_list_item_1, null);
65 | viewHolder.value = (TextView) convertView.findViewById(android.R.id.text1);
66 | viewHolder.value.setTextColor(Res.color(context, R.color.black));
67 | convertView.setTag(viewHolder);
68 | } else
69 | viewHolder = (ViewHolder) convertView.getTag();
70 | BaseCity baseCity = list.get(position);
71 | viewHolder.value.setText(baseCity.getCityName());
72 | return convertView;
73 | }
74 |
75 | class ViewHolder
76 | {
77 | TextView value;
78 | }
79 |
80 | @Override
81 | public Filter getFilter()
82 | {
83 | return new Filter()
84 | {
85 | @Override
86 | protected FilterResults performFiltering(CharSequence constraint)
87 | {
88 | String s = constraint.toString().trim();
89 | FilterResults results = new FilterResults();
90 | if (s.length() == 0)
91 | return results;
92 |
93 | List citys = cityPickerPresenter.searchCity(constraint.toString().trim());
94 | results.count = citys.size();
95 | results.values = citys;
96 | return results;
97 | }
98 |
99 | @Override
100 | protected void publishResults(CharSequence constraint, FilterResults results)
101 | {
102 | SearchAdapter.this.list = (List) results.values;
103 | if (results.count > 0)
104 | notifyDataSetChanged();
105 | else
106 | notifyDataSetInvalidated();
107 | }
108 | };
109 | }
110 |
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/app/src/main/java/com/desmond/citypicker/views/pull2refresh/BaseViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker.views.pull2refresh;
2 |
3 | import android.support.annotation.IdRes;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 |
9 | /**
10 | * Created by 2015/12/19.
11 | */
12 | public class BaseViewHolder extends RecyclerView.ViewHolder {
13 |
14 |
15 | public BaseViewHolder(View itemView) {
16 | super(itemView);
17 | }
18 |
19 | public BaseViewHolder(ViewGroup parent, int layoutId) {
20 | super(LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false));
21 | onInitializeView();
22 | }
23 |
24 | public void onInitializeView() {
25 |
26 | }
27 |
28 | public T findViewById(@IdRes int resId) {
29 | return (T) itemView.findViewById(resId);
30 | }
31 |
32 | public void setData(final T object) {
33 | itemView.setOnClickListener(new View.OnClickListener() {
34 | @Override
35 | public void onClick(View v) {
36 | onItemViewClick(object);
37 | }
38 | });
39 | }
40 |
41 | public void setData(final T object,int pos) {
42 | setData(object);
43 | }
44 |
45 | public void onItemViewClick(T object) {
46 |
47 | }
48 |
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/java/com/desmond/citypicker/views/pull2refresh/RecyclerViewDivider.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker.views.pull2refresh;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Canvas;
6 | import android.graphics.Paint;
7 | import android.graphics.Rect;
8 | import android.graphics.drawable.Drawable;
9 | import android.support.v4.content.ContextCompat;
10 | import android.support.v7.widget.LinearLayoutManager;
11 | import android.support.v7.widget.RecyclerView;
12 | import android.view.View;
13 |
14 | import com.desmond.citypicker.tools.PxConvertUtil;
15 |
16 |
17 | /**
18 | * The type Recycler view divider.
19 | *
20 | * @Todo
21 | * @Author desmond
22 | * @Date 2016 /12/12
23 | * @Pacakge com.chinasoft.widget.recyclerview
24 | */
25 | public class RecyclerViewDivider extends RecyclerView.ItemDecoration
26 | {
27 | /**
28 | * The M paint.
29 | */
30 | private Paint mPaint;
31 | /**
32 | * The M divider.
33 | */
34 | private Drawable mDivider;
35 | /**
36 | * The M divider height.
37 | */
38 | private int mDividerHeight = 2;//分割线高度,默认为1px
39 | /**
40 | * The M orientation.
41 | */
42 | private int mOrientation;//列表的方向:LinearLayoutManager.VERTICAL或LinearLayoutManager.HORIZONTAL
43 |
44 | /**
45 | * The Span count.
46 | */
47 | private int spanCount;
48 | /**
49 | * The constant ATTRS.
50 | */
51 | private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
52 |
53 | /**
54 | * The constant VERTICAL.
55 | */
56 | public static final int VERTICAL = LinearLayoutManager.VERTICAL;
57 |
58 | /**
59 | * The constant HORIZONTAL.
60 | */
61 | public static final int HORIZONTAL = LinearLayoutManager.HORIZONTAL;
62 |
63 | /**
64 | * The constant BOTH.
65 | */
66 | public static final int BOTH = 3;
67 |
68 | /**
69 | * 默认分割线:高度为2px,颜色为灰色
70 | *
71 | * @param context the context
72 | * @param orientation 列表方向
73 | * @param spanCount the span count
74 | */
75 | public RecyclerViewDivider(Context context, int orientation, int spanCount)
76 | {
77 | if (orientation != VERTICAL && orientation != HORIZONTAL && orientation != BOTH)
78 | {
79 | throw new IllegalArgumentException("请输入正确的参数!");
80 | }
81 |
82 | mOrientation = orientation;
83 | this.spanCount = spanCount;
84 | final TypedArray a = context.obtainStyledAttributes(ATTRS);
85 | // mDivider = a.getDrawable(0);
86 | a.recycle();
87 | }
88 |
89 | /**
90 | * 自定义分割线
91 | *
92 | * @param context the context
93 | * @param orientation 列表方向
94 | * @param drawableId 分割线图片
95 | * @param spanCount the span count
96 | */
97 | public RecyclerViewDivider(Context context, int orientation, int drawableId, int spanCount)
98 | {
99 | this(context, orientation, spanCount);
100 | mDivider = ContextCompat.getDrawable(context, drawableId);
101 | mDividerHeight = mDivider.getIntrinsicHeight();
102 | }
103 |
104 | /**
105 | * 自定义分割线
106 | *
107 | * @param context the context
108 | * @param orientation 列表方向
109 | * @param dividerHeight 分割线高度
110 | * @param dividerColor 分割线颜色
111 | * @param spanCount the span count
112 | */
113 | public RecyclerViewDivider(Context context, int orientation, int dividerHeight, int dividerColor, int spanCount)
114 | {
115 | this(context, orientation, spanCount);
116 | mDividerHeight = PxConvertUtil.dip2px(context,dividerHeight);
117 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
118 | mPaint.setStyle(Paint.Style.FILL);
119 | mPaint.setColor(dividerColor);
120 | }
121 |
122 | /**
123 | * Gets item offsets.
124 | *
125 | * @param outRect the out rect
126 | * @param view the view
127 | * @param parent the parent
128 | * @param state the state
129 | */
130 | //获取分割线尺寸
131 | @Override
132 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)
133 | {
134 | super.getItemOffsets(outRect, view, parent, state);
135 | SimpleBaseAdapter adapter = (SimpleBaseAdapter) parent.getAdapter();
136 | int position = parent.getChildLayoutPosition(view);
137 | if (adapter.isHeaderViewPosition(position) || adapter.isFooterViewPosition(position))
138 | {
139 | outRect.set(0, 0, 0, 0);
140 | return;
141 | }
142 | position = position - adapter.getHeaderSize();
143 | int right, bottom;
144 | switch (mOrientation)
145 | {
146 | case VERTICAL:
147 | if (position + 1 == adapter.getRealItemCount())
148 | bottom = 0;
149 | else
150 | bottom = mDividerHeight;
151 | outRect.set(0, 0, 0, bottom);
152 | break;
153 | case HORIZONTAL:
154 | if (position % spanCount == 1)
155 | right = 0;
156 | else
157 | right = mDividerHeight;
158 | outRect.set(0, 0, right, 0);
159 | break;
160 | case BOTH:
161 | if (position % spanCount == 1)
162 | right = 0;
163 | else
164 | right = mDividerHeight;
165 |
166 | if (position + 1 == adapter.getRealItemCount() || (position % spanCount == 0 && position + 2 == adapter.getRealItemCount()))
167 | bottom = 0;
168 | else
169 | bottom = mDividerHeight;
170 | outRect.set(0, 0, right, bottom);
171 | break;
172 | }
173 | }
174 |
175 | /**
176 | * On draw.
177 | *
178 | * @param c the c
179 | * @param parent the parent
180 | * @param state the state
181 | */
182 | //绘制分割线
183 | @Override
184 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)
185 | {
186 | super.onDraw(c, parent, state);
187 | switch (mOrientation)
188 | {
189 | case VERTICAL:
190 | drawVertical(c, parent);
191 | break;
192 | case HORIZONTAL:
193 | drawHorizontal(c, parent);
194 | break;
195 | case BOTH:
196 | drawBoth(c, parent);
197 | break;
198 | }
199 | }
200 |
201 |
202 | /**
203 | * Draw horizontal.
204 | *
205 | * @param canvas the canvas
206 | * @param parent the parent
207 | */
208 | //绘制横向 item 分割线
209 | private void drawHorizontal(Canvas canvas, RecyclerView parent)
210 | {
211 | final int left = parent.getPaddingLeft();
212 | final int right = parent.getMeasuredWidth() - parent.getPaddingRight();
213 | final int childSize = parent.getChildCount();
214 | for (int i = 0; i < childSize; i++)
215 | {
216 | final View child = parent.getChildAt(i);
217 | RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
218 | final int top = child.getBottom() + layoutParams.bottomMargin;
219 | final int bottom = top + mDividerHeight;
220 | if (mPaint != null)
221 | {
222 | canvas.drawRect(left, top, right, bottom, mPaint);
223 | continue;
224 | }
225 | if (mDivider != null)
226 | {
227 | mDivider.setBounds(left, top, right, bottom);
228 | mDivider.draw(canvas);
229 | }
230 |
231 | }
232 | }
233 |
234 | /**
235 | * Draw vertical.
236 | *
237 | * @param canvas the canvas
238 | * @param parent the parent
239 | */
240 | //绘制纵向 item 分割线
241 | private void drawVertical(Canvas canvas, RecyclerView parent)
242 | {
243 | int top = parent.getPaddingTop();
244 | final int childSize = parent.getChildCount();
245 | for (int i = 0; i < childSize; i++)
246 | {
247 | final View child = parent.getChildAt(i);
248 | RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
249 | final int left = child.getRight() + layoutParams.rightMargin;
250 | final int right = left + mDividerHeight;
251 | final int bottom = child.getBottom();
252 | if (mPaint != null)
253 | {
254 | canvas.drawRect(left, top, right, bottom, mPaint);
255 | continue;
256 | }
257 | if (mDivider != null)
258 | {
259 | mDivider.setBounds(left, top, right, bottom);
260 | mDivider.draw(canvas);
261 | }
262 | }
263 | }
264 |
265 | /**
266 | * Draw both.
267 | *
268 | * @param canvas the canvas
269 | * @param parent the parent
270 | */
271 | private void drawBoth(Canvas canvas, RecyclerView parent)
272 | {
273 | int left = 0;
274 | int right = 0;
275 | int bottom = 0;
276 | int top = 0;
277 | final int childSize = parent.getChildCount();
278 | for (int i = 0; i < childSize; i++)
279 | {
280 | final View child = parent.getChildAt(i);
281 | RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
282 |
283 | //横线
284 | left = child.getLeft();
285 | top = child.getBottom() + layoutParams.bottomMargin;
286 | right = child.getRight();
287 | bottom = top + mDividerHeight;
288 | canvas.drawRect(left, top, right, bottom, mPaint);
289 |
290 | //竖线
291 | left = child.getRight() + layoutParams.rightMargin;
292 | top = child.getTop();
293 | right = left + mDividerHeight;
294 | bottom = child.getBottom() + mDividerHeight;
295 | canvas.drawRect(left, top, right, bottom, mPaint);
296 | }
297 | }
298 |
299 | }
300 |
--------------------------------------------------------------------------------
/app/src/main/java/com/desmond/citypicker/views/pull2refresh/RefreshRecyclerView.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker.views.pull2refresh;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.ColorRes;
5 | import android.support.v4.widget.SwipeRefreshLayout;
6 | import android.support.v7.widget.DefaultItemAnimator;
7 | import android.support.v7.widget.RecyclerView;
8 | import android.util.AttributeSet;
9 | import android.view.View;
10 | import android.widget.AbsListView;
11 | import android.widget.LinearLayout;
12 | import android.widget.RelativeLayout;
13 | import android.widget.TextView;
14 |
15 | import com.desmond.citypicker.R;
16 | import com.desmond.citypicker.views.pull2refresh.callback.IOnItemClickListener;
17 | import com.desmond.citypicker.views.pull2refresh.callback.IOnItemLongClickListener;
18 | import com.desmond.citypicker.views.pull2refresh.callback.IOnRefreshListener;
19 |
20 |
21 | /**
22 | * The type Refresh recycler view.
23 | *
24 | * @Todo
25 | * @Author desmond
26 | * @Date 2016 /12/13
27 | * @Pacakge com.chinasoft.mengniu.view.pull2refresh
28 | */
29 | public class RefreshRecyclerView extends RelativeLayout
30 | {
31 | /**
32 | * The Pull srl.
33 | */
34 | private SwipeRefreshLayout pullSrl;
35 |
36 | /**
37 | * The Content rv.
38 | */
39 | private RecyclerView contentRv;
40 |
41 | /**
42 | * The Root.
43 | */
44 | private View root;
45 |
46 | /**
47 | * The M status view.
48 | */
49 | private View footview;
50 |
51 | /**
52 | * The M load more view.
53 | */
54 | private LinearLayout mLoadMoreView;
55 | /**
56 | * The M no more view.
57 | */
58 | private TextView mNoMoreView;
59 |
60 | /**
61 | * The Empty ll.
62 | */
63 | private LinearLayout emptyLl;
64 |
65 |
66 | /**
67 | * The Callback.
68 | */
69 | private IOnRefreshListener callback;
70 |
71 | /**
72 | * The Is refreshing.
73 | */
74 | private volatile boolean isRefreshing, /**
75 | * The Is loadding more.
76 | */
77 | isLoaddingMore, /**
78 | * The Has more.
79 | */
80 | hasMore = true;
81 |
82 | /**
83 | * Instantiates a new Refresh recycler view.
84 | *
85 | * @param context the context
86 | * @param attrs the attrs
87 | */
88 | public RefreshRecyclerView(Context context, AttributeSet attrs)
89 | {
90 | super(context, attrs);
91 | init();
92 | }
93 |
94 |
95 | /**
96 | * Init.
97 | */
98 | private void init()
99 | {
100 | root = inflate(getContext(), R.layout.pull2refresh_recyclerview, this);
101 | pullSrl = (SwipeRefreshLayout) root.findViewById(R.id.pull2_refresh_swiperefreshlayout);
102 | contentRv = (RecyclerView) root.findViewById(R.id.pull2_recycler_recyclerview);
103 | emptyLl = (LinearLayout) root.findViewById(R.id.pull2_refresh_empty_linearlayout);
104 |
105 | contentRv.setHasFixedSize(true);
106 | contentRv.setItemAnimator(new DefaultItemAnimator());
107 |
108 | pullSrl.setEnabled(true);
109 | // ((SimpleItemAnimator)contentRv.getItemAnimator()).setSupportsChangeAnimations(false);
110 |
111 | pullSrl.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener()
112 | {
113 | @Override
114 | public void onRefresh()
115 | {
116 | hasMore = true;
117 | isRefreshing = true;
118 | if (callback != null)
119 | callback.onRefresh();
120 | }
121 | });
122 |
123 | contentRv.addOnScrollListener(new RecyclerView.OnScrollListener()
124 | {
125 | @Override
126 | public void onScrollStateChanged(RecyclerView recyclerView, int newState)
127 | {
128 | super.onScrollStateChanged(recyclerView, newState);
129 | if (newState != AbsListView.OnScrollListener.SCROLL_STATE_IDLE) return;
130 | boolean isBottom = isSlideToBottom();
131 | if (callback != null && isBottom && !isLoaddingMore && !isRefreshing && hasMore)
132 | callback.onLoadMore();
133 | }
134 |
135 | @Override
136 | public void onScrolled(RecyclerView recyclerView, int dx, int dy)
137 | {
138 | }
139 | });
140 | }
141 |
142 |
143 | /**
144 | * Is slide to bottom boolean.
145 | *
146 | * @return the boolean
147 | */
148 | public boolean isSlideToBottom()
149 | {
150 | if (contentRv == null || getAdapter() == null) return false;
151 | return getAdapter().isScoll2Bottom();
152 | }
153 |
154 | /**
155 | * Gets last visible position.
156 | *
157 | * @return the last visible position
158 | */
159 | private int getLastVisiblePosition()
160 | {
161 | View lastVisibleChild = contentRv.getChildAt(contentRv.getChildCount() - 1);
162 | return lastVisibleChild != null ? contentRv.getChildAdapterPosition(lastVisibleChild) : -1;
163 | }
164 |
165 |
166 | /**
167 | * Show load more footer view.
168 | */
169 | public void setLoadMoreLoddingView(View v)
170 | {
171 | this.footview = v;
172 |
173 | addFooterView(footview);
174 | }
175 |
176 | /**
177 | * Show no more view.
178 | */
179 | public void showNoMoreView()
180 | {
181 | footview.setVisibility(VISIBLE);
182 | // mNoMoreView.setVisibility(View.VISIBLE);
183 | mLoadMoreView.setVisibility(View.GONE);
184 | hasMore = false;
185 | }
186 |
187 | /**
188 | * Show load more view.
189 | */
190 | public void showLoadMoreView()
191 | {
192 | footview.setVisibility(VISIBLE);
193 | // mNoMoreView.setVisibility(View.GONE);
194 | mLoadMoreView.setVisibility(View.VISIBLE);
195 | isLoaddingMore = true;
196 | hasMore = true;
197 | }
198 |
199 | /**
200 | * Dismiss load more.
201 | */
202 | public void dismissLoadMore()
203 | {
204 | if (footview != null)
205 | footview.setVisibility(GONE);
206 | isLoaddingMore = false;
207 | }
208 |
209 | /**
210 | * Sets refreshing.
211 | */
212 | public void setRefreshing()
213 | {
214 | pullSrl.post(new Runnable()
215 | {
216 | @Override
217 | public void run()
218 | {
219 | isRefreshing = true;
220 | dismissLoadMore();
221 | pullSrl.setRefreshing(true);
222 | if (callback != null)
223 | callback.onRefresh();
224 | }
225 | });
226 | }
227 |
228 | /**
229 | * Dismiss refresh.
230 | */
231 | public void dismissRefresh()
232 | {
233 | pullSrl.post(new Runnable()
234 | {
235 | @Override
236 | public void run()
237 | {
238 | isRefreshing = false;
239 | pullSrl.setRefreshing(false);
240 | }
241 | });
242 | }
243 |
244 | /**
245 | * Add header view.
246 | *
247 | * @param v the v
248 | */
249 | public void addHeaderView(View v)
250 | {
251 | if (getAdapter() == null) return;
252 | getAdapter().addHeaderView(v);
253 | }
254 |
255 | /**
256 | * Add footer view.
257 | *
258 | * @param v the v
259 | */
260 | public void addFooterView(View v)
261 | {
262 | if (getAdapter() == null) return;
263 | getAdapter().addFooterView(v);
264 | }
265 |
266 | /**
267 | * Remove header view.
268 | *
269 | * @param v the v
270 | */
271 | public void removeHeaderView(View v)
272 | {
273 | if (getAdapter() == null) return;
274 | getAdapter().removeHeaderView(v);
275 | }
276 |
277 | /**
278 | * Remove footer view.
279 | *
280 | * @param v the v
281 | */
282 | public void removeFooterView(View v)
283 | {
284 | if (getAdapter() == null) return;
285 | getAdapter().removeFooterView(v);
286 | }
287 |
288 | /**
289 | * Remove all header view.
290 | */
291 | public void removeAllHeaderView()
292 | {
293 | if (getAdapter() == null) return;
294 | getAdapter().removeAllHeaderView();
295 | }
296 |
297 | /**
298 | * Sets adapter.
299 | *
300 | * @param adapter the adapter
301 | */
302 | public void setAdapter(SimpleBaseAdapter adapter)
303 | {
304 | adapter.setData(null);
305 | contentRv.setAdapter(adapter);
306 | }
307 |
308 | /**
309 | * Gets adapter.
310 | *
311 | * @return the adapter
312 | */
313 | public SimpleBaseAdapter getAdapter()
314 | {
315 | return (SimpleBaseAdapter) contentRv.getAdapter();
316 | }
317 |
318 | /**
319 | * Sets on refresh listener.
320 | *
321 | * @param callback the callback
322 | */
323 | public void setOnRefreshListener(IOnRefreshListener callback)
324 | {
325 | this.callback = callback;
326 | }
327 |
328 | /**
329 | * Sets layout manager.
330 | *
331 | * @param manager the manager
332 | */
333 | public void setLayoutManager(RecyclerView.LayoutManager manager)
334 | {
335 | contentRv.setLayoutManager(manager);
336 | }
337 |
338 | /**
339 | * Add item decoration.
340 | *
341 | * @param decor the decor
342 | */
343 | public void addItemDecoration(RecyclerView.ItemDecoration decor)
344 | {
345 | contentRv.addItemDecoration(decor);
346 | }
347 |
348 |
349 | /**
350 | * Is loadding more boolean.
351 | *
352 | * @return the boolean
353 | */
354 | public boolean isLoaddingMore()
355 | {
356 | return isLoaddingMore;
357 | }
358 |
359 | /**
360 | * Sets loadding more.
361 | *
362 | * @param loaddingMore the loadding more
363 | */
364 | public void setLoaddingMore(boolean loaddingMore)
365 | {
366 | isLoaddingMore = loaddingMore;
367 | }
368 |
369 |
370 | /**
371 | * Sets on item click listener.
372 | *
373 | * @param onItemClickListener the on item click listener
374 | */
375 | public void setOnItemClickListener(IOnItemClickListener> onItemClickListener)
376 | {
377 | if (getAdapter() == null) return;
378 | getAdapter().setOnItemClickListener(onItemClickListener);
379 | }
380 |
381 | /**
382 | * Sets on item long click listener.
383 | *
384 | * @param onItemLongClickListener the on item long click listener
385 | */
386 | public void setOnItemLongClickListener(IOnItemLongClickListener> onItemLongClickListener)
387 | {
388 | if (getAdapter() == null) return;
389 | getAdapter().setOnItemLongClickListener(onItemLongClickListener);
390 | }
391 |
392 |
393 | /**
394 | * Sets empty view.
395 | *
396 | * @param view the view
397 | */
398 | public void setEmptyView(View view)
399 | {
400 | removeEmptyView();
401 | emptyLl.addView(view);
402 | }
403 |
404 | /**
405 | * Remove empty view.
406 | */
407 | public void removeEmptyView()
408 | {
409 | emptyLl.removeAllViews();
410 | }
411 |
412 | /**
413 | * Show empty view.
414 | */
415 | public void showEmptyView()
416 | {
417 | // removeAllHeaderView();
418 | emptyLl.setVisibility(VISIBLE);
419 | }
420 |
421 |
422 | /**
423 | * Hide empty view.
424 | */
425 | public void hideEmptyView()
426 | {
427 | emptyLl.setVisibility(GONE);
428 | }
429 |
430 | public void setColorSchemeResources(@ColorRes int... colorResIds)
431 | {
432 | pullSrl.setColorSchemeResources(colorResIds);
433 | }
434 |
435 | public RecyclerView getRecyclerView()
436 | {
437 | return contentRv;
438 | }
439 |
440 | public SwipeRefreshLayout getSwipeRefreshLayout()
441 | {
442 | return pullSrl;
443 | }
444 |
445 | public void disablePullLable()
446 | {
447 | pullSrl.setEnabled(false);
448 | }
449 | }
450 |
--------------------------------------------------------------------------------
/app/src/main/java/com/desmond/citypicker/views/pull2refresh/SimpleBaseAdapter.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker.views.pull2refresh;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.DrawableRes;
5 | import android.support.v4.util.SparseArrayCompat;
6 | import android.support.v7.widget.GridLayoutManager;
7 | import android.support.v7.widget.RecyclerView;
8 | import android.support.v7.widget.StaggeredGridLayoutManager;
9 | import android.text.TextUtils;
10 | import android.view.LayoutInflater;
11 | import android.view.View;
12 | import android.view.ViewGroup;
13 | import android.widget.ImageView;
14 |
15 | import com.desmond.citypicker.views.pull2refresh.callback.IOnItemClickListener;
16 | import com.desmond.citypicker.views.pull2refresh.callback.IOnItemLongClickListener;
17 |
18 | import java.util.ArrayList;
19 | import java.util.List;
20 |
21 | /**
22 | * The type Simple base adapter.
23 | *
24 | * @param the type parameter
25 | * @Todo
26 | * @Author desmond
27 | * @Date 2016 /12/13
28 | * @Pacakge com.chinasoft.mengniu.view.pull2refresh
29 | */
30 | public class SimpleBaseAdapter extends RecyclerView.Adapter
31 | {
32 | /**
33 | * The Context.
34 | */
35 | protected Context context;
36 | /**
37 | * The List.
38 | */
39 | private List list;
40 |
41 | /**
42 | * The Header views.
43 | */
44 | private SparseArrayCompat headerViews, /**
45 | * The Footer views.
46 | */
47 | footerViews;
48 |
49 | /**
50 | * The constant BASE_ITEM_HEADER_KEY.
51 | */
52 | private static final int BASE_ITEM_HEADER_KEY = 10000;
53 | /**
54 | * The constant BASE_ITEM_FOOTER_KEY.
55 | */
56 | private static final int BASE_ITEM_FOOTER_KEY = 20000;
57 |
58 |
59 | /**
60 | * The On click listener.
61 | */
62 | private View.OnClickListener onClickListener;
63 | /**
64 | * The On item click listener.
65 | */
66 | private IOnItemClickListener onItemClickListener;
67 | /**
68 | * The On item long click listener.
69 | */
70 | private IOnItemLongClickListener onItemLongClickListener;
71 |
72 |
73 | /**
74 | * 是否滚动到底部
75 | */
76 | private boolean isScoll2Bottom;
77 |
78 |
79 | /**
80 | * Instantiates a new Simple base adapter.
81 | *
82 | * @param context the context
83 | */
84 | public SimpleBaseAdapter(Context context)
85 | {
86 | this.context = context;
87 | headerViews = new SparseArrayCompat<>();
88 | footerViews = new SparseArrayCompat<>();
89 | }
90 |
91 |
92 | /**
93 | * Sets on click listener.
94 | *
95 | * @param onClickListener the on click listener
96 | */
97 | public void setOnClickListener(View.OnClickListener onClickListener)
98 | {
99 | this.onClickListener = onClickListener;
100 | }
101 |
102 | /**
103 | * Sets on item click listener.
104 | *
105 | * @param onItemClickListener the on item click listener
106 | */
107 | public void setOnItemClickListener(IOnItemClickListener onItemClickListener)
108 | {
109 | this.onItemClickListener = onItemClickListener;
110 | }
111 |
112 | /**
113 | * Sets on item long click listener.
114 | *
115 | * @param onItemLongClickListener the on item long click listener
116 | */
117 | public void setOnItemLongClickListener(IOnItemLongClickListener onItemLongClickListener)
118 | {
119 | this.onItemLongClickListener = onItemLongClickListener;
120 | }
121 |
122 | /**
123 | * Sets data.
124 | *
125 | * @param list the list
126 | */
127 | public void setData(List list)
128 | {
129 | this.list = list == null ? new ArrayList(0) : list;
130 | }
131 |
132 | /**
133 | * Gets data.
134 | *
135 | * @return the data
136 | */
137 | public List getData()
138 | {
139 | return list;
140 | }
141 |
142 | /**
143 | * Gets item count.
144 | *
145 | * @return the item count
146 | */
147 | @Override
148 | public int getItemCount()
149 | {
150 | return list.size() + getHeaderSize() + getFooterSize();
151 | }
152 |
153 | /**
154 | * Gets real item count.
155 | *
156 | * @return the real item count
157 | */
158 | public int getRealItemCount()
159 | {
160 | return list.size();
161 | }
162 |
163 | /**
164 | * Add header view.
165 | *
166 | * @param v the v
167 | */
168 | public void addHeaderView(View v)
169 | {
170 | headerViews.put(BASE_ITEM_HEADER_KEY + getHeaderSize(), v);
171 | }
172 |
173 | /**
174 | * Add footer view.
175 | *
176 | * @param v the v
177 | */
178 | public void addFooterView(View v)
179 | {
180 | footerViews.put(BASE_ITEM_FOOTER_KEY + getFooterSize(), v);
181 | }
182 |
183 | /**
184 | * Remove header view.
185 | *
186 | * @param v the v
187 | */
188 | public void removeHeaderView(View v)
189 | {
190 | for (int i = 0; i < headerViews.size(); i++)
191 | {
192 | View hv = headerViews.valueAt(i);
193 | if(hv == v)
194 | {
195 | headerViews.removeAt(i);
196 | return;
197 | }
198 | }
199 | }
200 |
201 | /**
202 | * Remove all header view.
203 | */
204 | public void removeAllHeaderView()
205 | {
206 | headerViews.clear();
207 | }
208 |
209 | /**
210 | * Remove footer view.
211 | *
212 | * @param v the v
213 | */
214 | public void removeFooterView(View v)
215 | {
216 | for (int i = 0; i < footerViews.size(); i++)
217 | {
218 | View fv = footerViews.valueAt(i);
219 | if(fv == v)
220 | {
221 | footerViews.removeAt(i);
222 | return;
223 | }
224 | }
225 | }
226 |
227 | /**
228 | * Remove all footer view.
229 | */
230 | public void removeAllFooterView()
231 | {
232 | footerViews.clear();
233 | }
234 |
235 | /**
236 | * Gets header size.
237 | *
238 | * @return the header size
239 | */
240 | public int getHeaderSize()
241 | {
242 | return headerViews.size();
243 | }
244 |
245 | /**
246 | * Gets footer size.
247 | *
248 | * @return the footer size
249 | */
250 | public int getFooterSize()
251 | {
252 | return footerViews.size();
253 | }
254 |
255 |
256 | /**
257 | * Is header view position boolean.
258 | *
259 | * @param position the position
260 | * @return the boolean
261 | */
262 | public boolean isHeaderViewPosition(int position)
263 | {
264 | return position < getHeaderSize();
265 | }
266 |
267 | /**
268 | * Is footer view position boolean.
269 | *
270 | * @param position the position
271 | * @return the boolean
272 | */
273 | public boolean isFooterViewPosition(int position)
274 | {
275 | int start = getHeaderSize() + getRealItemCount();
276 | int end = getItemCount();
277 | return position >= start && position < end;
278 | }
279 |
280 | /**
281 | * Inflate view.
282 | *
283 | * @param layout the layout
284 | * @param parent the parent
285 | * @return the view
286 | */
287 | protected View inflate(int layout, ViewGroup parent)
288 | {
289 | return LayoutInflater.from(this.context).inflate(layout, parent, false);
290 | }
291 |
292 | /**
293 | * On create view holder recycler view . view holder.
294 | *
295 | * @param parent the parent
296 | * @param viewType the view type
297 | * @return the recycler view . view holder
298 | */
299 | @Override
300 | public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
301 | {
302 | if (headerViews.get(viewType) != null)
303 | {
304 | return new BaseViewHolder(headerViews.get(viewType));
305 | }
306 |
307 | if (footerViews.get(viewType) != null)
308 | {
309 | return new BaseViewHolder(footerViews.get(viewType));
310 | }
311 | return onCreateItemViewHolder(parent, viewType);
312 | }
313 |
314 | /**
315 | * On bind view holder.
316 | *
317 | * @param holder the holder
318 | * @param position the position
319 | */
320 | @Override
321 | public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
322 | {
323 | if (isHeaderViewPosition(position) || isFooterViewPosition(position)) return;
324 | isScoll2Bottom = position+1 == getRealItemCount();
325 | onBindItemViewHolder((BaseViewHolder) holder, position - getHeaderSize());
326 |
327 | }
328 |
329 | /**
330 | * On create item view holder recycler view . view holder.
331 | *
332 | * @param parent the parent
333 | * @param viewType the view type
334 | * @return the recycler view . view holder
335 | */
336 | protected RecyclerView.ViewHolder onCreateItemViewHolder(final ViewGroup parent, int viewType)
337 | {
338 | return new BaseViewHolder(parent);
339 | }
340 |
341 |
342 | /**
343 | * On bind item view holder.
344 | *
345 | * @param holder the holder
346 | * @param position the position
347 | */
348 | public void onBindItemViewHolder(BaseViewHolder holder, final int position)
349 | {
350 | if (position >= getRealItemCount())
351 | return;
352 | holder.setData(list.get(position),position);
353 |
354 | holder.itemView.setOnLongClickListener(new View.OnLongClickListener()
355 | {
356 | @Override
357 | public boolean onLongClick(View v)
358 | {
359 | if (onItemLongClickListener == null)
360 | return false;
361 | return onItemLongClickListener.onItemLongClick(list.get(position), position);
362 | }
363 | });
364 |
365 | holder.itemView.setOnClickListener(new View.OnClickListener()
366 | {
367 | @Override
368 | public void onClick(View v)
369 | {
370 | if (onItemClickListener != null)
371 | onItemClickListener.onItemClick(list.get(position), position);
372 | }
373 | });
374 |
375 | }
376 |
377 |
378 | /**
379 | * Gets item view type.
380 | *
381 | * @param position the position
382 | * @return the item view type
383 | */
384 | @Override
385 | public int getItemViewType(int position)
386 | {
387 |
388 | if (isHeaderViewPosition(position))
389 | return headerViews.keyAt(position);
390 | else if (isFooterViewPosition(position))
391 | return footerViews.keyAt(position - getHeaderSize() - getRealItemCount());
392 | return super.getItemViewType(position - getHeaderSize());
393 | }
394 |
395 | /**
396 | * On attached to recycler view.
397 | *
398 | * @param recyclerView the recycler view
399 | */
400 | @Override
401 | public void onAttachedToRecyclerView(RecyclerView recyclerView)
402 | {
403 | super.onAttachedToRecyclerView(recyclerView);
404 |
405 | final RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
406 |
407 | if (manager instanceof GridLayoutManager)
408 | {
409 | final GridLayoutManager gm = (GridLayoutManager) manager;
410 | gm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup()
411 | {
412 | @Override
413 | public int getSpanSize(int position)
414 | {
415 | int viewType = getItemViewType(position);
416 | if (headerViews.get(viewType) != null)
417 | return gm.getSpanCount();
418 | if (footerViews.get(viewType) != null)
419 | return gm.getSpanCount();
420 | return 1;
421 | }
422 | });
423 | gm.setSpanCount(gm.getSpanCount());
424 | }
425 | }
426 |
427 |
428 | /**
429 | * On view attached to window.
430 | *
431 | * @param holder the holder
432 | */
433 | @Override
434 | public void onViewAttachedToWindow(RecyclerView.ViewHolder holder)
435 | {
436 | super.onViewAttachedToWindow(holder);
437 | int position = holder.getLayoutPosition();
438 | if (isHeaderViewPosition(position) || isFooterViewPosition(position))
439 | {
440 | ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
441 | if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams)
442 | {
443 | StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
444 | p.setFullSpan(true);
445 | }
446 | }
447 | }
448 |
449 |
450 | /**
451 | * Is scoll 2 bottom boolean.
452 | *
453 | * @return the boolean
454 | */
455 | public boolean isScoll2Bottom()
456 | {
457 | return isScoll2Bottom;
458 | }
459 |
460 |
461 | /**
462 | * Clear all.
463 | */
464 | public void clearAll()
465 | {
466 | if (list == null) return;
467 | list.clear();
468 | }
469 |
470 | /**
471 | * Add all.
472 | *
473 | * @param list the list
474 | */
475 | public void addAll(List list)
476 | {
477 | if (this.list == null) return;
478 | this.list.addAll(list);
479 | }
480 |
481 |
482 | /**
483 | * Display image.
484 | *
485 | * @param url the url
486 | * @param view the view
487 | * @param rounded the rounded
488 | * @param loddingImg the lodding img
489 | * @param failedImg the failed img
490 | */
491 | protected void displayImage(String url, ImageView view, int rounded, @DrawableRes int loddingImg, @DrawableRes int failedImg)
492 | {
493 | if (!TextUtils.equals(String.valueOf(view.getTag()), url))
494 | {
495 | // LoadImageFactory.getInstance().displayImage(url, view, rounded, loddingImg, failedImg);
496 | view.setTag(url);
497 | }
498 | }
499 | }
500 |
--------------------------------------------------------------------------------
/app/src/main/java/com/desmond/citypicker/views/pull2refresh/callback/IOnItemClickListener.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker.views.pull2refresh.callback;
2 |
3 | /**
4 | * The interface On item click listener.
5 | *
6 | * @param the type parameter
7 | * @Todo
8 | * @Author desmond
9 | * @Date 2016 /12/14
10 | * @Pacakge com.chinasoft.mengniu.view.pull2refresh
11 | */
12 | public interface IOnItemClickListener
13 | {
14 | /**
15 | * On item click.
16 | *
17 | * @param obj the obj
18 | * @param position the position
19 | */
20 | void onItemClick(T obj, int position);
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/desmond/citypicker/views/pull2refresh/callback/IOnItemLongClickListener.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker.views.pull2refresh.callback;
2 |
3 | /**
4 | * The interface On item long click listener.
5 | *
6 | * @param the type parameter
7 | * @Todo
8 | * @Author desmond
9 | * @Date 2016 /12/15
10 | * @Pacakge com.chinasoft.mengniu.view.pull2refresh
11 | */
12 | public interface IOnItemLongClickListener
13 | {
14 | /**
15 | * On item long click boolean.
16 | *
17 | * @param obj the obj
18 | * @param position the position
19 | * @return the boolean
20 | */
21 | boolean onItemLongClick(T obj, int position);
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/desmond/citypicker/views/pull2refresh/callback/IOnRefreshListener.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker.views.pull2refresh.callback;
2 |
3 | /**
4 | * The interface On refresh listener.
5 | *
6 | * @Todo
7 | * @Author desmond
8 | * @Date 2016 /12/13
9 | * @Pacakge com.chinasoft.mengniu.view.pull2refresh
10 | */
11 | public interface IOnRefreshListener
12 | {
13 | /**
14 | * On refresh.
15 | */
16 | void onRefresh();
17 |
18 | /**
19 | * On load more.
20 | */
21 | void onLoadMore();
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/back_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuruizhe/CityPicker/f7829d49eb173f8049841ee84e46cdd2862d3861/app/src/main/res/drawable-xxhdpi/back_normal.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/def_btn_press_bg.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuruizhe/CityPicker/f7829d49eb173f8049841ee84e46cdd2862d3861/app/src/main/res/drawable-xxhdpi/def_btn_press_bg.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/header_city_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/header_city_bg_press.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/press_def_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/white_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/city_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
13 |
14 |
26 |
27 |
38 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/city_picker.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
12 |
17 |
18 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/city_picker_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
24 |
25 |
36 |
37 |
45 |
46 |
47 |
48 |
49 |
50 |
61 |
62 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/citypicker_title_bar.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
24 |
25 |
33 |
34 |
35 |
43 |
44 |
61 |
62 |
70 |
71 |
72 |
79 |
80 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/pull2refresh_recyclerview.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
15 |
16 |
17 |
23 |
24 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuruizhe/CityPicker/f7829d49eb173f8049841ee84e46cdd2862d3861/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuruizhe/CityPicker/f7829d49eb173f8049841ee84e46cdd2862d3861/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuruizhe/CityPicker/f7829d49eb173f8049841ee84e46cdd2862d3861/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuruizhe/CityPicker/f7829d49eb173f8049841ee84e46cdd2862d3861/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuruizhe/CityPicker/f7829d49eb173f8049841ee84e46cdd2862d3861/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 | #2A2A2A
8 |
9 | #FBFBFB
10 |
11 | #FFFFFF
12 |
13 |
14 | #006DCC
15 | #c7ceb2
16 | #006DCC
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 10dp
3 | 45dp
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | CityPicker
3 | 输入城市名称或拼音
4 | 获取位置中...
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/test/java/com/desmond/citypicker/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.desmond.citypicker;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest
13 | {
14 | @Test
15 | public void addition_isCorrect() throws Exception
16 | {
17 | assertEquals(4, 2 + 2);
18 | }
19 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.2.2'
9 | // classpath 'com.novoda:bintray-release:0.5.0'
10 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
11 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7'
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | jcenter()
21 | }
22 | }
23 |
24 | task clean(type: Delete) {
25 | delete rootProject.buildDir
26 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuruizhe/CityPicker/f7829d49eb173f8049841ee84e46cdd2862d3861/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 28 10:00:20 PST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/screenshot/Screenshot_2017-05-22-11-22-45.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuruizhe/CityPicker/f7829d49eb173f8049841ee84e46cdd2862d3861/screenshot/Screenshot_2017-05-22-11-22-45.png
--------------------------------------------------------------------------------
/screenshot/Screenshot_2017-05-22-11-22-58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuruizhe/CityPicker/f7829d49eb173f8049841ee84e46cdd2862d3861/screenshot/Screenshot_2017-05-22-11-22-58.png
--------------------------------------------------------------------------------
/screenshot/Screenshot_2017-05-22-11-23-08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuruizhe/CityPicker/f7829d49eb173f8049841ee84e46cdd2862d3861/screenshot/Screenshot_2017-05-22-11-23-08.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------