├── LICENSE
├── README.md
└── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
├── release
├── app-release.apk
└── output.json
└── src
├── androidTest
└── java
│ └── com
│ └── example
│ └── zhangzhihao
│ └── channelmanagedemo
│ └── ExampleInstrumentedTest.java
├── main
├── AndroidManifest.xml
├── java
│ └── com
│ │ └── example
│ │ └── zhangzhihao
│ │ └── channelmanagedemo
│ │ ├── ChannelAdapter.java
│ │ ├── ChannelBean.java
│ │ ├── GridSpacingItemDecoration.java
│ │ ├── ItemDragCallback.java
│ │ └── MainActivity.java
└── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ ├── ic_launcher_background.xml
│ └── icon_close.png
│ ├── layout
│ ├── activity_main.xml
│ ├── adapter_channel.xml
│ ├── adapter_more_channel.xml
│ ├── adapter_tab.xml
│ └── adapter_title.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
└── test
└── java
└── com
└── example
└── zhangzhihao
└── channelmanagedemo
└── ExampleUnitTest.java
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 zzh12138
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ChannelManageDemo_Java
2 | https://www.jianshu.com/p/57324eb516df
3 | ###### ChannelManageDemo by java
4 | ### 高仿腾讯新闻频道管理页面
5 | #### 用recyclerView+ItemTouchHelper实现
6 | 
7 |
8 | ## License
9 | MIT
10 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 26
5 | defaultConfig {
6 | applicationId "com.example.zhangzhihao.channelmanagedemo"
7 | minSdkVersion 15
8 | targetSdkVersion 26
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | implementation fileTree(dir: 'libs', include: ['*.jar'])
23 | implementation 'com.android.support:appcompat-v7:26.1.0'
24 | compile 'com.android.support:recyclerview-v7:26.1.0'
25 | implementation 'com.android.support.constraint:constraint-layout:1.0.2'
26 | testImplementation 'junit:junit:4.12'
27 | androidTestImplementation 'com.android.support.test:runner:1.0.1'
28 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
29 | }
30 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/release/app-release.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzh12138/ChannelManageDemo_Java/42eb374e29a8057e96b88e23b0ae04106d6369a5/app/release/app-release.apk
--------------------------------------------------------------------------------
/app/release/output.json:
--------------------------------------------------------------------------------
1 | [{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":1},"path":"app-release.apk","properties":{"packageId":"com.example.zhangzhihao.channelmanagedemo","split":"","minSdkVersion":"15"}}]
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/example/zhangzhihao/channelmanagedemo/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.example.zhangzhihao.channelmanagedemo;
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 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.example.zhangzhihao.channelmanagedemo", 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/example/zhangzhihao/channelmanagedemo/ChannelAdapter.java:
--------------------------------------------------------------------------------
1 | package com.example.zhangzhihao.channelmanagedemo;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.animation.AnimatorSet;
6 | import android.animation.ObjectAnimator;
7 | import android.content.Context;
8 | import android.graphics.Typeface;
9 | import android.support.v7.widget.RecyclerView;
10 | import android.text.Layout;
11 | import android.view.LayoutInflater;
12 | import android.view.View;
13 | import android.view.ViewGroup;
14 | import android.view.ViewTreeObserver;
15 | import android.widget.ImageView;
16 | import android.widget.LinearLayout;
17 | import android.widget.TextView;
18 |
19 | import java.util.Collections;
20 | import java.util.List;
21 |
22 |
23 | /**
24 | * Created by zhangzhihao on 2018/2/26.
25 | */
26 |
27 | public class ChannelAdapter extends RecyclerView.Adapter {
28 | private Context mContext;
29 | private List mList;
30 | private List recommendList; //推荐频道
31 | private List cityList; //地方新闻
32 | private int selectedSize;
33 | private int fixSize; //已选频道中固定频道大小
34 | private boolean isRecommend; //当前是否显示推荐频道
35 | private onItemRangeChangeListener onItemRangeChangeListener;
36 | private int mLeft, mRight; //蓝色线条距离屏幕左边的距离
37 | private int mTabY; //Tab距离parent的Y的距离
38 |
39 |
40 | public ChannelAdapter(Context mContext, List mList, List recommendList, List cityList) {
41 | this.mContext = mContext;
42 | this.mList = mList;
43 | this.recommendList = recommendList;
44 | this.cityList = cityList;
45 | mLeft = -1;
46 | mRight = -1;
47 | }
48 |
49 | public void setOnItemRangeChangeListener(ChannelAdapter.onItemRangeChangeListener onItemRangeChangeListener) {
50 | this.onItemRangeChangeListener = onItemRangeChangeListener;
51 | }
52 |
53 | public int getSelectedSize() {
54 | return selectedSize;
55 | }
56 |
57 | public void setSelectedSize(int selectedSize) {
58 | this.selectedSize = selectedSize;
59 | }
60 |
61 | public int getFixSize() {
62 | return fixSize;
63 | }
64 |
65 | public void setFixSize(int fixSize) {
66 | this.fixSize = fixSize;
67 | }
68 |
69 | public void setRecommend(boolean recommend) {
70 | isRecommend = recommend;
71 | }
72 |
73 | @Override
74 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
75 | View view = LayoutInflater.from(mContext).inflate(viewType, parent, false);
76 | if (viewType == R.layout.adapter_channel) {
77 | return new ChannelHolder(view);
78 | } else if (viewType == R.layout.adapter_more_channel) {
79 | return new MoreChannelHolder(view);
80 | } else if (viewType == R.layout.adapter_tab) {
81 | return new TabHolder(view);
82 | } else {
83 | return new TitleHolder(view);
84 | }
85 | }
86 |
87 | @Override
88 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
89 | if (holder instanceof ChannelHolder) {
90 | setChannel((ChannelHolder) holder, mList.get(position));
91 | } else if (holder instanceof MoreChannelHolder) {
92 | setMoreChannel((MoreChannelHolder) holder);
93 | } else if (holder instanceof TabHolder) {
94 | setTab((TabHolder) holder);
95 | } else {
96 |
97 | }
98 | }
99 |
100 | private void setChannel(final ChannelHolder holder, ChannelBean bean) {
101 | final int position = holder.getLayoutPosition();
102 | holder.name.setText(bean.getName());
103 | holder.name.setOnClickListener(new View.OnClickListener() {
104 | @Override
105 | public void onClick(View v) {
106 | if (holder.getLayoutPosition() < selectedSize + 1) {
107 | //tab上面的 点击移除
108 | if (holder.getLayoutPosition() > fixSize) {
109 | removeFromSelected(holder);
110 | }
111 | } else {
112 | //tab下面的 点击添加到已选频道
113 | selectedSize++;
114 | itemMove(holder.getLayoutPosition(), selectedSize);
115 | notifyItemChanged(selectedSize);
116 | if (onItemRangeChangeListener != null) {
117 | onItemRangeChangeListener.refreshItemDecoration();
118 | }
119 | }
120 | }
121 | });
122 | holder.name.setOnLongClickListener(new View.OnLongClickListener() {
123 | @Override
124 | public boolean onLongClick(View v) {
125 | //返回true 防止长按拖拽事件跟点击事件冲突
126 | return true;
127 | }
128 | });
129 | holder.delete.setOnClickListener(new View.OnClickListener() {
130 | @Override
131 | public void onClick(View v) {
132 | removeFromSelected(holder);
133 | }
134 | });
135 |
136 | //tab下面的不显示删除按钮
137 | if (position - 1 < fixSize || position > selectedSize) {
138 | holder.delete.setVisibility(View.GONE);
139 | } else {
140 | holder.delete.setVisibility(View.VISIBLE);
141 | }
142 | }
143 |
144 | private void setMoreChannel(MoreChannelHolder holder) {
145 | holder.itemView.setOnClickListener(new View.OnClickListener() {
146 | @Override
147 | public void onClick(View v) {
148 | //todo to more channel activity
149 | }
150 | });
151 | }
152 |
153 | private void setTab(final TabHolder holder) {
154 | final LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) holder.indicator.getLayoutParams();
155 | //测量蓝色线条距离
156 | if (mLeft == -1) {
157 | holder.recommend.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
158 | @Override
159 | public boolean onPreDraw() {
160 | Layout layout = holder.recommend.getLayout();
161 | mLeft = (int) (holder.recommend.getLeft() + layout.getPrimaryHorizontal(0)) - MainActivity.dip2px(mContext, 10); //textView左边距离+第一个文字绘制的距离 设置了padding 所以要减掉
162 | params.leftMargin = mLeft;
163 | holder.indicator.setLayoutParams(params);
164 | holder.recommend.getViewTreeObserver().removeOnPreDrawListener(this);
165 | return true;
166 | }
167 | });
168 | }
169 | holder.city.setOnClickListener(new View.OnClickListener() {
170 | @Override
171 | public void onClick(View v) {
172 | if (isRecommend) {
173 | holder.city.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
174 | holder.recommend.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));
175 | if (mRight == -1) {
176 | mRight = mLeft + holder.city.getLeft() - MainActivity.dip2px(mContext, 10);
177 | }
178 | params.leftMargin = mRight;
179 | isRecommend = false;
180 | recommendList.clear();
181 | recommendList.addAll(mList.subList(selectedSize + 2, mList.size()));
182 | mList.removeAll(recommendList);
183 | mList.addAll(cityList);
184 | notifyDataSetChanged();
185 | }
186 | }
187 | });
188 | holder.recommend.setOnClickListener(new View.OnClickListener() {
189 | @Override
190 | public void onClick(View v) {
191 | if (!isRecommend) {
192 | holder.city.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));
193 | holder.recommend.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
194 | params.leftMargin = mLeft;
195 | isRecommend = true;
196 | cityList.clear();
197 | cityList.addAll(mList.subList(selectedSize + 2, mList.size()));
198 | mList.removeAll(cityList);
199 | mList.addAll(recommendList);
200 | notifyDataSetChanged();
201 | }
202 | }
203 | });
204 | final ViewTreeObserver observer = holder.itemView.getViewTreeObserver();
205 | observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
206 | @Override
207 | public boolean onPreDraw() {
208 | //计算tab的Y,用于后面的动画 需要一直监听
209 | mTabY = holder.itemView.getTop();
210 | return true;
211 | }
212 | });
213 | }
214 |
215 | private void removeFromSelected(ChannelHolder holder) {
216 | int position = holder.getLayoutPosition();
217 | holder.delete.setVisibility(View.GONE);
218 | ChannelBean bean = mList.get(position);
219 | if ((isRecommend && bean.isRecommend()) || (!isRecommend && !bean.isRecommend())) {
220 | //移除的频道属于当前tab显示的频道,直接调用系统的移除动画
221 | itemMove(position, selectedSize + 1);
222 | notifyItemRangeChanged(selectedSize + 1, 1);
223 | if (onItemRangeChangeListener != null) {
224 | //如果设置了itemDecoration,必须调用recyclerView.invalidateItemDecorations(),否则间距会不对
225 | onItemRangeChangeListener.refreshItemDecoration();
226 | }
227 | } else {
228 | //不属于当前tab显示的频道
229 | removeAnimation(holder.itemView, isRecommend ? mRight : mLeft, mTabY, position);
230 | }
231 | selectedSize--;
232 | }
233 |
234 | private void removeAnimation(final View view, final float x, final float y, final int position) {
235 | final int fromX = view.getLeft();
236 | final int fromY = view.getTop();
237 | final ObjectAnimator animatorX = ObjectAnimator.ofFloat(view, "translationX", 0, x - fromX);
238 | final ObjectAnimator animatorY = ObjectAnimator.ofFloat(view, "translationY", 0, y - fromY);
239 | ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 1, 0);
240 | final AnimatorSet set = new AnimatorSet();
241 | set.playTogether(animatorX, animatorY, alpha);
242 | set.setDuration(350);
243 | set.start();
244 | set.addListener(new Animator.AnimatorListener() {
245 | @Override
246 | public void onAnimationStart(Animator animation) {
247 |
248 | }
249 |
250 | @Override
251 | public void onAnimationEnd(Animator animation) {
252 | if (isRecommend) {
253 | cityList.add(0, mList.get(position));
254 | } else {
255 | recommendList.add(0, mList.get(position));
256 | }
257 | mList.remove(position);
258 | notifyItemRemoved(position);
259 | onItemRangeChangeListener.refreshItemDecoration();
260 | //这里需要重置view的属性
261 | resetView(view, x - fromX, y - fromY);
262 | }
263 |
264 | @Override
265 | public void onAnimationCancel(Animator animation) {
266 |
267 | }
268 |
269 | @Override
270 | public void onAnimationRepeat(Animator animation) {
271 |
272 | }
273 | });
274 | }
275 |
276 | /**
277 | * 重置view的位置
278 | *
279 | * @param view
280 | * @param toX
281 | * @param toY
282 | */
283 | private void resetView(View view, float toX, float toY) {
284 | ObjectAnimator animatorX = ObjectAnimator.ofFloat(view, "translationX", -toX, 0);
285 | ObjectAnimator animatorY = ObjectAnimator.ofFloat(view, "translationY", -toY, 0);
286 | ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 0, 1);
287 | AnimatorSet set = new AnimatorSet();
288 | set.playTogether(animatorX, animatorY, alpha);
289 | set.setDuration(0);
290 | set.setStartDelay(5);
291 | set.start();
292 | }
293 |
294 | void itemMove(int fromPosition, int toPosition) {
295 | if (fromPosition < toPosition) {
296 | for (int i = fromPosition; i < toPosition; i++) {
297 | Collections.swap(mList, i, i + 1);
298 | }
299 | } else {
300 | for (int i = fromPosition; i > toPosition; i--) {
301 | Collections.swap(mList, i, i - 1);
302 | }
303 | }
304 | notifyItemMoved(fromPosition, toPosition);
305 | }
306 |
307 | @Override
308 | public int getItemCount() {
309 | return mList == null ? 0 : mList.size();
310 | }
311 |
312 | @Override
313 | public int getItemViewType(int position) {
314 | return mList.get(position).getLayoutId();
315 | }
316 |
317 | class ChannelHolder extends RecyclerView.ViewHolder {
318 | TextView name;
319 | ImageView delete;
320 |
321 | public ChannelHolder(View itemView) {
322 | super(itemView);
323 | name = itemView.findViewById(R.id.channel_name);
324 | delete = itemView.findViewById(R.id.channel_delete);
325 | }
326 | }
327 |
328 | class TabHolder extends RecyclerView.ViewHolder {
329 | TextView recommend;
330 | TextView city;
331 | View indicator;
332 |
333 | public TabHolder(View itemView) {
334 | super(itemView);
335 | recommend = itemView.findViewById(R.id.recommend_channel);
336 | city = itemView.findViewById(R.id.city_channel);
337 | indicator = itemView.findViewById(R.id.indicator);
338 | }
339 | }
340 |
341 | class MoreChannelHolder extends RecyclerView.ViewHolder {
342 |
343 | public MoreChannelHolder(View itemView) {
344 | super(itemView);
345 | }
346 | }
347 |
348 | class TitleHolder extends RecyclerView.ViewHolder {
349 |
350 | public TitleHolder(View itemView) {
351 | super(itemView);
352 | }
353 | }
354 |
355 | interface onItemRangeChangeListener {
356 | void refreshItemDecoration();
357 | }
358 | }
359 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/zhangzhihao/channelmanagedemo/ChannelBean.java:
--------------------------------------------------------------------------------
1 | package com.example.zhangzhihao.channelmanagedemo;
2 |
3 | /**
4 | * Created by zhangzhihao on 2018/2/26.
5 | */
6 |
7 | public class ChannelBean {
8 | private String name;
9 | private int spanSize;
10 | private int layoutId;
11 | private boolean isRecommend;
12 |
13 | public ChannelBean() {
14 | }
15 |
16 | public ChannelBean(String name, int spanSize, int layoutId, boolean isRecommend) {
17 | this.name = name;
18 | this.spanSize = spanSize;
19 | this.layoutId = layoutId;
20 | this.isRecommend = isRecommend;
21 | }
22 |
23 | public String getName() {
24 | return name;
25 | }
26 |
27 | public void setName(String name) {
28 | this.name = name;
29 | }
30 |
31 | public int getSpanSize() {
32 | return spanSize;
33 | }
34 |
35 | public void setSpanSize(int spanSize) {
36 | this.spanSize = spanSize;
37 | }
38 |
39 | public int getLayoutId() {
40 | return layoutId;
41 | }
42 |
43 | public void setLayoutId(int layoutId) {
44 | this.layoutId = layoutId;
45 | }
46 |
47 | public boolean isRecommend() {
48 | return isRecommend;
49 | }
50 |
51 | public void setRecommend(boolean recommend) {
52 | isRecommend = recommend;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/zhangzhihao/channelmanagedemo/GridSpacingItemDecoration.java:
--------------------------------------------------------------------------------
1 | package com.example.zhangzhihao.channelmanagedemo;
2 |
3 | import android.graphics.Rect;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.View;
6 |
7 | /**
8 | * Created by zhangzhihao on 2018/2/26.
9 | */
10 |
11 | public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
12 | private int spanCount;
13 | private int spacing;
14 | private boolean includeEdge;
15 | private int tabPosition;
16 |
17 | public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) {
18 | this.spanCount = spanCount;
19 | this.spacing = spacing;
20 | this.includeEdge = includeEdge;
21 | }
22 |
23 | @Override
24 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
25 | int position = parent.getChildAdapterPosition(view);
26 | if (position > 0) {
27 | int id = parent.getAdapter().getItemViewType(position);
28 | if (id == R.layout.adapter_tab) {
29 | tabPosition = position;
30 | }
31 | if (id == R.layout.adapter_channel) {
32 | if (position <= tabPosition) {
33 | position--;
34 | } else {
35 | position = position - (tabPosition + 1);
36 | }
37 | int column = position % spanCount; //列数
38 | if (includeEdge) {
39 | outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
40 | outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)
41 |
42 | if (position < spanCount) { // top edge
43 | outRect.top = 20;
44 | }
45 | outRect.bottom = 20; // item bottom
46 | } else {
47 | outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)
48 | outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f / spanCount) * spacing)
49 | if (position >= spanCount) {
50 | outRect.top = 20; // item top
51 | }
52 | }
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/zhangzhihao/channelmanagedemo/ItemDragCallback.java:
--------------------------------------------------------------------------------
1 | package com.example.zhangzhihao.channelmanagedemo;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Color;
5 | import android.graphics.DashPathEffect;
6 | import android.graphics.Paint;
7 | import android.graphics.PathEffect;
8 | import android.support.v7.widget.RecyclerView;
9 | import android.support.v7.widget.helper.ItemTouchHelper;
10 | import android.view.View;
11 |
12 | import static android.support.v7.widget.helper.ItemTouchHelper.ACTION_STATE_DRAG;
13 |
14 | /**
15 | * Created by zhangzhihao on 2018/2/27.
16 | */
17 |
18 | public class ItemDragCallback extends ItemTouchHelper.Callback {
19 | private static final String TAG = "ItemDragCallback";
20 | private ChannelAdapter mAdapter;
21 | private Paint mPaint; //虚线画笔
22 | private int mPadding; //虚线框框跟按钮间的距离
23 |
24 | public ItemDragCallback(ChannelAdapter mAdapter, int mPadding) {
25 | this.mAdapter = mAdapter;
26 | this.mPadding = mPadding;
27 | mPaint = new Paint();
28 | mPaint.setColor(Color.GRAY);
29 | mPaint.setAntiAlias(true);
30 | mPaint.setStrokeWidth(1);
31 | mPaint.setStyle(Paint.Style.STROKE);
32 | PathEffect pathEffect = new DashPathEffect(new float[]{5f, 5f}, 5f); //虚线
33 | mPaint.setPathEffect(pathEffect);
34 | }
35 |
36 | @Override
37 | public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
38 | //固定位置及tab下面的channel不能拖动
39 | if (viewHolder.getLayoutPosition() < mAdapter.getFixSize() + 1 || viewHolder.getLayoutPosition() > mAdapter.getSelectedSize()) {
40 | return 0;
41 | }
42 | int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
43 | int swipeFlags = 0;
44 | return makeMovementFlags(dragFlags, swipeFlags);
45 | }
46 |
47 | @Override
48 | public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
49 | int fromPosition = viewHolder.getAdapterPosition(); //拖动的position
50 | int toPosition = target.getAdapterPosition(); //释放的position
51 | //固定位置及tab下面的channel不能拖动
52 | if (toPosition < mAdapter.getFixSize() + 1 || toPosition > mAdapter.getSelectedSize())
53 | return false;
54 | mAdapter.itemMove(fromPosition, toPosition);
55 | return true;
56 | }
57 |
58 | @Override
59 | public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
60 |
61 | }
62 |
63 |
64 | @Override
65 | public void onChildDrawOver(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
66 | super.onChildDrawOver(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
67 | if (dX != 0 && dY != 0 || isCurrentlyActive) {
68 | //长按拖拽时底部绘制一个虚线矩形
69 | c.drawRect(viewHolder.itemView.getLeft(),viewHolder.itemView.getTop()-mPadding,viewHolder.itemView.getRight(),viewHolder.itemView.getBottom(),mPaint);
70 | }
71 | }
72 |
73 | @Override
74 | public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
75 | super.onSelectedChanged(viewHolder, actionState);
76 | if(actionState==ACTION_STATE_DRAG){
77 | //长按时调用
78 | ChannelAdapter.ChannelHolder holder= (ChannelAdapter.ChannelHolder) viewHolder;
79 | holder.name.setBackgroundColor(Color.parseColor("#FDFDFE"));
80 | holder.delete.setVisibility(View.GONE);
81 | holder.name.setElevation(5f);
82 | }
83 | }
84 |
85 | @Override
86 | public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
87 | super.clearView(recyclerView, viewHolder);
88 | ChannelAdapter.ChannelHolder holder= (ChannelAdapter.ChannelHolder) viewHolder;
89 | holder.name.setBackgroundColor(Color.parseColor("#f0f0f0"));
90 | holder.name.setElevation(0f);
91 | holder.delete.setVisibility(View.VISIBLE);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/zhangzhihao/channelmanagedemo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.zhangzhihao.channelmanagedemo;
2 |
3 | import android.content.Context;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.os.Bundle;
6 | import android.support.v7.widget.DefaultItemAnimator;
7 | import android.support.v7.widget.GridLayoutManager;
8 | import android.support.v7.widget.RecyclerView;
9 | import android.support.v7.widget.helper.ItemTouchHelper;
10 | import android.view.WindowManager;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | public class MainActivity extends AppCompatActivity implements ChannelAdapter.onItemRangeChangeListener {
16 |
17 | private RecyclerView mRecyclerView;
18 | private List mList;
19 | private ChannelAdapter mAdapter;
20 | private String select[] = {"要闻", "体育", "新时代", "汽车", "时尚", "国际", "电影", "财经", "游戏", "科技", "房产", "政务", "图片", "独家"};
21 | private String recommend[] = {"娱乐", "军事", "文化", "视频", "股票", "动漫", "理财", "电竞", "数码", "星座", "教育", "美容", "旅游"};
22 | private String city[] = {"重庆", "深圳", "汕头", "东莞", "佛山", "江门", "湛江", "惠州", "中山", "揭阳", "韶关", "茂名", "肇庆", "梅州", "汕尾", "河源", "云浮", "四川"};
23 |
24 | @Override
25 | protected void onCreate(Bundle savedInstanceState) {
26 | super.onCreate(savedInstanceState);
27 | setContentView(R.layout.activity_main);
28 | mRecyclerView = findViewById(R.id.recyclerView);
29 | mList = new ArrayList<>();
30 | GridLayoutManager manager = new GridLayoutManager(this, 4);
31 | manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
32 | @Override
33 | public int getSpanSize(int position) {
34 | return mList.get(position).getSpanSize();
35 | }
36 | });
37 | mRecyclerView.setLayoutManager(manager);
38 | DefaultItemAnimator animator = new DefaultItemAnimator();
39 | animator.setMoveDuration(300); //设置动画时间
40 | animator.setRemoveDuration(0);
41 | mRecyclerView.setItemAnimator(animator);
42 | ChannelBean title = new ChannelBean();
43 | title.setLayoutId(R.layout.adapter_title);
44 | title.setSpanSize(4);
45 | mList.add(title);
46 | for (String bean : select) {
47 | mList.add(new ChannelBean(bean, 1, R.layout.adapter_channel, true));
48 | }
49 | ChannelBean tabBean = new ChannelBean();
50 | tabBean.setLayoutId(R.layout.adapter_tab);
51 | tabBean.setSpanSize(4);
52 | mList.add(tabBean);
53 | List recommendList = new ArrayList<>();
54 | for (String bean : recommend) {
55 | recommendList.add(new ChannelBean(bean, 1, R.layout.adapter_channel, true));
56 | }
57 | List cityList = new ArrayList<>();
58 | for (String bean : city) {
59 | cityList.add(new ChannelBean(bean, 1, R.layout.adapter_channel, false));
60 | }
61 | ChannelBean moreBean = new ChannelBean();
62 | moreBean.setLayoutId(R.layout.adapter_more_channel);
63 | moreBean.setSpanSize(4);
64 | cityList.add(moreBean);
65 | mList.addAll(recommendList);
66 | mAdapter = new ChannelAdapter(this, mList, recommendList, cityList);
67 | mAdapter.setFixSize(1);
68 | mAdapter.setSelectedSize(select.length);
69 | mAdapter.setRecommend(true);
70 | mAdapter.setOnItemRangeChangeListener(this);
71 | mRecyclerView.setAdapter(mAdapter);
72 | WindowManager m = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
73 | int spacing = (m.getDefaultDisplay().getWidth() - dip2px(this, 70) * 4) / 5;
74 | mRecyclerView.addItemDecoration(new GridSpacingItemDecoration(4,spacing,true));
75 | ItemDragCallback callback=new ItemDragCallback(mAdapter,2);
76 | ItemTouchHelper helper=new ItemTouchHelper(callback);
77 | helper.attachToRecyclerView(mRecyclerView);
78 | }
79 |
80 | public static int dip2px(Context context, float dpValue) {
81 | float scale = context.getResources().getDisplayMetrics().density;
82 | return (int) (dpValue * scale + 0.5f);
83 | }
84 |
85 | @Override
86 | public void refreshItemDecoration() {
87 | mRecyclerView.invalidateItemDecorations();
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzh12138/ChannelManageDemo_Java/42eb374e29a8057e96b88e23b0ae04106d6369a5/app/src/main/res/drawable/icon_close.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
17 |
18 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/adapter_channel.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
17 |
18 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/adapter_more_channel.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/adapter_tab.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
24 |
25 |
32 |
33 |
34 |
39 |
40 |
46 |
47 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/adapter_title.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzh12138/ChannelManageDemo_Java/42eb374e29a8057e96b88e23b0ae04106d6369a5/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzh12138/ChannelManageDemo_Java/42eb374e29a8057e96b88e23b0ae04106d6369a5/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzh12138/ChannelManageDemo_Java/42eb374e29a8057e96b88e23b0ae04106d6369a5/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzh12138/ChannelManageDemo_Java/42eb374e29a8057e96b88e23b0ae04106d6369a5/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzh12138/ChannelManageDemo_Java/42eb374e29a8057e96b88e23b0ae04106d6369a5/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzh12138/ChannelManageDemo_Java/42eb374e29a8057e96b88e23b0ae04106d6369a5/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzh12138/ChannelManageDemo_Java/42eb374e29a8057e96b88e23b0ae04106d6369a5/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzh12138/ChannelManageDemo_Java/42eb374e29a8057e96b88e23b0ae04106d6369a5/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzh12138/ChannelManageDemo_Java/42eb374e29a8057e96b88e23b0ae04106d6369a5/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzh12138/ChannelManageDemo_Java/42eb374e29a8057e96b88e23b0ae04106d6369a5/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ChannelManageDemo
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/example/zhangzhihao/channelmanagedemo/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.example.zhangzhihao.channelmanagedemo;
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 | }
--------------------------------------------------------------------------------