├── .gitignore
├── .idea
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── gradle.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── GIF.gif
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── cm
│ │ └── channlemanagerview
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── cm
│ │ │ └── channlemanagerview
│ │ │ └── MainActivity.java
│ └── res
│ │ ├── drawable
│ │ └── custom_channel_item_bg.xml
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── arrays.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── cm
│ └── channlemanagerview
│ └── ExampleUnitTest.java
├── build.gradle
├── channeltagviewLib
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── zhl
│ │ │ └── channeltagview
│ │ │ ├── adapter
│ │ │ ├── GroupedGridLayoutManager.java
│ │ │ └── GroupedListAdapter.java
│ │ │ ├── bean
│ │ │ ├── ChannelItem.java
│ │ │ └── GroupItem.java
│ │ │ ├── listener
│ │ │ ├── OnChannelItemClicklistener.java
│ │ │ └── UserActionListener.java
│ │ │ ├── util
│ │ │ └── MeasureUtil.java
│ │ │ └── view
│ │ │ ├── ChannelTagView.java
│ │ │ ├── FloatItemView.java
│ │ │ └── FloatItemViewManager.java
│ └── res
│ │ ├── drawable
│ │ ├── channel_item_bg.xml
│ │ ├── channel_item_draging.xml
│ │ ├── error_drawable_circle.xml
│ │ ├── error_inside.xml
│ │ └── fixed_item_bg.xml
│ │ ├── layout
│ │ ├── channel_tag_layout.xml
│ │ ├── float_item_view.xml
│ │ ├── item_channel_view.xml
│ │ └── item_mulite_banner.xml
│ │ └── values
│ │ ├── attr.xml
│ │ ├── colors.xml
│ │ └── strings.xml
│ └── test
│ └── java
│ └── com
│ └── zhl
│ └── channeltagview
│ └── ExampleUnitTest.java
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
├── show.mp4
└── show_category.png
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/misc.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 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/GIF.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yilylong/ChannelTagView/d55e95e0e8c4f77ad2b68a42c0234a2d4bdb454a/GIF.gif
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://jitpack.io/#yilylong/ChannelTagView)
2 | # ChannelTagView
3 |
4 | 一个频道管理view,可拖拽排序,滑动删除。
5 | ----
6 |
7 |
8 |
9 | 使用
10 | --
11 | step1.Add it in your root build.gradle at the end of repositories:
12 | -
13 |
14 | allprojects {
15 | repositories {
16 | ...
17 | maven { url 'https://www.jitpack.io' }
18 | }
19 | }
20 |
21 | stpe2.Add the dependency:
22 | -
23 | dependencies {
24 | compile 'com.github.yilylong:ChannelTagView:v1.0.1'
25 | }
26 |
27 |
28 | xml中直接引用:
29 |
30 |
37 |
38 |
39 | 调用 ChannelTagView的initChannels() 方法填充数据即可。有针对里面的item修改的各种属性,针对点击事件和用户的拖动滑动事件接口监听。
40 | 4.4一下可能需要权限,详情查看demo
41 |
42 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25.0.2"
6 | defaultConfig {
7 | applicationId "com.cm.channlemanagerview"
8 | minSdkVersion 15
9 | targetSdkVersion 25
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(include: ['*.jar'], dir: 'libs')
24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
25 | exclude group: 'com.android.support', module: 'support-annotations'
26 | })
27 | compile 'com.android.support:appcompat-v7:25.3.0'
28 | compile 'com.android.support.constraint:constraint-layout:1.0.2'
29 | testCompile 'junit:junit:4.12'
30 | compile project(':channeltagviewLib')
31 | }
32 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\work\AndroidSDK/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/cm/channlemanagerview/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.cm.channlemanagerview;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.cm.channlemanagerview", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/cm/channlemanagerview/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.cm.channlemanagerview;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.view.View;
6 | import android.widget.Button;
7 | import android.widget.Toast;
8 |
9 | import com.zhl.channeltagview.bean.ChannelItem;
10 | import com.zhl.channeltagview.bean.GroupItem;
11 | import com.zhl.channeltagview.listener.OnChannelItemClicklistener;
12 | import com.zhl.channeltagview.listener.UserActionListener;
13 | import com.zhl.channeltagview.view.ChannelTagView;
14 |
15 | import java.util.ArrayList;
16 |
17 | import cn.bingoogolapple.badgeview.BGABadgeTextView;
18 |
19 | public class MainActivity extends AppCompatActivity {
20 | private ChannelTagView channelTagView;
21 | private ArrayList addedChannels = new ArrayList<>();
22 | private ArrayList unAddedChannels = new ArrayList<>();
23 | private ArrayList unAddedItems = new ArrayList<>();
24 |
25 | @Override
26 | protected void onCreate(Bundle savedInstanceState) {
27 | super.onCreate(savedInstanceState);
28 | setContentView(R.layout.activity_main);
29 | channelTagView = (ChannelTagView) findViewById(R.id.channel_tag_view);
30 | initData();
31 | Button btn = (Button) findViewById(R.id.btn_opencategory);
32 | btn.setOnClickListener(new View.OnClickListener() {
33 | @Override
34 | public void onClick(View view) {
35 | channelTagView.oPenCategory(!channelTagView.isOpenCategory());
36 | }
37 | });
38 | Button btnShow = (Button) findViewById(R.id.btn_showdrawableleft);
39 | btnShow.setOnClickListener(new View.OnClickListener() {
40 | @Override
41 | public void onClick(View view) {
42 | channelTagView.showItemDrawableLeft(!channelTagView.isShowItemDrawableLeft());
43 | }
44 | });
45 | channelTagView.showPahtAnim(true);
46 | // channelTagView.setItemLeftDrawableLayoutParams(new RelativeLayout.LayoutParams(50,50));
47 | // channelTagView.showItemDrawableLeft(true);
48 | // channelTagView.setSwipeEnable(false);
49 | // channelTagView.setCategoryItemBg(R.color.content_color);
50 | // channelTagView.setCategoryItemTxColor(ContextCompat.getColor(this,R.color.content_color));
51 | // channelTagView.setCategoryItemTxSize(18);
52 | // channelTagView.setChannelItemTxColor(Color.BLUE);
53 | // channelTagView.setChannelItemBg(R.drawable.custom_channel_item_bg);
54 | // channelTagView.setCategrayUnAddedBannerTX("更多栏目");
55 | // channelTagView.setCategoryAddedBannerBg(R.color.content_color);
56 | // channelTagView.setCategoryBannerTXsize(40);
57 | // channelTagView.setCategoryBannerTXColor(Color.argb(255,221,224,98));
58 | // channelTagView.setColumnVerticalSpace(20);
59 | channelTagView.initChannels(addedChannels, unAddedItems, true, new ChannelTagView.RedDotRemainderListener() {
60 |
61 | @Override
62 | public boolean showAddedChannelBadge(BGABadgeTextView itemView, int position) {
63 | if (addedChannels.get(position).title.equals("直播")) {
64 | return true;
65 | } else {
66 | return false;
67 | }
68 | }
69 |
70 | @Override
71 | public boolean showUnAddedChannelBadge(BGABadgeTextView itemView, int position) {
72 | if (unAddedChannels.get(position).title.equals("数码") || unAddedChannels.get(position).title.equals("科技")) {
73 | return true;
74 | } else {
75 | return false;
76 | }
77 | }
78 |
79 | @Override
80 | public void handleAddedChannelReddot(BGABadgeTextView itemView, int position) {
81 | itemView.showCirclePointBadge();
82 | }
83 |
84 | @Override
85 | public void handleUnAddedChannelReddot(BGABadgeTextView itemView, int position) {
86 | if (unAddedChannels.get(position).title.equals("科技")) {
87 | itemView.showTextBadge("new");
88 | } else {
89 | itemView.showCirclePointBadge();
90 | }
91 | }
92 |
93 | @Override
94 | public void OnDragDismiss(BGABadgeTextView itemView, int position) {
95 | Toast.makeText(MainActivity.this, "拖拽取消红点提示-", Toast.LENGTH_SHORT).show();
96 | itemView.hiddenBadge();
97 | }
98 |
99 | });
100 | // channelTagView.setFixedChannelBg(R.drawable.fixed_item_bg);
101 | // channelTagView.setFixedChannel(0);
102 | // channelTagView.showPahtAnim(true);
103 | channelTagView.setOnChannelItemClicklistener(new OnChannelItemClicklistener() {
104 |
105 | @Override
106 | public void onAddedChannelItemClick(View itemView, int position) {
107 | Toast.makeText(MainActivity.this, "打开-" + addedChannels.get(position).title, Toast.LENGTH_SHORT).show();
108 | }
109 |
110 | @Override
111 | public void onUnAddedChannelItemClick(View itemView, int position) {
112 | ChannelItem item = unAddedChannels.remove(position);
113 | addedChannels.add(item);
114 | Toast.makeText(MainActivity.this, "添加频道-" + item.title, Toast.LENGTH_SHORT).show();
115 | }
116 |
117 | @Override
118 | public void onItemDrawableClickListener(View itemView, int position) {
119 | Toast.makeText(MainActivity.this, "删除-" + MainActivity.this.addedChannels.get(position).title, Toast.LENGTH_SHORT).show();
120 | unAddedChannels.add(addedChannels.remove(position));
121 | }
122 | });
123 | channelTagView.setUserActionListener(new UserActionListener() {
124 | @Override
125 | public void onMoved(int fromPos, int toPos, ArrayList checkedChannels) {
126 | Toast.makeText(MainActivity.this, "将-" + addedChannels.get(fromPos).title + " 换到 " + addedChannels.get(toPos).title, Toast.LENGTH_SHORT).show();
127 | addedChannels.clear();
128 | addedChannels.addAll(checkedChannels);
129 | }
130 |
131 | @Override
132 | public void onSwiped(int position, View itemView, ArrayList checkedChannels, ArrayList uncheckedChannels) {
133 | Toast.makeText(MainActivity.this, "删除-" + MainActivity.this.addedChannels.remove(position).title, Toast.LENGTH_SHORT).show();
134 | unAddedChannels.clear();
135 | unAddedChannels.addAll(uncheckedChannels);
136 | }
137 | });
138 | }
139 |
140 | private void initData() {
141 | String[] chanles = getResources().getStringArray(R.array.chanles);
142 | for (int i = 0; i < 7; i++) {
143 | ChannelItem item = new ChannelItem();
144 | item.id = i;
145 | item.title = chanles[i];
146 | item.category = "头条";
147 | addedChannels.add(item);
148 | }
149 | GroupItem groupFinance = new GroupItem();
150 | groupFinance.category = "金融";
151 | for (int i = 7; i < 9; i++) {
152 | ChannelItem item = new ChannelItem();
153 | item.id = i;
154 | item.title = chanles[i];
155 | item.category = "金融";
156 | unAddedChannels.add(item);
157 | groupFinance.addChanelItem(item);
158 | }
159 | unAddedItems.add(groupFinance);
160 |
161 | GroupItem groupLife = new GroupItem();
162 | groupLife.category = "生活";
163 | for (int i = 9; i < 18; i++) {
164 | ChannelItem item = new ChannelItem();
165 | item.id = i;
166 | item.title = chanles[i];
167 | item.category = "生活";
168 | unAddedChannels.add(item);
169 | groupLife.addChanelItem(item);
170 | }
171 | unAddedItems.add(groupLife);
172 |
173 | GroupItem groupEntertainment = new GroupItem();
174 | groupEntertainment.category = "娱乐";
175 | for (int i = 18; i < 22; i++) {
176 | ChannelItem item = new ChannelItem();
177 | item.id = i;
178 | item.title = chanles[i];
179 | item.category = "娱乐";
180 | unAddedChannels.add(item);
181 | groupEntertainment.addChanelItem(item);
182 | }
183 | unAddedItems.add(groupEntertainment);
184 |
185 | GroupItem Grouphumanity = new GroupItem();
186 | Grouphumanity.category = "人文";
187 | for (int i = 22; i <= 25; i++) {
188 | ChannelItem item = new ChannelItem();
189 | item.id = i;
190 | item.title = chanles[i];
191 | item.category = "人文";
192 | unAddedChannels.add(item);
193 | Grouphumanity.addChanelItem(item);
194 | }
195 | unAddedItems.add(Grouphumanity);
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/custom_channel_item_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 |
10 | -
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
25 |
26 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yilylong/ChannelTagView/d55e95e0e8c4f77ad2b68a42c0234a2d4bdb454a/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yilylong/ChannelTagView/d55e95e0e8c4f77ad2b68a42c0234a2d4bdb454a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yilylong/ChannelTagView/d55e95e0e8c4f77ad2b68a42c0234a2d4bdb454a/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yilylong/ChannelTagView/d55e95e0e8c4f77ad2b68a42c0234a2d4bdb454a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yilylong/ChannelTagView/d55e95e0e8c4f77ad2b68a42c0234a2d4bdb454a/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yilylong/ChannelTagView/d55e95e0e8c4f77ad2b68a42c0234a2d4bdb454a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yilylong/ChannelTagView/d55e95e0e8c4f77ad2b68a42c0234a2d4bdb454a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yilylong/ChannelTagView/d55e95e0e8c4f77ad2b68a42c0234a2d4bdb454a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yilylong/ChannelTagView/d55e95e0e8c4f77ad2b68a42c0234a2d4bdb454a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yilylong/ChannelTagView/d55e95e0e8c4f77ad2b68a42c0234a2d4bdb454a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.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 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #64ce9d
4 | #8dbece
5 | #33000001
6 | #00000000
7 | #ffffff
8 | #efefef
9 | #de6262
10 | #4c4b4b
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 15sp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ChannelManagerView
3 | Hello world!
4 | Settings
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/test/java/com/cm/channlemanagerview/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.cm.channlemanagerview;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.3.3'
9 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | maven { url 'https://jitpack.io' }
19 | }
20 |
21 |
22 | }
23 |
24 | task clean(type: Delete) {
25 | delete rootProject.buildDir
26 | }
27 |
--------------------------------------------------------------------------------
/channeltagviewLib/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/channeltagviewLib/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.github.dcendents.android-maven'
3 | group='com.github.yilylong'
4 | android {
5 | compileSdkVersion 25
6 | buildToolsVersion "25.0.2"
7 |
8 | defaultConfig {
9 | minSdkVersion 15
10 | targetSdkVersion 25
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(include: ['*.jar'], dir: 'libs')
24 | compile 'com.android.support:appcompat-v7:25.3.0'
25 | compile 'com.android.support:recyclerview-v7:25.3.0'
26 | compile 'com.zhy:base-rvadapter:3.0.3'
27 | compile 'cn.bingoogolapple:bga-badgeview:1.1.3@aar'
28 | compile 'com.github.donkingliang:GroupedRecyclerViewAdapter:1.0.1'
29 | }
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/channeltagviewLib/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\work\AndroidSDK/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/java/com/zhl/channeltagview/adapter/GroupedGridLayoutManager.java:
--------------------------------------------------------------------------------
1 | package com.zhl.channeltagview.adapter;
2 |
3 | import android.content.Context;
4 | import android.support.v7.widget.GridLayoutManager;
5 |
6 | import com.donkingliang.groupedadapter.adapter.GroupedRecyclerViewAdapter;
7 |
8 |
9 | /**
10 | * 为分组列表提供的GridLayoutManager。
11 | * 因为分组列表如果要使用GridLayoutManager实现网格布局。要保证组的头部和尾部是要单独占用一行的。
12 | * 否则组的头、尾可能会跟子项混着一起,造成布局混乱。
13 | */
14 | public class GroupedGridLayoutManager extends GridLayoutManager {
15 |
16 | private GroupedRecyclerViewAdapter mAdapter;
17 |
18 | public GroupedGridLayoutManager(Context context, int spanCount,
19 | GroupedRecyclerViewAdapter adapter) {
20 | super(context, spanCount);
21 | mAdapter = adapter;
22 | setSpanSizeLookup();
23 | }
24 |
25 | private void setSpanSizeLookup() {
26 | super.setSpanSizeLookup(new SpanSizeLookup() {
27 | @Override
28 | public int getSpanSize(int position) {
29 | int count = getSpanCount();
30 | if (mAdapter != null) {
31 | int type = mAdapter.judgeType(position);
32 | //只对子项做Grid效果
33 | if (type == GroupedRecyclerViewAdapter.TYPE_CHILD) {
34 | int groupPosition = mAdapter.getGroupPositionForPosition(position);
35 | int childPosition =
36 | mAdapter.getChildPositionForPosition(groupPosition, position);
37 | return getChildSpanSize(groupPosition, childPosition);
38 | }
39 | }
40 |
41 | return count;
42 | }
43 | });
44 | }
45 |
46 | /**
47 | * 提供这个方法可以使外部改变子项的SpanSize。
48 | * 这个方法的作用跟{@link SpanSizeLookup#getSpanSize(int)}一样。
49 | * @param groupPosition
50 | * @param childPosition
51 | * @return
52 | */
53 | public int getChildSpanSize(int groupPosition, int childPosition) {
54 | return 1;
55 | }
56 |
57 | @Override
58 | public void setSpanSizeLookup(SpanSizeLookup spanSizeLookup) {
59 |
60 | }
61 | }
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/java/com/zhl/channeltagview/adapter/GroupedListAdapter.java:
--------------------------------------------------------------------------------
1 | package com.zhl.channeltagview.adapter;
2 |
3 | import android.content.Context;
4 | import android.util.TypedValue;
5 | import android.widget.TextView;
6 |
7 | import com.donkingliang.groupedadapter.adapter.GroupedRecyclerViewAdapter;
8 | import com.donkingliang.groupedadapter.holder.BaseViewHolder;
9 | import com.zhl.channeltagview.R;
10 | import com.zhl.channeltagview.bean.ChannelItem;
11 | import com.zhl.channeltagview.bean.GroupItem;
12 | import com.zhl.channeltagview.view.ChannelTagView;
13 |
14 | import java.util.ArrayList;
15 |
16 | import cn.bingoogolapple.badgeview.BGABadgeTextView;
17 | import cn.bingoogolapple.badgeview.BGABadgeable;
18 | import cn.bingoogolapple.badgeview.BGADragDismissDelegate;
19 |
20 | /**
21 | * 这是普通的分组Adapter 每一个组都有头部、尾部和子项。
22 | */
23 | public class GroupedListAdapter extends GroupedRecyclerViewAdapter {
24 | private int itemTxColor=-1;
25 | private int itemBg=-1;
26 | private int categoryBg = -1;
27 | private int categoryTxColor = -1;
28 | private int categoryTxSize = 13;
29 | private ArrayList mGroups;
30 | private boolean openCategory;
31 | private ChannelTagView.RedDotRemainderListener redDotRemainderListener;
32 |
33 | public GroupedListAdapter(Context context, ArrayList groups,boolean openCategory) {
34 | super(context);
35 | mGroups = groups;
36 | this.openCategory = openCategory;
37 | }
38 |
39 | @Override
40 | public int getGroupCount() {
41 | return mGroups == null ? 0 : mGroups.size();
42 | }
43 |
44 | @Override
45 | public int getChildrenCount(int groupPosition) {
46 | ArrayList children = mGroups.get(groupPosition).getChannelItems();
47 | return children == null ? 0 : children.size();
48 | }
49 |
50 | @Override
51 | public boolean hasHeader(int groupPosition) {
52 | return openCategory;
53 | }
54 |
55 | @Override
56 | public boolean hasFooter(int groupPosition) {
57 | return false;
58 | }
59 |
60 | @Override
61 | public int getHeaderLayout(int viewType) {
62 | return R.layout.item_mulite_banner;
63 | }
64 |
65 | @Override
66 | public int getFooterLayout(int viewType) {
67 | return 0;
68 | }
69 |
70 | @Override
71 | public int getChildLayout(int viewType) {
72 | return R.layout.item_channel_view;
73 | }
74 |
75 | @Override
76 | public void onBindHeaderViewHolder(BaseViewHolder holder, int groupPosition) {
77 | GroupItem entity = mGroups.get(groupPosition);
78 | TextView categoryTitle = holder.get(R.id.banner_title);
79 | categoryTitle.setText(entity.category);
80 | if(categoryBg!=-1){
81 | categoryTitle.setBackgroundResource(categoryBg);
82 | }
83 | if(categoryTxColor!=-1){
84 | categoryTitle.setTextColor(categoryTxColor);
85 | }
86 | categoryTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP,categoryTxSize);
87 | }
88 |
89 | @Override
90 | public void onBindFooterViewHolder(BaseViewHolder holder, int groupPosition) {
91 |
92 | }
93 |
94 | @Override
95 | public void onBindChildViewHolder(BaseViewHolder holder, int groupPosition, int childPosition) {
96 | ChannelItem entity = mGroups.get(groupPosition).getChannelItems().get(childPosition);
97 | final BGABadgeTextView title = (BGABadgeTextView) holder.get(R.id.item_tv);
98 | title.setText(entity.title);
99 | if(itemTxColor!=-1){
100 | title.setTextColor(itemTxColor);
101 | }
102 | if(itemBg!=-1){
103 | title.setBackgroundResource(itemBg);
104 | }
105 | int pos = getPositionForChild(groupPosition,childPosition);
106 | final int position = openCategory?pos - (groupPosition + 1) : pos;
107 | if (redDotRemainderListener.showUnAddedChannelBadge(title, position)) {
108 | title.setDragDismissDelegage(new BGADragDismissDelegate() {
109 | @Override
110 | public void onDismiss(BGABadgeable badgeable) {
111 | redDotRemainderListener.OnDragDismiss(title, position);
112 | }
113 | });
114 | redDotRemainderListener.handleUnAddedChannelReddot(title, position);
115 | } else {
116 | title.hiddenBadge();
117 | }
118 |
119 | }
120 |
121 | public void setRedDotRemainderListener(ChannelTagView.RedDotRemainderListener redDotRemainderListener) {
122 | this.redDotRemainderListener = redDotRemainderListener;
123 | }
124 |
125 | public void setOpenCategory(boolean openCategory) {
126 | this.openCategory = openCategory;
127 | this.changeDataSet();
128 | }
129 |
130 | public void setItemTxColor(int itemTxColor) {
131 | this.itemTxColor = itemTxColor;
132 | changeDataSet();
133 | }
134 |
135 | public void setItemBg(int itemBg) {
136 | this.itemBg = itemBg;
137 | changeDataSet();
138 | }
139 |
140 | public void setCategoryBg(int categoryBg) {
141 | this.categoryBg = categoryBg;
142 | changeDataSet();
143 | }
144 |
145 | public void setCategoryTxColor(int color) {
146 | this.categoryTxColor = color;
147 | changeDataSet();
148 | }
149 |
150 | public void setCategoryTxSize(int size) {
151 | this.categoryTxSize = size;
152 | changeDataSet();
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/java/com/zhl/channeltagview/bean/ChannelItem.java:
--------------------------------------------------------------------------------
1 | package com.zhl.channeltagview.bean;
2 |
3 | /**
4 | * 描述:频道bean
5 | * Created by zhaohl on 2017-3-7.
6 | */
7 | public class ChannelItem {
8 | public int id;
9 | public int iconResid;
10 | public String category;
11 | public String title;
12 | }
13 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/java/com/zhl/channeltagview/bean/GroupItem.java:
--------------------------------------------------------------------------------
1 | package com.zhl.channeltagview.bean;
2 |
3 | import java.util.ArrayList;
4 |
5 | /**
6 | * 描述:
7 | * Created by zhaohl on 2017-5-11.
8 | */
9 |
10 | public class GroupItem {
11 | public String category;
12 | private ArrayList channelItems = new ArrayList<>();
13 |
14 | public ArrayList getChannelItems() {
15 | return channelItems;
16 | }
17 |
18 | public void setChannelItems(ArrayList channelItems) {
19 | this.channelItems = channelItems;
20 | }
21 | public void addChanelItem(ChannelItem item){
22 | if(channelItems!=null){
23 | channelItems.add(item);
24 | }
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/java/com/zhl/channeltagview/listener/OnChannelItemClicklistener.java:
--------------------------------------------------------------------------------
1 | package com.zhl.channeltagview.listener;
2 |
3 | import android.view.View;
4 |
5 | /**
6 | * 描述:频道item点击事件回调接口
7 | * Created by zhaohl on 2017-3-7.
8 | */
9 |
10 | public interface OnChannelItemClicklistener {
11 | /**
12 | * 已选中频道item点击监听回调
13 | *
14 | * @param itemView
15 | * @param position
16 | */
17 | public void onAddedChannelItemClick(View itemView, int position);
18 |
19 | /**
20 | * 未选中频道item点击监听回调
21 | *
22 | * @param itemView
23 | * @param position
24 | */
25 | public void onUnAddedChannelItemClick(View itemView, int position);
26 |
27 | /**
28 | * item icon 点击监听回调
29 | *
30 | * @param itemView
31 | * @param position
32 | */
33 | public void onItemDrawableClickListener(View itemView, int position);
34 | }
35 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/java/com/zhl/channeltagview/listener/UserActionListener.java:
--------------------------------------------------------------------------------
1 | package com.zhl.channeltagview.listener;
2 |
3 | import android.view.View;
4 |
5 | import com.zhl.channeltagview.bean.ChannelItem;
6 |
7 | import java.util.ArrayList;
8 |
9 | /**
10 | * 描述:针对已添加频道的用户操作回调
11 | * Created by zhaohl on 2017-3-13.
12 | */
13 |
14 | public interface UserActionListener {
15 | /**
16 | * 拖动完成
17 | * @param fromPos 拖动起始位置
18 | * @param toPos 拖动结束位置
19 | * @param addedChannels 拖动完成后频道数据集合
20 | */
21 | public void onMoved(int fromPos, int toPos, ArrayList addedChannels);
22 |
23 | /**
24 | * 侧滑删除后
25 | * @param position 删除的positon
26 | * @param itemView 当前拖动itemview
27 | * @param addedChannels 删除positon项后的已添加频道数据集合
28 | * @param unAddedChannels 删除positon项后的未添加频道数据集合
29 | */
30 | public void onSwiped(int position, View itemView, ArrayList addedChannels, ArrayList unAddedChannels);
31 | }
32 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/java/com/zhl/channeltagview/util/MeasureUtil.java:
--------------------------------------------------------------------------------
1 | package com.zhl.channeltagview.util;
2 |
3 |
4 | import android.app.Activity;
5 | import android.content.Context;
6 | import android.graphics.Bitmap;
7 | import android.graphics.Color;
8 | import android.graphics.Paint;
9 | import android.graphics.Rect;
10 | import android.util.DisplayMetrics;
11 | import android.util.TypedValue;
12 | import android.view.View.MeasureSpec;
13 | import android.view.Window;
14 |
15 | /**
16 | * 测绘工具类
17 | */
18 | public final class MeasureUtil {
19 | public static final int RATION_WIDTH = 0;
20 | public static final int RATION_HEIGHT = 1;
21 |
22 | /**
23 | * 获取屏幕尺寸
24 | *
25 | * @param activity Activity
26 | * @return 屏幕尺寸像素值,下标为0的值为宽,下标为1的值为高
27 | */
28 | public static int[] getScreenSize(Activity activity) {
29 | DisplayMetrics metrics = new DisplayMetrics();
30 | activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
31 | return new int[]{metrics.widthPixels, metrics.heightPixels};
32 | }
33 |
34 | /**
35 | * 自定义控件获取测量后的尺寸方法
36 | *
37 | * @param measureSpec 测量规格
38 | * @param ratio 宽高标识
39 | * @param mStr 自定义控件上需要测绘的文字
40 | * @param mBitmap 自定义控件上需要测绘的图片
41 | * @param paddings 自定义控件的padding值int[]{left,top,right,bottom}
42 | * @return 宽或高的测量值
43 | */
44 | public static int getMeasureSize(int measureSpec, int ratio, String mStr, Bitmap mBitmap, Paint mPaint, int[] paddings) {
45 | // 声明临时变量保存测量值
46 | int result = 0;
47 | /*
48 | * 获取测量mode和size
49 | */
50 | int mode = MeasureSpec.getMode(measureSpec);
51 | int size = MeasureSpec.getSize(measureSpec);
52 | /*
53 | * 判断mode的具体值
54 | */
55 | switch (mode) {
56 | case MeasureSpec.EXACTLY:// EXACTLY时直接赋值
57 | result = size;
58 | break;
59 | default:// 默认情况下将UNSPECIFIED和AT_MOST一并处理
60 | if (ratio == RATION_WIDTH) {
61 | if (mStr != null && mBitmap != null) {
62 | float textWidth = mPaint.measureText(mStr);
63 | result = ((int) (textWidth >= mBitmap.getWidth() ? textWidth : mBitmap.getWidth()));
64 | } else if (mBitmap != null) {
65 | result = mBitmap.getWidth();
66 | } else if (mStr != null) {
67 | result = (int) mPaint.measureText(mStr);
68 | }
69 | if (paddings != null) {
70 | result += (paddings[0] + paddings[2]);
71 | }
72 | } else if (ratio == RATION_HEIGHT) {
73 | if (mStr != null && mBitmap != null) {
74 | result = ((int) ((mPaint.descent() - mPaint.ascent()) * 2 + mBitmap.getHeight()));
75 | } else if (mBitmap != null) {
76 | result = mBitmap.getHeight();
77 | } else if (mStr != null) {
78 | result = (int) ((mPaint.descent() - mPaint.ascent()) * 2);
79 | }
80 | if (paddings != null) {
81 | result += (paddings[1] + paddings[3]);
82 | }
83 | }
84 |
85 | /*
86 | * AT_MOST时判断size和result的大小取小值
87 | */
88 | if (mode == MeasureSpec.AT_MOST) {
89 | result = Math.min(result, size);
90 | }
91 | break;
92 | }
93 | return result;
94 | }
95 |
96 | public static Bitmap changeColor(Bitmap src, int keyColor, int replColor, int tolerance) {
97 | Bitmap copy = src.copy(Bitmap.Config.ARGB_8888, true);
98 | int width = copy.getWidth();
99 | int height = copy.getHeight();
100 | int[] pixels = new int[width * height];
101 | src.getPixels(pixels, 0, width, 0, 0, width, height);
102 | int sR = Color.red(keyColor);
103 | int sG = Color.green(keyColor);
104 | int sB = Color.blue(keyColor);
105 | int tR = Color.red(replColor);
106 | int tG = Color.green(replColor);
107 | int tB = Color.blue(replColor);
108 | float[] hsv = new float[3];
109 | Color.RGBToHSV(tR, tG, tB, hsv);
110 | float targetHue = hsv[0];
111 | float targetSat = hsv[1];
112 | float targetVal = hsv[2];
113 |
114 | for (int i = 0; i < pixels.length; ++i) {
115 | int pixel = pixels[i];
116 |
117 | if (pixel == keyColor) {
118 | pixels[i] = replColor;
119 | } else {
120 | int pR = Color.red(pixel);
121 | int pG = Color.green(pixel);
122 | int pB = Color.blue(pixel);
123 |
124 | int deltaR = Math.abs(pR - sR);
125 | int deltaG = Math.abs(pG - sG);
126 | int deltaB = Math.abs(pB - sB);
127 |
128 | if (deltaR <= tolerance && deltaG <= tolerance && deltaB <= tolerance) {
129 | Color.RGBToHSV(pR, pG, pB, hsv);
130 | hsv[0] = targetHue;
131 | hsv[1] = targetSat;
132 | hsv[2] *= targetVal;
133 |
134 | int mixTrgColor = Color.HSVToColor(Color.alpha(pixel), hsv);
135 | pixels[i] = mixTrgColor;
136 | }
137 | }
138 | }
139 |
140 | copy.setPixels(pixels, 0, width, 0, 0, width, height);
141 |
142 | return copy;
143 | }
144 |
145 | public static int dp2px(Context context, float dp) {
146 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());
147 | }
148 |
149 | public static int sp2px(Context context, float sp) {
150 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, context.getResources().getDisplayMetrics());
151 | }
152 |
153 | public static int px2dp(Context context, float px) {
154 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, px, context.getResources().getDisplayMetrics());
155 | }
156 |
157 | /**
158 | * 测量文字的高度
159 | *
160 | * @param textsize px
161 | * @return
162 | */
163 | public static int measureTextHeight(int textsize) {
164 | Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
165 | paint.setTextSize(textsize);
166 | Paint.FontMetrics metrics = paint.getFontMetrics();
167 | return (int) Math.ceil(metrics.descent - metrics.ascent);
168 | }
169 |
170 | /**
171 | * 测量文字的宽度
172 | *
173 | * @param text
174 | * @param textsize px
175 | * @return
176 | */
177 | public static int measureTextWidth(String text, int textsize) {
178 | Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
179 | paint.setTextSize(textsize);
180 | return (int) paint.measureText(text);
181 | }
182 |
183 | public static Bitmap createBitmapSafely(int width, int height, Bitmap.Config config, int retryCount) {
184 | try {
185 | return Bitmap.createBitmap(width, height, config);
186 | } catch (OutOfMemoryError e) {
187 | e.printStackTrace();
188 | if (retryCount > 0) {
189 | System.gc();
190 | return createBitmapSafely(width, height, config, retryCount - 1);
191 | }
192 | return null;
193 | }
194 | }
195 |
196 | public static int getStatusBarHeight(Activity activity) {
197 | Rect rectangle = new Rect();
198 | Window window = activity.getWindow();
199 | window.getDecorView().getWindowVisibleDisplayFrame(rectangle);
200 | int statusBarHeight = rectangle.top;
201 | return statusBarHeight;
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/java/com/zhl/channeltagview/view/ChannelTagView.java:
--------------------------------------------------------------------------------
1 | package com.zhl.channeltagview.view;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorSet;
5 | import android.animation.ValueAnimator;
6 | import android.app.Activity;
7 | import android.content.Context;
8 | import android.content.res.TypedArray;
9 | import android.graphics.Canvas;
10 | import android.graphics.Point;
11 | import android.graphics.Rect;
12 | import android.graphics.drawable.Drawable;
13 | import android.support.annotation.Nullable;
14 | import android.support.v4.view.ViewCompat;
15 | import android.support.v7.widget.GridLayoutManager;
16 | import android.support.v7.widget.RecyclerView;
17 | import android.support.v7.widget.helper.ItemTouchHelper;
18 | import android.util.AttributeSet;
19 | import android.util.TypedValue;
20 | import android.view.LayoutInflater;
21 | import android.view.View;
22 | import android.widget.ImageView;
23 | import android.widget.LinearLayout;
24 | import android.widget.RelativeLayout;
25 | import android.widget.TextView;
26 |
27 | import com.donkingliang.groupedadapter.adapter.GroupedRecyclerViewAdapter;
28 | import com.donkingliang.groupedadapter.holder.BaseViewHolder;
29 | import com.zhl.channeltagview.R;
30 | import com.zhl.channeltagview.adapter.GroupedGridLayoutManager;
31 | import com.zhl.channeltagview.adapter.GroupedListAdapter;
32 | import com.zhl.channeltagview.bean.ChannelItem;
33 | import com.zhl.channeltagview.bean.GroupItem;
34 | import com.zhl.channeltagview.listener.OnChannelItemClicklistener;
35 | import com.zhl.channeltagview.listener.UserActionListener;
36 | import com.zhl.channeltagview.util.MeasureUtil;
37 | import com.zhy.adapter.recyclerview.CommonAdapter;
38 | import com.zhy.adapter.recyclerview.MultiItemTypeAdapter;
39 | import com.zhy.adapter.recyclerview.base.ViewHolder;
40 |
41 | import java.util.ArrayList;
42 | import java.util.List;
43 |
44 | import cn.bingoogolapple.badgeview.BGABadgeTextView;
45 |
46 | /**
47 | * 描述:
48 | * Created by zhaohl on 2017-3-15.
49 | */
50 |
51 | public class ChannelTagView extends LinearLayout {
52 | private static final int MIN_INTERVAL_TIME = 500;
53 | /**
54 | * 频道显示列数
55 | */
56 | public int colums = 5;
57 | /**
58 | * 频道列间距
59 | */
60 | public int columnHorizontalSpace = 10;
61 | /**
62 | * 频道行间距
63 | */
64 | public int columnVerticalSpace = 10;
65 | /**
66 | * 已添加的频道数据集合
67 | */
68 | private ArrayList addedChannels = new ArrayList<>();
69 | /**
70 | * 未添加的频道数据集合
71 | */
72 | private ArrayList unAddedChannels = new ArrayList<>();
73 |
74 | private ArrayList unAddedGroups = new ArrayList();
75 |
76 | private ArrayList categoryList = new ArrayList<>();
77 | /**
78 | * 已、未添加的频道adapter
79 | */
80 | private ChannelAdapter addedAdapter;
81 |
82 | private GroupedListAdapter unAddedAdapter;
83 | /**
84 | * 处理recyclerview手势的辅助类
85 | */
86 | private ItemTouchHelper itemTouchHelper;
87 | /**
88 | * 频道点击事件回调接口
89 | */
90 | private OnChannelItemClicklistener onChannelItemClicklistener;
91 | /**
92 | * 用户操作已添加频道的一些手势事件回调接口
93 | */
94 | private UserActionListener userActionListener;
95 | /**
96 | * 红点提示view的处理回调接口
97 | */
98 | private RedDotRemainderListener redDotRemainderListener;
99 | /**
100 | * 是否显示添加后的轨迹动画
101 | */
102 | private boolean showPahtAnim;
103 | /**
104 | * 固定position
105 | */
106 | private int fixedPos = -1;
107 | /**
108 | * 固定频道的背景
109 | */
110 | private int fixedChannelBg;
111 | /**
112 | * 频道拖拽时的背景
113 | */
114 | private int channelItemDragingBg;
115 | /**
116 | * 频道item背景
117 | */
118 | private int channelItemBg;
119 | /**
120 | * 频道文字颜色
121 | */
122 | private int channelItemTxColor;
123 | /**
124 | * 频道文字大小
125 | */
126 | private int channelItemTxSize;
127 | /**
128 | * 栏目分组banner颜色
129 | */
130 | private int categoryAddedBannerBg, categoryUnAddedBannerBg;
131 | private RecyclerView addedRecyclerView, unaddedRecyclerView;
132 | /**
133 | * 栏目分组标题textview
134 | */
135 | private TextView categaryAddedTopView, categrayUnAddedTopView;
136 | private AnimatorSet pathAnimator;
137 | /**
138 | * 是否开启分组
139 | */
140 | private boolean openCategory;
141 | private GridLayoutManager addedLayoutManager;
142 | private GroupedGridLayoutManager unAddLayoutManager;
143 | private SpacesItemDecoration itemDecoration;
144 | /**
145 | * item 左侧icon drawable
146 | */
147 | private Drawable itemDrawableLeft;
148 | private int itemBgDrawableRes = -1;
149 | private boolean showItemDrawableLeft;
150 | private RelativeLayout.LayoutParams leftDrawableParams;
151 | /**
152 | * 是否可以侧滑
153 | */
154 | private boolean swipeEnable = true;
155 | private boolean longPressEnable = true;
156 | private long lastClickTime;
157 |
158 |
159 | public ChannelTagView(Context context) {
160 | this(context, null);
161 | }
162 |
163 | public ChannelTagView(Context context, @Nullable AttributeSet attrs) {
164 | this(context, attrs, -1);
165 | }
166 |
167 | public ChannelTagView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
168 | super(context, attrs, defStyleAttr);
169 | TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.channel_tag_style);
170 | showPahtAnim = array.getBoolean(R.styleable.channel_tag_style_showPathAnim, true);
171 | channelItemBg = array.getResourceId(R.styleable.channel_tag_style_channelItemBg, R.drawable.channel_item_bg);
172 | fixedChannelBg = array.getResourceId(R.styleable.channel_tag_style_channelItemFixedBg, R.drawable.fixed_item_bg);
173 | categoryAddedBannerBg = array.getResourceId(R.styleable.channel_tag_style_addedCategroyTitleBg, R.color.cmv_category_banner_view_bg);
174 | categoryUnAddedBannerBg = array.getResourceId(R.styleable.channel_tag_style_unAddedCategroyTitleBg, R.color.cmv_category_banner_view_bg);
175 | channelItemDragingBg = array.getResourceId(R.styleable.channel_tag_style_channelItemDragingBg, R.drawable.channel_item_draging);
176 | fixedPos = array.getInt(R.styleable.channel_tag_style_fixedPos, -1);
177 | colums = array.getInt(R.styleable.channel_tag_style_colums, 5);
178 | columnHorizontalSpace = array.getDimensionPixelOffset(R.styleable.channel_tag_style_columnHorizontalSpace, 10);
179 | columnVerticalSpace = array.getDimensionPixelOffset(R.styleable.channel_tag_style_columnVerticalSpace, 10);
180 | channelItemTxColor = array.getColor(R.styleable.channel_tag_style_channelItemTxColor, 0xff000000);
181 | channelItemTxSize = array.getDimensionPixelOffset(R.styleable.channel_tag_style_channelItemTxSize, 39);
182 | itemDrawableLeft = array.getDrawable(R.styleable.channel_tag_style_itemDrawableLeft);
183 | setOrientation(VERTICAL);
184 | init();
185 | }
186 |
187 | private void init() {
188 | View contentView = LayoutInflater.from(getContext()).inflate(R.layout.channel_tag_layout, this, true);
189 | addedRecyclerView = (RecyclerView) contentView.findViewById(R.id.added_channel_recyclerview);
190 | unaddedRecyclerView = (RecyclerView) contentView.findViewById(R.id.unAdded_channel_recyclerview);
191 | categaryAddedTopView = (TextView) contentView.findViewById(R.id.categray_added_title);
192 | categaryAddedTopView.setBackgroundResource(categoryAddedBannerBg);
193 | categrayUnAddedTopView = (TextView) contentView.findViewById(R.id.categray_unadded_title);
194 | categrayUnAddedTopView.setBackgroundResource(categoryUnAddedBannerBg);
195 | addedRecyclerView.setLayoutManager(addedLayoutManager = new GridLayoutManager(getContext(), colums));
196 | addedRecyclerView.addItemDecoration(itemDecoration = new SpacesItemDecoration(columnHorizontalSpace, columnVerticalSpace));
197 | unaddedRecyclerView.addItemDecoration(itemDecoration);
198 | itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.Callback() {
199 | @Override
200 | public boolean isItemViewSwipeEnabled() {
201 | return swipeEnable;
202 | }
203 |
204 | @Override
205 | public boolean isLongPressDragEnabled() {
206 | return longPressEnable;
207 | }
208 |
209 | @Override
210 | public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
211 | int dragFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
212 | int swipeFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
213 | return makeMovementFlags(dragFlag, swipeFlag);
214 | }
215 |
216 | @Override
217 | public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
218 | int from = viewHolder.getAdapterPosition();
219 | int to = target.getAdapterPosition();
220 | if (from == fixedPos) {
221 | return false;
222 | }
223 | if (to == fixedPos) {
224 | return false;
225 | }
226 | ChannelItem item = addedChannels.remove(from);
227 | addedChannels.add(to, item);
228 | addedAdapter.notifyItemMoved(from, to);
229 | return true;
230 | }
231 |
232 | @Override
233 | public void onMoved(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, int fromPos, RecyclerView.ViewHolder target, int toPos, int x, int y) {
234 | super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y);
235 | if (userActionListener != null) {
236 | userActionListener.onMoved(fromPos, toPos, addedChannels);
237 | }
238 | }
239 |
240 | @Override
241 | public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
242 | int position = viewHolder.getAdapterPosition();
243 | if (position != fixedPos) {
244 | ChannelItem removeChanle = addedChannels.remove(position);
245 | addedAdapter.notifyItemRemoved(position);
246 | insertToUnaddedChannel(removeChanle);
247 | if (userActionListener != null) {
248 | userActionListener.onSwiped(position, viewHolder.itemView, addedChannels, unAddedChannels);
249 | }
250 | }
251 | }
252 |
253 | @Override
254 | public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
255 | int position = viewHolder.getAdapterPosition();
256 | if (position != fixedPos) {
257 | super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
258 | }
259 | if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE && position != fixedPos) {
260 | float alpha = 1 - Math.abs(dX) / viewHolder.itemView.getWidth();
261 | viewHolder.itemView.setAlpha(alpha);
262 | viewHolder.itemView.setTranslationX(dX);
263 | }
264 | }
265 |
266 | @Override
267 | public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
268 | super.onSelectedChanged(viewHolder, actionState);
269 | if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
270 | ViewCompat.setScaleX(viewHolder.itemView, 1.1f);
271 | ViewCompat.setScaleY(viewHolder.itemView, 1.1f);
272 | viewHolder.itemView.setBackgroundResource(channelItemDragingBg);
273 | }
274 | }
275 |
276 | @Override
277 | public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
278 | super.clearView(recyclerView, viewHolder);
279 | ViewCompat.setScaleX(viewHolder.itemView, 1.0f);
280 | ViewCompat.setScaleY(viewHolder.itemView, 1.0f);
281 | if (viewHolder.getAdapterPosition() == fixedPos) {
282 | viewHolder.itemView.setBackgroundResource(fixedChannelBg);
283 | } else {
284 | viewHolder.itemView.setBackgroundResource(channelItemBg);
285 | }
286 | }
287 | });
288 | itemTouchHelper.attachToRecyclerView(addedRecyclerView);
289 | addedRecyclerView.setAdapter(addedAdapter = new ChannelAdapter(getContext(), R.layout.item_channel_view, addedChannels));
290 | unaddedRecyclerView.setAdapter(unAddedAdapter = new GroupedListAdapter(getContext(), unAddedGroups, openCategory));
291 | unaddedRecyclerView.setLayoutManager(unAddLayoutManager = new GroupedGridLayoutManager(getContext(), colums, unAddedAdapter));
292 | addedAdapter.setOnItemClickListener(new MultiItemTypeAdapter.OnItemClickListener() {
293 | @Override
294 | public void onItemClick(View view, RecyclerView.ViewHolder holder, int position) {
295 | if (showItemDrawableLeft) {
296 | return;
297 | }
298 | ((BGABadgeTextView) view.findViewById(R.id.item_tv)).hiddenBadge();
299 | if (onChannelItemClicklistener != null) {
300 | onChannelItemClicklistener.onAddedChannelItemClick(view, position);
301 | }
302 | }
303 |
304 | @Override
305 | public boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position) {
306 | return false;
307 | }
308 | });
309 | unAddedAdapter.setOnChildClickListener(new GroupedRecyclerViewAdapter.OnChildClickListener() {
310 | @Override
311 | public void onChildClick(GroupedRecyclerViewAdapter groupedRecyclerViewAdapter, BaseViewHolder baseViewHolder, int groupPosition, int childPosition) {
312 | if (showItemDrawableLeft) {
313 | return;
314 | }
315 | // 防止重复点击
316 | if ((System.currentTimeMillis() - lastClickTime) > MIN_INTERVAL_TIME) {
317 | lastClickTime = System.currentTimeMillis();
318 | int position = groupedRecyclerViewAdapter.getPositionForChild(groupPosition, childPosition);
319 | View convertView = baseViewHolder.get(R.id.item_tv);
320 | ((BGABadgeTextView) convertView).hiddenBadge();
321 | ChannelItem removeItem = unAddedGroups.get(groupPosition).getChannelItems().remove(childPosition);
322 | if (showPahtAnim) {
323 | startPahtAnim(convertView, groupPosition, childPosition, removeItem);
324 | } else {
325 | unAddedAdapter.removeChild(groupPosition, childPosition);
326 | addedChannels.add(removeItem);
327 | addedAdapter.notifyItemInserted(addedChannels.size() - 1);
328 | }
329 | if (onChannelItemClicklistener != null) {
330 | // 用户在这个回调处理自己的逻辑 如果分组 会添加一个分组头 所以要减去分组头
331 | onChannelItemClicklistener.onUnAddedChannelItemClick(convertView, openCategory ? position - (groupPosition + 1) : position);
332 | }
333 | }
334 | }
335 | });
336 |
337 | }
338 |
339 | private void insertToUnaddedChannel(ChannelItem removeChannel) {
340 | if (removeChannel.category == null || removeChannel.category.isEmpty()) {// 没有分组信息
341 | if (!categoryList.contains("其它")) {
342 | GroupItem groupItem = new GroupItem();
343 | groupItem.category = "其它";
344 | categoryList.add("其它");
345 | unAddedGroups.add(groupItem);
346 | unAddedAdapter.insertGroup(unAddedGroups.size() - 1);
347 | }
348 | unAddedGroups.get(unAddedGroups.size() - 1).addChanelItem(removeChannel);
349 | unAddedAdapter.insertChild(unAddedGroups.size() - 1, unAddedGroups.get(unAddedGroups.size() - 1).getChannelItems().size() - 1);
350 | } else {
351 | if (!categoryList.contains(removeChannel.category)) {
352 | GroupItem groupItem = new GroupItem();
353 | groupItem.category = removeChannel.category;
354 | categoryList.add(removeChannel.category);
355 | unAddedGroups.add(groupItem);
356 | unAddedAdapter.insertGroup(unAddedGroups.size() - 1);
357 | }
358 | for (int groupPos = 0; groupPos < unAddedGroups.size(); groupPos++) {
359 | GroupItem groupItem = unAddedGroups.get(groupPos);
360 | if (groupItem.category != null && groupItem.category.equals(removeChannel.category)) {
361 | groupItem.addChanelItem(removeChannel);
362 | unAddedAdapter.insertChild(groupPos, groupItem.getChannelItems().size() - 1);
363 | }
364 | }
365 | }
366 | getUnAddedChannels();
367 | }
368 |
369 | /**
370 | * 初始化频道数据
371 | *
372 | * @param addedChannels 已添加的频道
373 | * @param unAddedChannels 未添加的频道
374 | */
375 | public ChannelTagView initChannels(ArrayList addedChannels, ArrayList unAddedChannels, boolean openCategory, RedDotRemainderListener redDotRemainderListener) {
376 | this.redDotRemainderListener = redDotRemainderListener;
377 | if (addedChannels != null) {
378 | this.addedChannels.clear();
379 | this.addedChannels.addAll(addedChannels);
380 | this.addedAdapter.notifyDataSetChanged();
381 | }
382 | if (unAddedChannels != null) {
383 | unAddedGroups.clear();
384 | unAddedGroups.addAll(unAddedChannels);
385 | }
386 | for (GroupItem group : unAddedGroups) {
387 | categoryList.add(group.category);
388 | }
389 | getUnAddedChannels();
390 | this.openCategory = openCategory;
391 | unAddedAdapter.setOpenCategory(openCategory);
392 | unAddedAdapter.setRedDotRemainderListener(redDotRemainderListener);
393 | return this;
394 | }
395 |
396 | /**
397 | * 是否开启分组
398 | *
399 | * @param openCategory
400 | */
401 | public void oPenCategory(boolean openCategory) {
402 | this.openCategory = openCategory;
403 | unAddedAdapter.setOpenCategory(openCategory);
404 | }
405 |
406 | /**
407 | * 当前是否已开启分组
408 | *
409 | * @return
410 | */
411 | public boolean isOpenCategory() {
412 | return openCategory;
413 | }
414 |
415 | /**
416 | * 开始轨迹动画
417 | *
418 | * @param tragetView
419 | * @param childposition
420 | */
421 | private void startPahtAnim(View tragetView, final int groupPosition, final int childposition, final ChannelItem removeItem) {
422 | if (pathAnimator != null && pathAnimator.isRunning()) {
423 | return;
424 | }
425 | View lastCheckedChild = addedRecyclerView.getChildAt(addedChannels.size() - 1);
426 | Rect rectLast = new Rect();
427 | int tragetX = 0;
428 | int tragetY = 0;
429 | int statusBarHeight = MeasureUtil.getStatusBarHeight((Activity) getContext());
430 | if (null != lastCheckedChild) {
431 | lastCheckedChild.getGlobalVisibleRect(rectLast);
432 | if (addedChannels.size() % colums == 0) {// 换行
433 | tragetX = addedRecyclerView.getLeft() + addedRecyclerView.getPaddingLeft() + columnHorizontalSpace;
434 | tragetY = rectLast.top + tragetView.getHeight() + 2 * columnVerticalSpace - statusBarHeight;
435 | } else {
436 | tragetX = rectLast.right + 2 * columnHorizontalSpace;
437 | tragetY = rectLast.top - statusBarHeight;
438 | }
439 | } else {
440 | rectLast.left = addedRecyclerView.getLeft() + addedRecyclerView.getPaddingLeft();
441 | rectLast.top = addedRecyclerView.getTop() + addedRecyclerView.getPaddingTop();
442 | tragetX = rectLast.left;
443 | tragetY = rectLast.top;
444 | }
445 | Rect rectTarget = new Rect();
446 | tragetView.getGlobalVisibleRect(rectTarget);
447 | final Point point = new Point();
448 | point.x = rectTarget.left;
449 | point.y = rectTarget.top - statusBarHeight;
450 | FloatItemViewManager.showFloatADwindow(getContext(), tragetView, removeItem.title, channelItemBg, point);
451 |
452 | final Point updatePoint = new Point();
453 | ValueAnimator animX = ValueAnimator.ofFloat(point.x, tragetX);
454 | animX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
455 | @Override
456 | public void onAnimationUpdate(ValueAnimator animation) {
457 | float valueX = (float) animation.getAnimatedValue();
458 | updatePoint.x = (int) valueX;
459 | FloatItemViewManager.updateFloatViewPosition(updatePoint);
460 | }
461 | });
462 | ValueAnimator animY = ValueAnimator.ofFloat(point.y, tragetY);
463 | animY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
464 | @Override
465 | public void onAnimationUpdate(ValueAnimator animation) {
466 | float valueY = (float) animation.getAnimatedValue();
467 | updatePoint.y = (int) valueY;
468 | FloatItemViewManager.updateFloatViewPosition(updatePoint);
469 | }
470 | });
471 | // final ChannelItem removeItem = unAddedGroups.get(groupPosition).getChannelItems().remove(childposition);
472 | pathAnimator = new AnimatorSet();
473 | pathAnimator.setDuration(300);
474 | pathAnimator.addListener(new Animator.AnimatorListener() {
475 | @Override
476 | public void onAnimationStart(Animator animation) {
477 | unAddedAdapter.removeChild(groupPosition, childposition);
478 | }
479 |
480 | @Override
481 | public void onAnimationEnd(Animator animation) {
482 | addedChannels.add(removeItem);
483 | addedAdapter.notifyItemInserted(addedChannels.size() - 1);
484 | FloatItemViewManager.hideFloatView();
485 | }
486 |
487 | @Override
488 | public void onAnimationCancel(Animator animation) {
489 |
490 | }
491 |
492 | @Override
493 | public void onAnimationRepeat(Animator animation) {
494 |
495 | }
496 | });
497 | pathAnimator.play(animX).with(animY);
498 | pathAnimator.start();
499 |
500 | }
501 |
502 |
503 | private class ChannelAdapter extends CommonAdapter {
504 |
505 | public ChannelAdapter(Context context, int layoutId, List datas) {
506 | super(context, layoutId, datas);
507 | }
508 |
509 | @Override
510 | protected void convert(final ViewHolder holder, ChannelItem channelItem, final int position) {
511 | final BGABadgeTextView title = (BGABadgeTextView) holder.getConvertView().findViewById(R.id.item_tv);
512 | final ImageView drawableLeft = (ImageView) holder.getConvertView().findViewById(R.id.item_drawable_left);
513 | if (fixedPos == position) {
514 | holder.getConvertView().setBackgroundResource(fixedChannelBg);
515 | } else {
516 | holder.getConvertView().setBackgroundResource(channelItemBg);
517 | }
518 | title.setTextColor(channelItemTxColor);
519 | title.setTextSize(TypedValue.COMPLEX_UNIT_PX, channelItemTxSize);
520 | title.setText(channelItem.title);
521 | if (showItemDrawableLeft) {
522 | if (itemDrawableLeft != null) {
523 | drawableLeft.setImageDrawable(itemDrawableLeft);
524 | }
525 | if (itemBgDrawableRes != -1) {
526 | drawableLeft.setBackgroundResource(itemBgDrawableRes);
527 | }
528 | if (leftDrawableParams != null) {
529 | drawableLeft.setLayoutParams(leftDrawableParams);
530 | }
531 | drawableLeft.setVisibility(VISIBLE);
532 | drawableLeft.setOnClickListener(new OnClickListener() {
533 | @Override
534 | public void onClick(View v) {
535 | if (null != onChannelItemClicklistener) {
536 | int pos = holder.getAdapterPosition();
537 | if (pos != fixedPos) {
538 | ChannelItem removeChanle = addedChannels.remove(pos);
539 | addedAdapter.notifyItemRemoved(pos);
540 | insertToUnaddedChannel(removeChanle);
541 | onChannelItemClicklistener.onItemDrawableClickListener(holder.getConvertView(), pos);
542 | }
543 | }
544 | }
545 | });
546 | } else {
547 | drawableLeft.setVisibility(GONE);
548 | }
549 | if (redDotRemainderListener != null) {
550 | if (redDotRemainderListener.showAddedChannelBadge(title, position)) {
551 | title.getBadgeViewHelper().setDragable(false);
552 | redDotRemainderListener.handleAddedChannelReddot(title, position);
553 | } else {
554 | title.hiddenBadge();
555 | }
556 | }
557 | }
558 | }
559 |
560 | private class SpacesItemDecoration extends RecyclerView.ItemDecoration {
561 | private int horizontalSpace, verticalSpace;
562 |
563 | public SpacesItemDecoration(int spaceH, int spaceV) {
564 | this.horizontalSpace = spaceH;
565 | this.verticalSpace = spaceV;
566 | }
567 |
568 | @Override
569 | public void getItemOffsets(Rect outRect, View view,
570 | RecyclerView parent, RecyclerView.State state) {
571 | outRect.left = horizontalSpace;
572 | outRect.right = horizontalSpace;
573 | outRect.bottom = verticalSpace;
574 | outRect.top = verticalSpace;
575 | }
576 |
577 | public int getHorizontalSpace() {
578 | return horizontalSpace;
579 | }
580 |
581 | public void setHorizontalSpace(int horizontalSpace) {
582 | this.horizontalSpace = horizontalSpace;
583 | }
584 |
585 | public int getVerticalSpace() {
586 | return verticalSpace;
587 | }
588 |
589 | public void setVerticalSpace(int verticalSpace) {
590 | this.verticalSpace = verticalSpace;
591 | }
592 | }
593 |
594 | /**
595 | * get Added channel group RecyclerView;
596 | *
597 | * @return
598 | */
599 | public RecyclerView getAddedRecyclerView() {
600 | return addedRecyclerView;
601 | }
602 |
603 | /**
604 | * get unAdded channel group RecyclerView
605 | *
606 | * @return
607 | */
608 | public RecyclerView getUnaddedRecyclerView() {
609 | return unaddedRecyclerView;
610 | }
611 |
612 | /**
613 | * 获取已添加栏目banner可以自定义文字内容和样式
614 | *
615 | * @return
616 | */
617 | public TextView getCategaryAddedTopView() {
618 | return categaryAddedTopView;
619 | }
620 |
621 | /**
622 | * 获取未添加栏目banner 可以自定义文字内容和样式
623 | *
624 | * @return
625 | */
626 | public TextView getCategrayUnAddedTopView() {
627 | return categrayUnAddedTopView;
628 | }
629 |
630 | /**
631 | * 是否开启轨迹动画
632 | *
633 | * @param showPahtAnim
634 | */
635 | public void showPahtAnim(boolean showPahtAnim) {
636 | this.showPahtAnim = showPahtAnim;
637 | }
638 |
639 | /**
640 | * 设置channel item的列数
641 | *
642 | * @param colums
643 | */
644 | public void setColums(int colums) {
645 | if (colums <= 0) {
646 | return;
647 | }
648 | this.colums = colums;
649 | addedLayoutManager.setSpanCount(colums);
650 | unAddLayoutManager.setSpanCount(colums);
651 | }
652 |
653 |
654 | /**
655 | * 获取列间距
656 | *
657 | * @return
658 | */
659 | public int getColumnHorizontalSpace() {
660 | return columnHorizontalSpace;
661 | }
662 |
663 | /**
664 | * 设置列间距
665 | *
666 | * @param columnHorizontalSpace
667 | */
668 | public void setColumnHorizontalSpace(int columnHorizontalSpace) {
669 | if (columnHorizontalSpace < 0) {
670 | return;
671 | }
672 | this.columnHorizontalSpace = columnHorizontalSpace;
673 | itemDecoration.setHorizontalSpace(columnHorizontalSpace);
674 | }
675 |
676 | /**
677 | * 获取行间距
678 | *
679 | * @return
680 | */
681 | public int getColumnVerticalSpace() {
682 | return columnVerticalSpace;
683 | }
684 |
685 | /**
686 | * 获取行间距
687 | *
688 | * @param columnVerticalSpace
689 | */
690 | public void setColumnVerticalSpace(int columnVerticalSpace) {
691 | if (columnVerticalSpace < 0) {
692 | return;
693 | }
694 | this.columnVerticalSpace = columnVerticalSpace;
695 | itemDecoration.setVerticalSpace(columnVerticalSpace);
696 | }
697 |
698 | /**
699 | * set which channel item fixed position
700 | *
701 | * @param fixedPos
702 | */
703 | public void setFixedPos(int fixedPos) {
704 | this.fixedPos = fixedPos;
705 | }
706 |
707 | /**
708 | * remove fixed position
709 | */
710 | public void removeFixedItem() {
711 | this.fixedPos = -1;
712 | addedAdapter.notifyDataSetChanged();
713 | }
714 |
715 | /**
716 | * set fixed item background ResID
717 | *
718 | * @param fixedChannelBgResID
719 | */
720 | public void setFixedChannelBg(int fixedChannelBgResID) {
721 | this.fixedChannelBg = fixedChannelBgResID;
722 | }
723 |
724 | /**
725 | * set item draging background Res ID
726 | *
727 | * @param channelItemDragingBgResID
728 | */
729 | public void setChannelItemDragingBg(int channelItemDragingBgResID) {
730 | this.channelItemDragingBg = channelItemDragingBgResID;
731 | }
732 |
733 | /**
734 | * set channel item background ResID
735 | *
736 | * @param channelItemBgResID
737 | */
738 | public void setChannelItemBg(int channelItemBgResID) {
739 | this.channelItemBg = channelItemBgResID;
740 | unAddedAdapter.setItemBg(channelItemBg);
741 | }
742 |
743 | /**
744 | * 设置频道字体颜色
745 | *
746 | * @param channelItemTxColor 颜色值
747 | */
748 | public void setChannelItemTxColor(int channelItemTxColor) {
749 | this.channelItemTxColor = channelItemTxColor;
750 | unAddedAdapter.setItemTxColor(channelItemTxColor);
751 | }
752 |
753 | /**
754 | * 设置频道字体大小
755 | *
756 | * @param pixel 大小(pixel)
757 | */
758 | public void setChannelItemTxSizePixel(int pixel) {
759 | this.channelItemTxSize = channelItemTxSize;
760 | }
761 |
762 | /**
763 | * 设置频道字体大小(sp)
764 | *
765 | * @param sp 大小(sp)
766 | */
767 | public void setChannelItemTxSizeSP(int sp) {
768 | this.channelItemTxSize = MeasureUtil.sp2px(getContext(), channelItemTxSize);
769 | }
770 |
771 | /**
772 | * 设置已添加栏目的banner background
773 | *
774 | * @param categoryAddedBannerBg
775 | */
776 | public void setCategoryAddedBannerBg(int categoryAddedBannerBg) {
777 | this.categoryAddedBannerBg = categoryAddedBannerBg;
778 | categaryAddedTopView.setBackgroundResource(categoryAddedBannerBg);
779 | }
780 |
781 | /**
782 | * 设置未添加栏目的banner background
783 | *
784 | * @param categoryUnAddedBannerBg
785 | */
786 | public void setCategoryUnAddedBannerBg(int categoryUnAddedBannerBg) {
787 | this.categoryUnAddedBannerBg = categoryUnAddedBannerBg;
788 | categrayUnAddedTopView.setBackgroundResource(categoryUnAddedBannerBg);
789 | }
790 |
791 | /**
792 | * 设置已添加栏目banner的文字
793 | *
794 | * @param bannerTX
795 | */
796 | public void setCategaryAddedBannerTX(String bannerTX) {
797 | categaryAddedTopView.setText(bannerTX);
798 | }
799 |
800 | /**
801 | * 设置未添加栏目的banner文字
802 | *
803 | * @param bannerTX
804 | */
805 | public void setCategrayUnAddedBannerTX(String bannerTX) {
806 | categrayUnAddedTopView.setText(bannerTX);
807 | }
808 |
809 | /**
810 | * 设置 banner的文字大小
811 | *
812 | * @param pixel
813 | */
814 | public void setCategoryBannerTXsize(int pixel) {
815 | categaryAddedTopView.setTextSize(TypedValue.COMPLEX_UNIT_PX, pixel);
816 | categrayUnAddedTopView.setTextSize(TypedValue.COMPLEX_UNIT_PX, pixel);
817 | }
818 |
819 | /**
820 | * 设置banner的文字颜色
821 | *
822 | * @param colorValue
823 | */
824 | public void setCategoryBannerTXColor(int colorValue) {
825 | categaryAddedTopView.setTextColor(colorValue);
826 | categrayUnAddedTopView.setTextColor(colorValue);
827 | }
828 |
829 | /**
830 | * 设置未添加栏目的分组头 bg
831 | *
832 | * @param bgResID
833 | */
834 | public void setCategoryItemBg(int bgResID) {
835 | unAddedAdapter.setCategoryBg(bgResID);
836 | }
837 |
838 | /**
839 | * 设置未添加栏目的分组头文字颜色
840 | *
841 | * @param color
842 | */
843 | public void setCategoryItemTxColor(int color) {
844 | unAddedAdapter.setCategoryTxColor(color);
845 | }
846 |
847 | /**
848 | * 设置未添加栏目的分组头文字大小
849 | *
850 | * @param size
851 | */
852 | public void setCategoryItemTxSize(int size) {
853 | unAddedAdapter.setCategoryTxSize(size);
854 | }
855 |
856 | /**
857 | * get weather swipe enable
858 | *
859 | * @return true or false
860 | */
861 | public boolean isSwipeEnable() {
862 | return swipeEnable;
863 | }
864 |
865 | /**
866 | * set weather swipe enable
867 | */
868 | public void setSwipeEnable(boolean swipeEnable) {
869 | this.swipeEnable = swipeEnable;
870 | }
871 |
872 | @Override
873 | protected void onDetachedFromWindow() {
874 | super.onDetachedFromWindow();
875 | FloatItemViewManager.removeFloawAdView(getContext());
876 | }
877 |
878 | /**
879 | * weather or not show item left drawable
880 | *
881 | * @param showDrawableLeft
882 | */
883 | public void showItemDrawableLeft(boolean showDrawableLeft) {
884 | showItemDrawableLeft = showDrawableLeft;
885 | // longPressEnable = !showItemDrawableLeft;
886 | // swipeEnable = !showDrawableLeft;
887 | addedAdapter.notifyDataSetChanged();
888 | }
889 |
890 | /**
891 | * get weather or not show item left drawbale
892 | *
893 | * @return
894 | */
895 | public boolean isShowItemDrawableLeft() {
896 | return showItemDrawableLeft;
897 | }
898 |
899 | /**
900 | * set item left drawable
901 | *
902 | * @param itemDrawableLeft
903 | */
904 | public void setItemDrawableLeft(Drawable itemDrawableLeft) {
905 | this.itemDrawableLeft = itemDrawableLeft;
906 | }
907 |
908 | public void setItemLeftDrawableBgRes(int itemLeftDrawableBgRes) {
909 | this.itemBgDrawableRes = itemLeftDrawableBgRes;
910 | }
911 |
912 | public void setItemLeftDrawableLayoutParams(RelativeLayout.LayoutParams layoutParams) {
913 | this.leftDrawableParams = layoutParams;
914 | }
915 |
916 | ;
917 |
918 | public ArrayList getUnAddedChannels() {
919 | unAddedChannels.clear();
920 | for (GroupItem groupItem : unAddedGroups) {
921 | unAddedChannels.addAll(groupItem.getChannelItems());
922 | }
923 | return unAddedChannels;
924 | }
925 |
926 | public ArrayList getAddedChannels() {
927 | return addedChannels;
928 | }
929 |
930 | /**
931 | * 描述:频道item的红点提示处理回调接口
932 | * author: zhl 2017-3-20
933 | */
934 | public interface RedDotRemainderListener {
935 | /**
936 | * 已添加栏目是否显示红点提示view
937 | *
938 | * @param itemView
939 | * @param position
940 | * @return return true show the bgabadgeTipView otherwish not show
941 | */
942 | public boolean showAddedChannelBadge(BGABadgeTextView itemView, int position);
943 |
944 | /**
945 | * 未添加栏目是否显示红点提示view
946 | *
947 | * @param itemView
948 | * @param position
949 | * @return return true show the bgabadgeTipView otherwish not show
950 | */
951 | public boolean showUnAddedChannelBadge(BGABadgeTextView itemView, int position);
952 |
953 | /**
954 | * 处理已添加栏目红点提示view 通过BGABadgeTextView.getBadgeViewHelper()可以设置红点提示view一些属性 如:间距 显示样式 文字大小等等
955 | *
956 | * @param itemView
957 | * @param position
958 | */
959 | public void handleAddedChannelReddot(BGABadgeTextView itemView, int position);
960 |
961 | /**
962 | * 处理已添加栏目红点提示view 通过BGABadgeTextView.getBadgeViewHelper()可以设置红点提示view一些属性 如:间距 显示样式 文字大小等等
963 | *
964 | * @param itemView
965 | * @param position
966 | */
967 | public void handleUnAddedChannelReddot(BGABadgeTextView itemView, int position);
968 |
969 | /**
970 | * 拖拽提示view消失回调
971 | *
972 | * @param itemView
973 | * @param position
974 | */
975 | public void OnDragDismiss(BGABadgeTextView itemView, int position);
976 |
977 | }
978 |
979 | /**
980 | * 设置channel item 的点击回调事件
981 | *
982 | * @param onChannelItemClicklistener
983 | */
984 | public void setOnChannelItemClicklistener(OnChannelItemClicklistener onChannelItemClicklistener) {
985 | this.onChannelItemClicklistener = onChannelItemClicklistener;
986 | }
987 |
988 | /**
989 | * 设置对channel item的拖拽、欢动的操作回调监听
990 | *
991 | * @param userActionListener
992 | */
993 | public void setUserActionListener(UserActionListener userActionListener) {
994 | this.userActionListener = userActionListener;
995 | }
996 | }
997 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/java/com/zhl/channeltagview/view/FloatItemView.java:
--------------------------------------------------------------------------------
1 | package com.zhl.channeltagview.view;
2 |
3 | import android.content.Context;
4 | import android.graphics.drawable.Drawable;
5 | import android.util.AttributeSet;
6 | import android.util.TypedValue;
7 | import android.view.LayoutInflater;
8 | import android.view.MotionEvent;
9 | import android.view.View;
10 | import android.view.WindowManager;
11 | import android.widget.LinearLayout;
12 | import android.widget.TextView;
13 |
14 | import com.zhl.channeltagview.R;
15 |
16 | import java.lang.reflect.Field;
17 |
18 | /**
19 | * @author huang-hua
20 | * @date 2017-3-20
21 | * 描述:
22 | *
23 | */
24 | public class FloatItemView extends LinearLayout {
25 | /**
26 | * 记录小悬浮窗的宽度
27 | */
28 | public static int viewWidth;
29 |
30 | /**
31 | * 记录小悬浮窗的高度
32 | */
33 | public static int viewHeight;
34 |
35 | /**
36 | * 记录系统状态栏的高度
37 | */
38 | private static int statusBarHeight;
39 |
40 | /**
41 | * 用于更新小悬浮窗的位置
42 | */
43 | private WindowManager windowManager;
44 |
45 | /**
46 | * 小悬浮窗的参数
47 | */
48 | private WindowManager.LayoutParams mParams;
49 |
50 | /**
51 | * 记录当前手指位置在屏幕上的横坐标值
52 | */
53 | private float xInScreen;
54 |
55 | /**
56 | * 记录当前手指位置在屏幕上的纵坐标值
57 | */
58 | private float yInScreen;
59 |
60 | /**
61 | * 记录手指按下时在屏幕上的横坐标的值
62 | */
63 | private float xDownInScreen;
64 |
65 | /**
66 | * 记录手指按下时在屏幕上的纵坐标的值
67 | */
68 | private float yDownInScreen;
69 |
70 | /**
71 | * 记录手指按下时在小悬浮窗的View上的横坐标的值
72 | */
73 | private float xInView;
74 |
75 | /**
76 | * 记录手指按下时在小悬浮窗的View上的纵坐标的值
77 | */
78 | private float yInView;
79 | private TextView floatView;
80 | private Context ctx;
81 |
82 | public FloatItemView(Context context) {
83 | super(context);
84 | this.ctx = context;
85 | windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
86 | LayoutInflater.from(context).inflate(R.layout.float_item_view,this);
87 | View view = findViewById(R.id.floatLayout);
88 | viewWidth = view.getLayoutParams().width;
89 | viewHeight = view.getLayoutParams().height;
90 | floatView = (TextView) findViewById(R.id.floatText);
91 | }
92 |
93 | public FloatItemView(Context context, AttributeSet attrs) {
94 | super(context, attrs);
95 | }
96 |
97 | public FloatItemView(Context context, AttributeSet attrs, int defStyleAttr) {
98 | super(context, attrs, defStyleAttr);
99 | }
100 |
101 |
102 | public void setFloatTitle(String title){
103 | if(floatView!=null){
104 | floatView.setText(title);
105 | }
106 | }
107 |
108 | @Override
109 | public boolean onTouchEvent(MotionEvent event) {
110 | switch (event.getAction()) {
111 | case MotionEvent.ACTION_DOWN:
112 | // 手指按下时记录必要数据,纵坐标的值都需要减去状态栏高度
113 | xInView = event.getX();
114 | yInView = event.getY();
115 | xDownInScreen = event.getRawX();
116 | yDownInScreen = event.getRawY() - getStatusBarHeight();
117 | xInScreen = event.getRawX();
118 | yInScreen = event.getRawY() - getStatusBarHeight();
119 | break;
120 | case MotionEvent.ACTION_MOVE:
121 | xInScreen = event.getRawX();
122 | yInScreen = event.getRawY() - getStatusBarHeight();
123 | // 手指移动的时候更新小悬浮窗的位置
124 | updateViewPosition();
125 | break;
126 | case MotionEvent.ACTION_UP:
127 | // 如果手指离开屏幕时,xDownInScreen和xInScreen相等,且yDownInScreen和yInScreen相等,则视为触发了单击事件。
128 | // if (xDownInScreen == xInScreen && yDownInScreen == yInScreen) {
129 | // }
130 | break;
131 | default:
132 | break;
133 | }
134 | return true;
135 | }
136 |
137 |
138 | public TextView getFloatView() {
139 | return floatView;
140 | }
141 |
142 | /**
143 | * 将小悬浮窗的参数传入,用于更新小悬浮窗的位置。
144 | *
145 | * @param params 小悬浮窗的参数
146 | */
147 | public void setParams(WindowManager.LayoutParams params) {
148 | mParams = params;
149 | }
150 |
151 | public void setFloatViewBg(int bgResid){
152 | if(floatView!=null){
153 | floatView.setBackgroundResource(bgResid);
154 | }
155 | }
156 | public void setFloatViewBgDrawable(Drawable drawable){
157 | if(floatView!=null){
158 | floatView.setBackgroundDrawable(drawable);
159 | }
160 | }
161 |
162 | /**
163 | * 更新小悬浮窗在屏幕中的位置。
164 | */
165 | private void updateViewPosition() {
166 | mParams.x = (int) (xInScreen - xInView);
167 | mParams.y = (int) (yInScreen - yInView);
168 | windowManager.updateViewLayout(this, mParams);
169 | }
170 |
171 | public void updatePosition(){
172 | windowManager.updateViewLayout(this, mParams);
173 | }
174 |
175 | public void setTextSize(float size){
176 | floatView.setTextSize(TypedValue.COMPLEX_UNIT_SP,size);
177 | }
178 |
179 | /**
180 | * 用于获取状态栏的高度。
181 | *
182 | * @return 返回状态栏高度的像素值。
183 | */
184 | private int getStatusBarHeight() {
185 | if (statusBarHeight == 0) {
186 | try {
187 | Class> c = Class.forName("com.android.internal.R$dimen");
188 | Object o = c.newInstance();
189 | Field field = c.getField("status_bar_height");
190 | int x = (Integer) field.get(o);
191 | statusBarHeight = getResources().getDimensionPixelSize(x);
192 | } catch (Exception e) {
193 | e.printStackTrace();
194 | }
195 | }
196 | return statusBarHeight;
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/java/com/zhl/channeltagview/view/FloatItemViewManager.java:
--------------------------------------------------------------------------------
1 | package com.zhl.channeltagview.view;
2 |
3 | import android.animation.Animator;
4 | import android.animation.ObjectAnimator;
5 | import android.content.Context;
6 | import android.graphics.PixelFormat;
7 | import android.graphics.Point;
8 | import android.os.Build;
9 | import android.util.TypedValue;
10 | import android.view.Gravity;
11 | import android.view.View;
12 | import android.view.WindowManager;
13 | import android.widget.TextView;
14 |
15 | /**
16 | * 描述:浮动窗口管理
17 | * Created by zhaohl on 2016-11-3.
18 | */
19 | public class FloatItemViewManager {
20 | private static ObjectAnimator alphaAnim;
21 | public static FloatItemView floatItemView;
22 | public static Point lastPoint = null;
23 | /**
24 | * 小悬浮窗View的参数
25 | */
26 | private static WindowManager.LayoutParams smallWindowParams;
27 | private static Context ctx;
28 |
29 | /**
30 | * 显示一个floatview
31 | * @param context
32 | * @param title
33 | * @param bgResid
34 | * @param point
35 | */
36 | public static void showFloatADwindow(Context context, View targetView, String title, int bgResid, Point point) {
37 | lastPoint = point;
38 | ctx = context;
39 | if(floatItemView!=null&&alphaAnim!=null&&alphaAnim.isRunning()){
40 | alphaAnim.cancel();
41 | removeFloawAdView(context);
42 | }
43 | WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
44 | // int []screenSize = MeasureUtil.getScreenSize(context);
45 | // int screenWidth = screenSize[0];
46 | // int screenHeight = screenSize[1];
47 | if (floatItemView == null) {
48 | floatItemView = new FloatItemView(context);
49 | floatItemView.getFloatView().setText(title);
50 | if(bgResid!=-1){
51 | floatItemView.setFloatViewBg(bgResid);
52 | }
53 | // TODO 这里未添加频道的textsize 比已添加的textsize 要大1 还没找到原因 使用同样的item布局
54 | floatItemView.getFloatView().setTextSize(TypedValue.COMPLEX_UNIT_PX,((TextView)targetView).getTextSize()-1);
55 | floatItemView.getFloatView().setTextColor(((TextView)targetView).getCurrentTextColor());
56 |
57 | if (smallWindowParams == null) {
58 | smallWindowParams = new WindowManager.LayoutParams();
59 | // API level 19 之后 TYPE_TOAST 可以接受事件且不需要申请权限
60 | // TYPE_APPLICATION 只能配合Activity在当前APP使用
61 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
62 | smallWindowParams.type = WindowManager.LayoutParams.TYPE_TOAST;
63 | } else {
64 | smallWindowParams.type = WindowManager.LayoutParams.TYPE_PHONE;
65 | }
66 | smallWindowParams.format = PixelFormat.RGBA_8888;
67 | smallWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
68 | | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
69 | smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP;
70 | smallWindowParams.width = targetView.getWidth();
71 | smallWindowParams.height = targetView.getHeight();
72 | smallWindowParams.x = point.x;
73 | smallWindowParams.y = point.y;
74 | }
75 | floatItemView.setParams(smallWindowParams);
76 | windowManager.addView(floatItemView, smallWindowParams);
77 | }
78 | // else{
79 | // updateFloatViewPosition(point);
80 | // floatItemView.setFloatTitle(title);
81 | // if(bgResid!=-1){
82 | // floatItemView.setFloatViewBg(bgResid);
83 | // }
84 | // floatItemView.setAlpha(1);
85 | // }
86 | }
87 | /**
88 | * 描述:更新位置
89 | * @param point
90 | */
91 | public static void updateFloatViewPosition(Point point){
92 | smallWindowParams.x = point.x;
93 | smallWindowParams.y = point.y;
94 | floatItemView.setParams(smallWindowParams);
95 | floatItemView.updatePosition();
96 | }
97 | /**
98 | * 将小悬浮窗从屏幕上移除。
99 | *
100 | * @param context
101 | * 必须为应用程序的Context.
102 | */
103 | public static void removeFloawAdView(Context context) {
104 | if (floatItemView != null) {
105 | if(context!=null){
106 | WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
107 | windowManager.removeView(floatItemView);
108 | floatItemView = null;
109 | ctx = null;
110 | }
111 | }
112 | }
113 |
114 | public static void hideFloatView(){
115 | if(alphaAnim==null){
116 | alphaAnim = ObjectAnimator.ofFloat(floatItemView,"alpha",1,0);
117 | alphaAnim.setDuration(200);
118 | alphaAnim.addListener(new Animator.AnimatorListener() {
119 | @Override
120 | public void onAnimationStart(Animator animation) {
121 |
122 | }
123 | @Override
124 | public void onAnimationEnd(Animator animation) {
125 | removeFloawAdView(ctx);
126 | }
127 | @Override
128 | public void onAnimationCancel(Animator animation) {
129 |
130 | }
131 | @Override
132 | public void onAnimationRepeat(Animator animation) {
133 |
134 | }
135 | });
136 | }
137 | alphaAnim.start();
138 | }
139 |
140 | /**
141 | * 是否有悬浮窗(包括小悬浮窗和大悬浮窗)显示在屏幕上。
142 | *
143 | * @return 有悬浮窗显示在桌面上返回true,没有的话返回false。
144 | */
145 | public static boolean isWindowShowing() {
146 | return floatItemView != null;
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/res/drawable/channel_item_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 |
10 | -
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/res/drawable/channel_item_draging.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
9 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/res/drawable/error_drawable_circle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 | -
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/res/drawable/error_inside.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 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/res/drawable/fixed_item_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 |
10 | -
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/res/layout/channel_tag_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
15 |
16 |
22 |
23 |
31 |
32 |
38 |
39 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/res/layout/float_item_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/res/layout/item_channel_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
19 |
20 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/res/layout/item_mulite_banner.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/res/values/attr.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #64ce9d
4 | #8dbece
5 | #17b6e2
6 | #ffffff
7 | #000000
8 | #ff0c0c
9 | #989898
10 | #8dbece
11 | #c8c9ca
12 | #338e8e91
13 | #33000001
14 | #4ea6ed
15 |
16 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ChannelTagView
3 |
4 |
--------------------------------------------------------------------------------
/channeltagviewLib/src/test/java/com/zhl/channeltagview/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.zhl.channeltagview;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yilylong/ChannelTagView/d55e95e0e8c4f77ad2b68a42c0234a2d4bdb454a/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Mar 15 12:38:28 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':channeltagviewLib'
2 |
--------------------------------------------------------------------------------
/show.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yilylong/ChannelTagView/d55e95e0e8c4f77ad2b68a42c0234a2d4bdb454a/show.mp4
--------------------------------------------------------------------------------
/show_category.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yilylong/ChannelTagView/d55e95e0e8c4f77ad2b68a42c0234a2d4bdb454a/show_category.png
--------------------------------------------------------------------------------