├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── config.yml
│ ├── issue_en_template_bug.yml
│ ├── issue_en_template_question.yml
│ ├── issue_en_template_suggest.yml
│ ├── issue_zh_template_bug.yml
│ ├── issue_zh_template_question.yml
│ └── issue_zh_template_suggest.yml
└── workflows
│ └── android.yml
├── .gitignore
├── LICENSE
├── README.md
├── app
├── AppSignature.jks
├── build.gradle
├── gradle.properties
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── hjq
│ │ └── shape
│ │ └── drawable
│ │ └── demo
│ │ ├── AppApplication.java
│ │ └── MainActivity.java
│ └── res
│ ├── layout
│ └── activity_main.xml
│ ├── mipmap-hdpi
│ ├── 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
│ ├── values-v23
│ └── styles.xml
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── library
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── hjq
│ └── shape
│ └── drawable
│ ├── ShapeDrawable.java
│ ├── ShapeDrawableUtils.java
│ ├── ShapeGradientOrientation.java
│ ├── ShapeGradientType.java
│ ├── ShapeGradientTypeLimit.java
│ ├── ShapeState.java
│ ├── ShapeType.java
│ └── ShapeTypeLimit.java
├── picture
├── demo_code.png
└── long_screenshots.jpg
└── settings.gradle
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: https://raw.githubusercontent.com/getActivity/Donate/master/picture/pay_ali.png
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/issue_en_template_bug.yml:
--------------------------------------------------------------------------------
1 | name: Submit Bug
2 | description: Please let me know the issues with the framework, and I will assist you in resolving them!
3 | title: "[Bug]:"
4 | labels: ["bug"]
5 |
6 | body:
7 | - type: markdown
8 | attributes:
9 | value: |
10 | ## [Warning: Please make sure to fill in the issue template accurately. If an issue is found to be filled incorrectly, it will be closed without further notice.](https://github.com/getActivity/IssueTemplateGuide)
11 | - type: input
12 | id: input_id_1
13 | attributes:
14 | label: Framework Version [Required]
15 | description: Please enter the version of the framework you are using.
16 | validations:
17 | required: true
18 | - type: textarea
19 | id: input_id_2
20 | attributes:
21 | label: Issue Description [Required]
22 | description: Please describe the issue you are facing.
23 | validations:
24 | required: true
25 | - type: textarea
26 | id: input_id_3
27 | attributes:
28 | label: Steps to Reproduce [Required]
29 | description: Please provide steps to reproduce the issue.
30 | validations:
31 | required: true
32 | - type: dropdown
33 | id: input_id_4
34 | attributes:
35 | label: Is the Issue Reproducible? [Required]
36 | multiple: false
37 | options:
38 | - "Not Selected"
39 | - "Yes"
40 | - "No"
41 | validations:
42 | required: true
43 | - type: input
44 | id: input_id_5
45 | attributes:
46 | label: Project targetSdkVersion [Required]
47 | validations:
48 | required: true
49 | - type: input
50 | id: input_id_6
51 | attributes:
52 | label: Device Information [Required]
53 | description: Please provide the brand and model of the device where the issue occurred.
54 | validations:
55 | required: true
56 | - type: input
57 | id: input_id_7
58 | attributes:
59 | label: Android Version [Required]
60 | description: Please provide the Android version where the issue occurred.
61 | validations:
62 | required: true
63 | - type: dropdown
64 | id: input_id_8
65 | attributes:
66 | label: Issue Source Channel [Required]
67 | multiple: true
68 | options:
69 | - Encountered by myself
70 | - Identified in Bugly
71 | - User feedback
72 | - Other channels
73 | - type: input
74 | id: input_id_9
75 | attributes:
76 | label: Is it specific to certain device models? [Required]
77 | description: Specify whether the issue is specific to certain devices (e.g., specific brand or Android version).
78 | validations:
79 | required: true
80 | - type: dropdown
81 | id: input_id_10
82 | attributes:
83 | label: Does the latest version of the framework have this issue? [Required]
84 | description: If you are using an older version, it is recommended to upgrade and check if the issue still persists.
85 | multiple: false
86 | options:
87 | - "Not Selected"
88 | - "Yes"
89 | - "No"
90 | validations:
91 | required: true
92 | - type: dropdown
93 | id: input_id_11
94 | attributes:
95 | label: Is the issue mentioned in the framework documentation? [Required]
96 | description: The documentation provides answers to frequently asked questions. Please check if the information you are looking for is already provided.
97 | multiple: false
98 | options:
99 | - "Not Selected"
100 | - "Yes"
101 | - "No"
102 | validations:
103 | required: true
104 | - type: dropdown
105 | id: input_id_12
106 | attributes:
107 | label: Did you consult the framework documentation but couldn't find a solution? [Required]
108 | description: If you have consulted the documentation but still couldn't find a solution, you can select "Yes."
109 | multiple: false
110 | options:
111 | - "Not Selected"
112 | - "Yes"
113 | - "No"
114 | validations:
115 | required: true
116 | - type: dropdown
117 | id: input_id_13
118 | attributes:
119 | label: Has a similar issue been reported in the issue list? [Required]
120 | description: You can search the issue list for keywords related to your problem and refer to the solutions provided by others.
121 | multiple: false
122 | options:
123 | - "Not Selected"
124 | - "Yes"
125 | - "No"
126 | validations:
127 | required: true
128 | - type: dropdown
129 | id: input_id_14
130 | attributes:
131 | label: Have you searched the issue list but couldn't find a solution? [Required]
132 | description: If you have searched the issue list and couldn't find a solution, you can select "Yes."
133 | multiple: false
134 | options:
135 | - "Not Selected"
136 | - "Yes"
137 | - "No"
138 | validations:
139 | required: true
140 | - type: dropdown
141 | id: input_id_15
142 | attributes:
143 | label: Can the issue be reproduced with a demo project? [Required]
144 | description: Check if the issue can be reproduced in a minimal demo project to isolate potential issues in your own code.
145 | multiple: false
146 | options:
147 | - "Not Selected"
148 | - "Yes"
149 | - "No"
150 | validations:
151 | required: true
152 | - type: textarea
153 | id: input_id_16
154 | attributes:
155 | label: Provide Error Stack Trace
156 | description: If there is an error, please provide the stack trace. Note, Do not include obfuscated code in the stack trace.
157 | render: text
158 | validations:
159 | required: false
160 | - type: textarea
161 | id: input_id_17
162 | attributes:
163 | label: Provide Screenshots or Videos
164 | description: Provide screenshots or videos if necessary. This field is optional.
165 | validations:
166 | required: false
167 | - type: textarea
168 | id: input_id_18
169 | attributes:
170 | label: Provide a Solution
171 | description: If you have already found a solution, this field is optional.
172 | validations:
173 | required: false
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/issue_en_template_question.yml:
--------------------------------------------------------------------------------
1 | name: Ask a Question
2 | description: Ask your questions, and I will provide you with answers.
3 | title: "[Question]:"
4 | labels: ["question"]
5 |
6 | body:
7 | - type: markdown
8 | attributes:
9 | value: |
10 | ## [Warning: Please make sure to fill in the issue template accurately. If an issue is found to be filled incorrectly, it will be closed without further notice.](https://github.com/getActivity/IssueTemplateGuide)
11 | - type: textarea
12 | id: input_id_1
13 | attributes:
14 | label: Question Description [Required]
15 | description: Please describe your question (Note, If it is a framework bug, please do not raise it here, as it will not be accepted).
16 | validations:
17 | required: true
18 | - type: dropdown
19 | id: input_id_2
20 | attributes:
21 | label: Is the issue mentioned in the framework documentation? [Required]
22 | description: The documentation provides answers to frequently asked questions. Please check if the information you are looking for is already provided.
23 | multiple: false
24 | options:
25 | - "Not Selected"
26 | - "Yes"
27 | - "No"
28 | validations:
29 | required: true
30 | - type: dropdown
31 | id: input_id_3
32 | attributes:
33 | label: Did you consult the framework documentation but couldn't find a solution? [Required]
34 | description: If you have consulted the documentation but still couldn't find a solution, you can select "Yes."
35 | multiple: false
36 | options:
37 | - "Not Selected"
38 | - "Yes"
39 | - "No"
40 | validations:
41 | required: true
42 | - type: dropdown
43 | id: input_id_4
44 | attributes:
45 | label: Has a similar issue been reported in the issue list? [Required]
46 | description: You can search the issue list for keywords related to your problem and refer to the solutions provided by others.
47 | multiple: false
48 | options:
49 | - "Not Selected"
50 | - "Yes"
51 | - "No"
52 | validations:
53 | required: true
54 | - type: dropdown
55 | id: input_id_5
56 | attributes:
57 | label: Have you searched the issue list but couldn't find a solution? [Required]
58 | description: If you have searched the issue list and couldn't find a solution, you can select "Yes."
59 | multiple: false
60 | options:
61 | - "Not Selected"
62 | - "Yes"
63 | - "No"
64 | validations:
65 | required: true
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/issue_en_template_suggest.yml:
--------------------------------------------------------------------------------
1 | name: Submit Suggestion
2 | description: Please let me know the shortcomings of the framework, so that I can improve it!
3 | title: "[Suggestion]:"
4 | labels: ["help wanted"]
5 |
6 | body:
7 | - type: markdown
8 | attributes:
9 | value: |
10 | ## [Warning: Please make sure to fill in the issue template accurately. If an issue is found to be filled incorrectly, it will be closed without further notice.](https://github.com/getActivity/IssueTemplateGuide)
11 | - type: textarea
12 | id: input_id_1
13 | attributes:
14 | label: What are the shortcomings you have noticed in the framework? [Required]
15 | description: You can describe any aspects of the framework that you are not satisfied with.
16 | validations:
17 | required: true
18 | - type: dropdown
19 | id: input_id_2
20 | attributes:
21 | label: Has a similar suggestion been made in the issue list? [Required]
22 | description: If a similar suggestion has already been made, I will not address it again.
23 | multiple: false
24 | options:
25 | - "Not Selected"
26 | - "Yes"
27 | - "No"
28 | validations:
29 | required: true
30 | - type: dropdown
31 | id: input_id_3
32 | attributes:
33 | label: Is the suggestion mentioned in the framework documentation? [Required]
34 | description: The documentation provides answers to frequently asked questions. Please check if the information you are looking for is already provided.
35 | multiple: false
36 | options:
37 | - "Not Selected"
38 | - "Yes"
39 | - "No"
40 | validations:
41 | required: true
42 | - type: dropdown
43 | id: input_id_4
44 | attributes:
45 | label: Did you consult the framework documentation but couldn't find a solution? [Required]
46 | description: If you have consulted the documentation but still couldn't find a solution, you can select "Yes."
47 | multiple: false
48 | options:
49 | - "Not Selected"
50 | - "Yes"
51 | - "No"
52 | validations:
53 | required: true
54 | - type: textarea
55 | id: input_id_5
56 | attributes:
57 | label: How do you suggest improving it? [Optional]
58 | description: You can provide your ideas or approaches for the author's reference.
59 | validations:
60 | required: false
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/issue_zh_template_bug.yml:
--------------------------------------------------------------------------------
1 | name: 提交 Bug
2 | description: 请告诉我框架存在的问题,我会协助你解决此问题!
3 | title: "[Bug]:"
4 | labels: ["bug"]
5 |
6 | body:
7 | - type: markdown
8 | attributes:
9 | value: |
10 | ## [【警告:请务必按照 issue 模板填写,不要抱有侥幸心理,一旦发现 issue 没有按照模板认真填写,一律直接关闭】](https://github.com/getActivity/IssueTemplateGuide)
11 | - type: input
12 | id: input_id_1
13 | attributes:
14 | label: 框架版本【必填】
15 | description: 请输入你使用的框架版本
16 | validations:
17 | required: true
18 | - type: textarea
19 | id: input_id_2
20 | attributes:
21 | label: 问题描述【必填】
22 | description: 请输入你对这个问题的描述
23 | validations:
24 | required: true
25 | - type: textarea
26 | id: input_id_3
27 | attributes:
28 | label: 复现步骤【必填】
29 | description: 请输入问题的复现步骤
30 | validations:
31 | required: true
32 | - type: dropdown
33 | id: input_id_4
34 | attributes:
35 | label: 是否必现【必填】
36 | multiple: false
37 | options:
38 | - "未选择"
39 | - "是"
40 | - "否"
41 | validations:
42 | required: true
43 | - type: input
44 | id: input_id_5
45 | attributes:
46 | label: 项目 targetSdkVersion【必填】
47 | validations:
48 | required: true
49 | - type: input
50 | id: input_id_6
51 | attributes:
52 | label: 出现问题的手机信息【必填】
53 | description: 请填写出现问题的品牌和机型
54 | validations:
55 | required: true
56 | - type: input
57 | id: input_id_7
58 | attributes:
59 | label: 出现问题的安卓版本【必填】
60 | description: 请填写出现问题的 Android 版本
61 | validations:
62 | required: true
63 | - type: dropdown
64 | id: input_id_8
65 | attributes:
66 | label: 问题信息的来源渠道【必填】
67 | multiple: true
68 | options:
69 | - 自己遇到的
70 | - Bugly 看到的
71 | - 用户反馈
72 | - 其他渠道
73 | - type: input
74 | id: input_id_9
75 | attributes:
76 | label: 是部分机型还是所有机型都会出现【必答】
77 | description: 部分/全部(例如:某为,某 Android 版本会出现)
78 | validations:
79 | required: true
80 | - type: dropdown
81 | id: input_id_10
82 | attributes:
83 | label: 框架最新的版本是否存在这个问题【必答】
84 | description: 如果用的是旧版本的话,建议升级看问题是否还存在
85 | multiple: false
86 | options:
87 | - "未选择"
88 | - "是"
89 | - "否"
90 | validations:
91 | required: true
92 | - type: dropdown
93 | id: input_id_11
94 | attributes:
95 | label: 框架文档是否提及了该问题【必答】
96 | description: 文档会提供最常见的问题解答,可以先看看是否有自己想要的
97 | multiple: false
98 | options:
99 | - "未选择"
100 | - "是"
101 | - "否"
102 | validations:
103 | required: true
104 | - type: dropdown
105 | id: input_id_12
106 | attributes:
107 | label: 是否已经查阅框架文档但还未能解决的【必答】
108 | description: 如果查阅了文档但还是没有解决的话,可以选择是
109 | multiple: false
110 | options:
111 | - "未选择"
112 | - "是"
113 | - "否"
114 | validations:
115 | required: true
116 | - type: dropdown
117 | id: input_id_13
118 | attributes:
119 | label: issue 列表中是否有人曾提过类似的问题【必答】
120 | description: 可以在 issue 列表在搜索问题关键字,参考一下别人的解决方案
121 | multiple: false
122 | options:
123 | - "未选择"
124 | - "是"
125 | - "否"
126 | validations:
127 | required: true
128 | - type: dropdown
129 | id: input_id_14
130 | attributes:
131 | label: 是否已经搜索过了 issue 列表但还未能解决的【必答】
132 | description: 如果搜索过了 issue 列表但是问题没有解决的话,可以选择是
133 | multiple: false
134 | options:
135 | - "未选择"
136 | - "是"
137 | - "否"
138 | validations:
139 | required: true
140 | - type: dropdown
141 | id: input_id_15
142 | attributes:
143 | label: 是否可以通过 Demo 来复现该问题【必答】
144 | description: 排查一下是不是自己的项目代码写得有问题导致的
145 | multiple: false
146 | options:
147 | - "未选择"
148 | - "是"
149 | - "否"
150 | validations:
151 | required: true
152 | - type: textarea
153 | id: input_id_16
154 | attributes:
155 | label: 提供报错堆栈
156 | description: 如果有报错的话必填,注意不要拿被混淆过的代码堆栈上来
157 | render: text
158 | validations:
159 | required: false
160 | - type: textarea
161 | id: input_id_17
162 | attributes:
163 | label: 提供截图或视频
164 | description: 根据需要提供,此项不强制
165 | validations:
166 | required: false
167 | - type: textarea
168 | id: input_id_18
169 | attributes:
170 | label: 提供解决方案
171 | description: 如果已经解决了的话,此项不强制
172 | validations:
173 | required: false
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/issue_zh_template_question.yml:
--------------------------------------------------------------------------------
1 | name: 提出疑问
2 | description: 提出你的困惑,我会给你解答
3 | title: "[疑惑]:"
4 | labels: ["question"]
5 |
6 | body:
7 | - type: markdown
8 | attributes:
9 | value: |
10 | ## [【警告:请务必按照 issue 模板填写,不要抱有侥幸心理,一旦发现 issue 没有按照模板认真填写,一律直接关闭】](https://github.com/getActivity/IssueTemplateGuide)
11 | - type: textarea
12 | id: input_id_1
13 | attributes:
14 | label: 问题描述【必填】
15 | description: 请描述一下你的问题(注意:如果确定是框架 bug 请不要在这里提,否则一概不受理)
16 | validations:
17 | required: true
18 | - type: dropdown
19 | id: input_id_2
20 | attributes:
21 | label: 框架文档是否提及了该问题【必答】
22 | description: 文档会提供最常见的问题解答,可以先看看是否有自己想要的
23 | multiple: false
24 | options:
25 | - "未选择"
26 | - "是"
27 | - "否"
28 | validations:
29 | required: true
30 | - type: dropdown
31 | id: input_id_3
32 | attributes:
33 | label: 是否已经查阅框架文档但还未能解决的【必答】
34 | description: 如果查阅了文档但还是没有解决的话,可以选择是
35 | multiple: false
36 | options:
37 | - "未选择"
38 | - "是"
39 | - "否"
40 | validations:
41 | required: true
42 | - type: dropdown
43 | id: input_id_4
44 | attributes:
45 | label: issue 列表中是否有人曾提过类似的问题【必答】
46 | description: 可以在 issue 列表在搜索问题关键字,参考一下别人的解决方案
47 | multiple: false
48 | options:
49 | - "未选择"
50 | - "是"
51 | - "否"
52 | validations:
53 | required: true
54 | - type: dropdown
55 | id: input_id_5
56 | attributes:
57 | label: 是否已经搜索过了 issue 列表但还未能解决的【必答】
58 | description: 如果搜索过了 issue 列表但是问题没有解决的话,可以选择是
59 | multiple: false
60 | options:
61 | - "未选择"
62 | - "是"
63 | - "否"
64 | validations:
65 | required: true
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/issue_zh_template_suggest.yml:
--------------------------------------------------------------------------------
1 | name: 提交建议
2 | description: 请告诉我框架的不足之处,让我做得更好!
3 | title: "[建议]:"
4 | labels: ["help wanted"]
5 |
6 | body:
7 | - type: markdown
8 | attributes:
9 | value: |
10 | ## [【警告:请务必按照 issue 模板填写,不要抱有侥幸心理,一旦发现 issue 没有按照模板认真填写,一律直接关闭】](https://github.com/getActivity/IssueTemplateGuide)
11 | - type: textarea
12 | id: input_id_1
13 | attributes:
14 | label: 你觉得框架有什么不足之处?【必答】
15 | description: 你可以描述框架有什么令你不满意的地方
16 | validations:
17 | required: true
18 | - type: dropdown
19 | id: input_id_2
20 | attributes:
21 | label: issue 是否有人曾提过类似的建议?【必答】
22 | description: 一旦出现重复提问我将不会再次解答
23 | multiple: false
24 | options:
25 | - "未选择"
26 | - "是"
27 | - "否"
28 | validations:
29 | required: true
30 | - type: dropdown
31 | id: input_id_3
32 | attributes:
33 | label: 框架文档是否提及了该问题【必答】
34 | description: 文档会提供最常见的问题解答,可以先看看是否有自己想要的
35 | multiple: false
36 | options:
37 | - "未选择"
38 | - "是"
39 | - "否"
40 | validations:
41 | required: true
42 | - type: dropdown
43 | id: input_id_4
44 | attributes:
45 | label: 是否已经查阅框架文档但还未能解决的【必答】
46 | description: 如果查阅了文档但还是没有解决的话,可以选择是
47 | multiple: false
48 | options:
49 | - "未选择"
50 | - "是"
51 | - "否"
52 | validations:
53 | required: true
54 | - type: textarea
55 | id: input_id_5
56 | attributes:
57 | label: 你觉得该怎么去完善会比较好?【非必答】
58 | description: 你可以提供一下自己的想法或者做法供作者参考
59 | validations:
60 | required: false
--------------------------------------------------------------------------------
/.github/workflows/android.yml:
--------------------------------------------------------------------------------
1 | name: Android CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v2
12 | - name: set up JDK 1.8
13 | uses: actions/setup-java@v1
14 | with:
15 | java-version: 1.8
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | .idea
3 | .cxx
4 | .externalNativeBuild
5 | build
6 | captures
7 |
8 | ._*
9 | *.iml
10 | .DS_Store
11 | local.properties
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, October 2023
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2023 Huang JinQun
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ShapeDrawable 框架
2 |
3 | * 项目地址:[Github](https://github.com/getActivity/ShapeDrawable)
4 |
5 | * 可以扫码下载 Demo 进行演示或者测试,如果扫码下载不了的,[点击此处可直接下载](https://github.com/getActivity/ShapeDrawable/releases/download/3.3/ShapeDrawable.apk)
6 |
7 | 
8 |
9 | 
10 |
11 | #### 集成步骤
12 |
13 | * 如果你的项目 Gradle 配置是在 `7.0 以下`,需要在 `build.gradle` 文件中加入
14 |
15 | ```groovy
16 | allprojects {
17 | repositories {
18 | // JitPack 远程仓库:https://jitpack.io
19 | maven { url 'https://jitpack.io' }
20 | }
21 | }
22 | ```
23 |
24 | * 如果你的 Gradle 配置是 `7.0 及以上`,则需要在 `settings.gradle` 文件中加入
25 |
26 | ```groovy
27 | dependencyResolutionManagement {
28 | repositories {
29 | // JitPack 远程仓库:https://jitpack.io
30 | maven { url 'https://jitpack.io' }
31 | }
32 | }
33 | ```
34 |
35 | * 配置完远程仓库后,在项目 app 模块下的 `build.gradle` 文件中加入远程依赖
36 |
37 | ```groovy
38 | android {
39 | // 支持 JDK 1.8
40 | compileOptions {
41 | targetCompatibility JavaVersion.VERSION_1_8
42 | sourceCompatibility JavaVersion.VERSION_1_8
43 | }
44 | }
45 |
46 | dependencies {
47 | // ShapeDrawable:https://github.com/getActivity/ShapeDrawable
48 | implementation 'com.github.getActivity:ShapeDrawable:3.3'
49 | }
50 | ```
51 |
52 | #### 框架文档
53 |
54 | * 通用属性
55 |
56 | ```java
57 | // 设置 Shape 形状
58 | setType(@ShapeTypeLimit int shape)
59 |
60 | // 设置 Shape 宽度
61 | setWidth(int width)
62 |
63 | // 设置 Shape 高度
64 | setHeight(int height)
65 |
66 | // 设置矩形的圆角大小
67 | setRadius(float radius)
68 | setRadius(float topLeftRadius, float topRightRadius, float bottomLeftRadius, float bottomRightRadius)
69 | ```
70 |
71 | * 填充色相关
72 |
73 | ```java
74 | // 设置填充色
75 | setSolidColor(@ColorInt int startColor, @ColorInt int endColor)
76 | setSolidColor(@ColorInt int startColor, @ColorInt int centerColor, @ColorInt int endColor)
77 | setSolidColor(@ColorInt int... colors)
78 |
79 | // 设置填充色渐变类型
80 | setSolidGradientType(@ShapeGradientTypeLimit int type)
81 |
82 | // 设置填充色渐变方向
83 | setSolidGradientOrientation(ShapeGradientOrientation orientation)
84 |
85 | // 设置填充色渐变中心 X 点坐标的相对位置(默认值为 0.5)
86 | setSolidGradientCenterX(float centerX)
87 | // 设置填充色渐变中心 Y 点坐标的相对位置(默认值为 0.5)
88 | setSolidGradientCenterY(float centerY)
89 |
90 | // 设置填充色渐变半径大小
91 | setSolidGradientRadius(float radius)
92 | ```
93 |
94 | * 边框色相关
95 |
96 | ```java
97 | // 设置边框色
98 | setStrokeColor(@ColorInt int startColor, @ColorInt int endColor)
99 | setStrokeColor(@ColorInt int startColor, @ColorInt int centerColor, @ColorInt int endColor)
100 | setStrokeColor(@ColorInt int... colors)
101 |
102 | // 设置边框色渐变方向
103 | setStrokeGradientOrientation(ShapeGradientOrientation orientation)
104 |
105 | // 设置边框大小
106 | setStrokeSize(int size)
107 |
108 | // 设置边框每一节虚线宽度
109 | setStrokeDashSize(float dashSize)
110 | // 设置边框虚线每一节间隔
111 | setStrokeDashGap(float dashGap)
112 | ```
113 |
114 | * 阴影相关
115 |
116 | ```java
117 | // 设置阴影颜色
118 | setShadowColor(@ColorInt int color)
119 |
120 | // 设置阴影大小
121 | setShadowSize(int size)
122 |
123 | // 设置阴影水平偏移
124 | setShadowOffsetX(int offsetX)
125 |
126 | // 设置阴影垂直偏移
127 | setShadowOffsetY(int offsetY)
128 | ```
129 |
130 | * 圆环相关
131 |
132 | ```java
133 | // 设置内环的半径大小
134 | setRingInnerRadiusSize(int size)
135 | // 设置内环的半径比率
136 | setRingInnerRadiusRatio(float ratio)
137 |
138 | // 设置外环的厚度大小
139 | setRingThicknessSize(int size)
140 | // 设置外环的厚度比率
141 | setRingThicknessRatio(float ratio)
142 | ```
143 |
144 | * 线条相关
145 |
146 | ```java
147 | // 设置线条重心
148 | setLineGravity(int lineGravity)
149 | ```
150 |
151 | * 其他的
152 |
153 | ```java
154 | // 将当前 Drawable 对象应用到 View 背景,需要调用此 API 设置到 View 背景,否则可能会导致虚线或者阴影无法生效
155 | intoBackground(View view)
156 | ```
157 |
158 | #### 框架亮点
159 |
160 | * 相比系统提供的 GradientDrawable 更强大,ShapeDrawable 支持设置阴影(颜色、大小、偏移量)
161 |
162 | * 相比系统提供的 GradientDrawable 更强大,ShapeDrawable 支持单独给边框设置渐变色(渐变色色值、渐变色方向)
163 |
164 | * 相比系统提供的 GradientDrawable 更强大,ShapeDrawable 支持单独给线条设置方向(top、bottom、left、right、start、end 方向都支持)
165 |
166 | #### 作者的其他开源项目
167 |
168 | * 安卓技术中台:[AndroidProject](https://github.com/getActivity/AndroidProject)  
169 |
170 | * 安卓技术中台 Kt 版:[AndroidProject-Kotlin](https://github.com/getActivity/AndroidProject-Kotlin)  
171 |
172 | * 权限框架:[XXPermissions](https://github.com/getActivity/XXPermissions)  
173 |
174 | * 吐司框架:[Toaster](https://github.com/getActivity/Toaster)  
175 |
176 | * 网络框架:[EasyHttp](https://github.com/getActivity/EasyHttp)  
177 |
178 | * 标题栏框架:[TitleBar](https://github.com/getActivity/TitleBar)  
179 |
180 | * 悬浮窗框架:[EasyWindow](https://github.com/getActivity/EasyWindow)  
181 |
182 | * ShapeView 框架:[ShapeView](https://github.com/getActivity/ShapeView)  
183 |
184 | * 语种切换框架:[MultiLanguages](https://github.com/getActivity/MultiLanguages)  
185 |
186 | * Gson 解析容错:[GsonFactory](https://github.com/getActivity/GsonFactory)  
187 |
188 | * 日志查看框架:[Logcat](https://github.com/getActivity/Logcat)  
189 |
190 | * 嵌套滚动布局框架:[NestedScrollLayout](https://github.com/getActivity/NestedScrollLayout)  
191 |
192 | * Android 版本适配:[AndroidVersionAdapter](https://github.com/getActivity/AndroidVersionAdapter)  
193 |
194 | * Android 代码规范:[AndroidCodeStandard](https://github.com/getActivity/AndroidCodeStandard)  
195 |
196 | * Android 资源大汇总:[AndroidIndex](https://github.com/getActivity/AndroidIndex)  
197 |
198 | * Android 开源排行榜:[AndroidGithubBoss](https://github.com/getActivity/AndroidGithubBoss)  
199 |
200 | * Studio 精品插件:[StudioPlugins](https://github.com/getActivity/StudioPlugins)  
201 |
202 | * 表情包大集合:[EmojiPackage](https://github.com/getActivity/EmojiPackage)  
203 |
204 | * 省市区 Json 数据:[ProvinceJson](https://github.com/getActivity/ProvinceJson)  
205 |
206 | #### 微信公众号:Android轮子哥
207 |
208 | 
209 |
210 | #### Android 技术 Q 群:10047167
211 |
212 | #### 如果您觉得我的开源库帮你节省了大量的开发时间,请扫描下方的二维码随意打赏,要是能打赏个 10.24 :monkey_face:就太:thumbsup:了。您的支持将鼓励我继续创作:octocat:
213 |
214 |  
215 |
216 | #### [点击查看捐赠列表](https://github.com/getActivity/Donate)
217 |
218 | ## License
219 |
220 | ```text
221 | Copyright 2023 Huang JinQun
222 |
223 | Licensed under the Apache License, Version 2.0 (the "License");
224 | you may not use this file except in compliance with the License.
225 | You may obtain a copy of the License at
226 |
227 | http://www.apache.org/licenses/LICENSE-2.0
228 |
229 | Unless required by applicable law or agreed to in writing, software
230 | distributed under the License is distributed on an "AS IS" BASIS,
231 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
232 | See the License for the specific language governing permissions and
233 | limitations under the License.
234 | ```
--------------------------------------------------------------------------------
/app/AppSignature.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/ShapeDrawable/caa59f8408a7d6f121b0ffaab0fedd4febe2d3e8/app/AppSignature.jks
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 |
6 | defaultConfig {
7 | applicationId "com.hjq.shape.drawable.demo"
8 | minSdkVersion 16
9 | targetSdkVersion 28
10 | versionCode 33
11 | versionName "3.3"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | }
14 |
15 | // Apk 签名的那些事:https://www.jianshu.com/p/a1f8e5896aa2
16 | signingConfigs {
17 | config {
18 | storeFile file(StoreFile)
19 | storePassword StorePassword
20 | keyAlias KeyAlias
21 | keyPassword KeyPassword
22 | }
23 | }
24 |
25 | // 支持 Java JDK 8
26 | compileOptions {
27 | targetCompatibility JavaVersion.VERSION_1_8
28 | sourceCompatibility JavaVersion.VERSION_1_8
29 | }
30 |
31 | buildTypes {
32 | debug {
33 | minifyEnabled false
34 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
35 | signingConfig signingConfigs.config
36 | }
37 |
38 | release {
39 | minifyEnabled true
40 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
41 | signingConfig signingConfigs.config
42 | }
43 | }
44 |
45 | applicationVariants.configureEach { variant ->
46 | // apk 输出文件名配置
47 | variant.outputs.configureEach { output ->
48 | outputFileName = rootProject.getName() + '.apk'
49 | }
50 | }
51 | }
52 |
53 | dependencies {
54 | // 依赖 libs 目录下所有的 jar 和 aar 包
55 | implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
56 |
57 | implementation project(':library')
58 |
59 | implementation 'com.android.support:appcompat-v7:28.0.0'
60 | implementation 'com.android.support:design:28.0.0'
61 |
62 | // 标题栏框架:https://github.com/getActivity/TitleBar
63 | implementation 'com.github.getActivity:TitleBar:10.5'
64 |
65 | // 吐司框架:https://github.com/getActivity/Toaster
66 | implementation 'com.github.getActivity:Toaster:12.5'
67 | }
--------------------------------------------------------------------------------
/app/gradle.properties:
--------------------------------------------------------------------------------
1 | StoreFile = AppSignature.jks
2 | StorePassword = AndroidProject
3 | KeyAlias = AndroidProject
4 | KeyPassword = AndroidProject
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\SDK\Studio\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 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
Sets whether or not this drawable will honor its level
309 | * property.
Note: changing this property will affect all instances 311 | * of a drawable loaded from a resource. It is recommended to invoke 312 | * {@link #mutate()} before changing this property.
313 | * 314 | * @param useLevel True if this drawable should honor its level, false otherwise 315 | * 316 | * @see #mutate() 317 | * @see #setLevel(int) 318 | * @see #getLevel() 319 | */ 320 | public ShapeDrawable setUseLevel(boolean useLevel) { 321 | mShapeState.useLevel = useLevel; 322 | mRectDirty = true; 323 | invalidateSelf(); 324 | return this; 325 | } 326 | 327 | /** 328 | * 设置阴影颜色 329 | */ 330 | public ShapeDrawable setShadowColor(@ColorInt int color) { 331 | mShapeState.shadowColor = color; 332 | mPathDirty = true; 333 | mRectDirty = true; 334 | invalidateSelf(); 335 | return this; 336 | } 337 | 338 | /** 339 | * 设置阴影大小 340 | */ 341 | public ShapeDrawable setShadowSize(int size) { 342 | mShapeState.shadowSize = size; 343 | mPathDirty = true; 344 | mRectDirty = true; 345 | invalidateSelf(); 346 | return this; 347 | } 348 | 349 | /** 350 | * 设置阴影水平偏移 351 | */ 352 | public ShapeDrawable setShadowOffsetX(int offsetX) { 353 | mShapeState.shadowOffsetX = offsetX; 354 | mPathDirty = true; 355 | mRectDirty = true; 356 | invalidateSelf(); 357 | return this; 358 | } 359 | 360 | /** 361 | * 设置阴影垂直偏移 362 | */ 363 | public ShapeDrawable setShadowOffsetY(int offsetY) { 364 | mShapeState.shadowOffsetY = offsetY; 365 | mPathDirty = true; 366 | mRectDirty = true; 367 | invalidateSelf(); 368 | return this; 369 | } 370 | 371 | /** 372 | * 设置内环的半径大小 373 | */ 374 | public ShapeDrawable setRingInnerRadiusSize(int size) { 375 | mShapeState.ringInnerRadiusSize = size; 376 | mShapeState.ringInnerRadiusRatio = 0; 377 | mRectDirty = true; 378 | invalidateSelf(); 379 | return this; 380 | } 381 | 382 | /** 383 | * 设置内环的半径比率 384 | */ 385 | public ShapeDrawable setRingInnerRadiusRatio(float ratio) { 386 | mShapeState.ringInnerRadiusRatio = ratio; 387 | mShapeState.ringInnerRadiusSize = -1; 388 | mRectDirty = true; 389 | invalidateSelf(); 390 | return this; 391 | } 392 | 393 | /** 394 | * 设置外环的厚度大小 395 | */ 396 | public ShapeDrawable setRingThicknessSize(int size) { 397 | mShapeState.ringThicknessSize = size; 398 | mShapeState.ringThicknessRatio = 0; 399 | mRectDirty = true; 400 | invalidateSelf(); 401 | return this; 402 | } 403 | 404 | /** 405 | * 设置外环的厚度比率 406 | */ 407 | public ShapeDrawable setRingThicknessRatio(float ratio) { 408 | mShapeState.ringThicknessRatio = ratio; 409 | mShapeState.ringThicknessSize = -1; 410 | mRectDirty = true; 411 | invalidateSelf(); 412 | return this; 413 | } 414 | 415 | /** 416 | * 设置线条重心 417 | */ 418 | public ShapeDrawable setLineGravity(int lineGravity) { 419 | mShapeState.lineGravity = lineGravity; 420 | mRectDirty = true; 421 | invalidateSelf(); 422 | return this; 423 | } 424 | 425 | /** 426 | * 将当前 Drawable 对象应用到 View 背景 427 | */ 428 | public void intoBackground(View view) { 429 | if (mShapeState.strokeDashGap > 0 || mShapeState.shadowSize > 0) { 430 | // 需要关闭硬件加速,否则虚线或者阴影在某些手机上面无法生效 431 | view.setLayerType(View.LAYER_TYPE_SOFTWARE, null); 432 | } 433 | view.setBackground(this); 434 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 435 | // 布局方向 436 | int layoutDirection = view.getLayoutDirection(); 437 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 438 | setLayoutDirection(layoutDirection); 439 | } 440 | } 441 | } 442 | 443 | @SuppressLint("WrongConstant") 444 | @Override 445 | public void draw(@NonNull Canvas canvas) { 446 | if (!ensureValidRect()) { 447 | // nothing to draw 448 | return; 449 | } 450 | 451 | // remember the alpha values, in case we temporarily overwrite them 452 | // when we modulate them with mAlpha 453 | final int prevFillAlpha = mSolidPaint.getAlpha(); 454 | final int prevStrokeAlpha = mStrokePaint.getAlpha(); 455 | // compute the modulate alpha values 456 | final int currFillAlpha = modulateAlpha(prevFillAlpha); 457 | final int currStrokeAlpha = modulateAlpha(prevStrokeAlpha); 458 | 459 | final boolean haveShadow = mShapeState.shadowSize > 0; 460 | final boolean haveStroke = currStrokeAlpha > 0 && mStrokePaint.getStrokeWidth() > 0; 461 | final boolean haveFill = currFillAlpha > 0; 462 | final ShapeState st = mShapeState; 463 | /* we need a layer iff we're drawing both a fill and stroke, and the 464 | stroke is non-opaque, and our shape type actually supports 465 | fill+stroke. Otherwise we can just draw the stroke (if any) on top 466 | of the fill (if any) without worrying about blending artifacts. 467 | */ 468 | final boolean useLayer = haveStroke && haveFill && st.shapeType != ShapeType.LINE && 469 | currStrokeAlpha < 255 && (mAlpha < 255 || mColorFilter != null); 470 | 471 | /* Drawing with a layer is slower than direct drawing, but it 472 | allows us to apply paint effects like alpha and color filter to 473 | the result of multiple separate draws. In our case, if the user 474 | asks for a non-opaque alpha value (via setAlpha), and we're 475 | stroking, then we need to apply the alpha AFTER we've drawn 476 | both the fill and the stroke. 477 | */ 478 | 479 | if (useLayer) { 480 | if (mLayerPaint == null) { 481 | mLayerPaint = new Paint(); 482 | } 483 | mLayerPaint.setDither(mDither); 484 | mLayerPaint.setAlpha(mAlpha); 485 | mLayerPaint.setColorFilter(mColorFilter); 486 | 487 | float rad = mStrokePaint.getStrokeWidth(); 488 | ShapeDrawableUtils.saveCanvasLayer(canvas, mRect.left - rad, mRect.top - rad, 489 | mRect.right + rad, mRect.bottom + rad, mLayerPaint); 490 | 491 | // don't perform the filter in our individual paints 492 | // since the layer will do it for us 493 | mSolidPaint.setColorFilter(null); 494 | mStrokePaint.setColorFilter(null); 495 | } else { 496 | /* if we're not using a layer, apply the dither/filter to our 497 | individual paints 498 | */ 499 | mSolidPaint.setAlpha(currFillAlpha); 500 | mSolidPaint.setDither(mDither); 501 | mSolidPaint.setColorFilter(mColorFilter); 502 | if (mColorFilter != null && !mShapeState.hasSolidColor) { 503 | mSolidPaint.setColor(mAlpha << 24); 504 | } 505 | if (haveStroke) { 506 | mStrokePaint.setAlpha(currStrokeAlpha); 507 | mStrokePaint.setDither(mDither); 508 | mStrokePaint.setColorFilter(mColorFilter); 509 | } 510 | } 511 | 512 | if (haveShadow) { 513 | if (mShadowPaint == null) { 514 | mShadowPaint = new Paint(); 515 | mShadowPaint.setColor(Color.TRANSPARENT); 516 | mShadowPaint.setStyle(Paint.Style.STROKE); 517 | } 518 | 519 | if (haveStroke) { 520 | mShadowPaint.setStrokeWidth(mStrokePaint.getStrokeWidth()); 521 | } else { 522 | mShadowPaint.setStrokeWidth(mShapeState.shadowSize / 4f); 523 | } 524 | 525 | int shadowColor = mShapeState.shadowColor; 526 | // 如果阴影颜色是非透明的,则需要设置一点透明度进去,否则会显示不出来 527 | if (ShapeDrawableUtils.setColorAlphaComponent(mShapeState.shadowColor, 255) == mShapeState.shadowColor) { 528 | shadowColor = ShapeDrawableUtils.setColorAlphaComponent(mShapeState.shadowColor, 254); 529 | } 530 | 531 | mShadowPaint.setColor(shadowColor); 532 | 533 | float shadowRadius; 534 | // 这里解释一下为什么要阴影大小除以倍数,因为如果不这么做会导致阴影显示会超过 View 边界,从而导致出现阴影被截断的效果 535 | if (Build.VERSION.SDK_INT >= 28) { 536 | shadowRadius = mShapeState.shadowSize / 2f; 537 | } else { 538 | shadowRadius = mShapeState.shadowSize / 3f; 539 | } 540 | mShadowPaint.setMaskFilter(new BlurMaskFilter(shadowRadius, BlurMaskFilter.Blur.NORMAL)); 541 | 542 | } else { 543 | if (mShadowPaint != null) { 544 | mShadowPaint.clearShadowLayer(); 545 | } 546 | } 547 | 548 | switch (st.shapeType) { 549 | case ShapeType.RECTANGLE: 550 | if (st.radiusArray != null) { 551 | if (mPathDirty || mRectDirty) { 552 | mPath.reset(); 553 | mPath.addRoundRect(mRect, st.radiusArray, Path.Direction.CW); 554 | mPathDirty = mRectDirty = false; 555 | } 556 | if (haveShadow) { 557 | mShadowPath.reset(); 558 | mShadowPath.addRoundRect(mShadowRect, st.radiusArray, Path.Direction.CW); 559 | canvas.drawPath(mShadowPath, mShadowPaint); 560 | } 561 | canvas.drawPath(mPath, mSolidPaint); 562 | if (haveStroke) { 563 | canvas.drawPath(mPath, mStrokePaint); 564 | } 565 | } else if (st.radius > 0.0f) { 566 | // since the caller is only giving us 1 value, we will force 567 | // it to be square if the rect is too small in one dimension 568 | // to show it. If we did nothing, Skia would clamp the rad 569 | // independently along each axis, giving us a thin ellipse 570 | // if the rect were very wide but not very tall 571 | float rad = st.radius; 572 | float r = Math.min(mRect.width(), mRect.height()) * 0.5f; 573 | if (rad > r) { 574 | rad = r; 575 | } 576 | if (haveShadow) { 577 | canvas.drawRoundRect(mShadowRect, rad, rad, mShadowPaint); 578 | } 579 | canvas.drawRoundRect(mRect, rad, rad, mSolidPaint); 580 | if (haveStroke) { 581 | canvas.drawRoundRect(mRect, rad, rad, mStrokePaint); 582 | } 583 | } else { 584 | if (haveShadow) { 585 | canvas.drawRect(mShadowRect, mShadowPaint); 586 | } 587 | if (mSolidPaint.getColor() != 0 || mColorFilter != null || 588 | mSolidPaint.getShader() != null) { 589 | canvas.drawRect(mRect, mSolidPaint); 590 | } 591 | if (haveStroke) { 592 | canvas.drawRect(mRect, mStrokePaint); 593 | } 594 | } 595 | break; 596 | case ShapeType.OVAL: 597 | if (haveShadow) { 598 | canvas.drawOval(mShadowRect, mShadowPaint); 599 | } 600 | canvas.drawOval(mRect, mSolidPaint); 601 | if (haveStroke) { 602 | canvas.drawOval(mRect, mStrokePaint); 603 | } 604 | break; 605 | case ShapeType.LINE: { 606 | RectF r = mRect; 607 | float startX; 608 | float startY; 609 | float stopX; 610 | float stopY; 611 | int lineGravity; 612 | Callback callback = getCallback(); 613 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && callback instanceof View) { 614 | int layoutDirection = ((View) callback).getContext().getResources().getConfiguration().getLayoutDirection(); 615 | lineGravity = Gravity.getAbsoluteGravity(st.lineGravity, layoutDirection); 616 | } else { 617 | lineGravity = st.lineGravity; 618 | } 619 | 620 | switch (lineGravity) { 621 | case Gravity.LEFT: 622 | startX = 0; 623 | startY = 0; 624 | stopX = 0; 625 | stopY = r.bottom; 626 | break; 627 | case Gravity.RIGHT: 628 | startX = r.right; 629 | startY = 0; 630 | stopX = r.right; 631 | stopY = r.bottom; 632 | break; 633 | case Gravity.TOP: 634 | startX = 0; 635 | startY = 0; 636 | stopX = r.right; 637 | stopY = 0; 638 | break; 639 | case Gravity.BOTTOM: 640 | startX = 0; 641 | startY = r.bottom; 642 | stopX = r.right; 643 | stopY = r.bottom; 644 | break; 645 | case Gravity.CENTER: 646 | default: 647 | float y = r.centerY(); 648 | startX = r.left; 649 | startY = y; 650 | stopX = r.right; 651 | stopY = y; 652 | break; 653 | } 654 | 655 | if (haveShadow) { 656 | canvas.drawLine(startX, startY, stopX, stopY, mShadowPaint); 657 | } 658 | canvas.drawLine(startX, startY, stopX, stopY, mStrokePaint); 659 | break; 660 | } 661 | case ShapeType.RING: 662 | Path path = buildRing(st); 663 | if (haveShadow) { 664 | canvas.drawPath(path, mShadowPaint); 665 | } 666 | canvas.drawPath(path, mSolidPaint); 667 | if (haveStroke) { 668 | canvas.drawPath(path, mStrokePaint); 669 | } 670 | break; 671 | default: 672 | break; 673 | } 674 | 675 | if (useLayer) { 676 | canvas.restore(); 677 | } else { 678 | mSolidPaint.setAlpha(prevFillAlpha); 679 | if (haveStroke) { 680 | mStrokePaint.setAlpha(prevStrokeAlpha); 681 | } 682 | } 683 | } 684 | 685 | @Override 686 | public boolean onLayoutDirectionChanged(int layoutDirection) { 687 | mLayoutDirection = layoutDirection; 688 | return mShapeState.shapeType == ShapeType.LINE; 689 | } 690 | 691 | private int modulateAlpha(int alpha) { 692 | int scale = mAlpha + (mAlpha >> 7); 693 | return alpha * scale >> 8; 694 | } 695 | 696 | @Override 697 | public int getChangingConfigurations() { 698 | return super.getChangingConfigurations() | mShapeState.changingConfigurations; 699 | } 700 | 701 | @Override 702 | public void setAlpha(int alpha) { 703 | if (alpha != mAlpha) { 704 | mAlpha = alpha; 705 | invalidateSelf(); 706 | } 707 | } 708 | 709 | @Override 710 | public int getAlpha() { 711 | return mAlpha; 712 | } 713 | 714 | @Override 715 | public void setDither(boolean dither) { 716 | if (dither != mDither) { 717 | mDither = dither; 718 | invalidateSelf(); 719 | } 720 | } 721 | 722 | @Override 723 | public void setColorFilter(ColorFilter cf) { 724 | if (cf != mColorFilter) { 725 | mColorFilter = cf; 726 | invalidateSelf(); 727 | } 728 | } 729 | 730 | @Override 731 | public int getOpacity() { 732 | return mShapeState.opaque ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT; 733 | } 734 | 735 | @Override 736 | protected void onBoundsChange(Rect r) { 737 | super.onBoundsChange(r); 738 | mRingPath = null; 739 | mPathDirty = true; 740 | mRectDirty = true; 741 | } 742 | 743 | @Override 744 | protected boolean onLevelChange(int level) { 745 | super.onLevelChange(level); 746 | mRectDirty = true; 747 | mPathDirty = true; 748 | invalidateSelf(); 749 | return true; 750 | } 751 | 752 | private Path buildRing(ShapeState shapeState) { 753 | if (mRingPath != null && (!shapeState.useLevelForShape || !mPathDirty)) { 754 | return mRingPath; 755 | } 756 | mPathDirty = false; 757 | 758 | float sweep = shapeState.useLevelForShape ? (360.0f * getLevel() / 10000f) : 360f; 759 | 760 | RectF bounds = new RectF(mRect); 761 | 762 | float x = bounds.width() / 2.0f; 763 | float y = bounds.height() / 2.0f; 764 | 765 | float thickness = shapeState.ringThicknessSize != -1 ? 766 | shapeState.ringThicknessSize : bounds.width() / shapeState.ringThicknessRatio; 767 | // inner radius 768 | float radius = shapeState.ringInnerRadiusSize != -1 ? 769 | shapeState.ringInnerRadiusSize : bounds.width() / shapeState.ringInnerRadiusRatio; 770 | 771 | RectF innerBounds = new RectF(bounds); 772 | innerBounds.inset(x - radius, y - radius); 773 | 774 | bounds = new RectF(innerBounds); 775 | bounds.inset(-thickness, -thickness); 776 | 777 | if (mRingPath == null) { 778 | mRingPath = new Path(); 779 | } else { 780 | mRingPath.reset(); 781 | } 782 | 783 | final Path ringPath = mRingPath; 784 | // arcTo treats the sweep angle mod 360, so check for that, since we 785 | // think 360 means draw the entire oval 786 | if (sweep < 360 && sweep > -360) { 787 | ringPath.setFillType(Path.FillType.EVEN_ODD); 788 | // inner top 789 | ringPath.moveTo(x + radius, y); 790 | // outer top 791 | ringPath.lineTo(x + radius + thickness, y); 792 | // outer arc 793 | ringPath.arcTo(bounds, 0.0f, sweep, false); 794 | // inner arc 795 | ringPath.arcTo(innerBounds, sweep, -sweep, false); 796 | ringPath.close(); 797 | } else { 798 | // add the entire ovals 799 | ringPath.addOval(bounds, Path.Direction.CW); 800 | ringPath.addOval(innerBounds, Path.Direction.CCW); 801 | } 802 | 803 | return ringPath; 804 | } 805 | 806 | /** 807 | * This checks mRectIsDirty, and if it is true, recomputes both our drawing 808 | * rectangle (mRect) and the gradient itself, since it depends on our 809 | * rectangle too. 810 | * @return true if the resulting rectangle is not empty, false otherwise 811 | */ 812 | private boolean ensureValidRect() { 813 | if (!mRectDirty) { 814 | return !mRect.isEmpty(); 815 | } 816 | 817 | mRectDirty = false; 818 | 819 | Rect bounds = getBounds(); 820 | float inset = mStrokePaint.getStrokeWidth() * 0.5f; 821 | 822 | final ShapeState st = mShapeState; 823 | 824 | float let = bounds.left + inset + mShapeState.shadowSize; 825 | float top = bounds.top + inset + mShapeState.shadowSize; 826 | float right = bounds.right - inset - mShapeState.shadowSize; 827 | float bottom = bounds.bottom - inset - mShapeState.shadowSize; 828 | 829 | mRect.set(let, top, right, bottom); 830 | 831 | float shadowLet; 832 | float shadowTop; 833 | float shadowRight; 834 | float shadowBottom; 835 | 836 | if (mShapeState.shadowOffsetX > 0) { 837 | shadowLet = let + mShapeState.shadowOffsetX; 838 | shadowRight = right; 839 | } else { 840 | shadowLet = let; 841 | shadowRight = right + mShapeState.shadowOffsetX; 842 | } 843 | 844 | if (mShapeState.shadowOffsetY > 0) { 845 | shadowTop = top + mShapeState.shadowOffsetY; 846 | shadowBottom = bottom; 847 | } else { 848 | shadowTop = top; 849 | shadowBottom = bottom + mShapeState.shadowOffsetY; 850 | } 851 | 852 | mShadowRect.set(shadowLet, shadowTop, shadowRight, shadowBottom); 853 | 854 | if (st.solidColors == null) { 855 | mSolidPaint.setShader(null); 856 | } 857 | 858 | if (st.strokeColors == null) { 859 | mStrokePaint.setShader(null); 860 | } 861 | 862 | if (st.solidColors != null) { 863 | RectF rect = mRect; 864 | 865 | switch (st.solidGradientType) { 866 | case ShapeGradientType.LINEAR_GRADIENT: { 867 | final float level = st.useLevel ? getLevel() / 10000f : 1f; 868 | float[] coordinate = ShapeDrawableUtils.computeLinearGradientCoordinate( 869 | mLayoutDirection, mRect, level, st.solidGradientOrientation); 870 | mSolidPaint.setShader(new LinearGradient(coordinate[0], coordinate[1], coordinate[2], coordinate[3], 871 | st.solidColors, st.positions, Shader.TileMode.CLAMP)); 872 | break; 873 | } 874 | case ShapeGradientType.RADIAL_GRADIENT: { 875 | float x0; 876 | float y0; 877 | x0 = rect.left + (rect.right - rect.left) * st.solidCenterX; 878 | y0 = rect.top + (rect.bottom - rect.top) * st.solidCenterY; 879 | 880 | final float level = st.useLevel ? getLevel() / 10000f : 1f; 881 | 882 | mSolidPaint.setShader(new RadialGradient(x0, y0, 883 | level * st.gradientRadius, st.solidColors, null, 884 | Shader.TileMode.CLAMP)); 885 | break; 886 | } 887 | case ShapeGradientType.SWEEP_GRADIENT: { 888 | float x0; 889 | float y0; 890 | 891 | x0 = rect.left + (rect.right - rect.left) * st.solidCenterX; 892 | y0 = rect.top + (rect.bottom - rect.top) * st.solidCenterY; 893 | 894 | int[] tempSolidColors = st.solidColors; 895 | float[] tempSolidPositions = null; 896 | 897 | if (st.useLevel) { 898 | tempSolidColors = st.tempSolidColors; 899 | final int length = st.solidColors.length; 900 | if (tempSolidColors == null || tempSolidColors.length != length + 1) { 901 | tempSolidColors = st.tempSolidColors = new int[length + 1]; 902 | } 903 | System.arraycopy(st.solidColors, 0, tempSolidColors, 0, length); 904 | tempSolidColors[length] = st.solidColors[length - 1]; 905 | 906 | 907 | tempSolidPositions = st.tempSolidPositions; 908 | final float fraction = 1f / (length - 1); 909 | if (tempSolidPositions == null || tempSolidPositions.length != length + 1) { 910 | tempSolidPositions = st.tempSolidPositions = new float[length + 1]; 911 | } 912 | 913 | final float level = getLevel() / 10000f; 914 | for (int i = 0; i < length; i++) { 915 | tempSolidPositions[i] = i * fraction * level; 916 | } 917 | tempSolidPositions[length] = 1f; 918 | } 919 | 920 | mSolidPaint.setShader(new SweepGradient(x0, y0, tempSolidColors, tempSolidPositions)); 921 | break; 922 | } 923 | default: 924 | break; 925 | } 926 | 927 | // If we don't have a solid color, the alpha channel must be 928 | // maxed out so that alpha modulation works correctly. 929 | if (!st.hasSolidColor) { 930 | mSolidPaint.setColor(Color.BLACK); 931 | } 932 | } 933 | 934 | if (st.strokeColors != null) { 935 | final float level = st.useLevel ? getLevel() / 10000f : 1f; 936 | float[] coordinate = ShapeDrawableUtils.computeLinearGradientCoordinate( 937 | mLayoutDirection, mRect, level, st.strokeGradientOrientation); 938 | mStrokePaint.setShader(new LinearGradient(coordinate[0], coordinate[1], coordinate[2], coordinate[3], 939 | st.strokeColors, st.positions, Shader.TileMode.CLAMP)); 940 | 941 | if (!st.hasStrokeColor) { 942 | mStrokePaint.setColor(Color.BLACK); 943 | } 944 | } 945 | return !mRect.isEmpty(); 946 | } 947 | 948 | @Override 949 | public int getIntrinsicWidth() { 950 | return mShapeState.width; 951 | } 952 | 953 | @Override 954 | public int getIntrinsicHeight() { 955 | return mShapeState.height; 956 | } 957 | 958 | @Override 959 | public ConstantState getConstantState() { 960 | mShapeState.changingConfigurations = getChangingConfigurations(); 961 | return mShapeState; 962 | } 963 | 964 | @NonNull 965 | @Override 966 | public Drawable mutate() { 967 | if (!mMutated && super.mutate() == this) { 968 | mShapeState = new ShapeState(mShapeState); 969 | initializeWithState(mShapeState); 970 | mMutated = true; 971 | } 972 | return this; 973 | } 974 | 975 | private void initializeWithState(ShapeState state) { 976 | if (state.hasSolidColor) { 977 | mSolidPaint.setColor(state.solidColor); 978 | } else if (state.solidColors == null) { 979 | // If we don't have a solid color and we don't have a gradient, 980 | // the app is stroking the shape, set the color to the default 981 | // value of state.solidColor 982 | mSolidPaint.setColor(0); 983 | } else { 984 | // Otherwise, make sure the fill alpha is maxed out. 985 | mSolidPaint.setColor(Color.BLACK); 986 | } 987 | mPadding = state.padding; 988 | if (state.strokeSize >= 0) { 989 | if (state.hasStrokeColor) { 990 | setStrokeColor(state.strokeColor); 991 | } else { 992 | setStrokeColor(state.strokeColors); 993 | } 994 | setStrokeSize(state.strokeSize); 995 | setStrokeDashSize(state.strokeDashSize); 996 | setStrokeDashGap(state.strokeDashGap); 997 | } 998 | } 999 | } -------------------------------------------------------------------------------- /library/src/main/java/com/hjq/shape/drawable/ShapeDrawableUtils.java: -------------------------------------------------------------------------------- 1 | package com.hjq.shape.drawable; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Paint; 5 | import android.graphics.RectF; 6 | import android.os.Build; 7 | import android.support.annotation.ColorInt; 8 | import android.support.annotation.IntRange; 9 | import android.support.annotation.Nullable; 10 | import android.view.View; 11 | 12 | /** 13 | * author : Android 轮子哥 14 | * github : https://github.com/getActivity/ShapeDrawable 15 | * time : 2023/07/15 16 | * desc : ShapeDrawable 工具类(仅供内部使用) 17 | */ 18 | final class ShapeDrawableUtils { 19 | 20 | static void saveCanvasLayer(Canvas canvas, float left, float top, float right, float bottom, @Nullable Paint paint) { 21 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 22 | canvas.saveLayer(left, top, right, bottom, paint); 23 | } else { 24 | canvas.saveLayer(left, top, right, bottom, paint, 0x04); 25 | } 26 | } 27 | 28 | static float[] computeLinearGradientCoordinate(int layoutDirection, RectF r, float level, ShapeGradientOrientation orientation) { 29 | float x0, x1, y0, y1; 30 | switch (orientation) { 31 | case START_TO_END: 32 | return computeLinearGradientCoordinate(layoutDirection, r, level, 33 | layoutDirection == View.LAYOUT_DIRECTION_RTL ? 34 | ShapeGradientOrientation.RIGHT_TO_LEFT : ShapeGradientOrientation.LEFT_TO_RIGHT); 35 | case END_TO_START: 36 | return computeLinearGradientCoordinate(layoutDirection, r, level, 37 | layoutDirection == View.LAYOUT_DIRECTION_RTL ? 38 | ShapeGradientOrientation.LEFT_TO_RIGHT : ShapeGradientOrientation.RIGHT_TO_LEFT); 39 | case TOP_START_TO_BOTTOM_END: 40 | return computeLinearGradientCoordinate(layoutDirection, r, level, 41 | layoutDirection == View.LAYOUT_DIRECTION_RTL ? 42 | ShapeGradientOrientation.TOP_RIGHT_TO_BOTTOM_LEFT : ShapeGradientOrientation.TOP_LEFT_TO_BOTTOM_RIGHT); 43 | case TOP_END_TO_BOTTOM_START: 44 | return computeLinearGradientCoordinate(layoutDirection, r, level, 45 | layoutDirection == View.LAYOUT_DIRECTION_RTL ? 46 | ShapeGradientOrientation.TOP_LEFT_TO_BOTTOM_RIGHT : ShapeGradientOrientation.TOP_RIGHT_TO_BOTTOM_LEFT); 47 | case BOTTOM_START_TO_TOP_END: 48 | return computeLinearGradientCoordinate(layoutDirection, r, level, 49 | layoutDirection == View.LAYOUT_DIRECTION_RTL ? 50 | ShapeGradientOrientation.BOTTOM_RIGHT_TO_TOP_LEFT : ShapeGradientOrientation.BOTTOM_LEFT_TO_TOP_RIGHT); 51 | case BOTTOM_END_TO_TOP_START: 52 | return computeLinearGradientCoordinate(layoutDirection, r, level, 53 | layoutDirection == View.LAYOUT_DIRECTION_RTL ? 54 | ShapeGradientOrientation.BOTTOM_LEFT_TO_TOP_RIGHT : ShapeGradientOrientation.BOTTOM_RIGHT_TO_TOP_LEFT); 55 | case TOP_TO_BOTTOM: 56 | x0 = r.left; y0 = r.top; 57 | x1 = x0; y1 = level * r.bottom; 58 | break; 59 | case TOP_RIGHT_TO_BOTTOM_LEFT: 60 | x0 = r.right; y0 = r.top; 61 | x1 = level * r.left; y1 = level * r.bottom; 62 | break; 63 | case RIGHT_TO_LEFT: 64 | x0 = r.right; y0 = r.top; 65 | x1 = level * r.left; y1 = y0; 66 | break; 67 | case BOTTOM_RIGHT_TO_TOP_LEFT: 68 | x0 = r.right; y0 = r.bottom; 69 | x1 = level * r.left; y1 = level * r.top; 70 | break; 71 | case BOTTOM_TO_TOP: 72 | x0 = r.left; y0 = r.bottom; 73 | x1 = x0; y1 = level * r.top; 74 | break; 75 | case BOTTOM_LEFT_TO_TOP_RIGHT: 76 | x0 = r.left; y0 = r.bottom; 77 | x1 = level * r.right; y1 = level * r.top; 78 | break; 79 | case LEFT_TO_RIGHT: 80 | x0 = r.left; y0 = r.top; 81 | x1 = level * r.right; y1 = y0; 82 | break; 83 | case TOP_LEFT_TO_BOTTOM_RIGHT: 84 | default: 85 | x0 = r.left; y0 = r.top; 86 | x1 = level * r.right; y1 = level * r.bottom; 87 | break; 88 | } 89 | return new float[] {x0, y0, x1, y1}; 90 | } 91 | 92 | /** 93 | * 设置颜色的透明度,参考 Support 包中的 ColorUtils.setAlphaComponent 方法 94 | */ 95 | @ColorInt 96 | public static int setColorAlphaComponent(@ColorInt int color, 97 | @IntRange(from = 0x0, to = 0xFF) int alpha) { 98 | if (alpha < 0 || alpha > 255) { 99 | throw new IllegalArgumentException("alpha must be between 0 and 255."); 100 | } 101 | return (color & 0x00ffffff) | (alpha << 24); 102 | } 103 | } -------------------------------------------------------------------------------- /library/src/main/java/com/hjq/shape/drawable/ShapeGradientOrientation.java: -------------------------------------------------------------------------------- 1 | package com.hjq.shape.drawable; 2 | 3 | /** 4 | * author : Android 轮子哥 5 | * github : https://github.com/getActivity/ShapeDrawable 6 | * time : 2021/08/15 7 | * desc : Shape 渐变方向 8 | */ 9 | public enum ShapeGradientOrientation { 10 | 11 | /** 从左到右绘制渐变(0 度) */ 12 | LEFT_TO_RIGHT, 13 | START_TO_END, 14 | 15 | /** 从右到左绘制渐变(180 度) */ 16 | RIGHT_TO_LEFT, 17 | END_TO_START, 18 | 19 | /** 从下到上绘制渐变(90 度) */ 20 | BOTTOM_TO_TOP, 21 | 22 | /** 从上到下绘制渐变(270 度) */ 23 | TOP_TO_BOTTOM, 24 | 25 | // ------------------------------ // 26 | 27 | /** 从左上角到右下角绘制渐变(315 度) */ 28 | TOP_LEFT_TO_BOTTOM_RIGHT, 29 | TOP_START_TO_BOTTOM_END, 30 | 31 | /** 从右上角到左下角绘制渐变(225 度) */ 32 | TOP_RIGHT_TO_BOTTOM_LEFT, 33 | TOP_END_TO_BOTTOM_START, 34 | 35 | /** 从左下角到右上角绘制渐变(45 度) */ 36 | BOTTOM_LEFT_TO_TOP_RIGHT, 37 | BOTTOM_START_TO_TOP_END, 38 | 39 | /** 从右下角到左上角绘制渐变(135 度) */ 40 | BOTTOM_RIGHT_TO_TOP_LEFT, 41 | BOTTOM_END_TO_TOP_START 42 | } -------------------------------------------------------------------------------- /library/src/main/java/com/hjq/shape/drawable/ShapeGradientType.java: -------------------------------------------------------------------------------- 1 | package com.hjq.shape.drawable; 2 | 3 | /** 4 | * author : Android 轮子哥 5 | * github : https://github.com/getActivity/ShapeDrawable 6 | * time : 2021/08/15 7 | * desc : Shape 渐变类型 8 | */ 9 | public final class ShapeGradientType { 10 | 11 | /** 线性渐变 */ 12 | public static final int LINEAR_GRADIENT = 0; 13 | 14 | /** 径向渐变 */ 15 | public static final int RADIAL_GRADIENT = 1; 16 | 17 | /** 扫描渐变 */ 18 | public static final int SWEEP_GRADIENT = 2; 19 | } -------------------------------------------------------------------------------- /library/src/main/java/com/hjq/shape/drawable/ShapeGradientTypeLimit.java: -------------------------------------------------------------------------------- 1 | package com.hjq.shape.drawable; 2 | 3 | import android.support.annotation.IntDef; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | 8 | /** 9 | * author : Android 轮子哥 10 | * github : https://github.com/getActivity/ShapeDrawable 11 | * time : 2023/07/16 12 | * desc : Shape 渐变类型赋值限制 13 | */ 14 | @IntDef({ 15 | ShapeGradientType.LINEAR_GRADIENT, 16 | ShapeGradientType.RADIAL_GRADIENT, 17 | ShapeGradientType.SWEEP_GRADIENT 18 | }) 19 | @Retention(RetentionPolicy.SOURCE) 20 | public @interface ShapeGradientTypeLimit {} -------------------------------------------------------------------------------- /library/src/main/java/com/hjq/shape/drawable/ShapeState.java: -------------------------------------------------------------------------------- 1 | package com.hjq.shape.drawable; 2 | 3 | import android.content.res.Resources; 4 | import android.graphics.Rect; 5 | import android.graphics.drawable.Drawable; 6 | import android.view.Gravity; 7 | 8 | /** 9 | * author : Android 轮子哥 10 | * github : https://github.com/getActivity/ShapeDrawable 11 | * time : 2021/08/15 12 | * desc : ShapeDrawable 参数构建 13 | */ 14 | public class ShapeState extends Drawable.ConstantState { 15 | 16 | public int changingConfigurations; 17 | @ShapeTypeLimit 18 | public int shapeType = ShapeType.RECTANGLE; 19 | @ShapeGradientTypeLimit 20 | public int solidGradientType = ShapeGradientType.LINEAR_GRADIENT; 21 | public ShapeGradientOrientation solidGradientOrientation = ShapeGradientOrientation.TOP_TO_BOTTOM; 22 | public int[] solidColors; 23 | public int[] strokeColors; 24 | public int[] tempSolidColors; // no need to copy 25 | public float[] tempSolidPositions; // no need to copy 26 | public float[] positions; 27 | public boolean hasSolidColor; 28 | public boolean hasStrokeColor; 29 | public int solidColor; 30 | public int strokeSize = -1; // if >= 0 use stroking. 31 | public ShapeGradientOrientation strokeGradientOrientation = ShapeGradientOrientation.TOP_TO_BOTTOM; 32 | public int strokeColor; 33 | public float strokeDashSize; 34 | public float strokeDashGap; 35 | public float radius; // use this if mRadiusArray is null 36 | public float[] radiusArray; 37 | public Rect padding; 38 | public int width = -1; 39 | public int height = -1; 40 | public float ringInnerRadiusRatio; 41 | public float ringThicknessRatio; 42 | public int ringInnerRadiusSize = -1; 43 | public int ringThicknessSize = -1; 44 | public float solidCenterX = 0.5f; 45 | public float solidCenterY = 0.5f; 46 | public float gradientRadius = 0.5f; 47 | public boolean useLevel; 48 | public boolean useLevelForShape; 49 | public boolean opaque; 50 | 51 | public int shadowSize; 52 | public int shadowColor; 53 | public int shadowOffsetX; 54 | public int shadowOffsetY; 55 | 56 | public int lineGravity = Gravity.CENTER; 57 | 58 | public ShapeState() {} 59 | 60 | public ShapeState(ShapeState state) { 61 | changingConfigurations = state.changingConfigurations; 62 | shapeType = state.shapeType; 63 | solidGradientType = state.solidGradientType; 64 | solidGradientOrientation = state.solidGradientOrientation; 65 | if (state.solidColors != null) { 66 | solidColors = state.solidColors.clone(); 67 | } 68 | if (state.strokeColors != null) { 69 | strokeColors = state.strokeColors.clone(); 70 | } 71 | if (state.positions != null) { 72 | positions = state.positions.clone(); 73 | } 74 | hasSolidColor = state.hasSolidColor; 75 | hasStrokeColor = state.hasStrokeColor; 76 | solidColor = state.solidColor; 77 | strokeSize = state.strokeSize; 78 | strokeGradientOrientation = state.strokeGradientOrientation; 79 | strokeColor = state.strokeColor; 80 | strokeDashSize = state.strokeDashSize; 81 | strokeDashGap = state.strokeDashGap; 82 | radius = state.radius; 83 | if (state.radiusArray != null) { 84 | radiusArray = state.radiusArray.clone(); 85 | } 86 | if (state.padding != null) { 87 | padding = new Rect(state.padding); 88 | } 89 | width = state.width; 90 | height = state.height; 91 | ringInnerRadiusRatio = state.ringInnerRadiusRatio; 92 | ringThicknessRatio = state.ringThicknessRatio; 93 | ringInnerRadiusSize = state.ringInnerRadiusSize; 94 | ringThicknessSize = state.ringThicknessSize; 95 | solidCenterX = state.solidCenterX; 96 | solidCenterY = state.solidCenterY; 97 | gradientRadius = state.gradientRadius; 98 | useLevel = state.useLevel; 99 | useLevelForShape = state.useLevelForShape; 100 | opaque = state.opaque; 101 | 102 | shadowSize = state.shadowSize; 103 | shadowColor = state.shadowColor; 104 | shadowOffsetX = state.shadowOffsetX; 105 | shadowOffsetY = state.shadowOffsetY; 106 | 107 | lineGravity = state.lineGravity; 108 | } 109 | 110 | @Override 111 | public Drawable newDrawable() { 112 | return new ShapeDrawable(this); 113 | } 114 | 115 | @Override 116 | public Drawable newDrawable(Resources res) { 117 | return new ShapeDrawable(this); 118 | } 119 | 120 | @Override 121 | public int getChangingConfigurations() { 122 | return changingConfigurations; 123 | } 124 | 125 | public void setType(int shape) { 126 | shapeType = shape; 127 | computeOpacity(); 128 | } 129 | 130 | public void setSolidGradientType(int gradientType) { 131 | this.solidGradientType = gradientType; 132 | } 133 | 134 | public void setSolidColor(int... colors) { 135 | if (colors == null) { 136 | solidColor = 0; 137 | hasSolidColor = true; 138 | computeOpacity(); 139 | return; 140 | } 141 | 142 | if (colors.length == 1) { 143 | hasSolidColor = true; 144 | solidColor = colors[0]; 145 | solidColors = null; 146 | } else { 147 | hasSolidColor = false; 148 | solidColor = 0; 149 | solidColors = colors; 150 | } 151 | computeOpacity(); 152 | } 153 | 154 | public void setSolidColor(int argb) { 155 | hasSolidColor = true; 156 | solidColor = argb; 157 | solidColors = null; 158 | computeOpacity(); 159 | } 160 | 161 | private void computeOpacity() { 162 | if (shapeType != ShapeType.RECTANGLE) { 163 | opaque = false; 164 | return; 165 | } 166 | 167 | if (radius > 0 || radiusArray != null) { 168 | opaque = false; 169 | return; 170 | } 171 | 172 | if (shadowSize > 0) { 173 | opaque = false; 174 | return; 175 | } 176 | 177 | if (strokeSize > 0 && !isOpaque(strokeColor)) { 178 | opaque = false; 179 | return; 180 | } 181 | 182 | if (hasSolidColor) { 183 | opaque = isOpaque(solidColor); 184 | return; 185 | } 186 | 187 | if (solidColors != null) { 188 | for (int color : solidColors) { 189 | if (!isOpaque(color)) { 190 | opaque = false; 191 | return; 192 | } 193 | } 194 | } 195 | 196 | if (hasStrokeColor) { 197 | opaque = isOpaque(strokeColor); 198 | return; 199 | } 200 | 201 | if (strokeColors != null) { 202 | for (int color : strokeColors) { 203 | if (!isOpaque(color)) { 204 | opaque = false; 205 | return; 206 | } 207 | } 208 | } 209 | 210 | opaque = true; 211 | } 212 | 213 | private static boolean isOpaque(int color) { 214 | return ((color >> 24) & 0xff) == 0xff; 215 | } 216 | 217 | public void setStrokeSize(int size) { 218 | strokeSize = size; 219 | computeOpacity(); 220 | } 221 | 222 | public void setStrokeColor(int... colors) { 223 | if (colors == null) { 224 | strokeColor = 0; 225 | hasStrokeColor = true; 226 | computeOpacity(); 227 | return; 228 | } 229 | 230 | if (colors.length == 1) { 231 | hasStrokeColor = true; 232 | strokeColor = colors[0]; 233 | strokeColors = null; 234 | } else { 235 | hasStrokeColor = false; 236 | strokeColor = 0; 237 | strokeColors = colors; 238 | } 239 | computeOpacity(); 240 | } 241 | 242 | public void setCornerRadius(float radius) { 243 | if (radius < 0) { 244 | radius = 0; 245 | } 246 | this.radius = radius; 247 | radiusArray = null; 248 | } 249 | 250 | public void setCornerRadii(float[] radii) { 251 | radiusArray = radii; 252 | if (radii == null) { 253 | radius = 0; 254 | } 255 | } 256 | } -------------------------------------------------------------------------------- /library/src/main/java/com/hjq/shape/drawable/ShapeType.java: -------------------------------------------------------------------------------- 1 | package com.hjq.shape.drawable; 2 | 3 | /** 4 | * author : Android 轮子哥 5 | * github : https://github.com/getActivity/ShapeDrawable 6 | * time : 2021/08/15 7 | * desc : Shape 形状类型 8 | */ 9 | public final class ShapeType { 10 | 11 | /** 矩形 */ 12 | public static final int RECTANGLE = 0; 13 | 14 | /** 椭圆形 */ 15 | public static final int OVAL = 1; 16 | 17 | /** 线条 */ 18 | public static final int LINE = 2; 19 | 20 | /** 圆环 */ 21 | public static final int RING = 3; 22 | } -------------------------------------------------------------------------------- /library/src/main/java/com/hjq/shape/drawable/ShapeTypeLimit.java: -------------------------------------------------------------------------------- 1 | package com.hjq.shape.drawable; 2 | 3 | import android.support.annotation.IntDef; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | 8 | /** 9 | * author : Android 轮子哥 10 | * github : https://github.com/getActivity/ShapeDrawable 11 | * time : 2023/07/16 12 | * desc : Shape 形状类型赋值限制 13 | */ 14 | @IntDef({ShapeType.RECTANGLE, ShapeType.OVAL, 15 | ShapeType.LINE, ShapeType.RING}) 16 | @Retention(RetentionPolicy.SOURCE) 17 | public @interface ShapeTypeLimit {} -------------------------------------------------------------------------------- /picture/demo_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getActivity/ShapeDrawable/caa59f8408a7d6f121b0ffaab0fedd4febe2d3e8/picture/demo_code.png -------------------------------------------------------------------------------- /picture/long_screenshots.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getActivity/ShapeDrawable/caa59f8408a7d6f121b0ffaab0fedd4febe2d3e8/picture/long_screenshots.jpg -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':library' --------------------------------------------------------------------------------