├── .DS_Store ├── .gitignore ├── .idea ├── caches │ ├── build_file_checksums.ser │ └── gradle_models.ser ├── checkstyle-idea.xml ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── encodings.xml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .DS_Store ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── cn │ │ └── deemons │ │ └── duck │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── cn │ │ │ └── deemons │ │ │ └── duck │ │ │ ├── App.java │ │ │ ├── CustomView.java │ │ │ └── MainActivity.java │ └── res │ │ ├── color │ │ ├── selector_press_color.xml │ │ └── selector_press_stroke_color.xml │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ └── test.xml │ │ ├── layout │ │ └── activity_main.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_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── cn │ └── deemons │ └── duck │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── image ├── 14-06-02.gif ├── Snipaste_2019-05-29_11-47-18.png ├── duck.gif ├── eberhard-grossgasteiger.jpg ├── screenshot-1551340161787.jpg ├── screenshot-1551340182026.jpg └── screenshot-1551340314962.jpg ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── cn │ │ └── deemons │ │ └── library │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── cn │ │ │ └── deemons │ │ │ └── library │ │ │ ├── core │ │ │ ├── AspectPlugin.java │ │ │ ├── DuckFactor.java │ │ │ └── Injector.java │ │ │ ├── shape │ │ │ ├── ShapeInjector.java │ │ │ └── ShapeUtils.java │ │ │ └── view │ │ │ ├── DuckFrameLayout.java │ │ │ ├── DuckLinearLayout.java │ │ │ ├── DuckRelativeLayout.java │ │ │ ├── DuckScrollView.java │ │ │ └── DuckTableLayout.java │ └── res │ │ └── values │ │ ├── strings.xml │ │ └── style.xml │ └── test │ └── java │ └── cn │ └── deemons │ └── library │ └── ExampleUnitTest.java ├── plugin ├── .gitignore ├── build.gradle └── src │ └── main │ ├── groovy │ └── com │ │ └── deemons │ │ └── plugin │ │ ├── JavassistPlugin.groovy │ │ ├── JavassistTransform.groovy │ │ ├── MyInject.groovy │ │ └── Utils.groovy │ └── resources │ └── META-INF │ └── gradle-plugins │ └── com.deemons.duck.properties ├── repo └── com │ └── deemons │ └── plugin │ └── duck │ ├── 0.0.1 │ ├── duck-0.0.1.jar │ ├── duck-0.0.1.jar.md5 │ ├── duck-0.0.1.jar.sha1 │ ├── duck-0.0.1.pom │ ├── duck-0.0.1.pom.md5 │ └── duck-0.0.1.pom.sha1 │ ├── 0.0.2 │ ├── duck-0.0.2.jar │ ├── duck-0.0.2.jar.md5 │ ├── duck-0.0.2.jar.sha1 │ ├── duck-0.0.2.pom │ ├── duck-0.0.2.pom.md5 │ └── duck-0.0.2.pom.sha1 │ ├── 0.0.3 │ ├── duck-0.0.3.jar │ ├── duck-0.0.3.jar.md5 │ ├── duck-0.0.3.jar.sha1 │ ├── duck-0.0.3.pom │ ├── duck-0.0.3.pom.md5 │ └── duck-0.0.3.pom.sha1 │ ├── maven-metadata.xml │ ├── maven-metadata.xml.md5 │ └── maven-metadata.xml.sha1 ├── settings.gradle └── settings_duck.jar /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deemonser/Duck/a1d190da701627c78b02dc73cea37e404f64c002/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/dictionaries 41 | .idea/libraries 42 | 43 | # Keystore files 44 | # Uncomment the following line if you do not want to check your keystore files in. 45 | #*.jks 46 | 47 | # External native build folder generated in Android Studio 2.2 and later 48 | .externalNativeBuild 49 | 50 | # Google Services (e.g. APIs or Firebase) 51 | google-services.json 52 | 53 | # Freeline 54 | freeline.py 55 | freeline/ 56 | freeline_project_description.json 57 | 58 | # fastlane 59 | fastlane/report.xml 60 | fastlane/Preview.html 61 | fastlane/screenshots 62 | fastlane/test_output 63 | fastlane/readme.md 64 | /README.md 65 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deemonser/Duck/a1d190da701627c78b02dc73cea37e404f64c002/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/caches/gradle_models.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deemonser/Duck/a1d190da701627c78b02dc73cea37e404f64c002/.idea/caches/gradle_models.ser -------------------------------------------------------------------------------- /.idea/checkstyle-idea.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 30 | 226 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 36 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Duck 文档 2 | Duck 能帮助开发者直接在 xml 的任意控件上实现 Shape 效果,无需创建额外的xml文件,并且没有任何侵入性。 3 | 4 | ![duck](https://raw.githubusercontent.com/Deemonser/Duck/master/image/duck.gif) 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | #### 使用 13 | 14 | 1. 在项目的 build.gradle 文件下添加插件依赖 15 | 16 | ```gr 17 | buildscript { 18 | ... 19 | dependencies { 20 | ... 21 | classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.2' 22 | } 23 | } 24 | ``` 25 | 26 | 2. 在模块的 build.gradle 文件下添加 27 | 28 | ```groovy 29 | api 'com.deemons.duck:duck:0.0.2' 30 | 31 | // support androidx 32 | api 'com.deemons.duck:duckx:1.0.0' 33 | ``` 34 | 35 | 3. 可选 36 | 37 | 在 xml 中使用自定义属性时,是没有提示的,我们可以通过 `Live Template` 来实现。 38 | 39 | 项目根目录下的 `settings_duck.jar` 文件,此文件已经设置了 AS 中的 Live Template,下载此文件并导入到系统设置中 `File` -> `Import setting` 。 40 | 41 | ![14-06-02](https://raw.githubusercontent.com/Deemonser/Duck/master/image/14-06-02.gif) 42 | 43 | 4. 直接在 xml 中使用 44 | 45 | ~~~xml 46 | 58 | 59 | ~~~ 60 | 61 | 或者在代码中使用 62 | 63 | ```java 64 | TextView view = findViewById(R.id.text); 65 | 66 | view.setBackground(new ShapeUtils(GradientDrawable.RECTANGLE) 67 | .corner(10) 68 | .stroke(3, Color.parseColor("#0000ff")) 69 | .gradientLinear(GradientDrawable.Orientation.LEFT_RIGHT) 70 | .gradientColor(Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW) 71 | .create() 72 | ); 73 | 74 | ``` 75 | 76 | 77 | 78 |
79 | 80 | ### API 81 | 82 | | 代码 | 功能 | 代码 | 功能 | 83 | | :-------------------------- | :------------------------------------------- | ----------------------- | ------------------------------------------ | 84 | | solid | 填充背景色
(API 21支持 SelectorColor) | stroke_color | 边框颜色
(API 21支持 SelectorColor) | 85 | | corner_top_left | 左上倒角 | stroke_width | 边框宽度 | 86 | | corner_top_right | 右上倒角 | stroke_dash_gap | 虚线边框单个长度 | 87 | | corner_bottom_left | 左下倒角 | stroke_dash_width | 虚线边框的间隔 | 88 | | corner_bottom_right | 右下倒角 | padding_left | 左内间距 | 89 | | corner | 所有倒角 | padding_top | 上内间距 | 90 | | gradient_color_start | 渐变初始颜色 | padding_right | 右内间距 | 91 | | gradient_color_center | 渐变中心颜色 | padding_bottom | 下内间距 | 92 | | gradient_color_end | 渐变结尾颜色 | size_width | Shape 的宽 | 93 | | gradient_linear_orientation | 线性 渐变方向 | size_height | Shape 的高 | 94 | | gradient_sweep_centerX | 扫描渐变中心X坐标 | gradient_radial_centerX | 径向渐变中心点X坐标 | 95 | | gradient_sweep_centerY | 扫描渐变中心Y坐标 | gradient_radial_centerY | 径向渐变中心点Y坐标 | 96 | | shape | 图形 | | | 97 | 98 | 99 | 100 |
101 | 102 | #### 原理 103 | 104 | 在考虑用什么技术实现时,考虑这几点: 105 | 106 | 1. 任何控件都能有效,即使是自定义控件。 107 | 2. 不能有侵入性,即使更换或废弃本库,也能保证稳定性。 108 | 109 | 最开始,第一个想到的是 `LayoutInflater.Factory` ,xml 控件解析成 View时,必须经过它,也是换肤的解决方案,但这样得一个个替换成自己的,非常麻烦。 110 | 111 | 有没有更好的解决方案呢? 112 | 113 | **得益于 AspectJ 的 AOP(面向切面编程)能力,我们可以在编译时期,直接在 View 及其子类的构造方法中插入相关代码,解析xml 中自定义的属性,最后设置到控件上。** 114 | 115 | ```java 116 | @Pointcut("execution(android.view.View+.new(..))") 117 | public void callViewConstructor() { 118 | } 119 | 120 | @After("callViewConstructor()") 121 | public void inject(JoinPoint joinPoint) throws Throwable { 122 | 123 | Signature signature = joinPoint.getSignature(); 124 | Object target = joinPoint.getTarget(); 125 | Object[] args = joinPoint.getArgs(); 126 | 127 | int length = args.length; 128 | if (!(target instanceof View) || length < 2 || target.hashCode() == lastHash || !(args[0] instanceof Context) || !(args[1] instanceof AttributeSet)) { 129 | return; 130 | } 131 | lastHash = target.hashCode(); 132 | 133 | Context context = (Context) args[0]; 134 | AttributeSet attrs = (AttributeSet) args[1]; 135 | 136 | int count = attrs.getAttributeCount(); 137 | 138 | for (int i = 0; i < count; i++) { 139 | Log.i(TAG, attrs.getAttributeName(i) + " = " + attrs.getAttributeValue(i)); 140 | } 141 | 142 | Log.i(TAG, "inject =====> " + signature.toString()); 143 | DuckFactor.getFactor().inject((View) target, context, attrs); 144 | } 145 | ``` 146 | 147 | AOP 相关内容,可以查看[AOP 系列](https://deemons.cn/categories/AOP/) 包含: 148 | 149 | [1.OOP 与 AOP](https://deemons.cn/2017/09/21/OOP%E4%B8%8EAOP/) 150 | 151 | [2.Java 注解处理器](https://deemons.cn/2017/09/28/Java-%E6%B3%A8%E8%A7%A3%E5%A4%84%E7%90%86%E5%99%A8/) 152 | 153 | [3.Aspect](https://deemons.cn/2017/10/10/AspectJ/) 154 | 155 | [4.Android中使用 Javassist](https://deemons.cn/2017/11/07/Android%E4%B8%AD%E4%BD%BF%E7%94%A8Javassist/) 156 | 157 | 158 | 159 | 由于 AspectJ 能遍历项目中所有依赖包,因此,无论是 support 库,还是第三方库都能得到很好支持。 160 | 161 | 但是 AOP 也存在一定问题,我们的 apk 中是不会存在系统原生 Android SDK 的,例如 `TextView` 这个系统控件,在编译时是不会打包到 apk 中,因此,AOP 技术对这种原生控件无能为力。 162 | 163 | 幸好,我们绝大部分项目为了兼容性,一般都会直接依赖官方的兼容库,即 `support` 相关的库。 164 | 165 | 在 support· 库中,会将一些原生控件,直接替换成 support 相关控件。相关代码如下: 166 | 167 | ```java 168 | android/support/v7/app/AppCompatViewInflater 169 | 170 | switch (name) { 171 | case "TextView": 172 | view = createTextView(context, attrs); 173 | verifyNotNull(view, name); 174 | break; 175 | case "ImageView": 176 | view = createImageView(context, attrs); 177 | verifyNotNull(view, name); 178 | break; 179 | case "Button": 180 | view = createButton(context, attrs); 181 | verifyNotNull(view, name); 182 | break; 183 | case "EditText": 184 | view = createEditText(context, attrs); 185 | verifyNotNull(view, name); 186 | break; 187 | ...... 188 | } 189 | ``` 190 | 191 | 而对于这些控件,我们的 AOP 都能够生效了。 192 | 193 | 在 support 库中,没有替换掉 ViewGroup 的几个常用子类,如`LinearLayout` 、`RelativeLayout`、`FrameLayout`等, 194 | 195 | 所以,我们我们仿照 support 的替换方式,直接在 `LayoutInflater.Factory.onCreateView` 方法中注入相应的替换代码。 196 | 197 | ```java 198 | 199 | @Pointcut("execution(* *..LayoutInflater.Factory+.onCreateView(..))") 200 | public void callLayoutInflater() { 201 | } 202 | 203 | @Around("callLayoutInflater()") 204 | public Object replaceView(ProceedingJoinPoint joinPoint) throws Throwable { 205 | 206 | .... 207 | 208 | switch (name) { 209 | case "RelativeLayout": 210 | return new DuckRelativeLayout(context, attrs); 211 | case "LinearLayout": 212 | return new DuckLinearLayout(context, attrs); 213 | case "FrameLayout": 214 | return new DuckFrameLayout(context, attrs); 215 | case "TableLayout": 216 | return new DuckTableLayout(context, attrs); 217 | case "ScrollView": 218 | return new DuckScrollView(context, attrs); 219 | default: 220 | break; 221 | } 222 | 223 | return result; 224 | } 225 | 226 | ``` 227 | 228 | 229 | 230 | 这个库的代码其实很少,我这里也只是实现了 Shape 这一个功能。 231 | 232 | ```java 233 | private static Injector mInjector; 234 | 235 | public static void setFactor(Injector injector) { 236 | mInjector = injector; 237 | } 238 | 239 | public static Injector getFactor() { 240 | if (mInjector == null) { 241 | mInjector = new ShapeInjector(); 242 | } 243 | return mInjector; 244 | } 245 | ``` 246 | 247 | 这里保留的 Duck 的扩展性,如果觉得不够,可以自行实现功能更强大的 Injector 来替换默认的。 248 | 249 | AOP 的能力远不止如此,还有很多事情可以做,建议大家可以发挥想象,进行更多的扩展。 250 | 251 |
252 | 253 | ### 初衷 254 | 这个库的由来,是因为公司一个维护了 4 年的项目。 255 | 256 | 经历 4 年的项目,产品设计不知道改了多少版,期间产生并堆砌大量`shape.xml` 文件,这些文件因为索引的问题往往还无法清理。 257 | 258 | 同时,同一个 `shape.xml` 文件,因为设计存在不规范的问题,在不同页面改动了一点颜色、倒角或线宽等,就无法复用,必须据此创建新的文件。 259 | 260 | 最后,大量的文件堆积,开发人员开发时,想复用去画时间找 `shape.xml` ,还不如自己创建新的方便,这样恶性循环, 只能 GG。 261 | 262 | 最后,我想说,Android 设计 `Shape` 的初衷是好的: 一个 APP,统一的设计规范,就应该复用 `Shape` 。 263 | 264 | 但这种情况对于国内的生态来说并不适用。 265 | 266 | 首先,相同屏幕尺寸,中文承载信息的能力远大于英文,这就导致国外大部分 APP 界面设计简洁清爽,国内就显得非常复杂,同时国内互联网更新速度很快,界面是生命周期短,人员流动,很难做到界面统一。 267 | 268 | 所有,Android 的 Shape 并不适合国内生态。 269 | 270 | 开发时,超级羡慕对面 IOS 开发们可以直接在控件上进行花式倒角、加线框等骚操作,想不通为啥 Android 不能在这一点上借鉴IOS。哎,Android 与 IOS 的宿命之争,说多了都是泪。 271 | 272 | 基于上面种种原因,所以出现了想开发这个库。 273 | 274 |
275 | 276 | 这个库只实现了最常用的 Shape 功能,但 selector 及 layout-list 并未实现,因为有两点考虑: 277 | 278 | 1. shape 使用场景更多,并且更频繁,其他两种只在少数特定场景中使用。 279 | 280 | 2. selector 及 layout-list 需要更多精细的代码控制,如全部挤在 xml 中一个控件上,会非常臃肿,难以维护。 281 | 282 |
283 | 284 | 285 | 286 | 287 | ### 不足 288 | 289 | 由于使用 AOP ,所以在编写时,无法实时预览,看看后续能否通过 AS 插件补足吧! -------------------------------------------------------------------------------- /app/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deemonser/Duck/a1d190da701627c78b02dc73cea37e404f64c002/app/.DS_Store -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'android-aspectjx' 4 | 5 | //apply plugin: 'com.deemons.duck' 6 | 7 | android { 8 | compileSdkVersion 29 9 | defaultConfig { 10 | applicationId "cn.deemons.duck" 11 | minSdkVersion 16 12 | targetSdkVersion 29 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(include: ['*.jar'], dir: 'libs') 27 | implementation 'androidx.appcompat:appcompat:1.0.2' 28 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 29 | testImplementation 'junit:junit:4.12' 30 | androidTestImplementation 'androidx.test:runner:1.2.0' 31 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 32 | 33 | implementation project(':library') 34 | // implementation 'com.deemons.duck:duck:0.0.2' 35 | debugImplementation 'com.facebook.stetho:stetho:1.5.0' 36 | debugImplementation 'com.facebook.stetho:stetho-okhttp3:1.5.0' 37 | 38 | } 39 | -------------------------------------------------------------------------------- /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 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/cn/deemons/duck/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package cn.deemons.duck; 2 | 3 | import android.content.Context; 4 | import androidx.test.InstrumentationRegistry; 5 | import androidx.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("cn.deemons.duck", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/cn/deemons/duck/App.java: -------------------------------------------------------------------------------- 1 | package cn.deemons.duck; 2 | 3 | import android.app.Application; 4 | 5 | /** 6 | * author: deemons 7 | * date: 2018/9/5 8 | * desc: 9 | */ 10 | public class App extends Application { 11 | 12 | @Override 13 | public void onCreate() { 14 | super.onCreate(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/cn/deemons/duck/CustomView.java: -------------------------------------------------------------------------------- 1 | package cn.deemons.duck; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import androidx.annotation.Nullable; 8 | import android.util.AttributeSet; 9 | import android.view.View; 10 | 11 | /** 12 | * author: deemons 13 | * date: 2019/2/22 14 | * desc: 15 | */ 16 | public class CustomView extends View { 17 | public CustomView(Context context) { 18 | super(context); 19 | } 20 | 21 | public CustomView(Context context, @Nullable AttributeSet attrs) { 22 | super(context, attrs); 23 | } 24 | 25 | public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 26 | super(context, attrs, defStyleAttr); 27 | } 28 | 29 | 30 | @Override 31 | protected void onDraw(Canvas canvas) { 32 | super.onDraw(canvas); 33 | Paint paint = new Paint(); 34 | paint.setTextSize(40); 35 | paint.setColor(Color.BLACK); 36 | canvas.drawText("CustomView", getWidth()/3, getHeight()/2, paint); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/cn/deemons/duck/MainActivity.java: -------------------------------------------------------------------------------- 1 | package cn.deemons.duck; 2 | 3 | import android.os.Bundle; 4 | import androidx.appcompat.app.AppCompatActivity; 5 | 6 | public class MainActivity extends AppCompatActivity { 7 | 8 | private static final String TAG = "MainActivity"; 9 | 10 | 11 | @Override 12 | protected void onCreate(Bundle savedInstanceState) { 13 | 14 | // LayoutInflaterCompat.setFactory2(LayoutInflater.from(this), new LayoutInflater.Factory2() { 15 | // @Override 16 | // public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { 17 | // 18 | // Log.e(TAG, "name = " + name); 19 | // int n = attrs.getAttributeCount(); 20 | // for (int i = 0; i < n; i++) { 21 | // Log.e(TAG, attrs.getAttributeName(i) + " , " + attrs.getAttributeValue(i)); 22 | // } 23 | // 24 | // return null; 25 | // } 26 | // 27 | // @Override 28 | // public View onCreateView(String name, Context context, AttributeSet attrs) { 29 | // 30 | // return null; 31 | // } 32 | // }); 33 | 34 | 35 | 36 | super.onCreate(savedInstanceState); 37 | setContentView(R.layout.activity_main); 38 | 39 | // TextView view = findViewById(R.id.text); 40 | // ConstraintLayout root = findViewById(R.id.root_view); 41 | 42 | 43 | // view.setBackground(new ShapeUtils(GradientDrawable.RECTANGLE) 44 | // .corner(10) 45 | // .stroke(3, Color.parseColor("#0000ff")) 46 | // .gradientLinear(GradientDrawable.Orientation.LEFT_RIGHT) 47 | // .gradientColor(Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW) 48 | // .create() 49 | // ); 50 | 51 | } 52 | 53 | 54 | 55 | 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/res/color/selector_press_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/color/selector_press_stroke_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /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/test.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | 20 | 21 | 22 | 32 | 33 | 44 | 45 | 46 | 51 | 52 | 62 | 63 | 73 | 74 | 75 | 76 | 77 | 81 | 82 | 94 | 95 | 105 | 106 | 107 | 108 | 109 | 120 | 121 | 133 | 134 | 148 | 149 | 163 | 164 | 165 | 180 | 181 | 182 | 198 | 199 | 200 | 217 | 218 | 219 | 225 | 226 | 227 |