4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jpeng.jptabbar.badgeview;
18 |
19 | import android.content.Context;
20 | import android.graphics.Bitmap;
21 | import android.graphics.Canvas;
22 | import android.util.AttributeSet;
23 | import android.view.MotionEvent;
24 | import android.widget.RelativeLayout;
25 |
26 | /**
27 | * Author jpeng
28 | * Date: 16-11-15
29 | * E-mail:peng8350@gmail.com
30 | */
31 | public class BadgeRelativeLayout extends RelativeLayout implements Badgeable {
32 | private BadgeViewHelper mBadgeViewHeler;
33 |
34 | public BadgeRelativeLayout(Context context) {
35 | this(context, null);
36 | }
37 |
38 | public BadgeRelativeLayout(Context context, AttributeSet attrs) {
39 | this(context, attrs, 0);
40 | }
41 |
42 | public BadgeRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
43 | super(context, attrs, defStyleAttr);
44 | mBadgeViewHeler = new BadgeViewHelper(this, context, attrs, BadgeViewHelper.BadgeGravity.RightCenter);
45 | }
46 |
47 | @Override
48 | public boolean onTouchEvent(MotionEvent event) {
49 | return mBadgeViewHeler.onTouchEvent(event);
50 | }
51 |
52 | @Override
53 | public boolean callSuperOnTouchEvent(MotionEvent event) {
54 | return super.onTouchEvent(event);
55 | }
56 |
57 | @Override
58 | protected void dispatchDraw(Canvas canvas) {
59 | super.dispatchDraw(canvas);
60 | mBadgeViewHeler.drawBadge(canvas);
61 | }
62 |
63 | @Override
64 | public void showCirclePointBadge() {
65 | mBadgeViewHeler.showCirclePointBadge();
66 | }
67 |
68 | @Override
69 | public void showTextBadge(String badgeText) {
70 | mBadgeViewHeler.showTextBadge(badgeText);
71 | }
72 |
73 | @Override
74 | public void hiddenBadge() {
75 | mBadgeViewHeler.hiddenBadge();
76 | }
77 |
78 | @Override
79 | public void showDrawableBadge(Bitmap bitmap) {
80 | mBadgeViewHeler.showDrawable(bitmap);
81 | }
82 |
83 | @Override
84 | public void setDragDismissDelegage(DragDismissDelegate delegate) {
85 | mBadgeViewHeler.setDragDismissDelegage(delegate);
86 | }
87 |
88 | @Override
89 | public boolean isShowBadge() {
90 | return mBadgeViewHeler.isShowBadge();
91 | }
92 |
93 | @Override
94 | public BadgeViewHelper getBadgeViewHelper() {
95 | return mBadgeViewHeler;
96 | }
97 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jpeng/demo/Tab1Pager.java:
--------------------------------------------------------------------------------
1 | package com.jpeng.demo;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.Nullable;
5 | import android.support.v4.app.Fragment;
6 | import android.text.Editable;
7 | import android.text.TextWatcher;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.Button;
12 | import android.widget.EditText;
13 | import android.widget.ImageButton;
14 | import com.jpeng.jptabbar.JPTabBar;
15 |
16 | /**
17 | * Created by jpeng on 16-11-14.
18 | */
19 | public class Tab1Pager extends Fragment implements View.OnClickListener, TextWatcher {
20 |
21 | private EditText mNumberEt;
22 | private ImageButton mMinusIb, mPlusIb;
23 | private Button mShowTextBtn,mHideBtn,mShowCircleBtn;
24 | private JPTabBar mTabBar;
25 |
26 |
27 | @Override
28 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
29 | View layout = inflater.inflate(R.layout.tab1, null);
30 | init(layout);
31 | return layout;
32 | }
33 |
34 | private void init(View layout) {
35 | mTabBar = ((MainActivity)getActivity()).getTabbar();
36 | mShowTextBtn = (Button) layout.findViewById(R.id.button1);
37 | mShowCircleBtn = (Button) layout.findViewById(R.id.button2);
38 | mHideBtn = (Button) layout.findViewById(R.id.button3);
39 | mNumberEt = (EditText) layout.findViewById(R.id.et_count);
40 | mMinusIb = (ImageButton) layout.findViewById(R.id.imageButton1);
41 | mPlusIb = (ImageButton) layout.findViewById(R.id.imageButton2);
42 | mShowTextBtn.setOnClickListener(this);
43 | mShowCircleBtn.setOnClickListener(this);
44 | mHideBtn.setOnClickListener(this);
45 | mNumberEt.addTextChangedListener(this);
46 | mPlusIb.setOnClickListener(this);
47 | mMinusIb.setOnClickListener(this);
48 | }
49 |
50 | @Override
51 | public void onClick(View v) {
52 | int count = Integer.parseInt(mNumberEt.getText().toString());
53 | if (v == mMinusIb) {
54 | count--;
55 | mNumberEt.setText(count + "");
56 | } else if(v==mPlusIb) {
57 | count++;
58 | mNumberEt.setText(count + "");
59 | }
60 | else if(v==mShowTextBtn){
61 | mTabBar.showBadge(0,"文字",true);
62 | }
63 | else if(v==mShowCircleBtn){
64 | mTabBar.showCircleBadge(0,true);
65 | }
66 | else{
67 | mTabBar.hideBadge(0);
68 | }
69 | }
70 |
71 | @Override
72 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
73 |
74 | }
75 |
76 | @Override
77 | public void onTextChanged(CharSequence s, int start, int before, int count) {
78 | }
79 |
80 | @Override
81 | public void afterTextChanged(Editable s) {
82 | if(s!=null&&s.toString().equals("0")){
83 | mTabBar.showBadge(0, ""+0,true);
84 | mTabBar.hideBadge(0);
85 | return;
86 | }
87 | if (s.toString().equals("")) {
88 | mTabBar.showBadge(0, ""+0,true);
89 | return;
90 | }
91 | int count = Integer.parseInt(s.toString());
92 | if(mTabBar!=null)
93 | mTabBar.showBadge(0, count+"",true);
94 | }
95 |
96 | public void clearCount() {
97 | //当徽章拖拽爆炸后,一旦View被销毁,不判断就会空指针异常
98 | if(mNumberEt!=null)
99 | mNumberEt.setText("0");
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jpeng/demo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.jpeng.demo;
2 |
3 | import android.os.Bundle;
4 | import android.support.v4.app.Fragment;
5 | import android.support.v4.view.ViewPager;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.view.View;
8 | import android.widget.Toast;
9 | import com.jpeng.jptabbar.BadgeDismissListener;
10 | import com.jpeng.jptabbar.JPTabBar;
11 | import com.jpeng.jptabbar.OnTabSelectListener;
12 | import com.jpeng.jptabbar.anno.NorIcons;
13 | import com.jpeng.jptabbar.anno.SeleIcons;
14 | import com.jpeng.jptabbar.anno.Titles;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | import static com.jpeng.demo.R.id.tabbar;
20 |
21 |
22 | public class MainActivity extends AppCompatActivity implements BadgeDismissListener, OnTabSelectListener{
23 |
24 | @Titles
25 | private static final int[] mTitles = {R.string.tab1,R.string.tab2,R.string.tab3,R.string.tab4};
26 |
27 | @SeleIcons
28 | private static final int[] mSeleIcons = {R.mipmap.tab1_selected,R.mipmap.tab2_selected,R.mipmap.tab3_selected,R.mipmap.tab4_selected};
29 |
30 | @NorIcons
31 | private static final int[] mNormalIcons = {R.mipmap.tab1_normal, R.mipmap.tab2_normal, R.mipmap.tab3_normal, R.mipmap.tab4_normal};
32 |
33 | private List
[](http://android-arsenal.com/details/1/4685)
5 | [  ](https://bintray.com/peng83508440/maven/JPTabBar/_latestVersion)
6 | [](https://github.com/peng8350/JPTabBar/blob/master/LICENSE)
7 |
8 | ###
9 |
10 | # 效果图
11 | 
12 | 
13 |
14 | # 主要功能以及特色:
15 | - [x] 多种Tab切换的动画效果
16 |
17 | - [x] 实现底部导航中间按钮凸出的效果
18 |
19 | - [x] 实现类似Wechat图标渐变,并且带动画
20 |
21 | - [x] 实现TabBar上的红色标记,并且可以拖动
22 |
23 | - [x] 提供监听Tab的点击事件,中间点击以及badge被拖拉消失的接口
24 |
25 | - [x] 引用注解方式,免去自己手动构造TabBarItem
26 |
27 | # 依赖
28 | [BGABadgeView-Android](https://github.com/bingoogolapple/BGABadgeView-Android)
29 |
30 | # 用法:
31 | 1.引入Gradle依赖
32 | ```
33 | repositories {
34 | jcenter()
35 | }
36 |
37 | dependencies{
38 | compile 'com.jpeng:JPTabBar:1.4.0'
39 | }
40 |
41 | ```
42 | 2.添加JPTabBar到你的主界面布局
43 | ```
44 |
45 |
212 | 2.Flip(翻转)动画失效问题,由于华为部分7.0手机不支持setRotationY和setRotationX,
213 | Flip动画正是调用了setRotationY
214 |
215 | # 更新日志
216 | ### V1.1.2
217 | - 给动画添加了弹性
218 | - 修复当pager没有数据时无法点击问题
219 |
220 | ### V1.1.4
221 | - 修复这个BUG当页面数量大于或者小于TabItem数量的问题
222 | - 添加点击ITEM颜色渐变(有selectedicon)的效果
223 | - 增加另一套初始化TabBarItems的方法
224 | - 解决因为drawable共用内存,每次finish后,没有图标的问题。
225 |
226 | ### V1.1.5
227 | - 移除标题必须设置的限制,可以没有标题
228 | - 再次修复徽章位置问题
229 | - 添加了一些调用方法和修改了一些方法命名规范
230 |
231 | ### V1.1.6
232 | - 添加之前忘记的标题渐变效果
233 | - 删除BadgeDraggable结点,用ShowBadge方法设置是否可以拖动
234 | - 添加几个方法,减少TabBar的限制性
235 |
236 | ### V1.1.7
237 | - 修复高度问题,还有移除TabHeight结点(粗心导致)
238 | - 修复这个BUG,当用户每次运行APP图标渐变问题
239 |
240 | ### V1.1.9
241 | - 修正BUG,当setUseScrollAnimate(false),滑动TAB动画未有还原
242 | - 改变滚动动画和渐变默认关闭
243 | - 改变Badge的位置为水平居上方,以适应屏幕适配
244 |
245 | ### V1.2.0
246 | - 修正之前一直没有解决的中间图标点击外面没有响应问题
247 | - 添加一些动态的方法
248 | - 添加两个结点设置中间图标的属性
249 |
250 | ### V1.2.3
251 | - 修改一些存在问题和BUG
252 | - 优化了旋转动画弹性
253 | - 中间TabItem 使用自定义View代替只可以一个图标
254 |
255 | ### V1.2.5
256 | - 添加另外一种方法去获取状态栏的高度
257 | - 修正中间View布局参数的BUG
258 |
259 | ### V1.3.0
260 | - 添加另外一种缩放动画
261 | - 添加一种新的特性:tabitem可以展示动画,当用户按下或者在View外面范围松开
262 | - 调整了一些动画的效果
263 |
264 | ### V1.3.2
265 | - 修正不使用动画时按下奔溃问题
266 | - 修改默认动画为没有动画
267 |
268 | ### V1.3.5
269 | - 新增动态修改结点的方法
270 | - 修正触摸事件逻辑判断问题(换到别的Tab不能滑动徽章)
271 | - 修复在attrs里TabAnimate值的错误
272 | - 暴露TabItem接口,可以直接操作TabItem
273 |
274 | ### V1.4.0
275 | - 添加设置TabItem的字体类型的功能
276 | - 添加onInterruptSelect回调方法来决定是否中断点击事件
277 | - 移除部分作用不大的方法和属性,比如:消息限制格式
278 | - 增加可以控制按住动画效果开关
279 | - 增加TabBar部分方法,比如:修改某个tab标题,图标等
280 | - 重命名setUseScrollAnimate setUseFilter这两个方法名,并且可通过结点设置
281 |
282 | # 关于我
283 | 一名在校大学生,目前还在专研学习各种技术中...
284 | 邮箱:peng8350@gmail.com
285 |
286 | # License
287 | ```
288 | Copyright 2016 JPeng
289 |
290 | Licensed under the Apache License, Version 2.0 (the "License");
291 | you may not use this file except in compliance with the License.
292 | You may obtain a copy of the License at
293 |
294 | http://www.apache.org/licenses/LICENSE-2.0
295 |
296 | Unless required by applicable law or agreed to in writing, software
297 | distributed under the License is distributed on an "AS IS" BASIS,
298 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
299 | See the License for the specific language governing permissions and
300 | limitations under the License.
301 | ```
302 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # :blush:JPTabBar:blush:
2 | [](https://travis-ci.org/peng8350/JPTabBar)
3 | [](https://bintray.com/bintray/jcenter/JPTabBar)
4 |
[](http://android-arsenal.com/details/1/4685)
5 | [ ](https://bintray.com/peng83508440/maven/JPTabBar/_latestVersion)
6 | [](https://github.com/peng8350/JPTabBar/blob/master/LICENSE)
7 |
8 | 阅读中文文档 [请点击这里](https://github.com/peng8350/JPTabBar/blob/master/README_CN.md)
9 |
10 | # ScreenShots:
11 | 
12 | 
13 |
14 | # Main functions and features:
15 | - [x] More Animation effects of multiple Tab switching
16 |
17 | - [x] Implements the effect of the middle button of the bottom navigation
18 |
19 | - [x] Implements like Wechat icon filter and provide animation change.
20 |
21 | - [x] Implements the red mark on the TabBar, and can drag
22 |
23 | - [x] Provide listening to the click event, middle click and badge is dragged away the interface
24 |
25 | - [x] Reference annotation method, construction TabBarItem
26 |
27 | # Dependencies
28 | [BGABadgeView-Android](https://github.com/bingoogolapple/BGABadgeView-Android)
29 |
30 | # Usage:
31 | 1.Introducing Gradle dependency
32 | ```
33 | repositories {
34 | jcenter()
35 | }
36 |
37 | dependencies{
38 | compile 'com.jpeng:JPTabBar:1.4.0'
39 | }
40 |
41 | ```
42 | 2.Add JPTabBar to your main interface layout
43 | ```
44 |
45 |
46 |
217 | 2.Flip animation failure problem, because HUAWEI Part 7 mobile phones do not support
218 | setRotationY and setRotationX,The Flip animation is called setRotationY
219 |
220 |
221 | # Update Log
222 | ### V1.1.2
223 | - Add the Bouncing of the Animation
224 | - Reverse don't click when have no Pager in the Adapter
225 |
226 | ### V1.1.4
227 | - Reverse the Bug of When the Pagers count of ViewPager less than or more than the count of tab
228 | - Add the Color FIlter to the Tab Icon When user Switch Tab
229 | - Add the Another init item method
230 | - solve the drawable in the same memory problem,Every time finish the activity,have no Icon show.
231 |
232 | ### V1.1.5
233 | - Remove the limit of the titles,You can set without titles
234 | - Fix the position of the badge again
235 | - Add some methods and Update some method's name
236 |
237 | ### V1.1.6
238 | - Add the title Filter
239 | - Remove the BadgeDraggable Attribute,and replace with the ShowBadge method
240 | - Add several methods to reduce the TabBar limit
241 |
242 | ### V1.1.7
243 | - Fix the problem of height and remove the TabHeight attribute
244 | - Fix BUG, when the app run gradient problem
245 |
246 | ### V1.1.9
247 | - Fix Bug,When setUseScrollAnimate (false), the slide TAB animation is not restored
248 | - Change the Filter and ScrollAnimate default is not open
249 | - Change the location of the Badge to the top of the level, in order to adapt to the screen adaptation
250 |
251 | ### V1.2.0
252 | - Fix the bug of Touch outside area not responseable in the MiddleIcon Click Event
253 | - Add some dymanic Methods in TabBar
254 | - Add Attributes in MiddleIcon set
255 |
256 | ### V1.2.3
257 | - Fix some problems or bugs.
258 | - Updated RotateAnimater
259 | - Add Custom Middle View to replace only Icon
260 |
261 | ### V1.2.5
262 | - Add another way to get the StatusbarHeight
263 | - Fix the bug of Customview Layoutparams
264 |
265 | ### V1.3.0
266 | - Add another Scale Animation
267 | - Add a new feature that tabbar's icon can display animtion when user pressdown or touchout tabbaritem
268 | - Fix some animations effects
269 |
270 | ### V1.3.2
271 | - Fix a crash when pressing down and not using animation
272 | - Update the default animation is None
273 |
274 | ### V1.3.5
275 | - adding methods of dynamically modified nodes
276 | - Modify the logical judgment of touch events (switch to other Tab ineminable badges)
277 | - Fix the error value in TabAnimate in attrs.xml
278 | - Exposing the TabItem interface, you can manipulate TabItem directly
279 |
280 | ### V1.4.0
281 | - Add the function of the font type for setting up TabItem
282 | - Add the onInterruptSelect callback method to determine whether or not the click event is interrupted
283 | - Remove methods and attributes that have little partial effect, such as message limit
284 | - Add the control to press down the animation effect switch
285 | - Add some TabBar methods, such as modifying a tab title, icons, and so on
286 | - Rename the two method names of setUseScrollAnimate setUseFilter and can be set by nodes
287 |
288 | # About Me
289 | A college student, is still in the study of various techniques...
290 | E-mail:peng8350@gmail.com
291 |
292 | # License
293 | ```
294 | Copyright 2016 JPeng
295 |
296 | Licensed under the Apache License, Version 2.0 (the "License");
297 | you may not use this file except in compliance with the License.
298 | You may obtain a copy of the License at
299 |
300 | http://www.apache.org/licenses/LICENSE-2.0
301 |
302 | Unless required by applicable law or agreed to in writing, software
303 | distributed under the License is distributed on an "AS IS" BASIS,
304 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
305 | See the License for the specific language governing permissions and
306 | limitations under the License.
307 | ```
308 |
309 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jpeng/jptabbar/badgeview/BadgeViewHelper.java:
--------------------------------------------------------------------------------
1 | package com.jpeng.jptabbar.badgeview;
2 |
3 | import android.content.Context;
4 | import android.graphics.*;
5 | import android.text.TextUtils;
6 | import android.util.AttributeSet;
7 | import android.view.MotionEvent;
8 | import android.view.View;
9 | import com.jpeng.jptabbar.DensityUtils;
10 |
11 | /**
12 | * Author jpeng
13 | * Date: 16-11-16
14 | * E-mail:peng8350@gmail.com
15 | */
16 | public class BadgeViewHelper {
17 | private Bitmap mBitmap;
18 | private Badgeable mBadgeable;
19 | private Paint mBadgePaint;
20 | /**
21 | * 徽章背景色
22 | */
23 | private int mBadgeBgColor;
24 | /**
25 | * 徽章文本的颜色
26 | */
27 | private int mBadgeTextColor;
28 | /**
29 | * 徽章文本字体大小
30 | */
31 | private int mBadgeTextSize;
32 | /**
33 | * 徽章背景与宿主控件上下边缘间距离
34 | */
35 | private int mBadgeVerticalMargin;
36 | /**
37 | * 徽章背景与宿主控件左右边缘间距离
38 | */
39 | private int mBadgeHorizontalMargin;
40 | /***
41 | * 徽章文本边缘与徽章背景边缘间的距离
42 | */
43 | private int mBadgePadding;
44 | /**
45 | * 徽章文本
46 | */
47 | private String mBadgeText;
48 | /**
49 | * 徽章文本所占区域大小
50 | */
51 | private Rect mBadgeNumberRect;
52 | /**
53 | * 是否显示Badge
54 | */
55 | private boolean mIsShowBadge;
56 | /**
57 | * 徽章在宿主控件中的位置
58 | */
59 | private BadgeGravity mBadgeGravity;
60 | /**
61 | * 整个徽章所占区域
62 | */
63 | private RectF mBadgeRectF;
64 | /**
65 | * 是否可拖动
66 | */
67 | private boolean mDragable;
68 | /**
69 | * 拖拽徽章超出轨迹范围后,再次放回到轨迹范围时,是否恢复轨迹
70 | */
71 | private boolean mIsResumeTravel;
72 | /***
73 | * 徽章描边宽度
74 | */
75 | private int mBadgeBorderWidth;
76 | /***
77 | * 徽章描边颜色
78 | */
79 | private int mBadgeBorderColor;
80 | /**
81 | * 触发开始拖拽徽章事件的扩展触摸距离
82 | */
83 | private int mDragExtra;
84 | /**
85 | * 整个徽章加上其触发开始拖拽区域所占区域
86 | */
87 | private RectF mBadgeDragExtraRectF;
88 | /**
89 | * 拖动时的徽章控件
90 | */
91 | private DragBadgeView mDropBadgeView;
92 | /**
93 | * 是否正在拖动
94 | */
95 | private boolean mIsDraging;
96 | /**
97 | * 拖动大于BGABadgeViewHelper.mMoveHiddenThreshold后抬起手指徽章消失的代理
98 | */
99 | private DragDismissDelegate mDelegage;
100 | private boolean mIsShowDrawable = false;
101 |
102 | public BadgeViewHelper(Badgeable badgeable, Context context, AttributeSet attrs, BadgeGravity defaultBadgeGravity) {
103 | mBadgeable = badgeable;
104 | initDefaultAttrs(context, defaultBadgeGravity);
105 | afterInitDefaultAndCustomAttrs();
106 | mDropBadgeView = new DragBadgeView(context, this);
107 | }
108 |
109 | private void initDefaultAttrs(Context context, BadgeGravity defaultBadgeGravity) {
110 | mBadgeNumberRect = new Rect();
111 | mBadgeRectF = new RectF();
112 | mBadgeBgColor = Color.RED;
113 | mBadgeTextColor = Color.WHITE;
114 | mBadgeTextSize = DensityUtils.sp2px(context, 10);
115 |
116 | mBadgePaint = new Paint();
117 | mBadgePaint.setAntiAlias(true);
118 | mBadgePaint.setStyle(Paint.Style.FILL);
119 | // 设置mBadgeText居中,保证mBadgeText长度为1时,文本也能居中
120 | mBadgePaint.setTextAlign(Paint.Align.CENTER);
121 |
122 | mBadgePadding = DensityUtils.dp2px(context, 4);
123 | mBadgeVerticalMargin = DensityUtils.dp2px(context, 0);
124 |
125 | mBadgeHorizontalMargin = DensityUtils.dp2px(context, 0);
126 |
127 | mBadgeGravity = defaultBadgeGravity;
128 | mIsShowBadge = false;
129 |
130 | mBadgeText = null;
131 |
132 | mBitmap = null;
133 |
134 | mIsDraging = false;
135 |
136 | mDragable = false;
137 |
138 | mBadgeBorderColor = Color.WHITE;
139 |
140 | mDragExtra = DensityUtils.dp2px(context, 4);
141 | mBadgeDragExtraRectF = new RectF();
142 | }
143 |
144 | private void afterInitDefaultAndCustomAttrs() {
145 | mBadgePaint.setTextSize(mBadgeTextSize);
146 | }
147 |
148 | public void setBadgeBgColorInt(int badgeBgColor) {
149 | mBadgeBgColor = badgeBgColor;
150 | mBadgeable.postInvalidate();
151 | }
152 |
153 | public void setBadgeTextColorInt(int badgeTextColor) {
154 | mBadgeTextColor = badgeTextColor;
155 | mBadgeable.postInvalidate();
156 | }
157 |
158 | public void setBadgeTextSizeSp(int badgetextSize) {
159 | if (badgetextSize >= 0) {
160 | mBadgeTextSize = DensityUtils.sp2px(mBadgeable.getContext(), badgetextSize);
161 | mBadgePaint.setTextSize(mBadgeTextSize);
162 | mBadgeable.postInvalidate();
163 | }
164 | }
165 |
166 | public void setBadgeVerticalMarginDp(int badgeVerticalMargin) {
167 | if (badgeVerticalMargin >= 0) {
168 | mBadgeVerticalMargin = DensityUtils.dp2px(mBadgeable.getContext(), badgeVerticalMargin);
169 | mBadgeable.postInvalidate();
170 | }
171 | }
172 |
173 | public void setBadgeHorizontalMarginDp(int badgeHorizontalMargin) {
174 | mBadgeHorizontalMargin = DensityUtils.dp2px(mBadgeable.getContext(), badgeHorizontalMargin);
175 | mBadgeable.postInvalidate();
176 | }
177 |
178 | public void setBadgePaddingDp(int badgePadding) {
179 | if (badgePadding >= 0) {
180 | mBadgePadding = DensityUtils.dp2px(mBadgeable.getContext(), badgePadding);
181 | mBadgeable.postInvalidate();
182 | }
183 | }
184 |
185 | public void setBadgeGravity(BadgeGravity badgeGravity) {
186 | if (badgeGravity != null) {
187 | mBadgeGravity = badgeGravity;
188 | mBadgeable.postInvalidate();
189 | }
190 | }
191 |
192 | public void setDragable(boolean dragable) {
193 | mDragable = dragable;
194 | mBadgeable.postInvalidate();
195 | }
196 |
197 | public void setIsResumeTravel(boolean isResumeTravel) {
198 | mIsResumeTravel = isResumeTravel;
199 | mBadgeable.postInvalidate();
200 | }
201 |
202 | public void setBadgeBorderWidthDp(int badgeBorderWidthDp) {
203 | if (badgeBorderWidthDp >= 0) {
204 | mBadgeBorderWidth = DensityUtils.dp2px(mBadgeable.getContext(), badgeBorderWidthDp);
205 | mBadgeable.postInvalidate();
206 | }
207 | }
208 |
209 | public void setBadgeBorderColorInt(int badgeBorderColor) {
210 | mBadgeBorderColor = badgeBorderColor;
211 | mBadgeable.postInvalidate();
212 | }
213 |
214 | public boolean checkDragging(MotionEvent event){
215 | mBadgeDragExtraRectF.left = mBadgeRectF.left - mDragExtra;
216 | mBadgeDragExtraRectF.top = mBadgeRectF.top - mDragExtra;
217 | mBadgeDragExtraRectF.right = mBadgeRectF.right + mDragExtra;
218 | mBadgeDragExtraRectF.bottom = mBadgeRectF.bottom + mDragExtra;
219 | return (mBadgeBorderWidth == 0 || mIsShowDrawable) && mDragable && mIsShowBadge && mBadgeDragExtraRectF.contains(event.getX(), event.getY());
220 | }
221 |
222 | public boolean onTouchEvent(MotionEvent event) {
223 | switch (event.getAction()) {
224 | case MotionEvent.ACTION_DOWN:
225 |
226 |
227 | if (checkDragging(event)) {
228 | mIsDraging = true;
229 | mBadgeable.getParent().requestDisallowInterceptTouchEvent(true);
230 |
231 | Rect badgeableRect = new Rect();
232 | mBadgeable.getGlobalVisibleRect(badgeableRect);
233 | mDropBadgeView.setStickCenter(badgeableRect.left + mBadgeRectF.left + mBadgeRectF.width() / 2, badgeableRect.top + mBadgeRectF.top + mBadgeRectF.height() / 2);
234 |
235 | mDropBadgeView.onTouchEvent(event);
236 | mBadgeable.postInvalidate();
237 | return true;
238 | }
239 | break;
240 | case MotionEvent.ACTION_MOVE:
241 | if (mIsDraging) {
242 | mDropBadgeView.onTouchEvent(event);
243 | return true;
244 | }
245 | break;
246 | case MotionEvent.ACTION_UP:
247 | case MotionEvent.ACTION_CANCEL:
248 | if (mIsDraging) {
249 | mDropBadgeView.onTouchEvent(event);
250 | mIsDraging = false;
251 | return true;
252 | }
253 | break;
254 | default:
255 | break;
256 | }
257 | return mBadgeable.callSuperOnTouchEvent(event);
258 | }
259 |
260 | public void endDragWithDismiss() {
261 | hiddenBadge();
262 | if (mDelegage != null) {
263 | mDelegage.onDismiss(mBadgeable);
264 | }
265 | }
266 |
267 | public void endDragWithoutDismiss() {
268 | mBadgeable.postInvalidate();
269 | }
270 |
271 | public void drawBadge(Canvas canvas) {
272 | if (mIsShowBadge && !mIsDraging) {
273 | if (mIsShowDrawable) {
274 | drawDrawableBadge(canvas);
275 | } else {
276 | drawTextBadge(canvas);
277 | }
278 | }
279 | }
280 |
281 | /**
282 | * 绘制图像徽章
283 | *
284 | * @param canvas
285 | */
286 | private void drawDrawableBadge(Canvas canvas) {
287 | mBadgeRectF.left = mBadgeable.getWidth() - mBadgeHorizontalMargin - mBitmap.getWidth();
288 | mBadgeRectF.top = mBadgeVerticalMargin;
289 | switch (mBadgeGravity) {
290 | case RightTop:
291 | mBadgeRectF.top = mBadgeVerticalMargin;
292 | break;
293 | case RightCenter:
294 | mBadgeRectF.top = (mBadgeable.getHeight() - mBitmap.getHeight()) / 2;
295 | break;
296 | case RightBottom:
297 | mBadgeRectF.top = mBadgeable.getHeight() - mBitmap.getHeight() - mBadgeVerticalMargin;
298 | break;
299 | default:
300 | break;
301 | }
302 | canvas.drawBitmap(mBitmap, mBadgeRectF.left, mBadgeRectF.top, mBadgePaint);
303 | mBadgeRectF.right = mBadgeRectF.left + mBitmap.getWidth();
304 | mBadgeRectF.bottom = mBadgeRectF.top + mBitmap.getHeight();
305 | }
306 |
307 | /**
308 | * 绘制文字徽章
309 | *
310 | * @param canvas
311 | */
312 | private void drawTextBadge(Canvas canvas) {
313 | String badgeText = "";
314 | if (!TextUtils.isEmpty(mBadgeText)) {
315 | badgeText = mBadgeText;
316 | }
317 | // 获取文本宽所占宽高
318 | mBadgePaint.getTextBounds(badgeText, 0, badgeText.length(), mBadgeNumberRect);
319 | // 计算徽章背景的宽高
320 | int badgeHeight = mBadgeNumberRect.height() + mBadgePadding * 2;
321 | int badgeWidth;
322 | // 当mBadgeText的长度为1或0时,计算出来的高度会比宽度大,此时设置宽度等于高度
323 | if (badgeText.length() == 1 || badgeText.length() == 0) {
324 | badgeWidth = badgeHeight;
325 | } else {
326 | badgeWidth = mBadgeNumberRect.width() + mBadgePadding * 2;
327 | }
328 |
329 | // 计算徽章背景上下的值
330 | mBadgeRectF.top = mBadgeVerticalMargin;
331 | mBadgeRectF.bottom = mBadgeRectF.top + badgeHeight;
332 |
333 | // 计算徽章背景左右的值
334 | mBadgeRectF.right = mBadgeable.getWidth() / 2 + mBadgeHorizontalMargin;
335 | mBadgeRectF.left = mBadgeRectF.right - badgeWidth;
336 |
337 | if (mBadgeBorderWidth > 0) {
338 | // 设置徽章边框景色
339 | mBadgePaint.setColor(mBadgeBorderColor);
340 | // 绘制徽章边框背景
341 | canvas.drawRoundRect(mBadgeRectF, badgeHeight / 2, badgeHeight / 2, mBadgePaint);
342 |
343 | // 设置徽章背景色
344 | mBadgePaint.setColor(mBadgeBgColor);
345 | // 绘制徽章背景
346 | canvas.drawRoundRect(new RectF(mBadgeRectF.left + mBadgeBorderWidth, mBadgeRectF.top + mBadgeBorderWidth, mBadgeRectF.right - mBadgeBorderWidth, mBadgeRectF.bottom - mBadgeBorderWidth), (badgeHeight - 2 * mBadgeBorderWidth) / 2, (badgeHeight - 2 * mBadgeBorderWidth) / 2, mBadgePaint);
347 | } else {
348 | // 设置徽章背景色
349 | mBadgePaint.setColor(mBadgeBgColor);
350 | // 绘制徽章背景
351 | canvas.drawRoundRect(mBadgeRectF, badgeHeight / 2, badgeHeight / 2, mBadgePaint);
352 | }
353 |
354 |
355 | if (!TextUtils.isEmpty(mBadgeText)) {
356 | // 设置徽章文本颜色
357 | mBadgePaint.setColor(mBadgeTextColor);
358 | // initDefaultAttrs方法中设置了mBadgeText居中,此处的x为徽章背景的中心点y
359 | float x = mBadgeRectF.left + badgeWidth / 2;
360 | // 注意:绘制文本时的y是指文本底部,而不是文本的中间
361 | float y = mBadgeRectF.bottom - mBadgePadding;
362 | // 绘制徽章文本
363 | canvas.drawText(badgeText, x, y, mBadgePaint);
364 | }
365 | }
366 |
367 | public void showCirclePointBadge() {
368 | showTextBadge(null);
369 | }
370 |
371 | public void showTextBadge(String badgeText) {
372 | mIsShowDrawable = false;
373 | mBadgeText = badgeText;
374 | mIsShowBadge = true;
375 | mBadgeable.postInvalidate();
376 | }
377 |
378 | public void hiddenBadge() {
379 | mIsShowBadge = false;
380 | mBadgeable.postInvalidate();
381 | }
382 |
383 | public boolean isShowBadge() {
384 | return mIsShowBadge;
385 | }
386 |
387 | public void showDrawable(Bitmap bitmap) {
388 | mBitmap = bitmap;
389 | mIsShowDrawable = true;
390 | mIsShowBadge = true;
391 | mBadgeable.postInvalidate();
392 | }
393 |
394 | public boolean isShowDrawable() {
395 | return mIsShowDrawable;
396 | }
397 |
398 | public RectF getBadgeRectF() {
399 | return mBadgeRectF;
400 | }
401 |
402 | public int getBadgePadding() {
403 | return mBadgePadding;
404 | }
405 |
406 | public String getBadgeText() {
407 | return mBadgeText;
408 | }
409 |
410 | public int getBadgeBgColor() {
411 | return mBadgeBgColor;
412 | }
413 |
414 | public int getBadgeTextColor() {
415 | return mBadgeTextColor;
416 | }
417 |
418 | public int getBadgeTextSize() {
419 | return mBadgeTextSize;
420 | }
421 |
422 | public Bitmap getBitmap() {
423 | return mBitmap;
424 | }
425 |
426 | public void setDragDismissDelegage(DragDismissDelegate delegage) {
427 | mDelegage = delegage;
428 | }
429 |
430 | public View getRootView() {
431 | return mBadgeable.getRootView();
432 | }
433 |
434 | public boolean isResumeTravel() {
435 | return mIsResumeTravel;
436 | }
437 |
438 | public enum BadgeGravity {
439 | RightTop,
440 | RightCenter,
441 | RightBottom
442 | }
443 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/jpeng/jptabbar/JPTabItem.java:
--------------------------------------------------------------------------------
1 | package com.jpeng.jptabbar;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Paint;
6 | import android.graphics.Rect;
7 | import android.graphics.Typeface;
8 | import android.graphics.drawable.Drawable;
9 | import android.graphics.drawable.LayerDrawable;
10 | import android.support.annotation.DrawableRes;
11 | import android.view.ViewGroup;
12 | import android.widget.ImageView;
13 | import android.widget.LinearLayout;
14 | import android.widget.RelativeLayout;
15 | import com.jpeng.jptabbar.animate.Animatable;
16 | import com.jpeng.jptabbar.badgeview.BadgeRelativeLayout;
17 | import com.jpeng.jptabbar.badgeview.Badgeable;
18 | import com.jpeng.jptabbar.badgeview.DragDismissDelegate;
19 | import com.nineoldandroids.animation.ObjectAnimator;
20 |
21 | /**
22 | * Author jpeng
23 | * Date: 16-11-13
24 | * E-mail:peng8350@gmail.com
25 | * TabBarItem类
26 | */
27 | public class JPTabItem extends BadgeRelativeLayout {
28 | //颜色渐变时间
29 | private static final int FILTER_DURATION = 10;
30 | private Context mContext;
31 | private String mTitle;
32 | private int mIndex;
33 | // 对应图标的大小
34 | private int mIconSize;
35 | // Tab的上下边距
36 | private int mMargin;
37 | //选中颜色(包括底部文字和图标)
38 | private int mSelectColor;
39 | //没有选中的颜色(包括底部文字和图标)
40 | private int mNormalColor;
41 | // Tab字体大小
42 | private int mTextSize;
43 | // Tab字体类型
44 | private Typeface mTypeFace;
45 | //允许图标颜色随着字体颜色变化而变化
46 | private boolean mAcceptFilter;
47 | // Badge的字体大小
48 | private int mBadgeTextSize;
49 | // 徽章垂直Margin
50 | private int mBadgeVerMargin;
51 | // 徽章水平Margin
52 | private int mBadgeHorMargin;
53 | // Badge的背景颜色
54 | private int mBadgeBackground;
55 | private int mOffset;
56 | // 是否被选中
57 | private boolean mSelected;
58 | // badge的间距
59 | private int mBadgePadding;
60 | // Tab的没有选中图标
61 | private Drawable mNormalIcon;
62 | // Tab选中的图标
63 | private Drawable mSelectIcon;
64 | // 选中的颜色
65 | private Drawable mSelectBg;
66 | // Tab字体的画笔
67 | private Paint mTextPaint;
68 | // 图标的图层
69 | private LayerDrawable mCompundIcon;
70 | //图标ImageView
71 | private ImageView mIconView;
72 | // 动画对象
73 | private Animatable mAnimater;
74 | // 徽章被用户拖出去的回调事件
75 | private BadgeDismissListener mDismissListener;
76 |
77 | public JPTabItem(Context context) {
78 | super(context);
79 | }
80 |
81 | /**
82 | * 初始化布局和数据
83 | */
84 | private void init(Context context) {
85 | mContext = context;
86 | LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
87 | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
88 | params.weight = 3;
89 | setLayoutParams(params);
90 | initPaint();
91 | initImageView();
92 | setBackgroundResource(android.R.color.transparent);
93 | }
94 |
95 | /**
96 | * 初始化徽章
97 | */
98 | private void initBadge() {
99 | getBadgeViewHelper().setBadgeBgColorInt(mBadgeBackground);
100 | getBadgeViewHelper().setBadgeTextSizeSp(mBadgeTextSize);
101 | getBadgeViewHelper().setBadgePaddingDp(mBadgePadding);
102 | getBadgeViewHelper().setBadgeVerticalMarginDp(mBadgeVerMargin);
103 | getBadgeViewHelper().setBadgeHorizontalMarginDp(mBadgeHorMargin);
104 | getBadgeViewHelper().setDragDismissDelegage(new DragDismissDelegate() {
105 | @Override
106 | public void onDismiss(Badgeable badgeable) {
107 | if (mDismissListener != null)
108 | mDismissListener.onDismiss(mIndex);
109 | }
110 | });
111 | }
112 |
113 | /**
114 | * 初始化所有画笔
115 | */
116 | private void initPaint() {
117 | mTextPaint = new Paint();
118 | mTextPaint.setAntiAlias(true);
119 | mTextPaint.setTextAlign(Paint.Align.CENTER);
120 | mTextPaint.setTextSize(DensityUtils.sp2px(mContext, mTextSize));
121 | mTextPaint.setTypeface(mTypeFace);
122 | }
123 |
124 | /**
125 | * 初始化图标ImageView
126 | */
127 | private void initImageView() {
128 | mIconView = new ImageView(mContext);
129 | RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
130 | mIconSize, mIconSize);
131 | params.addRule(mTitle == null ? RelativeLayout.CENTER_IN_PARENT : RelativeLayout.CENTER_HORIZONTAL);
132 | if (mTitle != null)
133 | params.topMargin = mMargin;
134 | mIconView.setScaleType(ImageView.ScaleType.FIT_XY);
135 | mIconView.setLayoutParams(params);
136 | //添加进去主布局
137 | addView(mIconView);
138 | updateIcon();
139 | //初始化BadgeView设置回调和属性
140 | initBadge();
141 | }
142 |
143 | @Override
144 | protected void onDraw(Canvas canvas) {
145 | if (mTitle != null)
146 | DrawText(canvas);
147 | }
148 |
149 | /*
150 | * 更新IconView的图标
151 | */
152 | public void updateIcon() {
153 | if (mSelectIcon == null) {
154 | mIconView.setImageDrawable(mNormalIcon);
155 | } else {
156 | mCompundIcon = new LayerDrawable(new Drawable[]{mNormalIcon, mSelectIcon});
157 | mNormalIcon.setAlpha(255);
158 | mSelectIcon.setAlpha(0);
159 | mIconView.setImageDrawable(mCompundIcon);
160 | }
161 | }
162 |
163 | /**
164 | * 画底部文字
165 | */
166 | private void DrawText(Canvas canvas) {
167 | Rect textBound = new Rect();
168 | mTextPaint.getTextBounds(mTitle, 0, mTitle.length(), textBound);
169 | Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
170 | float x = getMeasuredWidth() / 2f;
171 | float y = getTextY(textBound, fontMetrics);
172 | mTextPaint.setColor(mNormalColor);
173 | mTextPaint.setAlpha(255 - mOffset);
174 | canvas.drawText(mTitle, x, y, mTextPaint);
175 | mTextPaint.setColor(mSelectColor);
176 | mTextPaint.setAlpha(mOffset);
177 | canvas.drawText(mTitle, x, y, mTextPaint);
178 | }
179 |
180 | /**
181 | * 得到字体的X轴坐标
182 | */
183 | private float getTextY(Rect textBound, Paint.FontMetrics fontMetrics) {
184 | return (getMeasuredHeight() - mMargin - textBound.height() / 2f - fontMetrics.descent + (fontMetrics.descent - fontMetrics.ascent) / 2);
185 | }
186 |
187 | Animatable getAnimater() {
188 | return mAnimater;
189 | }
190 |
191 | void setAnimater(Animatable animater) {
192 | mAnimater = animater;
193 | }
194 |
195 | /**
196 | * 给Item设置代理
197 | */
198 | void setDismissDelegate(BadgeDismissListener listener) {
199 | this.mDismissListener = listener;
200 | }
201 |
202 | void setNormalColor(int color) {
203 | this.mNormalColor = color;
204 | }
205 |
206 | void setSelectedColor(int color) {
207 | this.mSelectColor = color;
208 | }
209 |
210 | void setTextSize(int size) {
211 | this.mTextSize = size;
212 | mTextPaint.setTextSize(mTextSize);
213 | }
214 |
215 | void setTypeFace(Typeface typeFace) {
216 |
217 | mTextPaint.setTypeface(typeFace);
218 |
219 | postInvalidate();
220 |
221 | mTypeFace = typeFace;
222 | }
223 |
224 | /**
225 | * 设置TabItem选中的状态
226 | */
227 | void setSelect(boolean selected, boolean animated) {
228 | setSelect(selected, animated, true);
229 | }
230 |
231 | void setSelect(boolean selected, boolean animated, boolean filter) {
232 | if (selected && mSelectBg != null) {
233 | setBackgroundDrawable(mSelectBg);
234 | } else {
235 | setBackgroundResource(android.R.color.transparent);
236 | }
237 | if (mSelected != selected) {
238 | mSelected = selected;
239 | if (mCompundIcon != null) {
240 | if (selected) {
241 | if (!animated || mAnimater == null || !filter) {
242 | changeAlpha(1f);
243 | } else {
244 | ObjectAnimator.ofInt(mSelectIcon, "alpha", 0, 255).setDuration(FILTER_DURATION).start();
245 | ObjectAnimator.ofInt(mNormalIcon, "alpha", 255, 0).setDuration(FILTER_DURATION).start();
246 | }
247 | } else {
248 | if (!animated || mAnimater == null || !filter) {
249 | changeAlpha(0f);
250 | } else {
251 | ObjectAnimator.ofInt(mNormalIcon, "alpha", 0, 255).setDuration(FILTER_DURATION).start();
252 | ObjectAnimator.ofInt(mSelectIcon, "alpha", 255, 0).setDuration(FILTER_DURATION).start();
253 | }
254 | }
255 | } else {
256 | changeColorIfneed(selected);
257 | }
258 | //播放动画
259 | if (animated) {
260 | if (mAnimater != null) {
261 | mAnimater.onSelectChanged(mIconView, mSelected);
262 | }
263 | }
264 | if (mSelected)
265 | mOffset = 255;
266 | else
267 | mOffset = 0;
268 | postInvalidate();
269 | }
270 | }
271 |
272 |
273 | /**
274 | * 假如开发者没有提供selected Icon的时候,改变图标颜色
275 | * 而且要接受过滤
276 | */
277 | private void changeColorIfneed(boolean selected) {
278 | if (mAcceptFilter && mSelectIcon == null
279 | ) {
280 | if (selected) {
281 | mIconView.setColorFilter(mSelectColor);
282 | } else {
283 | mIconView.setColorFilter(mNormalColor);
284 | }
285 | }
286 | }
287 |
288 | /**
289 | * 这个方法用来改变图标的颜色
290 | * 在滑动Viewpager回调onScroll方法
291 | * 传入1时,selectedicon将会显示
292 | * 传入0时,NormalIcon将会显示
293 | */
294 | void changeAlpha(float offset) {
295 | if (mCompundIcon != null) {
296 | mNormalIcon.setAlpha((int) (255 * (1 - offset)));
297 | mSelectIcon.setAlpha((int) (offset * 255));
298 | this.mOffset = (int) (offset * 255);
299 | postInvalidate();
300 | }
301 | }
302 |
303 | public void setTitle(String title) {
304 | this.mTitle = title;
305 | postInvalidate();
306 | }
307 |
308 | public String getTitle() {
309 | return mTitle;
310 | }
311 |
312 | public String getBadgeStr() {
313 | return getBadgeViewHelper().getBadgeText();
314 | }
315 |
316 | public void setNormalIcon(int normalIcon) {
317 | mNormalIcon = getContext().getResources().getDrawable(normalIcon).mutate();
318 | updateIcon();
319 | }
320 |
321 | public void setSelectIcon(int selectIcon) {
322 | mSelectIcon = getContext().getResources().getDrawable(selectIcon).mutate();
323 | updateIcon();
324 | }
325 |
326 |
327 | public boolean isBadgeShow() {
328 | return isShowBadge();
329 | }
330 |
331 | public ImageView getIconView() {
332 | return mIconView;
333 | }
334 |
335 | public boolean isSelect() {
336 | return mSelected;
337 | }
338 |
339 | static class Builder {
340 | private int iconSize;
341 | private int margin;
342 | private int selectColor;
343 | private int normalColor;
344 | private int textSize;
345 | private int normalIcon;
346 | private int selectIcon;
347 | private int badgeBackground;
348 | private int badgeVerMargin;
349 | private int badgeHorMargin;
350 | private int badgeTextSize;
351 | private int badgepadding;
352 | private Drawable selectbg;
353 | private String title;
354 | private Context context;
355 | private String typeFacepath;
356 | private int index;
357 | private boolean iconfilter;
358 | private Animatable animater;
359 |
360 | Builder(Context context) {
361 | this.context = context;
362 | }
363 |
364 | Builder setAnimater(Animatable animater) {
365 | this.animater = animater;
366 | return this;
367 | }
368 |
369 | Builder setTypeFacePath(String typeFacepath) {
370 | this.typeFacepath = typeFacepath;
371 | return this;
372 | }
373 |
374 | Builder setIconSize(int size) {
375 | this.iconSize = size;
376 | return this;
377 | }
378 |
379 | Builder setIndex(int index) {
380 | this.index = index;
381 | return this;
382 | }
383 |
384 | Builder setNormalColor(int normalColor) {
385 | this.normalColor = normalColor;
386 | return this;
387 | }
388 |
389 | Builder setSelectedColor(int selectColor) {
390 | this.selectColor = selectColor;
391 | return this;
392 | }
393 |
394 | Builder setMargin(int margin) {
395 | this.margin = margin;
396 | return this;
397 | }
398 |
399 | Builder setIconFilte(boolean acceptFilte) {
400 | this.iconfilter = acceptFilte;
401 | return this;
402 | }
403 |
404 | Builder setBadgeVerMargin(int margin) {
405 | this.badgeVerMargin = margin;
406 | return this;
407 | }
408 |
409 | Builder setBadgeHorMargin(int margin) {
410 | this.badgeHorMargin = margin;
411 | return this;
412 | }
413 |
414 | Builder setBadgeTextSize(int size) {
415 | this.badgeTextSize = size;
416 | return this;
417 | }
418 |
419 | Builder setBadgeColor(int color) {
420 | this.badgeBackground = color;
421 | return this;
422 | }
423 |
424 | Builder setSelectBg(Drawable res) {
425 | this.selectbg = res;
426 | return this;
427 | }
428 |
429 | Builder setBadgePadding(int padding) {
430 | this.badgepadding = padding;
431 | return this;
432 | }
433 |
434 | Builder setNormalIcon(@DrawableRes int icon) {
435 | this.normalIcon = icon;
436 | return this;
437 | }
438 |
439 | Builder setSelectIcon(@DrawableRes int icon) {
440 | this.selectIcon = icon;
441 | return this;
442 | }
443 |
444 | Builder setTextSize(int size) {
445 | this.textSize = size;
446 | return this;
447 | }
448 |
449 | Builder setTitle(String title) {
450 | this.title = title;
451 | return this;
452 | }
453 |
454 | JPTabItem build() {
455 | JPTabItem item = new JPTabItem(context);
456 | item.mTextSize = textSize
457 | ;
458 | item.mTitle = title;
459 | item.mNormalColor = normalColor;
460 | item.mSelectColor = selectColor;
461 | item.mBadgeTextSize = badgeTextSize;
462 | item.mNormalIcon = context.getResources().getDrawable(normalIcon).mutate();
463 | if (selectIcon != 0)
464 | item.mSelectIcon = context.getResources().getDrawable(selectIcon).mutate();
465 | item.mBadgePadding = badgepadding;
466 | item.mBadgeBackground = badgeBackground;
467 | item.mIndex = index;
468 | item.mBadgeHorMargin = badgeHorMargin;
469 | item.mBadgeVerMargin = badgeVerMargin;
470 | item.mIconSize = iconSize;
471 | item.mMargin = margin;
472 | item.mAcceptFilter = iconfilter;
473 | item.mSelectBg = selectbg;
474 | item.mAnimater = animater;
475 | if (typeFacepath != null)
476 | item.mTypeFace = Typeface.createFromAsset(context.getAssets(), typeFacepath);
477 | item.init(context);
478 | return item;
479 | }
480 |
481 | }
482 | }
483 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jpeng/jptabbar/badgeview/DragBadgeView.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015 bingoogolapple
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jpeng.jptabbar.badgeview;
18 |
19 | import android.content.Context;
20 | import android.graphics.*;
21 | import android.view.Gravity;
22 | import android.view.MotionEvent;
23 | import android.view.View;
24 | import android.view.WindowManager;
25 | import android.view.animation.OvershootInterpolator;
26 | import com.jpeng.jptabbar.DensityUtils;
27 | import com.nineoldandroids.animation.Animator;
28 | import com.nineoldandroids.animation.AnimatorListenerAdapter;
29 | import com.nineoldandroids.animation.ValueAnimator;
30 |
31 | import java.lang.ref.WeakReference;
32 |
33 |
34 | /**
35 | * Author jpeng
36 | * Date: 16-11-15
37 | * E-mail:peng8350@gmail.com
38 | */
39 | class DragBadgeView extends View {
40 | private static final String TAG = DragBadgeView.class.getSimpleName();
41 | private BadgeViewHelper mBadgeViewHelper;
42 | private Paint mBadgePaint;
43 | private WindowManager mWindowManager;
44 | private WindowManager.LayoutParams mLayoutParams;
45 | private int mStartX;
46 | private int mStartY;
47 | private ExplosionAnimator mExplosionAnimator;
48 | private SetExplosionAnimatorNullTask mSetExplosionAnimatorNullTask;
49 |
50 | /**
51 | * 针圆切线的切点
52 | */
53 | private PointF[] mStickPoints = new PointF[]{
54 | new PointF(0, 0),
55 | new PointF(0, 0)
56 | };
57 | /**
58 | * 拖拽圆切线的切点
59 | */
60 | private PointF[] mDragPoints = new PointF[]{
61 | new PointF(0, 0),
62 | new PointF(0, 0)
63 | };
64 | /**
65 | * 控制点
66 | */
67 | private PointF mControlPoint = new PointF(0, 0);
68 | /**
69 | * 拖拽圆中心点
70 | */
71 | private PointF mDragCenter = new PointF(0, 0);
72 | /**
73 | * 拖拽圆半径
74 | */
75 | private float mDragRadius;
76 |
77 | /**
78 | * 针圆中心点
79 | */
80 | private PointF mStickCenter;
81 | /**
82 | * 针圆半径
83 | */
84 | private float mStickRadius;
85 | /**
86 | * 拖拽圆最大半径
87 | */
88 | private int mMaxDragRadius;
89 | /**
90 | * 拖拽圆半径和针圆半径的差值
91 | */
92 | private int mDragStickRadiusDifference;
93 | /**
94 | * 拖动mDismissThreshold距离后抬起手指徽章消失
95 | */
96 | private int mDismissThreshold;
97 |
98 | private boolean mDismissAble;
99 | private boolean mIsDragDisappear;
100 |
101 | public DragBadgeView(Context context, BadgeViewHelper badgeViewHelper) {
102 | super(context);
103 | mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
104 | mBadgeViewHelper = badgeViewHelper;
105 | initBadgePaint();
106 | initLayoutParams();
107 | initStick();
108 |
109 | mSetExplosionAnimatorNullTask = new SetExplosionAnimatorNullTask(this);
110 | }
111 |
112 | private void initBadgePaint() {
113 | mBadgePaint = new Paint();
114 | mBadgePaint.setAntiAlias(true);
115 | mBadgePaint.setStyle(Paint.Style.FILL);
116 | // 设置mBadgeText居中,保证mBadgeText长度为1时,文本也能居中
117 | mBadgePaint.setTextAlign(Paint.Align.CENTER);
118 | mBadgePaint.setTextSize(mBadgeViewHelper.getBadgeTextSize());
119 | }
120 |
121 | private void initLayoutParams() {
122 | mLayoutParams = new WindowManager.LayoutParams();
123 | mLayoutParams.gravity = Gravity.LEFT + Gravity.TOP;
124 | mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
125 | mLayoutParams.format = PixelFormat.TRANSLUCENT;
126 | mLayoutParams.type = WindowManager.LayoutParams.TYPE_TOAST;
127 | mLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
128 | mLayoutParams.height = WindowManager.LayoutParams.MATCH_PARENT;
129 | }
130 |
131 | private void initStick() {
132 | mMaxDragRadius = DensityUtils.dp2px(getContext(), 11);
133 | mDragStickRadiusDifference = DensityUtils.dp2px(getContext(), 1);
134 | }
135 |
136 | @Override
137 | protected void onDraw(Canvas canvas) {
138 | try {
139 | if (mExplosionAnimator == null) {
140 | if (mBadgeViewHelper.isShowDrawable()) {
141 | if (mBadgeViewHelper.getBadgeBgColor() == Color.RED) {
142 | mBadgePaint.setColor(mBadgeViewHelper.getBitmap().getPixel(mBadgeViewHelper.getBitmap().getWidth() / 2, mBadgeViewHelper.getBitmap().getHeight() / 2));
143 | } else {
144 | mBadgePaint.setColor(mBadgeViewHelper.getBadgeBgColor());
145 | }
146 | drawStick(canvas);
147 | drawDrawableBadge(canvas);
148 | } else {
149 | mBadgePaint.setColor(mBadgeViewHelper.getBadgeBgColor());
150 | drawStick(canvas);
151 | drawTextBadge(canvas);
152 | }
153 | } else {
154 | mExplosionAnimator.draw(canvas);
155 | }
156 | } catch (Exception e) {
157 | // 确保自己能被移除
158 | removeSelfWithException();
159 | }
160 | }
161 |
162 | private void drawDrawableBadge(Canvas canvas) {
163 | canvas.drawBitmap(mBadgeViewHelper.getBitmap(), mStartX, mStartY, mBadgePaint);
164 | }
165 |
166 | private void drawTextBadge(Canvas canvas) {
167 | // 绘制徽章文本
168 | String badgeText = mBadgeViewHelper.getBadgeText() == null ? "" : mBadgeViewHelper.getBadgeText();
169 | // 设置徽章背景色
170 | mBadgePaint.setColor(mBadgeViewHelper.getBadgeBgColor());
171 | // 绘制徽章背景
172 | canvas.drawRoundRect(new RectF(mStartX, mStartY, mStartX + mBadgeViewHelper.getBadgeRectF().width(), mStartY + mBadgeViewHelper.getBadgeRectF().height()), mBadgeViewHelper.getBadgeRectF().height() / 2, mBadgeViewHelper.getBadgeRectF().height() / 2, mBadgePaint);
173 | // 设置徽章文本颜色
174 | mBadgePaint.setColor(mBadgeViewHelper.getBadgeTextColor());
175 | float x = mStartX + mBadgeViewHelper.getBadgeRectF().width() / 2;
176 | // 注意:绘制文本时的y是指文本底部,而不是文本的中间
177 | float y = mStartY + mBadgeViewHelper.getBadgeRectF().height() - mBadgeViewHelper.getBadgePadding();
178 |
179 | canvas.drawText(badgeText, x, y, mBadgePaint);
180 | }
181 |
182 | private void drawStick(Canvas canvas) {
183 | float currentStickRadius = getCurrentStickRadius();
184 |
185 | // 2. 获取直线与圆的交点
186 | float yOffset = mStickCenter.y - mDragCenter.y;
187 | float xOffset = mStickCenter.x - mDragCenter.x;
188 | Double lineK = null;
189 | if (xOffset != 0) {
190 | lineK = (double) (yOffset / xOffset);
191 | }
192 | // 通过几何图形工具获取交点坐标
193 | mDragPoints = BadgeViewUtil.getIntersectionPoints(mDragCenter, mDragRadius, lineK);
194 | mStickPoints = BadgeViewUtil.getIntersectionPoints(mStickCenter, currentStickRadius, lineK);
195 |
196 | // 3. 获取控制点坐标
197 | mControlPoint = BadgeViewUtil.getMiddlePoint(mDragCenter, mStickCenter);
198 |
199 | // 保存画布状态
200 | canvas.save();
201 | canvas.translate(0, -BadgeViewUtil.getStatusBarHeight(mBadgeViewHelper.getRootView()));
202 |
203 | if (!mIsDragDisappear) {
204 | if (!mDismissAble) {
205 |
206 | // 3. 画连接部分
207 | Path path = new Path();
208 | // 跳到点1
209 | path.moveTo(mStickPoints[0].x, mStickPoints[0].y);
210 | // 画曲线1 -> 2
211 | path.quadTo(mControlPoint.x, mControlPoint.y, mDragPoints[0].x, mDragPoints[0].y);
212 | // 画直线2 -> 3
213 | path.lineTo(mDragPoints[1].x, mDragPoints[1].y);
214 | // 画曲线3 -> 4
215 | path.quadTo(mControlPoint.x, mControlPoint.y, mStickPoints[1].x, mStickPoints[1].y);
216 | path.close();
217 | canvas.drawPath(path, mBadgePaint);
218 |
219 | // 2. 画固定圆
220 | canvas.drawCircle(mStickCenter.x, mStickCenter.y, currentStickRadius, mBadgePaint);
221 | }
222 |
223 | // 1. 画拖拽圆
224 | canvas.drawCircle(mDragCenter.x, mDragCenter.y, mDragRadius, mBadgePaint);
225 | }
226 |
227 | // 恢复上次的保存状态
228 | canvas.restore();
229 | }
230 |
231 | /**
232 | * 获取针圆实时半径
233 | *
234 | * @return
235 | */
236 | private float getCurrentStickRadius() {
237 | /**
238 | * distance 0 -> mDismissThreshold
239 | * percent 0.0f -> 1.0f
240 | * currentStickRadius mStickRadius * 100% -> mStickRadius * 20%
241 | */
242 | float distance = BadgeViewUtil.getDistanceBetween2Points(mDragCenter, mStickCenter);
243 | distance = Math.min(distance, mDismissThreshold);
244 | float percent = distance / mDismissThreshold;
245 | return BadgeViewUtil.evaluate(percent, mStickRadius, mStickRadius * 0.2f);
246 | }
247 |
248 | public void setStickCenter(float x, float y) {
249 | mStickCenter = new PointF(x, y);
250 | }
251 |
252 | @Override
253 | public boolean onTouchEvent(MotionEvent event) {
254 | try {
255 | switch (event.getAction()) {
256 | case MotionEvent.ACTION_DOWN:
257 | handleActionDown(event);
258 | break;
259 | case MotionEvent.ACTION_MOVE:
260 | handleActionMove(event);
261 | break;
262 | case MotionEvent.ACTION_UP:
263 | case MotionEvent.ACTION_CANCEL:
264 | handleActionUp(event);
265 | break;
266 | }
267 | } catch (Exception e) {
268 | // 确保自己能被移除
269 | removeSelfWithException();
270 | }
271 | return true;
272 | }
273 |
274 | private void handleActionDown(MotionEvent event) {
275 | if (mExplosionAnimator == null && getParent() == null) {
276 | mDragRadius = Math.min(mBadgeViewHelper.getBadgeRectF().width() / 2, mMaxDragRadius);
277 | mStickRadius = mDragRadius - mDragStickRadiusDifference;
278 | mDismissThreshold = (int) (mStickRadius * 10);
279 |
280 | mDismissAble = false;
281 | mIsDragDisappear = false;
282 |
283 | mWindowManager.addView(this, mLayoutParams);
284 |
285 | updateDragPosition(event.getRawX(), event.getRawY());
286 | }
287 | }
288 |
289 | private void handleActionMove(MotionEvent event) {
290 | if (mExplosionAnimator == null && getParent() != null) {
291 | updateDragPosition(event.getRawX(), event.getRawY());
292 |
293 | // 处理断开事件
294 | if (BadgeViewUtil.getDistanceBetween2Points(mDragCenter, mStickCenter) > mDismissThreshold) {
295 | mDismissAble = true;
296 | postInvalidate();
297 | } else if (mBadgeViewHelper.isResumeTravel()) {
298 | mDismissAble = false;
299 | postInvalidate();
300 | }
301 | }
302 | }
303 |
304 | private void handleActionUp(MotionEvent event) {
305 | handleActionMove(event);
306 |
307 | if (mDismissAble) {
308 | // 拖拽点超出过范围
309 | if (BadgeViewUtil.getDistanceBetween2Points(mDragCenter, mStickCenter) > mDismissThreshold) {
310 | // 现在也超出范围,消失
311 | try {
312 | mIsDragDisappear = true;
313 | startDismissAnim(getNewStartX(event.getRawX()), getNewStartY(event.getRawY()));
314 | } catch (Exception e) {
315 | removeSelf();
316 | mBadgeViewHelper.endDragWithDismiss();
317 | }
318 | } else {
319 | // 现在没有超出范围,放回去
320 | removeSelf();
321 | mBadgeViewHelper.endDragWithoutDismiss();
322 | }
323 | } else {
324 | // 拖拽点没超出过范围,弹回去
325 | try {
326 | startSpringAnim();
327 | } catch (Exception e) {
328 | removeSelf();
329 | mBadgeViewHelper.endDragWithoutDismiss();
330 | }
331 | }
332 | }
333 |
334 | private void startSpringAnim() {
335 | final PointF startReleaseDragCenter = new PointF(mDragCenter.x, mDragCenter.y);
336 | ValueAnimator springAnim = ValueAnimator.ofFloat(1.0f);
337 | springAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
338 |
339 | @Override
340 | public void onAnimationUpdate(ValueAnimator mAnim) {
341 | // 0.0 -> 1.0f
342 | float percent = mAnim.getAnimatedFraction();
343 | PointF p = BadgeViewUtil.getPointByPercent(startReleaseDragCenter, mStickCenter, percent);
344 | updateDragPosition(p.x, p.y);
345 | }
346 | });
347 | springAnim.addListener(new AnimatorListenerAdapter() {
348 | @Override
349 | public void onAnimationEnd(Animator animation) {
350 | removeSelf();
351 | mBadgeViewHelper.endDragWithoutDismiss();
352 | }
353 |
354 | @Override
355 | public void onAnimationCancel(Animator animation) {
356 | removeSelf();
357 | mBadgeViewHelper.endDragWithoutDismiss();
358 | }
359 | });
360 |
361 | springAnim.setInterpolator(new OvershootInterpolator(4));
362 | springAnim.setRepeatCount(1);
363 | springAnim.setRepeatMode(ValueAnimator.INFINITE);
364 | springAnim.setDuration(ExplosionAnimator.ANIM_DURATION / 2);
365 | springAnim.start();
366 | }
367 |
368 | private void startDismissAnim(int newX, int newY) {
369 | int badgeWidth = (int) mBadgeViewHelper.getBadgeRectF().width();
370 | int badgeHeight = (int) mBadgeViewHelper.getBadgeRectF().height();
371 | Rect rect = new Rect(newX - badgeWidth / 2, newY - badgeHeight / 2, newX + badgeWidth / 2, newY + badgeHeight / 2);
372 |
373 | Bitmap badgeBitmap = BadgeViewUtil.createBitmapSafely(this, rect, 1);
374 | if (badgeBitmap == null) {
375 | removeSelf();
376 | mBadgeViewHelper.endDragWithDismiss();
377 | return;
378 | }
379 |
380 | if (mExplosionAnimator != null) {
381 | removeSelf();
382 | mBadgeViewHelper.endDragWithDismiss();
383 | return;
384 | }
385 |
386 | mExplosionAnimator = new ExplosionAnimator(this, rect, badgeBitmap);
387 | mExplosionAnimator.addListener(new AnimatorListenerAdapter() {
388 | @Override
389 | public void onAnimationEnd(Animator animation) {
390 | removeSelf();
391 | mBadgeViewHelper.endDragWithDismiss();
392 | }
393 |
394 | @Override
395 | public void onAnimationCancel(Animator animation) {
396 | removeSelf();
397 | mBadgeViewHelper.endDragWithDismiss();
398 | }
399 | });
400 | mExplosionAnimator.start();
401 | }
402 |
403 | private void removeSelf() {
404 | if (getParent() != null) {
405 | mWindowManager.removeView(this);
406 | }
407 | mDismissAble = false;
408 | mIsDragDisappear = false;
409 |
410 | // 处理有时候爆炸效果结束后出现一瞬间的拖拽效果
411 | postDelayed(mSetExplosionAnimatorNullTask, 60);
412 | }
413 |
414 | /**
415 | * 修改拖拽位置
416 | *
417 | * @param rawX
418 | * @param rawY
419 | */
420 | private void updateDragPosition(float rawX, float rawY) {
421 | mStartX = getNewStartX(rawX);
422 | mStartY = getNewStartY(rawY);
423 |
424 | mDragCenter.set(rawX, rawY);
425 | postInvalidate();
426 | }
427 |
428 | private int getNewStartX(float rawX) {
429 | int badgeWidth = (int) mBadgeViewHelper.getBadgeRectF().width();
430 | int newX = (int) rawX - badgeWidth / 2;
431 | if (newX < 0) {
432 | newX = 0;
433 | }
434 | if (newX > mWindowManager.getDefaultDisplay().getWidth() - badgeWidth) {
435 | newX = mWindowManager.getDefaultDisplay().getWidth() - badgeWidth;
436 | }
437 | return newX;
438 | }
439 |
440 | private int getNewStartY(float rawY) {
441 | int badgeHeight = (int) mBadgeViewHelper.getBadgeRectF().height();
442 | int maxNewY = getHeight() - badgeHeight;
443 | int newStartY = (int) rawY - badgeHeight / 2 - BadgeViewUtil.getStatusBarHeight(mBadgeViewHelper.getRootView());
444 | return Math.min(Math.max(0, newStartY), maxNewY);
445 | }
446 |
447 | private void removeSelfWithException() {
448 | removeSelf();
449 | if (BadgeViewUtil.getDistanceBetween2Points(mDragCenter, mStickCenter) > mDismissThreshold) {
450 | mBadgeViewHelper.endDragWithDismiss();
451 | } else {
452 | mBadgeViewHelper.endDragWithoutDismiss();
453 | }
454 | }
455 |
456 | private static class SetExplosionAnimatorNullTask implements Runnable {
457 | private final WeakReference