├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── ViewFactory.iml ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── xhome │ │ └── uestcfei │ │ └── com │ │ └── viewfactory │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── xhome │ │ │ └── uestcfei │ │ │ └── com │ │ │ └── viewfactory │ │ │ ├── MainActivity.java │ │ │ └── MyItemAnimator.java │ └── res │ │ ├── drawable │ │ ├── deadtime_background.xml │ │ ├── point1.xml │ │ ├── point2.xml │ │ └── point3.xml │ │ ├── layout │ │ ├── content_main.xml │ │ └── item.xml │ │ ├── menu │ │ └── menu_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ ├── edit.png │ │ ├── ic_launcher.png │ │ └── list.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── xhome │ └── uestcfei │ └── com │ └── viewfactory │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── sample └── app-debug.apk └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | .DS_Store 5 | /build 6 | # built application files 7 | 8 | # files for the dex VM 9 | *.dex 10 | 11 | # Java class files 12 | *.class 13 | .DS_Store 14 | 15 | # generated files 16 | bin/ 17 | gen/ 18 | Wiki/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Eclipse project files 24 | .classpath 25 | .project 26 | .settings/ 27 | 28 | # Proguard folder generated by Eclipse 29 | proguard/ 30 | 31 | #Android Studio 32 | build/ 33 | 34 | # Intellij project files 35 | *.iml 36 | *.ipr 37 | *.iws 38 | .idea/ 39 | 40 | #gradle 41 | .gradle/ 42 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | ViewFactory -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Class structureJava 39 | 40 | 41 | Code maturity issuesJava 42 | 43 | 44 | Java 45 | 46 | 47 | Java language level migration aidsJava 48 | 49 | 50 | Javadoc issuesJava 51 | 52 | 53 | Performance issuesJava 54 | 55 | 56 | TestNG 57 | 58 | 59 | Threading issuesJava 60 | 61 | 62 | 63 | 64 | Android 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 86 | 87 | 88 | 89 | 90 | 1.7 91 | 92 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CoolTodoCard 2 | 这是一个recyclerView的Item进入以及退出的动画项目,我参照DefaultItemAnimator类,并加入了一些其他的动画效果。具体情况请看代码,其实并不复杂。 3 | 4 | 5 | 我是从dribbble中获得的灵感[dribbble](https://dribbble.com/shots/2375358-Goal-App-Animation) 6 | 7 | 欢迎完善。 8 | 9 | # Preview 10 | 看起来可能是这个样子 11 | 12 | ![todolist card](http://img.blog.csdn.net/20151128165330538) 13 | -------------------------------------------------------------------------------- /ViewFactory.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | applicationId "xhome.uestcfei.com.viewfactory" 9 | minSdkVersion 15 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.1.0' 26 | compile 'com.android.support:design:23.1.0' 27 | compile 'com.android.support:recyclerview-v7:23.1.0' 28 | compile 'com.android.support:cardview-v7:23.1.0' 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/fei/Dev/Android/Tools/android-sdk-macosx/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/xhome/uestcfei/com/viewfactory/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package xhome.uestcfei.com.viewfactory; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/xhome/uestcfei/com/viewfactory/MainActivity.java: -------------------------------------------------------------------------------- 1 | package xhome.uestcfei.com.viewfactory; 2 | 3 | import android.graphics.Color; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.CardView; 7 | import android.support.v7.widget.DefaultItemAnimator; 8 | import android.support.v7.widget.GridLayoutManager; 9 | import android.support.v7.widget.LinearLayoutManager; 10 | import android.support.v7.widget.RecyclerView; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.ImageView; 15 | import android.widget.TextView; 16 | 17 | import java.util.ArrayList; 18 | import java.util.Date; 19 | import java.util.List; 20 | import java.util.Random; 21 | 22 | public class MainActivity extends AppCompatActivity { 23 | 24 | private RecyclerView recyclerView; 25 | private List data ; 26 | private List deadTimes ; 27 | 28 | private MyAdapter myAdapter; 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.content_main); 34 | data = new ArrayList<>(); 35 | deadTimes = new ArrayList<>(); 36 | 37 | data.add("To learn how to play"); 38 | data.add("Learn 50 fresh words"); 39 | data.add("Learn the poem by heart"); 40 | data.add("Draw a landscape"); 41 | 42 | deadTimes.add("until Monday"); 43 | deadTimes.add("until 21 Dec"); 44 | deadTimes.add("until tomorrow"); 45 | deadTimes.add("until Friday"); 46 | recyclerView = (RecyclerView) findViewById(R.id.recyclerView); 47 | GridLayoutManager gridLayoutManager = new GridLayoutManager(this,1); 48 | gridLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); 49 | recyclerView.setLayoutManager(gridLayoutManager); 50 | myAdapter = new MyAdapter(); 51 | recyclerView.setAdapter(myAdapter); 52 | MyItemAnimator animator = new MyItemAnimator(this); 53 | animator.setAddDuration(500); 54 | animator.setRemoveDuration(1000); 55 | recyclerView.setItemAnimator(animator); 56 | } 57 | 58 | 59 | public void remove4(View view) { 60 | if (data.size() < 4) { 61 | return; 62 | } 63 | data.remove(data.size() - 1); 64 | data.remove(data.size() - 1); 65 | data.remove(data.size() - 1); 66 | data.remove(data.size() - 1); 67 | deadTimes.remove(deadTimes.size() - 1); 68 | deadTimes.remove(deadTimes.size() - 1); 69 | deadTimes.remove(deadTimes.size() - 1); 70 | deadTimes.remove(deadTimes.size() - 1); 71 | myAdapter.notifyItemRangeRemoved(data.size(), 4); 72 | } 73 | 74 | public void add1(View view) { 75 | data.add("I have a new task to do !"); 76 | deadTimes.add("new date"); 77 | myAdapter.notifyItemInserted(data.size()); 78 | } 79 | 80 | public void remove1(View view) { 81 | if (data.size() == 0) { 82 | return; 83 | } 84 | data.remove(data.size() - 1); 85 | deadTimes.remove(deadTimes.size() - 1); 86 | myAdapter.notifyItemRemoved(data.size()); 87 | } 88 | 89 | public void add4(View view) { 90 | data.add("To learn how to play"); 91 | data.add("Learn 50 fresh words"); 92 | data.add("Learn the poem by heart"); 93 | data.add("Draw a landscape"); 94 | deadTimes.add("until Monday"); 95 | deadTimes.add("until 21 Dec"); 96 | deadTimes.add("until tomorrow"); 97 | deadTimes.add("until Friday"); 98 | myAdapter.notifyItemRangeInserted(data.size(), 4); 99 | } 100 | 101 | class MyAdapter extends RecyclerView.Adapter{ 102 | 103 | private static final String TAG = "MyAdapter"; 104 | private LayoutInflater inflater; 105 | 106 | public MyAdapter() { 107 | inflater = LayoutInflater.from(MainActivity.this); 108 | } 109 | 110 | @Override 111 | public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) { 112 | View view = inflater.inflate(R.layout.item, null); 113 | return new MyHolder(view); 114 | } 115 | 116 | @Override 117 | public void onBindViewHolder(MyHolder holder, int position) { 118 | CardView cardView = (CardView) holder.itemView; 119 | switch (position % 4) { 120 | case 0: 121 | cardView.setCardBackgroundColor(Color.parseColor("#6BC39A")); 122 | holder.indicator.setImageResource(R.drawable.point1); 123 | holder.deadTime.setTextColor(Color.parseColor("#76BEE6")); 124 | break; 125 | case 1: 126 | cardView.setCardBackgroundColor(Color.parseColor("#76BEE6")); 127 | holder.indicator.setImageResource(R.drawable.point2); 128 | holder.deadTime.setTextColor(Color.parseColor("#6BC39A")); 129 | break; 130 | case 2: 131 | cardView.setCardBackgroundColor(Color.parseColor("#E99A79")); 132 | holder.indicator.setImageResource(R.drawable.point3); 133 | holder.deadTime.setTextColor(Color.parseColor("#657DC1")); 134 | break; 135 | case 3: 136 | cardView.setCardBackgroundColor(Color.parseColor("#657DC1")); 137 | holder.indicator.setImageResource(R.drawable.point1); 138 | holder.deadTime.setTextColor(Color.parseColor("#E99A79")); 139 | break; 140 | } 141 | holder.text.setText(data.get(position)); 142 | holder.deadTime.setText(deadTimes.get(position)); 143 | holder.itemView.setTag(position); 144 | } 145 | 146 | @Override 147 | public int getItemCount() { 148 | return data.size(); 149 | } 150 | 151 | class MyHolder extends RecyclerView.ViewHolder{ 152 | 153 | private TextView text; 154 | private TextView deadTime; 155 | private ImageView indicator; 156 | 157 | public MyHolder(View itemView) { 158 | super(itemView); 159 | text = (TextView) itemView.findViewById(R.id.text); 160 | deadTime = (TextView) itemView.findViewById(R.id.deadtime); 161 | indicator = (ImageView) itemView.findViewById(R.id.indicator); 162 | } 163 | 164 | } 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /app/src/main/java/xhome/uestcfei/com/viewfactory/MyItemAnimator.java: -------------------------------------------------------------------------------- 1 | package xhome.uestcfei.com.viewfactory; 2 | 3 | /** 4 | * Email : luckyliangfei@gmail.com 5 | * Created by fei on 15/11/28. 6 | */ 7 | 8 | import android.app.Activity; 9 | import android.content.Context; 10 | import android.support.v4.animation.AnimatorCompatHelper; 11 | import android.support.v4.view.ViewCompat; 12 | import android.support.v4.view.ViewPropertyAnimatorCompat; 13 | import android.support.v4.view.ViewPropertyAnimatorListener; 14 | import android.support.v7.widget.RecyclerView; 15 | import android.support.v7.widget.RecyclerView.ViewHolder; 16 | import android.support.v7.widget.SimpleItemAnimator; 17 | import android.util.DisplayMetrics; 18 | import android.util.Log; 19 | import android.view.View; 20 | import android.view.animation.AnticipateOvershootInterpolator; 21 | import android.view.animation.OvershootInterpolator; 22 | 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | /** 27 | * This implementation of {@link RecyclerView.ItemAnimator} provides basic 28 | * animations on remove, add, and move events that happen to the items in 29 | * a RecyclerView. RecyclerView uses a DefaultItemAnimator by default. 30 | * 31 | * @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator) 32 | */ 33 | public class MyItemAnimator extends SimpleItemAnimator { 34 | private static final boolean DEBUG = false; 35 | private static final String TAG = "My"; 36 | 37 | private ArrayList mPendingRemovals = new ArrayList<>(); 38 | private ArrayList mPendingAdditions = new ArrayList<>(); 39 | private ArrayList mPendingMoves = new ArrayList<>(); 40 | private ArrayList mPendingChanges = new ArrayList<>(); 41 | 42 | private ArrayList> mAdditionsList = new ArrayList<>(); 43 | private ArrayList> mMovesList = new ArrayList<>(); 44 | private ArrayList> mChangesList = new ArrayList<>(); 45 | 46 | private ArrayList mAddAnimations = new ArrayList<>(); 47 | private ArrayList mMoveAnimations = new ArrayList<>(); 48 | private ArrayList mRemoveAnimations = new ArrayList<>(); 49 | private ArrayList mChangeAnimations = new ArrayList<>(); 50 | 51 | private Context mContext; 52 | private int width; 53 | private int height; 54 | 55 | public MyItemAnimator(Context context) { 56 | this.mContext = context; 57 | DisplayMetrics metric = new DisplayMetrics(); 58 | ((Activity)mContext).getWindowManager().getDefaultDisplay().getMetrics(metric); 59 | width = px2dp(metric.widthPixels); // 屏幕宽度(像素) 60 | height = px2dp(metric.heightPixels); // 屏幕高度(像素) 61 | } 62 | 63 | private static class MoveInfo { 64 | public ViewHolder holder; 65 | public int fromX, fromY, toX, toY; 66 | 67 | private MoveInfo(ViewHolder holder, int fromX, int fromY, int toX, int toY) { 68 | this.holder = holder; 69 | this.fromX = fromX; 70 | this.fromY = fromY; 71 | this.toX = toX; 72 | this.toY = toY; 73 | } 74 | } 75 | 76 | private static class ChangeInfo { 77 | public ViewHolder oldHolder, newHolder; 78 | public int fromX, fromY, toX, toY; 79 | private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder) { 80 | this.oldHolder = oldHolder; 81 | this.newHolder = newHolder; 82 | } 83 | 84 | private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder, 85 | int fromX, int fromY, int toX, int toY) { 86 | this(oldHolder, newHolder); 87 | this.fromX = fromX; 88 | this.fromY = fromY; 89 | this.toX = toX; 90 | this.toY = toY; 91 | } 92 | 93 | @Override 94 | public String toString() { 95 | return "ChangeInfo{" + 96 | "oldHolder=" + oldHolder + 97 | ", newHolder=" + newHolder + 98 | ", fromX=" + fromX + 99 | ", fromY=" + fromY + 100 | ", toX=" + toX + 101 | ", toY=" + toY + 102 | '}'; 103 | } 104 | } 105 | 106 | @Override 107 | public void runPendingAnimations() { 108 | boolean removalsPending = !mPendingRemovals.isEmpty(); 109 | boolean movesPending = !mPendingMoves.isEmpty(); 110 | boolean changesPending = !mPendingChanges.isEmpty(); 111 | boolean additionsPending = !mPendingAdditions.isEmpty(); 112 | if (!removalsPending && !movesPending && !additionsPending && !changesPending) { 113 | // nothing to animate 114 | return; 115 | } 116 | // First, remove stuff 117 | for (ViewHolder holder : mPendingRemovals) { 118 | animateRemoveImpl(holder); 119 | } 120 | mPendingRemovals.clear(); 121 | // Next, move stuff 122 | if (movesPending) { 123 | final ArrayList moves = new ArrayList<>(); 124 | moves.addAll(mPendingMoves); 125 | mMovesList.add(moves); 126 | mPendingMoves.clear(); 127 | Runnable mover = new Runnable() { 128 | @Override 129 | public void run() { 130 | for (MoveInfo moveInfo : moves) { 131 | animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY, 132 | moveInfo.toX, moveInfo.toY); 133 | } 134 | moves.clear(); 135 | mMovesList.remove(moves); 136 | } 137 | }; 138 | if (removalsPending) { 139 | View view = moves.get(0).holder.itemView; 140 | ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration()); 141 | } else { 142 | mover.run(); 143 | } 144 | } 145 | // Next, change stuff, to run in parallel with move animations 146 | if (changesPending) { 147 | final ArrayList changes = new ArrayList<>(); 148 | changes.addAll(mPendingChanges); 149 | mChangesList.add(changes); 150 | mPendingChanges.clear(); 151 | Runnable changer = new Runnable() { 152 | @Override 153 | public void run() { 154 | for (ChangeInfo change : changes) { 155 | animateChangeImpl(change); 156 | } 157 | changes.clear(); 158 | mChangesList.remove(changes); 159 | } 160 | }; 161 | if (removalsPending) { 162 | ViewHolder holder = changes.get(0).oldHolder; 163 | ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration()); 164 | } else { 165 | changer.run(); 166 | } 167 | } 168 | // Next, add stuff 169 | if (additionsPending) { 170 | final ArrayList additions = new ArrayList<>(); 171 | additions.addAll(mPendingAdditions); 172 | mAdditionsList.add(additions); 173 | mPendingAdditions.clear(); 174 | Runnable adder = new Runnable() { 175 | public void run() { 176 | for (ViewHolder holder : additions) { 177 | animateAddImpl(holder); 178 | } 179 | additions.clear(); 180 | mAdditionsList.remove(additions); 181 | } 182 | }; 183 | if (removalsPending || movesPending || changesPending) { 184 | long removeDuration = removalsPending ? getRemoveDuration() : 0; 185 | long moveDuration = movesPending ? getMoveDuration() : 0; 186 | long changeDuration = changesPending ? getChangeDuration() : 0; 187 | long totalDelay = removeDuration + Math.max(moveDuration, changeDuration); 188 | View view = additions.get(0).itemView; 189 | ViewCompat.postOnAnimationDelayed(view, adder, totalDelay); 190 | } else { 191 | adder.run(); 192 | } 193 | } 194 | } 195 | 196 | @Override 197 | public boolean animateRemove(final ViewHolder holder) { 198 | resetAnimation(holder); 199 | mPendingRemovals.add(holder); 200 | return true; 201 | } 202 | 203 | private void animateRemoveImpl(final ViewHolder holder) { 204 | final View view = holder.itemView; 205 | int position = (int) view.getTag(); 206 | final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); 207 | float endX ,endY; 208 | if (position % 2 == 0) { 209 | endY = -100; 210 | endX = -width; 211 | } else { 212 | endY = 100; 213 | endX = width; 214 | } 215 | mRemoveAnimations.add(holder); 216 | animation.setInterpolator(new AnticipateOvershootInterpolator()); 217 | animation.setDuration(getRemoveDuration()) 218 | .alpha(0).translationX(endX).translationY(endY).setListener(new VpaListenerAdapter() { 219 | @Override 220 | public void onAnimationStart(View view) { 221 | dispatchRemoveStarting(holder); 222 | } 223 | 224 | @Override 225 | public void onAnimationEnd(View view) { 226 | animation.setListener(null); 227 | //重置为正常 228 | ViewCompat.setAlpha(view, 1); 229 | ViewCompat.setTranslationX(view, 0); 230 | ViewCompat.setTranslationY(view, 0); 231 | dispatchRemoveFinished(holder); 232 | mRemoveAnimations.remove(holder); 233 | dispatchFinishedWhenDone(); 234 | } 235 | }).start(); 236 | } 237 | 238 | @Override 239 | public boolean animateAdd(final ViewHolder holder) { 240 | resetAnimation(holder); 241 | mPendingAdditions.add(holder); 242 | return true; 243 | } 244 | 245 | 246 | private void animateAddImpl(final ViewHolder holder) { 247 | final View view = holder.itemView; 248 | final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); 249 | mAddAnimations.add(holder); 250 | float startX ,startY; 251 | 252 | int position = (int) view.getTag(); 253 | 254 | if (position % 2 == 0) { 255 | startY = - 100; 256 | startX = -width; 257 | } else { 258 | startY = 100; 259 | startX = width; 260 | } 261 | Log.d("test", String.valueOf(holder.getLayoutPosition())); 262 | ViewCompat.setTranslationX(view, startX); 263 | //应该是相对偏移 264 | ViewCompat.setTranslationY(view, startY); 265 | ViewCompat.setAlpha(view, 0); 266 | animation.setInterpolator(new OvershootInterpolator()); 267 | animation.alpha(1).translationX(0).translationY(0).setDuration(getAddDuration()). 268 | setListener(new VpaListenerAdapter() { 269 | @Override 270 | public void onAnimationStart(View view) { 271 | dispatchAddStarting(holder); 272 | } 273 | 274 | @Override 275 | public void onAnimationCancel(View view) { 276 | ViewCompat.setAlpha(view, 1); 277 | } 278 | 279 | @Override 280 | public void onAnimationEnd(View view) { 281 | animation.setListener(null); 282 | dispatchAddFinished(holder); 283 | mAddAnimations.remove(holder); 284 | dispatchFinishedWhenDone(); 285 | //使用这个来复原 286 | ViewCompat.setTranslationY(view, 0); 287 | } 288 | }).start(); 289 | } 290 | 291 | @Override 292 | public boolean animateMove(final ViewHolder holder, int fromX, int fromY, 293 | int toX, int toY) { 294 | final View view = holder.itemView; 295 | fromX += ViewCompat.getTranslationX(holder.itemView); 296 | fromY += ViewCompat.getTranslationY(holder.itemView); 297 | resetAnimation(holder); 298 | int deltaX = toX - fromX; 299 | int deltaY = toY - fromY; 300 | if (deltaX == 0 && deltaY == 0) { 301 | dispatchMoveFinished(holder); 302 | return false; 303 | } 304 | if (deltaX != 0) { 305 | ViewCompat.setTranslationX(view, -deltaX); 306 | } 307 | if (deltaY != 0) { 308 | ViewCompat.setTranslationY(view, -deltaY); 309 | } 310 | mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY)); 311 | return true; 312 | } 313 | 314 | private void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) { 315 | final View view = holder.itemView; 316 | final int deltaX = toX - fromX; 317 | final int deltaY = toY - fromY; 318 | if (deltaX != 0) { 319 | ViewCompat.animate(view).translationX(0); 320 | } 321 | if (deltaY != 0) { 322 | ViewCompat.animate(view).translationY(0); 323 | } 324 | // TODO: make EndActions end listeners instead, since end actions aren't called when 325 | // vpas are canceled (and can't end them. why?) 326 | // need listener functionality in VPACompat for this. Ick. 327 | final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); 328 | mMoveAnimations.add(holder); 329 | animation.setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() { 330 | @Override 331 | public void onAnimationStart(View view) { 332 | dispatchMoveStarting(holder); 333 | } 334 | @Override 335 | public void onAnimationCancel(View view) { 336 | if (deltaX != 0) { 337 | ViewCompat.setTranslationX(view, 0); 338 | } 339 | if (deltaY != 0) { 340 | ViewCompat.setTranslationY(view, 0); 341 | } 342 | } 343 | @Override 344 | public void onAnimationEnd(View view) { 345 | animation.setListener(null); 346 | dispatchMoveFinished(holder); 347 | mMoveAnimations.remove(holder); 348 | dispatchFinishedWhenDone(); 349 | } 350 | }).start(); 351 | } 352 | 353 | @Override 354 | public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder, 355 | int fromX, int fromY, int toX, int toY) { 356 | if (oldHolder == newHolder) { 357 | // Don't know how to run change animations when the same view holder is re-used. 358 | // run a move animation to handle position changes. 359 | return animateMove(oldHolder, fromX, fromY, toX, toY); 360 | } 361 | final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView); 362 | final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView); 363 | final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView); 364 | resetAnimation(oldHolder); 365 | int deltaX = (int) (toX - fromX - prevTranslationX); 366 | int deltaY = (int) (toY - fromY - prevTranslationY); 367 | // recover prev translation state after ending animation 368 | ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX); 369 | ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY); 370 | ViewCompat.setAlpha(oldHolder.itemView, prevAlpha); 371 | if (newHolder != null) { 372 | // carry over translation values 373 | resetAnimation(newHolder); 374 | ViewCompat.setTranslationX(newHolder.itemView, -deltaX); 375 | ViewCompat.setTranslationY(newHolder.itemView, -deltaY); 376 | ViewCompat.setAlpha(newHolder.itemView, 0); 377 | } 378 | mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY)); 379 | return true; 380 | } 381 | 382 | private void animateChangeImpl(final ChangeInfo changeInfo) { 383 | final ViewHolder holder = changeInfo.oldHolder; 384 | final View view = holder == null ? null : holder.itemView; 385 | final ViewHolder newHolder = changeInfo.newHolder; 386 | final View newView = newHolder != null ? newHolder.itemView : null; 387 | if (view != null) { 388 | final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration( 389 | getChangeDuration()); 390 | mChangeAnimations.add(changeInfo.oldHolder); 391 | oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX); 392 | oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY); 393 | oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() { 394 | @Override 395 | public void onAnimationStart(View view) { 396 | dispatchChangeStarting(changeInfo.oldHolder, true); 397 | } 398 | 399 | @Override 400 | public void onAnimationEnd(View view) { 401 | oldViewAnim.setListener(null); 402 | ViewCompat.setAlpha(view, 1); 403 | ViewCompat.setTranslationX(view, 0); 404 | ViewCompat.setTranslationY(view, 0); 405 | dispatchChangeFinished(changeInfo.oldHolder, true); 406 | mChangeAnimations.remove(changeInfo.oldHolder); 407 | dispatchFinishedWhenDone(); 408 | } 409 | }).start(); 410 | } 411 | if (newView != null) { 412 | final ViewPropertyAnimatorCompat newViewAnimation = ViewCompat.animate(newView); 413 | mChangeAnimations.add(changeInfo.newHolder); 414 | newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()). 415 | alpha(1).setListener(new VpaListenerAdapter() { 416 | @Override 417 | public void onAnimationStart(View view) { 418 | dispatchChangeStarting(changeInfo.newHolder, false); 419 | } 420 | @Override 421 | public void onAnimationEnd(View view) { 422 | newViewAnimation.setListener(null); 423 | ViewCompat.setAlpha(newView, 1); 424 | ViewCompat.setTranslationX(newView, 0); 425 | ViewCompat.setTranslationY(newView, 0); 426 | dispatchChangeFinished(changeInfo.newHolder, false); 427 | mChangeAnimations.remove(changeInfo.newHolder); 428 | dispatchFinishedWhenDone(); 429 | } 430 | }).start(); 431 | } 432 | } 433 | 434 | private void endChangeAnimation(List infoList, ViewHolder item) { 435 | for (int i = infoList.size() - 1; i >= 0; i--) { 436 | ChangeInfo changeInfo = infoList.get(i); 437 | if (endChangeAnimationIfNecessary(changeInfo, item)) { 438 | if (changeInfo.oldHolder == null && changeInfo.newHolder == null) { 439 | infoList.remove(changeInfo); 440 | } 441 | } 442 | } 443 | } 444 | 445 | private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) { 446 | if (changeInfo.oldHolder != null) { 447 | endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder); 448 | } 449 | if (changeInfo.newHolder != null) { 450 | endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder); 451 | } 452 | } 453 | private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, ViewHolder item) { 454 | boolean oldItem = false; 455 | if (changeInfo.newHolder == item) { 456 | changeInfo.newHolder = null; 457 | } else if (changeInfo.oldHolder == item) { 458 | changeInfo.oldHolder = null; 459 | oldItem = true; 460 | } else { 461 | return false; 462 | } 463 | ViewCompat.setAlpha(item.itemView, 1); 464 | ViewCompat.setTranslationX(item.itemView, 0); 465 | ViewCompat.setTranslationY(item.itemView, 0); 466 | dispatchChangeFinished(item, oldItem); 467 | return true; 468 | } 469 | 470 | @Override 471 | public void endAnimation(ViewHolder item) { 472 | final View view = item.itemView; 473 | // this will trigger end callback which should set properties to their target values. 474 | ViewCompat.animate(view).cancel(); 475 | // TODO if some other animations are chained to end, how do we cancel them as well? 476 | for (int i = mPendingMoves.size() - 1; i >= 0; i--) { 477 | MoveInfo moveInfo = mPendingMoves.get(i); 478 | if (moveInfo.holder == item) { 479 | ViewCompat.setTranslationY(view, 0); 480 | ViewCompat.setTranslationX(view, 0); 481 | dispatchMoveFinished(item); 482 | mPendingMoves.remove(i); 483 | } 484 | } 485 | endChangeAnimation(mPendingChanges, item); 486 | if (mPendingRemovals.remove(item)) { 487 | ViewCompat.setAlpha(view, 1); 488 | dispatchRemoveFinished(item); 489 | } 490 | if (mPendingAdditions.remove(item)) { 491 | ViewCompat.setAlpha(view, 1); 492 | dispatchAddFinished(item); 493 | } 494 | 495 | for (int i = mChangesList.size() - 1; i >= 0; i--) { 496 | ArrayList changes = mChangesList.get(i); 497 | endChangeAnimation(changes, item); 498 | if (changes.isEmpty()) { 499 | mChangesList.remove(i); 500 | } 501 | } 502 | for (int i = mMovesList.size() - 1; i >= 0; i--) { 503 | ArrayList moves = mMovesList.get(i); 504 | for (int j = moves.size() - 1; j >= 0; j--) { 505 | MoveInfo moveInfo = moves.get(j); 506 | if (moveInfo.holder == item) { 507 | ViewCompat.setTranslationY(view, 0); 508 | ViewCompat.setTranslationX(view, 0); 509 | dispatchMoveFinished(item); 510 | moves.remove(j); 511 | if (moves.isEmpty()) { 512 | mMovesList.remove(i); 513 | } 514 | break; 515 | } 516 | } 517 | } 518 | for (int i = mAdditionsList.size() - 1; i >= 0; i--) { 519 | ArrayList additions = mAdditionsList.get(i); 520 | if (additions.remove(item)) { 521 | ViewCompat.setAlpha(view, 1); 522 | dispatchAddFinished(item); 523 | if (additions.isEmpty()) { 524 | mAdditionsList.remove(i); 525 | } 526 | } 527 | } 528 | 529 | // animations should be ended by the cancel above. 530 | //noinspection PointlessBooleanExpression,ConstantConditions 531 | if (mRemoveAnimations.remove(item) && DEBUG) { 532 | throw new IllegalStateException("after animation is cancelled, item should not be in " 533 | + "mRemoveAnimations list"); 534 | } 535 | 536 | //noinspection PointlessBooleanExpression,ConstantConditions 537 | if (mAddAnimations.remove(item) && DEBUG) { 538 | throw new IllegalStateException("after animation is cancelled, item should not be in " 539 | + "mAddAnimations list"); 540 | } 541 | 542 | //noinspection PointlessBooleanExpression,ConstantConditions 543 | if (mChangeAnimations.remove(item) && DEBUG) { 544 | throw new IllegalStateException("after animation is cancelled, item should not be in " 545 | + "mChangeAnimations list"); 546 | } 547 | 548 | //noinspection PointlessBooleanExpression,ConstantConditions 549 | if (mMoveAnimations.remove(item) && DEBUG) { 550 | throw new IllegalStateException("after animation is cancelled, item should not be in " 551 | + "mMoveAnimations list"); 552 | } 553 | dispatchFinishedWhenDone(); 554 | } 555 | 556 | private void resetAnimation(ViewHolder holder) { 557 | AnimatorCompatHelper.clearInterpolator(holder.itemView); 558 | endAnimation(holder); 559 | } 560 | 561 | @Override 562 | public boolean isRunning() { 563 | return (!mPendingAdditions.isEmpty() || 564 | !mPendingChanges.isEmpty() || 565 | !mPendingMoves.isEmpty() || 566 | !mPendingRemovals.isEmpty() || 567 | !mMoveAnimations.isEmpty() || 568 | !mRemoveAnimations.isEmpty() || 569 | !mAddAnimations.isEmpty() || 570 | !mChangeAnimations.isEmpty() || 571 | !mMovesList.isEmpty() || 572 | !mAdditionsList.isEmpty() || 573 | !mChangesList.isEmpty()); 574 | } 575 | 576 | /** 577 | * Check the state of currently pending and running animations. If there are none 578 | * pending/running, call {@link #dispatchAnimationsFinished()} to notify any 579 | * listeners. 580 | */ 581 | private void dispatchFinishedWhenDone() { 582 | if (!isRunning()) { 583 | dispatchAnimationsFinished(); 584 | } 585 | } 586 | 587 | @Override 588 | public void endAnimations() { 589 | int count = mPendingMoves.size(); 590 | for (int i = count - 1; i >= 0; i--) { 591 | MoveInfo item = mPendingMoves.get(i); 592 | View view = item.holder.itemView; 593 | ViewCompat.setTranslationY(view, 0); 594 | ViewCompat.setTranslationX(view, 0); 595 | dispatchMoveFinished(item.holder); 596 | mPendingMoves.remove(i); 597 | } 598 | count = mPendingRemovals.size(); 599 | for (int i = count - 1; i >= 0; i--) { 600 | ViewHolder item = mPendingRemovals.get(i); 601 | dispatchRemoveFinished(item); 602 | mPendingRemovals.remove(i); 603 | } 604 | count = mPendingAdditions.size(); 605 | for (int i = count - 1; i >= 0; i--) { 606 | ViewHolder item = mPendingAdditions.get(i); 607 | View view = item.itemView; 608 | ViewCompat.setAlpha(view, 1); 609 | dispatchAddFinished(item); 610 | mPendingAdditions.remove(i); 611 | } 612 | count = mPendingChanges.size(); 613 | for (int i = count - 1; i >= 0; i--) { 614 | endChangeAnimationIfNecessary(mPendingChanges.get(i)); 615 | } 616 | mPendingChanges.clear(); 617 | if (!isRunning()) { 618 | return; 619 | } 620 | 621 | int listCount = mMovesList.size(); 622 | for (int i = listCount - 1; i >= 0; i--) { 623 | ArrayList moves = mMovesList.get(i); 624 | count = moves.size(); 625 | for (int j = count - 1; j >= 0; j--) { 626 | MoveInfo moveInfo = moves.get(j); 627 | ViewHolder item = moveInfo.holder; 628 | View view = item.itemView; 629 | ViewCompat.setTranslationY(view, 0); 630 | ViewCompat.setTranslationX(view, 0); 631 | dispatchMoveFinished(moveInfo.holder); 632 | moves.remove(j); 633 | if (moves.isEmpty()) { 634 | mMovesList.remove(moves); 635 | } 636 | } 637 | } 638 | listCount = mAdditionsList.size(); 639 | for (int i = listCount - 1; i >= 0; i--) { 640 | ArrayList additions = mAdditionsList.get(i); 641 | count = additions.size(); 642 | for (int j = count - 1; j >= 0; j--) { 643 | ViewHolder item = additions.get(j); 644 | View view = item.itemView; 645 | ViewCompat.setAlpha(view, 1); 646 | dispatchAddFinished(item); 647 | additions.remove(j); 648 | if (additions.isEmpty()) { 649 | mAdditionsList.remove(additions); 650 | } 651 | } 652 | } 653 | listCount = mChangesList.size(); 654 | for (int i = listCount - 1; i >= 0; i--) { 655 | ArrayList changes = mChangesList.get(i); 656 | count = changes.size(); 657 | for (int j = count - 1; j >= 0; j--) { 658 | endChangeAnimationIfNecessary(changes.get(j)); 659 | if (changes.isEmpty()) { 660 | mChangesList.remove(changes); 661 | } 662 | } 663 | } 664 | 665 | cancelAll(mRemoveAnimations); 666 | cancelAll(mMoveAnimations); 667 | cancelAll(mAddAnimations); 668 | cancelAll(mChangeAnimations); 669 | 670 | dispatchAnimationsFinished(); 671 | } 672 | 673 | void cancelAll(List viewHolders) { 674 | for (int i = viewHolders.size() - 1; i >= 0; i--) { 675 | ViewCompat.animate(viewHolders.get(i).itemView).cancel(); 676 | } 677 | } 678 | 679 | private static class VpaListenerAdapter implements ViewPropertyAnimatorListener { 680 | @Override 681 | public void onAnimationStart(View view) {} 682 | 683 | @Override 684 | public void onAnimationEnd(View view) {} 685 | 686 | @Override 687 | public void onAnimationCancel(View view) {} 688 | } 689 | 690 | public int px2dp(float pxValue){ 691 | final float scale = mContext.getResources().getDisplayMetrics().density; 692 | return (int)(pxValue/scale+0.5f); 693 | } 694 | } 695 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/deadtime_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/point1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/point2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/point3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 17 | 18 | 19 | 25 |