├── .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 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 52 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CityPicker 2 | === 3 | 4 | [![API](https://img.shields.io/badge/API-14%2B-yellow.svg?style=flat)](https://android-arsenal.com/api?level=14)
5 | 一个仿大众点评的城市快速选择器, 6 | 最少只需 **一行** 代码即可启动城市选择器, 7 | 支持页面样式修改,多元化自定义 8 | 9 | ScreenShot 10 | --- 11 | 12 | | ![](https://github.com/yuruizhe/CityPicker/blob/master/screenshot/Screenshot_2017-05-22-11-22-58.png) | ![](https://github.com/yuruizhe/CityPicker/blob/master/screenshot/Screenshot_2017-05-22-11-23-08.png) | ![](https://github.com/yuruizhe/CityPicker/blob/master/screenshot/Screenshot_2017-05-22-11-22-45.png) | 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 | ![](https://www.pgyer.com/app/qrcode/ecVs) 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 | --------------------------------------------------------------------------------