├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── dictionaries │ └── YeChao.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── yechaoa │ │ └── customviews │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── yechaoa │ │ │ └── customviews │ │ │ ├── MainActivity.kt │ │ │ ├── expand │ │ │ ├── ExpandActivity.kt │ │ │ └── ExpandLinearLayout.kt │ │ │ ├── palette │ │ │ ├── PaletteActivity.kt │ │ │ └── PaletteView.kt │ │ │ ├── progress │ │ │ └── ProgressActivity.kt │ │ │ ├── randomtext │ │ │ ├── RandomTextActivity.kt │ │ │ └── RandomTextView.kt │ │ │ ├── roundimg │ │ │ ├── RoundImageView.kt │ │ │ └── RoundImageViewActivity.kt │ │ │ └── tagtext │ │ │ ├── TagTextView.kt │ │ │ └── TagTextViewActivity.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ ├── progress_bar.xml │ │ └── selector_btn.xml │ │ ├── layout │ │ ├── activity_expand.xml │ │ ├── activity_main.xml │ │ ├── activity_palette.xml │ │ ├── activity_progress.xml │ │ ├── activity_random_text.xml │ │ ├── activity_round_image_view.xml │ │ ├── activity_tag_text_view.xml │ │ └── item_text_tag.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_arrow_down.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_yechaoa.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── yechaoa │ └── customviews │ └── ExampleUnitTest.kt ├── build.gradle ├── gif ├── expand.gif ├── paletteView.png ├── progress1.gif ├── randomText.gif └── roundimg.png ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 123 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/dictionaries/YeChao.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | xfermode 5 | yechao 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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 [yyyy] [name of copyright owner] 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. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CustomViews 自定义view集合 2 | 3 | ![](https://img.shields.io/badge/language-kotlin-orange.svg) 4 | ![](https://img.shields.io/hexpm/l/plug.svg) 5 | ![](https://img.shields.io/badge/CSDN-yechaoa-green.svg) 6 | 7 | 后续会慢慢增加一些常用自定义view。 8 | 9 | # 效果 10 | 11 | | | | | 12 | | :--: | :--: | :--: | 13 | | 展开收起、抽屉效果 | 进度条 | 随机数、验证码 | 14 | 15 | | | | 16 | | :--: | :--: | 17 | | 圆角ImageView | 画板 | 18 | 19 |
20 | 21 | ### Todo 22 | - 进度条 23 | - 对话框 24 | - 等 25 | 26 |
27 | 28 | 29 | ``` 30 | Copyright [2020] [yechaoa] 31 | 32 | Licensed under the Apache License, Version 2.0 (the "License"); 33 | you may not use this file except in compliance with the License. 34 | You may obtain a copy of the License at 35 | 36 | http://www.apache.org/licenses/LICENSE-2.0 37 | 38 | Unless required by applicable law or agreed to in writing, software 39 | distributed under the License is distributed on an "AS IS" BASIS, 40 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 41 | See the License for the specific language governing permissions and 42 | limitations under the License. 43 | ``` 44 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | compileSdkVersion 30 6 | buildToolsVersion "30.0.2" 7 | 8 | defaultConfig { 9 | applicationId "com.yechaoa.customviews" 10 | minSdkVersion 23 11 | targetSdkVersion 30 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | buildFeatures { 25 | viewBinding = true 26 | } 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_1_8 29 | targetCompatibility JavaVersion.VERSION_1_8 30 | } 31 | 32 | kotlinOptions { 33 | jvmTarget = '1.8' 34 | } 35 | } 36 | 37 | dependencies { 38 | implementation fileTree(dir: "libs", include: ["*.jar"]) 39 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 40 | implementation 'androidx.core:core-ktx:1.5.0' 41 | implementation 'androidx.appcompat:appcompat:1.2.0' 42 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 43 | implementation 'com.google.android.material:material:1.2.1' 44 | testImplementation 'junit:junit:4.13.2' 45 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 46 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 47 | 48 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/yechaoa/customviews/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.yechaoa.customviews 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.yechaoa.customviews", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/yechaoa/customviews/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.yechaoa.customviews 2 | 3 | import android.content.Intent 4 | import androidx.appcompat.app.AppCompatActivity 5 | import android.os.Bundle 6 | import com.yechaoa.customviews.databinding.ActivityMainBinding 7 | import com.yechaoa.customviews.expand.ExpandActivity 8 | import com.yechaoa.customviews.palette.PaletteActivity 9 | import com.yechaoa.customviews.progress.ProgressActivity 10 | import com.yechaoa.customviews.randomtext.RandomTextActivity 11 | import com.yechaoa.customviews.roundimg.RoundImageViewActivity 12 | import com.yechaoa.customviews.tagtext.TagTextViewActivity 13 | 14 | class MainActivity : AppCompatActivity() { 15 | 16 | private val mBinding by lazy { ActivityMainBinding.inflate(layoutInflater) } 17 | 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | setContentView(mBinding.root) 21 | 22 | mBinding.btnExpand.setOnClickListener { 23 | startActivity(Intent(this, ExpandActivity::class.java)) 24 | } 25 | 26 | mBinding.btnProgress.setOnClickListener { 27 | startActivity(Intent(this, ProgressActivity::class.java)) 28 | } 29 | 30 | mBinding.btnRandomText.setOnClickListener { 31 | startActivity(Intent(this, RandomTextActivity::class.java)) 32 | } 33 | 34 | mBinding.btnTagText.setOnClickListener { 35 | startActivity(Intent(this, TagTextViewActivity::class.java)) 36 | } 37 | 38 | mBinding.btnPalette.setOnClickListener { 39 | startActivity(Intent(this, PaletteActivity::class.java)) 40 | } 41 | 42 | mBinding.btnRoundImage.setOnClickListener { 43 | startActivity(Intent(this, RoundImageViewActivity::class.java)) 44 | } 45 | 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yechaoa/customviews/expand/ExpandActivity.kt: -------------------------------------------------------------------------------- 1 | package com.yechaoa.customviews.expand 2 | 3 | import android.animation.ObjectAnimator 4 | import android.os.Bundle 5 | import android.widget.ImageView 6 | import androidx.appcompat.app.AppCompatActivity 7 | import com.yechaoa.customviews.databinding.ActivityExpandBinding 8 | 9 | class ExpandActivity : AppCompatActivity() { 10 | 11 | private val mBinding by lazy { ActivityExpandBinding.inflate(layoutInflater) } 12 | 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | super.onCreate(savedInstanceState) 15 | setContentView(mBinding.root) 16 | 17 | mBinding.llBtn.setOnClickListener { 18 | val toggle = mBinding.ell.toggle() 19 | mBinding.tvTip.text = if (toggle) "收起" else "展开" 20 | startImageRotate(mBinding.ivArrow, toggle) 21 | } 22 | } 23 | 24 | /** 25 | * 旋转箭头图标 26 | */ 27 | private fun startImageRotate(imageView: ImageView, toggle: Boolean) { 28 | val tarRotate: Float = if (toggle) { 29 | 0f 30 | } else { 31 | 180f 32 | } 33 | 34 | imageView.apply { 35 | ObjectAnimator.ofFloat(this, "rotation", rotation, tarRotate).let { 36 | it.duration = 300 37 | it.start() 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yechaoa/customviews/expand/ExpandLinearLayout.kt: -------------------------------------------------------------------------------- 1 | package com.yechaoa.customviews.expand 2 | 3 | import android.animation.ObjectAnimator 4 | import android.annotation.SuppressLint 5 | import android.content.Context 6 | import android.util.AttributeSet 7 | import android.widget.LinearLayout 8 | import androidx.core.view.marginBottom 9 | import androidx.core.view.marginTop 10 | 11 | /** 12 | * Created by yechao on 2020/11/11. 13 | * Describe : 可收起展开的LinearLayout 14 | * 15 | * 步骤: 16 | * 1.初始化参数 设置方向等 17 | * 2.根据动画执行进度计算高度 18 | * 19 | */ 20 | class ExpandLinearLayout : LinearLayout { 21 | 22 | //是否展开,默认展开 23 | private var isOpen = true 24 | 25 | //第一个子view的高度,即收起保留高度 26 | private var firstChildHeight = 0 27 | 28 | //所有子view高度,即总高度 29 | private var allChildHeight = 0 30 | 31 | /** 32 | * 动画值改变的时候 请求重新布局 33 | */ 34 | private var animPercent: Float = 0f 35 | set(value) { 36 | field = value 37 | requestLayout() 38 | } 39 | 40 | constructor(context: Context) : super(context) { 41 | initView() 42 | } 43 | 44 | constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) { 45 | initView() 46 | } 47 | 48 | constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int) : super( 49 | context, 50 | attributeSet, 51 | defStyleAttr 52 | ) { 53 | initView() 54 | } 55 | 56 | private fun initView() { 57 | //横向的话 稍加修改计算宽度即可 58 | orientation = VERTICAL 59 | 60 | animPercent = 1f 61 | isOpen = true 62 | } 63 | 64 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 65 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 66 | 67 | //重置高度 68 | allChildHeight = 0 69 | firstChildHeight = 0 70 | 71 | if (childCount > 0) { 72 | 73 | //遍历计算高度 74 | for (index in 0 until childCount) { 75 | //这个地方实际使用中除了measuredHeight,以及margin等,也要计算在内 76 | if (index == 0) { 77 | firstChildHeight = getChildAt(index).measuredHeight 78 | +getChildAt(index).marginTop + getChildAt(index).marginBottom 79 | +this.paddingTop + this.paddingBottom 80 | } 81 | //实际使用时或包括padding等 82 | allChildHeight += getChildAt(index).measuredHeight + getChildAt(index).marginTop + getChildAt(index).marginBottom 83 | 84 | //最后一条的时候 加上当前view自身的padding 85 | if (index == childCount - 1) { 86 | allChildHeight += this.paddingTop + this.paddingBottom 87 | } 88 | } 89 | 90 | // 根据是否展开设置高度 91 | if (isOpen) { 92 | setMeasuredDimension( 93 | widthMeasureSpec, 94 | firstChildHeight + ((allChildHeight - firstChildHeight) * animPercent).toInt() 95 | ) 96 | } else { 97 | setMeasuredDimension( 98 | widthMeasureSpec, 99 | allChildHeight - ((allChildHeight - firstChildHeight) * animPercent).toInt() 100 | ) 101 | } 102 | } 103 | } 104 | 105 | fun toggle(): Boolean { 106 | isOpen = !isOpen 107 | startAnim() 108 | return isOpen 109 | } 110 | 111 | /** 112 | * 执行动画的时候 更改 animPercent 属性的值 即从0-1 113 | */ 114 | @SuppressLint("AnimatorKeep") 115 | private fun startAnim() { 116 | //ofFloat,of xxxX 根据参数类型来确定 117 | //1,动画对象,即当前view。2.动画属性名。3,起始值。4,目标值。 118 | val animator = ObjectAnimator.ofFloat(this, "animPercent", 0f, 1f) 119 | animator.duration = 500 120 | animator.start() 121 | } 122 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yechaoa/customviews/palette/PaletteActivity.kt: -------------------------------------------------------------------------------- 1 | package com.yechaoa.customviews.palette 2 | 3 | import android.graphics.Color 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.core.content.ContextCompat 7 | import com.yechaoa.customviews.R 8 | import com.yechaoa.customviews.databinding.ActivityPaletteBinding 9 | 10 | class PaletteActivity : AppCompatActivity() { 11 | 12 | private val mBinding by lazy { ActivityPaletteBinding.inflate(layoutInflater) } 13 | 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | setContentView(mBinding.root) 17 | setSupportActionBar(mBinding.toolbar) 18 | setListener() 19 | } 20 | 21 | private fun setListener() { 22 | mBinding.btnRed.setOnClickListener { 23 | mBinding.paletteView.setPaintColor(Color.RED) 24 | } 25 | 26 | mBinding.btnGreen.setOnClickListener { 27 | mBinding.paletteView.setPaintColor(Color.GREEN) 28 | } 29 | 30 | mBinding.btnWidth.setOnClickListener { 31 | mBinding.paletteView.setStrokeWidth(30f) 32 | } 33 | 34 | mBinding.btnClear.setOnClickListener { 35 | mBinding.paletteView.clear() 36 | } 37 | 38 | mBinding.btnRevert.setOnClickListener { 39 | mBinding.paletteView.revert() 40 | } 41 | 42 | mBinding.btnReset.setOnClickListener { 43 | mBinding.paletteView.reset() 44 | } 45 | 46 | mBinding.tvSave.setOnClickListener { 47 | mBinding.paletteView.save() 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yechaoa/customviews/palette/PaletteView.kt: -------------------------------------------------------------------------------- 1 | package com.yechaoa.customviews.palette 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.graphics.* 6 | import android.os.Environment 7 | import android.util.AttributeSet 8 | import android.util.Log 9 | import android.view.MotionEvent 10 | import android.view.View 11 | import android.widget.Toast 12 | import androidx.annotation.ColorInt 13 | import java.io.File 14 | import java.io.FileOutputStream 15 | import java.text.SimpleDateFormat 16 | import java.util.* 17 | 18 | /** 19 | * Created by yechao on 2021/3/12. 20 | * Describe : 画板,主要涉及paint的常用属性,以及path的用法 21 | */ 22 | class PaletteView : View { 23 | 24 | private val tag = this.javaClass.name 25 | 26 | private var mPaint: Paint = Paint() 27 | private lateinit var mPath: Path 28 | private var mPaletteList = mutableListOf() 29 | 30 | //起始位置 31 | private var startX: Float = 0f 32 | private var startY: Float = 0f 33 | 34 | //结束位置 35 | private var stopX = 0f 36 | private var stopY = 0f 37 | 38 | private lateinit var cacheBitmap: Bitmap //定义一个内存中的图片,该图片作为缓冲区 39 | private lateinit var cacheCanvas: Canvas //定义cacheBitmap上的Canvas对象 40 | 41 | class PaletteInfo constructor(var path: Path, var paint: Paint) 42 | 43 | constructor(context: Context) : super(context) { 44 | init() 45 | } 46 | 47 | //如果只是在xml文件中使用,实现这一个构造即可 48 | constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) { 49 | init() 50 | } 51 | 52 | constructor(context: Context, attributeSet: AttributeSet, defStyle: Int) : super(context, attributeSet, defStyle) { 53 | init() 54 | } 55 | 56 | private fun init() { 57 | 58 | initPaint() 59 | } 60 | 61 | private fun initPaint() { 62 | //设置颜色 63 | mPaint.color = Color.RED 64 | //线条宽度 65 | mPaint.strokeWidth = 10f 66 | //画线模式 67 | mPaint.style = Paint.Style.STROKE 68 | //线头形状 圆头 69 | mPaint.strokeCap = Paint.Cap.ROUND 70 | //拐角形状 圆角 71 | mPaint.strokeJoin = Paint.Join.ROUND 72 | //开启抗锯齿 73 | mPaint.isAntiAlias = true 74 | //防抖动 75 | mPaint.isDither = true 76 | } 77 | 78 | @SuppressLint("ClickableViewAccessibility") 79 | override fun onTouchEvent(event: MotionEvent): Boolean { 80 | val currX = event.x 81 | val currY = event.y 82 | when (event.action) { 83 | MotionEvent.ACTION_DOWN -> { 84 | startX = currX 85 | startY = currY 86 | 87 | if (!::cacheBitmap.isInitialized) { 88 | initCache() 89 | } 90 | mPath = Path() 91 | //加到集合里,保存每一次轨迹 92 | mPaletteList.add(PaletteInfo(Path(mPath), Paint(mPaint))) 93 | //每一次开始的起始位置 94 | mPath.moveTo(currX, currY) 95 | } 96 | MotionEvent.ACTION_MOVE -> { 97 | stopX = currX 98 | stopY = currY 99 | //二次贝塞尔曲线 100 | mPath.quadTo(startX, startY, stopX, stopY) 101 | 102 | //再次赋值 更新位置 103 | startX = currX 104 | startY = currY 105 | 106 | cacheCanvas.drawPath(mPath, mPaint) 107 | invalidate() 108 | } 109 | MotionEvent.ACTION_UP -> { 110 | //重置 111 | mPath.reset() 112 | } 113 | } 114 | return true 115 | } 116 | 117 | private fun initCache() { 118 | //创建一个相同大小的缓存区 119 | cacheBitmap = Bitmap.createBitmap(this.width, this.height, Bitmap.Config.ARGB_8888) 120 | //一个新的画布 121 | cacheCanvas = Canvas() 122 | cacheCanvas.setBitmap(cacheBitmap) 123 | } 124 | 125 | @SuppressLint("DrawAllocation") 126 | override fun onDraw(canvas: Canvas?) { 127 | super.onDraw(canvas) 128 | canvas?.let { 129 | it.drawColor(Color.WHITE)//bg 130 | if (::cacheBitmap.isInitialized) { 131 | it.drawBitmap(cacheBitmap, 0f, 0f, null) 132 | } 133 | } 134 | } 135 | 136 | /** 137 | * 设置颜色 138 | */ 139 | fun setPaintColor(@ColorInt color: Int) { 140 | initPaint() 141 | //清除 xfermode 取消橡皮擦效果 142 | mPaint.xfermode = null 143 | mPaint.color = color 144 | } 145 | 146 | /** 147 | * 设置线条宽度 148 | */ 149 | fun setStrokeWidth(width: Float) { 150 | mPaint.strokeWidth = width 151 | } 152 | 153 | /** 154 | * 橡皮擦 155 | */ 156 | fun clear() { 157 | //设置颜色策略(源图像绘制到目标图像处时应该怎样确定二者结合后的颜色) 158 | mPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) 159 | //橡皮擦的时候可以适当加大,实际项目中应该是SeekBar调节 160 | mPaint.strokeWidth = 50f 161 | } 162 | 163 | /** 164 | * 撤回 need to do 165 | */ 166 | fun revert() { 167 | if (mPaletteList.size > 0) { 168 | Log.i(tag, "revert---" + mPaletteList.size) 169 | mPaletteList.removeAt(mPaletteList.size - 1) 170 | // drawCache() 171 | // cacheBitmap.eraseColor(Color.TRANSPARENT) 172 | mPaletteList.forEach { 173 | cacheCanvas.drawPath(it.path, it.paint) 174 | } 175 | invalidate() 176 | } 177 | } 178 | 179 | /** 180 | * 清空 181 | */ 182 | fun reset() { 183 | if (mPaletteList.size > 0) { 184 | cacheBitmap.eraseColor(Color.TRANSPARENT) 185 | invalidate() 186 | } 187 | } 188 | 189 | private fun drawCache() { 190 | Log.i(tag, "drawCache---" + mPaletteList.size) 191 | cacheBitmap.eraseColor(Color.TRANSPARENT) 192 | mPaletteList.forEach { 193 | cacheCanvas.drawPath(it.path, it.paint) 194 | } 195 | invalidate() 196 | } 197 | 198 | /** 199 | * 保存 need test 200 | * \内部存储\Android\data\com.fr.lawappandroid\files\Pictures 201 | */ 202 | fun save() { 203 | val storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) 204 | var file: File? = null 205 | try { 206 | // prefix 前缀 , suffix 后缀 , directory 目录 207 | file = File.createTempFile(getFileName(), ".png", storageDir) 208 | val out = FileOutputStream(file) 209 | cacheBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out) 210 | out.flush() 211 | out.close() 212 | } catch (e: Exception) { 213 | e.printStackTrace() 214 | } 215 | Toast.makeText(context, file?.name, Toast.LENGTH_SHORT).show() 216 | } 217 | 218 | /** 219 | * 时间戳文件名 220 | * filename="JPEG_20201222_115650_6934479097884473000.jpg 221 | */ 222 | private fun getFileName(): String { 223 | val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) 224 | return "PIC_" + timeStamp + "_" 225 | } 226 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yechaoa/customviews/progress/ProgressActivity.kt: -------------------------------------------------------------------------------- 1 | package com.yechaoa.customviews.progress 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AppCompatActivity 6 | import com.yechaoa.customviews.databinding.ActivityProgressBinding 7 | 8 | class ProgressActivity : AppCompatActivity() { 9 | 10 | private val mBinding by lazy { ActivityProgressBinding.inflate(layoutInflater) } 11 | 12 | @SuppressLint("SetTextI18n") 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | super.onCreate(savedInstanceState) 15 | setContentView(mBinding.root) 16 | 17 | mBinding.tvTip.text = "最大:" + mBinding.progressBar.max + " 当前:" + mBinding.progressBar.progress 18 | 19 | mBinding.btnAdd.setOnClickListener { 20 | mBinding.progressBar.progress += 10 21 | mBinding.tvTip.text = "最大:" + mBinding.progressBar.max + " 当前:" + mBinding.progressBar.progress 22 | } 23 | mBinding.btnSubtract.setOnClickListener { 24 | mBinding.progressBar.progress -= 10 25 | mBinding.tvTip.text = "最大:" + mBinding.progressBar.max + " 当前:" + mBinding.progressBar.progress 26 | } 27 | 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yechaoa/customviews/randomtext/RandomTextActivity.kt: -------------------------------------------------------------------------------- 1 | package com.yechaoa.customviews.randomtext 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import com.yechaoa.customviews.R 6 | 7 | class RandomTextActivity : AppCompatActivity() { 8 | override fun onCreate(savedInstanceState: Bundle?) { 9 | super.onCreate(savedInstanceState) 10 | setContentView(R.layout.activity_random_text) 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yechaoa/customviews/randomtext/RandomTextView.kt: -------------------------------------------------------------------------------- 1 | package com.yechaoa.customviews.randomtext 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.graphics.Canvas 6 | import android.graphics.Color 7 | import android.graphics.Paint 8 | import android.graphics.Rect 9 | import android.util.AttributeSet 10 | import android.util.TypedValue 11 | import android.view.View 12 | import com.yechaoa.customviews.R 13 | import kotlin.random.Random 14 | 15 | /** 16 | * Created by yechao on 2020/11/13. 17 | * Describe : 随机数字的TextView 18 | * 19 | * 步骤: 20 | * 1.自定义属性 21 | * 2.添加构造方法 22 | * 3.在构造里获取自定义样式 23 | * 4.重写onDraw计算坐标绘制 24 | * 5.重写onMeasure测量宽高 25 | * 6.设置点击事件 26 | */ 27 | class RandomTextView : View { 28 | 29 | //文本 30 | private var mRandomText: String 31 | 32 | //文本颜色 33 | private var mRandomTextColor: Int = 0 34 | 35 | //文本字体大小 36 | private var mRandomTextSize: Int = 0 37 | 38 | private var paint = Paint() 39 | private var bounds = Rect() 40 | 41 | //调用两个参数的构造 42 | constructor(context: Context) : this(context, null) 43 | 44 | //xml默认调用两个参数的构造,再调用三个参数的构造,在三个参数构造里获取自定义属性 45 | constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0) 46 | 47 | constructor(context: Context, attributeSet: AttributeSet?, defStyle: Int) : super(context, attributeSet, defStyle) { 48 | //获取自定义属性 49 | val typedArray = context.theme.obtainStyledAttributes( 50 | attributeSet, 51 | R.styleable.RandomTextView, 52 | defStyle, 53 | 0 54 | ) 55 | 56 | mRandomText = typedArray.getString(R.styleable.RandomTextView_randomText).toString() 57 | mRandomTextColor = typedArray.getColor(R.styleable.RandomTextView_randomTextColor, Color.BLACK)//默认黑色 58 | mRandomTextSize = typedArray.getDimensionPixelSize( 59 | R.styleable.RandomTextView_randomTextSize, 60 | TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16F, resources.displayMetrics).toInt() 61 | ) 62 | 63 | //获取完回收 64 | typedArray.recycle() 65 | 66 | 67 | paint.textSize = mRandomTextSize.toFloat() 68 | 69 | //返回文本边界,即包含文本的最小矩形,没有所谓“留白”,返回比measureText()更精确的text宽高,数据保存在bounds里 70 | paint.getTextBounds(mRandomText, 0, mRandomText.length, bounds) 71 | 72 | /** 73 | * 添加点击事件 74 | */ 75 | this.setOnClickListener { 76 | mRandomText = randomText() 77 | //更新 78 | postInvalidate() 79 | } 80 | 81 | } 82 | 83 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 84 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 85 | 86 | /** 87 | * EXACTLY:一般是设置了明确的值或者是MATCH_PARENT 88 | * AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT 89 | * UNSPECIFIED:表示子布局想要多大就多大,很少使用 90 | */ 91 | val widthMode = MeasureSpec.getMode(widthMeasureSpec) 92 | val widthSize = MeasureSpec.getSize(widthMeasureSpec) 93 | 94 | val heightMode = MeasureSpec.getMode(heightMeasureSpec) 95 | val heightSize = MeasureSpec.getSize(heightMeasureSpec) 96 | 97 | var width = 0 98 | var height = 0 99 | 100 | //如果指定了宽度,或不限制宽度,用可用宽度即可,如果是WARP_CONTENT,则用文本宽度,再加上左右padding 101 | when (widthMode) { 102 | MeasureSpec.UNSPECIFIED, 103 | MeasureSpec.EXACTLY -> { 104 | width = widthSize + paddingLeft + paddingRight 105 | } 106 | MeasureSpec.AT_MOST -> { 107 | width = bounds.width() + paddingLeft + paddingRight 108 | } 109 | } 110 | 111 | //如果指定了高度,或不限制高度,用可用高度即可,如果是WARP_CONTENT,则用文本高度,再加上上下padding 112 | when (heightMode) { 113 | MeasureSpec.UNSPECIFIED, 114 | MeasureSpec.EXACTLY -> { 115 | height = heightSize + paddingTop + paddingBottom 116 | } 117 | MeasureSpec.AT_MOST -> { 118 | height = bounds.height() + paddingTop + paddingBottom 119 | } 120 | } 121 | 122 | //保存测量的宽高 123 | setMeasuredDimension(width, height) 124 | } 125 | 126 | @SuppressLint("DrawAllocation") 127 | override fun onDraw(canvas: Canvas?) { 128 | super.onDraw(canvas) 129 | 130 | /** 131 | * 自定义View时,需要我们自己在onDraw中处理padding,否则是不生效的 132 | * 自定义ViewGroup时,子view的padding放在onMeasure中处理 133 | */ 134 | 135 | /** 136 | * 矩形背景 137 | */ 138 | paint.color = Color.YELLOW 139 | //计算坐标,因为原点是在文字的左下角,左边要是延伸出去就还要往左边去,所以是减,右边和下边是正,所以是加 140 | canvas?.drawRect( 141 | (0 - paddingLeft).toFloat(), 142 | (0 - paddingTop).toFloat(), 143 | (measuredWidth + paddingRight).toFloat(), 144 | (measuredHeight + paddingBottom).toFloat(), 145 | paint 146 | ) 147 | 148 | /** 149 | * 文本 150 | */ 151 | paint.color = mRandomTextColor 152 | //注意这里的坐标xy不是左上角,而是左下角,所以高度是相加的,在自定义view中,坐标轴右下为正 153 | //getWidth 等于 measuredWidth 154 | canvas?.drawText( 155 | mRandomText, 156 | (width / 2 - bounds.width() / 2).toFloat(), 157 | (height / 2 + bounds.height() / 2).toFloat(), 158 | paint 159 | ) 160 | } 161 | 162 | /** 163 | * 根据文本长度 随意数字 164 | */ 165 | private fun randomText(): String { 166 | 167 | val list = mutableListOf() 168 | for (index in mRandomText.indices) { 169 | list.add(Random.nextInt(10)) 170 | } 171 | 172 | val stringBuffer = StringBuffer() 173 | for (i in list) { 174 | stringBuffer.append("" + i) 175 | } 176 | 177 | return stringBuffer.toString() 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /app/src/main/java/com/yechaoa/customviews/roundimg/RoundImageView.kt: -------------------------------------------------------------------------------- 1 | package com.yechaoa.customviews.roundimg 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.graphics.Canvas 6 | import android.graphics.Path 7 | import android.util.AttributeSet 8 | import android.util.TypedValue 9 | import androidx.appcompat.widget.AppCompatImageView 10 | import com.yechaoa.customviews.R 11 | 12 | /** 13 | * GitHub : https://github.com/yechaoa 14 | * CSDN : http://blog.csdn.net/yechaoa 15 | * 16 | * Created by yechao on 2021/7/25. 17 | * Describe : 圆角ImageView 18 | * 1.clipPath 19 | * 2.PorterDuffXfermode 20 | */ 21 | class RoundImageView : AppCompatImageView { 22 | 23 | private var mCornerSize = 0F 24 | private var mWidth = 0F 25 | private var mHeight = 0F 26 | 27 | constructor(context: Context) : this(context, null) 28 | 29 | constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0) 30 | 31 | constructor(context: Context, attributeSet: AttributeSet?, defStyle: Int) : super(context, attributeSet, defStyle) { 32 | //获取自定义属性 33 | val typedArray = context.theme.obtainStyledAttributes( 34 | attributeSet, R.styleable.RoundImageView, defStyle, 0 35 | ) 36 | mCornerSize = typedArray.getDimensionPixelSize( 37 | R.styleable.RoundImageView_myCornerSize, 38 | TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 0F, resources.displayMetrics).toInt() 39 | ).toFloat() 40 | typedArray.recycle() 41 | } 42 | 43 | override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { 44 | super.onLayout(changed, left, top, right, bottom) 45 | mWidth = width.toFloat() 46 | mHeight = height.toFloat() 47 | } 48 | 49 | @SuppressLint("DrawAllocation") 50 | override fun onDraw(canvas: Canvas?) { 51 | if (mWidth > mCornerSize && mHeight > mCornerSize) { 52 | // 画圆角 53 | val path = Path() 54 | path.moveTo(mCornerSize, 0f) 55 | //右上 56 | path.lineTo(mWidth - mCornerSize, 0f) 57 | path.quadTo(mWidth, 0f, mWidth, mCornerSize) 58 | //右下 59 | path.lineTo(mWidth, mHeight - mCornerSize) 60 | path.quadTo(mWidth, mHeight, mWidth - mCornerSize, mHeight) 61 | //左下 62 | path.lineTo(mCornerSize, mHeight) 63 | path.quadTo(0f, mHeight, 0f, mHeight - mCornerSize) 64 | //左上 65 | path.lineTo(0f, mCornerSize) 66 | path.quadTo(0f, 0f, mCornerSize, 0f) 67 | canvas?.clipPath(path) 68 | } 69 | super.onDraw(canvas) 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yechaoa/customviews/roundimg/RoundImageViewActivity.kt: -------------------------------------------------------------------------------- 1 | package com.yechaoa.customviews.roundimg 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | import com.yechaoa.customviews.R 6 | 7 | class RoundImageViewActivity : AppCompatActivity() { 8 | override fun onCreate(savedInstanceState: Bundle?) { 9 | super.onCreate(savedInstanceState) 10 | setContentView(R.layout.activity_round_image_view) 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yechaoa/customviews/tagtext/TagTextView.kt: -------------------------------------------------------------------------------- 1 | package com.yechaoa.customviews.tagtext 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.graphics.drawable.BitmapDrawable 6 | import android.text.Spannable 7 | import android.text.SpannableString 8 | import android.text.SpannableStringBuilder 9 | import android.text.Spanned 10 | import android.text.style.ForegroundColorSpan 11 | import android.text.style.ImageSpan 12 | import android.util.AttributeSet 13 | import android.view.Gravity 14 | import android.view.LayoutInflater 15 | import android.view.View 16 | import android.widget.TextView 17 | import com.yechaoa.customviews.R 18 | 19 | 20 | /** 21 | * Created by yechao on 2020/10/29. 22 | * Describe : 23 | */ 24 | class TagTextView : androidx.appcompat.widget.AppCompatTextView { 25 | 26 | private var mContext: Context 27 | 28 | constructor(context: Context) : super(context) { 29 | mContext = context 30 | } 31 | 32 | constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) { 33 | mContext = context 34 | } 35 | 36 | constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int) : super( 37 | context, 38 | attributeSet, 39 | defStyleAttr 40 | ) { 41 | mContext = context 42 | } 43 | 44 | fun setTagAndText( 45 | tags: List, textColor: Int, bgColor: Int, 46 | text: String, startIndex: Int = 0, endIndex: Int = 0, titleColor: Int? = null 47 | ) { 48 | 49 | val spanBuilder = SpannableStringBuilder() 50 | 51 | val stringBuffer = StringBuffer() 52 | //将每个tag的内容添加到text之前,之后将用drawable替代这些tag所占的位置 53 | tags.forEach { 54 | stringBuffer.append(it) 55 | } 56 | var span = SpannableString(stringBuffer) 57 | 58 | tags.forEachIndexed { index, str -> 59 | val view = LayoutInflater.from(mContext).inflate(R.layout.item_text_tag, null) 60 | val tvTag = view.findViewById(R.id.tv_tag) 61 | tvTag.setTextColor(textColor) 62 | tvTag.setBackgroundColor(bgColor) 63 | tvTag.text = str 64 | 65 | //转为bitmap 66 | val bitmap = convertViewToBitmap(view) 67 | val bitmapDrawable = BitmapDrawable(resources, bitmap) 68 | //绘制位置 69 | bitmapDrawable.setBounds(0, 0, tvTag.width, tvTag.height) 70 | 71 | //图片将对齐底部边线 72 | val imageSpan = ImageSpan(bitmapDrawable, ImageSpan.ALIGN_BOTTOM) 73 | val start = getLastLength(tags, index) 74 | val end = start + str.length 75 | /** 76 | * what:对SpannableString进行润色的各种Span; 77 | * int:需要润色文字段开始的下标; 78 | * end:需要润色文字段结束的下标; 79 | * flags:决定开始和结束下标是否包含的标志位,有四个参数可选,默认SPAN_EXCLUSIVE_EXCLUSIVE 含头不含尾 80 | */ 81 | span.setSpan(imageSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) 82 | } 83 | 84 | spanBuilder.append(span) 85 | 86 | span = SpannableString(text) 87 | //endIndex!=0表示需要标题需要高亮 88 | if (endIndex != 0) { 89 | span.setSpan(ForegroundColorSpan(titleColor!!), startIndex, endIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) 90 | } 91 | spanBuilder.append(span) 92 | 93 | setText(spanBuilder) 94 | 95 | gravity = Gravity.CENTER_VERTICAL 96 | 97 | } 98 | 99 | private fun convertViewToBitmap(view: View): Bitmap? { 100 | //利用 LayoutInflater 生成的 View 并没有经过 measure() 和 layout() 方法的洗礼,所以一定没对它的 width 和 height 等属性赋值 101 | view.measure( 102 | View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), 103 | View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) 104 | ) 105 | view.layout(0, 0, view.measuredWidth, view.measuredHeight) 106 | 107 | //返回Bitmap 108 | view.buildDrawingCache() 109 | return view.getDrawingCache() 110 | } 111 | 112 | 113 | private fun getLastLength(list: List, maxLength: Int): Int { 114 | var length = 0 115 | for (i in 0 until maxLength) { 116 | length += list[i].length 117 | } 118 | return length 119 | } 120 | 121 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yechaoa/customviews/tagtext/TagTextViewActivity.kt: -------------------------------------------------------------------------------- 1 | package com.yechaoa.customviews.tagtext 2 | 3 | import android.graphics.Color 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AppCompatActivity 6 | import com.yechaoa.customviews.R 7 | import com.yechaoa.customviews.databinding.ActivityTagTextViewBinding 8 | 9 | class TagTextViewActivity : AppCompatActivity() { 10 | 11 | private val mBinding by lazy { ActivityTagTextViewBinding.inflate(layoutInflater) } 12 | 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | super.onCreate(savedInstanceState) 15 | setContentView(mBinding.root) 16 | 17 | // TODO: 2020/12/10 标签字体大小,标签颜色集合 18 | 19 | val list1 = listOf("标签") 20 | 21 | mBinding.tvTag1.setTagAndText( 22 | list1, 23 | Color.parseColor("#F44336"), 24 | Color.parseColor("#FFCDD2"), 25 | getString(R.string.app_name) 26 | ) 27 | 28 | 29 | val list2 = listOf("会员价", "促销", "抢购") 30 | 31 | mBinding.tvTag2.setTagAndText( 32 | list2, 33 | Color.parseColor("#1976D2"), 34 | Color.parseColor("#BBDEFB"), 35 | getString(R.string.test_string) 36 | ) 37 | 38 | val list3 = listOf("标签") 39 | 40 | mBinding.tvTag3.setTagAndText( 41 | list3, 42 | Color.parseColor("#388E3C"), 43 | Color.parseColor("#C8E6C9"), 44 | getString(R.string.test_string), 45 | 6, 46 | 8, 47 | Color.parseColor("#4CAF50") 48 | ) 49 | } 50 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/progress_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_btn.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_expand.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 24 | 25 | 31 | 32 | 33 | 34 | 41 | 42 | 49 | 50 | 56 | 57 | 63 | 64 | 71 | 72 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |