├── .gitignore ├── CHANGELOG.MD ├── LICENSE ├── README.MD ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── matpag │ │ └── sample │ │ ├── AppApplication.java │ │ └── MainActivity.java │ └── res │ ├── color │ └── colorstatelist.xml │ ├── drawable │ ├── close_blue.png │ ├── ic_android_black_24dp.xml │ ├── ic_close_black_24dp.xml │ └── ic_search_black_24dp.xml │ ├── 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 │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── gradle-jcenter-push.gradle ├── gradle.properties ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── matpag │ │ └── clickdrawabletextview │ │ ├── ClickDrawableAutoCompleteTextView.java │ │ ├── ClickDrawableEditText.java │ │ ├── ClickDrawableTextView.java │ │ ├── ClickableDrawable.java │ │ ├── CsDrawable.java │ │ ├── CsDrawableSettings.java │ │ ├── CsDrawableTouchUtils.java │ │ ├── CsDrawableViewManager.java │ │ ├── DrawablePosition.java │ │ └── interfaces │ │ └── OnDrawableClickListener.java │ └── res │ └── values │ └── attrs.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Log Files 26 | *.log 27 | 28 | # Android Studio Navigation editor temp files 29 | .navigation/ 30 | 31 | # Android Studio captures folder 32 | captures/ 33 | 34 | # Ignore idea folder 35 | /.idea 36 | 37 | # Ignore generated iml 38 | *.iml 39 | 40 | # Ignore metadata folder (OS X only) 41 | .DS_Store 42 | -------------------------------------------------------------------------------- /CHANGELOG.MD: -------------------------------------------------------------------------------- 1 | ### v3.1.0 - 29/03/2018 2 | - Fix a problem with accessibility click event 3 | - Update AGP to 3.1.0 4 | - Update Support Library to 27.1.0 5 | ## 6 | ### v3.0.0 - 16/12/2017 7 | - Fix a problem with ```disableFocusOnText()``` method, with some keyboard application like SwiftKey the text underline didn't disappear after choice selection 8 | - Add ```cs{Position}DrawableTint``` XML property to set a tint specific to a ```CsDrawable``` 9 | - Add ```cs{Position}DrawableTintMode``` XML property to set a tintMode specific to a ```CsDrawable``` 10 | - Add new constructors to ```CsDrawable.Builder``` to give the ability to create CsDrawable with locally mutable drawables 11 | - Remove ```CsDrawable.Builder(@NonNull Context context, @DrawableRes int drawableRes)``` method and replaced it with 12 | ```CsDrawable.Builder(@NonNull Context context, @DrawableRes int drawableRes, boolean mutable)```. If you were using 13 | the removed method you can simply add ```false``` as the latest parameter and everything will works again as usual. 14 | - Remove ```ClickableDrawable``` interface from the public api 15 | - Update AGP to 3.0.1 16 | - Update Build Tools to 27.0.2 17 | - Update Support Library to 27.0.2 18 | - Update ```compileSdk``` and ```targetSdk``` to 27 19 | ## 20 | ### v2.0.2 - 05/11/2017 21 | - Removed unused string 22 | - Small optimization to ```CsDrawableSettings.init(...)``` method 23 | ## 24 | ### v2.0.0 - 05/11/2017 25 | - Update Gradle Wrapper to 4.2.1 26 | - Update AGP to 3.0.0 27 | - Update Build Tools to 27.0.0 28 | - Update Support Library to 27.0.0 29 | ## 30 | ### v1.0.0 - 30/05/2017 31 | - minSdk lowered to API 16 (by default API 16 will have RTL support disabled) 32 | ## 33 | ### v0.6.0 - 27/05/2017 34 | - First release. Yuppy!!!! 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2017 Mattia Pagini 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # ClickDrawable - TextView/EditText/AutoComplete 2 | 3 | ### **Currently not mantained anymore but if you need help just open an issue** 4 | 5 | This library add the ability to listen for drawable click events for TextView, EditText and AutoCompleteTextView normally added in XML 6 | with ```android:drawableStart``` etc... or in code with ```setCompoundDrawables(...)``` and similar. 7 | Also with this library you can flat a layout used to side any widget with an ```ImageView``` or ```ImageButton``` added to listen for click on the icon, to one unique custom view. 8 | 9 | For example converting this XML: 10 | ```xml 11 | 15 | 21 | 25 | 26 | ``` 27 | Into this: 28 | ```xml 29 | 36 | ``` 37 | Removing a ViewGroup, removing inefficient weights and adding a lot more functionalities. 38 | 39 | ## Demo 40 | This GIF shows a ```ClickDrawableEditText``` with clickable drawables all around it (please never do this in a real app, it's really unpleasant :D) and a ```ClickDrawableAutoCompleteTextView``` with support for blocking inputs after user choice, until cancel icon is pressed. 41 | You can play with an interactive demo [here](https://appetize.io/app/9v3t7hcj8wcc93aceqwehhru80?device=nexus5&scale=75&orientation=portrait&osVersion=7.1) 42 | 43 | ![Library showcase](https://media.giphy.com/media/xUA7aXqmppLR7a0JfG/giphy.gif) 44 | 45 | ## Setup 46 | 47 | ##### This library is minSdk 16 (Android 4.1 Jelly Bean) 48 | 49 | ### 1. Add dependency in build.gradle 50 | ```gradle 51 | implementation "com.matpag:clickdrawabletextview:3.1.0@aar" 52 | 53 | //required dependency 54 | implementation "com.android.support:appcompat-v7:${versions.supportLib}" 55 | ``` 56 | 57 | ### 2. Use a support theme 58 | Use one of the theme from ```Theme.AppCompat.*``` for your application theme. 59 | 60 | ### 3. Initialize library in custom ```Application``` 61 | Call ```CsDrawableSettings.init(Context context, String packageName)``` in the ```onCreate``` of your custom ```Application``` class. 62 | In this way: 63 | ```java 64 | public class AppApplication extends Application { 65 | @Override 66 | public void onCreate() { 67 | super.onCreate(); 68 | //Init the library 69 | CsDrawableSettings.init(this, BuildConfig.APPLICATION_ID); 70 | } 71 | } 72 | ``` 73 | then you need to change your ```AndroidManifest.xml``` to load your custom 74 | application class instead of the default one adding the 75 | ```android:name".AppApplication"``` (you should update the path accordingly based on 76 | where you created the ```AppApplication``` class) to the manifest if you already didn't. 77 | 78 | Something similar to this: 79 | ```xml 80 | 86 | 87 | 88 | 89 | 90 | ``` 91 | 92 | ### 4. RTL support 93 | (Enabled only on API 17+ because the support for RTL was added in [API 17](https://developer.android.com/about/versions/android-4.2.html#RTL), lower API level will have the default layout direction to LTR) 94 | 95 | If you want to support RTL, you simply need to add ```android:supportsRtl="true"``` to the *application* tag in your ```AndroidManifest.xml``` and the library will handle everything for you. 96 | 97 | ## Docs 98 | 99 | The library provides 3 custom views with extended functionalities for drawables. 100 | 101 | #### 1. ```ClickDrawableTextView``` 102 | #### 2. ```ClickDrawableEditText``` 103 | #### 3. ```ClickDrawableAutoCompleteTextView``` 104 | 105 | Each of them it's extending the ```AppCompat``` counterpart class for max compatibility with every supported SDK version 106 | 107 | The custom drawables are ```CsDrawable```s objects. 108 | 109 | You can add a ```CsDrawable``` via XML or via Code, below you can find both the implementations. 110 | 111 | #### XML 112 | After declaring 113 | ```xml 114 | xmlns:app="http://schemas.android.com/apk/res-auto" 115 | ``` 116 | in the top parent layout, you can specify the custom properties shown below in any of the 117 | supported views. 118 | 119 | To add the drawable to the view in position you need, you can use: 120 | ```xml 121 | app:csStartDrawable="@drawable/my_icon" 122 | app:csEndDrawable="@drawable/my_icon" 123 | app:csTopDrawable="@drawable/my_icon" 124 | app:csBottomDrawable="@drawable/my_icon" 125 | ``` 126 | providing a reference to a drawable of any type (png, xml drawable, vector drawable) 127 | 128 | If you don't specify any size for the drawable, automatically the drawable will be sized to the width and height of the drawable itself (called **intrinsic** in the android platform). 129 | 130 | If you dont want to resize your original drawable resource (and you really shouldn't if not necessary), i suggest you to specify the width and the height in **dp** you need using: 131 | ```xml 132 | app:csStartDrawableHeight="30dp" 133 | app:csStartDrawableWidth="30dp" 134 | ``` 135 | for the **Start** drawable 136 | ```xml 137 | app:csEndDrawableHeight="30dp" 138 | app:csEndDrawableWidth="30dp" 139 | ``` 140 | for the **End** drawable 141 | ```xml 142 | app:csTopDrawableHeight="30dp" 143 | app:csTopDrawableWidth="30dp" 144 | ``` 145 | for the **Top** drawable 146 | ```xml 147 | app:csBottomDrawableHeight="30dp" 148 | app:csBottomDrawableWidth="30dp" 149 | ``` 150 | for the **Bottom** drawable. 151 | 152 | If you need to handle the initial visibility of the drawable(s) you can use 153 | ```xml 154 | app:csStartDrawableVisible 155 | app:csEndDrawableVisible 156 | app:csTopDrawableVisible 157 | app:csBottomDrawableVisible 158 | ``` 159 | (default is true) 160 | 161 | You can add a ```Tint```(Single color or reference to ```ColorStateList```) and a ```TintMode``` to the drawable(s) with 162 | ```xml 163 | app:csStartDrawableTint 164 | app:csEndDrawableTint 165 | app:csTopDrawableTint 166 | app:csBottomDrawableTint 167 | ``` 168 | 169 | ```xml 170 | app:csStartDrawableTintMode 171 | app:csEndDrawableTintMode 172 | app:csTopDrawableTintMode 173 | app:csBottomDrawableTintMode 174 | ``` 175 | 176 | #### Code 177 | 178 | Use this XML as example 179 | ```xml 180 | 184 | ``` 185 | Get the reference in code as usual 186 | 187 | ```java 188 | ClickDrawableEditText mCdEditText = (ClickDrawableEditText)findViewById(R.id.click_drawable_edit_text); 189 | ``` 190 | 191 | Create a ```CsDrawable``` object using the builder class 192 | 193 | ```java 194 | CsDrawable csDrawable = new CsDrawable.Builder(this, R.drawable.ic_close_red_24dp, true) 195 | .setDrawableDpSize(30, 30) //or .setDrawablePixelSize(width, height) for pixel 196 | .setVisibility(false) //optional, default true 197 | .build(); 198 | ``` 199 | 200 | Now you can add the ```CsDrawable``` to the view using one (or more) of the following methods 201 | ```java 202 | mCdEditText.addStartCsDrawable(csDrawable); 203 | mCdEditText.addTopCsDrawable(csDrawable); 204 | mCdEditText.addEndCsDrawable(csDrawable); 205 | mCdEditText.addBottomCsDrawable(csDrawable); 206 | ``` 207 | 208 | To change the visibility later, you can use 209 | ```java 210 | mCdEditText.showStartCsDrawable(true | false); 211 | mCdEditText.showTopCsDrawable(true | false); 212 | mCdEditText.showEndCsDrawable(true | false); 213 | mCdEditText.showBottomCsDrawable(true | false); 214 | ``` 215 | 216 | ## Listen to Drawables click events 217 | Now that you have configured your ```ClickDrawable*``` view with one or more drawables. You can add a listener to the drawable(s) click events. 218 | ```viewInstance.setOnDrawableClickListener(OnDrawableClickListener listener)```. 219 | 220 | Example: 221 | ```java 222 | viewInstance.setOnDrawableClickListener(new OnDrawableClickListener() { 223 | @Override 224 | public void onClick(View view, DrawablePosition position) { 225 | Toast.makeText(context, "Touched " + position.name() + " drawable", 226 | Toast.LENGHT_SHORT).show(); 227 | } 228 | }); 229 | ``` 230 | If you added more than 1 drawable to the view, you can create a switch case to distinguish between them 231 | ```java 232 | viewInstance.setOnDrawableClickListener(new OnDrawableClickListener() { 233 | @Override 234 | public void onClick(View view, DrawablePosition position) { 235 | switch (position){ 236 | case START: startDrawableClicked(); break; 237 | case END: endDrawableClicked(); break; 238 | } 239 | } 240 | }); 241 | ``` 242 | 243 | ### Advanced features 244 | If you need to block the user after some action, for example after selecting an item in the ```ClickDrawableAutoCompleteTextView``` dropdown. 245 | You can prevent the view from receiving the focus for editing with 246 | ```viewInstance.disableFocusOnText(boolean preventReFocus, boolean closeKeyboard)``` 247 | 248 | ```preventReFocus``` : Passing ```true```, will prevent re-focus on another EditText in the same ViewGroup (if present) 249 | 250 | ```closeKeyboard``` : Passing ```true```, will close the keyboard (if actually open) 251 | 252 | To re-enable the editing on the view simply call 253 | ```viewInstance.enableFocusOnText(boolean openKeyboard);``` 254 | 255 | ```openKeyboard``` : Passing ```true```, will open the keyboard automatically (if actually closed) 256 | 257 | You can find a concrete example of this use case in the sample app of the repository, in the [ClickDrawableAutoCompleteTextView configured in MainActivity](https://github.com/matpag/ClickDrawableTextView/blob/master/app/src/main/java/com/matpag/sample/MainActivity.java) 258 | 259 | ## Author 260 | 261 | * **[Mattia Pagini](https://github.com/matpag)** 262 | 263 | ## Contributing 264 | 265 | Please open issues for bug fixes or feature requests and send pull request only after discussion in the related issue (to prevent waste of your time on something that can't be merged). 266 | If you find typos somewhere or a lack of documentation, feel free to create a pull request. 267 | 268 | ## License 269 | 270 | This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details 271 | 272 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion setup.compileSdk 5 | 6 | defaultConfig { 7 | applicationId "com.matpag.sample" 8 | minSdkVersion setup.minSdk 9 | targetSdkVersion setup.targetSdk 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled true 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | lintOptions { 20 | abortOnError false 21 | } 22 | compileOptions { 23 | sourceCompatibility JavaVersion.VERSION_1_8 24 | targetCompatibility JavaVersion.VERSION_1_8 25 | } 26 | } 27 | 28 | dependencies { 29 | implementation "com.android.support:appcompat-v7:${versions.supportLib}" 30 | 31 | implementation project(':library') 32 | } 33 | -------------------------------------------------------------------------------- /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/mattiapagini/Desktop/AndroidSDK/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/matpag/sample/AppApplication.java: -------------------------------------------------------------------------------- 1 | package com.matpag.sample; 2 | 3 | import android.app.Application; 4 | 5 | import com.matpag.clickdrawabletextview.CsDrawableSettings; 6 | 7 | /** 8 | * Custom {@link Application} class 9 | * 10 | * Created by Mattia Pagini on 17/05/2017. 11 | */ 12 | public class AppApplication extends Application { 13 | 14 | @Override 15 | public void onCreate() { 16 | super.onCreate(); 17 | 18 | //Init the library 19 | CsDrawableSettings.init(this, BuildConfig.APPLICATION_ID); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/matpag/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.matpag.sample; 2 | 3 | import android.graphics.drawable.Drawable; 4 | import android.os.Bundle; 5 | import android.support.v4.content.ContextCompat; 6 | import android.support.v4.graphics.drawable.DrawableCompat; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.util.Log; 9 | import android.widget.ArrayAdapter; 10 | import android.widget.Toast; 11 | 12 | import com.matpag.clickdrawabletextview.ClickDrawableAutoCompleteTextView; 13 | import com.matpag.clickdrawabletextview.ClickDrawableEditText; 14 | import com.matpag.clickdrawabletextview.CsDrawable; 15 | 16 | /** 17 | * Showcase activity 18 | */ 19 | public class MainActivity extends AppCompatActivity { 20 | 21 | @Override 22 | protected void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | setContentView(R.layout.activity_main); 25 | 26 | //####### ClickDrawableEditText - surrounded example ######## 27 | //In this example we got the view from the XML, so go watch R.layout.activity_main to 28 | //understand more on how the properties were configured 29 | final ClickDrawableEditText mCdEditText = findViewById(R.id.click_drawable_edit_text); 30 | mCdEditText.setOnDrawableClickListener((view, position) -> { 31 | Toast.makeText(MainActivity.this, position.name(), 32 | Toast.LENGTH_SHORT).show(); 33 | Log.e("on_click", "DRAWABLE_CLICKED"); 34 | //close the keyboard if opened 35 | mCdEditText.closeKeyboard(); 36 | }); 37 | mCdEditText.setOnClickListener(v -> Log.e("on_click", "EDITEXT_CLICKED")); 38 | 39 | //####### ClickDrawableEditText - search example ######## 40 | //In this example we got the view from the XML, so go watch R.layout.activity_main to 41 | //understand more on how the properties were configured 42 | final ClickDrawableEditText mCdSearchEditText = 43 | findViewById(R.id.click_drawable_search_edit_text); 44 | mCdSearchEditText.setOnDrawableClickListener((view, position) -> { 45 | Toast.makeText(MainActivity.this, "Search started...", 46 | Toast.LENGTH_SHORT).show(); 47 | //close the keyboard if opened 48 | mCdSearchEditText.closeKeyboard(); 49 | }); 50 | 51 | 52 | //######## ClickDrawableAutoCompleteTextView example ############ 53 | final ClickDrawableAutoCompleteTextView mCdAutoComplete = 54 | findViewById(R.id.click_drawable_auto_text_view); 55 | 56 | //build a CsDrawable object with a vector drawable 57 | Drawable closeDrawable = ContextCompat.getDrawable(this, 58 | R.drawable.ic_close_black_24dp); 59 | assert closeDrawable != null; 60 | //tint the vector drawable with red 61 | DrawableCompat.setTint(closeDrawable, getResources().getColor(R.color.close_red)); 62 | //create a csdrawable with the configured drawable 63 | CsDrawable csDrawable1 = new CsDrawable.Builder(this, closeDrawable) 64 | .setDrawableDpSize(30, 30) 65 | .setVisibility(false) 66 | .build(); 67 | 68 | //add to the END 69 | mCdAutoComplete.addEndCsDrawable(csDrawable1); 70 | 71 | //add a list of random strings to the autocompletetextview 72 | String[] strings = new String[]{"Ant", "Dog", "Cat", "Mouse", "Bird", "Bee", 73 | "Cow", "Parrot", "Snake", "Tiger"}; 74 | ArrayAdapter adapter = new ArrayAdapter<>(this, 75 | android.R.layout.simple_list_item_1, strings); 76 | mCdAutoComplete.setAdapter(adapter); 77 | 78 | //se on item click, when the user choose an element from the list, show the close drawable 79 | //and disable the focus on it preventing the user to edit the value again 80 | mCdAutoComplete.setOnItemClickListener((parent, view, position, id) -> { 81 | //show the cancel drawable 82 | mCdAutoComplete.showEndCsDrawable(true); 83 | //prevent user to change the value without prior clicking on the cancel drawable 84 | mCdAutoComplete.disableFocusOnText(true, true); 85 | }); 86 | 87 | //set on CsDrawable click listener, when the user press the X drawable, we restore the 88 | //focus on the view and remove the right drawable 89 | mCdAutoComplete.setOnDrawableClickListener((view, position) -> { 90 | switch (position){ 91 | case END: //if we touched the END drawable 92 | //hide the close drawable 93 | mCdAutoComplete.showEndCsDrawable(false); 94 | //get focus on input (opening the keyboard) 95 | mCdAutoComplete.enableFocusOnText(true); 96 | //reset text 97 | mCdAutoComplete.setText(""); 98 | break; 99 | } 100 | }); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/res/color/colorstatelist.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/close_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matpag/ClickDrawableTextView/30f967e5aa723a109fa0328626f975267f720a06/app/src/main/res/drawable/close_blue.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_android_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 24 | 25 | 50 | 51 | 60 | 61 | 73 | 74 | 83 | 84 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matpag/ClickDrawableTextView/30f967e5aa723a109fa0328626f975267f720a06/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matpag/ClickDrawableTextView/30f967e5aa723a109fa0328626f975267f720a06/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matpag/ClickDrawableTextView/30f967e5aa723a109fa0328626f975267f720a06/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matpag/ClickDrawableTextView/30f967e5aa723a109fa0328626f975267f720a06/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matpag/ClickDrawableTextView/30f967e5aa723a109fa0328626f975267f720a06/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #009688 5 | #00796B 6 | #4CAF50 7 | 8 | #FFDB1100 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ClickDrawable Showcase 3 | 4 | ClickDrawableEditText - Surrounded 5 | ClickDrawableEdiText - Search 6 | Search something 7 | ClickDrawableAutoCompleteTextView 8 | Type an animal name 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /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 | google() 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.1.0' 10 | classpath 'com.novoda:bintray-release:0.8.0' 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | 16 | // To avoid manually setting the same values in all Android modules, set the value on the root 17 | // project and then reference this from the modules 18 | ext { 19 | setup = [compileSdk: 27, 20 | minSdk : 16, 21 | targetSdk : 27] 22 | 23 | versions = [supportLib: "27.1.0"] 24 | } 25 | } 26 | 27 | allprojects { 28 | repositories { 29 | google() 30 | jcenter() 31 | } 32 | } 33 | 34 | task clean(type: Delete) { 35 | delete rootProject.buildDir 36 | } 37 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | VERSION_NAME=3.1.0 4 | VERSION_CODE=310 5 | 6 | REPOSITORY_NAME=maven 7 | POM_GROUP=com.matpag 8 | POM_ARTIFACT_ID=clickdrawabletextview 9 | POM_DEVELOPER_ID=soulcyber 10 | POM_DEVELOPER_NAME=Mattia Pagini 11 | POM_DESCRIPTION=Android library to handle click on Textview drawables 12 | POM_URL=https://github.com/matpag/ClickDrawableTextView 13 | POM_SCM_URL=https://github.com/matpag/ClickDrawableTextView 14 | POM_SCM_CONNECTION=scm:git@github.com:matpag/ClickDrawableTextView.git 15 | POM_SCM_DEV_CONNECTION=scm:git@github.com:matpag/ClickDrawableTextView.git 16 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 17 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 18 | POM_LICENCE_DIST=repo -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matpag/ClickDrawableTextView/30f967e5aa723a109fa0328626f975267f720a06/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Mar 27 19:21:28 CEST 2018 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-4.4-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 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.novoda.bintray-release' 3 | 4 | android { 5 | compileSdkVersion setup.compileSdk 6 | 7 | defaultConfig { 8 | minSdkVersion setup.minSdk 9 | targetSdkVersion setup.targetSdk 10 | versionCode 310 11 | versionName "3.1.0" 12 | 13 | vectorDrawables.useSupportLibrary = true 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | compileOptions { 22 | sourceCompatibility JavaVersion.VERSION_1_8 23 | targetCompatibility JavaVersion.VERSION_1_8 24 | } 25 | lintOptions { 26 | abortOnError false 27 | } 28 | } 29 | 30 | apply from: 'gradle-jcenter-push.gradle' 31 | 32 | dependencies { 33 | implementation "com.android.support:appcompat-v7:${versions.supportLib}" 34 | } 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /library/gradle-jcenter-push.gradle: -------------------------------------------------------------------------------- 1 | publish { 2 | dryRun = false 3 | bintrayUser = project.hasProperty('bintray.user') ? project.property('bintray.user') : System.getenv('BINTRAY_USER') 4 | bintrayKey = project.hasProperty('bintray.apikey') ? project.property('bintray.apikey') : System.getenv('BINTRAY_API_KEY') 5 | repoName = project.property("REPOSITORY_NAME") 6 | userOrg = project.property("POM_DEVELOPER_ID") 7 | groupId = project.property("POM_GROUP") 8 | artifactId = project.property("POM_ARTIFACT_ID") 9 | publishVersion = project.property("VERSION_NAME") 10 | desc = project.property("POM_DESCRIPTION") 11 | website = project.property("POM_URL") 12 | } -------------------------------------------------------------------------------- /library/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=ClickDrawable Library 2 | POM_ARTIFACT_ID=clickdrawabletextview 3 | POM_PACKAGING=aar -------------------------------------------------------------------------------- /library/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/mattiapagini/Desktop/AndroidSDK/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 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /library/src/main/java/com/matpag/clickdrawabletextview/ClickDrawableAutoCompleteTextView.java: -------------------------------------------------------------------------------- 1 | package com.matpag.clickdrawabletextview; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.AppCompatAutoCompleteTextView; 5 | import android.text.TextWatcher; 6 | import android.util.AttributeSet; 7 | 8 | import com.matpag.clickdrawabletextview.interfaces.OnDrawableClickListener; 9 | 10 | /** 11 | * A custom implementation of {@link AppCompatAutoCompleteTextView} to add some advanced 12 | * features with the drawable handling 13 | * 14 | * Created by Mattia Pagini on 23/04/2017. 15 | */ 16 | public class ClickDrawableAutoCompleteTextView extends AppCompatAutoCompleteTextView 17 | implements ClickableDrawable { 18 | 19 | public CsDrawableViewManager mCsDrawableViewManager = new CsDrawableViewManager(this); 20 | 21 | public ClickDrawableAutoCompleteTextView(Context context) { 22 | super(context); 23 | mCsDrawableViewManager.init(context, null); 24 | } 25 | 26 | public ClickDrawableAutoCompleteTextView(Context context, AttributeSet attrs) { 27 | super(context, attrs); 28 | mCsDrawableViewManager.init(context, attrs); 29 | } 30 | 31 | public ClickDrawableAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) { 32 | super(context, attrs, defStyleAttr); 33 | mCsDrawableViewManager.init(context, attrs); 34 | } 35 | 36 | @Override 37 | public void setOnDrawableClickListener(OnDrawableClickListener listener) { 38 | mCsDrawableViewManager.setOnDrawableClickListener(listener); 39 | } 40 | 41 | @Override 42 | public void removeOnDrawableClickListener() { 43 | mCsDrawableViewManager.removeOnDrawableClickListener(); 44 | } 45 | 46 | @Override 47 | public void addStartCsDrawable(CsDrawable csDrawable) { 48 | mCsDrawableViewManager.addStartCsDrawable(csDrawable); 49 | } 50 | 51 | @Override 52 | public void addTopCsDrawable(CsDrawable csDrawable) { 53 | mCsDrawableViewManager.addTopCsDrawable(csDrawable); 54 | } 55 | 56 | @Override 57 | public void addEndCsDrawable(CsDrawable csDrawable) { 58 | mCsDrawableViewManager.addEndCsDrawable(csDrawable); 59 | } 60 | 61 | @Override 62 | public void addBottomCsDrawable(CsDrawable csDrawable) { 63 | mCsDrawableViewManager.addBottomCsDrawable(csDrawable); 64 | } 65 | 66 | @Override 67 | public void showStartCsDrawable(boolean visible) { 68 | mCsDrawableViewManager.showStartCsDrawable(visible); 69 | } 70 | 71 | @Override 72 | public void showTopCsDrawable(boolean visible) { 73 | mCsDrawableViewManager.showTopCsDrawable(visible); 74 | } 75 | 76 | @Override 77 | public void showEndCsDrawable(boolean visible) { 78 | mCsDrawableViewManager.showEndCsDrawable(visible); 79 | } 80 | 81 | @Override 82 | public void showBottomCsDrawable(boolean visible) { 83 | mCsDrawableViewManager.showBottomCsDrawable(visible); 84 | } 85 | 86 | @Override 87 | public void removeAllCsDrawables() { 88 | mCsDrawableViewManager.removeAllCsDrawables(); 89 | } 90 | 91 | 92 | @Override 93 | public void disableFocusOnText(boolean preventReFocus, boolean closeKeyboard) { 94 | mCsDrawableViewManager.disableFocusOnText(preventReFocus, closeKeyboard); 95 | } 96 | 97 | @Override 98 | public void enableFocusOnText(boolean openKeyboard) { 99 | mCsDrawableViewManager.enableFocusOnText(openKeyboard); 100 | } 101 | 102 | @Override 103 | public void setEnabled(boolean enabled) { 104 | super.setEnabled(enabled); 105 | } 106 | 107 | @Override 108 | public void openKeyboard() { 109 | mCsDrawableViewManager.openKeyboard(); 110 | } 111 | 112 | @Override 113 | public void closeKeyboard() { 114 | mCsDrawableViewManager.closeKeyboard(); 115 | } 116 | 117 | @Override 118 | public void addTextChangedListener(TextWatcher watcher) { 119 | super.addTextChangedListener(watcher); 120 | if (mCsDrawableViewManager != null){ 121 | mCsDrawableViewManager.addTextWatcher(watcher); 122 | } 123 | } 124 | 125 | @Override 126 | public void removeTextChangedListener(TextWatcher watcher) { 127 | super.removeTextChangedListener(watcher); 128 | if (mCsDrawableViewManager != null){ 129 | mCsDrawableViewManager.removeTextWatcher(); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /library/src/main/java/com/matpag/clickdrawabletextview/ClickDrawableEditText.java: -------------------------------------------------------------------------------- 1 | package com.matpag.clickdrawabletextview; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.AppCompatEditText; 5 | import android.text.TextWatcher; 6 | import android.util.AttributeSet; 7 | 8 | import com.matpag.clickdrawabletextview.interfaces.OnDrawableClickListener; 9 | 10 | /** 11 | * A custom implementation of {@link AppCompatEditText} to add some advanced 12 | * features with the drawable handling 13 | * 14 | * Created by Mattia Pagini on 23/04/2017. 15 | */ 16 | public class ClickDrawableEditText extends AppCompatEditText implements ClickableDrawable { 17 | 18 | public CsDrawableViewManager mCsDrawableViewManager = new CsDrawableViewManager(this); 19 | 20 | public ClickDrawableEditText(Context context) { 21 | super(context); 22 | mCsDrawableViewManager.init(context, null); 23 | } 24 | 25 | public ClickDrawableEditText(Context context, AttributeSet attrs) { 26 | super(context, attrs); 27 | mCsDrawableViewManager.init(context, attrs); 28 | } 29 | 30 | public ClickDrawableEditText(Context context, AttributeSet attrs, int defStyleAttr) { 31 | super(context, attrs, defStyleAttr); 32 | mCsDrawableViewManager.init(context, attrs); 33 | } 34 | 35 | @Override 36 | public void setOnDrawableClickListener(OnDrawableClickListener listener) { 37 | mCsDrawableViewManager.setOnDrawableClickListener(listener); 38 | } 39 | 40 | @Override 41 | public void removeOnDrawableClickListener() { 42 | mCsDrawableViewManager.removeOnDrawableClickListener(); 43 | } 44 | 45 | @Override 46 | public void addStartCsDrawable(CsDrawable csDrawable) { 47 | mCsDrawableViewManager.addStartCsDrawable(csDrawable); 48 | } 49 | 50 | @Override 51 | public void addTopCsDrawable(CsDrawable csDrawable) { 52 | mCsDrawableViewManager.addTopCsDrawable(csDrawable); 53 | } 54 | 55 | @Override 56 | public void addEndCsDrawable(CsDrawable csDrawable) { 57 | mCsDrawableViewManager.addEndCsDrawable(csDrawable); 58 | } 59 | 60 | @Override 61 | public void addBottomCsDrawable(CsDrawable csDrawable) { 62 | mCsDrawableViewManager.addBottomCsDrawable(csDrawable); 63 | } 64 | 65 | @Override 66 | public void showStartCsDrawable(boolean visible) { 67 | mCsDrawableViewManager.showStartCsDrawable(visible); 68 | } 69 | 70 | @Override 71 | public void showTopCsDrawable(boolean visible) { 72 | mCsDrawableViewManager.showTopCsDrawable(visible); 73 | } 74 | 75 | @Override 76 | public void showEndCsDrawable(boolean visible) { 77 | mCsDrawableViewManager.showEndCsDrawable(visible); 78 | } 79 | 80 | @Override 81 | public void showBottomCsDrawable(boolean visible) { 82 | mCsDrawableViewManager.showBottomCsDrawable(visible); 83 | } 84 | 85 | @Override 86 | public void removeAllCsDrawables() { 87 | mCsDrawableViewManager.removeAllCsDrawables(); 88 | } 89 | 90 | @Override 91 | public void disableFocusOnText(boolean preventReFocus, boolean closeKeyboard) { 92 | mCsDrawableViewManager.disableFocusOnText(preventReFocus, closeKeyboard); 93 | } 94 | 95 | @Override 96 | public void enableFocusOnText(boolean openKeyboard) { 97 | mCsDrawableViewManager.enableFocusOnText(openKeyboard); 98 | } 99 | 100 | @Override 101 | public void openKeyboard() { 102 | mCsDrawableViewManager.openKeyboard(); 103 | } 104 | 105 | @Override 106 | public void closeKeyboard() { 107 | mCsDrawableViewManager.closeKeyboard(); 108 | } 109 | 110 | @Override 111 | public void addTextChangedListener(TextWatcher watcher) { 112 | super.addTextChangedListener(watcher); 113 | if (mCsDrawableViewManager != null){ 114 | mCsDrawableViewManager.addTextWatcher(watcher); 115 | } 116 | } 117 | 118 | @Override 119 | public void removeTextChangedListener(TextWatcher watcher) { 120 | super.removeTextChangedListener(watcher); 121 | if (mCsDrawableViewManager != null){ 122 | mCsDrawableViewManager.removeTextWatcher(); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /library/src/main/java/com/matpag/clickdrawabletextview/ClickDrawableTextView.java: -------------------------------------------------------------------------------- 1 | package com.matpag.clickdrawabletextview; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.AppCompatTextView; 5 | import android.text.TextWatcher; 6 | import android.util.AttributeSet; 7 | 8 | import com.matpag.clickdrawabletextview.interfaces.OnDrawableClickListener; 9 | 10 | /** 11 | * A custom implementation of {@link AppCompatTextView} to add some advanced 12 | * features with the drawable handling 13 | * 14 | * Created by Mattia Pagini on 23/04/2017. 15 | */ 16 | public class ClickDrawableTextView extends AppCompatTextView implements ClickableDrawable { 17 | 18 | public CsDrawableViewManager mCsDrawableViewManager = new CsDrawableViewManager(this); 19 | 20 | public ClickDrawableTextView(Context context) { 21 | super(context); 22 | mCsDrawableViewManager.init(context, null); 23 | } 24 | 25 | public ClickDrawableTextView(Context context, AttributeSet attrs) { 26 | super(context, attrs); 27 | mCsDrawableViewManager.init(context, attrs); 28 | } 29 | 30 | public ClickDrawableTextView(Context context, AttributeSet attrs, int defStyleAttr) { 31 | super(context, attrs, defStyleAttr); 32 | mCsDrawableViewManager.init(context, attrs); 33 | } 34 | 35 | @Override 36 | public void setOnDrawableClickListener(OnDrawableClickListener listener) { 37 | mCsDrawableViewManager.setOnDrawableClickListener(listener); 38 | } 39 | 40 | @Override 41 | public void removeOnDrawableClickListener() { 42 | mCsDrawableViewManager.removeOnDrawableClickListener(); 43 | } 44 | 45 | @Override 46 | public void addStartCsDrawable(CsDrawable csDrawable) { 47 | mCsDrawableViewManager.addStartCsDrawable(csDrawable); 48 | } 49 | 50 | @Override 51 | public void addTopCsDrawable(CsDrawable csDrawable) { 52 | mCsDrawableViewManager.addTopCsDrawable(csDrawable); 53 | } 54 | 55 | @Override 56 | public void addEndCsDrawable(CsDrawable csDrawable) { 57 | mCsDrawableViewManager.addEndCsDrawable(csDrawable); 58 | } 59 | 60 | @Override 61 | public void addBottomCsDrawable(CsDrawable csDrawable) { 62 | mCsDrawableViewManager.addBottomCsDrawable(csDrawable); 63 | } 64 | 65 | @Override 66 | public void showStartCsDrawable(boolean visible) { 67 | mCsDrawableViewManager.showStartCsDrawable(visible); 68 | } 69 | 70 | @Override 71 | public void showTopCsDrawable(boolean visible) { 72 | mCsDrawableViewManager.showTopCsDrawable(visible); 73 | } 74 | 75 | @Override 76 | public void showEndCsDrawable(boolean visible) { 77 | mCsDrawableViewManager.showEndCsDrawable(visible); 78 | } 79 | 80 | @Override 81 | public void showBottomCsDrawable(boolean visible) { 82 | mCsDrawableViewManager.showBottomCsDrawable(visible); 83 | } 84 | 85 | @Override 86 | public void disableFocusOnText(boolean preventReFocus, boolean closeKeyboard) { 87 | throw new IllegalArgumentException("TextView can't be focused for editing"); 88 | } 89 | 90 | @Override 91 | public void enableFocusOnText(boolean openKeyboard) { 92 | throw new IllegalArgumentException("TextView can't be focused for editing"); 93 | } 94 | 95 | @Override 96 | public void openKeyboard() { 97 | throw new IllegalArgumentException("TextView can't be edited with keyboard"); 98 | } 99 | 100 | @Override 101 | public void closeKeyboard() { 102 | throw new IllegalArgumentException("TextView can't be edited with keyboard"); 103 | } 104 | 105 | @Override 106 | public void removeAllCsDrawables() { 107 | mCsDrawableViewManager.removeAllCsDrawables(); 108 | } 109 | 110 | @Override 111 | public void addTextChangedListener(TextWatcher watcher) { 112 | super.addTextChangedListener(watcher); 113 | if (mCsDrawableViewManager != null){ 114 | mCsDrawableViewManager.addTextWatcher(watcher); 115 | } 116 | } 117 | 118 | @Override 119 | public void removeTextChangedListener(TextWatcher watcher) { 120 | super.removeTextChangedListener(watcher); 121 | if (mCsDrawableViewManager != null){ 122 | mCsDrawableViewManager.removeTextWatcher(); 123 | } 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /library/src/main/java/com/matpag/clickdrawabletextview/ClickableDrawable.java: -------------------------------------------------------------------------------- 1 | package com.matpag.clickdrawabletextview; 2 | 3 | import com.matpag.clickdrawabletextview.interfaces.OnDrawableClickListener; 4 | 5 | /** 6 | * The interface that every {@link android.widget.TextView} subclass need to implement to add 7 | * the extra functionalities to the attached drawables 8 | * 9 | * Created by Mattia Pagini on 24/04/2017. 10 | */ 11 | interface ClickableDrawable { 12 | 13 | /** 14 | * Setup the listener to listen for the click events on the drawables 15 | * @param listener the listener 16 | */ 17 | void setOnDrawableClickListener(OnDrawableClickListener listener); 18 | 19 | /** 20 | * Remove the listener for the drawable, short method that equals to call 21 | * {@link #setOnDrawableClickListener(OnDrawableClickListener)} with a null 22 | * parameter 23 | */ 24 | void removeOnDrawableClickListener(); 25 | 26 | /** 27 | * Add the start drawable to the view, default the LEFT drawable. 28 | * this could be the LEFT or RIGHT drawable based on the user locale if the developer 29 | * has added android:supportsRtl="true" in his AndroidManifest.xml 30 | * @param csDrawable the CsDrawable object (can be null) 31 | */ 32 | void addStartCsDrawable(CsDrawable csDrawable); 33 | 34 | /** 35 | * Add the TOP drawable to the view 36 | * @param csDrawable the CsDrawable object (can be null) 37 | */ 38 | void addTopCsDrawable(CsDrawable csDrawable); 39 | 40 | /** 41 | * Add the end drawable to the view, default the RIGHT drawable. 42 | * this could be the LEFT or RIGHT drawable based on the user locale if the developer 43 | * has added android:supportsRtl="true" in his AndroidManifest.xml 44 | * @param csDrawable the CsDrawable object (can be null) 45 | */ 46 | void addEndCsDrawable(CsDrawable csDrawable); 47 | 48 | /** 49 | * Add the BOTTOM drawable to the view 50 | * @param csDrawable the CsDrawable object (can be null) 51 | */ 52 | void addBottomCsDrawable(CsDrawable csDrawable); 53 | 54 | /** 55 | * Change the {@link CsDrawable} object visibility attached to the START position 56 | * @param visible true for show it in the view, false to hide 57 | */ 58 | void showStartCsDrawable(boolean visible); 59 | 60 | /** 61 | * Change the {@link CsDrawable} object visibility attached to the TOP position 62 | * @param visible true for show it in the view, false to hide 63 | */ 64 | void showTopCsDrawable(boolean visible); 65 | 66 | /** 67 | * Change the {@link CsDrawable} object visibility attached to the END position 68 | * @param visible true for show it in the view, false to hide 69 | */ 70 | void showEndCsDrawable(boolean visible); 71 | 72 | /** 73 | * Change the {@link CsDrawable} object visibility attached to the BOTTOM position 74 | * @param visible true for show it in the view, false to hide 75 | */ 76 | void showBottomCsDrawable(boolean visible); 77 | 78 | /** 79 | * Remove all the {@link CsDrawable} objects to the view 80 | */ 81 | void removeAllCsDrawables(); 82 | 83 | /** 84 | * Disable focus on the view, simulating a {@link android.view.View#setEnabled(boolean)} 85 | * call with a false parameter. In this way we can still handle the touch 86 | * inputs on the {@link CsDrawable}/s attached. 87 | * @param preventReFocus true if the focus leaving the current view should 88 | * not fall on another view inside the parent view. false 89 | * for default behaviour. 90 | * @param closeKeyboard true if the keyboard should be closed if opened when 91 | * this method is called 92 | */ 93 | void disableFocusOnText(boolean preventReFocus, boolean closeKeyboard); 94 | 95 | /** 96 | * Re-enable the focus on the view, canceling a previous call to 97 | * {@link #disableFocusOnText(boolean, boolean)} method. 98 | * @param openKeyboard true if the keyboard should be opened if closed when this 99 | * method is called 100 | */ 101 | void enableFocusOnText(boolean openKeyboard); 102 | 103 | /** 104 | * Helper method to close the keyboard if the IME is currently opened. 105 | *

