Introduction
In this tutorial, we'll explore how to build a photo deletion utility for Android that mimics the swipe-to-delete functionality found in apps like Sponge. This project will teach you how to work with Android's touch handling, RecyclerView components, and media management APIs to create an efficient photo cleanup solution.
While the original article mentions a free app that makes photo deletion easy, we'll create our own implementation that demonstrates the core concepts behind such functionality.
Prerequisites
Before diving into this tutorial, you should have:
- Basic understanding of Android development with Java or Kotlin
- Android Studio installed and configured
- Familiarity with RecyclerView and Adapter patterns
- Experience with Android's MediaStore API
- Android device or emulator with sample photos
Step-by-Step Instructions
1. Create a new Android project
Start by creating a new Android project in Android Studio with an Empty Activity. Name it "PhotoSwipeDeleter" and ensure the minimum SDK is set to API 21 or higher.
2. Add required dependencies
Open your app-level build.gradle file and add the necessary dependencies:
dependencies {
implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.github.bumptech.glide:glide:4.15.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.6.2'
implementation 'androidx.lifecycle:lifecycle-livedata:2.6.2'
}
Why: We need RecyclerView for displaying photos, Glide for image loading, and Lifecycle components for proper memory management.
3. Create the photo model class
Create a new Java class called Photo to represent each photo in our gallery:
public class Photo {
private String id;
private String path;
private long dateTaken;
private boolean isDeleted;
public Photo(String id, String path, long dateTaken) {
this.id = id;
this.path = path;
this.dateTaken = dateTaken;
this.isDeleted = false;
}
// Getters and setters
public String getId() { return id; }
public String getPath() { return path; }
public long getDateTaken() { return dateTaken; }
public boolean isDeleted() { return isDeleted; }
public void setDeleted(boolean deleted) { isDeleted = deleted; }
}
Why: This model class will help us track photo information and deletion status throughout our application.
4. Design the photo item layout
Create a new layout file item_photo.xml in the res/layout folder:
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_margin="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="centerCrop"
/>
<TextView
android:id="@+id/dateTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:textSize="12sp"
android:textColor="#666666"
/>
</LinearLayout>
</androidx.cardview.widget.CardView>
Why: This layout defines how each photo will appear in our RecyclerView, including the image and date information.
5. Create the RecyclerView adapter
Create a new class PhotoAdapter that extends RecyclerView.Adapter:
public class PhotoAdapter extends RecyclerView.Adapter {
private List photos;
private OnPhotoSwipeListener swipeListener;
public PhotoAdapter(List photos) {
this.photos = photos;
}
public void setSwipeListener(OnPhotoSwipeListener listener) {
this.swipeListener = listener;
}
@NonNull
@Override
public PhotoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_photo, parent, false);
return new PhotoViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull PhotoViewHolder holder, int position) {
Photo photo = photos.get(position);
holder.bind(photo);
// Set up swipe gesture detection
holder.itemView.setOnTouchListener(new View.OnTouchListener() {
private float startX;
private float startY;
private boolean isSwiping = false;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = event.getX();
startY = event.getY();
isSwiping = false;
break;
case MotionEvent.ACTION_MOVE:
if (!isSwiping) {
float deltaX = event.getX() - startX;
float deltaY = event.getY() - startY;
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 50) {
isSwiping = true;
if (deltaX > 0) {
// Swipe right - undo
holder.itemView.animate().translationX(0).setDuration(200);
} else {
// Swipe left - delete
holder.itemView.animate().translationX(-holder.itemView.getWidth())
.setDuration(200)
.withEndAction(() -> {
if (swipeListener != null) {
swipeListener.onPhotoDeleted(position);
}
});
}
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (isSwiping) {
holder.itemView.animate().translationX(0).setDuration(200);
}
break;
}
return true;
}
});
}
@Override
public int getItemCount() {
return photos.size();
}
public void removePhoto(int position) {
photos.remove(position);
notifyItemRemoved(position);
}
static class PhotoViewHolder extends RecyclerView.ViewHolder {
ImageView imageView;
TextView dateTextView;
public PhotoViewHolder(@NonNull View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.imageView);
dateTextView = itemView.findViewById(R.id.dateTextView);
}
public void bind(Photo photo) {
// Load image using Glide
Glide.with(itemView.getContext())
.load(photo.getPath())
.placeholder(R.drawable.placeholder_image)
.into(imageView);
// Format date
SimpleDateFormat sdf = new SimpleDateFormat("MMM dd, yyyy", Locale.getDefault());
dateTextView.setText(sdf.format(new Date(photo.getDateTaken())));
}
}
public interface OnPhotoSwipeListener {
void onPhotoDeleted(int position);
}
}
Why: This adapter handles the swipe gestures and photo display, making it easy to delete photos with a simple swipe motion.
6. Implement the main activity
In your MainActivity.java, set up the RecyclerView and load photos:
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private PhotoAdapter adapter;
private List photoList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
loadPhotos();
setupRecyclerView();
}
private void initViews() {
recyclerView = findViewById(R.id.recyclerView);
photoList = new ArrayList<>();
}
private void loadPhotos() {
// Query MediaStore for photos
Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
String[] projection = {
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DATA,
MediaStore.Images.Media.DATE_TAKEN
};
Cursor cursor = getContentResolver().query(
uri, projection, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
String id = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media._ID));
String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
long dateTaken = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media.DATE_TAKEN));
photoList.add(new Photo(id, path, dateTaken));
}
cursor.close();
}
}
private void setupRecyclerView() {
adapter = new PhotoAdapter(photoList);
adapter.setSwipeListener(new PhotoAdapter.OnPhotoSwipeListener() {
@Override
public void onPhotoDeleted(int position) {
deletePhoto(position);
}
});
recyclerView.setLayoutManager(new GridLayoutManager(this, 2));
recyclerView.setAdapter(adapter);
}
private void deletePhoto(int position) {
Photo photo = photoList.get(position);
File file = new File(photo.getPath());
if (file.exists()) {
boolean deleted = file.delete();
if (deleted) {
// Remove from list and update UI
photoList.remove(position);
adapter.notifyItemRemoved(position);
Toast.makeText(this, "Photo deleted", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Failed to delete photo", Toast.LENGTH_SHORT).show();
}
}
}
}
Why: This activity coordinates the photo loading, RecyclerView setup, and deletion functionality, creating the core swipe-to-delete experience.
7. Update the main layout
Modify your activity_main.xml to include the RecyclerView:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
Why: This layout provides a full-screen RecyclerView container for our photo gallery.
Summary
In this tutorial, we've built a photo deletion utility that mimics the swipe-to-delete functionality of apps like Sponge. We created a RecyclerView-based gallery that allows users to swipe left on photos to delete them, implemented touch gesture detection for swipe recognition, and connected to Android's MediaStore API to access and delete photos. This project demonstrates core Android development concepts including RecyclerView adapters, touch event handling, and media management APIs.
The key learning points include understanding how to implement swipe gestures for user interactions, managing photo data with custom models, and working with Android's media storage system. This foundation can be extended with features like undo functionality, batch deletion, or cloud backup integration.



