├── .gitignore ├── AndroidSortAnimation-develop ├── .gitignore ├── .idea │ ├── caches │ │ └── build_file_checksums.ser │ ├── codeStyles │ │ └── Project.xml │ ├── gradle.xml │ ├── misc.xml │ ├── modules.xml │ ├── runConfigurations.xml │ └── vcs.xml ├── README.md ├── app │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ ├── release │ │ ├── app-release.apk │ │ └── output.json │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── ukhanoff │ │ │ └── bubblesort │ │ │ ├── common │ │ │ ├── AlgorithmAnimationListener.java │ │ │ ├── AlgorithmStepsInterface.java │ │ │ ├── AnimationScenarioItem.java │ │ │ └── AnimationsCoordinator.java │ │ │ ├── ui │ │ │ ├── activity │ │ │ │ └── SortActivity.java │ │ │ ├── customview │ │ │ │ └── BubbleView.java │ │ │ └── fragment │ │ │ │ └── SortFragment.java │ │ │ └── util │ │ │ └── Util.java │ │ └── res │ │ ├── anim │ │ └── first_swap.xml │ │ ├── layout │ │ ├── activity_sort.xml │ │ ├── activity_sorting.xml │ │ ├── fragment_sort.xml │ │ └── sorting_main_fragment_view.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── Note.md ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ └── pri │ │ └── weiqiang │ │ └── sortanimation │ │ ├── algorithm │ │ ├── Sort.java │ │ └── SortArrayList.java │ │ ├── animation │ │ ├── AlgorithmAnimationListener.java │ │ ├── AlgorithmStepsInterface.java │ │ ├── AnimationScenarioItem.java │ │ ├── AnimationsCoordinator.java │ │ ├── MergeAnimationListener.java │ │ ├── MergeAnimationScenarioItem.java │ │ ├── MergeAnimationsCoordinator.java │ │ └── MergeStepsInterface.java │ │ ├── constant │ │ └── Constant.java │ │ ├── ui │ │ ├── activity │ │ │ ├── CodeActivity.java │ │ │ └── SortActivity.java │ │ ├── customview │ │ │ └── RectView.java │ │ └── fragment │ │ │ └── SortFragment.java │ │ └── util │ │ └── Util.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ ├── activity_code.xml │ ├── activity_sort.xml │ ├── fragment_sort.xml │ └── view_line.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── array.xml │ ├── colors.xml │ ├── dimens.xml │ ├── ic_launcher_background.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshot ├── bubble_ukhanoff.gif ├── heap.gif ├── heer.gif ├── insert.gif ├── merge.gif ├── pubble.gif ├── quick.gif └── select.gif └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches/build_file_checksums.ser 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | .DS_Store 9 | /build 10 | /captures 11 | .externalNativeBuild 12 | .idea 13 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/54wall/SortAnimation/e76667db2747ab6bd3622b5554f51a3c497db364/AndroidSortAnimation-develop/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 36 | 37 | 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 | 94 | 106 | 107 | 108 | 109 | 110 | 111 | 113 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/README.md: -------------------------------------------------------------------------------- 1 | # AndroidSortAnimation 2 | 3 | Small App to visualize bubble sort algorithm. This App is no more than test assignment which just demonstrates how to work with animations, views etc. 4 | 5 | ## VideoDemo 6 | 7 | https://www.dropbox.com/s/bs6nn646udp85na/demo_sort.mov?dl=0 8 | 9 | ## Usage 10 | 11 | Clone the project 12 | ``` 13 | git clone https://github.com/ukhanoff/AndroidSortAnimation.git 14 | ``` 15 | Open cloned project in your Android Studio (we recommend to use version 2.2+). 16 | 17 | Push run button in Android Studio. And... thats all! Enjoy. 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | buildToolsVersion '28.0.3' 6 | defaultConfig { 7 | applicationId "com.ukhanoff.bubblesort" 8 | minSdkVersion 21 9 | targetSdkVersion 28 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | implementation fileTree(dir: 'libs', include: ['*.jar']) 24 | implementation 'com.android.support:appcompat-v7:28.0.0' 25 | 26 | } 27 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/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/ukhanoff/Library/Android/sdk/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 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/app/release/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/54wall/SortAnimation/e76667db2747ab6bd3622b5554f51a3c497db364/AndroidSortAnimation-develop/app/release/app-release.apk -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/app/release/output.json: -------------------------------------------------------------------------------- 1 | [{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/app/src/main/java/com/ukhanoff/bubblesort/common/AlgorithmAnimationListener.java: -------------------------------------------------------------------------------- 1 | package com.ukhanoff.bubblesort.common; 2 | 3 | /** 4 | * Created by ukhanoff on 2/7/17. 5 | */ 6 | 7 | public interface AlgorithmAnimationListener { 8 | void onSwapStepAnimationEnd(int endedPosition); 9 | } 10 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/app/src/main/java/com/ukhanoff/bubblesort/common/AlgorithmStepsInterface.java: -------------------------------------------------------------------------------- 1 | package com.ukhanoff.bubblesort.common; 2 | 3 | /** 4 | * Created by ukhanoff on 2/6/17. 5 | */ 6 | 7 | public interface AlgorithmStepsInterface { 8 | 9 | /** 10 | * Visualizes step, when elements should change their places with each other 11 | * 交换位置 12 | * @param position position of the firs element, which should be changed 13 | * @param isBubbleOnFinalPlace set true, when element after swapping is on the right place and his position is final 14 | */ 15 | void showSwapStep(int position, boolean isBubbleOnFinalPlace); 16 | 17 | /** 18 | * Visualizes step, when elements should stay on the same places; 19 | * 不交换位置 20 | * @param position position of the firs element 21 | * @param isBubbleOnFinalPlace set true, when element on position+1 is on the right place and his position is final 22 | */ 23 | void showNonSwapStep(int position, boolean isBubbleOnFinalPlace); 24 | 25 | /** 26 | * Call when last item was sorted. Notifies user that sorting is finished. 27 | * 结束全部动画,小球将处于最后排序完成后的颜色 28 | */ 29 | void showFinish(); 30 | 31 | /** 32 | * Cancel all current animations 33 | */ 34 | void cancelAllVisualisations(); 35 | } 36 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/app/src/main/java/com/ukhanoff/bubblesort/common/AnimationScenarioItem.java: -------------------------------------------------------------------------------- 1 | package com.ukhanoff.bubblesort.common; 2 | 3 | /** 4 | * Holds data about what kind of animation should be shown. 5 | */ 6 | 7 | public class AnimationScenarioItem { 8 | 9 | private boolean isShouldBeSwapped; 10 | private int firstItemPosition; 11 | 12 | private boolean isFinalPlace; 13 | 14 | public AnimationScenarioItem(boolean isShouldBeSwapped, int itemPosition, boolean isFinalPlace) { 15 | this.isShouldBeSwapped = isShouldBeSwapped; 16 | this.firstItemPosition = itemPosition; 17 | this.isFinalPlace = isFinalPlace; 18 | } 19 | 20 | public boolean isShouldBeSwapped() { 21 | return isShouldBeSwapped; 22 | } 23 | 24 | public int getAnimationViewItemPosition() { 25 | return firstItemPosition; 26 | } 27 | 28 | public boolean isFinalPlace() { 29 | return isFinalPlace; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/app/src/main/java/com/ukhanoff/bubblesort/common/AnimationsCoordinator.java: -------------------------------------------------------------------------------- 1 | package com.ukhanoff.bubblesort.common; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.ValueAnimator; 6 | import android.util.Log; 7 | import android.view.ViewGroup; 8 | import android.widget.Toast; 9 | 10 | import com.ukhanoff.bubblesort.R; 11 | import com.ukhanoff.bubblesort.ui.customview.BubbleView; 12 | 13 | import java.util.ArrayList; 14 | 15 | /** 16 | * Handles all animation which should be done in scope of sorting algorithm visualization. 17 | */ 18 | 19 | public class AnimationsCoordinator implements AlgorithmStepsInterface { 20 | 21 | private String TAG = AnimationsCoordinator.class.getSimpleName(); 22 | private ViewGroup bubblesContainer; 23 | private ArrayList listeners; 24 | private ValueAnimator blinkAnimation; 25 | 26 | public AnimationsCoordinator(ViewGroup bubblesContainer) { 27 | Log.e(TAG, "AnimationsCoordinator"); 28 | this.bubblesContainer = bubblesContainer; 29 | } 30 | 31 | @Override 32 | public void showSwapStep(final int position, final boolean isBubbleOnFinalPosition) { 33 | Log.e(TAG, "showSwapStep position:"+position+",isBubbleOnFinalPosition:"+isBubbleOnFinalPosition); 34 | if (bubblesContainer != null && bubblesContainer.getChildCount() > 0 && bubblesContainer.getChildCount() > position + 1) { 35 | final BubbleView tempView = (BubbleView) bubblesContainer.getChildAt(position); 36 | final BubbleView nextTempView = (BubbleView) bubblesContainer.getChildAt(position + 1); 37 | 38 | //值为0到5,偶数为选中状态,蓝色,基数为未选中状态,粉色,所以,视觉表现为闪烁2次 39 | blinkAnimation = ValueAnimator.ofInt(0, 5); 40 | blinkAnimation.setDuration(1000);//动画时长为2s 41 | blinkAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 42 | @Override 43 | public void onAnimationUpdate(ValueAnimator animation) { 44 | int value = ((Integer) animation.getAnimatedValue()).intValue(); 45 | // Log.e(TAG,"showSwapStep addUpdateListener value:"+value); 46 | if (value % 2 == 0) { 47 | tempView.setBubbleSelected(false); 48 | nextTempView.setBubbleSelected(false); 49 | } else { 50 | tempView.setBubbleSelected(true); 51 | nextTempView.setBubbleSelected(true); 52 | } 53 | } 54 | }); 55 | 56 | 57 | blinkAnimation.addListener(new AnimatorListenerAdapter() { 58 | @Override 59 | public void onAnimationEnd(Animator animation) {// 60 | super.onAnimationEnd(animation); 61 | tempView.setBubbleSelected(false); 62 | tempView.setBubbleIsOnFinalPlace(isBubbleOnFinalPosition); 63 | nextTempView.setBubbleSelected(false); 64 | bubblesContainer.removeView(tempView); 65 | bubblesContainer.addView(tempView, position + 1); 66 | 67 | notifySwapStepAnimationEnd(position); 68 | } 69 | }); 70 | 71 | blinkAnimation.start(); 72 | } 73 | } 74 | 75 | @Override 76 | public void showNonSwapStep(final int position, final boolean isBubbleOnFinalPlace) { 77 | Log.e(TAG, "showNonSwapStep position:"+position+",isBubbleOnFinalPosition:"+isBubbleOnFinalPlace); 78 | if (bubblesContainer != null && bubblesContainer.getChildCount() > 0 && bubblesContainer.getChildCount() > position + 1) { 79 | final BubbleView tempView = (BubbleView) bubblesContainer.getChildAt(position); 80 | final BubbleView nextTempView = (BubbleView) bubblesContainer.getChildAt(position + 1); 81 | 82 | //值为0到7,偶数为选中状态,蓝色,基数为未选中状态,粉色,所以,视觉表现为闪烁3次 83 | blinkAnimation = ValueAnimator.ofInt(0, 7); 84 | blinkAnimation.setDuration(1200); 85 | blinkAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 86 | @Override 87 | public void onAnimationUpdate(ValueAnimator animation) { 88 | int value = ((Integer) animation.getAnimatedValue()).intValue(); 89 | // Log.e(TAG,"showNonSwapStep addUpdateListener value:"+value); 90 | if (value % 2 == 0) { 91 | tempView.setBubbleSelected(false); 92 | nextTempView.setBubbleSelected(false); 93 | } else { 94 | tempView.setBubbleSelected(true); 95 | nextTempView.setBubbleSelected(true); 96 | } 97 | } 98 | }); 99 | 100 | blinkAnimation.start(); 101 | blinkAnimation.addListener(new AnimatorListenerAdapter() { 102 | @Override 103 | public void onAnimationEnd(Animator animation) { 104 | super.onAnimationEnd(animation); 105 | tempView.setBubbleSelected(false); 106 | nextTempView.setBubbleSelected(false); 107 | nextTempView.setBubbleIsOnFinalPlace(isBubbleOnFinalPlace); 108 | 109 | notifySwapStepAnimationEnd(position); 110 | } 111 | }); 112 | } 113 | } 114 | 115 | @Override 116 | public void showFinish() { 117 | Log.e(TAG, "showFinish"); 118 | if (bubblesContainer != null && bubblesContainer.getChildCount() > 0) { 119 | ((BubbleView) bubblesContainer.getChildAt(0)).setBubbleIsOnFinalPlace(true); 120 | } 121 | Toast.makeText(bubblesContainer.getContext(), R.string.sort_finish, Toast.LENGTH_SHORT).show(); 122 | } 123 | 124 | @Override 125 | public void cancelAllVisualisations() { 126 | Log.e(TAG, "cancelAllVisualisations"); 127 | if (blinkAnimation != null) { 128 | blinkAnimation.removeAllListeners(); 129 | blinkAnimation.cancel(); 130 | bubblesContainer.clearAnimation(); 131 | } 132 | } 133 | 134 | private void notifySwapStepAnimationEnd(int position) { 135 | Log.e(TAG, "notifySwapStepAnimationEnd:"+position); 136 | if (listeners != null && !listeners.isEmpty()) { 137 | int numListeners = listeners.size(); 138 | //这里直接只用listeners.get(0).onSwapStepAnimationEnd(position);即可,因为addListener(AlgorithmAnimationListener listener)仅使用了一次 139 | Log.e(TAG, "numListeners:"+numListeners); 140 | for (int i = 0; i < numListeners; ++i) { 141 | Log.e(TAG, "onSwapStepAnimationEnd i:"+i+",position:"+position); 142 | //将会调用SortFragment: onSwapStepAnimationEnd中实现具体的方法 143 | listeners.get(i).onSwapStepAnimationEnd(position); 144 | } 145 | } 146 | } 147 | 148 | public void addListener(AlgorithmAnimationListener listener) { 149 | Log.e(TAG, "addListener"); 150 | if (listeners == null) { 151 | listeners = new ArrayList<>(); 152 | } 153 | listeners.add(listener); 154 | } 155 | 156 | public void removeListener(Animator.AnimatorListener listener) { 157 | Log.e(TAG, "removeListener"); 158 | if (listeners == null) { 159 | return; 160 | } 161 | listeners.remove(listener); 162 | if (listeners.size() == 0) { 163 | listeners = null; 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/app/src/main/java/com/ukhanoff/bubblesort/ui/activity/SortActivity.java: -------------------------------------------------------------------------------- 1 | package com.ukhanoff.bubblesort.ui.activity; 2 | 3 | import android.os.Bundle; 4 | import androidx.appcompat.app.AppCompatActivity; 5 | 6 | import com.ukhanoff.bubblesort.R; 7 | import com.ukhanoff.bubblesort.ui.fragment.SortFragment; 8 | 9 | public class SortActivity extends AppCompatActivity { 10 | 11 | @Override 12 | protected void onCreate(Bundle savedInstanceState) { 13 | super.onCreate(savedInstanceState); 14 | setContentView(R.layout.activity_sort); 15 | 16 | if (savedInstanceState == null) { 17 | SortFragment fragment = new SortFragment(); 18 | getSupportFragmentManager().beginTransaction().add(R.id.fl_container, fragment) 19 | .commit(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/app/src/main/java/com/ukhanoff/bubblesort/ui/customview/BubbleView.java: -------------------------------------------------------------------------------- 1 | package com.ukhanoff.bubblesort.ui.customview; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Rect; 8 | import androidx.appcompat.widget.AppCompatImageView; 9 | import android.util.AttributeSet; 10 | 11 | import com.ukhanoff.bubblesort.R; 12 | 13 | import static com.ukhanoff.bubblesort.ui.fragment.SortFragment.PADDING; 14 | 15 | /** 16 | * This is custom ImageView which could draw a "Bubble with a number inside". 17 | */ 18 | 19 | public class BubbleView extends AppCompatImageView { 20 | public static final int START_X_POS = 25; 21 | public static final int TEXT_BASELINE_Y = 105; 22 | public static final int BOTTOM_POS = 120; 23 | public static final int TOP_POS = 60; 24 | public static final float TEXT_SIZE = 45f; 25 | //方法2 直接new 避免avoid object allocation during draw/layout operations (prelocate and reuse instead) 26 | // Paint paint = new Paint(Paint.LINEAR_TEXT_FLAG); 27 | // Rect bounds = new Rect(); 28 | Paint paint; 29 | Rect bounds; 30 | private String TAG = BubbleView.class.getSimpleName(); 31 | private Integer valueToDraw; 32 | private boolean isSelected; 33 | private boolean isOnFinalPlace; 34 | 35 | public BubbleView(Context context) { 36 | this(context, null); 37 | init(); 38 | } 39 | 40 | public BubbleView(Context context, AttributeSet attrs) { 41 | this(context, attrs, 0); 42 | init(); 43 | } 44 | 45 | public BubbleView(Context context, AttributeSet attrs, int defStyleAttr) { 46 | super(context, attrs, defStyleAttr); 47 | init(); 48 | } 49 | 50 | private void init() { 51 | paint = new Paint(Paint.LINEAR_TEXT_FLAG); 52 | paint.setAntiAlias(true); 53 | paint.setTextSize(TEXT_SIZE); 54 | bounds = new Rect(); 55 | } 56 | 57 | @Override 58 | protected void onDraw(Canvas canvas) { 59 | // Log.e(TAG,"onDraw()"); 60 | super.onDraw(canvas); 61 | if (valueToDraw != null) { 62 | String text = valueToDraw.toString(); 63 | paint.getTextBounds(text, 0, text.length(), bounds); 64 | if (isOnFinalPlace) { 65 | paint.setColor(getResources().getColor(R.color.colorPrimaryDark)); 66 | } else { 67 | if (isSelected) { 68 | paint.setColor(getResources().getColor(R.color.colorIndigo)); 69 | } else { 70 | paint.setColor(getResources().getColor(R.color.colorAccent)); 71 | } 72 | } 73 | canvas.drawOval(0, TOP_POS, bounds.width() + PADDING, BOTTOM_POS, paint); 74 | paint.setColor(Color.WHITE); 75 | canvas.drawText(text, START_X_POS, TEXT_BASELINE_Y, paint); 76 | } 77 | } 78 | 79 | /** 80 | * Draws a number as a bitmap inside of the bubble circle. 81 | * 在小球中央绘制数字 82 | * @param numberValueToDraw value which should appears in the center of {@link BubbleView} 83 | */ 84 | public void setNumber(Integer numberValueToDraw) { 85 | valueToDraw = numberValueToDraw; 86 | invalidate(); 87 | } 88 | 89 | /** 90 | * Background color of bubble will be changed to dark blue. 91 | * 设置小球处于未选中状态,背景颜色将作出相应改变 92 | * @param isOnFinalPlace 93 | */ 94 | public void setBubbleIsOnFinalPlace(boolean isOnFinalPlace) { 95 | this.isOnFinalPlace = isOnFinalPlace; 96 | invalidate(); 97 | } 98 | 99 | public boolean isBubbleSelected() { 100 | return isSelected; 101 | } 102 | 103 | /** 104 | * Background color will be changed to blue if true 105 | * 设置小球处于选中状态,背景颜色将作出相应改变 106 | * 107 | * @param isSelected 108 | */ 109 | public void setBubbleSelected(boolean isSelected) { 110 | this.isSelected = isSelected; 111 | invalidate(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/app/src/main/java/com/ukhanoff/bubblesort/ui/fragment/SortFragment.java: -------------------------------------------------------------------------------- 1 | package com.ukhanoff.bubblesort.ui.fragment; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Paint; 5 | import android.graphics.Rect; 6 | import android.os.Bundle; 7 | import androidx.annotation.Nullable; 8 | import androidx.fragment.app.Fragment; 9 | import android.text.TextUtils; 10 | import android.util.Log; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.Button; 15 | import android.widget.EditText; 16 | import android.widget.LinearLayout; 17 | import android.widget.Toast; 18 | 19 | import com.ukhanoff.bubblesort.R; 20 | import com.ukhanoff.bubblesort.common.AlgorithmAnimationListener; 21 | import com.ukhanoff.bubblesort.common.AnimationScenarioItem; 22 | import com.ukhanoff.bubblesort.common.AnimationsCoordinator; 23 | import com.ukhanoff.bubblesort.ui.customview.BubbleView; 24 | import com.ukhanoff.bubblesort.util.Util; 25 | 26 | import java.util.ArrayList; 27 | 28 | /** 29 | * Main fragment where sorting visualisation appears 30 | */ 31 | 32 | public class SortFragment extends Fragment { 33 | public static final int PADDING = 50; 34 | public static final int BUBBLE_MARGIN = 4; 35 | private String TAG = SortFragment.class.getSimpleName(); 36 | private EditText mEtInput; 37 | private Button mBtnStart; 38 | private boolean isAnimationRunning; 39 | private int scenarioItemIndex = 0; 40 | private LinearLayout mLlContainer; 41 | private AnimationsCoordinator animationsCoordinator; 42 | private ArrayList animationioList; 43 | View.OnClickListener buttonClickListener = new View.OnClickListener() { 44 | 45 | @Override 46 | public void onClick(View v) { 47 | String inputUserArray = mEtInput.getText().toString(); 48 | if (!TextUtils.isEmpty(inputUserArray)) { 49 | resetPreviousData(); 50 | animationioList = new ArrayList<>(); 51 | ArrayList integerArrayList = new ArrayList<>(convertToIntArray(inputUserArray)); 52 | drawBubbles(integerArrayList); 53 | generateSortScenario(integerArrayList); 54 | Log.e(TAG, "runAnimationIteration onClick"); 55 | runAnimationIteration(); 56 | } else { 57 | Toast.makeText(getContext(), R.string.empty_field_warning, Toast.LENGTH_LONG).show(); 58 | } 59 | } 60 | }; 61 | 62 | private void resetPreviousData() { 63 | Log.e(TAG, "resetPreviousData"); 64 | if (isAnimationRunning && animationsCoordinator != null) { 65 | animationsCoordinator.cancelAllVisualisations(); 66 | isAnimationRunning = false; 67 | } 68 | scenarioItemIndex = 0; 69 | } 70 | 71 | @Override 72 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 73 | Log.e(TAG, "onCreateView"); 74 | View view = inflater.inflate(R.layout.fragment_sort, container, false); 75 | mEtInput = view.findViewById(R.id.et_input); 76 | mBtnStart = view.findViewById(R.id.btn_start); 77 | mBtnStart.setOnClickListener(buttonClickListener); 78 | mLlContainer = view.findViewById(R.id.ll_container); 79 | animationsCoordinator = new AnimationsCoordinator(mLlContainer); 80 | animationsCoordinator.addListener(new AlgorithmAnimationListener() { 81 | @Override 82 | public void onSwapStepAnimationEnd(int endedPosition) { 83 | Log.e(TAG, "onSwapStepAnimationEnd endedPosition:"+endedPosition); 84 | runAnimationIteration(); 85 | } 86 | }); 87 | 88 | mBtnStart.callOnClick(); 89 | return view; 90 | } 91 | 92 | private void runAnimationIteration() { 93 | Log.e(TAG, "runAnimationIteration"); 94 | isAnimationRunning = true; 95 | if (animationioList != null && animationioList.size() == scenarioItemIndex) { 96 | animationsCoordinator.showFinish(); 97 | return; 98 | } 99 | if (animationioList != null && !animationioList.isEmpty() && animationioList.size() > scenarioItemIndex) { 100 | AnimationScenarioItem animationStep = animationioList.get(scenarioItemIndex); 101 | scenarioItemIndex++; 102 | if (animationStep.isShouldBeSwapped()) { 103 | animationsCoordinator.showSwapStep(animationStep.getAnimationViewItemPosition(), animationStep.isFinalPlace()); 104 | } else { 105 | animationsCoordinator.showNonSwapStep(animationStep.getAnimationViewItemPosition(), animationStep.isFinalPlace()); 106 | } 107 | } 108 | 109 | } 110 | 111 | private void swap(final ArrayList list, final int inner) { 112 | Log.e(TAG, "swap"); 113 | int temp = list.get(inner); 114 | list.set(inner, list.get(inner + 1)); 115 | list.set(inner + 1, temp); 116 | } 117 | 118 | private void drawBubbles(ArrayList listToDraw) { 119 | Log.e(TAG, "drawBubbles"); 120 | if (mLlContainer != null) { 121 | mLlContainer.removeAllViews(); 122 | mLlContainer.clearAnimation(); 123 | } 124 | 125 | LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); 126 | int marginInPx = Util.dpToPx(getContext(), BUBBLE_MARGIN); 127 | lp.setMargins(0, 0, marginInPx, 0); 128 | 129 | int pos = 0; 130 | for (Integer currentIntValue : listToDraw) { 131 | BubbleView bubbleView = new BubbleView(getContext()); 132 | bubbleView.setImageBitmap(createCalculatedBitmap(currentIntValue)); 133 | bubbleView.setMinimumHeight(250); 134 | bubbleView.setNumber(currentIntValue); 135 | bubbleView.setId(pos); 136 | if (mLlContainer != null) { 137 | mLlContainer.addView(bubbleView, lp); 138 | } 139 | pos++; 140 | } 141 | } 142 | 143 | /** 144 | * Calculates size of ImageView which would be generated with current text value. 145 | * 146 | * @param currentIntValue 147 | * @return empty bitmap with calculated size 148 | */ 149 | private Bitmap createCalculatedBitmap(Integer currentIntValue) { 150 | Log.e(TAG, "createCalculatedBitmap"); 151 | final Rect bounds = new Rect(); 152 | Paint paint = new Paint(Paint.LINEAR_TEXT_FLAG); 153 | paint.setTextSize(BubbleView.TEXT_SIZE); 154 | paint.getTextBounds(currentIntValue.toString(), 0, currentIntValue.toString().length(), bounds); 155 | return Bitmap.createBitmap(bounds.width() + PADDING, bounds.height() + PADDING, Bitmap.Config.ALPHA_8); 156 | } 157 | 158 | private ArrayList convertToIntArray(String inputUserArray) { 159 | Log.e(TAG, "convertToIntArray"); 160 | ArrayList parsedUserArray = new ArrayList<>(); 161 | String[] stringArray = inputUserArray.split(","); 162 | int numberOfElements = stringArray.length; 163 | for (int i = 0; i < numberOfElements; i++) { 164 | if (!TextUtils.isEmpty(stringArray[i])) { 165 | parsedUserArray.add(Integer.parseInt(stringArray[i])); 166 | } 167 | } 168 | return parsedUserArray; 169 | 170 | } 171 | 172 | private ArrayList generateSortScenario(ArrayList unsortedValues) { 173 | Log.e(TAG, "generateSortScenario"); 174 | ArrayList values = new ArrayList<>(unsortedValues); 175 | boolean isLastInLoop; 176 | for (int i = 0; i < values.size() - 1; i++) { 177 | for (int j = 0; j < values.size() - i - 1; j++) { 178 | if (j == values.size() - i - 2) { 179 | isLastInLoop = true; 180 | } else { 181 | isLastInLoop = false; 182 | } 183 | if (values.get(j) > values.get(j + 1)) { 184 | swap(values, j); 185 | animationioList.add(new AnimationScenarioItem(true, j, isLastInLoop)); 186 | } else { 187 | animationioList.add(new AnimationScenarioItem(false, j, isLastInLoop)); 188 | } 189 | } 190 | } 191 | return values; 192 | } 193 | 194 | } 195 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/app/src/main/java/com/ukhanoff/bubblesort/util/Util.java: -------------------------------------------------------------------------------- 1 | package com.ukhanoff.bubblesort.util; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * Typical android helpful staff lives here. 7 | */ 8 | 9 | public class Util { 10 | 11 | public static int dpToPx(Context context, int sizeInDp) { 12 | float scale = context.getResources().getDisplayMetrics().density; 13 | return (int) (sizeInDp * scale + 0.5f); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/app/src/main/res/anim/first_swap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/app/src/main/res/layout/activity_sort.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/app/src/main/res/layout/activity_sorting.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /AndroidSortAnimation-develop/app/src/main/res/layout/fragment_sort.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 26 | 27 |