WARNING

106 | *

107 | * This is a small utility which aims to provide a basic functionality which depends a lot 108 | * on the keyboard application the user it's using, your view and inputMethod settings. 109 | * So if this method is not working you probably need to provide your custom implementation. 110 | *

111 | */ 112 | void closeKeyboard(); 113 | 114 | /** 115 | * Helper method to open the keyboard on the current view if the IME is currently closed 116 | */ 117 | void openKeyboard(); 118 | 119 | } 120 | -------------------------------------------------------------------------------- /library/src/main/java/com/matpag/clickdrawabletextview/CsDrawable.java: -------------------------------------------------------------------------------- 1 | package com.matpag.clickdrawabletextview; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.Drawable; 5 | import android.support.annotation.DrawableRes; 6 | import android.support.annotation.NonNull; 7 | import android.support.annotation.Nullable; 8 | import android.support.v4.content.ContextCompat; 9 | import android.support.v4.graphics.drawable.DrawableCompat; 10 | import android.util.DisplayMetrics; 11 | import android.util.TypedValue; 12 | 13 | /** 14 | * Custom-Sizable drawable 15 | * 16 | * Created by Mattia Pagini on 12/02/2017. 17 | */ 18 | public class CsDrawable { 19 | 20 | /** 21 | * The wrapped drawable 22 | */ 23 | private Drawable drawable; 24 | 25 | /** 26 | * The application context 27 | */ 28 | private Context context; 29 | 30 | /** 31 | * The visibility flag for this {@link CsDrawable} 32 | */ 33 | private boolean visibility; 34 | 35 | /** 36 | * Internal constructor 37 | */ 38 | CsDrawable(@NonNull Context context, @NonNull Drawable drawable){ 39 | this.context = context.getApplicationContext(); 40 | this.drawable = drawable; 41 | this.visibility = true; 42 | setDefaultDrawableIntrinsicBounds(); 43 | } 44 | 45 | public boolean isVisible(){ 46 | return visibility; 47 | } 48 | 49 | void setVisibility(boolean visibility){ 50 | this.visibility = visibility; 51 | } 52 | 53 | public @NonNull Drawable getDrawable(){ 54 | return drawable; 55 | } 56 | 57 | @Nullable Drawable getDrawableIfVisible(){ 58 | return visibility ? drawable : null; 59 | } 60 | 61 | /** 62 | * Validate the dimension params before sizing the drawable 63 | * @param height the requested height of the drawable 64 | * @param width the requested width of the drawable 65 | * @return true if params are correct, false otherwise 66 | */ 67 | private static boolean validateSizeParams(int height, int width){ 68 | if (width < 0 || height < 0){ 69 | throw new IllegalArgumentException("CsDrawable requested height and width must be >= 0"); 70 | } 71 | return true; 72 | } 73 | 74 | void setDrawablePixelSize(int height, int width){ 75 | drawable.setBounds(0, 0, width, height); 76 | } 77 | 78 | void setDrawableDpSize(int heightDp, int widthDp){ 79 | DisplayMetrics metrics = context.getResources().getDisplayMetrics(); 80 | int pixelWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, widthDp, metrics); 81 | int pixelHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, heightDp, metrics); 82 | drawable.setBounds(0, 0, pixelWidth, pixelHeight); 83 | } 84 | 85 | private void setDefaultDrawableIntrinsicBounds(){ 86 | drawable.setBounds(0, 0, 87 | drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); 88 | } 89 | 90 | /** 91 | * The builder to create a {@link CsDrawable} object in the easy way 92 | */ 93 | public final static class Builder { 94 | 95 | private CsDrawable csDrawable; 96 | 97 | /** 98 | * Builder to create a {@link CsDrawable} object 99 | * @param context Any context 100 | * @param drawable A {@link Drawable} object, if you want a mutable drawable (to prevent 101 | * state sharing between drawable with the same origin) you should check 102 | * {@link Builder#Builder(Context, Drawable, boolean)} instead. 103 | * Or you can provide an already mutated {@link Drawable} object. 104 | */ 105 | public Builder(@NonNull Context context, Drawable drawable){ 106 | this (context, drawable, false); 107 | } 108 | 109 | /** 110 | * 111 | * @param context Any context 112 | * @param drawableRes A {@link DrawableRes} resourceId pointing to a drawable like a PNG or 113 | * a {@link android.graphics.drawable.VectorDrawable} 114 | * @param mutable If you want make the drawable mutable (to prevent 115 | * state sharing between drawable with the same origin). 116 | * Read here 117 | * for more info. 118 | *

This is usefull for tinting or other things which should act only on the 119 | * specific drawable object and not at global level.

120 | */ 121 | public Builder(@NonNull Context context, @DrawableRes int drawableRes, boolean mutable){ 122 | this(context, ContextCompat.getDrawable(context, drawableRes), mutable); 123 | } 124 | 125 | /** 126 | * 127 | * @param context Any context 128 | * @param drawable A {@link Drawable} object 129 | * @param mutable Pass true if you want make the drawable mutable (to prevent 130 | * state sharing between drawable with the same origin). 131 | * Read here 132 | * for more info. 133 | * This is usefull for tinting or other things which should act only on the 134 | * specific drawable object and not at global level. 135 | */ 136 | public Builder(@NonNull Context context, Drawable drawable, boolean mutable){ 137 | if (drawable == null){ 138 | throw new IllegalArgumentException("drawable can't be null"); 139 | } 140 | if (mutable) { 141 | Drawable wrappedDrawable = DrawableCompat.wrap(drawable); 142 | drawable = wrappedDrawable.mutate(); 143 | } 144 | csDrawable = new CsDrawable(context, drawable); 145 | } 146 | 147 | /** 148 | * set the drawable size in pixels 149 | * @param pixelHeight target height in pixel 150 | * @param pixelWidth target width in pixel 151 | * 152 | * @return current instance 153 | */ 154 | public Builder setDrawablePixelSize(int pixelHeight, int pixelWidth){ 155 | if (validateSizeParams(pixelHeight, pixelWidth)) { 156 | csDrawable.setDrawablePixelSize(pixelHeight, pixelWidth); 157 | } 158 | return this; 159 | } 160 | 161 | /** 162 | * set the drawable size in DP 163 | * @param dpHeight target height in DP 164 | * @param dpWidth target width in DP 165 | * 166 | * @return current instance 167 | */ 168 | public Builder setDrawableDpSize(int dpHeight, int dpWidth){ 169 | if (validateSizeParams(dpHeight, dpWidth)) { 170 | csDrawable.setDrawableDpSize(dpHeight, dpWidth); 171 | } 172 | return this; 173 | } 174 | 175 | /** 176 | * set the initial visibility of the drawable 177 | * @param visible default false 178 | * 179 | * @return current instance 180 | */ 181 | public Builder setVisibility(boolean visible){ 182 | csDrawable.setVisibility(visible); 183 | return this; 184 | } 185 | 186 | public CsDrawable build(){ 187 | return csDrawable; 188 | } 189 | 190 | } 191 | 192 | } 193 | -------------------------------------------------------------------------------- /library/src/main/java/com/matpag/clickdrawabletextview/CsDrawableSettings.java: -------------------------------------------------------------------------------- 1 | package com.matpag.clickdrawabletextview; 2 | 3 | import android.content.Context; 4 | import android.content.pm.ApplicationInfo; 5 | import android.content.pm.PackageManager; 6 | import android.os.Build; 7 | 8 | /** 9 | * Global configuration to handle correctly some user specific choices 10 | * 11 | * Created by Mattia Pagini on 17/05/2017. 12 | */ 13 | public class CsDrawableSettings { 14 | 15 | private boolean rtlSupportEnabled; 16 | 17 | private static CsDrawableSettings mSettings; 18 | 19 | private CsDrawableSettings(boolean rtlSupportEnabled){ 20 | this.rtlSupportEnabled = rtlSupportEnabled; 21 | } 22 | 23 | /** 24 | * Init method to call before every custom view initialization, this should preferably be 25 | * called in the custom {@link android.app.Application} app class. But you can use it in the 26 | * activity too before calling {@link android.app.Activity#setContentView(int)} or calling 27 | * this per Activity if you need to support RTL in some activities and not in others 28 | * @param context the app context 29 | * @param packageName the packageName of the app, BuildConfig.APPLICATION_ID should 30 | * be the proper choice in the most cases 31 | */ 32 | public static void init(Context context, String packageName){ 33 | // prior to API 17 RTL is not supported, so we create settings instance with default support 34 | // for RTL set to false 35 | boolean rtlSupport = false; 36 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1){ 37 | PackageManager pManager = context.getApplicationContext().getPackageManager(); 38 | try { 39 | ApplicationInfo appInfo = pManager.getApplicationInfo(packageName, 0); 40 | //read the Application android:supportsRtl xml properties (if present) 41 | rtlSupport = (appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_RTL) != 0; 42 | } catch (PackageManager.NameNotFoundException nfe){ 43 | throw new IllegalArgumentException("Unable to get info for the provided " + 44 | "packageName, are you sure is it correct? BuildConfig.APPLICATION_ID " + 45 | "should be fine in most cases"); 46 | } 47 | } 48 | mSettings = new CsDrawableSettings(rtlSupport); 49 | } 50 | 51 | /** 52 | * Expose the RTL support flag 53 | * @return true if the developer added android:supportsRtl="true" to the 54 | * application manifest, false otherwise 55 | */ 56 | static boolean isRtlSupportEnabled(){ 57 | if (mSettings == null){ 58 | throw new NullPointerException("You need to call CsDrawableSettings.init() in your " + 59 | "custom Application class or Activity before using the library"); 60 | } 61 | return mSettings.rtlSupportEnabled; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /library/src/main/java/com/matpag/clickdrawabletextview/CsDrawableTouchUtils.java: -------------------------------------------------------------------------------- 1 | package com.matpag.clickdrawabletextview; 2 | 3 | import android.graphics.Canvas; 4 | import android.view.MotionEvent; 5 | import android.widget.TextView; 6 | 7 | /** 8 | * Class to handle touch event on the view and calculate if the touch are happening inside 9 | * the drawables we have defined 10 | * 11 | * Created by Mattia Pagini on 29/04/2017. 12 | */ 13 | final class CsDrawableTouchUtils { 14 | 15 | /** 16 | *

vSpace = vertical space available in the TextView

17 | *

hSpace = horizontal space available in the TextView

18 | */ 19 | private int vSpace, hSpace; 20 | 21 | /** 22 | *

hHeight = half height of the drawable bounds

23 | *

hWidth = half width of the drawable bounds

24 | */ 25 | private int hHeight, hWidth = -1; 26 | 27 | /** 28 | *

centerY = the Y coordinates where the {@link TextView#onDraw(Canvas)} wants start to draw 29 | * the drawable from

30 | *

centerX = the X coordinates where the {@link TextView#onDraw(Canvas)} wants start to draw 31 | * the drawable from the center

32 | */ 33 | private int centerY, centerX = -1; 34 | 35 | /** 36 | * The offset for the X and Y axis (if the view is inside a ScrollView or similar) 37 | */ 38 | private int scrollX, scrollY; 39 | 40 | /** 41 | * The current touch event 42 | */ 43 | private MotionEvent event; 44 | 45 | /** 46 | * The current touched view 47 | */ 48 | private TextView view; 49 | 50 | /** 51 | * Support for RTL layout or not 52 | */ 53 | private boolean isLayoutRTL; 54 | 55 | CsDrawableTouchUtils(MotionEvent event, TextView view, boolean isLayoutRTL){ 56 | this.event = event; 57 | this.view = view; 58 | this.isLayoutRTL = isLayoutRTL; 59 | vSpace = view.getHeight() - view.getCompoundPaddingBottom() - view.getCompoundPaddingTop(); 60 | hSpace = view.getWidth() - view.getCompoundPaddingRight() - view.getCompoundPaddingLeft(); 61 | //if the drawable is extremely large (pushing the edges of the drawable itself 62 | //or of the other drawables out of the current view bounds, will not be possible 63 | //to calculate the correct touch position 64 | if (hSpace < 0 || vSpace < 0){ 65 | throw new IllegalArgumentException("The size of one of your drawable is exceeding the" + 66 | " calculated width or height of the view. In this case you should provide" + 67 | "a smaller drawable or provide a smaller dimension in XML or with the builder"); 68 | } 69 | scrollX = view.getScrollX(); 70 | scrollY = view.getScrollY(); 71 | } 72 | 73 | private boolean isClickInsideDrawableBounds(){ 74 | return (event.getX() >= centerX - hWidth) && (event.getX() <= centerX + hWidth) 75 | && (event.getY() >= centerY - hHeight) && (event.getY() <= centerY + hHeight); 76 | } 77 | 78 | boolean isStartDrawableTouched(CsDrawable drawable){ 79 | if (isLayoutRTL){ 80 | return isRightDrawableTouched(drawable); 81 | } else { 82 | return isLeftDrawableTouched(drawable); 83 | } 84 | } 85 | 86 | boolean isEndDrawableTouched(CsDrawable drawable){ 87 | if (isLayoutRTL){ 88 | return isLeftDrawableTouched(drawable); 89 | } else { 90 | return isRightDrawableTouched(drawable); 91 | } 92 | } 93 | 94 | boolean isTopDrawableTouched(CsDrawable drawable){ 95 | hHeight = drawable.getDrawable().getBounds().height() / 2; 96 | hWidth = drawable.getDrawable().getBounds().width() / 2; 97 | centerX = scrollX + view.getCompoundPaddingLeft() + hSpace / 2; 98 | centerY = scrollY + view.getPaddingTop() + hHeight; 99 | return isClickInsideDrawableBounds(); 100 | } 101 | 102 | boolean isBottomDrawableTouched(CsDrawable drawable){ 103 | hHeight = drawable.getDrawable().getBounds().height() / 2; 104 | hWidth = drawable.getDrawable().getBounds().width() / 2; 105 | centerX = scrollX + view.getCompoundPaddingLeft() + hSpace / 2; 106 | centerY = scrollY + view.getHeight() - view.getPaddingBottom() - hHeight; 107 | return isClickInsideDrawableBounds(); 108 | } 109 | 110 | private boolean isLeftDrawableTouched(CsDrawable drawable){ 111 | hHeight = drawable.getDrawable().getBounds().height() / 2; 112 | hWidth = drawable.getDrawable().getBounds().width() / 2; 113 | centerX = scrollX + view.getPaddingLeft() + hWidth; 114 | centerY = scrollY + view.getCompoundPaddingTop() + vSpace / 2; 115 | return isClickInsideDrawableBounds(); 116 | } 117 | 118 | private boolean isRightDrawableTouched(CsDrawable drawable){ 119 | hHeight = drawable.getDrawable().getBounds().height() / 2; 120 | hWidth = drawable.getDrawable().getBounds().width() / 2; 121 | centerX = scrollX + view.getWidth() - view.getPaddingRight() - hWidth; 122 | centerY = scrollY + view.getCompoundPaddingTop() + vSpace / 2; 123 | return isClickInsideDrawableBounds(); 124 | } 125 | 126 | 127 | 128 | } 129 | -------------------------------------------------------------------------------- /library/src/main/java/com/matpag/clickdrawabletextview/CsDrawableViewManager.java: -------------------------------------------------------------------------------- 1 | package com.matpag.clickdrawabletextview; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.annotation.TargetApi; 5 | import android.content.Context; 6 | import android.content.res.ColorStateList; 7 | import android.content.res.Configuration; 8 | import android.content.res.TypedArray; 9 | import android.graphics.PorterDuff; 10 | import android.graphics.drawable.Drawable; 11 | import android.os.Build; 12 | import android.support.v4.graphics.drawable.DrawableCompat; 13 | import android.text.TextWatcher; 14 | import android.util.AttributeSet; 15 | import android.util.DisplayMetrics; 16 | import android.view.MotionEvent; 17 | import android.view.View; 18 | import android.view.ViewConfiguration; 19 | import android.view.ViewGroup; 20 | import android.view.inputmethod.InputMethodManager; 21 | import android.widget.TextView; 22 | 23 | import com.matpag.clickdrawabletextview.interfaces.OnDrawableClickListener; 24 | 25 | /** 26 | * Wrapper manager for all the ClickDrawableViews which contains the shared logic 27 | * 28 | * Created by Mattia Pagini on 23/04/2017. 29 | */ 30 | 31 | final class CsDrawableViewManager implements ClickableDrawable { 32 | 33 | /** 34 | * This is the reference to the current {@link ClickableDrawable} subclass, one of the view 35 | * between {@link ClickDrawableTextView}, {@link ClickDrawableEditText} or 36 | * {@link ClickDrawableAutoCompleteTextView} 37 | */ 38 | private TextView view; 39 | 40 | private Context mContext; 41 | 42 | //the 4 drawables a view can setup around itself 43 | private CsDrawable mStartDrawable; 44 | private CsDrawable mTopDrawable; 45 | private CsDrawable mEndDrawable; 46 | private CsDrawable mBottomDrawable; 47 | 48 | //the position of the last valid touch performed on one of the drawable 49 | private DrawablePosition mTouchedPosition; 50 | 51 | private static DisplayMetrics mMetrics; 52 | 53 | //default true 54 | private boolean enableTouchOnText = true; 55 | 56 | private Configuration mConfig; 57 | 58 | private TextWatcher mViewTextWatcher; 59 | 60 | /** 61 | * Max allowed duration for a "click", in milliseconds. 62 | * 63 | * I've played a bit with the default android value for recognize a touch 64 | * at {@link ViewConfiguration#getTapTimeout()} but it seemed to me a little to small 65 | * for a normal touch, so i decided to double the amount 66 | */ 67 | private static final int MAX_CLICK_DURATION = ViewConfiguration.getTapTimeout() * 2; 68 | 69 | /** 70 | * Max allowed distance to move during a "click", in DP. 71 | */ 72 | private static final int MAX_CLICK_DISTANCE = 15; 73 | 74 | private float pressedX; 75 | private float pressedY; 76 | private boolean stayedWithinClickDistance; 77 | 78 | /** 79 | * Inteface to listen for drawable click 80 | */ 81 | private OnDrawableClickListener mOnDrawableClickListener; 82 | 83 | CsDrawableViewManager(TextView view){ 84 | this.view = view; 85 | } 86 | 87 | /** 88 | * Init method to call in every {@link TextView} subclass constructor 89 | * @param context the context 90 | * @param attrs the attributes passed from XML 91 | */ 92 | void init(Context context, AttributeSet attrs) { 93 | mContext = context; 94 | mMetrics = context.getResources().getDisplayMetrics(); 95 | mConfig = context.getResources().getConfiguration(); 96 | 97 | if (attrs != null) { 98 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CsDrawableViewManager); 99 | 100 | Drawable startUnwrappedDrawable = a.getDrawable( 101 | R.styleable.CsDrawableViewManager_csStartDrawable); 102 | 103 | if (startUnwrappedDrawable != null) { 104 | Drawable startDrawable = DrawableCompat.wrap(startUnwrappedDrawable); 105 | startDrawable = startDrawable.mutate(); 106 | mStartDrawable = new CsDrawable(context, startDrawable); 107 | int height = a.getDimensionPixelSize( 108 | R.styleable.CsDrawableViewManager_csStartDrawableHeight, -1); 109 | int width = a.getDimensionPixelSize( 110 | R.styleable.CsDrawableViewManager_csStartDrawableWidth, -1); 111 | if (height > -1 && width > -1) { 112 | mStartDrawable.setDrawablePixelSize(height, width); 113 | } 114 | boolean visibility = a.getBoolean( 115 | R.styleable.CsDrawableViewManager_csStartDrawableVisible, true); 116 | mStartDrawable.setVisibility(visibility); 117 | //handle tint and tintMode 118 | ColorStateList tintColor = a.getColorStateList( 119 | R.styleable.CsDrawableViewManager_csStartDrawableTint); 120 | if (tintColor != null){ 121 | DrawableCompat.setTintList(mStartDrawable.getDrawable(), tintColor); 122 | } 123 | PorterDuff.Mode tintMode = parseTintMode(a.getInt( 124 | R.styleable.CsDrawableViewManager_csStartDrawableTintMode, -1)); 125 | if (tintMode != null){ 126 | DrawableCompat.setTintMode(mStartDrawable.getDrawable(), tintMode); 127 | } 128 | } 129 | 130 | Drawable topUnwrappedDrawable = a.getDrawable( 131 | R.styleable.CsDrawableViewManager_csTopDrawable); 132 | if (topUnwrappedDrawable != null) { 133 | Drawable topDrawable = DrawableCompat.wrap(topUnwrappedDrawable); 134 | topDrawable = topDrawable.mutate(); 135 | mTopDrawable = new CsDrawable(context, topDrawable); 136 | int height = a.getDimensionPixelSize( 137 | R.styleable.CsDrawableViewManager_csTopDrawableHeight, -1); 138 | int width = a.getDimensionPixelSize( 139 | R.styleable.CsDrawableViewManager_csTopDrawableWidth, -1); 140 | if (height > -1 && width > -1) { 141 | mTopDrawable.setDrawablePixelSize(height, width); 142 | } 143 | boolean visibility = a.getBoolean( 144 | R.styleable.CsDrawableViewManager_csTopDrawableVisible, true); 145 | mTopDrawable.setVisibility(visibility); 146 | //handle tint and tintMode 147 | ColorStateList tintColor = a.getColorStateList( 148 | R.styleable.CsDrawableViewManager_csTopDrawableTint); 149 | if (tintColor != null){ 150 | DrawableCompat.setTintList(mTopDrawable.getDrawable(), tintColor); 151 | } 152 | PorterDuff.Mode tintMode = parseTintMode(a.getInt( 153 | R.styleable.CsDrawableViewManager_csTopDrawableTintMode, -1)); 154 | if (tintMode != null){ 155 | DrawableCompat.setTintMode(mTopDrawable.getDrawable(), tintMode); 156 | } 157 | } 158 | 159 | Drawable endUnwrappedDrawable = a.getDrawable( 160 | R.styleable.CsDrawableViewManager_csEndDrawable); 161 | if (endUnwrappedDrawable != null) { 162 | Drawable endDrawable = DrawableCompat.wrap(endUnwrappedDrawable); 163 | endDrawable = endDrawable.mutate(); 164 | mEndDrawable = new CsDrawable(context, endDrawable); 165 | int height = a.getDimensionPixelSize( 166 | R.styleable.CsDrawableViewManager_csEndDrawableHeight, -1); 167 | int width = a.getDimensionPixelSize( 168 | R.styleable.CsDrawableViewManager_csEndDrawableWidth, -1); 169 | if (height > -1 && width > -1) { 170 | mEndDrawable.setDrawablePixelSize(height, width); 171 | } 172 | boolean visibility = a.getBoolean( 173 | R.styleable.CsDrawableViewManager_csEndDrawableVisible, true); 174 | mEndDrawable.setVisibility(visibility); 175 | //handle tint and tintMode 176 | ColorStateList tintColor = a.getColorStateList( 177 | R.styleable.CsDrawableViewManager_csEndDrawableTint); 178 | if (tintColor != null){ 179 | DrawableCompat.setTintList(mEndDrawable.getDrawable(), tintColor); 180 | } 181 | PorterDuff.Mode tintMode = parseTintMode(a.getInt( 182 | R.styleable.CsDrawableViewManager_csEndDrawableTintMode, -1)); 183 | if (tintMode != null){ 184 | DrawableCompat.setTintMode(mEndDrawable.getDrawable(), tintMode); 185 | } 186 | } 187 | 188 | Drawable bottomUnwrappedDrawable = a.getDrawable( 189 | R.styleable.CsDrawableViewManager_csBottomDrawable); 190 | if (bottomUnwrappedDrawable != null) { 191 | Drawable bottomDrawable = DrawableCompat.wrap(bottomUnwrappedDrawable); 192 | bottomDrawable = bottomDrawable.mutate(); 193 | mBottomDrawable = new CsDrawable(context, bottomDrawable); 194 | int height = a.getDimensionPixelSize( 195 | R.styleable.CsDrawableViewManager_csBottomDrawableHeight, -1); 196 | int width = a.getDimensionPixelSize( 197 | R.styleable.CsDrawableViewManager_csBottomDrawableWidth, -1); 198 | if (height > -1 && width > -1) { 199 | mBottomDrawable.setDrawablePixelSize(height, width); 200 | } 201 | boolean visibility = a.getBoolean( 202 | R.styleable.CsDrawableViewManager_csBottomDrawableVisible, true); 203 | mBottomDrawable.setVisibility(visibility); 204 | //handle tint and tintMode 205 | ColorStateList tintColor = a.getColorStateList( 206 | R.styleable.CsDrawableViewManager_csBottomDrawableTint); 207 | if (tintColor != null){ 208 | DrawableCompat.setTintList(mBottomDrawable.getDrawable(), tintColor); 209 | } 210 | PorterDuff.Mode tintMode = parseTintMode(a.getInt( 211 | R.styleable.CsDrawableViewManager_csBottomDrawableTintMode, -1)); 212 | if (tintMode != null){ 213 | DrawableCompat.setTintMode(mBottomDrawable.getDrawable(), tintMode); 214 | } 215 | } 216 | 217 | a.recycle(); 218 | } 219 | 220 | invalidateDrawables(); 221 | 222 | setViewOnTouchListener(); 223 | } 224 | 225 | /** 226 | * Check if the current Locale is in RTL mode and if the user has enabled the view to support 227 | * it in the AndroidManifest.xml of his app 228 | * 229 | * NOTE: We should use ViewCompat.getLayoutDirection(view) == 230 | * ViewCompat.LAYOUT_DIRECTION_RTL but when you use developer option : Force RTL Layout 231 | * this is not working correctly (read 232 | * here), checking the configuration is the only rieliable way 233 | * to get the current direction without adding an extra utility to check if the app is running 234 | * in the emulator 235 | * 236 | * @return true if in RTL, false otherwise 237 | */ 238 | private boolean isLayoutRTL(){ 239 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 240 | return CsDrawableSettings.isRtlSupportEnabled() && 241 | mConfig.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 242 | } else { 243 | return CsDrawableSettings.isRtlSupportEnabled(); 244 | } 245 | } 246 | 247 | /** 248 | * Add the drawables to the view 249 | */ 250 | private void invalidateDrawables(){ 251 | if (isLayoutRTL()) { 252 | addCompoundDrawablesRelative(); 253 | } else { 254 | addCompoundDrawables(); 255 | } 256 | } 257 | 258 | /** 259 | * Add the compound drawables to the view, using the default method which 260 | * not take into account RTL support 261 | */ 262 | private void addCompoundDrawables(){ 263 | view.setCompoundDrawables( 264 | mStartDrawable != null ? mStartDrawable.getDrawableIfVisible() : null, 265 | mTopDrawable != null ? mTopDrawable.getDrawableIfVisible() : null, 266 | mEndDrawable != null ? mEndDrawable.getDrawableIfVisible() : null, 267 | mBottomDrawable != null ? mBottomDrawable.getDrawableIfVisible() : null 268 | ); 269 | } 270 | 271 | /** 272 | * Add the compound drawables to the view, if the Locale of the user is in 273 | * RTL mode the drawables will be added in the proper position 274 | */ 275 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) 276 | private void addCompoundDrawablesRelative(){ 277 | view.setCompoundDrawablesRelative( 278 | mStartDrawable != null ? mStartDrawable.getDrawableIfVisible() : null, 279 | mTopDrawable != null ? mTopDrawable.getDrawableIfVisible() : null, 280 | mEndDrawable != null ? mEndDrawable.getDrawableIfVisible() : null, 281 | mBottomDrawable != null ? mBottomDrawable.getDrawableIfVisible() : null 282 | ); 283 | } 284 | 285 | /** 286 | * Handle all the touch events of the current {@link #view} object 287 | */ 288 | @SuppressLint("ClickableViewAccessibility") 289 | private void setViewOnTouchListener(){ 290 | view.setOnTouchListener((v, e) -> { 291 | switch (e.getAction()) { 292 | case MotionEvent.ACTION_DOWN: { 293 | //if the user clicked on one of the drawables, save some datas about it 294 | if (isOneDrawableTouched(e)) { 295 | pressedX = e.getX(); 296 | pressedY = e.getY(); 297 | stayedWithinClickDistance = true; 298 | return true; 299 | } else if (!enableTouchOnText){ 300 | return true; 301 | } 302 | break; 303 | } 304 | case MotionEvent.ACTION_MOVE: { 305 | //if the user moved the finger to much far from the initial tap point, 306 | //we cancel the touch on the drawable 307 | if (stayedWithinClickDistance && 308 | distance(pressedX, pressedY, e.getX(), e.getY()) > MAX_CLICK_DISTANCE) { 309 | stayedWithinClickDistance = false; 310 | } 311 | break; 312 | } 313 | case MotionEvent.ACTION_UP: { 314 | // proceed with the drawable touch logic 315 | long eventDuration = e.getEventTime() - e.getDownTime(); 316 | if (isOneDrawableTouching()){ 317 | if ((eventDuration < MAX_CLICK_DURATION) && stayedWithinClickDistance) { 318 | //dispatch accessibility events 319 | view.performClick(); 320 | dispatchDrawableClickEvent(); 321 | } 322 | resetTouchedDrawable(); 323 | return true; 324 | } 325 | } 326 | } 327 | return view.onTouchEvent(e); 328 | }); 329 | } 330 | 331 | /** 332 | * Using the coordinates X and Y of the touch event, we check if they are inside 333 | * the bounds of one of the showing drawables, if true set {@link #mTouchedPosition} 334 | * with one of correct values of {@link DrawablePosition} 335 | */ 336 | private boolean isOneDrawableTouched(MotionEvent event){ 337 | CsDrawableTouchUtils cdu = new CsDrawableTouchUtils(event, view, isLayoutRTL()); 338 | if (mEndDrawable != null && mEndDrawable.isVisible() 339 | && cdu.isEndDrawableTouched(mEndDrawable)){ 340 | mTouchedPosition = DrawablePosition.END; 341 | } else if (mStartDrawable != null && mStartDrawable.isVisible() 342 | && cdu.isStartDrawableTouched(mStartDrawable)){ 343 | mTouchedPosition = DrawablePosition.START; 344 | } else if (mTopDrawable != null && mTopDrawable.isVisible() 345 | && cdu.isTopDrawableTouched(mTopDrawable)){ 346 | mTouchedPosition = DrawablePosition.TOP; 347 | } else if (mBottomDrawable != null && mBottomDrawable.isVisible() 348 | && cdu.isBottomDrawableTouched(mBottomDrawable)){ 349 | mTouchedPosition = DrawablePosition.BOTTOM; 350 | } 351 | return mTouchedPosition != null; 352 | } 353 | 354 | /** 355 | * Dispatch click event if the listener has been attached 356 | */ 357 | private void dispatchDrawableClickEvent(){ 358 | if (mOnDrawableClickListener != null && mTouchedPosition != null){ 359 | mOnDrawableClickListener.onClick(view, mTouchedPosition); 360 | } 361 | } 362 | 363 | private void resetTouchedDrawable(){ 364 | mTouchedPosition = null; 365 | } 366 | 367 | private boolean isOneDrawableTouching(){ 368 | return mTouchedPosition != null; 369 | } 370 | 371 | 372 | @Override 373 | public void setOnDrawableClickListener(OnDrawableClickListener listener){ 374 | mOnDrawableClickListener = listener; 375 | } 376 | 377 | @Override 378 | public void removeOnDrawableClickListener(){ 379 | mOnDrawableClickListener = null; 380 | } 381 | 382 | @Override 383 | public void addStartCsDrawable(CsDrawable csDrawable) { 384 | mStartDrawable = csDrawable; 385 | invalidateDrawables(); 386 | } 387 | 388 | @Override 389 | public void addTopCsDrawable(CsDrawable csDrawable) { 390 | mTopDrawable = csDrawable; 391 | invalidateDrawables(); 392 | } 393 | 394 | @Override 395 | public void addEndCsDrawable(CsDrawable csDrawable) { 396 | mEndDrawable = csDrawable; 397 | invalidateDrawables(); 398 | } 399 | 400 | @Override 401 | public void addBottomCsDrawable(CsDrawable csDrawable) { 402 | mBottomDrawable = csDrawable; 403 | invalidateDrawables(); 404 | } 405 | 406 | @Override 407 | public void showStartCsDrawable(boolean visible) { 408 | if (mStartDrawable.isVisible() != visible){ 409 | mStartDrawable.setVisibility(visible); 410 | invalidateDrawables(); 411 | } 412 | } 413 | 414 | @Override 415 | public void showTopCsDrawable(boolean visible) { 416 | if (mTopDrawable.isVisible() != visible) { 417 | mTopDrawable.setVisibility(visible); 418 | invalidateDrawables(); 419 | } 420 | } 421 | 422 | @Override 423 | public void showEndCsDrawable(boolean visible) { 424 | if (mEndDrawable.isVisible() != visible) { 425 | mEndDrawable.setVisibility(visible); 426 | invalidateDrawables(); 427 | } 428 | } 429 | 430 | @Override 431 | public void showBottomCsDrawable(boolean visible) { 432 | if (mBottomDrawable.isVisible() != visible) { 433 | mBottomDrawable.setVisibility(visible); 434 | invalidateDrawables(); 435 | } 436 | } 437 | 438 | @Override 439 | public void disableFocusOnText(boolean preventReFocus, boolean closeKeyboard) { 440 | //block refocus on others EditText on the same ViewGroup 441 | if (preventReFocus) { 442 | ViewGroup rootView = (ViewGroup) view.getRootView(); 443 | int dfValue = rootView.getDescendantFocusability(); 444 | rootView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 445 | view.clearFocus(); 446 | rootView.setDescendantFocusability(dfValue); 447 | } else { 448 | view.clearFocus(); 449 | } 450 | if (closeKeyboard){ 451 | //workaround a problem with some keyboard implementations like SwiftKey, where they 452 | //don't remove the underline from text even when the keyboard is closed 453 | if (mViewTextWatcher == null){ 454 | view.setText(view.getText()); 455 | } else { 456 | //if a TextWatcher is present remove it before calling setText or it could result 457 | //in false positive changes in the user code 458 | view.removeTextChangedListener(mViewTextWatcher); 459 | view.setText(view.getText()); 460 | view.addTextChangedListener(mViewTextWatcher); 461 | } 462 | //hide the keyboard if opened 463 | setImeVisibility(false); 464 | } 465 | enableTouchOnText = false; 466 | } 467 | 468 | @Override 469 | public void enableFocusOnText(boolean openKeyboard) { 470 | enableTouchOnText = true; 471 | if (openKeyboard) { 472 | setImeVisibility(true); 473 | view.requestFocus(); 474 | } 475 | } 476 | 477 | @Override 478 | public void openKeyboard() { 479 | setImeVisibility(true); 480 | } 481 | 482 | @Override 483 | public void closeKeyboard() { 484 | setImeVisibility(false); 485 | } 486 | 487 | 488 | void addTextWatcher(TextWatcher textWatcher){ 489 | mViewTextWatcher = textWatcher; 490 | } 491 | 492 | void removeTextWatcher(){ 493 | mViewTextWatcher = null; 494 | } 495 | 496 | /** 497 | * Open the keyboard with some Google trick 498 | * Link here 499 | * @param visible true for open the keyboard, false to close it 500 | */ 501 | private void setImeVisibility(final boolean visible) { 502 | if (visible) { 503 | view.post(mShowImeRunnable); 504 | } else { 505 | view.removeCallbacks(mShowImeRunnable); 506 | InputMethodManager imm = (InputMethodManager) mContext 507 | .getSystemService(Context.INPUT_METHOD_SERVICE); 508 | if (imm != null) { 509 | imm.hideSoftInputFromWindow(view.getWindowToken(), 0); 510 | } 511 | } 512 | } 513 | 514 | private Runnable mShowImeRunnable = new Runnable() { 515 | public void run() { 516 | InputMethodManager imm = (InputMethodManager) mContext 517 | .getSystemService(Context.INPUT_METHOD_SERVICE); 518 | if (imm != null) { 519 | imm.showSoftInput(view, 0); 520 | } 521 | } 522 | }; 523 | 524 | /** 525 | * Remove all the custom drawables 526 | */ 527 | @Override 528 | public void removeAllCsDrawables() { 529 | mStartDrawable = null; 530 | mTopDrawable = null; 531 | mEndDrawable = null; 532 | mBottomDrawable = null; 533 | invalidateDrawables(); 534 | } 535 | 536 | /** 537 | * Calculate the distance from 2 point in DP 538 | */ 539 | private static float distance(float x1, float y1, float x2, float y2) { 540 | float dx = x1 - x2; 541 | float dy = y1 - y2; 542 | float distanceInPx = (float) Math.sqrt(dx * dx + dy * dy); 543 | return pxToDp(distanceInPx); 544 | } 545 | 546 | private static float pxToDp(float px) { 547 | return px / mMetrics.density; 548 | } 549 | 550 | /** 551 | * Parses a {@link android.graphics.PorterDuff.Mode} from a tintMode 552 | * attribute's enum value. 553 | * 554 | * Copied from the AOSP source in the {@link Drawable} class 555 | * here 556 | */ 557 | private static PorterDuff.Mode parseTintMode(int value) { 558 | switch (value) { 559 | case 3: return PorterDuff.Mode.SRC_OVER; 560 | case 5: return PorterDuff.Mode.SRC_IN; 561 | case 9: return PorterDuff.Mode.SRC_ATOP; 562 | case 14: return PorterDuff.Mode.MULTIPLY; 563 | case 15: return PorterDuff.Mode.SCREEN; 564 | case 16: return PorterDuff.Mode.ADD; 565 | default: return null; 566 | } 567 | } 568 | 569 | } 570 | -------------------------------------------------------------------------------- /library/src/main/java/com/matpag/clickdrawabletextview/DrawablePosition.java: -------------------------------------------------------------------------------- 1 | package com.matpag.clickdrawabletextview; 2 | 3 | /** 4 | * Enum position 5 | * Created by Mattia Pagini on 13/02/2017. 6 | */ 7 | 8 | public enum DrawablePosition { 9 | START, //LEFT if not in RTL mode 10 | TOP, 11 | END, //RIGHT if not in RTL mode 12 | BOTTOM 13 | } -------------------------------------------------------------------------------- /library/src/main/java/com/matpag/clickdrawabletextview/interfaces/OnDrawableClickListener.java: -------------------------------------------------------------------------------- 1 | package com.matpag.clickdrawabletextview.interfaces; 2 | 3 | import android.view.View; 4 | 5 | import com.matpag.clickdrawabletextview.DrawablePosition; 6 | 7 | /** 8 | * 9 | * Interface for handling drawable touch events 10 | * 11 | * Created by Mattia Pagini on 03/03/2017. 12 | */ 13 | public interface OnDrawableClickListener { 14 | 15 | /** 16 | * Drawable click event 17 | * @param view one of the subclasses of the {@link android.widget.TextView} widget, 18 | * which received the touch input 19 | * @param position the position of the clicked drawable 20 | */ 21 | void onClick(View view, DrawablePosition position); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /library/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 18 | 19 | 21 | 22 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 42 | 43 | 45 | 46 | 48 | 49 | 51 | 52 | 53 | 54 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 69 | 70 | 72 | 73 | 75 | 76 | 78 | 79 | 80 | 81 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 96 | 97 | 99 | 100 | 102 | 103 | 105 | 106 | 107 | 108 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':library' 2 | --------------------------------------------------------------------------------