├── .gitignore ├── CHANGELOG.md ├── Demo.jpg ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── libs │ ├── qq_mta-sdk-1.6.2.jar │ └── qq_sdk_v3.3.0_lite.jar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── tsy │ │ └── socialsample │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── tsy │ │ │ └── socialsample │ │ │ ├── MainActivity.java │ │ │ └── WBShareActivity.java │ └── res │ │ ├── layout │ │ └── activity_main.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-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── tsy │ └── socialsample │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── social_sdk ├── .gitignore ├── build.gradle ├── libs ├── qq_mta-sdk-1.6.2.jar └── qq_sdk_v3.3.0_lite.jar ├── proguard-rules.pro └── src ├── androidTest └── java │ └── com │ └── tsy │ └── sdk │ └── social │ └── ApplicationTest.java ├── main ├── AndroidManifest.xml ├── java │ └── com │ │ └── tsy │ │ └── sdk │ │ └── social │ │ ├── PlatformConfig.java │ │ ├── PlatformType.java │ │ ├── SSOHandler.java │ │ ├── SocialApi.java │ │ ├── api │ │ └── WXApi.java │ │ ├── listener │ │ ├── AuthListener.java │ │ └── ShareListener.java │ │ ├── qq │ │ └── QQHandler.java │ │ ├── share_media │ │ ├── IShareMedia.java │ │ ├── ShareImageMedia.java │ │ ├── ShareMusicMedia.java │ │ ├── ShareTextImageMedia.java │ │ ├── ShareTextMedia.java │ │ ├── ShareVideoMedia.java │ │ └── ShareWebMedia.java │ │ ├── sina │ │ ├── SinaWBHandler.java │ │ └── WBShareCallbackActivity.java │ │ ├── util │ │ ├── BitmapUtils.java │ │ ├── LogUtils.java │ │ ├── NetUtils.java │ │ └── Utils.java │ │ └── weixin │ │ ├── WXCallbackActivity.java │ │ └── WXHandler.java └── res │ └── values │ └── strings.xml └── test └── java └── com └── tsy └── sdk └── social └── ExampleUnitTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea 5 | .DS_Store 6 | /build 7 | /captures 8 | app2 -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | ## Version 2.0.0 5 | 6 | _2018-01-15_ 7 | 8 | * 将Library上传至JCenter,并分别上传 full 和 core 版本 9 | 10 | ## Version 1.8.0 11 | 12 | _2018-01-08_ 13 | 14 | * 微信更新SDK为1.4.0,采用JCenter引用 15 | * QQ更新SDK为3.3.0 16 | 17 | ## Version 1.7.0 18 | 19 | _2017-04-13_ 20 | 21 | * 微博SDK更新至3.2,jcenter引用sdk,修改分享渠道只能为图文分享 22 | 23 | ## Version 1.6.0 24 | 25 | _2017-02-14_ 26 | 27 | * 增加混淆规则 28 | * 微博抽出分享回调,简化AndroidManifest的填写和回调设置代码 29 | 30 | ## Version 1.5.5 31 | 32 | _2017-01-04_ 33 | 34 | * 微信增加自定义Scope和State 35 | * 微博增加自定义REDIRECT_URL 36 | 37 | ## Version 1.5.4 38 | 39 | _2016-12-15_ 40 | 41 | * 修复未安装微信时的空指针异常 42 | 43 | ## Version 1.5.3 44 | 45 | _2016-12-12_ 46 | 47 | * 修复微信朋友圈分享回调失败的bug 48 | 49 | 50 | ## Version 1.5.2 51 | 52 | _2016-12-09_ 53 | 54 | * 简化微信接入时要创建WXEntryActivity的步骤 55 | 56 | 57 | ## Version 1.4 58 | 59 | _2016-11-13_ 60 | 61 | * 增加可选的微信Api调用 62 | 63 | 64 | ## Version 1.3 65 | 66 | _2016-09-18_ 67 | 68 | * 集成新浪微博登录授权、分享 69 | 70 | 71 | ## Version 1.2 72 | 73 | _2016-09-06_ 74 | 75 | * 将微信appsecret移除,客户端不放appsecret 76 | 77 | 78 | ## Version 1.1 79 | 80 | _2016-08-19_ 81 | 82 | * 集成QQ授权登录,QQ分享,QQ空间分享 83 | 84 | 85 | ## Version 1.0 86 | 87 | _2016-08-12_ 88 | 89 | * 集成微信授权登录,5种分享媒介,微信会话分享,微信朋友圈分享 -------------------------------------------------------------------------------- /Demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsy12321/SocialSDKAndroid/cb22aefdc04241df93c2c20896f4a583fe43b57a/Demo.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SocialSDKAndroid 2 | 3 | [![License](https://img.shields.io/badge/license-Apache%202-green.svg)](https://www.apache.org/licenses/LICENSE-2.0) 4 | 5 | > 对第三方社会化sdk的集成和二次封装,比如第三方授权登录、第三方分享等. 欢迎发issue建议新的功能点和渠道集成 6 | 7 | 原文阅读: 8 | 9 | [http://www.jianshu.com/p/4ec1d9c15763](http://www.jianshu.com/p/4ec1d9c15763) 10 | 11 | 12 | ## 0 版本更新记录 13 | 14 | [版本更新记录](https://github.com/tsy12321/SocialSDKAndroid/blob/master/CHANGELOG.md) 15 | 16 | ## 1 引用 17 | 18 | ### 第一种方式:三个平台包全部包含的Library 19 | 20 | 在项目级别的 `build-gradle` 中添加 21 | 22 | ```groovy 23 | allprojects { 24 | repositories { 25 | maven { url "https://dl.bintray.com/thelasterstar/maven" } //微博sdk maven库 26 | } 27 | } 28 | ``` 29 | 30 | 在app级别的 `build-gradle` 中添加 31 | 32 | ```groovy 33 | dependencies { 34 | compile 'com.tsy.social:social-sdk-full:2.0.0' 35 | } 36 | ``` 37 | 38 | ### 第二种方式:引入核心包,然后自由选择需要的平台SDK 39 | 40 | 在app级别的 `build-gradle` 中添加 41 | 42 | ```groovy 43 | dependencies { 44 | compile 'com.tsy.social:social-sdk-core:2.0.0' 45 | //social sdk 自由选择 46 | //微信sdk 47 | // compile 'com.tencent.mm.opensdk:wechat-sdk-android-without-mta:1.4.0' 48 | //QQ SDK 49 | // compile files('libs/qq_mta-sdk-1.6.2.jar') 50 | // compile files('libs/qq_sdk_v3.3.0_lite.jar') 51 | //微博SDK 52 | // compile 'com.sina.weibo.sdk:core:1.0.0:openDefaultRelease@aar' 53 | } 54 | ``` 55 | 56 | 如果使用了微博SDK,需要在项目级别的 `build-gradle` 中添加 57 | 58 | ```groovy 59 | allprojects { 60 | repositories { 61 | maven { url "https://dl.bintray.com/thelasterstar/maven" } //微博sdk maven库 62 | } 63 | } 64 | ``` 65 | 这种方式可以减少sdk的体积,需要什么平台就引入哪个平台.更为合理. 66 | 67 | **注意:该方式在sync时可能会报引用错误(如果没有使用微博的SDK),可以忽略** 68 | 69 | ### 1.1 目录介绍 70 | 71 | - app/ Demo代码 72 | - social_sdk/ sdk的开发源码module 开发完成后用gradle中makejar打成jar包 73 | - qq_sdk/ qq sdk 74 | 75 | ### 1.2 Demo介绍 76 | 77 | 替换Demo中的MainActivity中的qq appid、wx appid、weibo appkey为自己的 78 | 79 | ```java 80 | public class MainActivity extends AppCompatActivity implements IWeiboHandler.Response{ 81 | 82 | ... 83 | 84 | private static final String WX_APPID = "your wx appid"; //申请的wx appid 85 | private static final String QQ_APPID = "your qq appid"; //申请的qq appid 86 | private static final String SINA_WB_APPKEY = "your sina wb appkey"; //申请的新浪微博 appkey 87 | 88 | ... 89 | } 90 | ``` 91 | 92 | 替换AndroidManifest中的qq appid为自己的 93 | 94 | ```java 95 | 96 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | ``` 108 | 109 | 替换builde.gradle文件中的签名为自己的app签名。 110 | 111 | 修改 `build.gradle` 中的 `applicationID` 和 项目的 `PackageName` (右键IDE的项目树,修改包名) 112 | 113 | 修改完上面4个地方后即可跑通Demo。如下 114 | 115 | ![Demo](https://github.com/tsy12321/SocialSDKAndroid/blob/master/Demo.jpg) 116 | 117 | ## 2 功能介绍 118 | 119 | ### 2.1 授权登录 120 | 121 | 1. 微信授权登录 122 | 2. QQ授权登录 123 | 3. 新浪微博授权登录 124 | 125 | ### 2.2 分享 126 | 127 | #### 2.2.1 分享媒介 128 | 129 | 1. 文字 130 | 1. 图片 131 | 1. 音乐 132 | 1. 视频 133 | 1. 网页 134 | 135 | #### 2.2.2 分享平台 136 | 137 | 1. 微信会话分享 138 | 1. 微信朋友圈分享 139 | 1. QQ分享 140 | 1. QQ空间分享 141 | 1. 新浪微博分享 142 | 143 | ### 2.3 API调用 144 | 145 | SDK中封装了部分API,比如当第三方授权成功后获取到access_token,会提供一些API查询用户信息,刷新access_token等。 146 | 147 | **!注意:由于调用这些API会用到appsecret,所以一般不建议放到客户端做** 148 | 149 | 具体API见底附录。 150 | 151 | 152 | ## 3 开发说明 153 | 154 | ### 3.1 准备 155 | 156 | AndroidManifest加上以下基本的权限(之后各个平台会注册一些不同的信息后面会说明) 157 | 158 | ```xml 159 | 160 | 161 | 162 | 163 | 164 | ``` 165 | 166 | ### 3.2 配置平台信息 167 | 168 | 在项目入口(或者在调用前)需要配置平台的信息,配置一次即可. 169 | 170 | ```java 171 | PlatformConfig.setWeixin(WX_APPID); 172 | PlatformConfig.setQQ(QQ_APPID); 173 | PlatformConfig.setSinaWB(SINA_WB_APPKEY); 174 | ``` 175 | 176 | ### 3.3 接口使用说明 177 | 178 | 调用方式使用api调用登录或者分享接口,在参数中区别平台,实现回调接收成功、取消或者失败的结果. 179 | 180 | 示例如下:(某些平台会有一些特殊处理后面会在平台中说明) 181 | 182 | 初始化api: 183 | 184 | ```java 185 | SocialApi mSocialApi = SocialApi.get(getApplicationContext()); 186 | ``` 187 | 188 | 登录授权: 189 | ```java 190 | mSocialApi.doOauthVerify(this, PlatformType.WEIXIN, new AuthListener() { 191 | @Override 192 | public void onComplete(PlatformType platform_type, Map map) { 193 | Log.i("tsy", "oncomplete:" + map); 194 | } 195 | 196 | @Override 197 | public void onError(PlatformType platform_type, String err_msg) { 198 | Log.i("tsy", "onError:" + err_msg); 199 | } 200 | 201 | @Override 202 | public void onCancel(PlatformType platform_type) { 203 | Log.i("tsy", "onCancel"); 204 | } 205 | }); 206 | ``` 207 | 208 | 分享: 209 | ```java 210 | 211 | //分享媒介 后面有详细介绍 212 | ShareWebMedia shareMedia = new ShareWebMedia(); 213 | shareMedia.setTitle("分享网页测试"); 214 | shareMedia.setDescription("分享网页测试"); 215 | shareMedia.setWebPageUrl("http://www.baidu.com"); 216 | shareMedia.setThumb(BitmapUtils.readBitMap(getApplicationContext(), R.mipmap.ic_launcher)); 217 | 218 | mSocialApi.doShare(this, PlatformType.WEIXIN, shareMedia, new ShareListener() { 219 | @Override 220 | public void onComplete(PlatformType platform_type) { 221 | Log.i("tsy", "share onComplete"); 222 | } 223 | 224 | @Override 225 | public void onError(PlatformType platform_type, String err_msg) { 226 | Log.i("tsy", "share onError:" + err_msg); 227 | } 228 | 229 | @Override 230 | public void onCancel(PlatformType platform_type) { 231 | Log.i("tsy", "share onCancel"); 232 | } 233 | }); 234 | 235 | ``` 236 | 237 | ### 3.4 分享媒介 238 | 239 | 现在集成了文字分享,图片分享,音乐分享,视频分享,网页分享5种分享媒介.不同的平台可能只有其中某几种. 240 | 241 | #### 3.4.1 文字分享 242 | 243 | ```java 244 | ShareTextMedia shareMedia = new ShareTextMedia(); 245 | shareMedia.setText("分享文字测试"); 246 | ``` 247 | #### 3.4.2 图片分享 248 | 249 | ```java 250 | ShareImageMedia shareMedia = new ShareImageMedia(); 251 | shareMedia.setImage(BitmapUtils.readBitMap(getApplicationContext(), R.mipmap.ic_launcher)); 252 | ``` 253 | 254 | #### 3.4.3 音乐分享 255 | 256 | ```java 257 | ShareMusicMedia shareMedia = new ShareMusicMedia(); 258 | shareMedia.setTitle("分享音乐测试"); 259 | shareMedia.setDescription("分享音乐测试"); 260 | shareMedia.setMusicUrl("http://idg-tangsiyuan.tunnel.nibaguai.com/splash/music.mp3"); 261 | shareMedia.setThumb(BitmapUtils.readBitMap(getApplicationContext(), R.mipmap.ic_launcher)); 262 | ``` 263 | 264 | #### 3.4.4 视频分享 265 | 266 | ```java 267 | ShareVideoMedia shareMedia = new ShareVideoMedia(); 268 | shareMedia.setTitle("分享视频测试"); 269 | shareMedia.setDescription("分享视频测试"); 270 | shareMedia.setVideoUrl("http://idg-tangsiyuan.tunnel.nibaguai.com/splash/music.mp3"); 271 | shareMedia.setThumb(BitmapUtils.readBitMap(getApplicationContext(), R.mipmap.ic_launcher)); 272 | ``` 273 | 274 | #### 3.4.5 网页分享 275 | 276 | ```java 277 | ShareWebMedia shareMedia = new ShareWebMedia(); 278 | shareMedia.setTitle("分享网页测试"); 279 | shareMedia.setDescription("分享网页测试"); 280 | shareMedia.setWebPageUrl("http://www.baidu.com"); 281 | shareMedia.setThumb(BitmapUtils.readBitMap(getApplicationContext(), R.mipmap.ic_launcher)); 282 | ``` 283 | 284 | ## 4 第三方平台接入 285 | 286 | ### 4.1 微信 287 | 288 | #### 4.1.1 集成sdk 289 | 290 | ``` 291 | compile 'com.tencent.mm.opensdk:wechat-sdk-android-without-mta:1.4.0' 292 | ``` 293 | 294 | #### 4.1.2 配置 295 | 296 | AndroidManifest中添加: 297 | 298 | ```java 299 | 305 | 309 | ``` 310 | 311 | #### 4.1.3 常量定义 312 | 313 | 设置配置信息: 314 | 315 | ```java 316 | PlatformConfig.setWeixin(WX_APPID); 317 | ``` 318 | 319 | PlatformType: 320 | 321 | 微信:PlatformType.WEIXIN(可用于登录和微信回话分享) 322 | 323 | 朋友圈:PlatformType.WEIXIN_CIRCLE(用于微信朋友圈分享) 324 | 325 | 326 | #### 4.1.4 自定义scope state 327 | 328 | 可以通过接口setScopeState修改 scope 和 state。 329 | 330 | 默认scope是 "snsapi_userinfo,snsapi_friend,snsapi_message" 331 | 默认state是 "none" 332 | 333 | ```java 334 | WXHandler.setScopeState("your scope", "your state"); 335 | ``` 336 | 337 | #### 4.1.5 注意 338 | 339 | 使用微信登录分享需要签名打包,并且签名和包名要和微信平台填入的信息一致。 340 | 341 | 342 | ### 4.2 QQ 343 | 344 | #### 4.2.1 集成sdk 345 | 346 | 将目录中的qq_mta-sdk-1.6.2.jar和qq_sdk_v3.3.0_lite.jar放入项目. 347 | 348 | #### 4.2.2 配置 349 | 350 | AndroidManifest中添加: 351 | 352 | ```java 353 | 354 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 369 | ``` 370 | 371 | #### 4.2.3 常量定义 372 | 373 | 设置配置信息: 374 | 375 | ```java 376 | PlatformConfig.setQQ(QQ_APPID); 377 | ``` 378 | 379 | PlatformType: 380 | 381 | 微信:PlatformType.QQ(可用于登录和QQ分享) 382 | 383 | 朋友圈:PlatformType.QZONE(用于qq控件分享) 384 | 385 | #### 4.2.4 注意 386 | 387 | 使用QQ登录需要签名打包,并且签名和包名要和QQ开放平台填入的信息一致。 388 | 389 | #### 4.2.5 回调设置 390 | 391 | 要在onActivityResult添加以下 392 | 393 | ```java 394 | @Override 395 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 396 | super.onActivityResult(requestCode, resultCode, data); 397 | 398 | mSocialApi.onActivityResult(requestCode, resultCode, data); 399 | } 400 | ``` 401 | 402 | ### 4.3 新浪微博 403 | 404 | #### 4.3.1 集成sdk 405 | 406 | 在项目根目录的build.gradle中增加中央仓库 407 | 408 | ``` 409 | maven { url "https://dl.bintray.com/thelasterstar/maven/" } 410 | ``` 411 | 412 | 在需要引入SDK的module目录的build.gradle中引入sdk-core依赖 413 | 414 | ``` 415 | compile 'com.sina.weibo.sdk:core:1.0.0:openDefaultRelease@aar' 416 | ``` 417 | 418 | #### 4.3.2 配置 419 | 420 | 创建文件 `WBShareActivity` 继承 `WBShareCallbackActivity` 421 | 422 | ```java 423 | public class WBShareActivity extends WBShareCallbackActivity { 424 | } 425 | ``` 426 | 427 | AndroidManifest中添加: 428 | 429 | ```java 430 | 431 | 436 | 440 | 441 | 442 | 443 | 444 | 445 | ``` 446 | 447 | #### 4.3.3 回调设置 448 | 449 | 要在onActivityResult添加以下(如果qq已经添加则不需要重复添加) 450 | 451 | ```java 452 | @Override 453 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 454 | super.onActivityResult(requestCode, resultCode, data); 455 | 456 | mSocialApi.onActivityResult(requestCode, resultCode, data); 457 | } 458 | ``` 459 | 460 | #### 4.3.4 常量定义 461 | 462 | 设置配置信息: 463 | 464 | ```java 465 | PlatformConfig.setSinaWB(SINA_WB_APPKEY); 466 | ``` 467 | 468 | #### 4.3.5 自定义REDIRECT_URL 469 | 470 | 可以通过接口setRedirctUrl修改 REDIRECT_URL 471 | 472 | 默认REDIRECT_URL是 "https://api.weibo.com/oauth2/default.html" 473 | 474 | ```java 475 | SinaWBHandler.setRedirctUrl("your RedirctUrl"); 476 | ``` 477 | 478 | #### 4.3.6 注意 479 | 480 | 使用新浪登录分享需要签名打包,并且签名和包名要和新浪平台填入的信息一致。 481 | 482 | 并且微博开放平台的回调地址(REDIRECT_URL)要和代码中的REDIRECT_URL一致 483 | 484 | ## 附录 485 | 486 | ### API列表 487 | 488 | #### 1 微信API (WXApi) 489 | 490 | 1. 获取access_token 491 | 492 | ```java 493 | /** 494 | * 获取access_token 495 | * @param wxAppId wx appid 496 | * @param wxAppSecret wx appsecret 497 | * @param code 调用微信登录获取的code 498 | * @param callback 499 | */ 500 | ``` 501 | getAccessToken(String wxAppId, String wxAppSecret, 502 | String code, 503 | final Callback callback) 504 | 505 | 1. 获取用户信息 506 | 507 | ```java 508 | /** 509 | * 获取用户信息 510 | * @param openid openid 511 | * @param access_token access_token 512 | * @param callback 513 | */ 514 | ``` 515 | getUserInfo(String openid, String access_token, 516 | final Callback callback) 517 | 518 | ## 混淆 519 | 520 | ``` 521 | #social 522 | -dontwarn com.tsy.sdk.social.** 523 | -keep class com.tsy.sdk.social.**{*;} 524 | 525 | #qq & weixin 526 | -dontwarn com.tencent.** 527 | -keep class com.tencent.** {*;} 528 | 529 | #sina 530 | -dontwarn com.sina.** 531 | -keep class com.sina.** {*;} 532 | ``` 533 | 534 | ## About Me 535 | 简书地址:http://www.jianshu.com/users/21716b19302d/latest_articles 536 | 537 | 微信公众号 538 | 539 | ![我的公众号](https://github.com/tsy12321/PayAndroid/blob/master/wxmp_avatar.jpg) 540 | 541 | License 542 | ------- 543 | 544 | Copyright 2017 SY.Tang 545 | 546 | Licensed under the Apache License, Version 2.0 (the "License"); 547 | you may not use this file except in compliance with the License. 548 | You may obtain a copy of the License at 549 | 550 | http://www.apache.org/licenses/LICENSE-2.0 551 | 552 | Unless required by applicable law or agreed to in writing, software 553 | distributed under the License is distributed on an "AS IS" BASIS, 554 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 555 | See the License for the specific language governing permissions and 556 | limitations under the License. 557 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "24.0.3" 6 | 7 | defaultConfig { 8 | applicationId "com.tsy.socialsample" 9 | minSdkVersion 14 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | 15 | // signingConfigs { 16 | // release { 17 | // storeFile file("your sign file") 18 | // storePassword "your password" 19 | // keyAlias "your keyAlias" 20 | // keyPassword "your keyPassword" 21 | // } 22 | // } 23 | // 24 | // buildTypes { 25 | // debug { 26 | // signingConfig signingConfigs.release 27 | // } 28 | // release { 29 | // minifyEnabled false 30 | // proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 31 | // } 32 | // } 33 | } 34 | 35 | buildscript { 36 | repositories { 37 | mavenCentral() 38 | } 39 | dependencies { 40 | classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' 41 | } 42 | } 43 | 44 | apply plugin: 'com.neenbedankt.android-apt' 45 | 46 | dependencies { 47 | testCompile 'junit:junit:4.12' 48 | compile 'com.android.support:appcompat-v7:24.2.1' 49 | //ButterKnife 50 | compile 'com.jakewharton:butterknife:8.2.1' 51 | apt 'com.jakewharton:butterknife-compiler:8.2.1' 52 | compile 'pub.devrel:easypermissions:1.0.1' 53 | 54 | compile 'com.tsy.social:social-sdk-full:2.0.0' 55 | // compile 'com.tsy.social:social-sdk-core:2.0.0' 56 | // compile project(':social_sdk') 57 | //social sdk 58 | // compile 'com.tencent.mm.opensdk:wechat-sdk-android-without-mta:1.4.0' 59 | // compile files('libs/qq_mta-sdk-1.6.2.jar') 60 | // compile files('libs/qq_sdk_v3.3.0_lite.jar') 61 | // compile 'com.sina.weibo.sdk:core:1.0.0:openDefaultRelease@aar' 62 | } 63 | 64 | -------------------------------------------------------------------------------- /app/libs/qq_mta-sdk-1.6.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsy12321/SocialSDKAndroid/cb22aefdc04241df93c2c20896f4a583fe43b57a/app/libs/qq_mta-sdk-1.6.2.jar -------------------------------------------------------------------------------- /app/libs/qq_sdk_v3.3.0_lite.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsy12321/SocialSDKAndroid/cb22aefdc04241df93c2c20896f4a583fe43b57a/app/libs/qq_sdk_v3.3.0_lite.jar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/tsy/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/tsy/socialsample/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.tsy.socialsample; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 31 | 35 | 36 | 37 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 52 | 53 | 54 | 59 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/tsy/socialsample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.tsy.socialsample; 2 | 3 | import android.Manifest; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.graphics.Bitmap; 7 | import android.graphics.BitmapFactory; 8 | import android.os.Bundle; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.util.Log; 11 | import android.widget.RadioGroup; 12 | import android.widget.Toast; 13 | 14 | import com.tsy.sdk.social.PlatformConfig; 15 | import com.tsy.sdk.social.PlatformType; 16 | import com.tsy.sdk.social.SocialApi; 17 | import com.tsy.sdk.social.listener.AuthListener; 18 | import com.tsy.sdk.social.listener.ShareListener; 19 | import com.tsy.sdk.social.share_media.IShareMedia; 20 | import com.tsy.sdk.social.share_media.ShareImageMedia; 21 | import com.tsy.sdk.social.share_media.ShareMusicMedia; 22 | import com.tsy.sdk.social.share_media.ShareTextImageMedia; 23 | import com.tsy.sdk.social.share_media.ShareTextMedia; 24 | import com.tsy.sdk.social.share_media.ShareVideoMedia; 25 | import com.tsy.sdk.social.share_media.ShareWebMedia; 26 | 27 | import java.io.InputStream; 28 | import java.util.List; 29 | import java.util.Map; 30 | 31 | import butterknife.BindView; 32 | import butterknife.ButterKnife; 33 | import butterknife.OnClick; 34 | import pub.devrel.easypermissions.EasyPermissions; 35 | 36 | public class MainActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks { 37 | 38 | private static final int REQUEST_LOCATION = 1; 39 | @BindView(R.id.radioGShareMedia) 40 | RadioGroup radioGShareMedia; 41 | 42 | @BindView(R.id.radioGSharePlatform) 43 | RadioGroup radioGSharePlatform; 44 | 45 | private static final String WX_APPID = "your wx appid"; //申请的wx appid 46 | private static final String QQ_APPID = "your qq appid"; //申请的qq appid 47 | private static final String SINA_WB_APPKEY = "your sina wb appkey"; //申请的新浪微博 appkey 48 | 49 | private SocialApi mSocialApi; 50 | 51 | @Override 52 | protected void onCreate(Bundle savedInstanceState) { 53 | super.onCreate(savedInstanceState); 54 | setContentView(R.layout.activity_main); 55 | ButterKnife.bind(this); 56 | 57 | PlatformConfig.setWeixin(WX_APPID); 58 | PlatformConfig.setQQ(QQ_APPID); 59 | PlatformConfig.setSinaWB(SINA_WB_APPKEY); 60 | 61 | mSocialApi = SocialApi.get(getApplicationContext()); 62 | } 63 | 64 | /** 65 | * 微信登录 66 | */ 67 | @OnClick(R.id.btnWXLogin) 68 | public void onWXLogin() { 69 | mSocialApi.doOauthVerify(this, PlatformType.WEIXIN , new MyAuthListener()); 70 | } 71 | 72 | /** 73 | * qq登录 74 | */ 75 | @OnClick(R.id.btnQQLogin) 76 | public void onQQLogin() { 77 | mSocialApi.doOauthVerify(this, PlatformType.QQ, new MyAuthListener()); 78 | } 79 | 80 | /** 81 | * 新浪微博登录 82 | */ 83 | @OnClick(R.id.btnSinaWBLogin) 84 | public void onSinaWBLogin() { 85 | mSocialApi.doOauthVerify(this, PlatformType.SINA_WB, new MyAuthListener()); 86 | } 87 | 88 | @Override 89 | protected void onResume() { 90 | super.onResume(); 91 | 92 | //申请权限 93 | String[] perms = {Manifest.permission.WRITE_EXTERNAL_STORAGE}; 94 | if (!EasyPermissions.hasPermissions(this, perms)) { 95 | EasyPermissions.requestPermissions(this, "need access external storage", REQUEST_LOCATION, perms); 96 | } 97 | } 98 | 99 | @Override 100 | public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { 101 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 102 | EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); 103 | } 104 | 105 | @Override 106 | public void onPermissionsGranted(int requestCode, List perms) { 107 | 108 | } 109 | 110 | @Override 111 | public void onPermissionsDenied(int requestCode, List perms) { 112 | if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { 113 | Toast.makeText(getApplicationContext(), "前往设置开启访问存储空间权限", Toast.LENGTH_SHORT).show(); 114 | finish(); 115 | } 116 | } 117 | 118 | public class MyAuthListener implements AuthListener { 119 | @Override 120 | public void onComplete(PlatformType platform_type, Map map) { 121 | Toast.makeText(MainActivity.this, platform_type + " login onComplete", Toast.LENGTH_SHORT).show(); 122 | Log.i("tsy", "login onComplete:" + map); 123 | } 124 | 125 | @Override 126 | public void onError(PlatformType platform_type, String err_msg) { 127 | Toast.makeText(MainActivity.this, platform_type + " login onError:" + err_msg, Toast.LENGTH_SHORT).show(); 128 | Log.i("tsy", "login onError:" + err_msg); 129 | } 130 | 131 | @Override 132 | public void onCancel(PlatformType platform_type) { 133 | Toast.makeText(MainActivity.this, platform_type + " login onCancel", Toast.LENGTH_SHORT).show(); 134 | Log.i("tsy", "login onCancel"); 135 | } 136 | } 137 | 138 | @OnClick(R.id.btnShare) 139 | public void onShare() { 140 | //获取分享类型 141 | IShareMedia shareMedia; 142 | switch (radioGShareMedia.getCheckedRadioButtonId()) { 143 | case R.id.radioShareText: 144 | shareMedia = new ShareTextMedia(); 145 | ((ShareTextMedia)shareMedia).setText("分享文字测试"); 146 | break; 147 | 148 | case R.id.radioShareImage: 149 | shareMedia = new ShareImageMedia(); 150 | ((ShareImageMedia)shareMedia).setImage(readBitMap(getApplicationContext(), R.mipmap.ic_launcher)); 151 | break; 152 | 153 | case R.id.radioShareTextImage: 154 | shareMedia = new ShareTextImageMedia(); 155 | ((ShareTextImageMedia)shareMedia).setText("分享文字测试"); 156 | ((ShareTextImageMedia)shareMedia).setImage(readBitMap(getApplicationContext(), R.mipmap.ic_launcher)); 157 | break; 158 | 159 | case R.id.radioShareMusic: 160 | shareMedia = new ShareMusicMedia(); 161 | ((ShareMusicMedia)shareMedia).setTitle("分享音乐测试"); 162 | ((ShareMusicMedia)shareMedia).setDescription("分享音乐测试"); 163 | ((ShareMusicMedia)shareMedia).setMusicUrl("http://tsy.tunnel.nibaguai.com/splash/music.mp3"); 164 | ((ShareMusicMedia)shareMedia).setThumb(readBitMap(getApplicationContext(), R.mipmap.ic_launcher)); 165 | break; 166 | 167 | case R.id.radioShareVideo: 168 | shareMedia = new ShareVideoMedia(); 169 | ((ShareVideoMedia)shareMedia).setTitle("分享视频测试"); 170 | ((ShareVideoMedia)shareMedia).setDescription("分享视频测试"); 171 | ((ShareVideoMedia)shareMedia).setVideoUrl("http://tsy.tunnel.nibaguai.com/splash/music.mp3"); 172 | ((ShareVideoMedia)shareMedia).setThumb(readBitMap(getApplicationContext(), R.mipmap.ic_launcher)); 173 | break; 174 | 175 | case R.id.radioShareWeb: 176 | shareMedia = new ShareWebMedia(); 177 | ((ShareWebMedia)shareMedia).setTitle("分享网页测试"); 178 | ((ShareWebMedia)shareMedia).setDescription("分享网页测试"); 179 | ((ShareWebMedia)shareMedia).setWebPageUrl("http://www.baidu.com"); 180 | ((ShareWebMedia)shareMedia).setThumb(readBitMap(getApplicationContext(), R.mipmap.ic_launcher)); 181 | break; 182 | 183 | default: 184 | return; 185 | } 186 | 187 | //分享渠道 188 | switch (radioGSharePlatform.getCheckedRadioButtonId()) { 189 | case R.id.radioShareWX: 190 | mSocialApi.doShare(this, PlatformType.WEIXIN, shareMedia, new MyShareListener()); 191 | break; 192 | 193 | case R.id.radioShareWXCircle: 194 | mSocialApi.doShare(this, PlatformType.WEIXIN_CIRCLE, shareMedia, new MyShareListener()); 195 | break; 196 | 197 | case R.id.radioShareQQ: 198 | mSocialApi.doShare(this, PlatformType.QQ, shareMedia, new MyShareListener()); 199 | break; 200 | 201 | case R.id.radioShareQZone: 202 | mSocialApi.doShare(this, PlatformType.QZONE, shareMedia, new MyShareListener()); 203 | break; 204 | 205 | case R.id.radioShareSinaWB: 206 | mSocialApi.doShare(this, PlatformType.SINA_WB, shareMedia, new MyShareListener()); 207 | break; 208 | 209 | default: 210 | return; 211 | } 212 | } 213 | 214 | public class MyShareListener implements ShareListener { 215 | 216 | @Override 217 | public void onComplete(PlatformType platform_type) { 218 | Toast.makeText(MainActivity.this, platform_type + " share onComplete", Toast.LENGTH_SHORT).show(); 219 | } 220 | 221 | @Override 222 | public void onError(PlatformType platform_type, String err_msg) { 223 | Toast.makeText(MainActivity.this, platform_type + " share onError:" + err_msg, Toast.LENGTH_SHORT).show(); 224 | } 225 | 226 | @Override 227 | public void onCancel(PlatformType platform_type) { 228 | Toast.makeText(MainActivity.this, platform_type + " share onCancel", Toast.LENGTH_SHORT).show(); 229 | } 230 | } 231 | 232 | @Override 233 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 234 | mSocialApi.onActivityResult(requestCode, resultCode, data); 235 | } 236 | 237 | private Bitmap readBitMap(Context context, int resId) { 238 | BitmapFactory.Options opt = new BitmapFactory.Options(); 239 | opt.inPreferredConfig = Bitmap.Config.RGB_565; 240 | opt.inPurgeable = true; 241 | opt.inInputShareable = true; 242 | InputStream is = context.getResources().openRawResource(resId); 243 | return BitmapFactory.decodeStream(is, null, opt); 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /app/src/main/java/com/tsy/socialsample/WBShareActivity.java: -------------------------------------------------------------------------------- 1 | package com.tsy.socialsample; 2 | 3 | import com.tsy.sdk.social.sina.WBShareCallbackActivity; 4 | 5 | /** 6 | * Created by tsy on 2017/2/14. 7 | */ 8 | 9 | public class WBShareActivity extends WBShareCallbackActivity { 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 19 | 20 |