bookList){
33 | mBookList = bookList;
34 | notifyDataSetChanged();
35 | }
36 |
37 | @Override
38 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
39 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_select_list,parent,false);
40 | return new ViewHolder(view);
41 | }
42 |
43 | @Override
44 | public void onBindViewHolder(ViewHolder holder, final int position) {
45 | final Book book = mBookList.get(position);
46 | holder.title.setText(book.name);
47 | holder.summary.setText(book.summary);
48 | Picasso.with(holder.itemView.getContext()).load(book.imageUrl).into(holder.cover);
49 | if(mItemClickListener != null){
50 | holder.itemView.setOnClickListener(new View.OnClickListener() {
51 | @Override
52 | public void onClick(View v) {
53 | if(mItemClickListener != null){
54 | mItemClickListener.onItemClick(v,position,book);
55 | }
56 | }
57 | });
58 | }
59 | }
60 |
61 | @Override
62 | public int getItemCount() {
63 | if(mBookList != null) return mBookList.size();
64 | return 0;
65 | }
66 |
67 | public interface ItemClickListener{
68 | void onItemClick(View parent,int position,Book book);
69 | }
70 |
71 |
72 | public static class ViewHolder extends RecyclerView.ViewHolder{
73 | private ImageView cover;
74 | private TextView title;
75 | private TextView summary;
76 | public ViewHolder(View itemView) {
77 | super(itemView);
78 | cover = (ImageView) itemView.findViewById(R.id.book_cover);
79 | title = (TextView) itemView.findViewById(R.id.text_title);
80 | summary = (TextView) itemView.findViewById(R.id.text_summary);
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/classify/src/main/java/com/anarchy/classify/adapter/BaseSubAdapter.java:
--------------------------------------------------------------------------------
1 | package com.anarchy.classify.adapter;
2 |
3 | import android.content.Context;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.VelocityTracker;
6 | import android.view.View;
7 |
8 |
9 | import com.anarchy.classify.ClassifyView;
10 |
11 | import java.util.List;
12 |
13 | /**
14 | * Version 1.0
15 | *
16 | * Date: 16/6/1 15:34
17 | * Author: zhendong.wu@shoufuyou.com
18 | *
19 | * Copyright © 2014-2016 Shanghai Xiaotu Network Technology Co., Ltd.
20 | */
21 | public abstract class BaseSubAdapter extends RecyclerView.Adapter implements SubRecyclerViewCallBack {
22 | private final static int VELOCITY = 5;
23 | @Override
24 | public boolean canDragOnLongPress(int position, View pressedView) {
25 | return true;
26 | }
27 |
28 | private int mSelectedPosition = -1;
29 | @Override
30 | public void setDragPosition(int position) {
31 | if(position >= getItemCount()||position<-1) return;
32 | if(position == -1 && mSelectedPosition != -1){
33 | // int oldPosition = mSelectedPosition;
34 | mSelectedPosition = position;
35 | notifyDataSetChanged();
36 | // notifyItemChanged(oldPosition);
37 | }else {
38 | mSelectedPosition = position;
39 | notifyItemChanged(mSelectedPosition);
40 | }
41 | }
42 |
43 | @Override
44 | public void onBindViewHolder(VH holder, int position, List payloads) {
45 | if(position == mSelectedPosition){
46 | holder.itemView.setVisibility(View.INVISIBLE);
47 | }else {
48 | holder.itemView.setVisibility(View.VISIBLE);
49 | }
50 | super.onBindViewHolder(holder, position, payloads);
51 | }
52 |
53 | @Override
54 | public boolean canDropOver(int selectedPosition, int targetPosition) {
55 | return true;
56 | }
57 |
58 | @Override
59 | public boolean canDragOut(int selectedPosition) {
60 | return true;
61 | }
62 |
63 | @Override
64 | public void moved(int selectedPosition, int targetPosition) {
65 |
66 | }
67 | @Override
68 | public int getCurrentState(View selectedView, View targetView, int x, int y,
69 | VelocityTracker velocityTracker, int selectedPosition,
70 | int targetPosition) {
71 | if(velocityTracker == null) return ClassifyView.STATE_NONE;
72 | int left = x;
73 | int top = y;
74 | int right = left + selectedView.getWidth();
75 | int bottom = top + selectedView.getHeight();
76 | if((Math.abs(left - targetView.getLeft())+Math.abs(right - targetView.getRight())+
77 | Math.abs(top - targetView.getTop())+ Math.abs(bottom - targetView.getBottom()))
78 | <(targetView.getWidth()+targetView.getHeight()
79 | )/2){
80 | velocityTracker.computeCurrentVelocity(100);
81 | float xVelocity = velocityTracker.getXVelocity();
82 | float yVelocity = velocityTracker.getYVelocity();
83 | float limit = getVelocity(targetView.getContext());
84 | if(xVelocity < limit && yVelocity < limit){
85 | return ClassifyView.STATE_MOVE;
86 | }
87 | }
88 | return ClassifyView.STATE_NONE;
89 | }
90 |
91 | @Override
92 | public float getVelocity(Context context) {
93 | float density = context.getResources().getDisplayMetrics().density;
94 | return density*VELOCITY + .5f;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #JitPack
2 | [](https://jitpack.io/#AlphaBoom/ClassifyView/0.2.0)
3 | # ClassifyView
4 | 类似Launcher效果的拖拽合并的RecyclerView
5 | #效果如下
6 | 
7 | #使用配置
8 | **Step one:**Add the JitPack repository to your build file
9 | ```
10 | allprojects {
11 | repositories {
12 | ...
13 | maven { url "https://jitpack.io" }
14 | }
15 | }
16 | ```
17 | **Step two:**Add the dependency
18 | ```
19 | dependencies {
20 | compile 'com.github.AlphaBoom:ClassifyView:0.2.0'
21 | }
22 | ```
23 | #支持的自定义的属性
24 | ClassifyView attr
25 |
26 | 属性 | 说明
27 | ------------- | -------------
28 | MainSpanCount | 主层级目录的列数
29 | SubSpanCount | 次级层级目录的列数
30 | ShadowColor | 展开次级目录时阴影的颜色
31 | AnimationDuration | 打开次级目录的动画及合并动画的时间
32 | SubRatio | 次级目录的高度占主层级的高度比例
33 |
34 | InsertAbleGridView(显示合并布局的View)
35 |
36 | 属性 | 说明
37 | ------- | -------
38 | RowCount | 行数(默认 2)
39 | ColumnCount | 列数(默认 2)
40 | RowGap | 横向每列中的间隙距离
41 | ColumnGap | 纵向每行之间的间隙距离
42 | OutLinePadding | 处于可以合并状态及非合并状态 外围框的距离
43 | OutlineWidth | 外边框的宽度
44 | OutlineColor | 外边框的颜色
45 | InnerPadding | 当内部有多个子View 时 与周围的边距
46 |
47 | #高级自定义
48 |
49 | ##继承ClassifyView 重写以下方法:
50 |
51 | 1. *RecyclerView getMain(Context context, AttributeSet parentAttrs)* 返回主层级使用的 RecyclerView。
52 | 2. *RecyclerView getSub(Context context, AttributeSet parentAttrs)*返回次级层级使用的RecyclerView
53 | 3. *View chooseTarget(View selected, List swapTargets, int curX, int curY)* 当拖拽的View 覆盖到子View时会通过该方法在候选View中选择一个View 为目标View 之后的交互操作都会作用于当前所选择的View 及 这个目标View @param selected 当前选择的View @param swapTargets 候选的目标View(候选的目标View 为当前选择的View 能够覆盖到所有View) @param curX 当前选中View的X轴坐标 @param curY 当前选中View的Y轴坐标
54 | 4. `Drawable getDragDrawable(View view)` 返回用于渲染当前拖动View的显示 @param 当前选中的View @return 返回Drawable 用于设置拖拽View的背景
55 |
56 | 设置数据方式有两种方式:
57 |
58 | 1. 使用 *ClassifyView.setAdapter(BaseMainAdapter mainAdapter, BaseSubAdapter subAdapter)*用于分别设置主层级及次级层级的适配器
59 | 2. 使用 *setAdapter(BaseSimpleAdapter baseSimpleAdapter)*设置一个混合了主层级及次级层级的适配器,如何自定义可以参考 [SimpleAdapter](https://github.com/AlphaBoom/ClassifyView/blob/master/classify/src/main/java/com/anarchy/classify/simple/SimpleAdapter.java)
60 |
61 |
62 | ##主层级提供的回调
63 | 在BaseAdapter中对于mergeStart等又增加了ViewHolder形式的回调 本质是一样的。
64 |
65 | 回调方法 | 说明 | 是否有默认实现在BaseSubAdapter中
66 | ------ | ----- | ----
67 | setDragPosition | 设置当前被拖拽的位置 | true,默认效果为隐藏被拖拽的位置
68 | boolean canDragOnLongPress|是否可以长按拖拽该View | false
69 | boolean canDropOVer| 是否可以在对应点放下|true,默认返回true
70 | boolean onMergeStart|第一次处于可合并状态|false
71 | void onMerged|合并结束|false
72 | ChangeInfo onPrepareMerge|当准备进行合并动画时回调,返回的ChangeInfo用于做当前拖拽的View到目标位置的动画|false
73 | void onStartMergeAnimation|开始合并动画的回调|false
74 | void onMergeCancel|当脱离合并状态的回调|false
75 | boolean onMove|当需要触发移动时的回调|false
76 | void moved|移动完成的回调|false
77 | boolean canMergeItem|能否进行合并操作|false
78 | int onLeaveSubRegion|当从次级目录拖动出item到主层级时回调,返回int 为添加到主层级adapter的位置|false
79 | float getVelocity|只对低于这个速度的才判断能否移动(需要配合getCurrentState)|true
80 | int getCurrentState|判断当前处于的状态,返回三个值 Classify.STATE_NONE 无状态,Classify.STATE_MERGE 处于合并状态,Classify.STATE_MOVE 处于移动状态| true
81 | void onItemClick|当item被点击时的回调|false
82 | List explodeItem|用于是否展开次级目录,返回一个List 用于初始化次级目录的数据,对于List size 小于2的不展开次级目录而调用onItemClick|false
83 |
84 | ##次级层级的回调
85 | 次级层级与主层级相似 没有合并的相关回调 单独有两个回调:
86 |
87 | 方法|说明
88 | ---|---
89 | void initData|用于初始化次级层级数据,初始化的数据来自于主层级的 explodeItem
90 | boolean canDropOver | 对于次层级的item 能否拖动到主层级
91 |
92 | #结语
93 | **当前项目效果展现 使用[SimpleAdapter](https://github.com/AlphaBoom/ClassifyView/blob/master/classify/src/main/java/com/anarchy/classify/simple/SimpleAdapter.java),InsertAbleGridView 是配合SimpleAdapter的控件所写,所以本质是一个有两个RecyclerView的自定义View,支持拖拽item并提供相应回调。其他效果自行书写**
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/app/src/main/java/com/anarchy/classifyview/sample/demonstrate/logic/NetManager.java:
--------------------------------------------------------------------------------
1 | package com.anarchy.classifyview.sample.demonstrate.logic;
2 |
3 | import android.net.Uri;
4 | import android.os.Handler;
5 | import android.os.Looper;
6 | import android.os.Message;
7 | import android.text.TextUtils;
8 |
9 | import java.io.BufferedReader;
10 | import java.io.InputStream;
11 | import java.io.InputStreamReader;
12 | import java.net.HttpURLConnection;
13 | import java.net.URL;
14 |
15 | /**
16 | *
17 | * Date: 16/6/12 11:37
18 | * Author: zhendong.wu@shoufuyou.com
19 | *
20 | */
21 | public class NetManager {
22 | private static final String BASE_URL = "http://www.tngou.net/api/book";
23 | private static final int SUCCESS = 1;
24 | private static final int FAILURE = 0;
25 |
26 | private String get(String path) throws Exception {
27 | String result = "";
28 | StringBuilder sb = new StringBuilder();
29 | URL url = new URL(BASE_URL + path);
30 | HttpURLConnection connection = (HttpURLConnection) url.openConnection();
31 | connection.setRequestMethod("GET");
32 | if (connection.getResponseCode() == 200) {
33 | InputStream in = connection.getInputStream();
34 | BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
35 | String strRead;
36 | while ((strRead = reader.readLine()) != null) {
37 | sb.append(strRead);
38 | }
39 | reader.close();
40 | result = sb.toString();
41 | }
42 | return result;
43 | }
44 |
45 |
46 | public String getClassify() throws Exception {
47 | return get("/classify");
48 | }
49 |
50 | public String getList(String page, String rows, String id) throws Exception {
51 | Uri uri = Uri.parse("/list");
52 | Uri.Builder builder = uri.buildUpon();
53 | if(!TextUtils.isEmpty(page)) {
54 | builder.appendQueryParameter("page", page);
55 | }
56 | if(!TextUtils.isEmpty(rows)) {
57 | builder.appendQueryParameter("rows", rows);
58 | }
59 | if(!TextUtils.isEmpty(id)) {
60 | builder.appendQueryParameter("id", id);
61 | }
62 | uri = builder.build();
63 | return get(uri.toString());
64 | }
65 |
66 | public String getDetail(String id) throws Exception {
67 | Uri uri = Uri.parse("/show");
68 | uri = uri.buildUpon().appendQueryParameter("id", id).build();
69 | return get(uri.toString());
70 | }
71 |
72 |
73 | public void getBookList(final BookListener bookListener) {
74 | final Handler handler = new Handler(Looper.getMainLooper()){
75 | @Override
76 | public void handleMessage(Message msg) {
77 | int state = msg.what;
78 | if(state == SUCCESS){
79 | bookListener.onSuccess((String) msg.obj);
80 | }else {
81 | bookListener.onFailure((Exception) msg.obj);
82 | }
83 | }
84 | };
85 | new Thread(new Runnable() {
86 | @Override
87 | public void run() {
88 | try {
89 | String result = getList(null,40+"",null);
90 | Message message = Message.obtain();
91 | message.what = SUCCESS;
92 | message.obj = result;
93 | handler.sendMessage(message);
94 | } catch (Exception e) {
95 | Message message = Message.obtain();
96 | message.what = FAILURE;
97 | message.obj = e;
98 | handler.sendMessage(message);
99 | }
100 | }
101 | }).start();
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/src/main/java/com/anarchy/classifyview/sample/demonstrate/DemonstrateFragment.java:
--------------------------------------------------------------------------------
1 | package com.anarchy.classifyview.sample.demonstrate;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.Nullable;
5 | import android.support.design.widget.FloatingActionButton;
6 | import android.support.v4.app.Fragment;
7 | import android.support.v7.app.AlertDialog;
8 | import android.support.v7.widget.GridLayoutManager;
9 | import android.support.v7.widget.RecyclerView;
10 | import android.util.Log;
11 | import android.view.LayoutInflater;
12 | import android.view.View;
13 | import android.view.ViewGroup;
14 | import android.widget.ProgressBar;
15 | import android.widget.Toast;
16 |
17 | import com.anarchy.classify.ClassifyView;
18 | import com.anarchy.classifyview.R;
19 | import com.anarchy.classifyview.sample.demonstrate.logic.Book;
20 | import com.anarchy.classifyview.sample.demonstrate.logic.BookListAdapter;
21 | import com.anarchy.classifyview.sample.demonstrate.logic.BookListener;
22 | import com.anarchy.classifyview.sample.demonstrate.logic.NetManager;
23 | import com.anarchy.classifyview.sample.demonstrate.logic.SelectBookListAdapter;
24 |
25 | import org.json.JSONArray;
26 | import org.json.JSONException;
27 | import org.json.JSONObject;
28 |
29 | import java.util.ArrayList;
30 | import java.util.List;
31 |
32 | /**
33 | *
34 | * Date: 16/6/12 10:09
35 | * Author: zhendong.wu@shoufuyou.com
36 | *
37 | */
38 | public class DemonstrateFragment extends Fragment{
39 | private NetManager mNetManager = new NetManager();
40 | private List> mBooks = new ArrayList<>();
41 | private BookListAdapter mAdapter;
42 | @Nullable
43 | @Override
44 | public View onCreateView(LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable Bundle savedInstanceState) {
45 | View view = inflater.inflate(R.layout.demonstrate_main,container,false);
46 | FloatingActionButton button = (FloatingActionButton) view.findViewById(R.id.add_button);
47 | ClassifyView classifyView = (ClassifyView) view.findViewById(R.id.classify_view);
48 | mAdapter = new BookListAdapter(mBooks);
49 | classifyView.setAdapter(mAdapter);
50 | button.setOnClickListener(new View.OnClickListener() {
51 | @Override
52 | public void onClick(View v) {
53 | final AlertDialog.Builder builder = new AlertDialog.Builder(v.getContext());
54 | View content = LayoutInflater.from(v.getContext()).inflate(R.layout.select_book,null);
55 | RecyclerView recyclerView = (RecyclerView) content.findViewById(R.id.select_list);
56 | final ProgressBar progressBar = (ProgressBar) content.findViewById(R.id.progress_bar);
57 | recyclerView.setLayoutManager(new GridLayoutManager(v.getContext(),2));
58 | final SelectBookListAdapter selectBookListAdapter = new SelectBookListAdapter();
59 | recyclerView.setAdapter(selectBookListAdapter);
60 | builder.setView(content);
61 | final AlertDialog dialog = builder.show();
62 | selectBookListAdapter.setItemClickListener(new SelectBookListAdapter.ItemClickListener() {
63 | @Override
64 | public void onItemClick(View parent, int position,Book book) {
65 | List books = new ArrayList<>();
66 | books.add(book);
67 | mBooks.add(books);
68 | mAdapter.notifyItemInsert(mBooks.size()-1);
69 | dialog.hide();
70 | }
71 | });
72 | progressBar.setVisibility(View.VISIBLE);
73 | mNetManager.getBookList(new BookListener() {
74 | @Override
75 | public void onSuccess(String result) {
76 | Log.d("wzd",result);
77 | progressBar.setVisibility(View.INVISIBLE);
78 | List books = new ArrayList<>();
79 | try {
80 | JSONObject jsonObject = new JSONObject(result);
81 | JSONArray jsonArray = jsonObject.optJSONArray("list");
82 | if(jsonArray != null){
83 | for(int i = 0;i
19 | * Date: 16/6/7 10:32
20 | * Author: zhendong.wu@shoufuyou.com
21 | *
22 | */
23 | class BagDrawable extends Drawable {
24 | private RectF mRectF;
25 | private Paint mPaint;
26 | private Paint mOutlinePaint;
27 | private boolean keepShow = false;
28 | private boolean inMerge = false;
29 | private int mOutLineWidth;
30 | private int mOutLineColor;
31 | private int mOutlinePadding;
32 | private int mSavedOutlinePadding;
33 | private float mRadius = 5;
34 | private int mAnimationDuration = 200;
35 | private ObjectAnimator mStarAnimator;
36 | private ObjectAnimator mCancelAnimator;
37 | // private int[] mColors = new int[]{0xFF808080,0xFF808080,0xFFDDDDDD,0xFFFFFFFF,0xFFDDDDDD,0xFF808080,0xFF808080,
38 | // 0xFFDDDDDD,0xFFFFFFFF,0xFFDDDDDD,0xFF808080,0xFF808080};
39 | // private float[] mPositions = new float[]{0f,0.11f,0.11f,0.125f,0.14f,0.14f,0.61f,0.61f,0.625f,0.64f,0.64f,1f};
40 | private int mCenterColor = 0xFFFFFFFF;
41 | private int mEdgeColor = 0xFF808080;
42 |
43 | public BagDrawable(int outlinePadding) {
44 | mRectF = new RectF();
45 | mPaint = new Paint();
46 | mPaint.setAntiAlias(true);
47 | mOutlinePaint = new Paint();
48 | mOutlinePaint.setAntiAlias(true);
49 | mOutlinePaint.setStyle(Paint.Style.STROKE);
50 | mOutlinePaint.setStrokeCap(Paint.Cap.ROUND);
51 | mOutlinePadding = outlinePadding;
52 | mSavedOutlinePadding = outlinePadding;
53 | mRadius = outlinePadding;
54 | }
55 |
56 | public void setKeepShow(boolean keepShow) {
57 | this.keepShow = keepShow;
58 | }
59 |
60 | public void setOutlineStyle(int color, int width) {
61 | mOutLineColor = color;
62 | mOutLineWidth = width;
63 | }
64 |
65 | @Override
66 | public void draw(Canvas canvas) {
67 | if (keepShow||inMerge) {
68 | canvas.save();
69 | canvas.clipRect(getBounds());
70 | mRectF.set(getBounds());
71 |
72 | mRectF.inset(mOutlinePadding, mOutlinePadding);
73 | if (mOutLineWidth > 0) {
74 | mOutlinePaint.setStrokeWidth(mOutLineWidth);
75 | mRectF.inset(mOutLineWidth, mOutLineWidth);
76 | mOutlinePaint.setColor(mOutLineColor);
77 | canvas.drawRoundRect(mRectF, mRadius, mRadius, mOutlinePaint);
78 | }
79 | // mPaint.setShader(new SweepGradient(mRectF.centerX(),mRectF.centerY(),mColors,mPositions));
80 | mPaint.setShader(new RadialGradient(mRectF.centerX(), mRectF.centerY(), mRectF.width(), mCenterColor, mEdgeColor, Shader.TileMode.CLAMP));
81 | canvas.drawRoundRect(mRectF, mRadius, mRadius, mPaint);
82 | canvas.restore();
83 | }
84 | }
85 |
86 | @Override
87 | public void setAlpha(int alpha) {
88 | mPaint.setAlpha(alpha);
89 | invalidateSelf();
90 | }
91 |
92 | @Override
93 | public void setColorFilter(ColorFilter colorFilter) {
94 | mPaint.setColorFilter(colorFilter);
95 | invalidateSelf();
96 | }
97 |
98 | @Override
99 | public int getOpacity() {
100 | return PixelFormat.TRANSLUCENT;
101 | }
102 |
103 | public void startMergeAnimation() {
104 | if(mCancelAnimator != null&&mCancelAnimator.isRunning()){
105 | mCancelAnimator.cancel();
106 | }
107 | if(mStarAnimator == null) {
108 | mStarAnimator = ObjectAnimator.ofInt(this, mOutlineProperty, 0);
109 | mStarAnimator.setDuration(mAnimationDuration);
110 | mStarAnimator.addListener(new AnimatorListenerAdapter() {
111 | @Override
112 | public void onAnimationStart(Animator animation) {
113 | inMerge = true;
114 | }
115 | });
116 | }else if(mStarAnimator.isRunning()){
117 | mStarAnimator.cancel();
118 | }
119 | mStarAnimator.start();
120 | }
121 | public void cancelMergeAnimation(){
122 | if(mStarAnimator != null && mStarAnimator.isRunning()){
123 | mStarAnimator.cancel();
124 | }
125 | if(mCancelAnimator == null) {
126 | mCancelAnimator = ObjectAnimator.ofInt(this, mOutlineProperty, mSavedOutlinePadding);
127 | mCancelAnimator.setDuration(mAnimationDuration);
128 | mCancelAnimator.addListener(new AnimatorListenerAdapter() {
129 | @Override
130 | public void onAnimationEnd(Animator animation) {
131 | inMerge = false;
132 | }
133 | });
134 | }else if(mCancelAnimator.isRunning()){
135 | mCancelAnimator.cancel();
136 | }
137 | mCancelAnimator.start();
138 | }
139 |
140 | private Property mOutlineProperty = new Property(Integer.class,"outline") {
141 | @Override
142 | public Integer get(BagDrawable object) {
143 | return object.mOutlinePadding;
144 | }
145 |
146 | @Override
147 | public void set(BagDrawable object, Integer value) {
148 | object.mOutlinePadding = value;
149 | invalidateSelf();
150 | }
151 | };
152 | }
153 |
--------------------------------------------------------------------------------
/classify/src/main/java/com/anarchy/classify/adapter/BaseMainAdapter.java:
--------------------------------------------------------------------------------
1 | package com.anarchy.classify.adapter;
2 |
3 | import android.content.Context;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.VelocityTracker;
6 | import android.view.View;
7 |
8 | import com.anarchy.classify.ChangeInfo;
9 | import com.anarchy.classify.ClassifyView;
10 |
11 | import java.util.List;
12 |
13 | /**
14 | *
15 | * Date: 16/6/1 15:33
16 | * Author: zhendong.wu@shoufuyou.com
17 | *
18 | */
19 | public abstract class BaseMainAdapter extends RecyclerView.Adapter implements MainRecyclerViewCallBack {
20 | private final static int VELOCITY = 5;
21 | private int mSelectedPosition = -1;
22 |
23 | @Override
24 | public void setDragPosition(int position) {
25 | if (position >= getItemCount() || position < -1) return;
26 | if (position == -1 && mSelectedPosition != -1) {
27 | int oldPosition = mSelectedPosition;
28 | mSelectedPosition = position;
29 | notifyItemChanged(oldPosition);
30 | } else {
31 | mSelectedPosition = position;
32 | notifyItemChanged(mSelectedPosition);
33 | }
34 | }
35 |
36 |
37 | @Override
38 | public boolean onMergeStart(RecyclerView parent, int selectedPosition, int targetPosition) {
39 | VH selectedViewHolder = (VH) parent.findViewHolderForAdapterPosition(selectedPosition);
40 | VH targetViewHolder = (VH) parent.findViewHolderForAdapterPosition(targetPosition);
41 | return onMergeStart(selectedViewHolder, targetViewHolder, selectedPosition, targetPosition);
42 | }
43 |
44 | @Override
45 | public void onMergeCancel(RecyclerView parent, int selectedPosition, int targetPosition) {
46 | VH selectedViewHolder = (VH) parent.findViewHolderForAdapterPosition(selectedPosition);
47 | VH targetViewHolder = (VH) parent.findViewHolderForAdapterPosition(targetPosition);
48 | onMergeCancel(selectedViewHolder, targetViewHolder, selectedPosition, targetPosition);
49 | }
50 |
51 | @Override
52 | public void onMerged(RecyclerView parent, int selectedPosition, int targetPosition) {
53 | VH selectedViewHolder = (VH) parent.findViewHolderForAdapterPosition(selectedPosition);
54 | VH targetViewHolder = (VH) parent.findViewHolderForAdapterPosition(targetPosition);
55 | onMerged(selectedViewHolder, targetViewHolder, selectedPosition, targetPosition);
56 | }
57 |
58 | @Override
59 | public ChangeInfo onPrepareMerge(RecyclerView parent, int selectedPosition, int targetPosition) {
60 | VH selectedViewHolder = (VH) parent.findViewHolderForAdapterPosition(selectedPosition);
61 | VH targetViewHolder = (VH) parent.findViewHolderForAdapterPosition(targetPosition);
62 | return onPrePareMerge(selectedViewHolder, targetViewHolder, selectedPosition, targetPosition);
63 | }
64 |
65 | @Override
66 | public void onStartMergeAnimation(RecyclerView parent, int selectedPosition, int targetPosition,int duration) {
67 | VH selectedViewHolder = (VH) parent.findViewHolderForAdapterPosition(selectedPosition);
68 | VH targetViewHolder = (VH) parent.findViewHolderForAdapterPosition(targetPosition);
69 | onStartMergeAnimation(selectedViewHolder, targetViewHolder, selectedPosition, targetPosition,duration);
70 | }
71 |
72 | public abstract boolean onMergeStart(VH selectedViewHolder, VH targetViewHolder, int selectedPosition, int targetPosition);
73 |
74 | public abstract void onMergeCancel(VH selectedViewHolder, VH targetViewHolder, int selectedPosition, int targetPosition);
75 |
76 | public abstract void onMerged(VH selectedViewHolder, VH targetViewHolder, int selectedPosition, int targetPosition);
77 |
78 | public abstract ChangeInfo onPrePareMerge(VH selectedViewHolder, VH targetViewHolder, int selectedPosition, int targetPosition);
79 |
80 | public abstract void onStartMergeAnimation(VH selectedViewHolder, VH targetViewHolder, int selectedPosition, int targetPosition,int duration);
81 |
82 | @Override
83 | public void onBindViewHolder(VH holder, int position, List payloads) {
84 | if (position == mSelectedPosition) {
85 | holder.itemView.setVisibility(View.INVISIBLE);
86 | } else {
87 | holder.itemView.setVisibility(View.VISIBLE);
88 | }
89 | super.onBindViewHolder(holder, position, payloads);
90 | }
91 |
92 | @Override
93 | public boolean canDropOVer(int selectedPosition, int targetPosition) {
94 | return true;
95 | }
96 |
97 |
98 | @Override
99 | public int getCurrentState(View selectedView, View targetView, int x, int y,
100 | VelocityTracker velocityTracker, int selectedPosition,
101 | int targetPosition) {
102 | if (velocityTracker == null) return ClassifyView.STATE_NONE;
103 | int left = x;
104 | int top = y;
105 | int right = left + selectedView.getWidth();
106 | int bottom = top + selectedView.getHeight();
107 | if (canMergeItem(selectedPosition, targetPosition)) {
108 | if ((Math.abs(left - targetView.getLeft()) + Math.abs(right - targetView.getRight()) +
109 | Math.abs(top - targetView.getTop()) + Math.abs(bottom - targetView.getBottom()))
110 | < (targetView.getWidth() + targetView.getHeight()
111 | ) / 3) {
112 | return ClassifyView.STATE_MERGE;
113 | }
114 | }
115 | if ((Math.abs(left - targetView.getLeft()) + Math.abs(right - targetView.getRight()) +
116 | Math.abs(top - targetView.getTop()) + Math.abs(bottom - targetView.getBottom()))
117 | < (targetView.getWidth() + targetView.getHeight()
118 | ) / 2) {
119 | velocityTracker.computeCurrentVelocity(100);
120 | float xVelocity = velocityTracker.getXVelocity();
121 | float yVelocity = velocityTracker.getYVelocity();
122 | float limit = getVelocity(targetView.getContext());
123 | if (xVelocity < limit && yVelocity < limit) {
124 | return ClassifyView.STATE_MOVE;
125 | }
126 | }
127 | return ClassifyView.STATE_NONE;
128 | }
129 |
130 | @Override
131 | public float getVelocity(Context context) {
132 | float density = context.getResources().getDisplayMetrics().density;
133 | return density * VELOCITY + .5f;
134 | }
135 |
136 | @Override
137 | public void moved(int selectedPosition, int targetPosition) {
138 |
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/classify/src/main/java/com/anarchy/classify/simple/widget/InsertAbleGridView.java:
--------------------------------------------------------------------------------
1 | package com.anarchy.classify.simple.widget;
2 |
3 | import android.animation.ObjectAnimator;
4 | import android.animation.PropertyValuesHolder;
5 | import android.animation.ValueAnimator;
6 | import android.content.Context;
7 | import android.content.res.TypedArray;
8 | import android.graphics.Paint;
9 | import android.graphics.Point;
10 | import android.support.v4.widget.ScrollerCompat;
11 | import android.util.AttributeSet;
12 | import android.view.View;
13 | import android.view.ViewGroup;
14 | import android.view.animation.DecelerateInterpolator;
15 | import android.widget.ImageView;
16 |
17 | import com.anarchy.classify.ChangeInfo;
18 | import com.anarchy.classify.R;
19 | import com.anarchy.classify.simple.SimpleAdapter;
20 | import com.anarchy.classify.util.L;
21 |
22 | import java.util.List;
23 |
24 | /**
25 | * 显示merge状态及以列表形式排列子view
26 | * 接收一个 view 的集合 或则 图片的集合
27 | */
28 | public class InsertAbleGridView extends ViewGroup implements CanMergeView{
29 | private int mRowCount;
30 | private int mColumnCount;
31 | private int mRowGap;
32 | private int mColumnGap;
33 | private int mOutLinePadding;
34 | private int mInnerPadding;
35 | private BagDrawable mBagDrawable;
36 | private SimpleAdapter mSimpleAdapter;
37 | private int parentIndex;
38 | private ChangeInfo mReturnInfo = new ChangeInfo();
39 | private ScrollerCompat mScroller;
40 | public InsertAbleGridView(Context context) {
41 | this(context,null);
42 | }
43 |
44 | public InsertAbleGridView(Context context, AttributeSet attrs) {
45 | this(context,attrs,0);
46 | }
47 |
48 | public InsertAbleGridView(Context context, AttributeSet attrs, int defStyleAttr) {
49 | super(context, attrs, defStyleAttr);
50 | init(context,attrs,defStyleAttr);
51 | }
52 |
53 |
54 | private void init(Context context,AttributeSet attrs,int defStyleAttr){
55 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.InsertAbleGridView,defStyleAttr,R.style.InsertAbleGridViewDefaultStyle);
56 | mRowCount = a.getInt(R.styleable.InsertAbleGridView_RowCount,2);
57 | mColumnCount = a.getInt(R.styleable.InsertAbleGridView_ColumnCount,2);
58 | mRowGap = a.getDimensionPixelSize(R.styleable.InsertAbleGridView_RowGap,10);
59 | mColumnGap = a.getDimensionPixelSize(R.styleable.InsertAbleGridView_ColumnGap,10);
60 | mOutLinePadding = a.getDimensionPixelSize(R.styleable.InsertAbleGridView_OutlinePadding,10);
61 | mInnerPadding = a.getDimensionPixelOffset(R.styleable.InsertAbleGridView_InnerPadding,10);
62 | mBagDrawable = new BagDrawable(mOutLinePadding);
63 | mBagDrawable.setOutlineStyle(a.getColor(R.styleable.InsertAbleGridView_OutlineColor,0),a.getDimensionPixelSize(R.styleable.InsertAbleGridView_OutlineWidth,3));
64 | setBackgroundDrawable(mBagDrawable);
65 | a.recycle();
66 | mScroller = ScrollerCompat.create(context);
67 | }
68 |
69 | @Override
70 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
71 | int childCount = getChildCount();
72 | int width = Math.max(r - l - getPaddingLeft()-getPaddingRight()-2*mOutLinePadding,0);
73 | int height = Math.max(b - t - getPaddingBottom()-getPaddingTop()-2*mOutLinePadding,0);
74 | int itemWidth = getItemWidth(width);
75 | int itemHeight = getItemHeight(height);
76 | int itemTotal = mRowCount*mColumnCount;
77 | if(childCount>0){
78 | if(childCount == 1){
79 | mBagDrawable.setKeepShow(false);
80 | getChildAt(0).layout(getPaddingLeft()+mOutLinePadding,getPaddingTop()+mOutLinePadding,getPaddingLeft()+mOutLinePadding+width,getPaddingTop()+mOutLinePadding+height);
81 | }else {
82 | mBagDrawable.setKeepShow(true);
83 | int row,col;
84 | for(int i=0;i=childCount-itemTotal && childCount%itemTotal != 0){
95 | int newI = i%itemTotal;
96 | row = newI/mColumnCount;
97 | col = newI%mColumnCount;
98 | int left = getPaddingLeft()+mInnerPadding+mOutLinePadding+col*(itemWidth+mColumnGap);
99 | int right = left + itemWidth;
100 | int top = getHeight()+getPaddingTop()+mInnerPadding+mOutLinePadding+row*(itemHeight + mRowGap);
101 | int bottom = top+itemHeight;
102 | child.layout(left,top,right,bottom);
103 | }
104 |
105 | }
106 | }
107 | }
108 | }
109 | private ValueAnimator createConvertAnimator(final View view){
110 | int width = getWidth() - getPaddingLeft()-getPaddingRight()-2*mOutLinePadding;
111 | int height = getHeight() - getPaddingBottom() - getPaddingTop()-2*mOutLinePadding;
112 | PropertyValuesHolder left = PropertyValuesHolder.ofInt("left",view.getLeft(),getPaddingLeft()+mInnerPadding+mOutLinePadding);
113 | PropertyValuesHolder right = PropertyValuesHolder.ofInt("right",view.getRight(),getPaddingLeft()+mInnerPadding+mOutLinePadding+getItemWidth(width));
114 | PropertyValuesHolder top = PropertyValuesHolder.ofInt("top",view.getTop(),getPaddingTop()+mInnerPadding+mOutLinePadding);
115 | PropertyValuesHolder bottom = PropertyValuesHolder.ofInt("bottom",view.getBottom(),getPaddingTop()+mInnerPadding+mOutLinePadding+getItemHeight(height));
116 | ValueAnimator animator = ObjectAnimator.ofPropertyValuesHolder(left,right,top,bottom);
117 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
118 | @Override
119 | public void onAnimationUpdate(ValueAnimator animation) {
120 | int left = (int) animation.getAnimatedValue("left");
121 | int right = (int) animation.getAnimatedValue("right");
122 | int top = (int) animation.getAnimatedValue("top");
123 | int bottom = (int) animation.getAnimatedValue("bottom");
124 | view.layout(left,top,right,bottom);
125 | }
126 | });
127 | animator.setInterpolator(new DecelerateInterpolator());
128 | return animator;
129 | }
130 |
131 | private int getItemWidth(int width){
132 | return (width-2*mInnerPadding - (mColumnCount-1)*mRowGap)/mColumnCount;
133 | }
134 | private int getItemHeight(int height){
135 | return (height-2*mInnerPadding -(mRowCount-1)*mColumnGap)/mRowCount;
136 | }
137 | @Override
138 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
139 | int widthSize = MeasureSpec.getSize(widthMeasureSpec);
140 | int widthMode = MeasureSpec.getMode(widthMeasureSpec);
141 | int heightSize = MeasureSpec.getSize(heightMeasureSpec);
142 | int heightMode = MeasureSpec.getMode(heightMeasureSpec);
143 | if (widthMode != MeasureSpec.EXACTLY)
144 | widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
145 | if (heightMode != MeasureSpec.EXACTLY)
146 | heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
147 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
148 | }
149 |
150 | @Override
151 | public void onMergeStart() {
152 | mBagDrawable.startMergeAnimation();
153 | if(getChildCount() >= mRowCount*mColumnCount){
154 | mScroller.startScroll(0,0,0,getHeight(),500);
155 | invalidate();
156 | }
157 | }
158 |
159 | @Override
160 | public void computeScroll() {
161 | if(mScroller.computeScrollOffset()){
162 | scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
163 | invalidate();
164 | }
165 | }
166 |
167 | @Override
168 | public void onMergeCancel() {
169 | mBagDrawable.cancelMergeAnimation();
170 | if(getChildCount() >= mRowCount*mColumnCount){
171 | mScroller.startScroll(0,getHeight(),0,-getHeight(),500);
172 | }
173 | }
174 |
175 | @Override
176 | public void onMerged() {
177 | mBagDrawable.setKeepShow(true);
178 | mBagDrawable.cancelMergeAnimation();
179 | if(getChildCount() >= mRowCount*mColumnCount){
180 | mScroller.startScroll(0,getHeight(),0,-getHeight(),500);
181 | }
182 | }
183 |
184 | @Override
185 | public void startMergeAnimation(int duration) {
186 | if(getChildCount() == 1){
187 | View child = getChildAt(0);
188 | createConvertAnimator(child).setDuration(duration).start();
189 | }
190 | }
191 |
192 | @Override
193 | public ChangeInfo prepareMerge() {
194 | int futureCount = getChildCount() + 1;
195 | if(futureCount > 1){
196 | if(futureCount > mRowCount*mColumnCount) futureCount = futureCount%(mRowCount*mColumnCount);
197 | if(futureCount == 0) futureCount = mRowCount*mColumnCount;
198 | futureCount--;
199 | int row = futureCount/mColumnCount;
200 | int col = futureCount%mColumnCount;
201 | int width = getWidth() - getPaddingLeft()-getPaddingRight()-2*mOutLinePadding;
202 | int height = getHeight() - getPaddingTop() - getPaddingBottom()-2*mOutLinePadding;
203 | int itemWidth = getItemWidth(width);
204 | int itemHeight = getItemHeight(height);
205 | int left = getPaddingLeft()+mInnerPadding+mOutLinePadding+col*(itemWidth+mColumnGap);
206 | int top = getPaddingTop()+mInnerPadding+mOutLinePadding+row*(itemHeight + mRowGap);
207 | mReturnInfo.left = left;
208 | mReturnInfo.top = top;
209 | mReturnInfo.itemWidth = itemWidth;
210 | mReturnInfo.itemHeight = itemHeight;
211 | return mReturnInfo;
212 | }
213 | return null;
214 | }
215 |
216 | @Override
217 | public void setAdapter(SimpleAdapter simpleAdapter) {
218 | mSimpleAdapter = simpleAdapter;
219 | }
220 |
221 | @Override
222 | public void initMain(int parentIndex, List list) {
223 | removeAllViewsInLayout();
224 | this.parentIndex = parentIndex;
225 | for(int i =0;i
21 | * Date: 16/6/7 11:55
22 | * Author: zhendong.wu@shoufuyou.com
23 | *
24 | */
25 | public abstract class SimpleAdapter implements BaseSimpleAdapter {
26 | protected List> mData;
27 | private SimpleMainAdapter mSimpleMainAdapter;
28 | private SimpleSubAdapter mSimpleSubAdapter;
29 |
30 | public SimpleAdapter(List> data) {
31 | mData = data;
32 | mSimpleMainAdapter = new SimpleMainAdapter(this, mData);
33 | mSimpleSubAdapter = new SimpleSubAdapter(this);
34 | }
35 |
36 | @Override
37 | public BaseMainAdapter getMainAdapter() {
38 | return mSimpleMainAdapter;
39 | }
40 |
41 | @Override
42 | public BaseSubAdapter getSubAdapter() {
43 | return mSimpleSubAdapter;
44 | }
45 |
46 | protected VH onCreateViewHolder(ViewGroup parent, int viewType) {
47 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.simple_item, parent, false);
48 | return (VH) new ViewHolder(view);
49 | }
50 |
51 | protected void onBindMainViewHolder(VH holder, int position) {
52 | }
53 |
54 | protected void onBindSubViewHolder(VH holder, int mainPosition,int subPosition) {
55 | }
56 |
57 |
58 | public void notifyItemInsert(int position){
59 | mSimpleMainAdapter.notifyItemInserted(position);
60 | }
61 |
62 | public void notifyItemChanged(int position){
63 | mSimpleMainAdapter.notifyItemChanged(position);
64 | }
65 |
66 | public void notifyItemRangeChanged(int position,int count){
67 | mSimpleMainAdapter.notifyItemRangeChanged(position,count);
68 | }
69 |
70 | public void notifyItemRangeInsert(int position,int count){
71 | mSimpleMainAdapter.notifyItemRangeInserted(position,count);
72 | }
73 |
74 |
75 | public void notifyDataSetChanged(){
76 | mSimpleMainAdapter.notifyDataSetChanged();
77 | }
78 | /**
79 | * @param parentIndex
80 | * @param index if -1 in main region
81 | */
82 | protected void onItemClick(View view, int parentIndex, int index) {
83 | }
84 |
85 | /**
86 | * 显示一个item的布局
87 | *
88 | * @return
89 | */
90 | public abstract View getView(ViewGroup parent, int mainPosition, int subPosition);
91 |
92 | class SimpleMainAdapter extends BaseMainAdapter {
93 | private List> mData;
94 | private SimpleAdapter mSimpleAdapter;
95 |
96 | public SimpleMainAdapter(SimpleAdapter simpleAdapter, List> data) {
97 | mData = data;
98 | mSimpleAdapter = simpleAdapter;
99 | }
100 |
101 | @Override
102 | public VH onCreateViewHolder(ViewGroup parent, int viewType) {
103 | VH vh = mSimpleAdapter.onCreateViewHolder(parent, viewType);
104 | CanMergeView canMergeView = vh.getCanMergeView();
105 | if (canMergeView != null) {
106 | canMergeView.setAdapter(mSimpleAdapter);
107 | }
108 | return vh;
109 | }
110 |
111 | @Override
112 | public void onBindViewHolder(VH holder, int position) {
113 | CanMergeView canMergeView = holder.getCanMergeView();
114 | if (canMergeView != null) {
115 | canMergeView.initMain(position, mData.get(position));
116 | }
117 | mSimpleAdapter.onBindMainViewHolder(holder, position);
118 | }
119 |
120 | @Override
121 | public int getItemCount() {
122 | return mData.size();
123 | }
124 |
125 | @Override
126 | public boolean canDragOnLongPress(int position, View pressedView) {
127 | return true;
128 | }
129 |
130 |
131 |
132 | @Override
133 | public boolean onMergeStart(VH selectedViewHolder, VH targetViewHolder,
134 | int selectedPosition, int targetPosition) {
135 | L.d("on mergeStart:(%1$s,%2$s)",selectedPosition,targetPosition);
136 | CanMergeView canMergeView = targetViewHolder.getCanMergeView();
137 | if (canMergeView != null) {
138 | canMergeView.onMergeStart();
139 | }
140 | return true;
141 | }
142 |
143 | @Override
144 | public void onMergeCancel(VH selectedViewHolder, VH targetViewHolder,
145 | int selectedPosition, int targetPosition) {
146 | L.d("on mergeCancel:(%1$s,%2$s)",selectedPosition,targetPosition);
147 | CanMergeView canMergeView = targetViewHolder.getCanMergeView();
148 | if (canMergeView != null) {
149 | canMergeView.onMergeCancel();
150 | }
151 | }
152 |
153 | @Override
154 | public void onMerged(VH selectedViewHolder, VH targetViewHolder,
155 | int selectedPosition, int targetPosition) {
156 | L.d("on Merged:(%1$s,%2$s)",selectedPosition,targetPosition);
157 | CanMergeView canMergeView = targetViewHolder.getCanMergeView();
158 | if (canMergeView != null) {
159 | canMergeView.onMerged();
160 | }
161 | mData.get(targetPosition).add(mData.get(selectedPosition).get(0));
162 | mData.remove(selectedPosition);
163 | notifyItemRemoved(selectedPosition);
164 | if(selectedPosition < targetPosition) {
165 | notifyItemChanged(targetPosition-1);
166 | }else {
167 | notifyItemChanged(targetPosition);
168 | }
169 | }
170 |
171 | @Override
172 | public ChangeInfo onPrePareMerge(VH selectedViewHolder, VH targetViewHolder, int selectedPosition, int targetPosition) {
173 | if(targetViewHolder == null || selectedViewHolder == null) return null;
174 | CanMergeView canMergeView = targetViewHolder.getCanMergeView();
175 | if (canMergeView != null) {
176 | ChangeInfo info = canMergeView.prepareMerge();
177 | info.paddingLeft = selectedViewHolder.getPaddingLeft();
178 | info.paddingRight = selectedViewHolder.getPaddingRight();
179 | info.paddingTop = selectedViewHolder.getPaddingTop();
180 | info.paddingBottom = selectedViewHolder.getPaddingBottom();
181 | info.outlinePadding = canMergeView.getOutlinePadding();
182 | return info;
183 | }
184 | return null;
185 | }
186 |
187 | @Override
188 | public void onStartMergeAnimation(VH selectedViewHolder, VH targetViewHolder, int selectedPosition, int targetPosition,int duration) {
189 | CanMergeView canMergeView = targetViewHolder.getCanMergeView();
190 | if (canMergeView != null) {
191 | canMergeView.startMergeAnimation(duration);
192 | }
193 | }
194 |
195 |
196 | @Override
197 | public boolean onMove(int selectedPosition, int targetPosition) {
198 | notifyItemMoved(selectedPosition, targetPosition);
199 | List list = mData.remove(selectedPosition);
200 | mData.add(targetPosition, list);
201 | return true;
202 | }
203 |
204 | @Override
205 | public boolean canMergeItem(int selectedPosition, int targetPosition) {
206 | List currentSelected = mData.get(selectedPosition);
207 | return currentSelected.size() < 2;
208 | }
209 |
210 |
211 | @Override
212 | public int onLeaveSubRegion(int selectedPosition, SubAdapterReference subAdapterReference) {
213 | SimpleSubAdapter simpleSubAdapter = subAdapterReference.getAdapter();
214 | T t = simpleSubAdapter.getData().remove(selectedPosition);
215 | List list = new ArrayList<>();
216 | list.add(t);
217 | mData.add(list);
218 | int parentIndex = simpleSubAdapter.getParentIndex();
219 | if (parentIndex != -1) notifyItemChanged(parentIndex);
220 | return mData.size() - 1;
221 | }
222 |
223 | @Override
224 | public void onItemClick(int position, View pressedView) {
225 | mSimpleAdapter.onItemClick(pressedView, position, -1);
226 | }
227 |
228 | @Override
229 | public List explodeItem(int position, View pressedView) {
230 | if (position < mData.size())
231 | return mData.get(position);
232 | return null;
233 | }
234 | }
235 |
236 | class SimpleSubAdapter extends BaseSubAdapter {
237 | private List mData;
238 | private int parentIndex = -1;
239 | private SimpleAdapter mSimpleAdapter;
240 |
241 | public SimpleSubAdapter(SimpleAdapter simpleAdapter) {
242 | mSimpleAdapter = simpleAdapter;
243 | }
244 |
245 | @Override
246 | public VH onCreateViewHolder(ViewGroup parent, int viewType) {
247 | VH vh = mSimpleAdapter.onCreateViewHolder(parent, viewType);
248 | CanMergeView canMergeView = vh.getCanMergeView();
249 | if (canMergeView != null) {
250 | canMergeView.setAdapter(mSimpleAdapter);
251 | }
252 | return vh;
253 | }
254 |
255 | public int getParentIndex() {
256 | return parentIndex;
257 | }
258 |
259 | @Override
260 | public void onBindViewHolder(VH holder, int position) {
261 | CanMergeView canMergeView = holder.getCanMergeView();
262 | if (canMergeView != null) {
263 | canMergeView.initSub(parentIndex,position);
264 | }
265 | mSimpleAdapter.onBindSubViewHolder(holder,parentIndex,position);
266 | }
267 |
268 | @Override
269 | public int getItemCount() {
270 | if (mData == null) return 0;
271 | return mData.size();
272 | }
273 |
274 | @Override
275 | public void onItemClick(int position, View pressedView) {
276 | mSimpleAdapter.onItemClick(pressedView, parentIndex, position);
277 | }
278 |
279 | @Override
280 | public void initData(int parentIndex, List data) {
281 | mData = data;
282 | this.parentIndex = parentIndex;
283 | notifyDataSetChanged();
284 | }
285 |
286 | @Override
287 | public boolean onMove(int selectedPosition, int targetPosition) {
288 | notifyItemMoved(selectedPosition, targetPosition);
289 | T t = mData.remove(selectedPosition);
290 | mData.add(targetPosition, t);
291 | if(parentIndex != -1) {
292 | mSimpleMainAdapter.notifyItemChanged(parentIndex);
293 | }
294 | return true;
295 | }
296 |
297 | public List getData() {
298 | return mData;
299 | }
300 |
301 | }
302 |
303 | public static class ViewHolder extends RecyclerView.ViewHolder {
304 | protected CanMergeView mCanMergeView;
305 | private int paddingLeft;
306 | private int paddingRight;
307 | private int paddingTop;
308 | private int paddingBottom;
309 |
310 | public ViewHolder(View itemView) {
311 | super(itemView);
312 | if (itemView instanceof CanMergeView) {
313 | mCanMergeView = (CanMergeView) itemView;
314 | } else if (itemView instanceof ViewGroup) {
315 | ViewGroup group = (ViewGroup) itemView;
316 | paddingLeft = group.getPaddingLeft();
317 | paddingRight = group.getPaddingRight();
318 | paddingTop = group.getPaddingTop();
319 | paddingBottom = group.getPaddingBottom();
320 | //只遍历一层 寻找第一个符合条件的view
321 | for (int i = 0; i < group.getChildCount(); i++) {
322 | View child = group.getChildAt(i);
323 | if (child instanceof CanMergeView) {
324 | mCanMergeView = (CanMergeView) child;
325 | break;
326 | }
327 | }
328 | }
329 | }
330 |
331 | public CanMergeView getCanMergeView() {
332 | return mCanMergeView;
333 | }
334 |
335 | public int getPaddingLeft() {
336 | return paddingLeft;
337 | }
338 |
339 | public int getPaddingRight() {
340 | return paddingRight;
341 | }
342 |
343 | public int getPaddingTop() {
344 | return paddingTop;
345 | }
346 |
347 | public int getPaddingBottom() {
348 | return paddingBottom;
349 | }
350 | }
351 | }
352 |
--------------------------------------------------------------------------------
/classify/src/main/java/com/anarchy/classify/ClassifyItemAnimator.java:
--------------------------------------------------------------------------------
1 | package com.anarchy.classify;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.v4.animation.AnimatorCompatHelper;
5 | import android.support.v4.view.ViewCompat;
6 | import android.support.v4.view.ViewPropertyAnimatorCompat;
7 | import android.support.v4.view.ViewPropertyAnimatorListener;
8 | import android.support.v7.widget.RecyclerView;
9 | import android.support.v7.widget.SimpleItemAnimator;
10 | import android.view.View;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | /**
16 | * Version 1.0
17 | *
18 | * Date: 16/6/8 14:12
19 | * Author: zhendong.wu@shoufuyou.com
20 | *
21 | * Copyright © 2014-2016 Shanghai Xiaotu Network Technology Co., Ltd.
22 | */
23 | public class ClassifyItemAnimator extends SimpleItemAnimator {
24 | private static final boolean DEBUG = false;
25 |
26 | private ArrayList mPendingRemovals = new ArrayList<>();
27 | private ArrayList mPendingAdditions = new ArrayList<>();
28 | private ArrayList mPendingMoves = new ArrayList<>();
29 | private ArrayList mPendingChanges = new ArrayList<>();
30 |
31 | private ArrayList> mAdditionsList = new ArrayList<>();
32 | private ArrayList> mMovesList = new ArrayList<>();
33 | private ArrayList> mChangesList = new ArrayList<>();
34 |
35 | private ArrayList mAddAnimations = new ArrayList<>();
36 | private ArrayList mMoveAnimations = new ArrayList<>();
37 | private ArrayList mRemoveAnimations = new ArrayList<>();
38 | private ArrayList mChangeAnimations = new ArrayList<>();
39 |
40 | private static class MoveInfo {
41 | public RecyclerView.ViewHolder holder;
42 | public int fromX, fromY, toX, toY;
43 |
44 | private MoveInfo(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
45 | this.holder = holder;
46 | this.fromX = fromX;
47 | this.fromY = fromY;
48 | this.toX = toX;
49 | this.toY = toY;
50 | }
51 | }
52 |
53 | private static class ChangeInfo {
54 | public RecyclerView.ViewHolder oldHolder, newHolder;
55 | public int fromX, fromY, toX, toY;
56 | private ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder) {
57 | this.oldHolder = oldHolder;
58 | this.newHolder = newHolder;
59 | }
60 |
61 | private ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder,
62 | int fromX, int fromY, int toX, int toY) {
63 | this(oldHolder, newHolder);
64 | this.fromX = fromX;
65 | this.fromY = fromY;
66 | this.toX = toX;
67 | this.toY = toY;
68 | }
69 |
70 | @Override
71 | public String toString() {
72 | return "ChangeInfo{" +
73 | "oldHolder=" + oldHolder +
74 | ", newHolder=" + newHolder +
75 | ", fromX=" + fromX +
76 | ", fromY=" + fromY +
77 | ", toX=" + toX +
78 | ", toY=" + toY +
79 | '}';
80 | }
81 | }
82 |
83 | @Override
84 | public void runPendingAnimations() {
85 | boolean removalsPending = !mPendingRemovals.isEmpty();
86 | boolean movesPending = !mPendingMoves.isEmpty();
87 | boolean changesPending = !mPendingChanges.isEmpty();
88 | boolean additionsPending = !mPendingAdditions.isEmpty();
89 | if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
90 | // nothing to animate
91 | return;
92 | }
93 | // First, remove stuff
94 | for (RecyclerView.ViewHolder holder : mPendingRemovals) {
95 | animateRemoveImpl(holder);
96 | }
97 | mPendingRemovals.clear();
98 | // Next, move stuff
99 | if (movesPending) {
100 | final ArrayList moves = new ArrayList<>();
101 | moves.addAll(mPendingMoves);
102 | mMovesList.add(moves);
103 | mPendingMoves.clear();
104 | Runnable mover = new Runnable() {
105 | @Override
106 | public void run() {
107 | for (MoveInfo moveInfo : moves) {
108 | animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
109 | moveInfo.toX, moveInfo.toY);
110 | }
111 | moves.clear();
112 | mMovesList.remove(moves);
113 | }
114 | };
115 | if (removalsPending) {
116 | View view = moves.get(0).holder.itemView;
117 | ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
118 | } else {
119 | mover.run();
120 | }
121 | }
122 | // Next, change stuff, to run in parallel with move animations
123 | if (changesPending) {
124 | final ArrayList changes = new ArrayList<>();
125 | changes.addAll(mPendingChanges);
126 | mChangesList.add(changes);
127 | mPendingChanges.clear();
128 | Runnable changer = new Runnable() {
129 | @Override
130 | public void run() {
131 | for (ChangeInfo change : changes) {
132 | animateChangeImpl(change);
133 | }
134 | changes.clear();
135 | mChangesList.remove(changes);
136 | }
137 | };
138 | if (removalsPending) {
139 | RecyclerView.ViewHolder holder = changes.get(0).oldHolder;
140 | ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
141 | } else {
142 | changer.run();
143 | }
144 | }
145 | // Next, add stuff
146 | if (additionsPending) {
147 | final ArrayList additions = new ArrayList<>();
148 | additions.addAll(mPendingAdditions);
149 | mAdditionsList.add(additions);
150 | mPendingAdditions.clear();
151 | Runnable adder = new Runnable() {
152 | public void run() {
153 | for (RecyclerView.ViewHolder holder : additions) {
154 | animateAddImpl(holder);
155 | }
156 | additions.clear();
157 | mAdditionsList.remove(additions);
158 | }
159 | };
160 | if (removalsPending || movesPending || changesPending) {
161 | long removeDuration = removalsPending ? getRemoveDuration() : 0;
162 | long moveDuration = movesPending ? getMoveDuration() : 0;
163 | long changeDuration = changesPending ? getChangeDuration() : 0;
164 | long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
165 | View view = additions.get(0).itemView;
166 | ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
167 | } else {
168 | adder.run();
169 | }
170 | }
171 | }
172 |
173 | @Override
174 | public boolean animateRemove(final RecyclerView.ViewHolder holder) {
175 | resetAnimation(holder);
176 | mPendingRemovals.add(holder);
177 | return true;
178 | }
179 |
180 | private void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
181 | final View view = holder.itemView;
182 | final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
183 | mRemoveAnimations.add(holder);
184 | animation.setDuration(getRemoveDuration())
185 | .alpha(0).setListener(new VpaListenerAdapter() {
186 | @Override
187 | public void onAnimationStart(View view) {
188 | dispatchRemoveStarting(holder);
189 | }
190 |
191 | @Override
192 | public void onAnimationEnd(View view) {
193 | animation.setListener(null);
194 | ViewCompat.setAlpha(view, 1);
195 | dispatchRemoveFinished(holder);
196 | mRemoveAnimations.remove(holder);
197 | dispatchFinishedWhenDone();
198 | }
199 | }).start();
200 | }
201 |
202 | @Override
203 | public boolean animateAdd(final RecyclerView.ViewHolder holder) {
204 | resetAnimation(holder);
205 | ViewCompat.setAlpha(holder.itemView, 0);
206 | mPendingAdditions.add(holder);
207 | return true;
208 | }
209 |
210 | private void animateAddImpl(final RecyclerView.ViewHolder holder) {
211 | final View view = holder.itemView;
212 | final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
213 | mAddAnimations.add(holder);
214 | animation.alpha(1).setDuration(getAddDuration()).
215 | setListener(new VpaListenerAdapter() {
216 | @Override
217 | public void onAnimationStart(View view) {
218 | dispatchAddStarting(holder);
219 | }
220 | @Override
221 | public void onAnimationCancel(View view) {
222 | ViewCompat.setAlpha(view, 1);
223 | }
224 |
225 | @Override
226 | public void onAnimationEnd(View view) {
227 | animation.setListener(null);
228 | dispatchAddFinished(holder);
229 | mAddAnimations.remove(holder);
230 | dispatchFinishedWhenDone();
231 | }
232 | }).start();
233 | }
234 |
235 | @Override
236 | public boolean animateMove(final RecyclerView.ViewHolder holder, int fromX, int fromY,
237 | int toX, int toY) {
238 | final View view = holder.itemView;
239 | fromX += ViewCompat.getTranslationX(holder.itemView);
240 | fromY += ViewCompat.getTranslationY(holder.itemView);
241 | resetAnimation(holder);
242 | int deltaX = toX - fromX;
243 | int deltaY = toY - fromY;
244 | if (deltaX == 0 && deltaY == 0) {
245 | dispatchMoveFinished(holder);
246 | return false;
247 | }
248 | if (deltaX != 0) {
249 | ViewCompat.setTranslationX(view, -deltaX);
250 | }
251 | if (deltaY != 0) {
252 | ViewCompat.setTranslationY(view, -deltaY);
253 | }
254 | mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
255 | return true;
256 | }
257 |
258 | private void animateMoveImpl(final RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
259 | final View view = holder.itemView;
260 | final int deltaX = toX - fromX;
261 | final int deltaY = toY - fromY;
262 | if (deltaX != 0) {
263 | ViewCompat.animate(view).translationX(0);
264 | }
265 | if (deltaY != 0) {
266 | ViewCompat.animate(view).translationY(0);
267 | }
268 | // TODO: make EndActions end listeners instead, since end actions aren't called when
269 | // vpas are canceled (and can't end them. why?)
270 | // need listener functionality in VPACompat for this. Ick.
271 | final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
272 | mMoveAnimations.add(holder);
273 | animation.setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() {
274 | @Override
275 | public void onAnimationStart(View view) {
276 | dispatchMoveStarting(holder);
277 | }
278 | @Override
279 | public void onAnimationCancel(View view) {
280 | if (deltaX != 0) {
281 | ViewCompat.setTranslationX(view, 0);
282 | }
283 | if (deltaY != 0) {
284 | ViewCompat.setTranslationY(view, 0);
285 | }
286 | }
287 | @Override
288 | public void onAnimationEnd(View view) {
289 | animation.setListener(null);
290 | dispatchMoveFinished(holder);
291 | mMoveAnimations.remove(holder);
292 | dispatchFinishedWhenDone();
293 | }
294 | }).start();
295 | }
296 |
297 | @Override
298 | public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder,
299 | int fromX, int fromY, int toX, int toY) {
300 | if (oldHolder == newHolder) {
301 | // Don't know how to run change animations when the same view holder is re-used.
302 | // run a move animation to handle position changes.
303 | return animateMove(oldHolder, fromX, fromY, toX, toY);
304 | }
305 | final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView);
306 | final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView);
307 | final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView);
308 | resetAnimation(oldHolder);
309 | int deltaX = (int) (toX - fromX - prevTranslationX);
310 | int deltaY = (int) (toY - fromY - prevTranslationY);
311 | // recover prev translation state after ending animation
312 | ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX);
313 | ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY);
314 | ViewCompat.setAlpha(oldHolder.itemView, prevAlpha);
315 | if (newHolder != null) {
316 | // carry over translation values
317 | resetAnimation(newHolder);
318 | ViewCompat.setTranslationX(newHolder.itemView, -deltaX);
319 | ViewCompat.setTranslationY(newHolder.itemView, -deltaY);
320 | }
321 | mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
322 | return true;
323 | }
324 |
325 | private void animateChangeImpl(final ChangeInfo changeInfo) {
326 | final RecyclerView.ViewHolder holder = changeInfo.oldHolder;
327 | final View view = holder == null ? null : holder.itemView;
328 | final RecyclerView.ViewHolder newHolder = changeInfo.newHolder;
329 | final View newView = newHolder != null ? newHolder.itemView : null;
330 | if (view != null) {
331 | final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration(
332 | getChangeDuration());
333 | mChangeAnimations.add(changeInfo.oldHolder);
334 | oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
335 | oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
336 | oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() {
337 | @Override
338 | public void onAnimationStart(View view) {
339 | dispatchChangeStarting(changeInfo.oldHolder, true);
340 | }
341 |
342 | @Override
343 | public void onAnimationEnd(View view) {
344 | oldViewAnim.setListener(null);
345 | ViewCompat.setAlpha(view, 1);
346 | ViewCompat.setTranslationX(view, 0);
347 | ViewCompat.setTranslationY(view, 0);
348 | dispatchChangeFinished(changeInfo.oldHolder, true);
349 | mChangeAnimations.remove(changeInfo.oldHolder);
350 | dispatchFinishedWhenDone();
351 | }
352 | }).start();
353 | }
354 | if (newView != null) {
355 | final ViewPropertyAnimatorCompat newViewAnimation = ViewCompat.animate(newView);
356 | mChangeAnimations.add(changeInfo.newHolder);
357 | newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration())
358 | .setListener(new VpaListenerAdapter() {
359 | @Override
360 | public void onAnimationStart(View view) {
361 | dispatchChangeStarting(changeInfo.newHolder, false);
362 | }
363 | @Override
364 | public void onAnimationEnd(View view) {
365 | newViewAnimation.setListener(null);
366 | ViewCompat.setTranslationX(newView, 0);
367 | ViewCompat.setTranslationY(newView, 0);
368 | dispatchChangeFinished(changeInfo.newHolder, false);
369 | mChangeAnimations.remove(changeInfo.newHolder);
370 | dispatchFinishedWhenDone();
371 | }
372 | }).start();
373 | }
374 | }
375 |
376 | private void endChangeAnimation(List infoList, RecyclerView.ViewHolder item) {
377 | for (int i = infoList.size() - 1; i >= 0; i--) {
378 | ChangeInfo changeInfo = infoList.get(i);
379 | if (endChangeAnimationIfNecessary(changeInfo, item)) {
380 | if (changeInfo.oldHolder == null && changeInfo.newHolder == null) {
381 | infoList.remove(changeInfo);
382 | }
383 | }
384 | }
385 | }
386 |
387 | private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) {
388 | if (changeInfo.oldHolder != null) {
389 | endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder);
390 | }
391 | if (changeInfo.newHolder != null) {
392 | endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder);
393 | }
394 | }
395 | private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, RecyclerView.ViewHolder item) {
396 | boolean oldItem = false;
397 | if (changeInfo.newHolder == item) {
398 | changeInfo.newHolder = null;
399 | } else if (changeInfo.oldHolder == item) {
400 | changeInfo.oldHolder = null;
401 | oldItem = true;
402 | } else {
403 | return false;
404 | }
405 | ViewCompat.setAlpha(item.itemView, 1);
406 | ViewCompat.setTranslationX(item.itemView, 0);
407 | ViewCompat.setTranslationY(item.itemView, 0);
408 | dispatchChangeFinished(item, oldItem);
409 | return true;
410 | }
411 |
412 | @Override
413 | public void endAnimation(RecyclerView.ViewHolder item) {
414 | final View view = item.itemView;
415 | // this will trigger end callback which should set properties to their target values.
416 | ViewCompat.animate(view).cancel();
417 | // TODO if some other animations are chained to end, how do we cancel them as well?
418 | for (int i = mPendingMoves.size() - 1; i >= 0; i--) {
419 | MoveInfo moveInfo = mPendingMoves.get(i);
420 | if (moveInfo.holder == item) {
421 | ViewCompat.setTranslationY(view, 0);
422 | ViewCompat.setTranslationX(view, 0);
423 | dispatchMoveFinished(item);
424 | mPendingMoves.remove(i);
425 | }
426 | }
427 | endChangeAnimation(mPendingChanges, item);
428 | if (mPendingRemovals.remove(item)) {
429 | ViewCompat.setAlpha(view, 1);
430 | dispatchRemoveFinished(item);
431 | }
432 | if (mPendingAdditions.remove(item)) {
433 | ViewCompat.setAlpha(view, 1);
434 | dispatchAddFinished(item);
435 | }
436 |
437 | for (int i = mChangesList.size() - 1; i >= 0; i--) {
438 | ArrayList changes = mChangesList.get(i);
439 | endChangeAnimation(changes, item);
440 | if (changes.isEmpty()) {
441 | mChangesList.remove(i);
442 | }
443 | }
444 | for (int i = mMovesList.size() - 1; i >= 0; i--) {
445 | ArrayList moves = mMovesList.get(i);
446 | for (int j = moves.size() - 1; j >= 0; j--) {
447 | MoveInfo moveInfo = moves.get(j);
448 | if (moveInfo.holder == item) {
449 | ViewCompat.setTranslationY(view, 0);
450 | ViewCompat.setTranslationX(view, 0);
451 | dispatchMoveFinished(item);
452 | moves.remove(j);
453 | if (moves.isEmpty()) {
454 | mMovesList.remove(i);
455 | }
456 | break;
457 | }
458 | }
459 | }
460 | for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
461 | ArrayList additions = mAdditionsList.get(i);
462 | if (additions.remove(item)) {
463 | ViewCompat.setAlpha(view, 1);
464 | dispatchAddFinished(item);
465 | if (additions.isEmpty()) {
466 | mAdditionsList.remove(i);
467 | }
468 | }
469 | }
470 |
471 | // animations should be ended by the cancel above.
472 | //noinspection PointlessBooleanExpression,ConstantConditions
473 | if (mRemoveAnimations.remove(item) && DEBUG) {
474 | throw new IllegalStateException("after animation is cancelled, item should not be in "
475 | + "mRemoveAnimations list");
476 | }
477 |
478 | //noinspection PointlessBooleanExpression,ConstantConditions
479 | if (mAddAnimations.remove(item) && DEBUG) {
480 | throw new IllegalStateException("after animation is cancelled, item should not be in "
481 | + "mAddAnimations list");
482 | }
483 |
484 | //noinspection PointlessBooleanExpression,ConstantConditions
485 | if (mChangeAnimations.remove(item) && DEBUG) {
486 | throw new IllegalStateException("after animation is cancelled, item should not be in "
487 | + "mChangeAnimations list");
488 | }
489 |
490 | //noinspection PointlessBooleanExpression,ConstantConditions
491 | if (mMoveAnimations.remove(item) && DEBUG) {
492 | throw new IllegalStateException("after animation is cancelled, item should not be in "
493 | + "mMoveAnimations list");
494 | }
495 | dispatchFinishedWhenDone();
496 | }
497 |
498 | private void resetAnimation(RecyclerView.ViewHolder holder) {
499 | AnimatorCompatHelper.clearInterpolator(holder.itemView);
500 | endAnimation(holder);
501 | }
502 |
503 | @Override
504 | public boolean isRunning() {
505 | return (!mPendingAdditions.isEmpty() ||
506 | !mPendingChanges.isEmpty() ||
507 | !mPendingMoves.isEmpty() ||
508 | !mPendingRemovals.isEmpty() ||
509 | !mMoveAnimations.isEmpty() ||
510 | !mRemoveAnimations.isEmpty() ||
511 | !mAddAnimations.isEmpty() ||
512 | !mChangeAnimations.isEmpty() ||
513 | !mMovesList.isEmpty() ||
514 | !mAdditionsList.isEmpty() ||
515 | !mChangesList.isEmpty());
516 | }
517 |
518 | /**
519 | * Check the state of currently pending and running animations. If there are none
520 | * pending/running, call {@link #dispatchAnimationsFinished()} to notify any
521 | * listeners.
522 | */
523 | private void dispatchFinishedWhenDone() {
524 | if (!isRunning()) {
525 | dispatchAnimationsFinished();
526 | }
527 | }
528 |
529 | @Override
530 | public void endAnimations() {
531 | int count = mPendingMoves.size();
532 | for (int i = count - 1; i >= 0; i--) {
533 | MoveInfo item = mPendingMoves.get(i);
534 | View view = item.holder.itemView;
535 | ViewCompat.setTranslationY(view, 0);
536 | ViewCompat.setTranslationX(view, 0);
537 | dispatchMoveFinished(item.holder);
538 | mPendingMoves.remove(i);
539 | }
540 | count = mPendingRemovals.size();
541 | for (int i = count - 1; i >= 0; i--) {
542 | RecyclerView.ViewHolder item = mPendingRemovals.get(i);
543 | dispatchRemoveFinished(item);
544 | mPendingRemovals.remove(i);
545 | }
546 | count = mPendingAdditions.size();
547 | for (int i = count - 1; i >= 0; i--) {
548 | RecyclerView.ViewHolder item = mPendingAdditions.get(i);
549 | View view = item.itemView;
550 | ViewCompat.setAlpha(view, 1);
551 | dispatchAddFinished(item);
552 | mPendingAdditions.remove(i);
553 | }
554 | count = mPendingChanges.size();
555 | for (int i = count - 1; i >= 0; i--) {
556 | endChangeAnimationIfNecessary(mPendingChanges.get(i));
557 | }
558 | mPendingChanges.clear();
559 | if (!isRunning()) {
560 | return;
561 | }
562 |
563 | int listCount = mMovesList.size();
564 | for (int i = listCount - 1; i >= 0; i--) {
565 | ArrayList moves = mMovesList.get(i);
566 | count = moves.size();
567 | for (int j = count - 1; j >= 0; j--) {
568 | MoveInfo moveInfo = moves.get(j);
569 | RecyclerView.ViewHolder item = moveInfo.holder;
570 | View view = item.itemView;
571 | ViewCompat.setTranslationY(view, 0);
572 | ViewCompat.setTranslationX(view, 0);
573 | dispatchMoveFinished(moveInfo.holder);
574 | moves.remove(j);
575 | if (moves.isEmpty()) {
576 | mMovesList.remove(moves);
577 | }
578 | }
579 | }
580 | listCount = mAdditionsList.size();
581 | for (int i = listCount - 1; i >= 0; i--) {
582 | ArrayList additions = mAdditionsList.get(i);
583 | count = additions.size();
584 | for (int j = count - 1; j >= 0; j--) {
585 | RecyclerView.ViewHolder item = additions.get(j);
586 | View view = item.itemView;
587 | ViewCompat.setAlpha(view, 1);
588 | dispatchAddFinished(item);
589 | additions.remove(j);
590 | if (additions.isEmpty()) {
591 | mAdditionsList.remove(additions);
592 | }
593 | }
594 | }
595 | listCount = mChangesList.size();
596 | for (int i = listCount - 1; i >= 0; i--) {
597 | ArrayList changes = mChangesList.get(i);
598 | count = changes.size();
599 | for (int j = count - 1; j >= 0; j--) {
600 | endChangeAnimationIfNecessary(changes.get(j));
601 | if (changes.isEmpty()) {
602 | mChangesList.remove(changes);
603 | }
604 | }
605 | }
606 |
607 | cancelAll(mRemoveAnimations);
608 | cancelAll(mMoveAnimations);
609 | cancelAll(mAddAnimations);
610 | cancelAll(mChangeAnimations);
611 |
612 | dispatchAnimationsFinished();
613 | }
614 |
615 | void cancelAll(List viewHolders) {
616 | for (int i = viewHolders.size() - 1; i >= 0; i--) {
617 | ViewCompat.animate(viewHolders.get(i).itemView).cancel();
618 | }
619 | }
620 |
621 | /**
622 | * {@inheritDoc}
623 | *
624 | * If the payload list is not empty, DefaultItemAnimator returns true.
625 | * When this is the case:
626 | *
627 | * If you override {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)}, both
628 | * ViewHolder arguments will be the same instance.
629 | *
630 | *
631 | * If you are not overriding {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)},
632 | * then DefaultItemAnimator will call {@link #animateMove(RecyclerView.ViewHolder, int, int, int, int)} and
633 | * run a move animation instead.
634 | *
635 | *
636 | */
637 | @Override
638 | public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder,
639 | @NonNull List payloads) {
640 | return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads);
641 | }
642 |
643 | private static class VpaListenerAdapter implements ViewPropertyAnimatorListener {
644 | @Override
645 | public void onAnimationStart(View view) {}
646 |
647 | @Override
648 | public void onAnimationEnd(View view) {}
649 |
650 | @Override
651 | public void onAnimationCancel(View view) {}
652 | }
653 | }
654 |
--------------------------------------------------------------------------------
/classify/src/main/java/com/anarchy/classify/ClassifyView.java:
--------------------------------------------------------------------------------
1 | package com.anarchy.classify;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.animation.AnimatorSet;
6 | import android.animation.ObjectAnimator;
7 | import android.animation.PropertyValuesHolder;
8 | import android.annotation.TargetApi;
9 | import android.content.ClipData;
10 | import android.content.Context;
11 | import android.content.res.TypedArray;
12 | import android.database.Cursor;
13 | import android.graphics.Color;
14 | import android.graphics.drawable.Drawable;
15 | import android.os.Build;
16 | import android.os.SystemClock;
17 | import android.support.annotation.NonNull;
18 | import android.support.v4.view.GestureDetectorCompat;
19 | import android.support.v4.view.MotionEventCompat;
20 | import android.support.v4.view.ViewCompat;
21 | import android.support.v7.widget.GridLayoutManager;
22 | import android.support.v7.widget.LinearLayoutManager;
23 | import android.support.v7.widget.RecyclerView;
24 | import android.util.AttributeSet;
25 | import android.view.DragEvent;
26 | import android.view.GestureDetector;
27 | import android.view.Gravity;
28 | import android.view.MotionEvent;
29 | import android.view.VelocityTracker;
30 | import android.view.View;
31 | import android.view.ViewGroup;
32 | import android.view.animation.AccelerateDecelerateInterpolator;
33 | import android.view.animation.Interpolator;
34 | import android.widget.FrameLayout;
35 |
36 | import com.anarchy.classify.adapter.BaseMainAdapter;
37 | import com.anarchy.classify.adapter.BaseSubAdapter;
38 | import com.anarchy.classify.adapter.MainRecyclerViewCallBack;
39 | import com.anarchy.classify.adapter.SubAdapterReference;
40 | import com.anarchy.classify.adapter.SubRecyclerViewCallBack;
41 | import com.anarchy.classify.simple.BaseSimpleAdapter;
42 | import com.anarchy.classify.util.L;
43 |
44 | import java.util.ArrayList;
45 | import java.util.List;
46 |
47 | /**
48 | *
49 | * Date: 16/6/1 14:16
50 | * Author: zhendong.wu@shoufuyou.com
51 | *
52 | */
53 | public class ClassifyView extends FrameLayout {
54 | /**
55 | * 不做处理的状态
56 | */
57 | public static final int STATE_NONE = 0;
58 | /**
59 | * 当前状态为 可移动
60 | */
61 | public static final int STATE_MOVE = 1;
62 | /**
63 | * 当前状态为 可合并
64 | */
65 | public static final int STATE_MERGE = 2;
66 |
67 |
68 | private static final int ACTIVE_POINTER_ID_NONE = -1;
69 | private static final String DESCRIPTION = "Long press";
70 | private static final String MAIN = "main";
71 | private static final String SUB = "sub";
72 |
73 |
74 | /**
75 | * 放置主要RecyclerView的容器
76 | */
77 | private ViewGroup mMainContainer;
78 | /**
79 | * 放置次级RecyclerView的容器
80 | */
81 | private ViewGroup mSubContainer;
82 | /**
83 | * 被拖动的View
84 | */
85 | private View mDragView;
86 |
87 |
88 | private View mMainShadowView;
89 | private RecyclerView mMainRecyclerView;
90 | private RecyclerView mSubRecyclerView;
91 |
92 | private int mMainSpanCount;
93 | private int mSubSpanCount;
94 | private GestureDetectorCompat mMainGestureDetector;
95 | private GestureDetectorCompat mSubGestureDetector;
96 |
97 | private RecyclerView.OnItemTouchListener mMainItemTouchListener;
98 | private RecyclerView.OnItemTouchListener mSubItemTouchListener;
99 |
100 | private MainRecyclerViewCallBack mMainCallBack;
101 | private SubRecyclerViewCallBack mSubCallBack;
102 |
103 | private float mSubRatio;
104 | private int mMainActivePointerId = ACTIVE_POINTER_ID_NONE;
105 | private int mSubActivePointerId = ACTIVE_POINTER_ID_NONE;
106 | private int mShadowColor;
107 | private int mAnimationDuration;
108 |
109 |
110 | private int mSelectedStartX;
111 | private int mSelectedStartY;
112 | private float mInitialTouchX;
113 | private float mInitialTouchY;
114 | private float mDx;
115 | private float mDy;
116 | private View mSelected;
117 | private int mSelectedPosition;
118 | /**
119 | * 触发滑动距离
120 | */
121 | private int mEdgeWidth;
122 |
123 | private boolean inMainRegion;
124 | private boolean inSubRegion;
125 |
126 | private VelocityTracker mVelocityTracker;
127 |
128 | public ClassifyView(Context context) {
129 | super(context);
130 | init(context, null, 0);
131 | }
132 |
133 | public ClassifyView(Context context, AttributeSet attrs) {
134 | super(context, attrs);
135 | init(context, attrs, 0);
136 | }
137 |
138 | public ClassifyView(Context context, AttributeSet attrs, int defStyleAttr) {
139 | super(context, attrs, defStyleAttr);
140 | init(context, attrs, defStyleAttr);
141 | }
142 |
143 |
144 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
145 | public ClassifyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
146 | super(context, attrs, defStyleAttr, defStyleRes);
147 | init(context, attrs, defStyleAttr);
148 | }
149 |
150 | /**
151 | * 初始化容器
152 | */
153 | private void init(Context context, AttributeSet attrs, int defStyleAttr) {
154 | mMainContainer = new FrameLayout(context);
155 | mSubContainer = new FrameLayout(context);
156 | mMainContainer.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
157 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ClassifyView, defStyleAttr, R.style.DefaultStyle);
158 | mSubRatio = a.getFraction(R.styleable.ClassifyView_SubRatio, 1, 1, 0.7f);
159 | mMainSpanCount = a.getInt(R.styleable.ClassifyView_MainSpanCount, 3);
160 | mSubSpanCount = a.getInt(R.styleable.ClassifyView_SubSpanCount, 3);
161 | mShadowColor = a.getColor(R.styleable.ClassifyView_ShadowColor, 0x83585858);
162 | mAnimationDuration = a.getInt(R.styleable.ClassifyView_AnimationDuration, 200);
163 | mEdgeWidth = a.getDimensionPixelSize(R.styleable.ClassifyView_EdgeWidth, 15);
164 | a.recycle();
165 | mMainRecyclerView = getMain(context, attrs);
166 | mSubRecyclerView = getSub(context, attrs);
167 | mMainContainer.addView(mMainRecyclerView);
168 | mMainShadowView = new View(context);
169 | mMainShadowView.setBackgroundColor(mShadowColor);
170 | mMainShadowView.setVisibility(View.GONE);
171 | mMainShadowView.setOnClickListener(new OnClickListener() {
172 | @Override
173 | public void onClick(View v) {
174 | if (mHideSubAnim != null && mHideSubAnim.isRunning()) return;
175 | hideSubContainer();
176 | }
177 | });
178 | mMainShadowView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
179 | mMainContainer.addView(mMainShadowView);
180 | mSubContainer.addView(mSubRecyclerView);
181 | mSubContainer.setBackgroundColor(Color.CYAN);
182 | addViewInLayout(mMainContainer, 0, mMainContainer.getLayoutParams());
183 | mDragView = new View(context);
184 | mDragView.setVisibility(GONE);
185 | addViewInLayout(mDragView, -1, generateDefaultLayoutParams());
186 | setUpTouchListener(context);
187 | }
188 |
189 | protected
190 | @NonNull
191 | RecyclerView getMain(Context context, AttributeSet parentAttrs) {
192 | RecyclerView recyclerView = new RecyclerView(context);
193 | recyclerView.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
194 | recyclerView.setLayoutManager(new GridLayoutManager(context, mMainSpanCount));
195 | recyclerView.setItemAnimator(new ClassifyItemAnimator());
196 | return recyclerView;
197 | }
198 |
199 |
200 | protected
201 | @NonNull
202 | RecyclerView getSub(Context context, AttributeSet parentAttrs) {
203 | RecyclerView recyclerView = new RecyclerView(context);
204 | recyclerView.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
205 | recyclerView.setLayoutManager(new GridLayoutManager(context, mSubSpanCount));
206 | recyclerView.setItemAnimator(new ClassifyItemAnimator());
207 | return recyclerView;
208 | }
209 |
210 |
211 | public RecyclerView getMainRecyclerView() {
212 | return mMainRecyclerView;
213 | }
214 |
215 | public RecyclerView getSubRecyclerView() {
216 | return mSubRecyclerView;
217 | }
218 |
219 |
220 | private View findChildView(RecyclerView recyclerView, MotionEvent event) {
221 | // first check elevated views, if none, then call RV
222 | final float x = event.getX();
223 | final float y = event.getY();
224 | return recyclerView.findChildViewUnder(x, y);
225 | }
226 |
227 | /**
228 | * 设置adapter
229 | *
230 | * @param mainAdapter
231 | * @param subAdapter
232 | */
233 | public void setAdapter(BaseMainAdapter mainAdapter, BaseSubAdapter subAdapter) {
234 | mMainRecyclerView.setAdapter(mainAdapter);
235 | mMainRecyclerView.addOnItemTouchListener(mMainItemTouchListener);
236 | mMainCallBack = mainAdapter;
237 | mSubRecyclerView.setAdapter(subAdapter);
238 | mSubRecyclerView.addOnItemTouchListener(mSubItemTouchListener);
239 | mSubCallBack = subAdapter;
240 | mMainRecyclerView.setOnDragListener(new MainDragListener());
241 | mSubRecyclerView.setOnDragListener(new SubDragListener());
242 | }
243 |
244 | /**
245 | * @param baseSimpleAdapter
246 | */
247 | public void setAdapter(BaseSimpleAdapter baseSimpleAdapter) {
248 | setAdapter(baseSimpleAdapter.getMainAdapter(), baseSimpleAdapter.getSubAdapter());
249 | }
250 |
251 | public RecyclerView.LayoutManager getMainLayoutManager() {
252 | return mMainRecyclerView.getLayoutManager();
253 | }
254 |
255 | public RecyclerView.LayoutManager getSubLayoutManager() {
256 | return mSubRecyclerView.getLayoutManager();
257 | }
258 |
259 | /**
260 | * 初始化 触摸事件监听
261 | *
262 | * @param context
263 | */
264 | private void setUpTouchListener(Context context) {
265 | mMainGestureDetector = new GestureDetectorCompat(context, new GestureDetector.SimpleOnGestureListener() {
266 | @Override
267 | public boolean onDown(MotionEvent e) {
268 | return true;
269 | }
270 |
271 | @Override
272 | public boolean onSingleTapConfirmed(MotionEvent e) {
273 | View pressedView = findChildView(mMainRecyclerView, e);
274 | if (pressedView == null) return false;
275 | int position = mMainRecyclerView.getChildAdapterPosition(pressedView);
276 | List list = mMainCallBack.explodeItem(position, pressedView);
277 | if (list == null || list.size() < 2) {
278 | mMainCallBack.onItemClick(position, pressedView);
279 | return true;
280 | } else {
281 | mSubCallBack.initData(position, list);
282 | if (ViewCompat.isAttachedToWindow(mSubContainer)) {
283 | //取消之前进行的动画
284 | if (mShowSubAnim != null && mShowSubAnim.isRunning()) {
285 | mShowSubAnim.cancel();
286 | }
287 | //确保次级窗口在屏幕外
288 | resetSubContainerPlace();
289 | showSubContainer();
290 | } else {
291 | final int height = (int) (getHeight() * mSubRatio);
292 | LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height);
293 | params.gravity = Gravity.BOTTOM;
294 | mSubContainer.setLayoutParams(params);
295 | addView(mSubContainer);
296 | ViewCompat.postOnAnimation(mSubContainer, new Runnable() {
297 | @Override
298 | public void run() {
299 | mSubContainer.setTranslationY(height);
300 | showSubContainer();
301 | }
302 | });
303 | }
304 | return true;
305 | }
306 |
307 | }
308 |
309 | @Override
310 | public void onLongPress(MotionEvent e) {
311 | View pressedView = findChildView(mMainRecyclerView, e);
312 | if (pressedView == null) return;
313 | L.d("Main recycler view on long press: x: %1$s + y: %2$s", e.getX(), e.getY());
314 | int position = mMainRecyclerView.getChildAdapterPosition(pressedView);
315 |
316 | int pointerId = MotionEventCompat.getPointerId(e, 0);
317 | if (pointerId == mMainActivePointerId) {
318 | if (mMainCallBack.canDragOnLongPress(position, pressedView)) {
319 | mSelectedPosition = position;
320 | mSelectedStartX = pressedView.getLeft();
321 | mSelectedStartY = pressedView.getTop();
322 | mDx = mDy = 0f;
323 | int index = MotionEventCompat.findPointerIndex(e, mMainActivePointerId);
324 | mInitialTouchX = MotionEventCompat.getX(e, index);
325 | mInitialTouchY = MotionEventCompat.getY(e, index);
326 | L.d("handle event on long press:X: %1$s , Y: %2$s ", mInitialTouchX, mInitialTouchY);
327 | inMainRegion = true;
328 | mSelected = pressedView;
329 | pressedView.startDrag(ClipData.newPlainText(DESCRIPTION, MAIN),
330 | new ClassifyDragShadowBuilder(pressedView), mSelected, 0);
331 | }
332 | }
333 | }
334 | });
335 | mMainItemTouchListener = new RecyclerView.OnItemTouchListener() {
336 |
337 | @Override
338 | public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
339 | mMainGestureDetector.onTouchEvent(e);
340 | int action = MotionEventCompat.getActionMasked(e);
341 | switch (action) {
342 | case MotionEvent.ACTION_DOWN:
343 | mMainActivePointerId = MotionEventCompat.getPointerId(e, 0);
344 | break;
345 | case MotionEvent.ACTION_CANCEL:
346 | case MotionEvent.ACTION_UP:
347 | mMainActivePointerId = ACTIVE_POINTER_ID_NONE;
348 | inMergeState = false;
349 | break;
350 | }
351 | return false;
352 | }
353 |
354 | @Override
355 | public void onTouchEvent(RecyclerView rv, MotionEvent e) {
356 | mMainGestureDetector.onTouchEvent(e);
357 | int action = MotionEventCompat.getActionMasked(e);
358 | switch (action) {
359 | case MotionEvent.ACTION_MOVE:
360 | break;
361 | case MotionEvent.ACTION_CANCEL:
362 | case MotionEvent.ACTION_UP:
363 | mMainActivePointerId = ACTIVE_POINTER_ID_NONE;
364 | break;
365 | case MotionEvent.ACTION_POINTER_UP:
366 | int pointerIndex = MotionEventCompat.getActionIndex(e);
367 | int pointerId = MotionEventCompat.getPointerId(e, pointerIndex);
368 | if (pointerId == mSubActivePointerId) {
369 | int newPointerId = pointerIndex == 0 ? 1 : 0;
370 | mMainActivePointerId = MotionEventCompat.getPointerId(e, newPointerId);
371 | }
372 | break;
373 | }
374 | }
375 |
376 | @Override
377 | public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
378 |
379 | }
380 | };
381 | mSubGestureDetector = new GestureDetectorCompat(context, new GestureDetector.SimpleOnGestureListener() {
382 | @Override
383 | public boolean onDown(MotionEvent e) {
384 | return true;
385 | }
386 |
387 | @Override
388 | public boolean onSingleTapConfirmed(MotionEvent e) {
389 | View pressedView = findChildView(mSubRecyclerView, e);
390 | if (pressedView == null) return false;
391 | int position = mSubRecyclerView.getChildAdapterPosition(pressedView);
392 | mSubCallBack.onItemClick(position, pressedView);
393 | return true;
394 | }
395 |
396 | @Override
397 | public void onLongPress(MotionEvent e) {
398 | View pressedView = findChildView(mSubRecyclerView, e);
399 | if (pressedView == null) return;
400 | L.d("Sub recycler view on long press: x: %1$s + y: %2$s", e.getX(), e.getY());
401 | int position = mSubRecyclerView.getChildAdapterPosition(pressedView);
402 | int pointerId = MotionEventCompat.getPointerId(e, 0);
403 | if (pointerId == mSubActivePointerId) {
404 | if (mSubCallBack.canDragOnLongPress(position, pressedView)) {
405 | mSelectedPosition = position;
406 | mSelectedStartX = pressedView.getLeft();
407 | mSelectedStartY = pressedView.getTop();
408 | mDx = mDy = 0f;
409 | int index = MotionEventCompat.findPointerIndex(e, mSubActivePointerId);
410 | mInitialTouchX = MotionEventCompat.getX(e, index);
411 | mInitialTouchY = MotionEventCompat.getY(e, index);
412 | inSubRegion = true;
413 | mSelected = pressedView;
414 | pressedView.startDrag(ClipData.newPlainText(
415 | DESCRIPTION, SUB),
416 | getShadowBuilder(pressedView), mSelected, 0);
417 | }
418 | }
419 | }
420 | });
421 | mSubItemTouchListener = new RecyclerView.OnItemTouchListener() {
422 | @Override
423 | public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
424 | mSubGestureDetector.onTouchEvent(e);
425 | int action = MotionEventCompat.getActionMasked(e);
426 | switch (action) {
427 | case MotionEvent.ACTION_DOWN:
428 | mSubActivePointerId = MotionEventCompat.getPointerId(e, 0);
429 | mInitialTouchX = e.getX();
430 | mInitialTouchY = e.getY();
431 | break;
432 | case MotionEvent.ACTION_CANCEL:
433 | case MotionEvent.ACTION_UP:
434 | mSubActivePointerId = ACTIVE_POINTER_ID_NONE;
435 | break;
436 | }
437 | return false;
438 | }
439 |
440 | @Override
441 | public void onTouchEvent(RecyclerView rv, MotionEvent e) {
442 | mSubGestureDetector.onTouchEvent(e);
443 | int action = MotionEventCompat.getActionMasked(e);
444 | switch (action) {
445 | case MotionEvent.ACTION_MOVE:
446 | break;
447 | case MotionEvent.ACTION_CANCEL:
448 | case MotionEvent.ACTION_UP:
449 | mSubActivePointerId = ACTIVE_POINTER_ID_NONE;
450 | break;
451 | case MotionEvent.ACTION_POINTER_UP:
452 | int pointerIndex = MotionEventCompat.getActionIndex(e);
453 | int pointerId = MotionEventCompat.getPointerId(e, pointerIndex);
454 | if (pointerId == mSubActivePointerId) {
455 | int newPointerId = pointerIndex == 0 ? 1 : 0;
456 | mSubActivePointerId = MotionEventCompat.getPointerId(e, newPointerId);
457 | }
458 | break;
459 |
460 | }
461 | }
462 |
463 | @Override
464 | public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
465 |
466 | }
467 | };
468 | }
469 |
470 | private void resetSubContainerPlace() {
471 | int height = mSubContainer.getHeight();
472 | mSubContainer.setTranslationY(height);
473 | }
474 |
475 | private AnimatorSet mShowSubAnim;
476 | private AnimatorSet mHideSubAnim;
477 |
478 | /**
479 | * 显示次级窗口
480 | */
481 | private void showSubContainer() {
482 | if (mShowSubAnim != null && mShowSubAnim.isRunning()) return;
483 | mShowSubAnim = new AnimatorSet();
484 | ObjectAnimator subAnim = ObjectAnimator.ofFloat(mSubContainer, "translationY", 0);
485 | ObjectAnimator shadowAnim = ObjectAnimator.ofFloat(mMainShadowView, "alpha", 0f, 1f);
486 | mShowSubAnim.setDuration(mAnimationDuration);
487 | mShowSubAnim.setInterpolator(new AccelerateDecelerateInterpolator());
488 | mShowSubAnim.addListener(new AnimatorListenerAdapter() {
489 | @Override
490 | public void onAnimationCancel(Animator animation) {
491 | }
492 |
493 | @Override
494 | public void onAnimationEnd(Animator animation) {
495 | }
496 |
497 | @Override
498 | public void onAnimationStart(Animator animation) {
499 | mMainShadowView.setVisibility(VISIBLE);
500 | }
501 | });
502 | mShowSubAnim.play(subAnim).with(shadowAnim);
503 | mShowSubAnim.start();
504 | }
505 |
506 | /**
507 | * 隐藏次级窗口
508 | */
509 | private void hideSubContainer() {
510 | if (mHideSubAnim != null && mHideSubAnim.isRunning()) return;
511 | int height = mSubContainer.getHeight();
512 | mHideSubAnim = new AnimatorSet();
513 | ObjectAnimator subAnim = ObjectAnimator.ofFloat(mSubContainer, "translationY", height);
514 | ObjectAnimator shadowAnim = ObjectAnimator.ofFloat(mMainShadowView, "alpha", 1f, 0f);
515 | mHideSubAnim.setDuration(mAnimationDuration);
516 | mHideSubAnim.setInterpolator(new AccelerateDecelerateInterpolator());
517 | mHideSubAnim.addListener(new AnimatorListenerAdapter() {
518 | @Override
519 | public void onAnimationCancel(Animator animation) {
520 | mMainShadowView.setVisibility(GONE);
521 | }
522 |
523 | @Override
524 | public void onAnimationEnd(Animator animation) {
525 | mMainShadowView.setVisibility(GONE);
526 | }
527 |
528 | @Override
529 | public void onAnimationStart(Animator animation) {
530 | mMainShadowView.setVisibility(VISIBLE);
531 | }
532 | });
533 | mHideSubAnim.playTogether(subAnim, shadowAnim);
534 | mHideSubAnim.start();
535 |
536 | }
537 |
538 | private boolean mergeSuccess = false;
539 |
540 | class MainDragListener implements View.OnDragListener {
541 | @Override
542 | public boolean onDrag(View v, DragEvent event) {
543 | if (mSelected == null) return false;
544 | int action = event.getAction();
545 | int width = mSelected.getWidth();
546 | int height = mSelected.getHeight();
547 | float x = event.getX();
548 | float y = event.getY();
549 | float centerX = x - width / 2;
550 | float centerY = y - height / 2;
551 | switch (action) {
552 | case DragEvent.ACTION_DRAG_STARTED:
553 | if (inMainRegion) {
554 | obtainVelocityTracker();
555 | restoreDragView();
556 | mDragView.setBackgroundDrawable(getDragDrawable(mSelected));
557 | mDragView.setVisibility(VISIBLE);
558 | mMainCallBack.setDragPosition(mSelectedPosition);
559 | mDragView.setX(mInitialTouchX - width / 2);
560 | mDragView.setY(mInitialTouchY - height / 2);
561 | mDragView.bringToFront();
562 | mElevationHelper.floatView(mMainRecyclerView, mDragView);
563 | }
564 | break;
565 | case DragEvent.ACTION_DRAG_LOCATION:
566 | mVelocityTracker.addMovement(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
567 | MotionEvent.ACTION_MOVE, x, y, 0));
568 | mDragView.setX(centerX);
569 | mDragView.setY(centerY);
570 | mDx = x - mInitialTouchX;
571 | mDy = y - mInitialTouchY;
572 | moveIfNecessary(mSelected);
573 | removeCallbacks(mScrollRunnable);
574 | mScrollRunnable.run();
575 | invalidate();
576 | break;
577 | case DragEvent.ACTION_DRAG_ENDED:
578 | if (mergeSuccess) {
579 | mergeSuccess = false;
580 | break;
581 | }
582 | if (inMainRegion) {
583 | doRecoverAnimation();
584 | }
585 | releaseVelocityTracker();
586 | break;
587 | case DragEvent.ACTION_DRAG_EXITED:
588 | break;
589 | case DragEvent.ACTION_DROP:
590 | if (inMergeState) {
591 | inMergeState = false;
592 | if (mLastMergeStartPosition == -1) break;
593 | ChangeInfo changeInfo = mMainCallBack.onPrepareMerge(mMainRecyclerView, mSelectedPosition, mLastMergeStartPosition);
594 | RecyclerView.ViewHolder target = mMainRecyclerView.findViewHolderForAdapterPosition(mLastMergeStartPosition);
595 | if (target == null || changeInfo == null || target.itemView == mSelected) {
596 | mergeSuccess = false;
597 | break;
598 | }
599 | float scaleX = ((float) changeInfo.itemWidth) / ((float) (mSelected.getWidth() - changeInfo.paddingLeft - changeInfo.paddingRight - 2 * changeInfo.outlinePadding));
600 | float scaleY = ((float) changeInfo.itemHeight) / ((float) (mSelected.getHeight() - changeInfo.paddingTop - changeInfo.paddingBottom - 2 * changeInfo.outlinePadding));
601 | int targetX = (int) (target.itemView.getLeft() + changeInfo.left + changeInfo.paddingLeft - (changeInfo.paddingLeft + changeInfo.outlinePadding) * scaleX);
602 | int targetY = (int) (target.itemView.getTop() + changeInfo.top + changeInfo.paddingTop - (changeInfo.paddingTop + changeInfo.outlinePadding) * scaleY);
603 | mDragView.setPivotX(0);
604 | mDragView.setPivotY(0);
605 | L.d("targetX:%1$s,targetY:%2$s,scaleX:%3$s,scaleY:%4$s", targetX, targetY, scaleX, scaleY);
606 | mDragView.animate().x(targetX).y(targetY).scaleX(scaleX).scaleY(scaleY).setListener(mMergeAnimListener).setDuration(mAnimationDuration).start();
607 | mergeSuccess = true;
608 | }
609 | break;
610 | }
611 | return true;
612 | }
613 | }
614 |
615 | private AnimatorListenerAdapter mMergeAnimListener = new AnimatorListenerAdapter() {
616 | @Override
617 | public void onAnimationStart(Animator animation) {
618 | mMainCallBack.onStartMergeAnimation(mMainRecyclerView, mSelectedPosition, mLastMergeStartPosition, mAnimationDuration);
619 | }
620 |
621 | @Override
622 | public void onAnimationEnd(Animator animation) {
623 | mMainCallBack.onMerged(mMainRecyclerView, mSelectedPosition, mLastMergeStartPosition);
624 | restoreToInitial();
625 | }
626 |
627 | @Override
628 | public void onAnimationCancel(Animator animation) {
629 | mMainCallBack.onMerged(mMainRecyclerView, mSelectedPosition, mLastMergeStartPosition);
630 | restoreToInitial();
631 | }
632 | };
633 |
634 | protected Drawable getDragDrawable(View view) {
635 | return new DragDrawable(view);
636 | }
637 |
638 | class SubDragListener implements View.OnDragListener {
639 | @Override
640 | public boolean onDrag(View v, DragEvent event) {
641 | if (mSelected == null) return false;
642 | int action = event.getAction();
643 | int width = mSelected.getWidth();
644 | int height = mSelected.getHeight();
645 | float x = event.getX();
646 | float y = event.getY();
647 | float centerX = x - width / 2;
648 | float centerY = y - height / 2;
649 | float marginTop = getHeight() - mSubContainer.getHeight();
650 | switch (action) {
651 | case DragEvent.ACTION_DRAG_STARTED:
652 | if (inSubRegion) {
653 | obtainVelocityTracker();
654 | restoreDragView();
655 | mDragView.setBackgroundDrawable(getDragDrawable(mSelected));
656 | mDragView.setVisibility(VISIBLE);
657 | mSubCallBack.setDragPosition(mSelectedPosition);
658 | mDragView.setX(mInitialTouchX - width / 2);
659 | mDragView.setY(mInitialTouchY - height / 2 + marginTop);
660 | mDragView.bringToFront();
661 | mElevationHelper.floatView(mSubRecyclerView, mDragView);
662 | }
663 | break;
664 | case DragEvent.ACTION_DRAG_LOCATION:
665 | mVelocityTracker.addMovement(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
666 | MotionEvent.ACTION_MOVE, x, y, 0));
667 | mDragView.setX(centerX);
668 | mDragView.setY(centerY + marginTop);
669 | mDx = x - mInitialTouchX;
670 | mDy = y - mInitialTouchY;
671 | moveIfNecessary(mSelected);
672 | removeCallbacks(mScrollRunnable);
673 | mScrollRunnable.run();
674 | invalidate();
675 | break;
676 | case DragEvent.ACTION_DRAG_ENDED:
677 | if (inSubRegion) {
678 | doRecoverAnimation();
679 | }
680 | releaseVelocityTracker();
681 | break;
682 | case DragEvent.ACTION_DRAG_EXITED:
683 | if (mSubCallBack.canDragOut(mSelectedPosition)) {
684 | inSubRegion = false;
685 | inMainRegion = true;
686 | hideSubContainer();
687 | mSelectedPosition = mMainCallBack.onLeaveSubRegion(mSelectedPosition, new SubAdapterReference(mSubCallBack));
688 | mMainCallBack.setDragPosition(mSelectedPosition);
689 | mSubCallBack.setDragPosition(-1);
690 | }
691 | break;
692 | case DragEvent.ACTION_DROP:
693 | break;
694 | }
695 | return true;
696 | }
697 | }
698 |
699 | /**
700 | * 做恢复到之前状态的动画
701 | */
702 | private void doRecoverAnimation() {
703 | Animator recoverAnimator = null;
704 | if (inSubRegion) {
705 | RecyclerView.ViewHolder holder = mSubRecyclerView.findViewHolderForAdapterPosition(mSelectedPosition);
706 | if (holder == null) {
707 | PropertyValuesHolder yOffset = PropertyValuesHolder.ofFloat("y", getHeight() + mSelected.getHeight());
708 | recoverAnimator = ObjectAnimator.ofPropertyValuesHolder(mDragView, yOffset);
709 | } else {
710 | PropertyValuesHolder xOffset = PropertyValuesHolder.ofFloat("x", mSubContainer.getLeft() + holder.itemView.getLeft());
711 | PropertyValuesHolder yOffset = PropertyValuesHolder.ofFloat("y", mSubContainer.getTop() + holder.itemView.getTop());
712 | recoverAnimator = ObjectAnimator.ofPropertyValuesHolder(mDragView, xOffset, yOffset);
713 | }
714 | }
715 |
716 | if (inMainRegion) {
717 | RecyclerView.ViewHolder holder = mMainRecyclerView.findViewHolderForAdapterPosition(mSelectedPosition);
718 | if (holder == null) {
719 | PropertyValuesHolder yOffset = PropertyValuesHolder.ofFloat("y", getHeight() + mSelected.getHeight());
720 | recoverAnimator = ObjectAnimator.ofPropertyValuesHolder(mDragView, yOffset);
721 | } else {
722 | PropertyValuesHolder xOffset = PropertyValuesHolder.ofFloat("x", holder.itemView.getLeft());
723 | PropertyValuesHolder yOffset = PropertyValuesHolder.ofFloat("y", holder.itemView.getTop());
724 | recoverAnimator = ObjectAnimator.ofPropertyValuesHolder(mDragView, xOffset, yOffset);
725 | }
726 | }
727 | if (recoverAnimator == null) return;
728 | recoverAnimator.setDuration(mAnimationDuration);
729 | recoverAnimator.setInterpolator(sDragScrollInterpolator);
730 | recoverAnimator.addListener(mRecoverAnimatorListener);
731 | recoverAnimator.start();
732 | }
733 |
734 | private AnimatorListenerAdapter mRecoverAnimatorListener = new AnimatorListenerAdapter() {
735 | @Override
736 | public void onAnimationEnd(Animator animation) {
737 | restoreToInitial();
738 | }
739 | };
740 |
741 | private void restoreToInitial() {
742 |
743 | if (inSubRegion) {
744 | restoreDragView();
745 | mSubCallBack.setDragPosition(-1);
746 | inSubRegion = false;
747 | }
748 | if (inMainRegion) {
749 | restoreDragView();
750 | mMainCallBack.setDragPosition(-1);
751 | inMainRegion = false;
752 | }
753 | }
754 |
755 | private void restoreDragView() {
756 | mDragView.setVisibility(GONE);
757 | mDragView.setScaleX(1f);
758 | mDragView.setScaleY(1f);
759 | mDragView.setTranslationX(0f);
760 | mDragView.setTranslationX(0f);
761 | }
762 |
763 | /**
764 | * If user drags the view to the edge, trigger a scroll if necessary.
765 | */
766 | private boolean scrollIfNecessary() {
767 | RecyclerView recyclerView = null;
768 | if (inMainRegion) {
769 | recyclerView = mMainRecyclerView;
770 | }
771 | if (inSubRegion) {
772 | recyclerView = mSubRecyclerView;
773 | }
774 | if (recyclerView == null) return false;
775 | final long now = System.currentTimeMillis();
776 | final long scrollDuration = mDragScrollStartTimeInMs
777 | == Long.MIN_VALUE ? 0 : now - mDragScrollStartTimeInMs;
778 | RecyclerView.LayoutManager lm = recyclerView.getLayoutManager();
779 |
780 | int scrollX = 0;
781 | int scrollY = 0;
782 | if (lm.canScrollHorizontally()) {
783 | int curX = (int) (mInitialTouchX + mDx - mSelected.getWidth() / 2);
784 | final int leftDiff = curX - mEdgeWidth - recyclerView.getPaddingLeft();
785 | if (mDx < 0 && leftDiff < 0) {
786 | scrollX = leftDiff;
787 | } else if (mDx > 0) {
788 | final int rightDiff =
789 | curX + mSelected.getWidth() + mEdgeWidth - (recyclerView.getWidth() - recyclerView.getPaddingRight());
790 | if (rightDiff > 0) {
791 | scrollX = rightDiff;
792 | }
793 | }
794 | }
795 | if (lm.canScrollVertically()) {
796 | int curY = (int) (mInitialTouchY + mDy - mSelected.getHeight() / 2);
797 | final int topDiff = curY - mEdgeWidth - recyclerView.getPaddingTop();
798 | if (mDy < 0 && topDiff < 0) {
799 | scrollY = topDiff;
800 | } else if (mDy > 0) {
801 | final int bottomDiff = curY + mSelected.getHeight() + mEdgeWidth -
802 | (recyclerView.getHeight() - recyclerView.getPaddingBottom());
803 | if (bottomDiff > 0) {
804 | scrollY = bottomDiff;
805 | }
806 | }
807 | }
808 | if (scrollX != 0) {
809 | scrollX = interpolateOutOfBoundsScroll(recyclerView,
810 | mSelected.getWidth(), scrollX,
811 | recyclerView.getWidth(), scrollDuration);
812 | }
813 | if (scrollY != 0) {
814 | scrollY = interpolateOutOfBoundsScroll(recyclerView,
815 | mSelected.getHeight(), scrollY,
816 | recyclerView.getHeight(), scrollDuration);
817 | }
818 | if (scrollX != 0 || scrollY != 0) {
819 | if (mDragScrollStartTimeInMs == Long.MIN_VALUE) {
820 | mDragScrollStartTimeInMs = now;
821 | }
822 | recyclerView.scrollBy(scrollX, scrollY);
823 | return true;
824 | }
825 | mDragScrollStartTimeInMs = Long.MIN_VALUE;
826 | return false;
827 | }
828 |
829 |
830 | private int interpolateOutOfBoundsScroll(RecyclerView recyclerView,
831 | int viewSize, int viewSizeOutOfBounds,
832 | int totalSize, long msSinceStartScroll) {
833 | final int maxScroll = getMaxDragScroll(recyclerView);
834 | final int absOutOfBounds = Math.abs(viewSizeOutOfBounds);
835 | final int direction = (int) Math.signum(viewSizeOutOfBounds);
836 | // might be negative if other direction
837 | float outOfBoundsRatio = Math.min(1f, 1f * absOutOfBounds / viewSize);
838 | final int cappedScroll = (int) (direction * maxScroll *
839 | sDragViewScrollCapInterpolator.getInterpolation(outOfBoundsRatio));
840 | final float timeRatio;
841 | if (msSinceStartScroll > DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS) {
842 | timeRatio = 1f;
843 | } else {
844 | timeRatio = (float) msSinceStartScroll / DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS;
845 | }
846 | final int value = (int) (cappedScroll * sDragScrollInterpolator
847 | .getInterpolation(timeRatio));
848 | if (value == 0) {
849 | return viewSizeOutOfBounds > 0 ? 1 : -1;
850 | }
851 | return value;
852 | }
853 |
854 | private static final long DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS = 2000;
855 | private static final Interpolator sDragViewScrollCapInterpolator = new Interpolator() {
856 | public float getInterpolation(float t) {
857 | t -= 1.0f;
858 | return t * t * t * t * t + 1.0f;
859 | }
860 | };
861 | private static final Interpolator sDragScrollInterpolator = new Interpolator() {
862 | public float getInterpolation(float t) {
863 | return t * t * t * t * t;
864 | }
865 | };
866 | private int mCachedMaxScrollSpeed = -1;
867 |
868 | private int getMaxDragScroll(RecyclerView recyclerView) {
869 | if (mCachedMaxScrollSpeed == -1) {
870 | mCachedMaxScrollSpeed = recyclerView.getResources().getDimensionPixelSize(
871 | R.dimen.item_touch_helper_max_drag_scroll_per_frame);
872 | }
873 | return mCachedMaxScrollSpeed;
874 | }
875 |
876 | /**
877 | * When user started to drag scroll. Reset when we don't scroll
878 | */
879 | private long mDragScrollStartTimeInMs;
880 |
881 |
882 | /**
883 | * When user drags a view to the edge, we start scrolling the LayoutManager as long as View
884 | * is partially out of bounds.
885 | */
886 | private final Runnable mScrollRunnable = new Runnable() {
887 | @Override
888 | public void run() {
889 | if (mSelected != null && scrollIfNecessary()) {
890 | if (mSelected != null) { //it might be lost during scrolling
891 | moveIfNecessary(mSelected);
892 | }
893 | removeCallbacks(mScrollRunnable);
894 | ViewCompat.postOnAnimation(ClassifyView.this, this);
895 | }
896 | }
897 | };
898 | private boolean inMergeState = false;
899 | private int mLastMergeStartPosition = -1;
900 |
901 | private void moveIfNecessary(View view) {
902 | final int x = (int) (mSelectedStartX + mDx);
903 | final int y = (int) (mSelectedStartY + mDy);
904 | //如果移动范围在自身范围内
905 | if (Math.abs(y - view.getTop()) < view.getHeight() * 0.5f
906 | && Math.abs(x - view.getLeft())
907 | < view.getWidth() * 0.5f) {
908 | return;
909 | }
910 | List swapTargets = findSwapTargets(view);
911 | if (swapTargets.size() == 0) return;
912 | View target = chooseTarget(view, swapTargets, x, y);
913 | if (target == null) return;
914 | if (inSubRegion) {//次级目录下 没有merge形式
915 | int targetPosition = mSubRecyclerView.getChildAdapterPosition(target);
916 | int state = mSubCallBack.getCurrentState(mSelected, target, x, y, mVelocityTracker, mSelectedPosition,
917 | targetPosition);
918 | if (state == STATE_MOVE) {
919 | if (mSubCallBack.onMove(mSelectedPosition, targetPosition)) {
920 | mSelectedPosition = targetPosition;
921 | RecyclerView.ViewHolder viewHolder = mSubRecyclerView.findViewHolderForAdapterPosition(mSelectedPosition);
922 | if (viewHolder != null) mSelected = viewHolder.itemView;
923 | mSubCallBack.setDragPosition(targetPosition);
924 | mSubCallBack.moved(mSelectedPosition, targetPosition);
925 | }
926 | }
927 | }
928 | if (inMainRegion) {//在主层级下 有merge状况 以及次级目录拖动到主层级的状况
929 | int targetPosition = mMainRecyclerView.getChildAdapterPosition(target);
930 | if(targetPosition != mLastMergeStartPosition) inMergeState = false;
931 | int state = mMainCallBack.getCurrentState(mSelected, target, x, y, mVelocityTracker, mSelectedPosition,
932 | targetPosition);
933 | boolean mergeState = state == STATE_MERGE;
934 | if (mergeState ^ inMergeState) {
935 | if (mergeState) {
936 | if (mMainCallBack.onMergeStart(mMainRecyclerView, mSelectedPosition, targetPosition)) {
937 | inMergeState = true;
938 | mLastMergeStartPosition = targetPosition;
939 | }
940 | } else {
941 | if (mLastMergeStartPosition != -1 && inMergeState) {
942 | mMainCallBack.onMergeCancel(mMainRecyclerView, mSelectedPosition, mLastMergeStartPosition);
943 | mLastMergeStartPosition = -1;
944 | inMergeState = false;
945 | }
946 | }
947 | }
948 | if (state == STATE_MOVE) {
949 | if (inMergeState && mLastMergeStartPosition != -1) {
950 | //makeSure trigger mergeCancel
951 | mMainCallBack.onMergeCancel(mMainRecyclerView, mSelectedPosition, mLastMergeStartPosition);
952 | mLastMergeStartPosition = -1;
953 | inMergeState = false;
954 | }
955 | if (mMainCallBack.onMove(mSelectedPosition, targetPosition)) {
956 | mSelectedPosition = targetPosition;
957 | RecyclerView.ViewHolder viewHolder = mMainRecyclerView.findViewHolderForAdapterPosition(mSelectedPosition);
958 | if (viewHolder != null) mSelected = viewHolder.itemView;
959 | mMainCallBack.setDragPosition(targetPosition);
960 | mMainCallBack.moved(mSelectedPosition, targetPosition);
961 | }
962 | }
963 | }
964 | }
965 |
966 | private List mSwapTargets;
967 |
968 | /**
969 | * 找到当前移动View 有覆盖的view
970 | *
971 | * @return
972 | */
973 | private List findSwapTargets(View view) {
974 | if (mSwapTargets == null) {
975 | mSwapTargets = new ArrayList<>();
976 | } else {
977 | mSwapTargets.clear();
978 | }
979 | int left = Math.round(mSelectedStartX + mDx);
980 | int top = Math.round(mSelectedStartY + mDy);
981 | int right = left + view.getWidth();
982 | int bottom = top + view.getHeight();
983 | RecyclerView.LayoutManager lm = null;
984 | RecyclerView recyclerView = null;
985 | if (inMainRegion) {
986 | lm = getMainLayoutManager();
987 | recyclerView = mMainRecyclerView;
988 | }
989 | if (inSubRegion) {
990 | lm = getSubLayoutManager();
991 | recyclerView = mSubRecyclerView;
992 | }
993 | if (lm == null || recyclerView == null) return mSwapTargets;
994 | int childCount = lm.getChildCount();
995 | for (int i = 0; i < childCount; i++) {
996 | View child = lm.getChildAt(i);
997 | if (child == view) {
998 | //本身
999 | continue;
1000 | }
1001 | if (child.getBottom() < top || child.getTop() > bottom || child.getLeft() > right || child.getRight() < left) {
1002 | continue;//没有覆盖到
1003 | }
1004 | int targetPosition = recyclerView.getChildAdapterPosition(child);
1005 | //检验目标位置是否能移动
1006 | if (inMainRegion) {
1007 | if (!mMainCallBack.canDropOVer(mSelectedPosition, targetPosition)) continue;
1008 | }
1009 | if (inSubRegion) {
1010 | if (!mSubCallBack.canDropOver(mSelectedPosition, targetPosition)) continue;
1011 | }
1012 | mSwapTargets.add(child);
1013 | }
1014 | return mSwapTargets;
1015 | }
1016 |
1017 | private void obtainVelocityTracker() {
1018 | if (mVelocityTracker != null) {
1019 | mVelocityTracker.recycle();
1020 | }
1021 | mVelocityTracker = VelocityTracker.obtain();
1022 | }
1023 |
1024 | private void releaseVelocityTracker() {
1025 | if (mVelocityTracker != null) {
1026 | mVelocityTracker.recycle();
1027 | mVelocityTracker = null;
1028 | }
1029 | }
1030 |
1031 | /**
1032 | * 从候选项中找到最有优势的目标
1033 | *
1034 | * @param selected
1035 | * @param swapTargets
1036 | * @param curX
1037 | * @param curY
1038 | * @return
1039 | */
1040 | protected View chooseTarget(View selected, List swapTargets, int curX, int curY) {
1041 | int right = curX + selected.getWidth();
1042 | int bottom = curY + selected.getHeight();
1043 | View winner = null;
1044 | int winnerScore = Integer.MAX_VALUE;
1045 | final int dx = curX - selected.getLeft();
1046 | final int dy = curY - selected.getTop();
1047 | final int targetsSize = swapTargets.size();
1048 | for (int i = 0; i < targetsSize; i++) {
1049 | final View target = swapTargets.get(i);
1050 | final int score = Math.abs(target.getLeft() - curX) + Math.abs(target.getTop() - curY)
1051 | + Math.abs(target.getBottom() - bottom) + Math.abs(target.getRight() - right);
1052 | if (score < winnerScore) {
1053 | winnerScore = score;
1054 | winner = target;
1055 | }
1056 | // if (dx > 0) {
1057 | // int diff = target.getRight() - right;
1058 | // if (diff < 0) {
1059 | // final int score = Math.abs(diff);
1060 | // if (score > winnerScore) {
1061 | // winnerScore = score;
1062 | // winner = target;
1063 | // }
1064 | // }
1065 | // }
1066 | // if (dx < 0) {
1067 | // int diff = target.getLeft() - curX;
1068 | // if (diff > 0) {
1069 | // final int score = Math.abs(diff);
1070 | // if (score > winnerScore) {
1071 | // winnerScore = score;
1072 | // winner = target;
1073 | // }
1074 | // }
1075 | // }
1076 | // if (dy < 0) {
1077 | // int diff = target.getTop() - curY;
1078 | // if (diff > 0) {
1079 | // final int score = Math.abs(diff);
1080 | // if (score > winnerScore) {
1081 | // winnerScore = score;
1082 | // winner = target;
1083 | // }
1084 | // }
1085 | // }
1086 | //
1087 | // if (dy > 0) {
1088 | // int diff = target.getBottom() - bottom;
1089 | // if (diff < 0) {
1090 | // final int score = Math.abs(diff);
1091 | // if (score > winnerScore) {
1092 | // winnerScore = score;
1093 | // winner = target;
1094 | // }
1095 | // }
1096 | // }
1097 | }
1098 | return winner;
1099 | }
1100 |
1101 | /**
1102 | * 获取DragShadowBuilder 用于渲染 被拖动的view
1103 | * 默认使用 {@link ClassifyDragShadowBuilder} 实现 自定义时请重写该方法
1104 | *
1105 | * @param view 被拖动item 的 root view
1106 | * @return
1107 | */
1108 | protected DragShadowBuilder getShadowBuilder(View view) {
1109 | return new ClassifyDragShadowBuilder(view);
1110 | }
1111 |
1112 | private ElevationHelper mElevationHelper = new ElevationHelper();
1113 |
1114 | static class ElevationHelper {
1115 |
1116 |
1117 | public void floatView(RecyclerView recyclerView, View dragView) {
1118 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
1119 | float maxElevation = findMaxElevation(recyclerView) + 1f;
1120 | dragView.setElevation(maxElevation);
1121 | } else {
1122 | Drawable drawable = dragView.getBackground();
1123 | if (drawable instanceof DragDrawable) {
1124 | DragDrawable dragDrawable = (DragDrawable) drawable;
1125 | dragDrawable.showShadow();
1126 | dragView.setLayerType(View.LAYER_TYPE_SOFTWARE, dragDrawable.getPaint());
1127 | }
1128 | }
1129 | }
1130 |
1131 | private float findMaxElevation(RecyclerView recyclerView) {
1132 | final int childCount = recyclerView.getChildCount();
1133 | float max = 0;
1134 | for (int i = 0; i < childCount; i++) {
1135 | final View child = recyclerView.getChildAt(i);
1136 |
1137 | final float elevation = ViewCompat.getElevation(child);
1138 | if (elevation > max) {
1139 | max = elevation;
1140 | }
1141 | }
1142 | return max;
1143 | }
1144 | }
1145 |
1146 |
1147 | }
--------------------------------------------------------------------------------