├── .gitattributes ├── .gitignore ├── .gradle └── 2.14.1 │ ├── taskArtifacts │ ├── cache.properties │ ├── cache.properties.lock │ ├── fileHashes.bin │ ├── fileSnapshots.bin │ ├── fileSnapshotsToTreeSnapshotsIndex.bin │ └── taskArtifacts.bin │ └── tasks │ └── _app_compileDebugJavaWithJavac │ ├── localClassSetAnalysis │ ├── localClassSetAnalysis.bin │ └── localClassSetAnalysis.lock │ └── localJarClasspathSnapshot │ ├── localJarClasspathSnapshot.bin │ └── localJarClasspathSnapshot.lock ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── workspace.xml ├── GalleryGridView.iml ├── README.md ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── net │ │ └── darkion │ │ └── gallerygridview │ │ ├── FlyOutMenu.java │ │ ├── GridAdapter.java │ │ ├── GridViewItem.java │ │ ├── ImageButtonWithListener.java │ │ ├── LogDecelerateInterpolator.java │ │ ├── MainActivity.java │ │ └── MultiSelectGridView.java │ └── res │ ├── drawable │ ├── card.xml │ ├── circle_check.xml │ ├── ic_cancel.xml │ ├── ic_check.xml │ ├── ic_delete.xml │ ├── ic_rotate_left.xml │ ├── ic_rotate_right.xml │ └── ic_share.xml │ ├── layout │ ├── activity_main.xml │ ├── flyout_two_buttons_options.xml │ ├── grid_view_adapter_item.xml │ └── grid_view_item_checkbox.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-land │ └── int.xml │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── int.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── build ├── generated │ ├── mockable-android-23.jar │ └── mockable-android-24.jar └── intermediates │ └── dex-cache │ └── cache.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── local.properties ├── preview.gif └── settings.gradle /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | -------------------------------------------------------------------------------- /.gradle/2.14.1/taskArtifacts/cache.properties: -------------------------------------------------------------------------------- 1 | #Sun Dec 18 22:29:42 SGT 2016 2 | -------------------------------------------------------------------------------- /.gradle/2.14.1/taskArtifacts/cache.properties.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DarkionAvey/DragSelectionGridView/3c25bd1170d18e4cedabc7a9cdc7444a7bf02d2e/.gradle/2.14.1/taskArtifacts/cache.properties.lock -------------------------------------------------------------------------------- /.gradle/2.14.1/taskArtifacts/fileHashes.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DarkionAvey/DragSelectionGridView/3c25bd1170d18e4cedabc7a9cdc7444a7bf02d2e/.gradle/2.14.1/taskArtifacts/fileHashes.bin -------------------------------------------------------------------------------- /.gradle/2.14.1/taskArtifacts/fileSnapshots.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DarkionAvey/DragSelectionGridView/3c25bd1170d18e4cedabc7a9cdc7444a7bf02d2e/.gradle/2.14.1/taskArtifacts/fileSnapshots.bin -------------------------------------------------------------------------------- /.gradle/2.14.1/taskArtifacts/fileSnapshotsToTreeSnapshotsIndex.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DarkionAvey/DragSelectionGridView/3c25bd1170d18e4cedabc7a9cdc7444a7bf02d2e/.gradle/2.14.1/taskArtifacts/fileSnapshotsToTreeSnapshotsIndex.bin -------------------------------------------------------------------------------- /.gradle/2.14.1/taskArtifacts/taskArtifacts.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DarkionAvey/DragSelectionGridView/3c25bd1170d18e4cedabc7a9cdc7444a7bf02d2e/.gradle/2.14.1/taskArtifacts/taskArtifacts.bin -------------------------------------------------------------------------------- /.gradle/2.14.1/tasks/_app_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DarkionAvey/DragSelectionGridView/3c25bd1170d18e4cedabc7a9cdc7444a7bf02d2e/.gradle/2.14.1/tasks/_app_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.bin -------------------------------------------------------------------------------- /.gradle/2.14.1/tasks/_app_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DarkionAvey/DragSelectionGridView/3c25bd1170d18e4cedabc7a9cdc7444a7bf02d2e/.gradle/2.14.1/tasks/_app_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.lock -------------------------------------------------------------------------------- /.gradle/2.14.1/tasks/_app_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DarkionAvey/DragSelectionGridView/3c25bd1170d18e4cedabc7a9cdc7444a7bf02d2e/.gradle/2.14.1/tasks/_app_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.bin -------------------------------------------------------------------------------- /.gradle/2.14.1/tasks/_app_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DarkionAvey/DragSelectionGridView/3c25bd1170d18e4cedabc7a9cdc7444a7bf02d2e/.gradle/2.14.1/tasks/_app_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.lock -------------------------------------------------------------------------------- /.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/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | Android API 23 Platform 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /GalleryGridView.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DragSelectionGridView 2 | 3 | 4 | 5 | *Recreation of Google Images app's gridview drag selection* 6 | 7 | 8 | A grid view that supports drag selection gestures. The app also demonstrates the use of a fly-out context menu that provides a better UX than using contextual action bar since it is near users' fingers (once they finish dragging) - and it looks better. 9 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 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 | 104 | 105 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | defaultConfig { 7 | applicationId "net.darkion.gallerygridview" 8 | minSdkVersion 21 9 | targetSdkVersion 23 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(dir: 'libs', include: ['*.jar']) 23 | } 24 | -------------------------------------------------------------------------------- /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 F:\Program Files\Android\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 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/net/darkion/gallerygridview/FlyOutMenu.java: -------------------------------------------------------------------------------- 1 | package net.darkion.gallerygridview; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.content.Context; 6 | import android.transition.AutoTransition; 7 | import android.transition.TransitionManager; 8 | import android.util.AttributeSet; 9 | import android.view.View; 10 | import android.widget.Button; 11 | import android.widget.LinearLayout; 12 | 13 | /** 14 | * Created by DarkionAvey 15 | */ 16 | 17 | public class FlyOutMenu extends LinearLayout { 18 | public FlyOutMenu(Context context) { 19 | super(context); 20 | } 21 | 22 | public FlyOutMenu(Context context, AttributeSet attrs) { 23 | super(context, attrs); 24 | } 25 | 26 | public FlyOutMenu(Context context, AttributeSet attrs, int defStyleAttr) { 27 | super(context, attrs, defStyleAttr); 28 | } 29 | 30 | 31 | public enum FlyOutMenus { 32 | DELETE, SHARE, ROTATE 33 | } 34 | 35 | OnClickListener parkedOnClickListener; 36 | ImageButtonWithListener icon; 37 | 38 | public void setFlyOutMenu(int icon, final FlyOutMenus menus) { 39 | AutoTransition transition = new AutoTransition(); 40 | transition.setInterpolator(GridViewItem.interpolator); 41 | transition.excludeTarget(icon, true); 42 | transition.setDuration(100); 43 | transition.excludeChildren(icon, true); 44 | TransitionManager.beginDelayedTransition(this); 45 | 46 | View child; 47 | for (int i = 0; i < getChildCount(); i++) { 48 | child = getChildAt(i); 49 | if (child.getId() != icon) { 50 | child.setVisibility(View.GONE); 51 | } else { 52 | if (child instanceof ImageButtonWithListener) { 53 | this.icon = (ImageButtonWithListener) child; 54 | parkedOnClickListener = this.icon.getOnClickListener(); 55 | child.setOnClickListener(new OnClickListener() { 56 | @Override 57 | public void onClick(View v) { 58 | goToMainFlyOutMenu(); 59 | } 60 | }); 61 | 62 | } 63 | } 64 | } 65 | final View attachedView = findViewById(net.darkion.gallerygridview.R.id.options); 66 | attachedView.setVisibility(View.VISIBLE); 67 | Button positive = (Button) attachedView.findViewById(net.darkion.gallerygridview.R.id.positive); 68 | Button negative = (Button) attachedView.findViewById(net.darkion.gallerygridview.R.id.negative); 69 | 70 | negative.setVisibility(View.VISIBLE); 71 | negative.setVisibility(View.VISIBLE); 72 | switch (menus) { 73 | case DELETE: 74 | positive.setText("Confirm"); 75 | negative.setText("Cancel"); 76 | break; 77 | 78 | 79 | } 80 | positive.setOnClickListener(new OnClickListener() { 81 | @Override 82 | public void onClick(View v) { 83 | switch (menus) { 84 | case DELETE: 85 | reset(); 86 | break; 87 | } 88 | } 89 | }); 90 | 91 | negative.setOnClickListener(new OnClickListener() { 92 | @Override 93 | public void onClick(View v) { 94 | goToMainFlyOutMenu(); 95 | } 96 | }); 97 | 98 | 99 | } 100 | 101 | public void goToMainFlyOutMenu() { 102 | 103 | TransitionManager.beginDelayedTransition(this); 104 | icon.setOnClickListener(parkedOnClickListener); 105 | for (int i = 0; i < getChildCount(); i++) { 106 | getChildAt(i).setVisibility(View.VISIBLE); 107 | } 108 | findViewById(net.darkion.gallerygridview.R.id.options).setVisibility(View.GONE); 109 | 110 | 111 | } 112 | 113 | public void show() { 114 | final float verticalSpace = getResources().getDimension(net.darkion.gallerygridview.R.dimen.activity_vertical_margin); 115 | if(getTranslationY()==-verticalSpace)return; 116 | animate().translationY(-verticalSpace).setInterpolator(GridViewItem.interpolator).start(); 117 | for (int i = 0; i < getChildCount(); i++) { 118 | getChildAt(i).setAlpha(0f); 119 | getChildAt(i).setTranslationY(20f); 120 | getChildAt(i).animate().alpha(1f).translationY(0f).setStartDelay((i + 1) * 50).setInterpolator(GridViewItem.interpolator).start(); 121 | } 122 | } 123 | 124 | public void reset() { 125 | animate().translationY(200).setDuration(300).setListener(new AnimatorListenerAdapter() { 126 | @Override 127 | public void onAnimationEnd(Animator animation) { 128 | super.onAnimationEnd(animation); 129 | View child; 130 | for (int i = 0; i < getChildCount(); i++) { 131 | child = getChildAt(i); 132 | if (net.darkion.gallerygridview.R.id.options == child.getId()) child.setVisibility(View.GONE); 133 | else { 134 | child.setVisibility(View.VISIBLE); 135 | } 136 | } 137 | } 138 | }).setInterpolator(GridViewItem.interpolator).start(); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /app/src/main/java/net/darkion/gallerygridview/GridAdapter.java: -------------------------------------------------------------------------------- 1 | package net.darkion.gallerygridview; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.BaseAdapter; 8 | 9 | import java.util.ArrayList; 10 | 11 | /** 12 | * Created by DarkionAvey 13 | */ 14 | 15 | public class GridAdapter extends BaseAdapter { 16 | private Context context; 17 | int numberOfItems; 18 | ArrayList checkedItems = new ArrayList<>(); 19 | 20 | public GridAdapter(Context context, int numberOfItems) { 21 | this.context = context; 22 | this.numberOfItems = numberOfItems; 23 | for (int i = 0; i < numberOfItems; i++) { 24 | checkedItems.add(false); 25 | } 26 | } 27 | 28 | 29 | 30 | public View getView(int position, View convertView, ViewGroup parent) { 31 | 32 | LayoutInflater inflater = (LayoutInflater) context 33 | .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 34 | if (convertView == null) { 35 | convertView = inflater.inflate(R.layout.grid_view_adapter_item, parent, false); 36 | } 37 | 38 | ((GridViewItem) convertView).setChecked(checkedItems.get(position), false); 39 | //// ImageView imageView = (ImageView) convertView.findViewById(R.id.image); 40 | 41 | convertView.setTag(position); 42 | convertView.requestLayout(); 43 | return convertView; 44 | } 45 | 46 | public ArrayList getCheckedItems() { 47 | ArrayList results = new ArrayList<>(); 48 | 49 | for (int x = 0; x < checkedItems.size(); x++) { 50 | if (checkedItems.get(x)) { 51 | results.add(x); 52 | } 53 | } 54 | return results; 55 | } 56 | 57 | public int getCheckedItemsNumber() { 58 | int i = 0; 59 | for (int x = 0; x < checkedItems.size(); x++) { 60 | if (checkedItems.get(x)) { 61 | i++; 62 | } 63 | } 64 | return i; 65 | } 66 | 67 | public boolean getCheckedItem(int position) { 68 | return checkedItems.get(position); 69 | } 70 | 71 | public void setCheckedItem(int position, boolean checked) { 72 | checkedItems.set(position, checked); 73 | } 74 | 75 | @Override 76 | public int getCount() { 77 | return numberOfItems; 78 | } 79 | 80 | @Override 81 | public Object getItem(int position) { 82 | return null; 83 | } 84 | 85 | @Override 86 | public long getItemId(int position) { 87 | return 0; 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/java/net/darkion/gallerygridview/GridViewItem.java: -------------------------------------------------------------------------------- 1 | package net.darkion.gallerygridview; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.ColorDrawable; 5 | import android.util.AttributeSet; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.widget.FrameLayout; 9 | import android.widget.ImageView; 10 | 11 | /** 12 | * Created by DarkionAvey 13 | */ 14 | public class GridViewItem extends FrameLayout { 15 | public GridViewItem(Context context) { 16 | super(context); 17 | 18 | } 19 | 20 | public GridViewItem(Context context, AttributeSet attrs) { 21 | super(context, attrs); 22 | 23 | } 24 | 25 | public GridViewItem(Context context, AttributeSet attrs, int defStyleAttr) { 26 | super(context, attrs, defStyleAttr); 27 | } 28 | 29 | boolean init = false; 30 | ImageView checkBox; 31 | 32 | @Override 33 | protected void onFinishInflate() { 34 | super.onFinishInflate(); 35 | init(); 36 | } 37 | 38 | private void init() { 39 | if (init) return; 40 | init = true; 41 | setBackground(new ColorDrawable(getResources().getColor(R.color.gridCheckedBackground))); 42 | 43 | checkBox = (ImageView) LayoutInflater.from(getContext()).inflate(R.layout.grid_view_item_checkbox, this, false); 44 | addView(checkBox); 45 | checkBox.setScaleX(0f); 46 | checkBox.setScaleY(0f); 47 | checkBox.setTranslationY(UNCHECKED_TRANSLATION); 48 | checkBox.setTranslationX(UNCHECKED_TRANSLATION); 49 | 50 | } 51 | 52 | public boolean isChecked() { 53 | return isChecked; 54 | } 55 | 56 | private boolean isChecked = false; 57 | private float CHECKED_SCALE = 0.85f; 58 | private int ANIMATION_DURATION = 200; 59 | private final float UNCHECKED_TRANSLATION = -50f; 60 | public static final LogDecelerateInterpolator interpolator = new LogDecelerateInterpolator(60, 0); 61 | 62 | public void toggle() { 63 | setChecked(!isChecked(), true); 64 | } 65 | 66 | @Override 67 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 68 | super.onMeasure(widthMeasureSpec, widthMeasureSpec); 69 | } 70 | 71 | public void setChecked(boolean checked, boolean animated) { 72 | if (checked == isChecked) return; 73 | View image = getChildAt(0); 74 | 75 | if (checked) { 76 | if(animated) { 77 | image.animate().setInterpolator(interpolator).scaleY(CHECKED_SCALE).scaleX(CHECKED_SCALE).setDuration(ANIMATION_DURATION).start(); 78 | checkBox.animate().scaleX(1f).setInterpolator(interpolator).setDuration(ANIMATION_DURATION).scaleY(1f).translationY(1f).translationX(1f).start(); 79 | } 80 | else { 81 | image.setScaleX(CHECKED_SCALE); 82 | image.setScaleY(CHECKED_SCALE); 83 | checkBox.setScaleY(1f); 84 | checkBox.setScaleX(1f); 85 | checkBox.setTranslationY(1f); 86 | checkBox.setTranslationX(1f); 87 | } 88 | } else { 89 | if(animated) { 90 | image.animate().scaleY(1f).setInterpolator(interpolator).scaleX(1f).setDuration(ANIMATION_DURATION).start(); 91 | checkBox.animate().scaleX(0f).scaleY(0f).setInterpolator(interpolator).setDuration(ANIMATION_DURATION).translationY(UNCHECKED_TRANSLATION).translationX(UNCHECKED_TRANSLATION).start(); 92 | } 93 | else { 94 | image.setScaleX(1f); 95 | image.setScaleY(1f); 96 | checkBox.setScaleY(0f); 97 | checkBox.setScaleX(0f); 98 | checkBox.setTranslationY(UNCHECKED_TRANSLATION); 99 | checkBox.setTranslationX(UNCHECKED_TRANSLATION); 100 | } 101 | } 102 | //checkBox.setChecked(checked); 103 | 104 | isChecked = checked; 105 | 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/net/darkion/gallerygridview/ImageButtonWithListener.java: -------------------------------------------------------------------------------- 1 | package net.darkion.gallerygridview; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.content.Context; 6 | import android.graphics.drawable.Drawable; 7 | import android.util.AttributeSet; 8 | import android.widget.ImageButton; 9 | 10 | /** 11 | * Created by DarkionAvey 12 | */ 13 | 14 | public class ImageButtonWithListener extends ImageButton { 15 | OnClickListener onClickListener; 16 | 17 | public void setDrawable(final Drawable img) { 18 | animate().scaleX(0f).scaleY(0f).setDuration(100).alpha(0f).setListener(new AnimatorListenerAdapter() { 19 | @Override 20 | public void onAnimationEnd(Animator animation) { 21 | super.onAnimationEnd(animation); 22 | setImageDrawable(img); 23 | animate().alpha(1f).scaleX(1f).scaleY(1f).start(); 24 | } 25 | }).start(); 26 | } 27 | 28 | @Override 29 | public void setImageDrawable(Drawable drawable) { 30 | super.setImageDrawable(drawable); 31 | } 32 | 33 | public OnClickListener getOnClickListener() { 34 | return onClickListener; 35 | } 36 | 37 | @Override 38 | public void setOnClickListener(OnClickListener l) { 39 | super.setOnClickListener(l); 40 | this.onClickListener = l; 41 | } 42 | 43 | public ImageButtonWithListener(Context context) { 44 | super(context); 45 | } 46 | 47 | public ImageButtonWithListener(Context context, AttributeSet attrs) { 48 | super(context, attrs); 49 | } 50 | 51 | public ImageButtonWithListener(Context context, AttributeSet attrs, int defStyleAttr) { 52 | super(context, attrs, defStyleAttr); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/net/darkion/gallerygridview/LogDecelerateInterpolator.java: -------------------------------------------------------------------------------- 1 | package net.darkion.gallerygridview; 2 | 3 | import android.animation.TimeInterpolator; 4 | 5 | 6 | 7 | public class LogDecelerateInterpolator implements TimeInterpolator { 8 | 9 | int mBase; 10 | int mDrift; 11 | final float mLogScale; 12 | 13 | public LogDecelerateInterpolator(int base, int drift) { 14 | mBase = base; 15 | mDrift = drift; 16 | 17 | mLogScale = 1f / computeLog(1, mBase, mDrift); 18 | } 19 | 20 | static float computeLog(float t, int base, int drift) { 21 | return (float) -Math.pow(base, -t) + 1 + (drift * t); 22 | } 23 | 24 | @Override 25 | public float getInterpolation(float t) { 26 | return computeLog(t, mBase, mDrift) * mLogScale; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/net/darkion/gallerygridview/MainActivity.java: -------------------------------------------------------------------------------- 1 | package net.darkion.gallerygridview; 2 | 3 | import android.animation.ValueAnimator; 4 | import android.app.Activity; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | import android.view.View.OnClickListener; 8 | import android.widget.TextView; 9 | 10 | import net.darkion.gallerygridview.MultiSelectGridView.SelectionListener; 11 | 12 | import java.util.ArrayList; 13 | 14 | import static net.darkion.gallerygridview.FlyOutMenu.FlyOutMenus; 15 | import static net.darkion.gallerygridview.R.id.delete; 16 | import static net.darkion.gallerygridview.R.id.share; 17 | 18 | /** 19 | * Created by DarkionAvey 20 | */ 21 | public class MainActivity extends Activity { 22 | FlyOutMenu flyOut; 23 | MultiSelectGridView gridView; 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_main); 29 | gridView = (MultiSelectGridView) findViewById(R.id.gridView); 30 | gridView.setAdapter(new GridAdapter(getApplicationContext(), 100)); 31 | flyOut = (FlyOutMenu) findViewById(R.id.contextFlyOut); 32 | final int gridPaddingBottom = getResources().getDimensionPixelOffset(R.dimen.grid_padding_bottom); 33 | final TextView selectionCount = (TextView) findViewById(R.id.selectionCount); 34 | final View cancelSelection = findViewById(R.id.cancelSelection); 35 | cancelSelection.setOnClickListener(new OnClickListener() { 36 | @Override 37 | public void onClick(View v) { 38 | gridView.cancelSelection(); 39 | } 40 | }); 41 | flyOut.setOnClickListener(new OnClickListener() { 42 | @Override 43 | public void onClick(View v) { 44 | } 45 | }); 46 | flyOut.findViewById(delete).setOnClickListener(new OnClickListener() { 47 | @Override 48 | public void onClick(View v) { 49 | flyOut.setFlyOutMenu(v.getId(), FlyOutMenus.DELETE); 50 | } 51 | }); 52 | flyOut.findViewById(share).setOnClickListener(new OnClickListener() { 53 | @Override 54 | public void onClick(View v) { 55 | 56 | } 57 | }); 58 | 59 | 60 | flyOut.setTranslationY(200); 61 | gridView.setSelectionListener(new SelectionListener() { 62 | @Override 63 | public void onSelected(ArrayList selection) { 64 | selectionCount.setText(String.valueOf(selection.size())); 65 | } 66 | 67 | @Override 68 | public void onStartedSelection() { 69 | flyOut.show(); 70 | ValueAnimator animator = ValueAnimator.ofInt(0, gridPaddingBottom); 71 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 72 | @Override 73 | public void onAnimationUpdate(ValueAnimator animation) { 74 | gridView.setPadding(gridView.getPaddingLeft(), gridView.getPaddingTop(), gridView.getPaddingRight(), (int) animation.getAnimatedValue()); 75 | } 76 | }); 77 | 78 | animator.setInterpolator(GridViewItem.interpolator); 79 | animator.start(); 80 | } 81 | 82 | @Override 83 | public void onDoneSelection() { 84 | flyOut.reset(); 85 | ValueAnimator animator = ValueAnimator.ofInt(gridPaddingBottom, 0); 86 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 87 | @Override 88 | public void onAnimationUpdate(ValueAnimator animation) { 89 | gridView.setPadding(gridView.getPaddingLeft(), gridView.getPaddingTop(), gridView.getPaddingRight(), (int) animation.getAnimatedValue()); 90 | } 91 | }); 92 | 93 | animator.setInterpolator(GridViewItem.interpolator); 94 | animator.start(); 95 | 96 | } 97 | }); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/java/net/darkion/gallerygridview/MultiSelectGridView.java: -------------------------------------------------------------------------------- 1 | package net.darkion.gallerygridview; 2 | 3 | import android.content.Context; 4 | import android.graphics.Point; 5 | import android.graphics.Rect; 6 | import android.util.AttributeSet; 7 | import android.view.Display; 8 | import android.view.GestureDetector; 9 | import android.view.MotionEvent; 10 | import android.view.WindowManager; 11 | import android.widget.GridView; 12 | 13 | import java.util.ArrayList; 14 | 15 | /** 16 | * Created by DarkionAvey 17 | */ 18 | 19 | public class MultiSelectGridView extends GridView { 20 | 21 | public void cancelSelection() { 22 | for (int i = 0; i < getAdapter().getCount(); i++) { 23 | if (findViewWithTag(i) != null) 24 | ((GridViewItem) findViewWithTag(i)).setChecked(false, true); 25 | notifyCheckedItem(i, false); 26 | dispatchListeners(); 27 | 28 | } 29 | } 30 | 31 | private void dispatchListeners() { 32 | selectedItemsCount = getCheckedItemsNumber(); 33 | selectionMode = (selectedItemsCount > 0); 34 | 35 | if (selectionListener != null) { 36 | 37 | if (selectedItemsCount == 0 && !touchDown) { 38 | selectionListener.onDoneSelection(); 39 | dispatchedStartSelection = false; 40 | } else if (dispatchedStartSelection) selectionListener.onSelected(getCheckedItems()); 41 | else { 42 | selectionListener.onStartedSelection(); 43 | dispatchedStartSelection = true; 44 | } 45 | } 46 | 47 | } 48 | 49 | public MultiSelectGridView(Context context) { 50 | super(context); 51 | } 52 | 53 | public MultiSelectGridView(Context context, AttributeSet attrs) { 54 | super(context, attrs); 55 | } 56 | 57 | 58 | public MultiSelectGridView(Context context, AttributeSet attrs, int defStyleAttr) { 59 | super(context, attrs, defStyleAttr); 60 | 61 | initRects(); 62 | 63 | } 64 | 65 | private void initRects() { 66 | Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); 67 | Point size = new Point(); 68 | display.getSize(size); 69 | displayHeight = size.y; 70 | hotSpotTop = new Rect(0, 0, size.x, 100); 71 | hotSpotBottom = new Rect(0, displayHeight - 100, size.x, displayHeight); 72 | } 73 | 74 | Rect hotSpotTop, hotSpotBottom; 75 | 76 | int displayHeight; 77 | int mPosition; 78 | 79 | private boolean getCheckedItem(int position) { 80 | return ((GridAdapter) getAdapter()).getCheckedItem(position); 81 | } 82 | 83 | public ArrayList getCheckedItems() { 84 | 85 | return ((GridAdapter) getAdapter()).getCheckedItems(); 86 | } 87 | 88 | private int getCheckedItemsNumber() { 89 | 90 | return ((GridAdapter) getAdapter()).getCheckedItemsNumber(); 91 | } 92 | 93 | boolean dispatchedStartSelection = false; 94 | 95 | private void notifyCheckedItem(int position, boolean checked) { 96 | dispatchListeners(); 97 | ((GridAdapter) getAdapter()).setCheckedItem(position, checked); 98 | } 99 | 100 | boolean touchDown = false; 101 | 102 | class GridGesture extends GestureDetector.SimpleOnGestureListener { 103 | GridGesture() { 104 | super(); 105 | } 106 | 107 | 108 | @Override 109 | public boolean onSingleTapUp(MotionEvent e) { 110 | if (selectionMode && !touchDown) { 111 | 112 | int x = (int) e.getX(); 113 | int y = (int) e.getY(); 114 | int position = pointToPosition(x, y); 115 | if (position != GridView.INVALID_POSITION) { 116 | mPosition = position; 117 | //don't check mPosition == position because by the time onSingleTapUp is called, they're already equal 118 | 119 | GridViewItem cellView = (GridViewItem) findViewWithTag(position); 120 | cellView.setChecked(!cellView.isChecked(), true); 121 | notifyCheckedItem(position, cellView.isChecked()); 122 | 123 | } 124 | dispatchListeners(); 125 | return true; 126 | } 127 | return false; 128 | 129 | } 130 | 131 | 132 | @Override 133 | public void onLongPress(MotionEvent e) { 134 | shouldSelect = true; 135 | 136 | 137 | touchDown = true; 138 | int x = (int) e.getX(); 139 | int y = (int) e.getY(); 140 | int position = pointToPosition(x, y); 141 | //don't check mPosition == position because by the time onLongPress is called, they're already equal 142 | if (position != GridView.INVALID_POSITION) { 143 | mPosition = position; 144 | GridViewItem cellView = (GridViewItem) findViewWithTag(position); 145 | cellView.setChecked(!cellView.isChecked(), true); 146 | notifyCheckedItem(position, cellView.isChecked()); 147 | 148 | } 149 | dispatchListeners(); 150 | } 151 | 152 | 153 | } 154 | 155 | 156 | GestureDetector detector; 157 | int selectedItemsCount = 0; 158 | boolean checkedCycle = true, selectionMode = false, isScrolling = false, shouldSelect = false; 159 | boolean startingImageStatus = false; 160 | 161 | float previousY, previousX; 162 | 163 | enum HorizontalDirection { 164 | LEFT, RIGHT 165 | } 166 | 167 | enum VerticalDirection { 168 | DOWN, UP 169 | } 170 | 171 | public void setSelectionListener(SelectionListener selectionListener) { 172 | this.selectionListener = selectionListener; 173 | } 174 | 175 | VerticalDirection verticalDraggingDirection; 176 | HorizontalDirection horizontalDraggingDirection; 177 | int draggingDirectionThreshold = 20; 178 | SelectionListener selectionListener; 179 | 180 | interface SelectionListener { 181 | void onSelected(ArrayList selection); 182 | 183 | void onStartedSelection(); 184 | 185 | void onDoneSelection(); 186 | } 187 | 188 | public void resetFlyOutListener() { 189 | dispatchedStartSelection = false; 190 | } 191 | 192 | @Override 193 | public boolean onTouchEvent(MotionEvent event) { 194 | if (detector == null) detector = new GestureDetector(getContext(), new GridGesture()); 195 | 196 | switch (event.getAction()) { 197 | case MotionEvent.ACTION_UP: 198 | touchDown = false; 199 | 200 | dispatchListeners(); 201 | checkedCycle = !checkedCycle; 202 | startingImageStatus = false; 203 | isScrolling = false; 204 | shouldSelect = false; 205 | verticalDraggingDirection = null; 206 | horizontalDraggingDirection = null; 207 | 208 | break; 209 | 210 | 211 | case MotionEvent.ACTION_DOWN: 212 | int x = (int) event.getX(); 213 | int y = (int) event.getY(); 214 | int position = pointToPosition(x, y); 215 | if (position != GridView.INVALID_POSITION && mPosition != position) { 216 | mPosition = position; 217 | GridViewItem cellView = (GridViewItem) findViewWithTag(position); 218 | startingImageStatus = cellView.isChecked(); 219 | 220 | } 221 | previousY = y; 222 | previousX = x; 223 | touchDown = true; 224 | break; 225 | 226 | 227 | } 228 | 229 | 230 | if (!detector.onTouchEvent(event) && shouldSelect) { 231 | switch (event.getAction()) { 232 | 233 | case MotionEvent.ACTION_MOVE: 234 | int x = (int) event.getX(); 235 | int y = (int) event.getY(); 236 | if (hotSpotTop == null || hotSpotBottom == null) initRects(); 237 | if (hotSpotTop.contains(x, y)) { 238 | scrollListBy(-10); 239 | } else if (hotSpotBottom.contains(x, y)) { 240 | scrollListBy(10); 241 | } 242 | if (verticalDraggingDirection == null && Math.abs(y - previousY) >= draggingDirectionThreshold) 243 | if (y < previousY) { 244 | verticalDraggingDirection = VerticalDirection.UP; 245 | } else verticalDraggingDirection = VerticalDirection.DOWN; 246 | 247 | if (horizontalDraggingDirection == null && Math.abs(x - previousX) >= 10) 248 | if (x < previousX) { 249 | horizontalDraggingDirection = HorizontalDirection.LEFT; 250 | } else horizontalDraggingDirection = HorizontalDirection.RIGHT; 251 | 252 | 253 | int position = pointToPosition(x, y); 254 | 255 | if (position != GridView.INVALID_POSITION && mPosition != position) { 256 | 257 | int multiplier = position > mPosition ? 1 : -1; 258 | int delta = Math.abs(mPosition - position); 259 | 260 | for (int i = 0; i <= delta; i++) { 261 | int viewTag = mPosition + (i * multiplier); 262 | GridViewItem cellView = (GridViewItem) findViewWithTag(viewTag); 263 | if (cellView != null) { 264 | 265 | if (verticalDraggingDirection == null) { 266 | if (horizontalDraggingDirection == HorizontalDirection.RIGHT) 267 | cellView.setChecked(position > mPosition != startingImageStatus, true); 268 | else if (horizontalDraggingDirection == HorizontalDirection.LEFT) { 269 | cellView.setChecked(position < mPosition != startingImageStatus, true); 270 | } 271 | } else { 272 | if (verticalDraggingDirection == VerticalDirection.DOWN) 273 | cellView.setChecked(position > mPosition != startingImageStatus, true); 274 | else if (verticalDraggingDirection == VerticalDirection.UP) { 275 | cellView.setChecked(position < mPosition != startingImageStatus, true); 276 | } 277 | } 278 | notifyCheckedItem(viewTag, cellView.isChecked()); 279 | } 280 | 281 | } 282 | mPosition = position; 283 | 284 | 285 | } 286 | return true; 287 | } 288 | 289 | } 290 | return super.onTouchEvent(event); 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/card.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/circle_check.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_cancel.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_rotate_left.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_rotate_right.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_share.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | 22 | 33 | 34 | 42 | 43 | 50 | 51 | 52 | 53 | 60 | 61 | 62 | 69 | 70 | 77 | 78 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /app/src/main/res/layout/flyout_two_buttons_options.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 |