items) {
24 | mValues = items;
25 | }
26 |
27 | @Override
28 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
29 | View view = LayoutInflater.from(parent.getContext())
30 | .inflate(R.layout.fragment_item, parent, false);
31 | return new ViewHolder(view);
32 | }
33 |
34 | @Override
35 | public void onBindViewHolder(final ViewHolder holder, int position) {
36 | holder.mItem = mValues.get(position);
37 | holder.mIdView.setText(mValues.get(position).id);
38 | holder.mContentView.setText(mValues.get(position).content);
39 | }
40 |
41 | @Override
42 | public int getItemCount() {
43 | return mValues.size();
44 | }
45 |
46 | public class ViewHolder extends RecyclerView.ViewHolder {
47 | public final View mView;
48 | public final TextView mIdView;
49 | public final TextView mContentView;
50 | public DummyItem mItem;
51 |
52 | public ViewHolder(View view) {
53 | super(view);
54 | mView = view;
55 | mIdView = (TextView) view.findViewById(R.id.id);
56 | mContentView = (TextView) view.findViewById(R.id.content);
57 | }
58 |
59 | @Override
60 | public String toString() {
61 | return super.toString() + " '" + mContentView.getText() + "'";
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/me/stefan/easybehavior/fragment/dummy/DummyContent.java:
--------------------------------------------------------------------------------
1 | package me.stefan.easybehavior.fragment.dummy;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.List;
6 | import java.util.Map;
7 |
8 | /**
9 | * Helper class for providing sample content for user interfaces created by
10 | * Android template wizards.
11 | *
12 | * TODO: Replace all uses of this class before publishing your app.
13 | */
14 | public class DummyContent {
15 |
16 | /**
17 | * An array of sample (dummy) items.
18 | */
19 | public static final List ITEMS = new ArrayList();
20 |
21 | /**
22 | * A map of sample (dummy) items, by ID.
23 | */
24 | public static final Map ITEM_MAP = new HashMap();
25 |
26 | private static final int COUNT = 25;
27 |
28 | static {
29 | // Add some sample items.
30 | for (int i = 1; i <= COUNT; i++) {
31 | addItem(createDummyItem(i));
32 | }
33 | }
34 |
35 | private static void addItem(DummyItem item) {
36 | ITEMS.add(item);
37 | ITEM_MAP.put(item.id, item);
38 | }
39 |
40 | private static DummyItem createDummyItem(int position) {
41 | return new DummyItem(String.valueOf(position), "Item " + position, makeDetails(position));
42 | }
43 |
44 | private static String makeDetails(int position) {
45 | StringBuilder builder = new StringBuilder();
46 | builder.append("Details about Item: ").append(position);
47 | for (int i = 0; i < position; i++) {
48 | builder.append("\nMore details information here.");
49 | }
50 | return builder.toString();
51 | }
52 |
53 | /**
54 | * A dummy item representing a piece of content.
55 | */
56 | public static class DummyItem {
57 | public final String id;
58 | public final String content;
59 | public final String details;
60 |
61 | public DummyItem(String id, String content, String details) {
62 | this.id = id;
63 | this.content = content;
64 | this.details = details;
65 | }
66 |
67 | @Override
68 | public String toString() {
69 | return content;
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_uc_head_title.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
22 |
23 |
34 |
35 |
36 |
46 |
47 |
56 |
57 |
66 |
67 |
--------------------------------------------------------------------------------
/app/src/main/java/me/stefan/easybehavior/demo1/behavior/CircleImageInUsercBehavior.java:
--------------------------------------------------------------------------------
1 | package me.stefan.easybehavior.demo1.behavior;
2 |
3 | import android.content.Context;
4 | import android.support.design.widget.CoordinatorLayout;
5 | import android.support.v4.view.ViewCompat;
6 | import android.support.v7.widget.Toolbar;
7 | import android.util.AttributeSet;
8 | import android.view.View;
9 |
10 | import me.stefan.easybehavior.widget.CircleImageView;
11 | import me.stefan.easybehavior.widget.DisInterceptNestedScrollView;
12 |
13 |
14 | /**
15 | * @author stefan 邮箱:648701906@qq.com
16 | *
17 | * @describe 在app中,这个类用于头像的行为 具体使用在Strings.xml中的引用
18 | */
19 | public class CircleImageInUsercBehavior extends CoordinatorLayout.Behavior {
20 |
21 | private final String TAG_TOOLBAR = "toolbar";
22 |
23 | private float mStartAvatarY;
24 |
25 | private float mStartAvatarX;
26 |
27 | private int mAvatarMaxHeight;
28 |
29 | private int mToolBarHeight;
30 |
31 | private float mStartDependencyY;
32 |
33 | public CircleImageInUsercBehavior(Context context, AttributeSet attrs) {
34 | super(context, attrs);
35 | }
36 |
37 |
38 | @Override
39 | public boolean layoutDependsOn(CoordinatorLayout parent, CircleImageView child, View dependency) {
40 | return dependency instanceof DisInterceptNestedScrollView;
41 | }
42 |
43 |
44 | //当dependency变化的时候调用
45 | @Override
46 | public boolean onDependentViewChanged(CoordinatorLayout parent, CircleImageView child, View dependency) {
47 | //初始化一些基础参数
48 | init(parent, child, dependency);
49 | //计算比例
50 | if (child.getY() <= 0) return false;
51 | float percent = (child.getY() - mToolBarHeight) / (mStartAvatarY - mToolBarHeight);
52 |
53 | if (percent < 0) {
54 | percent = 0;
55 | }
56 | if (this.percent == percent || percent > 1) return true;
57 | this.percent = percent;
58 | //设置头像的大小
59 | ViewCompat.setScaleX(child, percent);
60 | ViewCompat.setScaleY(child, percent);
61 |
62 | return false;
63 | }
64 |
65 | /**
66 | * 初始化数据
67 | * @param parent
68 | * @param child
69 | * @param dependency
70 | */
71 | private void init(CoordinatorLayout parent, CircleImageView child, View dependency) {
72 | if (mStartAvatarY == 0) {
73 | mStartAvatarY = child.getY();
74 | }
75 | if (mStartDependencyY == 0) {
76 | mStartDependencyY = dependency.getY();
77 | }
78 | if (mStartAvatarX == 0) {
79 | mStartAvatarX = child.getX();
80 | }
81 |
82 | if (mAvatarMaxHeight == 0) {
83 | mAvatarMaxHeight = child.getHeight();
84 | }
85 | if (mToolBarHeight == 0) {
86 | Toolbar toolbar = (Toolbar) parent.findViewWithTag(TAG_TOOLBAR);
87 | mToolBarHeight = toolbar.getHeight();
88 | }
89 | }
90 |
91 | float percent = 0;
92 | }
93 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_demo1.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
27 |
28 |
29 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_uc_content.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
14 |
15 |
20 |
21 |
22 |
41 |
42 |
46 |
47 |
51 |
52 |
60 |
61 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
77 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_uc_head_middle.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
16 |
17 |
24 |
25 |
33 |
34 |
41 |
42 |
50 |
51 |
62 |
63 |
64 |
65 |
66 |
67 |
71 |
72 |
83 |
84 |
85 |
86 |
87 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_func_in_uc.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
19 |
20 |
30 |
31 |
41 |
42 |
52 |
53 |
63 |
64 |
65 |
66 |
72 |
73 |
83 |
84 |
94 |
95 |
105 |
106 |
114 |
115 |
116 |
117 |
121 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # EasyBehavior
2 | - 如果你正苦于实现一个酷炫的个人信息页面效果
3 | - 如果产品要求你实现下拉放大背景图,上滑能看到详细信息
4 | - 如果还要求一系列同步动画效果
5 | - 通过Behavior实现它将是你的不二选择,本项目旨在帮助各位轻松实现自己的Behavior!
6 | ----------
7 | ### 注意:
8 | - demo2已在master分支中移除,需要的话请前往backupv1分支,切换版本到低于26
9 | - androidx适配版本已发布,请拉取androidx分支
10 | ----------
11 | ## 博文地址
12 | [例子1 链接](http://blog.csdn.net/gjm15881133824/article/details/73742219)
13 | [例子2 链接](http://blog.csdn.net/gjm15881133824/article/details/74946322)
14 |
15 | ----------
16 | ## DEMO下载
17 | https://fir.im/ckh1
18 | ----------
19 | ## 效果图
20 | 
21 | 
22 | ----------
23 | ## 例子的实现
24 | 注意:以下内容可能引起您的轻度不适(xing fen),请慎重阅读,例子中呢,用到了两个Behavior。
25 | 1.用户头像的放大以及缩小,按照上面的方法,我们可以很明白的知道实现步骤了
26 |
27 |
28 | - 继承
29 |
30 | ```
31 | public class CircleImageInUsercBehavior extends CoordinatorLayout.Behavior {
32 | ```
33 | - 重写onDependentViewChanged,
34 | ```
35 | //当dependency变化的时候调用
36 | @Override
37 | public boolean onDependentViewChanged(CoordinatorLayout parent, CircleImageView child, View dependency) {
38 | //初始化一些基础参数
39 | init(parent, child, dependency);
40 | //计算比例
41 | ...
42 | //设置头像的大小
43 | ViewCompat.setScaleX(child, percent);
44 | ViewCompat.setScaleY(child, percent);
45 | return false;
46 | }
47 |
48 | ```
49 | 啊?这样就搞定了?是的!就是这么easy!!
50 | 
51 | **那我有一个问题了,是不是说每一个view想要做跟随动画,都得创建一个相应的Behavior呢?答案很明显是NO~!**
52 | 看完下一个例子你就会明白了
53 |
54 | ----------
55 |
56 | 2.这个Behavior用途主要有以下3点:
57 |
58 | - 控制背景图的放大以及回弹
59 | - 中间middle部分跟随背景图的放大缩小做相应的移动
60 | - Toolbar的背景Alpha的改变
61 |
62 | 第一步:初始化参数,通过tag查找每一个View,这里需要注意,我们需要在布局文件中,每个相应的View都需要声明相同的tag 如 `android:tag="你的tag"`,当然,也可以用原始的findViewById,这里只是希望id改动时,我们的Behavior可以不受到影响
63 |
64 | ```
65 | @Override
66 | public boolean onLayoutChild(CoordinatorLayout parent, AppBarLayout abl, int layoutDirection) {
67 | ...
68 | if (mToolBar == null) {
69 | mToolBar = (Toolbar) parent.findViewWithTag(TAG_TOOLBAR);
70 | }
71 | ...
72 | abl.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
73 | ...//实现Toolbar的背景变化
74 | });
75 | ...
76 | }
77 |
78 | ```
79 | 第二步:开始scale动画(下拉上划滑动过程中)
80 |
81 | ```
82 | @Override
83 | public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) {
84 | if (!isRecovering) {//未在回弹动画中,开始我们的变化动画
85 | if (...) {
86 | scale(child, target, dy);
87 | return;
88 | }
89 | }
90 | super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
91 |
92 | @Override
93 | public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY) {
94 | if (velocityY > 100) {//当y速度>100,就秒弹回
95 | isAnimate = false;
96 | }
97 | return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
98 | }
99 | }
100 | ```
101 | 第三步:松手的回弹
102 |
103 | ```
104 | @Override
105 | public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl, View target) {
106 | recovery(abl);//回弹,这个方法详细请看源码
107 | super.onStopNestedScroll(coordinatorLayout, abl, target);
108 | }
109 | ```
110 |
111 | ok,步骤就是这样,是不是很easy呢?
112 |
113 |
114 | ----------
115 |
116 |
117 | 附:AppBarLayout的跟随动画,不仅仅是上面的一种方式
118 | 我们也可以在逻辑代码中通过原生的Listener来实现
119 |
120 | ```
121 | mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
122 | @Override
123 | public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
124 | //计算进度百分比
125 | float percent = Float.valueOf(Math.abs(verticalOffset)) / Float.valueOf(appBarLayout.getTotalScrollRange());
126 | ...//根据百分比做你想做的
127 | }
128 | });
129 | ```
130 | ## License
131 | --------
132 | ```
133 | Copyright (C) 2017 JmStefanAndroid
134 |
135 | Licensed under the Apache License, Version 2.0 (the "License");
136 | you may not use this file except in compliance with the License.
137 | You may obtain a copy of the License at
138 |
139 | http://www.apache.org/licenses/LICENSE-2.0
140 |
141 | Unless required by applicable law or agreed to in writing, software
142 | distributed under the License is distributed on an "AS IS" BASIS,
143 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
144 | See the License for the specific language governing permissions and
145 | limitations under the License.
146 | ```
147 |
--------------------------------------------------------------------------------
/tabLayoutLibrary/src/main/java/com/flyco/tablayout/utils/UnreadMsgUtils.java:
--------------------------------------------------------------------------------
1 | package com.flyco.tablayout.utils;
2 |
3 |
4 | import android.util.DisplayMetrics;
5 | import android.view.View;
6 | import android.widget.FrameLayout;
7 | import android.widget.LinearLayout;
8 | import android.widget.RelativeLayout;
9 |
10 | import com.flyco.tablayout.widget.MsgView;
11 |
12 | /**
13 | * 未读消息提示View,显示小红点或者带有数字的红点:
14 | * 数字一位,圆
15 | * 数字两位,圆角矩形,圆角是高度的一半
16 | * 数字超过两位,显示99+
17 | */
18 | public class UnreadMsgUtils {
19 | public static void show(MsgView msgView, int num) {
20 | if (msgView == null) {
21 | return;
22 | }
23 | RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) msgView.getLayoutParams();
24 | DisplayMetrics dm = msgView.getResources().getDisplayMetrics();
25 | msgView.setVisibility(View.VISIBLE);
26 | if (num <= 0) {//圆点,设置默认宽高
27 | msgView.setStrokeWidth(0);
28 | msgView.setText("");
29 |
30 | lp.width = (int) (8 * dm.density);
31 | lp.height = (int) (8 * dm.density);
32 | msgView.setLayoutParams(lp);
33 | } else {
34 | lp.height = (int) (18 * dm.density);
35 | if (num > 0 && num < 10) {//圆
36 | lp.width = (int) (18 * dm.density);
37 | msgView.setText(num + "");
38 | } else if (num > 9 && num < 100) {//圆角矩形,圆角是高度的一半,设置默认padding
39 | lp.width = RelativeLayout.LayoutParams.WRAP_CONTENT;
40 | msgView.setPadding((int) (6 * dm.density), 0, (int) (6 * dm.density), 0);
41 | msgView.setText(num + "");
42 | } else {//数字超过两位,显示99+
43 | lp.width = RelativeLayout.LayoutParams.WRAP_CONTENT;
44 | msgView.setPadding((int) (6 * dm.density), 0, (int) (6 * dm.density), 0);
45 | msgView.setText("99+");
46 | }
47 | msgView.setLayoutParams(lp);
48 | }
49 | }
50 |
51 | public static void showRight(MsgView msgView, int num) {
52 | if (msgView == null) {
53 | return;
54 | }
55 | RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) msgView.getLayoutParams();
56 | DisplayMetrics dm = msgView.getResources().getDisplayMetrics();
57 | msgView.setVisibility(View.VISIBLE);
58 | if (num <= 0) {//圆点,设置默认宽高
59 | msgView.setStrokeWidth(0);
60 | msgView.setText("");
61 | lp.width = (int) (8 * dm.density);
62 | lp.height = (int) (8 * dm.density);
63 | msgView.setLayoutParams(lp);
64 | msgView.setVisibility(View.INVISIBLE);
65 | } else {
66 | lp.height = (int) (18 * dm.density);
67 | if (num > 0 && num < 10) {//圆
68 | lp.width = (int) (18 * dm.density);
69 | msgView.setPadding(0, 0, 0, 0);
70 | msgView.setText(num + "");
71 | } else if (num > 9 && num < 100) {//圆角矩形,圆角是高度的一半,设置默认padding
72 | lp.width = LinearLayout.LayoutParams.WRAP_CONTENT;
73 | msgView.setPadding((int) (6 * dm.density), -1, (int) (6 * dm.density), 0);
74 | msgView.setText(num + "");
75 | } else {//数字超过两位,显示99+
76 | lp.width = LinearLayout.LayoutParams.WRAP_CONTENT;
77 | msgView.setPadding((int) (6 * dm.density), 0, (int) (6 * dm.density), 0);
78 | msgView.setText("99+");
79 | }
80 | msgView.setLayoutParams(lp);
81 |
82 | }
83 | }
84 | public static void setSize(MsgView rtv, int size) {
85 | if (rtv == null) {
86 | return;
87 | }
88 | RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) rtv.getLayoutParams();
89 | lp.width = size;
90 | lp.height = size;
91 | rtv.setLayoutParams(lp);
92 | }
93 |
94 | public static void showPoint(MsgView msgView, int num) {
95 | if (msgView == null) {
96 | return;
97 | }
98 | FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) msgView.getLayoutParams();
99 | DisplayMetrics dm = msgView.getResources().getDisplayMetrics();
100 | msgView.setVisibility(View.VISIBLE);
101 | if (num <= 0) {//圆点,设置默认宽高
102 | msgView.setStrokeWidth(0);
103 | msgView.setText("");
104 | lp.width = (int) (8 * dm.density);
105 | lp.height = (int) (8 * dm.density);
106 | msgView.setLayoutParams(lp);
107 | msgView.setVisibility(View.GONE);
108 | } else {
109 | lp.height = (int) (18 * dm.density);
110 | if (num > 0 && num < 10) {//圆
111 | lp.width = (int) (18 * dm.density);
112 | msgView.setPadding(0, 0, 0, 0);
113 | msgView.setText(num + "");
114 | } else if (num > 9 && num < 100) {//圆角矩形,圆角是高度的一半,设置默认padding
115 | lp.width = LinearLayout.LayoutParams.WRAP_CONTENT;
116 | msgView.setPadding((int) (6 * dm.density), -1, (int) (6 * dm.density), 0);
117 | msgView.setText(num + "");
118 | } else {//数字超过两位,显示99+
119 | lp.width = LinearLayout.LayoutParams.WRAP_CONTENT;
120 | msgView.setPadding((int) (6 * dm.density), 0, (int) (6 * dm.density), 0);
121 | msgView.setText("99+");
122 | }
123 | msgView.setLayoutParams(lp);
124 |
125 | }
126 | }
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/tabLayoutLibrary/src/main/java/com/flyco/tablayout/widget/MsgView.java:
--------------------------------------------------------------------------------
1 | package com.flyco.tablayout.widget;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Color;
6 | import android.graphics.drawable.GradientDrawable;
7 | import android.graphics.drawable.StateListDrawable;
8 | import android.os.Build;
9 | import android.util.AttributeSet;
10 | import android.widget.TextView;
11 |
12 | import com.flyco.tablayout.R;
13 |
14 | /** 用于需要圆角矩形框背景的TextView的情况,减少直接使用TextView时引入的shape资源文件 */
15 | public class MsgView extends TextView {
16 | private Context context;
17 | private GradientDrawable gd_background = new GradientDrawable();
18 | private int backgroundColor;
19 | private int cornerRadius;
20 | private int strokeWidth;
21 | private int strokeColor;
22 | private boolean isRadiusHalfHeight;
23 | private boolean isWidthHeightEqual;
24 |
25 | public MsgView(Context context) {
26 | this(context, null);
27 | }
28 |
29 | public MsgView(Context context, AttributeSet attrs) {
30 | this(context, attrs, 0);
31 | }
32 |
33 | public MsgView(Context context, AttributeSet attrs, int defStyleAttr) {
34 | super(context, attrs, defStyleAttr);
35 | this.context = context;
36 | obtainAttributes(context, attrs);
37 | }
38 |
39 |
40 | private void obtainAttributes(Context context, AttributeSet attrs) {
41 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MsgView);
42 | backgroundColor = ta.getColor(R.styleable.MsgView_mv_backgroundColor, Color.TRANSPARENT);
43 | cornerRadius = ta.getDimensionPixelSize(R.styleable.MsgView_mv_cornerRadius, 0);
44 | strokeWidth = ta.getDimensionPixelSize(R.styleable.MsgView_mv_strokeWidth, 0);
45 | strokeColor = ta.getColor(R.styleable.MsgView_mv_strokeColor, Color.TRANSPARENT);
46 | isRadiusHalfHeight = ta.getBoolean(R.styleable.MsgView_mv_isRadiusHalfHeight, false);
47 | isWidthHeightEqual = ta.getBoolean(R.styleable.MsgView_mv_isWidthHeightEqual, false);
48 |
49 | ta.recycle();
50 | }
51 |
52 | @Override
53 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
54 | if (isWidthHeightEqual() && getWidth() > 0 && getHeight() > 0) {
55 | int max = Math.max(getWidth(), getHeight());
56 | int measureSpec = MeasureSpec.makeMeasureSpec(max, MeasureSpec.EXACTLY);
57 | super.onMeasure(measureSpec, measureSpec);
58 | return;
59 | }
60 |
61 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
62 | }
63 |
64 | @Override
65 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
66 | super.onLayout(changed, left, top, right, bottom);
67 | if (isRadiusHalfHeight()) {
68 | setCornerRadius(getHeight() / 2);
69 | } else {
70 | setBgSelector();
71 | }
72 | }
73 |
74 |
75 | public void setBackgroundColor(int backgroundColor) {
76 | this.backgroundColor = backgroundColor;
77 | setBgSelector();
78 | }
79 |
80 | public void setCornerRadius(int cornerRadius) {
81 | this.cornerRadius = dp2px(cornerRadius);
82 | setBgSelector();
83 | }
84 |
85 | public void setStrokeWidth(int strokeWidth) {
86 | this.strokeWidth = dp2px(strokeWidth);
87 | setBgSelector();
88 | }
89 |
90 | public void setStrokeColor(int strokeColor) {
91 | this.strokeColor = strokeColor;
92 | setBgSelector();
93 | }
94 |
95 | public void setIsRadiusHalfHeight(boolean isRadiusHalfHeight) {
96 | this.isRadiusHalfHeight = isRadiusHalfHeight;
97 | setBgSelector();
98 | }
99 |
100 | public void setIsWidthHeightEqual(boolean isWidthHeightEqual) {
101 | this.isWidthHeightEqual = isWidthHeightEqual;
102 | setBgSelector();
103 | }
104 |
105 | public int getBackgroundColor() {
106 | return backgroundColor;
107 | }
108 |
109 | public int getCornerRadius() {
110 | return cornerRadius;
111 | }
112 |
113 | public int getStrokeWidth() {
114 | return strokeWidth;
115 | }
116 |
117 | public int getStrokeColor() {
118 | return strokeColor;
119 | }
120 |
121 | public boolean isRadiusHalfHeight() {
122 | return isRadiusHalfHeight;
123 | }
124 |
125 | public boolean isWidthHeightEqual() {
126 | return isWidthHeightEqual;
127 | }
128 |
129 | protected int dp2px(float dp) {
130 | final float scale = context.getResources().getDisplayMetrics().density;
131 | return (int) (dp * scale + 0.5f);
132 | }
133 |
134 | protected int sp2px(float sp) {
135 | final float scale = this.context.getResources().getDisplayMetrics().scaledDensity;
136 | return (int) (sp * scale + 0.5f);
137 | }
138 |
139 | private void setDrawable(GradientDrawable gd, int color, int strokeColor) {
140 | gd.setColor(color);
141 | gd.setCornerRadius(cornerRadius);
142 | gd.setStroke(strokeWidth, strokeColor);
143 | }
144 |
145 | public void setBgSelector() {
146 | StateListDrawable bg = new StateListDrawable();
147 |
148 | setDrawable(gd_background, backgroundColor, strokeColor);
149 | bg.addState(new int[]{-android.R.attr.state_pressed}, gd_background);
150 |
151 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {//16
152 | setBackground(bg);
153 | } else {
154 | //noinspection deprecation
155 | setBackgroundDrawable(bg);
156 | }
157 | }
158 |
159 | }
160 |
--------------------------------------------------------------------------------
/app/src/main/java/me/stefan/easybehavior/demo1/widget/RoundProgressBar.java:
--------------------------------------------------------------------------------
1 | package me.stefan.easybehavior.demo1.widget;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.RectF;
9 | import android.os.Handler;
10 | import android.os.Message;
11 | import android.util.AttributeSet;
12 | import android.view.View;
13 |
14 | import me.stefan.easybehavior.R;
15 |
16 |
17 | /**
18 | * 带进度的进度条,线程安全的View,可直接在线程中更新进度
19 | *
20 | * @author stefan 修正改进
21 | */
22 | public class RoundProgressBar extends View {
23 | /**
24 | * 画笔对象的引用
25 | */
26 | private Paint paint;
27 |
28 | /**
29 | * 圆环的颜色
30 | */
31 | private int roundColor;
32 |
33 | /**
34 | * 圆环进度的颜色
35 | */
36 | private int roundProgressColor;
37 |
38 | /**
39 | * 中间进度百分比的字符串的颜色
40 | */
41 | private int textColor;
42 |
43 | /**
44 | * 中间进度百分比的字符串的字体
45 | */
46 | private float textSize;
47 |
48 | /**
49 | * 圆环的宽度
50 | */
51 | private float roundWidth;
52 |
53 | /**
54 | * 最大进度
55 | */
56 | private int max;
57 |
58 | /**
59 | * 当前进度
60 | */
61 | private int progress;
62 | /**
63 | * 是否循环
64 | **/
65 | public boolean isSpinning = false;
66 |
67 | public RoundProgressBar(Context context) {
68 | this(context, null);
69 | }
70 |
71 | public RoundProgressBar(Context context, AttributeSet attrs) {
72 | this(context, attrs, 0);
73 | }
74 |
75 | public RoundProgressBar(Context context, AttributeSet attrs, int defStyle) {
76 | super(context, attrs, defStyle);
77 |
78 | paint = new Paint();
79 |
80 | TypedArray mTypedArray = context.obtainStyledAttributes(attrs,
81 | R.styleable.RoundProgressBar);
82 |
83 | //获取自定义属性和默认值
84 | roundColor = mTypedArray.getColor(R.styleable.RoundProgressBar_round_color, Color.RED);
85 | roundProgressColor = mTypedArray.getColor(R.styleable.RoundProgressBar_round_progressColor, Color.GREEN);
86 | textColor = mTypedArray.getColor(R.styleable.RoundProgressBar_round_textColor, Color.GREEN);
87 | textSize = mTypedArray.getDimension(R.styleable.RoundProgressBar_round_textsize, 15);
88 | roundWidth = mTypedArray.getDimension(R.styleable.RoundProgressBar_round_width, 5);
89 | max = mTypedArray.getInteger(R.styleable.RoundProgressBar_round_max, 360);
90 |
91 | mTypedArray.recycle();//必须Recycle
92 | }
93 |
94 |
95 | @Override
96 | protected void onDraw(Canvas canvas) {
97 | super.onDraw(canvas);
98 |
99 | /**
100 | * 画最外层的大圆环
101 | */
102 | int centre = getWidth()/2; //获取圆心的x坐标
103 | int radius = (int) (centre - roundWidth/2); //圆环的半径
104 | paint.setColor(roundColor); //设置圆环的颜色
105 | paint.setStyle(Paint.Style.STROKE); //设置空心
106 | paint.setStrokeWidth(roundWidth); //设置圆环的宽度
107 | paint.setAntiAlias(true); //消除锯齿
108 |
109 |
110 | if(isSpinning){
111 | /**
112 | * 画圆弧 ,画圆环的进度
113 | */
114 | //设置进度是实心还是空心
115 | paint.setStrokeWidth(roundWidth); //设置圆环的宽度
116 | paint.setColor(roundProgressColor); //设置进度的颜色
117 | RectF oval = new RectF(centre - radius, centre - radius, centre
118 | + radius, centre + radius); //用于定义的圆弧的形状和大小的界限
119 | paint.setStyle(Paint.Style.STROKE);
120 | canvas.drawArc(oval, progress-90, 320 , false, paint); //根据进度画圆弧
121 | }else{
122 | /**
123 | * 画圆弧 ,画圆环的进度
124 | */
125 | //设置进度是实心还是空心
126 | paint.setStrokeWidth(roundWidth); //设置圆环的宽度
127 | paint.setColor(roundProgressColor); //设置进度的颜色
128 | RectF oval = new RectF(centre - radius, centre - radius, centre
129 | + radius, centre + radius); //用于定义的圆弧的形状和大小的界限
130 | paint.setStyle(Paint.Style.STROKE);
131 | canvas.drawArc(oval, -90, progress , false, paint); //根据进度画圆弧
132 | }
133 |
134 |
135 |
136 | }
137 |
138 |
139 | /**
140 | * Reset the count (in increment mode)
141 | */
142 | public void resetCount() {
143 | progress = 0;
144 | invalidate();
145 | }
146 |
147 | /**
148 | * Turn off spin mode
149 | */
150 | public void stopSpinning() {
151 | isSpinning = false;
152 | progress = 0;
153 | spinHandler.removeMessages(0);
154 | }
155 |
156 | /**
157 | * Puts the view on spin mode
158 | */
159 | public void spin() {
160 | isSpinning = true;
161 | spinHandler.sendEmptyMessage(0);
162 | }
163 |
164 | /**
165 | * Increment the progress by 1 (of 360)
166 | */
167 | public void incrementProgress() {
168 | isSpinning = false;
169 | if (progress > 360)
170 | progress = 0;
171 | spinHandler.sendEmptyMessage(0);
172 | }
173 |
174 | private Handler spinHandler = new Handler() {
175 | /**
176 | * This is the code that will increment the progress variable
177 | * and so spin the wheel
178 | */
179 | @Override
180 | public void handleMessage(Message msg) {
181 | invalidate();
182 | if (isSpinning) {
183 | progress += 10;
184 | if (progress > 360) {
185 | progress = 0;
186 | }
187 | spinHandler.sendEmptyMessageDelayed(0, 0);
188 | }
189 | //super.handleMessage(msg);
190 | }
191 | };
192 |
193 | /**
194 | * 设置进度,此为线程安全控件,由于考虑多线的问题,需要同步
195 | * 刷新界面调用postInvalidate()能在非UI线程刷新
196 | *
197 | * @param progress
198 | */
199 | public synchronized void setProgress(int progress) {
200 | if (progress < 0) {
201 | throw new IllegalArgumentException("progress not less than 0");
202 | }
203 | if (progress > max) {
204 | progress = max;
205 | }
206 | if (progress <= max) {
207 | this.progress = progress;
208 | postInvalidate();
209 | }
210 |
211 | }
212 |
213 | public synchronized int getMax() {
214 | return max;
215 | }
216 |
217 | /**
218 | * 设置进度的最大值
219 | *
220 | * @param max
221 | */
222 | public synchronized void setMax(int max) {
223 | if (max < 0) {
224 | throw new IllegalArgumentException("max not less than 0");
225 | }
226 | this.max = max;
227 | }
228 |
229 | /**
230 | * 获取进度.需要同步
231 | *
232 | * @return
233 | */
234 | public synchronized int getProgress() {
235 | return progress;
236 | }
237 |
238 | public int getCricleColor() {
239 | return roundColor;
240 | }
241 |
242 | public void setCricleColor(int cricleColor) {
243 | this.roundColor = cricleColor;
244 | }
245 |
246 | public int getCricleProgressColor() {
247 | return roundProgressColor;
248 | }
249 |
250 | public void setCricleProgressColor(int cricleProgressColor) {
251 | this.roundProgressColor = cricleProgressColor;
252 | }
253 |
254 | public int getTextColor() {
255 | return textColor;
256 | }
257 |
258 | public void setTextColor(int textColor) {
259 | this.textColor = textColor;
260 | }
261 |
262 | public float getTextSize() {
263 | return textSize;
264 | }
265 |
266 | public void setTextSize(float textSize) {
267 | this.textSize = textSize;
268 | }
269 |
270 | public float getRoundWidth() {
271 | return roundWidth;
272 | }
273 |
274 | public void setRoundWidth(float roundWidth) {
275 | this.roundWidth = roundWidth;
276 | }
277 |
278 |
279 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/stefan/easybehavior/demo1/behavior/AppBarLayoutOverScrollViewBehavior.java:
--------------------------------------------------------------------------------
1 | package me.stefan.easybehavior.demo1.behavior;
2 |
3 | import android.animation.Animator;
4 | import android.animation.ValueAnimator;
5 | import android.content.Context;
6 | import android.support.design.widget.AppBarLayout;
7 | import android.support.design.widget.CoordinatorLayout;
8 | import android.support.v4.view.ViewCompat;
9 | import android.support.v7.widget.Toolbar;
10 | import android.util.AttributeSet;
11 | import android.view.View;
12 | import android.view.ViewGroup;
13 |
14 | import me.stefan.easybehavior.widget.DisInterceptNestedScrollView;
15 |
16 |
17 | /**
18 | * Created by gjm on 2017/5/24.
19 | * 目前包括的事件:
20 | * 图片放大回弹
21 | * 个人信息布局的top和botoom跟随图片位移
22 | * toolbar背景变色
23 | */
24 | public class AppBarLayoutOverScrollViewBehavior extends AppBarLayout.Behavior {
25 | private static final String TAG = "overScroll";
26 | private static final String TAG_TOOLBAR = "toolbar";
27 | private static final String TAG_MIDDLE = "middle";
28 | private static final float TARGET_HEIGHT = 1500;
29 | private View mTargetView;
30 | private int mParentHeight;
31 | private int mTargetViewHeight;
32 | private float mTotalDy;
33 | private float mLastScale;
34 | private int mLastBottom;
35 | private boolean isAnimate;
36 | private Toolbar mToolBar;
37 | private ViewGroup middleLayout;//个人信息布局
38 | private int mMiddleHeight;
39 | private boolean isRecovering = false;//是否正在自动回弹中
40 |
41 | private final float MAX_REFRESH_LIMIT = 0.3f;//达到这个下拉临界值就开始刷新动画
42 |
43 | public AppBarLayoutOverScrollViewBehavior() {
44 | }
45 |
46 | public AppBarLayoutOverScrollViewBehavior(Context context, AttributeSet attrs) {
47 | super(context, attrs);
48 | }
49 |
50 |
51 | @Override
52 | public boolean onLayoutChild(CoordinatorLayout parent, AppBarLayout abl, int layoutDirection) {
53 | boolean handled = super.onLayoutChild(parent, abl, layoutDirection);
54 |
55 | if (mToolBar == null) {
56 | mToolBar = (Toolbar) parent.findViewWithTag(TAG_TOOLBAR);
57 | }
58 | if (middleLayout == null) {
59 | middleLayout = (ViewGroup) parent.findViewWithTag(TAG_MIDDLE);
60 | }
61 | // 需要在调用过super.onLayoutChild()方法之后获取
62 | if (mTargetView == null) {
63 | mTargetView = parent.findViewWithTag(TAG);
64 | if (mTargetView != null) {
65 | initial(abl);
66 | }
67 | }
68 | abl.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
69 |
70 |
71 | @Override
72 | public final void onOffsetChanged(AppBarLayout appBarLayout, int i) {
73 | mToolBar.setAlpha(Float.valueOf(Math.abs(i)) / Float.valueOf(appBarLayout.getTotalScrollRange()));
74 |
75 | }
76 |
77 | });
78 | return handled;
79 | }
80 |
81 | @Override
82 | public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes,int type) {
83 | isAnimate = true;
84 | if (target instanceof DisInterceptNestedScrollView) return true;//这个布局就是middleLayout
85 | return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes,type);
86 | }
87 |
88 | @Override
89 | public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed, int type) {
90 | if (!isRecovering) {
91 | if (mTargetView != null && ((dy < 0 && child.getBottom() >= mParentHeight)
92 | || (dy > 0 && child.getBottom() > mParentHeight))) {
93 | scale(child, target, dy);
94 | return;
95 | }
96 | }
97 | super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed,type);
98 | }
99 |
100 | @Override
101 | public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY) {
102 | if (velocityY > 100) {//当y速度>100,就秒弹回
103 | isAnimate = false;
104 | }
105 | return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
106 | }
107 |
108 |
109 | @Override
110 | public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl, View target, int type) {
111 | recovery(abl);
112 | super.onStopNestedScroll(coordinatorLayout, abl, target,type);
113 | }
114 |
115 | private void initial(AppBarLayout abl) {
116 | abl.setClipChildren(false);
117 | mParentHeight = abl.getHeight();
118 | mTargetViewHeight = mTargetView.getHeight();
119 | mMiddleHeight = middleLayout.getHeight();
120 | }
121 |
122 | private void scale(AppBarLayout abl, View target, int dy) {
123 | mTotalDy += -dy;
124 | mTotalDy = Math.min(mTotalDy, TARGET_HEIGHT);
125 | mLastScale = Math.max(1f, 1f + mTotalDy / TARGET_HEIGHT);
126 | ViewCompat.setScaleX(mTargetView, mLastScale);
127 | ViewCompat.setScaleY(mTargetView, mLastScale);
128 | mLastBottom = mParentHeight + (int) (mTargetViewHeight / 2 * (mLastScale - 1));
129 | abl.setBottom(mLastBottom);
130 | target.setScrollY(0);
131 |
132 | middleLayout.setTop(mLastBottom - mMiddleHeight);
133 | middleLayout.setBottom(mLastBottom);
134 |
135 | if (onProgressChangeListener != null) {
136 | float progress = Math.min((mLastScale - 1) / MAX_REFRESH_LIMIT, 1);//计算0~1的进度
137 | onProgressChangeListener.onProgressChange(progress, false);
138 | }
139 |
140 | }
141 |
142 | public interface onProgressChangeListener {
143 | /**
144 | * 范围 0~1
145 | *
146 | * @param progress
147 | * @param isRelease 是否是释放状态
148 | */
149 | void onProgressChange(float progress, boolean isRelease);
150 | }
151 |
152 | public void setOnProgressChangeListener(AppBarLayoutOverScrollViewBehavior.onProgressChangeListener onProgressChangeListener) {
153 | this.onProgressChangeListener = onProgressChangeListener;
154 | }
155 |
156 | onProgressChangeListener onProgressChangeListener;
157 |
158 | private void recovery(final AppBarLayout abl) {
159 | if (isRecovering) return;
160 | if (mTotalDy > 0) {
161 | isRecovering = true;
162 | mTotalDy = 0;
163 | if (isAnimate) {
164 | ValueAnimator anim = ValueAnimator.ofFloat(mLastScale, 1f).setDuration(200);
165 | anim.addUpdateListener(
166 | new ValueAnimator.AnimatorUpdateListener() {
167 | @Override
168 | public void onAnimationUpdate(ValueAnimator animation) {
169 |
170 | float value = (float) animation.getAnimatedValue();
171 | ViewCompat.setScaleX(mTargetView, value);
172 | ViewCompat.setScaleY(mTargetView, value);
173 | abl.setBottom((int) (mLastBottom - (mLastBottom - mParentHeight) * animation.getAnimatedFraction()));
174 | middleLayout.setTop((int) (mLastBottom -
175 | (mLastBottom - mParentHeight) * animation.getAnimatedFraction() - mMiddleHeight));
176 |
177 | if (onProgressChangeListener != null) {
178 | float progress = Math.min((value - 1) / MAX_REFRESH_LIMIT, 1);//计算0~1的进度
179 | onProgressChangeListener.onProgressChange(progress, true);
180 | }
181 | }
182 | }
183 | );
184 | anim.addListener(new Animator.AnimatorListener() {
185 | @Override
186 | public void onAnimationStart(Animator animation) {
187 | }
188 |
189 | @Override
190 | public void onAnimationEnd(Animator animation) {
191 | isRecovering = false;
192 | }
193 |
194 | @Override
195 | public void onAnimationCancel(Animator animation) {
196 | }
197 |
198 | @Override
199 | public void onAnimationRepeat(Animator animation) {
200 | }
201 | });
202 | anim.start();
203 | } else {
204 | ViewCompat.setScaleX(mTargetView, 1f);
205 | ViewCompat.setScaleY(mTargetView, 1f);
206 | abl.setBottom(mParentHeight);
207 | middleLayout.setTop(mParentHeight - mMiddleHeight);
208 | // middleLayout.setBottom(mParentHeight);
209 | isRecovering = false;
210 |
211 | if (onProgressChangeListener != null)
212 | onProgressChangeListener.onProgressChange(0, true);
213 | }
214 | }
215 | }
216 |
217 |
218 | }
--------------------------------------------------------------------------------
/tabLayoutLibrary/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
--------------------------------------------------------------------------------
/app/src/main/java/me/stefan/easybehavior/Demo1Activity.java:
--------------------------------------------------------------------------------
1 | package me.stefan.easybehavior;
2 |
3 | import android.content.Context;
4 | import android.os.Build;
5 | import android.os.Bundle;
6 | import android.support.design.widget.AppBarLayout;
7 | import android.support.design.widget.CollapsingToolbarLayout;
8 | import android.support.design.widget.CoordinatorLayout;
9 | import android.support.v4.app.Fragment;
10 | import android.support.v4.view.ViewPager;
11 | import android.support.v7.app.AppCompatActivity;
12 | import android.support.v7.widget.Toolbar;
13 | import android.view.View;
14 | import android.view.ViewGroup;
15 | import android.widget.ImageView;
16 |
17 | import com.flyco.tablayout.CommonTabLayout;
18 | import com.flyco.tablayout.listener.CustomTabEntity;
19 | import com.flyco.tablayout.listener.OnTabSelectListener;
20 | import com.jaeger.library.StatusBarUtil;
21 |
22 | import java.util.ArrayList;
23 | import java.util.List;
24 | import java.util.Random;
25 |
26 | import me.stefan.easybehavior.demo1.behavior.AppBarLayoutOverScrollViewBehavior;
27 | import me.stefan.easybehavior.demo1.widget.NoScrollViewPager;
28 | import me.stefan.easybehavior.demo1.widget.RoundProgressBar;
29 | import me.stefan.easybehavior.fragment.ItemFragment1;
30 | import me.stefan.easybehavior.fragment.MyFragmentPagerAdapter;
31 | import me.stefan.easybehavior.fragment.dummy.TabEntity;
32 | import me.stefan.easybehavior.widget.CircleImageView;
33 |
34 | /**
35 | * @author stefan 邮箱:648701906@qq.com
36 | */
37 | public class Demo1Activity extends AppCompatActivity {
38 |
39 | private ImageView mZoomIv;
40 | private Toolbar mToolBar;
41 | private ViewGroup titleContainer;
42 | private AppBarLayout mAppBarLayout;
43 | private ViewGroup titleCenterLayout;
44 | private RoundProgressBar progressBar;
45 | private ImageView mSettingIv, mMsgIv;
46 | private CircleImageView mAvater;
47 | private CommonTabLayout mTablayout;
48 | private NoScrollViewPager mViewPager;
49 |
50 | private ArrayList mTabEntities = new ArrayList<>();
51 | private List fragments;
52 | private int lastState = 1;
53 |
54 | @Override
55 | protected void onCreate(Bundle savedInstanceState) {
56 | super.onCreate(savedInstanceState);
57 | setContentView(R.layout.activity_demo1);
58 |
59 | findId();
60 | initListener();
61 | initTab();
62 | initStatus();
63 |
64 | }
65 |
66 | /**
67 | * 初始化id
68 | */
69 | private void findId() {
70 | mZoomIv = (ImageView) findViewById(R.id.uc_zoomiv);
71 | mToolBar = (Toolbar) findViewById(R.id.toolbar);
72 | titleContainer = (ViewGroup) findViewById(R.id.title_layout);
73 | mAppBarLayout = (AppBarLayout) findViewById(R.id.appbar_layout);
74 | titleCenterLayout = (ViewGroup) findViewById(R.id.title_center_layout);
75 | progressBar = (RoundProgressBar) findViewById(R.id.uc_progressbar);
76 | mSettingIv = (ImageView) findViewById(R.id.uc_setting_iv);
77 | mMsgIv = (ImageView) findViewById(R.id.uc_msg_iv);
78 | mAvater = (CircleImageView) findViewById(R.id.uc_avater);
79 | mTablayout = (CommonTabLayout) findViewById(R.id.uc_tablayout);
80 | mViewPager = (NoScrollViewPager) findViewById(R.id.uc_viewpager);
81 | }
82 |
83 | /**
84 | * 初始化tab
85 | */
86 | private void initTab() {
87 | fragments = getFragments();
88 | MyFragmentPagerAdapter myFragmentPagerAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager(), fragments, getNames());
89 |
90 | mTablayout.setTabData(mTabEntities);
91 | mViewPager.setAdapter(myFragmentPagerAdapter);
92 | }
93 |
94 | /**
95 | * 绑定事件
96 | */
97 | private void initListener() {
98 | mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
99 | @Override
100 | public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
101 | float percent = Float.valueOf(Math.abs(verticalOffset)) / Float.valueOf(appBarLayout.getTotalScrollRange());
102 | if (titleCenterLayout != null && mAvater != null && mSettingIv != null && mMsgIv != null) {
103 | titleCenterLayout.setAlpha(percent);
104 | StatusBarUtil.setTranslucentForImageView(Demo1Activity.this, (int) (255f * percent), null);
105 | if (percent == 0) {
106 | groupChange(1f, 1);
107 | } else if (percent == 1) {
108 | if (mAvater.getVisibility() != View.GONE) {
109 | mAvater.setVisibility(View.GONE);
110 | }
111 | groupChange(1f, 2);
112 | } else {
113 | if (mAvater.getVisibility() != View.VISIBLE) {
114 | mAvater.setVisibility(View.VISIBLE);
115 | }
116 | groupChange(percent, 0);
117 | }
118 |
119 | }
120 | }
121 | });
122 | AppBarLayoutOverScrollViewBehavior myAppBarLayoutBehavoir = (AppBarLayoutOverScrollViewBehavior)
123 | ((CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams()).getBehavior();
124 | myAppBarLayoutBehavoir.setOnProgressChangeListener(new AppBarLayoutOverScrollViewBehavior.onProgressChangeListener() {
125 | @Override
126 | public void onProgressChange(float progress, boolean isRelease) {
127 | progressBar.setProgress((int) (progress * 360));
128 | if (progress == 1 && !progressBar.isSpinning && isRelease) {
129 | // 刷新viewpager里的fragment
130 | }
131 | if (mMsgIv != null) {
132 | if (progress == 0 && !progressBar.isSpinning) {
133 | mMsgIv.setVisibility(View.VISIBLE);
134 | } else if (progress > 0 && mSettingIv.getVisibility() == View.VISIBLE) {
135 | mMsgIv.setVisibility(View.INVISIBLE);
136 | }
137 | }
138 | }
139 | });
140 | mTablayout.setOnTabSelectListener(new OnTabSelectListener() {
141 | @Override
142 | public void onTabSelect(int position) {
143 | mViewPager.setCurrentItem(position);
144 | }
145 |
146 | @Override
147 | public void onTabReselect(int position) {
148 |
149 | }
150 | });
151 | mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
152 | @Override
153 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
154 |
155 | }
156 |
157 | @Override
158 | public void onPageSelected(int position) {
159 | mTablayout.setCurrentTab(position);
160 | }
161 |
162 | @Override
163 | public void onPageScrollStateChanged(int state) {
164 |
165 | }
166 | });
167 | }
168 |
169 | /**
170 | * 初始化状态栏位置
171 | */
172 | private void initStatus() {
173 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {//4.4以下不支持状态栏变色
174 | //注意了,这里使用了第三方库 StatusBarUtil,目的是改变状态栏的alpha
175 | StatusBarUtil.setTransparentForImageView(Demo1Activity.this, null);
176 | //这里是重设我们的title布局的topMargin,StatusBarUtil提供了重设的方法,但是我们这里有两个布局
177 | //TODO 关于为什么不把Toolbar和@layout/layout_uc_head_title放到一起,是因为需要Toolbar来占位,防止AppBarLayout折叠时将title顶出视野范围
178 | int statusBarHeight = getStatusBarHeight(Demo1Activity.this);
179 | CollapsingToolbarLayout.LayoutParams lp1 = (CollapsingToolbarLayout.LayoutParams) titleContainer.getLayoutParams();
180 | lp1.topMargin = statusBarHeight;
181 | titleContainer.setLayoutParams(lp1);
182 | CollapsingToolbarLayout.LayoutParams lp2 = (CollapsingToolbarLayout.LayoutParams) mToolBar.getLayoutParams();
183 | lp2.topMargin = statusBarHeight;
184 | mToolBar.setLayoutParams(lp2);
185 | }
186 | }
187 |
188 | /**
189 | * @param alpha
190 | * @param state 0-正在变化 1展开 2 关闭
191 | */
192 | public void groupChange(float alpha, int state) {
193 | lastState = state;
194 |
195 | mSettingIv.setAlpha(alpha);
196 | mMsgIv.setAlpha(alpha);
197 |
198 | switch (state) {
199 | case 1://完全展开 显示白色
200 | mMsgIv.setImageResource(R.drawable.icon_msg);
201 | mSettingIv.setImageResource(R.drawable.icon_setting);
202 | mViewPager.setNoScroll(false);
203 | break;
204 | case 2://完全关闭 显示黑色
205 | mMsgIv.setImageResource(R.drawable.icon_msg_black);
206 | mSettingIv.setImageResource(R.drawable.icon_setting_black);
207 | mViewPager.setNoScroll(false);
208 | break;
209 | case 0://介于两种临界值之间 显示黑色
210 | if (lastState != 0) {
211 | mMsgIv.setImageResource(R.drawable.icon_msg_black);
212 | mSettingIv.setImageResource(R.drawable.icon_setting_black);
213 | }
214 | //为什么禁止滑动?在介于开关状态之间,不允许滑动,开启可能会导致不好的体验
215 | mViewPager.setNoScroll(true);
216 | break;
217 | }
218 | }
219 |
220 |
221 | /**
222 | * 获取状态栏高度
223 | * !!这个方法来自StatusBarUtil,因为作者将之设为private,所以直接copy出来
224 | *
225 | * @param context context
226 | * @return 状态栏高度
227 | */
228 | private int getStatusBarHeight(Context context) {
229 | // 获得状态栏高度
230 | int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
231 | return context.getResources().getDimensionPixelSize(resourceId);
232 | }
233 |
234 | /**
235 | * 假数据
236 | *
237 | * @return
238 | */
239 | public String[] getNames() {
240 | String[] mNames = new String[]{"Weather", "Moon", "Like", "Fans"};
241 | for (String str : mNames) {
242 | mTabEntities.add(new TabEntity(String.valueOf(new Random().nextInt(200)), str));
243 | }
244 |
245 | return mNames;
246 | }
247 |
248 | public List getFragments() {
249 | List fragments = new ArrayList<>();
250 | fragments.add(new ItemFragment1());
251 | fragments.add(new ItemFragment1());
252 | fragments.add(new ItemFragment1());
253 | fragments.add(new ItemFragment1());
254 | return fragments;
255 | }
256 | }
257 |
--------------------------------------------------------------------------------
/app/src/main/java/me/stefan/easybehavior/widget/CircleImageView.java:
--------------------------------------------------------------------------------
1 | package me.stefan.easybehavior.widget;
2 | import android.content.Context;
3 | import android.content.res.TypedArray;
4 | import android.graphics.Bitmap;
5 | import android.graphics.BitmapShader;
6 | import android.graphics.Canvas;
7 | import android.graphics.Color;
8 | import android.graphics.ColorFilter;
9 | import android.graphics.Matrix;
10 | import android.graphics.Paint;
11 | import android.graphics.RectF;
12 | import android.graphics.Shader;
13 | import android.graphics.drawable.BitmapDrawable;
14 | import android.graphics.drawable.ColorDrawable;
15 | import android.graphics.drawable.Drawable;
16 | import android.net.Uri;
17 | import android.support.annotation.ColorInt;
18 | import android.support.annotation.ColorRes;
19 | import android.support.annotation.DrawableRes;
20 | import android.support.v7.widget.AppCompatImageView;
21 | import android.util.AttributeSet;
22 | import android.widget.ImageView;
23 |
24 | import me.stefan.easybehavior.R;
25 |
26 |
27 | /**
28 | * Created by wxy on 16/6/27.
29 | */
30 | public class CircleImageView extends ImageView {
31 | private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
32 |
33 | private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
34 | private static final int COLORDRAWABLE_DIMENSION = 2;
35 |
36 | private static final int DEFAULT_BORDER_WIDTH = 0;
37 | private static final int DEFAULT_BORDER_COLOR = Color.BLACK;
38 | private static final int DEFAULT_FILL_COLOR = Color.TRANSPARENT;
39 | private static final boolean DEFAULT_BORDER_OVERLAY = false;
40 |
41 | private final RectF mDrawableRect = new RectF();
42 | private final RectF mBorderRect = new RectF();
43 |
44 | private final Matrix mShaderMatrix = new Matrix();
45 | private final Paint mBitmapPaint = new Paint();
46 | private final Paint mBorderPaint = new Paint();
47 | private final Paint mFillPaint = new Paint();
48 |
49 | private int mBorderColor = DEFAULT_BORDER_COLOR;
50 | private int mBorderWidth = DEFAULT_BORDER_WIDTH;
51 | private int mFillColor = DEFAULT_FILL_COLOR;
52 |
53 | private Bitmap mBitmap;
54 | private BitmapShader mBitmapShader;
55 | private int mBitmapWidth;
56 | private int mBitmapHeight;
57 |
58 | private float mDrawableRadius;
59 | private float mBorderRadius;
60 |
61 | private ColorFilter mColorFilter;
62 |
63 | private boolean mReady;
64 | private boolean mSetupPending;
65 | private boolean mBorderOverlay;
66 | private boolean mDisableCircularTransformation;
67 |
68 | public CircleImageView(Context context) {
69 | super(context);
70 |
71 | init();
72 | }
73 |
74 | public CircleImageView(Context context, AttributeSet attrs) {
75 | this(context, attrs, 0);
76 | }
77 |
78 | public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
79 | super(context, attrs, defStyle);
80 |
81 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);
82 |
83 | mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_civ_border_width, DEFAULT_BORDER_WIDTH);
84 | mBorderColor = a.getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR);
85 | mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY);
86 | mFillColor = a.getColor(R.styleable.CircleImageView_civ_fill_color, DEFAULT_FILL_COLOR);
87 |
88 | a.recycle();
89 |
90 | init();
91 | }
92 |
93 | private void init() {
94 | super.setScaleType(SCALE_TYPE);
95 | mReady = true;
96 |
97 | if (mSetupPending) {
98 | setup();
99 | mSetupPending = false;
100 | }
101 | }
102 |
103 | @Override
104 | public ScaleType getScaleType() {
105 | return SCALE_TYPE;
106 | }
107 |
108 | @Override
109 | public void setScaleType(ScaleType scaleType) {
110 | if (scaleType != SCALE_TYPE) {
111 | throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));
112 | }
113 | }
114 |
115 | @Override
116 | public void setAdjustViewBounds(boolean adjustViewBounds) {
117 | if (adjustViewBounds) {
118 | throw new IllegalArgumentException("adjustViewBounds not supported.");
119 | }
120 | }
121 |
122 | @Override
123 | protected void onDraw(Canvas canvas) {
124 | if (mDisableCircularTransformation) {
125 | super.onDraw(canvas);
126 | return;
127 | }
128 |
129 | if (mBitmap == null) {
130 | return;
131 | }
132 |
133 | if (mFillColor != Color.TRANSPARENT) {
134 | canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mFillPaint);
135 | }
136 | canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mBitmapPaint);
137 | if (mBorderWidth > 0) {
138 | canvas.drawCircle(mBorderRect.centerX(), mBorderRect.centerY(), mBorderRadius, mBorderPaint);
139 | }
140 | }
141 |
142 | @Override
143 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
144 | super.onSizeChanged(w, h, oldw, oldh);
145 | setup();
146 | }
147 |
148 | public int getBorderColor() {
149 | return mBorderColor;
150 | }
151 |
152 | public void setBorderColor(@ColorInt int borderColor) {
153 | if (borderColor == mBorderColor) {
154 | return;
155 | }
156 |
157 | mBorderColor = borderColor;
158 | mBorderPaint.setColor(mBorderColor);
159 | invalidate();
160 | }
161 |
162 | /**
163 | * @deprecated Use {@link #setBorderColor(int)} instead
164 | */
165 | @Deprecated
166 | public void setBorderColorResource(@ColorRes int borderColorRes) {
167 | setBorderColor(getContext().getResources().getColor(borderColorRes));
168 | }
169 |
170 | /**
171 | * Return the color drawn behind the circle-shaped drawable.
172 | *
173 | * @return The color drawn behind the drawable
174 | *
175 | * @deprecated Fill color support is going to be removed in the future
176 | */
177 | @Deprecated
178 | public int getFillColor() {
179 | return mFillColor;
180 | }
181 |
182 | /**
183 | * Set a color to be drawn behind the circle-shaped drawable. Note that
184 | * this has no effect if the drawable is opaque or no drawable is set.
185 | *
186 | * @param fillColor The color to be drawn behind the drawable
187 | *
188 | * @deprecated Fill color support is going to be removed in the future
189 | */
190 | @Deprecated
191 | public void setFillColor(@ColorInt int fillColor) {
192 | if (fillColor == mFillColor) {
193 | return;
194 | }
195 |
196 | mFillColor = fillColor;
197 | mFillPaint.setColor(fillColor);
198 | invalidate();
199 | }
200 |
201 | /**
202 | * Set a color to be drawn behind the circle-shaped drawable. Note that
203 | * this has no effect if the drawable is opaque or no drawable is set.
204 | *
205 | * @param fillColorRes The color resource to be resolved to a color and
206 | * drawn behind the drawable
207 | *
208 | * @deprecated Fill color support is going to be removed in the future
209 | */
210 | @Deprecated
211 | public void setFillColorResource(@ColorRes int fillColorRes) {
212 | setFillColor(getContext().getResources().getColor(fillColorRes));
213 | }
214 |
215 | public int getBorderWidth() {
216 | return mBorderWidth;
217 | }
218 |
219 | public void setBorderWidth(int borderWidth) {
220 | if (borderWidth == mBorderWidth) {
221 | return;
222 | }
223 |
224 | mBorderWidth = borderWidth;
225 | setup();
226 | }
227 |
228 | public boolean isBorderOverlay() {
229 | return mBorderOverlay;
230 | }
231 |
232 | public void setBorderOverlay(boolean borderOverlay) {
233 | if (borderOverlay == mBorderOverlay) {
234 | return;
235 | }
236 |
237 | mBorderOverlay = borderOverlay;
238 | setup();
239 | }
240 |
241 | public boolean isDisableCircularTransformation() {
242 | return mDisableCircularTransformation;
243 | }
244 |
245 | public void setDisableCircularTransformation(boolean disableCircularTransformation) {
246 | if (mDisableCircularTransformation == disableCircularTransformation) {
247 | return;
248 | }
249 |
250 | mDisableCircularTransformation = disableCircularTransformation;
251 | initializeBitmap();
252 | }
253 |
254 | @Override
255 | public void setImageBitmap(Bitmap bm) {
256 | super.setImageBitmap(bm);
257 | initializeBitmap();
258 | }
259 |
260 | @Override
261 | public void setImageDrawable(Drawable drawable) {
262 | super.setImageDrawable(drawable);
263 | initializeBitmap();
264 | }
265 |
266 | @Override
267 | public void setImageResource(@DrawableRes int resId) {
268 | super.setImageResource(resId);
269 | initializeBitmap();
270 | }
271 |
272 | @Override
273 | public void setImageURI(Uri uri) {
274 | super.setImageURI(uri);
275 | initializeBitmap();
276 | }
277 |
278 | @Override
279 | public void setColorFilter(ColorFilter cf) {
280 | if (cf == mColorFilter) {
281 | return;
282 | }
283 |
284 | mColorFilter = cf;
285 | applyColorFilter();
286 | invalidate();
287 | }
288 |
289 | @Override
290 | public ColorFilter getColorFilter() {
291 | return mColorFilter;
292 | }
293 |
294 | private void applyColorFilter() {
295 | if (mBitmapPaint != null) {
296 | mBitmapPaint.setColorFilter(mColorFilter);
297 | }
298 | }
299 |
300 | private Bitmap getBitmapFromDrawable(Drawable drawable) {
301 | if (drawable == null) {
302 | return null;
303 | }
304 |
305 | if (drawable instanceof BitmapDrawable) {
306 | return ((BitmapDrawable) drawable).getBitmap();
307 | }
308 |
309 | try {
310 | Bitmap bitmap;
311 |
312 | if (drawable instanceof ColorDrawable) {
313 | bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
314 | } else {
315 | bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);
316 | }
317 |
318 | Canvas canvas = new Canvas(bitmap);
319 | drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
320 | drawable.draw(canvas);
321 | return bitmap;
322 | } catch (Exception e) {
323 | e.printStackTrace();
324 | return null;
325 | }
326 | }
327 |
328 | private void initializeBitmap() {
329 | if (mDisableCircularTransformation) {
330 | mBitmap = null;
331 | } else {
332 | mBitmap = getBitmapFromDrawable(getDrawable());
333 | }
334 | setup();
335 | }
336 |
337 | private void setup() {
338 | if (!mReady) {
339 | mSetupPending = true;
340 | return;
341 | }
342 |
343 | if (getWidth() == 0 && getHeight() == 0) {
344 | return;
345 | }
346 |
347 | if (mBitmap == null) {
348 | invalidate();
349 | return;
350 | }
351 |
352 | mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
353 |
354 | mBitmapPaint.setAntiAlias(true);
355 | mBitmapPaint.setShader(mBitmapShader);
356 |
357 | mBorderPaint.setStyle(Paint.Style.STROKE);
358 | mBorderPaint.setAntiAlias(true);
359 | mBorderPaint.setColor(mBorderColor);
360 | mBorderPaint.setStrokeWidth(mBorderWidth);
361 |
362 | mFillPaint.setStyle(Paint.Style.FILL);
363 | mFillPaint.setAntiAlias(true);
364 | mFillPaint.setColor(mFillColor);
365 |
366 | mBitmapHeight = mBitmap.getHeight();
367 | mBitmapWidth = mBitmap.getWidth();
368 |
369 | mBorderRect.set(calculateBounds());
370 | mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f);
371 |
372 | mDrawableRect.set(mBorderRect);
373 | if (!mBorderOverlay && mBorderWidth > 0) {
374 | mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f);
375 | }
376 | mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f);
377 |
378 | applyColorFilter();
379 | updateShaderMatrix();
380 | invalidate();
381 | }
382 |
383 | private RectF calculateBounds() {
384 | int availableWidth = getWidth() - getPaddingLeft() - getPaddingRight();
385 | int availableHeight = getHeight() - getPaddingTop() - getPaddingBottom();
386 |
387 | int sideLength = Math.min(availableWidth, availableHeight);
388 |
389 | float left = getPaddingLeft() + (availableWidth - sideLength) / 2f;
390 | float top = getPaddingTop() + (availableHeight - sideLength) / 2f;
391 |
392 | return new RectF(left, top, left + sideLength, top + sideLength);
393 | }
394 |
395 | private void updateShaderMatrix() {
396 | float scale;
397 | float dx = 0;
398 | float dy = 0;
399 |
400 | mShaderMatrix.set(null);
401 |
402 | if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
403 | scale = mDrawableRect.height() / (float) mBitmapHeight;
404 | dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
405 | } else {
406 | scale = mDrawableRect.width() / (float) mBitmapWidth;
407 | dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
408 | }
409 |
410 | mShaderMatrix.setScale(scale, scale);
411 | mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);
412 | mBitmapShader.setLocalMatrix(mShaderMatrix);
413 | }
414 |
415 | }
416 |
--------------------------------------------------------------------------------
/tabLayoutLibrary/src/main/java/com/flyco/tablayout/SegmentTabLayout.java:
--------------------------------------------------------------------------------
1 | package com.flyco.tablayout;
2 |
3 | import android.animation.TypeEvaluator;
4 | import android.animation.ValueAnimator;
5 | import android.content.Context;
6 | import android.content.res.TypedArray;
7 | import android.graphics.Canvas;
8 | import android.graphics.Color;
9 | import android.graphics.Paint;
10 | import android.graphics.Rect;
11 | import android.graphics.drawable.GradientDrawable;
12 | import android.os.Bundle;
13 | import android.os.Parcelable;
14 | import android.support.v4.app.Fragment;
15 | import android.support.v4.app.FragmentActivity;
16 | import android.util.AttributeSet;
17 | import android.util.SparseArray;
18 | import android.util.TypedValue;
19 | import android.view.View;
20 | import android.view.ViewGroup;
21 | import android.view.animation.OvershootInterpolator;
22 | import android.widget.FrameLayout;
23 | import android.widget.LinearLayout;
24 | import android.widget.TextView;
25 |
26 | import com.flyco.tablayout.listener.OnTabSelectListener;
27 | import com.flyco.tablayout.utils.FragmentChangeManager;
28 | import com.flyco.tablayout.utils.UnreadMsgUtils;
29 | import com.flyco.tablayout.widget.MsgView;
30 |
31 | import java.util.ArrayList;
32 |
33 | public class SegmentTabLayout extends FrameLayout implements ValueAnimator.AnimatorUpdateListener {
34 | private Context mContext;
35 | private String[] mTitles;
36 | private LinearLayout mTabsContainer;
37 | private int mCurrentTab;
38 | private int mLastTab;
39 | private int mTabCount;
40 | /** 用于绘制显示器 */
41 | private Rect mIndicatorRect = new Rect();
42 | private GradientDrawable mIndicatorDrawable = new GradientDrawable();
43 | private GradientDrawable mRectDrawable = new GradientDrawable();
44 |
45 | private Paint mDividerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
46 |
47 | private float mTabPadding;
48 | private boolean mTabSpaceEqual;
49 | private float mTabWidth;
50 |
51 | /** indicator */
52 | private int mIndicatorColor;
53 | private float mIndicatorHeight;
54 | private float mIndicatorCornerRadius;
55 | private float mIndicatorMarginLeft;
56 | private float mIndicatorMarginTop;
57 | private float mIndicatorMarginRight;
58 | private float mIndicatorMarginBottom;
59 | private long mIndicatorAnimDuration;
60 | private boolean mIndicatorAnimEnable;
61 | private boolean mIndicatorBounceEnable;
62 |
63 | /** divider */
64 | private int mDividerColor;
65 | private float mDividerWidth;
66 | private float mDividerPadding;
67 |
68 | /** title */
69 | private float mTextsize;
70 | private int mTextSelectColor;
71 | private int mTextUnselectColor;
72 | private boolean mTextBold;
73 | private boolean mTextAllCaps;
74 |
75 | private int mBarColor;
76 | private int mBarStrokeColor;
77 | private float mBarStrokeWidth;
78 |
79 | private int mHeight;
80 |
81 | /** anim */
82 | private ValueAnimator mValueAnimator;
83 | private OvershootInterpolator mInterpolator = new OvershootInterpolator(0.8f);
84 |
85 | private FragmentChangeManager mFragmentChangeManager;
86 | private float[] mRadiusArr = new float[8];
87 |
88 | public SegmentTabLayout(Context context) {
89 | this(context, null, 0);
90 | }
91 |
92 | public SegmentTabLayout(Context context, AttributeSet attrs) {
93 | this(context, attrs, 0);
94 | }
95 |
96 | public SegmentTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
97 | super(context, attrs, defStyleAttr);
98 | setWillNotDraw(false);//重写onDraw方法,需要调用这个方法来清除flag
99 | setClipChildren(false);
100 | setClipToPadding(false);
101 |
102 | this.mContext = context;
103 | mTabsContainer = new LinearLayout(context);
104 | addView(mTabsContainer);
105 |
106 | obtainAttributes(context, attrs);
107 |
108 | //get layout_height
109 | String height = attrs.getAttributeValue("http://schemas.android.com/apk/res/android", "layout_height");
110 |
111 | //create ViewPager
112 | if (height.equals(ViewGroup.LayoutParams.MATCH_PARENT + "")) {
113 | } else if (height.equals(ViewGroup.LayoutParams.WRAP_CONTENT + "")) {
114 | } else {
115 | int[] systemAttrs = {android.R.attr.layout_height};
116 | TypedArray a = context.obtainStyledAttributes(attrs, systemAttrs);
117 | mHeight = a.getDimensionPixelSize(0, ViewGroup.LayoutParams.WRAP_CONTENT);
118 | a.recycle();
119 | }
120 |
121 | mValueAnimator = ValueAnimator.ofObject(new PointEvaluator(), mLastP, mCurrentP);
122 | mValueAnimator.addUpdateListener(this);
123 | }
124 |
125 | private void obtainAttributes(Context context, AttributeSet attrs) {
126 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SegmentTabLayout);
127 |
128 | mIndicatorColor = ta.getColor(R.styleable.SegmentTabLayout_tl_indicator_color, Color.parseColor("#222831"));
129 | mIndicatorHeight = ta.getDimension(R.styleable.SegmentTabLayout_tl_indicator_height, -1);
130 | mIndicatorCornerRadius = ta.getDimension(R.styleable.SegmentTabLayout_tl_indicator_corner_radius, -1);
131 | mIndicatorMarginLeft = ta.getDimension(R.styleable.SegmentTabLayout_tl_indicator_margin_left, dp2px(0));
132 | mIndicatorMarginTop = ta.getDimension(R.styleable.SegmentTabLayout_tl_indicator_margin_top, 0);
133 | mIndicatorMarginRight = ta.getDimension(R.styleable.SegmentTabLayout_tl_indicator_margin_right, dp2px(0));
134 | mIndicatorMarginBottom = ta.getDimension(R.styleable.SegmentTabLayout_tl_indicator_margin_bottom, 0);
135 | mIndicatorAnimEnable = ta.getBoolean(R.styleable.SegmentTabLayout_tl_indicator_anim_enable, false);
136 | mIndicatorBounceEnable = ta.getBoolean(R.styleable.SegmentTabLayout_tl_indicator_bounce_enable, true);
137 | mIndicatorAnimDuration = ta.getInt(R.styleable.SegmentTabLayout_tl_indicator_anim_duration, -1);
138 |
139 | mDividerColor = ta.getColor(R.styleable.SegmentTabLayout_tl_divider_color, mIndicatorColor);
140 | mDividerWidth = ta.getDimension(R.styleable.SegmentTabLayout_tl_divider_width, dp2px(1));
141 | mDividerPadding = ta.getDimension(R.styleable.SegmentTabLayout_tl_divider_padding, 0);
142 |
143 | mTextsize = ta.getDimension(R.styleable.SegmentTabLayout_tl_textsize, sp2px(13f));
144 | mTextSelectColor = ta.getColor(R.styleable.SegmentTabLayout_tl_textSelectColor, Color.parseColor("#ffffff"));
145 | mTextUnselectColor = ta.getColor(R.styleable.SegmentTabLayout_tl_textUnselectColor, mIndicatorColor);
146 | mTextBold = ta.getBoolean(R.styleable.SegmentTabLayout_tl_textBold, false);
147 | mTextAllCaps = ta.getBoolean(R.styleable.SegmentTabLayout_tl_textAllCaps, false);
148 |
149 | mTabSpaceEqual = ta.getBoolean(R.styleable.SegmentTabLayout_tl_tab_space_equal, true);
150 | mTabWidth = ta.getDimension(R.styleable.SegmentTabLayout_tl_tab_width, dp2px(-1));
151 | mTabPadding = ta.getDimension(R.styleable.SegmentTabLayout_tl_tab_padding, mTabSpaceEqual || mTabWidth > 0 ? dp2px(0) : dp2px(10));
152 |
153 | mBarColor = ta.getColor(R.styleable.SegmentTabLayout_tl_bar_color, Color.TRANSPARENT);
154 | mBarStrokeColor = ta.getColor(R.styleable.SegmentTabLayout_tl_bar_stroke_color, mIndicatorColor);
155 | mBarStrokeWidth = ta.getDimension(R.styleable.SegmentTabLayout_tl_bar_stroke_width, dp2px(1));
156 |
157 | ta.recycle();
158 | }
159 |
160 | public void setTabData(String[] titles) {
161 | if (titles == null || titles.length == 0) {
162 | throw new IllegalStateException("Titles can not be NULL or EMPTY !");
163 | }
164 |
165 | this.mTitles = titles;
166 |
167 | notifyDataSetChanged();
168 | }
169 |
170 | /** 关联数据支持同时切换fragments */
171 | public void setTabData(String[] titles, FragmentActivity fa, int containerViewId, ArrayList fragments) {
172 | mFragmentChangeManager = new FragmentChangeManager(fa.getSupportFragmentManager(), containerViewId, fragments);
173 | setTabData(titles);
174 | }
175 |
176 | /** 更新数据 */
177 | public void notifyDataSetChanged() {
178 | mTabsContainer.removeAllViews();
179 | this.mTabCount = mTitles.length;
180 | View tabView;
181 | for (int i = 0; i < mTabCount; i++) {
182 | tabView = View.inflate(mContext, R.layout.layout_tab_segment, null);
183 | tabView.setTag(i);
184 | addTab(i, tabView);
185 | }
186 |
187 | updateTabStyles();
188 | }
189 |
190 | /** 创建并添加tab */
191 | private void addTab(final int position, View tabView) {
192 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title);
193 | tv_tab_title.setText(mTitles[position]);
194 |
195 | tabView.setOnClickListener(new OnClickListener() {
196 | @Override
197 | public void onClick(View v) {
198 | int position = (Integer) v.getTag();
199 | if (mCurrentTab != position) {
200 | setCurrentTab(position);
201 | if (mListener != null) {
202 | mListener.onTabSelect(position);
203 | }
204 | } else {
205 | if (mListener != null) {
206 | mListener.onTabReselect(position);
207 | }
208 | }
209 | }
210 | });
211 |
212 | /** 每一个Tab的布局参数 */
213 | LinearLayout.LayoutParams lp_tab = mTabSpaceEqual ?
214 | new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f) :
215 | new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
216 | if (mTabWidth > 0) {
217 | lp_tab = new LinearLayout.LayoutParams((int) mTabWidth, LayoutParams.MATCH_PARENT);
218 | }
219 | mTabsContainer.addView(tabView, position, lp_tab);
220 | }
221 |
222 | private void updateTabStyles() {
223 | for (int i = 0; i < mTabCount; i++) {
224 | View tabView = mTabsContainer.getChildAt(i);
225 | tabView.setPadding((int) mTabPadding, 0, (int) mTabPadding, 0);
226 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title);
227 | tv_tab_title.setTextColor(i == mCurrentTab ? mTextSelectColor : mTextUnselectColor);
228 | tv_tab_title.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextsize);
229 | // tv_tab_title.setPadding((int) mTabPadding, 0, (int) mTabPadding, 0);
230 | if (mTextAllCaps) {
231 | tv_tab_title.setText(tv_tab_title.getText().toString().toUpperCase());
232 | }
233 |
234 | if (mTextBold) {
235 | tv_tab_title.getPaint().setFakeBoldText(mTextBold);
236 | }
237 | }
238 | }
239 |
240 | private void updateTabSelection(int position) {
241 | for (int i = 0; i < mTabCount; ++i) {
242 | View tabView = mTabsContainer.getChildAt(i);
243 | final boolean isSelect = i == position;
244 | TextView tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title);
245 | tab_title.setTextColor(isSelect ? mTextSelectColor : mTextUnselectColor);
246 | }
247 | }
248 |
249 | private void calcOffset() {
250 | final View currentTabView = mTabsContainer.getChildAt(this.mCurrentTab);
251 | mCurrentP.left = currentTabView.getLeft();
252 | mCurrentP.right = currentTabView.getRight();
253 |
254 | final View lastTabView = mTabsContainer.getChildAt(this.mLastTab);
255 | mLastP.left = lastTabView.getLeft();
256 | mLastP.right = lastTabView.getRight();
257 |
258 | // Log.d("AAA", "mLastP--->" + mLastP.left + "&" + mLastP.right);
259 | // Log.d("AAA", "mCurrentP--->" + mCurrentP.left + "&" + mCurrentP.right);
260 | if (mLastP.left == mCurrentP.left && mLastP.right == mCurrentP.right) {
261 | invalidate();
262 | } else {
263 | mValueAnimator.setObjectValues(mLastP, mCurrentP);
264 | if (mIndicatorBounceEnable) {
265 | mValueAnimator.setInterpolator(mInterpolator);
266 | }
267 |
268 | if (mIndicatorAnimDuration < 0) {
269 | mIndicatorAnimDuration = mIndicatorBounceEnable ? 500 : 250;
270 | }
271 | mValueAnimator.setDuration(mIndicatorAnimDuration);
272 | mValueAnimator.start();
273 | }
274 | }
275 |
276 | private void calcIndicatorRect() {
277 | View currentTabView = mTabsContainer.getChildAt(this.mCurrentTab);
278 | float left = currentTabView.getLeft();
279 | float right = currentTabView.getRight();
280 |
281 | mIndicatorRect.left = (int) left;
282 | mIndicatorRect.right = (int) right;
283 |
284 | if (!mIndicatorAnimEnable) {
285 | if (mCurrentTab == 0) {
286 | /**The corners are ordered top-left, top-right, bottom-right, bottom-left*/
287 | mRadiusArr[0] = mIndicatorCornerRadius;
288 | mRadiusArr[1] = mIndicatorCornerRadius;
289 | mRadiusArr[2] = 0;
290 | mRadiusArr[3] = 0;
291 | mRadiusArr[4] = 0;
292 | mRadiusArr[5] = 0;
293 | mRadiusArr[6] = mIndicatorCornerRadius;
294 | mRadiusArr[7] = mIndicatorCornerRadius;
295 | } else if (mCurrentTab == mTabCount - 1) {
296 | /**The corners are ordered top-left, top-right, bottom-right, bottom-left*/
297 | mRadiusArr[0] = 0;
298 | mRadiusArr[1] = 0;
299 | mRadiusArr[2] = mIndicatorCornerRadius;
300 | mRadiusArr[3] = mIndicatorCornerRadius;
301 | mRadiusArr[4] = mIndicatorCornerRadius;
302 | mRadiusArr[5] = mIndicatorCornerRadius;
303 | mRadiusArr[6] = 0;
304 | mRadiusArr[7] = 0;
305 | } else {
306 | /**The corners are ordered top-left, top-right, bottom-right, bottom-left*/
307 | mRadiusArr[0] = 0;
308 | mRadiusArr[1] = 0;
309 | mRadiusArr[2] = 0;
310 | mRadiusArr[3] = 0;
311 | mRadiusArr[4] = 0;
312 | mRadiusArr[5] = 0;
313 | mRadiusArr[6] = 0;
314 | mRadiusArr[7] = 0;
315 | }
316 | } else {
317 | /**The corners are ordered top-left, top-right, bottom-right, bottom-left*/
318 | mRadiusArr[0] = mIndicatorCornerRadius;
319 | mRadiusArr[1] = mIndicatorCornerRadius;
320 | mRadiusArr[2] = mIndicatorCornerRadius;
321 | mRadiusArr[3] = mIndicatorCornerRadius;
322 | mRadiusArr[4] = mIndicatorCornerRadius;
323 | mRadiusArr[5] = mIndicatorCornerRadius;
324 | mRadiusArr[6] = mIndicatorCornerRadius;
325 | mRadiusArr[7] = mIndicatorCornerRadius;
326 | }
327 | }
328 |
329 | @Override
330 | public void onAnimationUpdate(ValueAnimator animation) {
331 | IndicatorPoint p = (IndicatorPoint) animation.getAnimatedValue();
332 | mIndicatorRect.left = (int) p.left;
333 | mIndicatorRect.right = (int) p.right;
334 | invalidate();
335 | }
336 |
337 | private boolean mIsFirstDraw = true;
338 |
339 | @Override
340 | protected void onDraw(Canvas canvas) {
341 | super.onDraw(canvas);
342 |
343 | if (isInEditMode() || mTabCount <= 0) {
344 | return;
345 | }
346 |
347 | int height = getHeight();
348 | int paddingLeft = getPaddingLeft();
349 |
350 | if (mIndicatorHeight < 0) {
351 | mIndicatorHeight = height - mIndicatorMarginTop - mIndicatorMarginBottom;
352 | }
353 |
354 | if (mIndicatorCornerRadius < 0 || mIndicatorCornerRadius > mIndicatorHeight / 2) {
355 | mIndicatorCornerRadius = mIndicatorHeight / 2;
356 | }
357 |
358 | //draw rect
359 | mRectDrawable.setColor(mBarColor);
360 | mRectDrawable.setStroke((int) mBarStrokeWidth, mBarStrokeColor);
361 | mRectDrawable.setCornerRadius(mIndicatorCornerRadius);
362 | mRectDrawable.setBounds(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
363 | mRectDrawable.draw(canvas);
364 |
365 | // draw divider
366 | if (!mIndicatorAnimEnable && mDividerWidth > 0) {
367 | mDividerPaint.setStrokeWidth(mDividerWidth);
368 | mDividerPaint.setColor(mDividerColor);
369 | for (int i = 0; i < mTabCount - 1; i++) {
370 | View tab = mTabsContainer.getChildAt(i);
371 | canvas.drawLine(paddingLeft + tab.getRight(), mDividerPadding, paddingLeft + tab.getRight(), height - mDividerPadding, mDividerPaint);
372 | }
373 | }
374 |
375 |
376 | //draw indicator line
377 | if (mIndicatorAnimEnable) {
378 | if (mIsFirstDraw) {
379 | mIsFirstDraw = false;
380 | calcIndicatorRect();
381 | }
382 | } else {
383 | calcIndicatorRect();
384 | }
385 |
386 | mIndicatorDrawable.setColor(mIndicatorColor);
387 | mIndicatorDrawable.setBounds(paddingLeft + (int) mIndicatorMarginLeft + mIndicatorRect.left,
388 | (int) mIndicatorMarginTop, (int) (paddingLeft + mIndicatorRect.right - mIndicatorMarginRight),
389 | (int) (mIndicatorMarginTop + mIndicatorHeight));
390 | mIndicatorDrawable.setCornerRadii(mRadiusArr);
391 | mIndicatorDrawable.draw(canvas);
392 |
393 | }
394 |
395 | //setter and getter
396 | public void setCurrentTab(int currentTab) {
397 | mLastTab = this.mCurrentTab;
398 | this.mCurrentTab = currentTab;
399 | updateTabSelection(currentTab);
400 | if (mFragmentChangeManager != null) {
401 | mFragmentChangeManager.setFragments(currentTab);
402 | }
403 | if (mIndicatorAnimEnable) {
404 | calcOffset();
405 | } else {
406 | invalidate();
407 | }
408 | }
409 |
410 | public void setTabPadding(float tabPadding) {
411 | this.mTabPadding = dp2px(tabPadding);
412 | updateTabStyles();
413 | }
414 |
415 | public void setTabSpaceEqual(boolean tabSpaceEqual) {
416 | this.mTabSpaceEqual = tabSpaceEqual;
417 | updateTabStyles();
418 | }
419 |
420 | public void setTabWidth(float tabWidth) {
421 | this.mTabWidth = dp2px(tabWidth);
422 | updateTabStyles();
423 | }
424 |
425 | public void setIndicatorColor(int indicatorColor) {
426 | this.mIndicatorColor = indicatorColor;
427 | invalidate();
428 | }
429 |
430 | public void setIndicatorHeight(float indicatorHeight) {
431 | this.mIndicatorHeight = dp2px(indicatorHeight);
432 | invalidate();
433 | }
434 |
435 | public void setIndicatorCornerRadius(float indicatorCornerRadius) {
436 | this.mIndicatorCornerRadius = dp2px(indicatorCornerRadius);
437 | invalidate();
438 | }
439 |
440 | public void setIndicatorMargin(float indicatorMarginLeft, float indicatorMarginTop,
441 | float indicatorMarginRight, float indicatorMarginBottom) {
442 | this.mIndicatorMarginLeft = dp2px(indicatorMarginLeft);
443 | this.mIndicatorMarginTop = dp2px(indicatorMarginTop);
444 | this.mIndicatorMarginRight = dp2px(indicatorMarginRight);
445 | this.mIndicatorMarginBottom = dp2px(indicatorMarginBottom);
446 | invalidate();
447 | }
448 |
449 | public void setIndicatorAnimDuration(long indicatorAnimDuration) {
450 | this.mIndicatorAnimDuration = indicatorAnimDuration;
451 | }
452 |
453 | public void setIndicatorAnimEnable(boolean indicatorAnimEnable) {
454 | this.mIndicatorAnimEnable = indicatorAnimEnable;
455 | }
456 |
457 | public void setIndicatorBounceEnable(boolean indicatorBounceEnable) {
458 | this.mIndicatorBounceEnable = indicatorBounceEnable;
459 | }
460 |
461 | public void setDividerColor(int dividerColor) {
462 | this.mDividerColor = dividerColor;
463 | invalidate();
464 | }
465 |
466 | public void setDividerWidth(float dividerWidth) {
467 | this.mDividerWidth = dp2px(dividerWidth);
468 | invalidate();
469 | }
470 |
471 | public void setDividerPadding(float dividerPadding) {
472 | this.mDividerPadding = dp2px(dividerPadding);
473 | invalidate();
474 | }
475 |
476 | public void setTextsize(float textsize) {
477 | this.mTextsize = sp2px(textsize);
478 | updateTabStyles();
479 | }
480 |
481 | public void setTextSelectColor(int textSelectColor) {
482 | this.mTextSelectColor = textSelectColor;
483 | updateTabStyles();
484 | }
485 |
486 | public void setTextUnselectColor(int textUnselectColor) {
487 | this.mTextUnselectColor = textUnselectColor;
488 | updateTabStyles();
489 | }
490 |
491 | public void setTextBold(boolean textBold) {
492 | this.mTextBold = textBold;
493 | updateTabStyles();
494 | }
495 |
496 | public void setTextAllCaps(boolean textAllCaps) {
497 | this.mTextAllCaps = textAllCaps;
498 | updateTabStyles();
499 | }
500 |
501 | public int getTabCount() {
502 | return mTabCount;
503 | }
504 |
505 | public int getCurrentTab() {
506 | return mCurrentTab;
507 | }
508 |
509 | public float getTabPadding() {
510 | return mTabPadding;
511 | }
512 |
513 | public boolean isTabSpaceEqual() {
514 | return mTabSpaceEqual;
515 | }
516 |
517 | public float getTabWidth() {
518 | return mTabWidth;
519 | }
520 |
521 | public int getIndicatorColor() {
522 | return mIndicatorColor;
523 | }
524 |
525 | public float getIndicatorHeight() {
526 | return mIndicatorHeight;
527 | }
528 |
529 | public float getIndicatorCornerRadius() {
530 | return mIndicatorCornerRadius;
531 | }
532 |
533 | public float getIndicatorMarginLeft() {
534 | return mIndicatorMarginLeft;
535 | }
536 |
537 | public float getIndicatorMarginTop() {
538 | return mIndicatorMarginTop;
539 | }
540 |
541 | public float getIndicatorMarginRight() {
542 | return mIndicatorMarginRight;
543 | }
544 |
545 | public float getIndicatorMarginBottom() {
546 | return mIndicatorMarginBottom;
547 | }
548 |
549 | public long getIndicatorAnimDuration() {
550 | return mIndicatorAnimDuration;
551 | }
552 |
553 | public boolean isIndicatorAnimEnable() {
554 | return mIndicatorAnimEnable;
555 | }
556 |
557 | public boolean isIndicatorBounceEnable() {
558 | return mIndicatorBounceEnable;
559 | }
560 |
561 | public int getDividerColor() {
562 | return mDividerColor;
563 | }
564 |
565 | public float getDividerWidth() {
566 | return mDividerWidth;
567 | }
568 |
569 | public float getDividerPadding() {
570 | return mDividerPadding;
571 | }
572 |
573 | public float getTextsize() {
574 | return mTextsize;
575 | }
576 |
577 | public int getTextSelectColor() {
578 | return mTextSelectColor;
579 | }
580 |
581 | public int getTextUnselectColor() {
582 | return mTextUnselectColor;
583 | }
584 |
585 | public boolean isTextBold() {
586 | return mTextBold;
587 | }
588 |
589 | public boolean isTextAllCaps() {
590 | return mTextAllCaps;
591 | }
592 |
593 | public TextView getTitleView(int tab) {
594 | View tabView = mTabsContainer.getChildAt(tab);
595 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title);
596 | return tv_tab_title;
597 | }
598 |
599 | //setter and getter
600 | // show MsgTipView
601 | private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
602 | private SparseArray mInitSetMap = new SparseArray<>();
603 |
604 | /**
605 | * 显示未读消息
606 | *
607 | * @param position 显示tab位置
608 | * @param num num小于等于0显示红点,num大于0显示数字
609 | */
610 | public void showMsg(int position, int num) {
611 | if (position >= mTabCount) {
612 | position = mTabCount - 1;
613 | }
614 |
615 | View tabView = mTabsContainer.getChildAt(position);
616 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip);
617 | if (tipView != null) {
618 | UnreadMsgUtils.show(tipView, num);
619 |
620 | if (mInitSetMap.get(position) != null && mInitSetMap.get(position)) {
621 | return;
622 | }
623 |
624 | setMsgMargin(position, 2, 2);
625 |
626 | mInitSetMap.put(position, true);
627 | }
628 | }
629 |
630 | /**
631 | * 显示未读红点
632 | *
633 | * @param position 显示tab位置
634 | */
635 | public void showDot(int position) {
636 | if (position >= mTabCount) {
637 | position = mTabCount - 1;
638 | }
639 | showMsg(position, 0);
640 | }
641 |
642 | public void hideMsg(int position) {
643 | if (position >= mTabCount) {
644 | position = mTabCount - 1;
645 | }
646 |
647 | View tabView = mTabsContainer.getChildAt(position);
648 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip);
649 | if (tipView != null) {
650 | tipView.setVisibility(View.GONE);
651 | }
652 | }
653 |
654 | /**
655 | * 设置提示红点偏移,注意
656 | * 1.控件为固定高度:参照点为tab内容的右上角
657 | * 2.控件高度不固定(WRAP_CONTENT):参照点为tab内容的右上角,此时高度已是红点的最高显示范围,所以这时bottomPadding其实就是topPadding
658 | */
659 | public void setMsgMargin(int position, float leftPadding, float bottomPadding) {
660 | if (position >= mTabCount) {
661 | position = mTabCount - 1;
662 | }
663 | View tabView = mTabsContainer.getChildAt(position);
664 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip);
665 | if (tipView != null) {
666 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title);
667 | mTextPaint.setTextSize(mTextsize);
668 | float textWidth = mTextPaint.measureText(tv_tab_title.getText().toString());
669 | float textHeight = mTextPaint.descent() - mTextPaint.ascent();
670 | MarginLayoutParams lp = (MarginLayoutParams) tipView.getLayoutParams();
671 |
672 | lp.leftMargin = dp2px(leftPadding);
673 | lp.topMargin = mHeight > 0 ? (int) (mHeight - textHeight) / 2 - dp2px(bottomPadding) : dp2px(bottomPadding);
674 |
675 | tipView.setLayoutParams(lp);
676 | }
677 | }
678 |
679 | /** 当前类只提供了少许设置未读消息属性的方法,可以通过该方法获取MsgView对象从而各种设置 */
680 | public MsgView getMsgView(int position) {
681 | if (position >= mTabCount) {
682 | position = mTabCount - 1;
683 | }
684 | View tabView = mTabsContainer.getChildAt(position);
685 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip);
686 | return tipView;
687 | }
688 |
689 | private OnTabSelectListener mListener;
690 |
691 | public void setOnTabSelectListener(OnTabSelectListener listener) {
692 | this.mListener = listener;
693 | }
694 |
695 | @Override
696 | protected Parcelable onSaveInstanceState() {
697 | Bundle bundle = new Bundle();
698 | bundle.putParcelable("instanceState", super.onSaveInstanceState());
699 | bundle.putInt("mCurrentTab", mCurrentTab);
700 | return bundle;
701 | }
702 |
703 | @Override
704 | protected void onRestoreInstanceState(Parcelable state) {
705 | if (state instanceof Bundle) {
706 | Bundle bundle = (Bundle) state;
707 | mCurrentTab = bundle.getInt("mCurrentTab");
708 | state = bundle.getParcelable("instanceState");
709 | if (mCurrentTab != 0 && mTabsContainer.getChildCount() > 0) {
710 | updateTabSelection(mCurrentTab);
711 | }
712 | }
713 | super.onRestoreInstanceState(state);
714 | }
715 |
716 | class IndicatorPoint {
717 | public float left;
718 | public float right;
719 | }
720 |
721 | private IndicatorPoint mCurrentP = new IndicatorPoint();
722 | private IndicatorPoint mLastP = new IndicatorPoint();
723 |
724 | class PointEvaluator implements TypeEvaluator {
725 | @Override
726 | public IndicatorPoint evaluate(float fraction, IndicatorPoint startValue, IndicatorPoint endValue) {
727 | float left = startValue.left + fraction * (endValue.left - startValue.left);
728 | float right = startValue.right + fraction * (endValue.right - startValue.right);
729 | IndicatorPoint point = new IndicatorPoint();
730 | point.left = left;
731 | point.right = right;
732 | return point;
733 | }
734 | }
735 |
736 | protected int dp2px(float dp) {
737 | final float scale = mContext.getResources().getDisplayMetrics().density;
738 | return (int) (dp * scale + 0.5f);
739 | }
740 |
741 | protected int sp2px(float sp) {
742 | final float scale = this.mContext.getResources().getDisplayMetrics().scaledDensity;
743 | return (int) (sp * scale + 0.5f);
744 | }
745 | }
746 |
--------------------------------------------------------------------------------
/tabLayoutLibrary/src/main/java/com/flyco/tablayout/SlidingTabLayout.java:
--------------------------------------------------------------------------------
1 | package com.flyco.tablayout;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.Path;
9 | import android.graphics.Rect;
10 | import android.graphics.drawable.GradientDrawable;
11 | import android.os.Bundle;
12 | import android.os.Parcelable;
13 | import android.support.v4.app.Fragment;
14 | import android.support.v4.app.FragmentActivity;
15 | import android.support.v4.app.FragmentManager;
16 | import android.support.v4.app.FragmentPagerAdapter;
17 | import android.support.v4.view.PagerAdapter;
18 | import android.support.v4.view.ViewPager;
19 | import android.util.AttributeSet;
20 | import android.util.SparseArray;
21 | import android.util.TypedValue;
22 | import android.view.Gravity;
23 | import android.view.View;
24 | import android.view.ViewGroup;
25 | import android.widget.HorizontalScrollView;
26 | import android.widget.LinearLayout;
27 | import android.widget.TextView;
28 |
29 | import com.flyco.tablayout.listener.OnTabSelectListener;
30 | import com.flyco.tablayout.utils.UnreadMsgUtils;
31 | import com.flyco.tablayout.widget.MsgView;
32 |
33 | import java.util.ArrayList;
34 | import java.util.Collections;
35 |
36 | /** 滑动TabLayout,对于ViewPager的依赖性强 */
37 | public class SlidingTabLayout extends HorizontalScrollView implements ViewPager.OnPageChangeListener {
38 | private Context mContext;
39 | private ViewPager mViewPager;
40 | private ArrayList mTitles;
41 | private LinearLayout mTabsContainer;
42 | private int mCurrentTab;
43 | private float mCurrentPositionOffset;
44 | private int mTabCount;
45 | /** 用于绘制显示器 */
46 | private Rect mIndicatorRect = new Rect();
47 | /** 用于实现滚动居中 */
48 | private Rect mTabRect = new Rect();
49 | private GradientDrawable mIndicatorDrawable = new GradientDrawable();
50 |
51 | private Paint mRectPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
52 | private Paint mDividerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
53 | private Paint mTrianglePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
54 | private Path mTrianglePath = new Path();
55 | private static final int STYLE_NORMAL = 0;
56 | private static final int STYLE_TRIANGLE = 1;
57 | private static final int STYLE_BLOCK = 2;
58 | private int mIndicatorStyle = STYLE_NORMAL;
59 |
60 | private float mTabPadding;
61 | private boolean mTabSpaceEqual;
62 | private float mTabWidth;
63 |
64 | /** indicator */
65 | private int mIndicatorColor;
66 | private float mIndicatorHeight;
67 | private float mIndicatorWidth;
68 | private float mIndicatorCornerRadius;
69 | private float mIndicatorMarginLeft;
70 | private float mIndicatorMarginTop;
71 | private float mIndicatorMarginRight;
72 | private float mIndicatorMarginBottom;
73 | private int mIndicatorGravity;
74 | private boolean mIndicatorWidthEqualTitle;
75 |
76 | /** underline */
77 | private int mUnderlineColor;
78 | private float mUnderlineHeight;
79 | private int mUnderlineGravity;
80 |
81 | /** divider */
82 | private int mDividerColor;
83 | private float mDividerWidth;
84 | private float mDividerPadding;
85 |
86 | /** title */
87 | private float mTextsize;
88 | private int mTextSelectColor;
89 | private int mTextUnselectColor;
90 | private boolean mTextBold;
91 | private boolean mTextAllCaps;
92 |
93 | private int mLastScrollX;
94 | private int mHeight;
95 |
96 | public SlidingTabLayout(Context context) {
97 | this(context, null, 0);
98 | }
99 |
100 | public SlidingTabLayout(Context context, AttributeSet attrs) {
101 | this(context, attrs, 0);
102 | }
103 |
104 | public SlidingTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
105 | super(context, attrs, defStyleAttr);
106 | setFillViewport(true);//设置滚动视图是否可以伸缩其内容以填充视口
107 | setWillNotDraw(false);//重写onDraw方法,需要调用这个方法来清除flag
108 | setClipChildren(false);
109 | setClipToPadding(false);
110 |
111 | this.mContext = context;
112 | mTabsContainer = new LinearLayout(context);
113 | addView(mTabsContainer);
114 |
115 | obtainAttributes(context, attrs);
116 |
117 | //get layout_height
118 | String height = attrs.getAttributeValue("http://schemas.android.com/apk/res/android", "layout_height");
119 |
120 | //create ViewPager
121 | if (height.equals(ViewGroup.LayoutParams.MATCH_PARENT + "")) {
122 | } else if (height.equals(ViewGroup.LayoutParams.WRAP_CONTENT + "")) {
123 | } else {
124 | int[] systemAttrs = {android.R.attr.layout_height};
125 | TypedArray a = context.obtainStyledAttributes(attrs, systemAttrs);
126 | mHeight = a.getDimensionPixelSize(0, ViewGroup.LayoutParams.WRAP_CONTENT);
127 | a.recycle();
128 | }
129 | }
130 |
131 | private void obtainAttributes(Context context, AttributeSet attrs) {
132 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingTabLayout);
133 |
134 | mIndicatorStyle = ta.getInt(R.styleable.SlidingTabLayout_tl_indicator_style, STYLE_NORMAL);
135 | mIndicatorColor = ta.getColor(R.styleable.SlidingTabLayout_tl_indicator_color, Color.parseColor(mIndicatorStyle == STYLE_BLOCK ? "#4B6A87" : "#ffffff"));
136 | mIndicatorHeight = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_height,
137 | dp2px(mIndicatorStyle == STYLE_TRIANGLE ? 4 : (mIndicatorStyle == STYLE_BLOCK ? -1 : 2)));
138 | mIndicatorWidth = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_width, dp2px(mIndicatorStyle == STYLE_TRIANGLE ? 10 : -1));
139 | mIndicatorCornerRadius = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_corner_radius, dp2px(mIndicatorStyle == STYLE_BLOCK ? -1 : 0));
140 | mIndicatorMarginLeft = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_margin_left, dp2px(0));
141 | mIndicatorMarginTop = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_margin_top, dp2px(mIndicatorStyle == STYLE_BLOCK ? 7 : 0));
142 | mIndicatorMarginRight = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_margin_right, dp2px(0));
143 | mIndicatorMarginBottom = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_margin_bottom, dp2px(mIndicatorStyle == STYLE_BLOCK ? 7 : 0));
144 | mIndicatorGravity = ta.getInt(R.styleable.SlidingTabLayout_tl_indicator_gravity, Gravity.BOTTOM);
145 | mIndicatorWidthEqualTitle = ta.getBoolean(R.styleable.SlidingTabLayout_tl_indicator_width_equal_title, false);
146 |
147 | mUnderlineColor = ta.getColor(R.styleable.SlidingTabLayout_tl_underline_color, Color.parseColor("#ffffff"));
148 | mUnderlineHeight = ta.getDimension(R.styleable.SlidingTabLayout_tl_underline_height, dp2px(0));
149 | mUnderlineGravity = ta.getInt(R.styleable.SlidingTabLayout_tl_underline_gravity, Gravity.BOTTOM);
150 |
151 | mDividerColor = ta.getColor(R.styleable.SlidingTabLayout_tl_divider_color, Color.parseColor("#ffffff"));
152 | mDividerWidth = ta.getDimension(R.styleable.SlidingTabLayout_tl_divider_width, dp2px(0));
153 | mDividerPadding = ta.getDimension(R.styleable.SlidingTabLayout_tl_divider_padding, dp2px(12));
154 |
155 | mTextsize = ta.getDimension(R.styleable.SlidingTabLayout_tl_textsize, sp2px(14));
156 | mTextSelectColor = ta.getColor(R.styleable.SlidingTabLayout_tl_textSelectColor, Color.parseColor("#ffffff"));
157 | mTextUnselectColor = ta.getColor(R.styleable.SlidingTabLayout_tl_textUnselectColor, Color.parseColor("#AAffffff"));
158 | mTextBold = ta.getBoolean(R.styleable.SlidingTabLayout_tl_textBold, false);
159 | mTextAllCaps = ta.getBoolean(R.styleable.SlidingTabLayout_tl_textAllCaps, false);
160 |
161 | mTabSpaceEqual = ta.getBoolean(R.styleable.SlidingTabLayout_tl_tab_space_equal, false);
162 | mTabWidth = ta.getDimension(R.styleable.SlidingTabLayout_tl_tab_width, dp2px(-1));
163 | mTabPadding = ta.getDimension(R.styleable.SlidingTabLayout_tl_tab_padding, mTabSpaceEqual || mTabWidth > 0 ? dp2px(0) : dp2px(20));
164 |
165 | ta.recycle();
166 | }
167 |
168 | /** 关联ViewPager */
169 | public void setViewPager(ViewPager vp) {
170 | if (vp == null || vp.getAdapter() == null) {
171 | throw new IllegalStateException("ViewPager or ViewPager adapter can not be NULL !");
172 | }
173 |
174 | this.mViewPager = vp;
175 |
176 | this.mViewPager.removeOnPageChangeListener(this);
177 | this.mViewPager.addOnPageChangeListener(this);
178 | notifyDataSetChanged();
179 | }
180 |
181 | /** 关联ViewPager,用于不想在ViewPager适配器中设置titles数据的情况 */
182 | public void setViewPager(ViewPager vp, String[] titles) {
183 | if (vp == null || vp.getAdapter() == null) {
184 | throw new IllegalStateException("ViewPager or ViewPager adapter can not be NULL !");
185 | }
186 |
187 | if (titles == null || titles.length == 0) {
188 | throw new IllegalStateException("Titles can not be EMPTY !");
189 | }
190 |
191 | if (titles.length != vp.getAdapter().getCount()) {
192 | throw new IllegalStateException("Titles length must be the same as the page count !");
193 | }
194 |
195 | this.mViewPager = vp;
196 | mTitles = new ArrayList<>();
197 | Collections.addAll(mTitles, titles);
198 |
199 | this.mViewPager.removeOnPageChangeListener(this);
200 | this.mViewPager.addOnPageChangeListener(this);
201 | notifyDataSetChanged();
202 | }
203 |
204 | /** 关联ViewPager,用于连适配器都不想自己实例化的情况 */
205 | public void setViewPager(ViewPager vp, String[] titles, FragmentActivity fa, ArrayList fragments) {
206 | if (vp == null) {
207 | throw new IllegalStateException("ViewPager can not be NULL !");
208 | }
209 |
210 | if (titles == null || titles.length == 0) {
211 | throw new IllegalStateException("Titles can not be EMPTY !");
212 | }
213 |
214 | this.mViewPager = vp;
215 | this.mViewPager.setAdapter(new InnerPagerAdapter(fa.getSupportFragmentManager(), fragments, titles));
216 |
217 | this.mViewPager.removeOnPageChangeListener(this);
218 | this.mViewPager.addOnPageChangeListener(this);
219 | notifyDataSetChanged();
220 | }
221 |
222 | /** 更新数据 */
223 | public void notifyDataSetChanged() {
224 | mTabsContainer.removeAllViews();
225 | this.mTabCount = mTitles == null ? mViewPager.getAdapter().getCount() : mTitles.size();
226 | View tabView;
227 | for (int i = 0; i < mTabCount; i++) {
228 | tabView = View.inflate(mContext, R.layout.layout_tab, null);
229 | CharSequence pageTitle = mTitles == null ? mViewPager.getAdapter().getPageTitle(i) : mTitles.get(i);
230 | addTab(i, pageTitle.toString(), tabView);
231 | }
232 |
233 | updateTabStyles();
234 | }
235 |
236 | public void addNewTab(String title) {
237 | View tabView = View.inflate(mContext, R.layout.layout_tab, null);
238 | if (mTitles != null) {
239 | mTitles.add(title);
240 | }
241 |
242 | CharSequence pageTitle = mTitles == null ? mViewPager.getAdapter().getPageTitle(mTabCount) : mTitles.get(mTabCount);
243 | addTab(mTabCount, pageTitle.toString(), tabView);
244 | this.mTabCount = mTitles == null ? mViewPager.getAdapter().getCount() : mTitles.size();
245 |
246 | updateTabStyles();
247 | }
248 |
249 | /** 创建并添加tab */
250 | private void addTab(final int position, String title, View tabView) {
251 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title);
252 | if (tv_tab_title != null) {
253 | if (title != null) tv_tab_title.setText(title);
254 | }
255 |
256 | tabView.setOnClickListener(new OnClickListener() {
257 | @Override
258 | public void onClick(View v) {
259 | int position = mTabsContainer.indexOfChild(v);
260 | if (position != -1) {
261 | if (mViewPager.getCurrentItem() != position) {
262 | mViewPager.setCurrentItem(position);
263 | if (mListener != null) {
264 | mListener.onTabSelect(position);
265 | }
266 | } else {
267 | if (mListener != null) {
268 | mListener.onTabReselect(position);
269 | }
270 | }
271 | }
272 | }
273 | });
274 |
275 | /** 每一个Tab的布局参数 */
276 | LinearLayout.LayoutParams lp_tab = mTabSpaceEqual ?
277 | new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f) :
278 | new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
279 | if (mTabWidth > 0) {
280 | lp_tab = new LinearLayout.LayoutParams((int) mTabWidth, LayoutParams.MATCH_PARENT);
281 | }
282 |
283 | mTabsContainer.addView(tabView, position, lp_tab);
284 | }
285 |
286 | private void updateTabStyles() {
287 | for (int i = 0; i < mTabCount; i++) {
288 | View v = mTabsContainer.getChildAt(i);
289 | // v.setPadding((int) mTabPadding, v.getPaddingTop(), (int) mTabPadding, v.getPaddingBottom());
290 | TextView tv_tab_title = (TextView) v.findViewById(R.id.tv_tab_title);
291 | if (tv_tab_title != null) {
292 | tv_tab_title.setTextColor(i == mCurrentTab ? mTextSelectColor : mTextUnselectColor);
293 | tv_tab_title.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextsize);
294 | tv_tab_title.setPadding((int) mTabPadding, 0, (int) mTabPadding, 0);
295 | if (mTextAllCaps) {
296 | tv_tab_title.setText(tv_tab_title.getText().toString().toUpperCase());
297 | }
298 |
299 | if (mTextBold) {
300 | tv_tab_title.getPaint().setFakeBoldText(mTextBold);
301 | }
302 | }
303 | }
304 | }
305 |
306 | @Override
307 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
308 | /**
309 | * position:当前View的位置
310 | * mCurrentPositionOffset:当前View的偏移量比例.[0,1)
311 | */
312 | this.mCurrentTab = position;
313 | this.mCurrentPositionOffset = positionOffset;
314 | scrollToCurrentTab();
315 | invalidate();
316 | }
317 |
318 | @Override
319 | public void onPageSelected(int position) {
320 | updateTabSelection(position);
321 | }
322 |
323 | @Override
324 | public void onPageScrollStateChanged(int state) {
325 | }
326 |
327 | /** HorizontalScrollView滚到当前tab,并且居中显示 */
328 | private void scrollToCurrentTab() {
329 | if (mTabCount <= 0) {
330 | return;
331 | }
332 |
333 | int offset = (int) (mCurrentPositionOffset * mTabsContainer.getChildAt(mCurrentTab).getWidth());
334 | /**当前Tab的left+当前Tab的Width乘以positionOffset*/
335 | int newScrollX = mTabsContainer.getChildAt(mCurrentTab).getLeft() + offset;
336 |
337 | if (mCurrentTab > 0 || offset > 0) {
338 | /**HorizontalScrollView移动到当前tab,并居中*/
339 | newScrollX -= getWidth() / 2 - getPaddingLeft();
340 | calcIndicatorRect();
341 | newScrollX += ((mTabRect.right - mTabRect.left) / 2);
342 | }
343 |
344 | if (newScrollX != mLastScrollX) {
345 | mLastScrollX = newScrollX;
346 | /** scrollTo(int x,int y):x,y代表的不是坐标点,而是偏移量
347 | * x:表示离起始位置的x水平方向的偏移量
348 | * y:表示离起始位置的y垂直方向的偏移量
349 | */
350 | scrollTo(newScrollX, 0);
351 | }
352 | }
353 |
354 | private void updateTabSelection(int position) {
355 | for (int i = 0; i < mTabCount; ++i) {
356 | View tabView = mTabsContainer.getChildAt(i);
357 | final boolean isSelect = i == position;
358 | if (tabView != null) {
359 | TextView tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title);
360 |
361 | if (tab_title != null) {
362 | tab_title.setTextColor(isSelect ? mTextSelectColor : mTextUnselectColor);
363 | }
364 | }
365 |
366 | }
367 | }
368 |
369 | private float margin;
370 |
371 | private void calcIndicatorRect() {
372 | View currentTabView = mTabsContainer.getChildAt(this.mCurrentTab);
373 | float left = currentTabView.getLeft();
374 | float right = currentTabView.getRight();
375 |
376 | //for mIndicatorWidthEqualTitle
377 | if (mIndicatorStyle == STYLE_NORMAL && mIndicatorWidthEqualTitle) {
378 | TextView tab_title = (TextView) currentTabView.findViewById(R.id.tv_tab_title);
379 | mTextPaint.setTextSize(mTextsize);
380 | float textWidth = mTextPaint.measureText(tab_title.getText().toString());
381 | margin = (right - left - textWidth) / 2;
382 | }
383 |
384 | if (this.mCurrentTab < mTabCount - 1) {
385 | View nextTabView = mTabsContainer.getChildAt(this.mCurrentTab + 1);
386 | float nextTabLeft = nextTabView.getLeft();
387 | float nextTabRight = nextTabView.getRight();
388 |
389 | left = left + mCurrentPositionOffset * (nextTabLeft - left);
390 | right = right + mCurrentPositionOffset * (nextTabRight - right);
391 |
392 | //for mIndicatorWidthEqualTitle
393 | if (mIndicatorStyle == STYLE_NORMAL && mIndicatorWidthEqualTitle) {
394 | TextView next_tab_title = (TextView) nextTabView.findViewById(R.id.tv_tab_title);
395 | mTextPaint.setTextSize(mTextsize);
396 | float nextTextWidth = mTextPaint.measureText(next_tab_title.getText().toString());
397 | float nextMargin = (nextTabRight - nextTabLeft - nextTextWidth) / 2;
398 | margin = margin + mCurrentPositionOffset * (nextMargin - margin);
399 | }
400 | }
401 |
402 | mIndicatorRect.left = (int) left;
403 | mIndicatorRect.right = (int) right;
404 | //for mIndicatorWidthEqualTitle
405 | if (mIndicatorStyle == STYLE_NORMAL && mIndicatorWidthEqualTitle) {
406 | mIndicatorRect.left = (int) (left + margin - 1);
407 | mIndicatorRect.right = (int) (right - margin - 1);
408 | }
409 |
410 | mTabRect.left = (int) left;
411 | mTabRect.right = (int) right;
412 |
413 | if (mIndicatorWidth < 0) { //indicatorWidth小于0时,原jpardogo's PagerSlidingTabStrip
414 |
415 | } else {//indicatorWidth大于0时,圆角矩形以及三角形
416 | float indicatorLeft = currentTabView.getLeft() + (currentTabView.getWidth() - mIndicatorWidth) / 2;
417 |
418 | if (this.mCurrentTab < mTabCount - 1) {
419 | View nextTab = mTabsContainer.getChildAt(this.mCurrentTab + 1);
420 | indicatorLeft = indicatorLeft + mCurrentPositionOffset * (currentTabView.getWidth() / 2 + nextTab.getWidth() / 2);
421 | }
422 |
423 | mIndicatorRect.left = (int) indicatorLeft;
424 | mIndicatorRect.right = (int) (mIndicatorRect.left + mIndicatorWidth);
425 | }
426 | }
427 |
428 | @Override
429 | protected void onDraw(Canvas canvas) {
430 | super.onDraw(canvas);
431 |
432 | if (isInEditMode() || mTabCount <= 0) {
433 | return;
434 | }
435 |
436 | int height = getHeight();
437 | int paddingLeft = getPaddingLeft();
438 | // draw divider
439 | if (mDividerWidth > 0) {
440 | mDividerPaint.setStrokeWidth(mDividerWidth);
441 | mDividerPaint.setColor(mDividerColor);
442 | for (int i = 0; i < mTabCount - 1; i++) {
443 | View tab = mTabsContainer.getChildAt(i);
444 | canvas.drawLine(paddingLeft + tab.getRight(), mDividerPadding, paddingLeft + tab.getRight(), height - mDividerPadding, mDividerPaint);
445 | }
446 | }
447 |
448 | // draw underline
449 | if (mUnderlineHeight > 0) {
450 | mRectPaint.setColor(mUnderlineColor);
451 | if (mUnderlineGravity == Gravity.BOTTOM) {
452 | canvas.drawRect(paddingLeft, height - mUnderlineHeight, mTabsContainer.getWidth() + paddingLeft, height, mRectPaint);
453 | } else {
454 | canvas.drawRect(paddingLeft, 0, mTabsContainer.getWidth() + paddingLeft, mUnderlineHeight, mRectPaint);
455 | }
456 | }
457 |
458 | //draw indicator line
459 |
460 | calcIndicatorRect();
461 | if (mIndicatorStyle == STYLE_TRIANGLE) {
462 | if (mIndicatorHeight > 0) {
463 | mTrianglePaint.setColor(mIndicatorColor);
464 | mTrianglePath.reset();
465 | mTrianglePath.moveTo(paddingLeft + mIndicatorRect.left, height);
466 | mTrianglePath.lineTo(paddingLeft + mIndicatorRect.left / 2 + mIndicatorRect.right / 2, height - mIndicatorHeight);
467 | mTrianglePath.lineTo(paddingLeft + mIndicatorRect.right, height);
468 | mTrianglePath.close();
469 | canvas.drawPath(mTrianglePath, mTrianglePaint);
470 | }
471 | } else if (mIndicatorStyle == STYLE_BLOCK) {
472 | if (mIndicatorHeight < 0) {
473 | mIndicatorHeight = height - mIndicatorMarginTop - mIndicatorMarginBottom;
474 | } else {
475 |
476 | }
477 |
478 | if (mIndicatorHeight > 0) {
479 | if (mIndicatorCornerRadius < 0 || mIndicatorCornerRadius > mIndicatorHeight / 2) {
480 | mIndicatorCornerRadius = mIndicatorHeight / 2;
481 | }
482 |
483 | mIndicatorDrawable.setColor(mIndicatorColor);
484 | mIndicatorDrawable.setBounds(paddingLeft + (int) mIndicatorMarginLeft + mIndicatorRect.left,
485 | (int) mIndicatorMarginTop, (int) (paddingLeft + mIndicatorRect.right - mIndicatorMarginRight),
486 | (int) (mIndicatorMarginTop + mIndicatorHeight));
487 | mIndicatorDrawable.setCornerRadius(mIndicatorCornerRadius);
488 | mIndicatorDrawable.draw(canvas);
489 | }
490 | } else {
491 | /* mRectPaint.setColor(mIndicatorColor);
492 | calcIndicatorRect();
493 | canvas.drawRect(getPaddingLeft() + mIndicatorRect.left, getHeight() - mIndicatorHeight,
494 | mIndicatorRect.right + getPaddingLeft(), getHeight(), mRectPaint);*/
495 |
496 | if (mIndicatorHeight > 0) {
497 | mIndicatorDrawable.setColor(mIndicatorColor);
498 |
499 | if (mIndicatorGravity == Gravity.BOTTOM) {
500 | mIndicatorDrawable.setBounds(paddingLeft + (int) mIndicatorMarginLeft + mIndicatorRect.left,
501 | height - (int) mIndicatorHeight - (int) mIndicatorMarginBottom,
502 | paddingLeft + mIndicatorRect.right - (int) mIndicatorMarginRight,
503 | height - (int) mIndicatorMarginBottom);
504 | } else {
505 | mIndicatorDrawable.setBounds(paddingLeft + (int) mIndicatorMarginLeft + mIndicatorRect.left,
506 | (int) mIndicatorMarginTop,
507 | paddingLeft + mIndicatorRect.right - (int) mIndicatorMarginRight,
508 | (int) mIndicatorHeight + (int) mIndicatorMarginTop);
509 | }
510 | mIndicatorDrawable.setCornerRadius(mIndicatorCornerRadius);
511 | mIndicatorDrawable.draw(canvas);
512 | }
513 | }
514 | }
515 |
516 | //setter and getter
517 | public void setCurrentTab(int currentTab) {
518 | this.mCurrentTab = currentTab;
519 | mViewPager.setCurrentItem(currentTab);
520 | }
521 |
522 | public void setIndicatorStyle(int indicatorStyle) {
523 | this.mIndicatorStyle = indicatorStyle;
524 | invalidate();
525 | }
526 |
527 | public void setTabPadding(float tabPadding) {
528 | this.mTabPadding = dp2px(tabPadding);
529 | updateTabStyles();
530 | }
531 |
532 | public void setTabSpaceEqual(boolean tabSpaceEqual) {
533 | this.mTabSpaceEqual = tabSpaceEqual;
534 | updateTabStyles();
535 | }
536 |
537 | public void setTabWidth(float tabWidth) {
538 | this.mTabWidth = dp2px(tabWidth);
539 | updateTabStyles();
540 | }
541 |
542 | public void setIndicatorColor(int indicatorColor) {
543 | this.mIndicatorColor = indicatorColor;
544 | invalidate();
545 | }
546 |
547 | public void setIndicatorHeight(float indicatorHeight) {
548 | this.mIndicatorHeight = dp2px(indicatorHeight);
549 | invalidate();
550 | }
551 |
552 | public void setIndicatorWidth(float indicatorWidth) {
553 | this.mIndicatorWidth = dp2px(indicatorWidth);
554 | invalidate();
555 | }
556 |
557 | public void setIndicatorCornerRadius(float indicatorCornerRadius) {
558 | this.mIndicatorCornerRadius = dp2px(indicatorCornerRadius);
559 | invalidate();
560 | }
561 |
562 | public void setIndicatorGravity(int indicatorGravity) {
563 | this.mIndicatorGravity = indicatorGravity;
564 | invalidate();
565 | }
566 |
567 | public void setIndicatorMargin(float indicatorMarginLeft, float indicatorMarginTop,
568 | float indicatorMarginRight, float indicatorMarginBottom) {
569 | this.mIndicatorMarginLeft = dp2px(indicatorMarginLeft);
570 | this.mIndicatorMarginTop = dp2px(indicatorMarginTop);
571 | this.mIndicatorMarginRight = dp2px(indicatorMarginRight);
572 | this.mIndicatorMarginBottom = dp2px(indicatorMarginBottom);
573 | invalidate();
574 | }
575 |
576 | public void setIndicatorWidthEqualTitle(boolean indicatorWidthEqualTitle) {
577 | this.mIndicatorWidthEqualTitle = indicatorWidthEqualTitle;
578 | invalidate();
579 | }
580 |
581 | public void setUnderlineColor(int underlineColor) {
582 | this.mUnderlineColor = underlineColor;
583 | invalidate();
584 | }
585 |
586 | public void setUnderlineHeight(float underlineHeight) {
587 | this.mUnderlineHeight = dp2px(underlineHeight);
588 | invalidate();
589 | }
590 |
591 | public void setUnderlineGravity(int underlineGravity) {
592 | this.mUnderlineGravity = underlineGravity;
593 | invalidate();
594 | }
595 |
596 | public void setDividerColor(int dividerColor) {
597 | this.mDividerColor = dividerColor;
598 | invalidate();
599 | }
600 |
601 | public void setDividerWidth(float dividerWidth) {
602 | this.mDividerWidth = dp2px(dividerWidth);
603 | invalidate();
604 | }
605 |
606 | public void setDividerPadding(float dividerPadding) {
607 | this.mDividerPadding = dp2px(dividerPadding);
608 | invalidate();
609 | }
610 |
611 | public void setTextsize(float textsize) {
612 | this.mTextsize = sp2px(textsize);
613 | updateTabStyles();
614 | }
615 |
616 | public void setTextSelectColor(int textSelectColor) {
617 | this.mTextSelectColor = textSelectColor;
618 | updateTabStyles();
619 | }
620 |
621 | public void setTextUnselectColor(int textUnselectColor) {
622 | this.mTextUnselectColor = textUnselectColor;
623 | updateTabStyles();
624 | }
625 |
626 | public void setTextBold(boolean textBold) {
627 | this.mTextBold = textBold;
628 | updateTabStyles();
629 | }
630 |
631 | public void setTextAllCaps(boolean textAllCaps) {
632 | this.mTextAllCaps = textAllCaps;
633 | updateTabStyles();
634 | }
635 |
636 |
637 | public int getTabCount() {
638 | return mTabCount;
639 | }
640 |
641 | public int getCurrentTab() {
642 | return mCurrentTab;
643 | }
644 |
645 | public int getIndicatorStyle() {
646 | return mIndicatorStyle;
647 | }
648 |
649 | public float getTabPadding() {
650 | return mTabPadding;
651 | }
652 |
653 | public boolean isTabSpaceEqual() {
654 | return mTabSpaceEqual;
655 | }
656 |
657 | public float getTabWidth() {
658 | return mTabWidth;
659 | }
660 |
661 | public int getIndicatorColor() {
662 | return mIndicatorColor;
663 | }
664 |
665 | public float getIndicatorHeight() {
666 | return mIndicatorHeight;
667 | }
668 |
669 | public float getIndicatorWidth() {
670 | return mIndicatorWidth;
671 | }
672 |
673 | public float getIndicatorCornerRadius() {
674 | return mIndicatorCornerRadius;
675 | }
676 |
677 | public float getIndicatorMarginLeft() {
678 | return mIndicatorMarginLeft;
679 | }
680 |
681 | public float getIndicatorMarginTop() {
682 | return mIndicatorMarginTop;
683 | }
684 |
685 | public float getIndicatorMarginRight() {
686 | return mIndicatorMarginRight;
687 | }
688 |
689 | public float getIndicatorMarginBottom() {
690 | return mIndicatorMarginBottom;
691 | }
692 |
693 | public int getUnderlineColor() {
694 | return mUnderlineColor;
695 | }
696 |
697 | public float getUnderlineHeight() {
698 | return mUnderlineHeight;
699 | }
700 |
701 | public int getDividerColor() {
702 | return mDividerColor;
703 | }
704 |
705 | public float getDividerWidth() {
706 | return mDividerWidth;
707 | }
708 |
709 | public float getDividerPadding() {
710 | return mDividerPadding;
711 | }
712 |
713 | public float getTextsize() {
714 | return mTextsize;
715 | }
716 |
717 | public int getTextSelectColor() {
718 | return mTextSelectColor;
719 | }
720 |
721 | public int getTextUnselectColor() {
722 | return mTextUnselectColor;
723 | }
724 |
725 | public boolean isTextBold() {
726 | return mTextBold;
727 | }
728 |
729 | public boolean isTextAllCaps() {
730 | return mTextAllCaps;
731 | }
732 |
733 | public TextView getTitleView(int tab) {
734 | View tabView = mTabsContainer.getChildAt(tab);
735 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title);
736 | return tv_tab_title;
737 | }
738 |
739 | //setter and getter
740 |
741 | // show MsgTipView
742 | private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
743 | private SparseArray mInitSetMap = new SparseArray<>();
744 |
745 | /**
746 | * 显示未读消息
747 | *
748 | * @param position 显示tab位置
749 | * @param num num小于等于0显示红点,num大于0显示数字
750 | */
751 | public void showMsg(int position, int num) {
752 | if (position >= mTabCount) {
753 | position = mTabCount - 1;
754 | }
755 |
756 | View tabView = mTabsContainer.getChildAt(position);
757 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip);
758 | if (tipView != null) {
759 | UnreadMsgUtils.show(tipView, num);
760 |
761 | if (mInitSetMap.get(position) != null && mInitSetMap.get(position)) {
762 | return;
763 | }
764 |
765 | setMsgMargin(position, 4, 2);
766 | mInitSetMap.put(position, true);
767 | }
768 | }
769 |
770 | /**
771 | * 显示未读红点
772 | *
773 | * @param position 显示tab位置
774 | */
775 | public void showDot(int position) {
776 | if (position >= mTabCount) {
777 | position = mTabCount - 1;
778 | }
779 | showMsg(position, 0);
780 | }
781 |
782 | /** 隐藏未读消息 */
783 | public void hideMsg(int position) {
784 | if (position >= mTabCount) {
785 | position = mTabCount - 1;
786 | }
787 |
788 | View tabView = mTabsContainer.getChildAt(position);
789 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip);
790 |
791 | if (tipView != null) {
792 | tipView.setVisibility(View.GONE);
793 | }
794 | }
795 |
796 | /** 设置未读消息偏移,原点为文字的右上角.当控件高度固定,消息提示位置易控制,显示效果佳 */
797 | public void setMsgMargin(int position, float leftPadding, float bottomPadding) {
798 | if (position >= mTabCount) {
799 | position = mTabCount - 1;
800 | }
801 | View tabView = mTabsContainer.getChildAt(position);
802 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip);
803 | if (tipView != null) {
804 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title);
805 | mTextPaint.setTextSize(mTextsize);
806 | float textWidth = mTextPaint.measureText(tv_tab_title.getText().toString());
807 | float textHeight = mTextPaint.descent() - mTextPaint.ascent();
808 | MarginLayoutParams lp = (MarginLayoutParams) tipView.getLayoutParams();
809 | lp.leftMargin = mTabWidth >= 0 ? (int) (mTabWidth / 2 + textWidth / 2 + dp2px(leftPadding)) : (int) (mTabPadding + textWidth + dp2px(leftPadding));
810 | lp.topMargin = mHeight > 0 ? (int) (mHeight - textHeight) / 2 - dp2px(bottomPadding) : 0;
811 | tipView.setLayoutParams(lp);
812 | }
813 | }
814 |
815 | /** 当前类只提供了少许设置未读消息属性的方法,可以通过该方法获取MsgView对象从而各种设置 */
816 | public MsgView getMsgView(int position) {
817 | if (position >= mTabCount) {
818 | position = mTabCount - 1;
819 | }
820 | View tabView = mTabsContainer.getChildAt(position);
821 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip);
822 | return tipView;
823 | }
824 |
825 | private OnTabSelectListener mListener;
826 |
827 | public void setOnTabSelectListener(OnTabSelectListener listener) {
828 | this.mListener = listener;
829 | }
830 |
831 | class InnerPagerAdapter extends FragmentPagerAdapter {
832 | private ArrayList fragments = new ArrayList<>();
833 | private String[] titles;
834 |
835 | public InnerPagerAdapter(FragmentManager fm, ArrayList fragments, String[] titles) {
836 | super(fm);
837 | this.fragments = fragments;
838 | this.titles = titles;
839 | }
840 |
841 | @Override
842 | public int getCount() {
843 | return fragments.size();
844 | }
845 |
846 | @Override
847 | public CharSequence getPageTitle(int position) {
848 | return titles[position];
849 | }
850 |
851 | @Override
852 | public Fragment getItem(int position) {
853 | return fragments.get(position);
854 | }
855 |
856 | @Override
857 | public void destroyItem(ViewGroup container, int position, Object object) {
858 | // 覆写destroyItem并且空实现,这样每个Fragment中的视图就不会被销毁
859 | // super.destroyItem(container, position, object);
860 | }
861 |
862 | @Override
863 | public int getItemPosition(Object object) {
864 | return PagerAdapter.POSITION_NONE;
865 | }
866 | }
867 |
868 | @Override
869 | protected Parcelable onSaveInstanceState() {
870 | Bundle bundle = new Bundle();
871 | bundle.putParcelable("instanceState", super.onSaveInstanceState());
872 | bundle.putInt("mCurrentTab", mCurrentTab);
873 | return bundle;
874 | }
875 |
876 | @Override
877 | protected void onRestoreInstanceState(Parcelable state) {
878 | if (state instanceof Bundle) {
879 | Bundle bundle = (Bundle) state;
880 | mCurrentTab = bundle.getInt("mCurrentTab");
881 | state = bundle.getParcelable("instanceState");
882 | if (mCurrentTab != 0 && mTabsContainer.getChildCount() > 0) {
883 | updateTabSelection(mCurrentTab);
884 | scrollToCurrentTab();
885 | }
886 | }
887 | super.onRestoreInstanceState(state);
888 | }
889 |
890 | protected int dp2px(float dp) {
891 | final float scale = mContext.getResources().getDisplayMetrics().density;
892 | return (int) (dp * scale + 0.5f);
893 | }
894 |
895 | protected int sp2px(float sp) {
896 | final float scale = this.mContext.getResources().getDisplayMetrics().scaledDensity;
897 | return (int) (sp * scale + 0.5f);
898 | }
899 | }
900 |
--------------------------------------------------------------------------------